Compare commits

...
2 Commits
44 changed files with 2072 additions and 1 deletions
@@ -0,0 +1,561 @@
<title>Radiografía Marmotech</title>
<meta name="viewport" content="width=device-width, initial-scale=1">
<link rel="preconnect" href="https://fonts.googleapis.com">
<link rel="preconnect" href="https://fonts.gstatic.com" crossorigin>
<link rel="stylesheet" href="https://fonts.googleapis.com/css2?family=Fraunces:opsz,wght@9..144,400;9..144,500;9..144,600;9..144,700&family=IBM+Plex+Sans:wght@400;500;600;700&family=IBM+Plex+Mono:wght@400;500;600&display=swap">
<style>
:root{
--bg:#f2efe9; --surface:#fbfaf7; --surface-2:#f5f2ea;
--ink:#231f18; --muted:#726c60; --faint:#9a9284;
--line:#e4dfd4; --line-2:#efe9dd;
--accent:#9a6a2b; --accent-soft:#e9dcc4;
--pos:#2f7d57; --neg:#b3402a; --warn:#b1832a;
--grid:#eae4d8;
--shadow:0 1px 2px rgba(40,33,20,.05),0 8px 24px -12px rgba(40,33,20,.14);
--radius:14px;
}
@media (prefers-color-scheme:dark){
:root:not([data-theme="light"]){
--bg:#17150f; --surface:#211e16; --surface-2:#26221a;
--ink:#ece6da; --muted:#a69d8c; --faint:#7c7361;
--line:#332e24; --line-2:#2b271e;
--accent:#cd9c56; --accent-soft:#3a3020;
--pos:#4fb07f; --neg:#e0674e; --warn:#d5a34a;
--grid:#2a261d;
--shadow:0 1px 2px rgba(0,0,0,.4),0 10px 30px -14px rgba(0,0,0,.6);
}
}
:root[data-theme="dark"]{
--bg:#17150f; --surface:#211e16; --surface-2:#26221a;
--ink:#ece6da; --muted:#a69d8c; --faint:#7c7361;
--line:#332e24; --line-2:#2b271e;
--accent:#cd9c56; --accent-soft:#3a3020;
--pos:#4fb07f; --neg:#e0674e; --warn:#d5a34a;
--grid:#2a261d;
--shadow:0 1px 2px rgba(0,0,0,.4),0 10px 30px -14px rgba(0,0,0,.6);
}
*{box-sizing:border-box}
html,body{margin:0}
body{
background:var(--bg); color:var(--ink);
font-family:"IBM Plex Sans",system-ui,sans-serif;
font-size:15px; line-height:1.5;
-webkit-font-smoothing:antialiased;
}
.wrap{max-width:1220px; margin:0 auto; padding:22px 20px 60px}
.num{font-variant-numeric:tabular-nums; font-feature-settings:"tnum" 1}
/* Header */
header.top{display:flex; align-items:flex-end; justify-content:space-between; gap:20px; flex-wrap:wrap; margin-bottom:6px}
.brand{display:flex; align-items:center; gap:14px}
.mark{width:44px; height:44px; flex:0 0 auto}
.brand h1{font-family:"Fraunces",serif; font-weight:600; font-size:26px; letter-spacing:-.01em; margin:0; line-height:1.05}
.brand .sub{color:var(--muted); font-size:12.5px; letter-spacing:.14em; text-transform:uppercase; margin-top:3px}
.period{text-align:right}
.period .lbl{font-size:11px; letter-spacing:.16em; text-transform:uppercase; color:var(--faint)}
.period .val{font-family:"Fraunces",serif; font-size:19px; font-weight:500}
.period .note{font-size:11.5px; color:var(--muted)}
.rule{height:1px; background:linear-gradient(90deg,var(--accent),var(--line) 60%,transparent); margin:16px 0 18px; opacity:.7}
/* Tabs */
nav.tabs{display:flex; gap:4px; margin-bottom:20px; border-bottom:1px solid var(--line); flex-wrap:wrap}
nav.tabs button{
appearance:none; border:0; background:none; cursor:pointer; color:var(--muted);
font-family:inherit; font-size:14px; font-weight:500; padding:10px 15px; border-radius:8px 8px 0 0;
border-bottom:2px solid transparent; margin-bottom:-1px; display:flex; align-items:center; gap:8px;
}
nav.tabs button .idx{font-family:"IBM Plex Mono",monospace; font-size:11px; color:var(--faint)}
nav.tabs button:hover{color:var(--ink); background:var(--surface-2)}
nav.tabs button[aria-selected="true"]{color:var(--ink); border-bottom-color:var(--accent)}
nav.tabs button[aria-selected="true"] .idx{color:var(--accent)}
:focus-visible{outline:2px solid var(--accent); outline-offset:2px}
.page{display:none; animation:fade .35s ease}
.page.active{display:block}
@keyframes fade{from{opacity:0; transform:translateY(4px)}to{opacity:1; transform:none}}
@media (prefers-reduced-motion:reduce){.page,.page.active{animation:none}}
/* Cards & grid */
.grid{display:grid; gap:14px}
.kpis{grid-template-columns:repeat(4,1fr)}
.card{background:var(--surface); border:1px solid var(--line); border-radius:var(--radius); box-shadow:var(--shadow); padding:16px 17px}
.card h3{margin:0 0 2px; font-size:11.5px; letter-spacing:.13em; text-transform:uppercase; color:var(--muted); font-weight:600}
.card .cap{font-size:11.5px; color:var(--faint); margin:0 0 12px}
.kpi .k-val{font-family:"Fraunces",serif; font-weight:600; font-size:29px; letter-spacing:-.01em; line-height:1.05; margin-top:6px}
.kpi .k-val small{font-size:14px; color:var(--muted); font-weight:500; font-family:"IBM Plex Sans"}
.kpi .k-sub{display:flex; align-items:center; gap:8px; margin-top:9px; font-size:12.5px; color:var(--muted)}
.delta{display:inline-flex; align-items:center; gap:4px; font-weight:600; font-variant-numeric:tabular-nums; padding:2px 7px; border-radius:20px; font-size:12px}
.delta.up{color:var(--pos); background:color-mix(in srgb,var(--pos) 12%,transparent)}
.delta.down{color:var(--neg); background:color-mix(in srgb,var(--neg) 12%,transparent)}
.delta.flat{color:var(--warn); background:color-mix(in srgb,var(--warn) 12%,transparent)}
.accent-bar{height:3px; border-radius:3px; background:var(--accent); opacity:.9; margin:-16px -17px 14px; border-radius:var(--radius) var(--radius) 0 0}
.row{display:grid; gap:14px; margin-top:14px}
.r-8-4{grid-template-columns:1.9fr 1.1fr}
.r-6-6{grid-template-columns:1fr 1fr}
.r-7-5{grid-template-columns:1.4fr 1fr}
/* Chart bits */
svg{display:block; width:100%; height:auto; overflow:visible}
.lgd{display:flex; gap:16px; flex-wrap:wrap; font-size:12px; color:var(--muted); margin-top:10px}
.lgd span{display:inline-flex; align-items:center; gap:6px}
.dot{width:10px; height:10px; border-radius:3px; display:inline-block}
table.tbl{width:100%; border-collapse:collapse; font-size:13px}
table.tbl th{text-align:left; font-size:11px; letter-spacing:.08em; text-transform:uppercase; color:var(--faint); font-weight:600; padding:7px 8px; border-bottom:1px solid var(--line)}
table.tbl td{padding:8px; border-bottom:1px solid var(--line-2); font-variant-numeric:tabular-nums}
table.tbl td.r,table.tbl th.r{text-align:right}
table.tbl tr:last-child td{border-bottom:0}
.barcell{position:relative}
.barcell .fill{position:absolute; left:0; top:50%; transform:translateY(-50%); height:60%; border-radius:3px; background:var(--accent-soft)}
.barcell span{position:relative}
.ratio{display:flex; align-items:baseline; gap:10px}
.ratio .big{font-family:"Fraunces",serif; font-weight:600; font-size:34px; letter-spacing:-.02em}
.foot{color:var(--muted); font-size:12px; margin-top:26px; border-top:1px solid var(--line); padding-top:14px}
.foot code{font-family:"IBM Plex Mono",monospace; font-size:11.5px; background:var(--surface-2); padding:1px 5px; border-radius:4px}
.pill{font-size:10.5px; letter-spacing:.1em; text-transform:uppercase; color:var(--accent); background:var(--accent-soft); padding:3px 8px; border-radius:20px; font-weight:600}
@media(max-width:880px){
.kpis{grid-template-columns:repeat(2,1fr)}
.r-8-4,.r-6-6,.r-7-5{grid-template-columns:1fr}
}
</style>
<div class="wrap">
<header class="top">
<div class="brand">
<svg class="mark" viewBox="0 0 44 44" aria-hidden="true">
<rect x="1" y="1" width="42" height="42" rx="9" fill="none" stroke="var(--accent)" stroke-width="1.5"/>
<path d="M8 30 L17 15 L23 24 L29 12 L36 30 Z" fill="none" stroke="var(--accent)" stroke-width="1.6" stroke-linejoin="round"/>
<path d="M8 33 L36 33" stroke="var(--accent)" stroke-width="1.6"/>
<circle cx="29" cy="12" r="1.8" fill="var(--accent)"/>
</svg>
<div>
<h1>Radiografía de la Empresa</h1>
<div class="sub">Marmotech, S.A. &middot; Cuadro de Mando de Presidencia</div>
</div>
</div>
<div class="period">
<div class="lbl">Periodo</div>
<div class="val num">Enero Julio 2026</div>
<div class="note">acumulado del año · comparado vs. 2025</div>
</div>
</header>
<div class="rule"></div>
<nav class="tabs" role="tablist">
<button role="tab" aria-selected="true" data-p="p1"><span class="idx">01</span> Radiografía</button>
<button role="tab" aria-selected="false" data-p="p2"><span class="idx">02</span> Análisis de Gastos</button>
<button role="tab" aria-selected="false" data-p="p3"><span class="idx">03</span> Estructura Financiera</button>
</nav>
<!-- ============ PÁGINA 1 ============ -->
<section class="page active" id="p1" role="tabpanel">
<div class="grid kpis">
<div class="card kpi"><div class="accent-bar"></div>
<h3>Ventas Netas</h3>
<div class="k-val num">RD$ 1,375.3<small> M</small></div>
<div class="k-sub"><span class="delta down">▼ 1.3%</span> vs. 1,393.1 M (2025)</div>
</div>
<div class="card kpi">
<h3>Utilidad Bruta</h3>
<div class="k-val num">RD$ 706.9<small> M</small></div>
<div class="k-sub"><span class="delta down">▼ 1.4 pp</span> margen 51.4% <span style="color:var(--faint)">(52.8%)</span></div>
</div>
<div class="card kpi">
<h3>Utilidad Operativa</h3>
<div class="k-val num">RD$ 292.1<small> M</small></div>
<div class="k-sub"><span class="delta flat">21.2%</span> margen operativo</div>
</div>
<div class="card kpi"><div class="accent-bar"></div>
<h3>Utilidad Neta</h3>
<div class="k-val num">RD$ 219.6<small> M</small></div>
<div class="k-sub"><span class="delta down">▼ 21.4%</span> margen 16.0% <span style="color:var(--faint)">(20.1%)</span></div>
</div>
</div>
<div class="row r-7-5">
<div class="card">
<h3>Estado de Resultados — Cascada</h3>
<p class="cap">De ventas a utilidad neta · acumulado 2026 (RD$ millones)</p>
<svg id="waterfall" viewBox="0 0 720 300" role="img" aria-label="Cascada del estado de resultados"></svg>
</div>
<div class="card">
<h3>Composición de cada RD$ vendido</h3>
<p class="cap">Estructura del peso de ventas · 2026</p>
<svg id="composition" viewBox="0 0 360 300" role="img" aria-label="Composición de ventas"></svg>
</div>
</div>
<div class="row r-7-5">
<div class="card">
<h3>Comportamiento del periodo actual</h3>
<p class="cap">Ventas netas (barras) y utilidad neta (línea) por mes · 2026</p>
<svg id="trend" viewBox="0 0 720 280" role="img" aria-label="Tendencia mensual 2026"></svg>
<div class="lgd">
<span><i class="dot" style="background:var(--accent-soft)"></i> Ventas netas</span>
<span><i class="dot" style="background:var(--accent)"></i> Utilidad neta</span>
<span><i class="dot" style="background:var(--pos)"></i> Margen neto %</span>
</div>
</div>
<div class="card">
<h3>Trayectoria anual</h3>
<p class="cap">Ventas y utilidad neta por año (RD$ M) · 2026 parcial</p>
<svg id="annual" viewBox="0 0 360 280" role="img" aria-label="Comparativo anual"></svg>
</div>
</div>
</section>
<!-- ============ PÁGINA 2 ============ -->
<section class="page" id="p2" role="tabpanel">
<div class="grid kpis">
<div class="card kpi"><div class="accent-bar"></div>
<h3>Gastos Operativos</h3>
<div class="k-val num">RD$ 414.7<small> M</small></div>
<div class="k-sub"><span class="delta down">▲ 7.1%</span> vs. 387.2 M (2025)</div>
</div>
<div class="card kpi">
<h3>Gasto / Ventas</h3>
<div class="k-val num">30.2<small> %</small></div>
<div class="k-sub"><span class="delta down">▲ 2.4 pp</span> vs. 27.8% (2025)</div>
</div>
<div class="card kpi">
<h3>Gerencia mayor peso</h3>
<div class="k-val" style="font-size:22px">Ventas y Mercadeo</div>
<div class="k-sub num">RD$ 184.5 M · 44% del gasto</div>
</div>
<div class="card kpi">
<h3>Mayor alza YoY</h3>
<div class="k-val" style="font-size:22px">Admin. y Materiales</div>
<div class="k-sub num"><span class="delta down">▲ 13.3%</span> +12.3 M</div>
</div>
</div>
<div class="row r-6-6">
<div class="card">
<h3>Gasto por gerencia</h3>
<p class="cap">2026 vs. 2025 · acumulado enejul (RD$ M)</p>
<svg id="ger" viewBox="0 0 380 300" role="img" aria-label="Gasto por gerencia"></svg>
<div class="lgd">
<span><i class="dot" style="background:var(--accent)"></i> 2026</span>
<span><i class="dot" style="background:var(--accent-soft)"></i> 2025</span>
</div>
</div>
<div class="card">
<h3>Naturaleza del gasto</h3>
<p class="cap">Por subgrupo contable · 2026 (RD$ M)</p>
<table class="tbl">
<thead><tr><th>Subgrupo</th><th class="r">Monto</th><th class="r">% ventas</th></tr></thead>
<tbody id="subg"></tbody>
</table>
<p class="cap" style="margin-top:12px">El subgrupo <b>76 Cierre de gastos de fabricación</b> (325.7 M) es una absorción a costo; reduce el gasto neto operativo.</p>
</div>
</div>
<div class="row">
<div class="card">
<h3>Gasto mensual — control de eficiencia</h3>
<p class="cap">Gasto operativo por mes y su relación con las ventas · 2026</p>
<svg id="gtrend" viewBox="0 0 720 250" role="img" aria-label="Gasto mensual"></svg>
</div>
</div>
</section>
<!-- ============ PÁGINA 3 ============ -->
<section class="page" id="p3" role="tabpanel">
<div class="grid kpis">
<div class="card kpi"><div class="accent-bar"></div>
<h3>Activos Totales</h3>
<div class="k-val num">RD$ 6,435<small> M</small></div>
<div class="k-sub">al 31-jul-2026</div>
</div>
<div class="card kpi">
<h3>Razón Corriente</h3>
<div class="k-val num">2.29<small> ×</small></div>
<div class="k-sub"><span class="delta up">sólida</span> activo cte. 3,250 / pasivo cte. 1,420</div>
</div>
<div class="card kpi">
<h3>Capital de Trabajo</h3>
<div class="k-val num">RD$ 1,830<small> M</small></div>
<div class="k-sub">holgura de liquidez operativa</div>
</div>
<div class="card kpi">
<h3>Endeudamiento</h3>
<div class="k-val num">36<small> %</small></div>
<div class="k-sub"><span class="delta up">conservador</span> pasivo / activo</div>
</div>
</div>
<div class="row r-6-6">
<div class="card">
<h3>De dónde viene y dónde está el capital</h3>
<p class="cap">Activos = Pasivos + Patrimonio + resultado del ejercicio (RD$ M)</p>
<svg id="bal" viewBox="0 0 380 300" role="img" aria-label="Estructura del balance"></svg>
</div>
<div class="card">
<h3>Composición del activo</h3>
<p class="cap">al 31-jul-2026 (RD$ M)</p>
<table class="tbl">
<thead><tr><th>Rubro</th><th class="r">Saldo</th></tr></thead>
<tbody id="act"></tbody>
</table>
<h3 style="margin-top:18px">Composición del pasivo</h3>
<table class="tbl">
<thead><tr><th>Rubro</th><th class="r">Saldo</th></tr></thead>
<tbody id="pas"></tbody>
</table>
</div>
</div>
</section>
<div class="foot">
<span class="pill">Prototipo de diseño</span>
&nbsp; Vista previa del tablero de Power BI. Fuente: <code>MarmotechBI.presidencia.vwPLMensual</code>, <code>vwGastosDepto</code>, <code>vwBalanceMensual</code>
reproduce el Mayor General Analítico (cgprrp008) y el Análisis de Gastos (cgprrp009). Cifras validadas contra el sistema al 31-jul-2026.
</div>
</div>
<script>
(function(){
var css=getComputedStyle(document.documentElement);
var C=function(n){return css.getPropertyValue(n).trim()};
function col(n){return C(n)}
var money=function(v){return v.toLocaleString('en-US',{minimumFractionDigits:1,maximumFractionDigits:1})};
/* tabs */
var btns=document.querySelectorAll('nav.tabs button');
btns.forEach(function(b){
b.addEventListener('click',function(){
btns.forEach(function(x){x.setAttribute('aria-selected','false')});
b.setAttribute('aria-selected','true');
document.querySelectorAll('.page').forEach(function(p){p.classList.remove('active')});
document.getElementById(b.dataset.p).classList.add('active');
});
});
var NS='http://www.w3.org/2000/svg';
function el(t,a){var e=document.createElementNS(NS,t);for(var k in a)e.setAttribute(k,a[k]);return e;}
function txt(x,y,s,o){o=o||{};var t=el('text',{x:x,y:y});t.textContent=s;
t.setAttribute('fill',o.fill||col('--muted'));
t.setAttribute('font-size',o.fs||11);
t.setAttribute('font-family',o.mono?'"IBM Plex Mono",monospace':'"IBM Plex Sans",sans-serif');
t.setAttribute('font-weight',o.fw||400);
if(o.anchor)t.setAttribute('text-anchor',o.anchor);
t.setAttribute('font-variant-numeric','tabular-nums');
return t;}
/* ---------- WATERFALL ---------- */
(function(){
var svg=document.getElementById('waterfall'); if(!svg)return;
var W=720,H=300,padL=8,padR=8,base=250,top=20;
var steps=[
{l:'Ventas',v:1375.3,type:'start'},
{l:'Costo',v:-668.4,type:'neg'},
{l:'Ut. Bruta',v:706.9,type:'sub'},
{l:'Gastos',v:-414.7,type:'neg'},
{l:'Ut. Operat.',v:292.1,type:'sub'},
{l:'Otros/ISR',v:-72.5,type:'neg'},
{l:'Ut. Neta',v:219.6,type:'end'}
];
var max=1375.3, sc=(base-top)/max;
var n=steps.length, gap=14, bw=(W-padL-padR-gap*(n-1))/n;
var run=0;
steps.forEach(function(s,i){
var x=padL+i*(bw+gap), h, y, fill;
if(s.type==='start'||s.type==='sub'||s.type==='end'){
h=s.v*sc; y=base-h; run=s.v;
fill = s.type==='end'?col('--accent') : (s.type==='start'?col('--accent'):col('--ink'));
if(s.type==='sub')fill=col('--faint');
}else{
h=Math.abs(s.v)*sc; y=base-(run*sc); run=run+s.v;
fill=col('--neg');
}
svg.appendChild(el('rect',{x:x,y:y,width:bw,height:Math.max(2,h),rx:3,fill:fill,opacity:s.type==='sub'?0.5:0.92}));
svg.appendChild(txt(x+bw/2,base+16,s.l,{anchor:'middle',fs:10.5,fill:col('--muted')}));
svg.appendChild(txt(x+bw/2,y-6,(s.v>0?'':'')+money(Math.abs(s.v)),{anchor:'middle',fs:11,mono:true,fw:600,fill:col('--ink')}));
});
})();
/* ---------- COMPOSITION (stacked bar of one peso) ---------- */
(function(){
var svg=document.getElementById('composition'); if(!svg)return;
var parts=[
{l:'Costo de ventas',p:48.6,c:col('--neg')},
{l:'Gastos operativos',p:30.2,c:col('--warn')},
{l:'Otros / ISR',p:5.3,c:col('--faint')},
{l:'Utilidad neta',p:16.0,c:col('--pos')}
];
var x=90,y=24,w=54,H=250,total=100,sc=H/total,cy=y;
parts.forEach(function(p){
var h=p.p*sc;
svg.appendChild(el('rect',{x:x,y:cy,width:w,height:h,fill:p.c,opacity:.9}));
svg.appendChild(txt(x-10,cy+h/2+4,p.p.toFixed(1)+'%',{anchor:'end',mono:true,fs:12,fw:600,fill:col('--ink')}));
svg.appendChild(txt(x+w+10,cy+h/2+4,p.l,{fs:11.5,fill:col('--muted')}));
cy+=h;
});
svg.appendChild(txt(x+w/2,y-8,'RD$ 1.00',{anchor:'middle',fs:10,fill:col('--faint')}));
})();
/* ---------- TREND (bars + line) ---------- */
(function(){
var svg=document.getElementById('trend'); if(!svg)return;
var data=[
{m:'Ene',v:132.4,u:2.5},{m:'Feb',v:169.9,u:7.2},{m:'Mar',v:224.8,u:56.3},
{m:'Abr',v:224.3,u:52.6},{m:'May',v:190.8,u:33.0},{m:'Jun',v:225.0,u:37.8},
{m:'Jul',v:208.1,u:30.2}
];
var W=720,base=230,top=18,padL=34,padR=12;
var max=240, sc=(base-top)/max;
[0,60,120,180,240].forEach(function(g){
var y=base-g*sc;
svg.appendChild(el('line',{x1:padL,y1:y,x2:W-padR,y2:y,stroke:col('--grid'),'stroke-width':1}));
svg.appendChild(txt(padL-6,y+3,g,{anchor:'end',fs:9.5,fill:col('--faint'),mono:true}));
});
var n=data.length, gap=18, bw=(W-padL-padR-gap*(n-1))/n;
var pts=[];
data.forEach(function(d,i){
var x=padL+i*(bw+gap);
var h=d.v*sc;
svg.appendChild(el('rect',{x:x,y:base-h,width:bw,height:h,rx:3,fill:col('--accent-soft')}));
svg.appendChild(txt(x+bw/2,base+15,d.m,{anchor:'middle',fs:10.5}));
svg.appendChild(txt(x+bw/2,base-h-6,d.v.toFixed(0),{anchor:'middle',fs:10,mono:true,fill:col('--muted')}));
pts.push([x+bw/2, base-d.u*sc]);
});
var dpath='M'+pts.map(function(p){return p[0]+','+p[1]}).join(' L');
svg.appendChild(el('path',{d:dpath,fill:'none',stroke:col('--accent'),'stroke-width':2.4,'stroke-linejoin':'round'}));
pts.forEach(function(p){svg.appendChild(el('circle',{cx:p[0],cy:p[1],r:3.4,fill:col('--surface'),stroke:col('--accent'),'stroke-width':2}));});
})();
/* ---------- ANNUAL ---------- */
(function(){
var svg=document.getElementById('annual'); if(!svg)return;
var data=[{y:'2024',v:2401,u:462},{y:'2025',v:2401,u:457},{y:"2026*",v:1375,u:220}];
var W=360,base=230,top=18,padL=30,padR=10;
var max=2500,sc=(base-top)/max;
var n=data.length,gap=40,grp=(W-padL-padR-gap*(n-1))/n, bw=grp/2-3;
data.forEach(function(d,i){
var x=padL+i*(grp+gap);
var h1=d.v*sc,h2=d.u*sc;
svg.appendChild(el('rect',{x:x,y:base-h1,width:bw,height:h1,rx:3,fill:col('--accent-soft')}));
svg.appendChild(el('rect',{x:x+bw+6,y:base-h2,width:bw,height:h2,rx:3,fill:col('--accent')}));
svg.appendChild(txt(x+grp/2,base+15,d.y,{anchor:'middle',fs:11}));
svg.appendChild(txt(x+bw/2,base-h1-5,(d.v/1000).toFixed(2)+'k',{anchor:'middle',fs:9.5,mono:true,fill:col('--muted')}));
svg.appendChild(txt(x+bw+6+bw/2,base-h2-5,d.u,{anchor:'middle',fs:9.5,mono:true,fill:col('--muted')}));
});
svg.appendChild(txt(padL,top-4,'RD$ M',{fs:9.5,fill:col('--faint')}));
})();
/* ---------- GERENCIA (grouped horizontal) ---------- */
(function(){
var svg=document.getElementById('ger'); if(!svg)return;
var data=[
{l:'Ventas y Mercadeo',a:184.5,b:178.3},
{l:'Admin. y Materiales',a:104.8,b:92.5},
{l:'Planta / Producción',a:78.1,b:79.9},
{l:'Gerencia General',a:41.3,b:36.4},
{l:'Planta Agregados',a:4.4,b:0},
{l:'Caribbean',a:1.6,b:0}
];
var W=380,padL=8,padR=44,top=6,rowH=46,x0=padL, maxW=W-padL-padR, max=190, sc=maxW/max;
data.forEach(function(d,i){
var y=top+i*rowH;
svg.appendChild(txt(x0,y+11,d.l,{fs:11,fw:500,fill:col('--ink')}));
svg.appendChild(el('rect',{x:x0,y:y+16,width:Math.max(2,d.a*sc),height:9,rx:2,fill:col('--accent')}));
svg.appendChild(el('rect',{x:x0,y:y+27,width:Math.max(2,d.b*sc),height:9,rx:2,fill:col('--accent-soft')}));
svg.appendChild(txt(x0+d.a*sc+6,y+24,money(d.a),{fs:10,mono:true,fill:col('--muted')}));
});
})();
/* ---------- SUBGRUPO table ---------- */
(function(){
var tb=document.getElementById('subg'); if(!tb)return;
var rows=[
['74 · Otros gastos y servicios',304.1,22.1],
['71 · Compensación empleados',293.8,21.4],
['73 · Gastos fijos',114.7,8.3],
['72 · Otros rel. a empleados',27.8,2.0],
['76 · Cierre fabricación (absorción)',-325.7,-23.7]
];
var max=310;
rows.forEach(function(r){
var tr=document.createElement('tr');
var w=Math.abs(r[1])/max*100;
tr.innerHTML='<td class="barcell"><div class="fill" style="width:'+w+'%;background:'+(r[1]<0?'color-mix(in srgb,var(--neg) 20%,transparent)':'var(--accent-soft)')+'"></div><span>'+r[0]+'</span></td>'+
'<td class="r num" style="font-weight:600;color:'+(r[1]<0?'var(--neg)':'var(--ink)')+'">'+(r[1]<0?'':'')+money(Math.abs(r[1]))+'</td>'+
'<td class="r num" style="color:var(--muted)">'+r[2].toFixed(1)+'%</td>';
tb.appendChild(tr);
});
})();
/* ---------- GASTO TREND ---------- */
(function(){
var svg=document.getElementById('gtrend'); if(!svg)return;
var data=[
{m:'Ene',g:55.1,r:41.6},{m:'Feb',g:54.8,r:32.2},{m:'Mar',g:59.2,r:26.3},
{m:'Abr',g:59.9,r:26.7},{m:'May',g:57.7,r:30.3},{m:'Jun',g:61.5,r:27.3},{m:'Jul',g:66.6,r:32.0}
];
var W=720,base=200,top=16,padL=34,padR=40;
var max=80,sc=(base-top)/max;
[0,20,40,60,80].forEach(function(g){var y=base-g*sc;
svg.appendChild(el('line',{x1:padL,y1:y,x2:W-padR,y2:y,stroke:col('--grid')}));
svg.appendChild(txt(padL-6,y+3,g,{anchor:'end',fs:9.5,mono:true,fill:col('--faint')}));});
var n=data.length,gap=20,bw=(W-padL-padR-gap*(n-1))/n, rpts=[];
data.forEach(function(d,i){
var x=padL+i*(bw+gap),h=d.g*sc;
svg.appendChild(el('rect',{x:x,y:base-h,width:bw,height:h,rx:3,fill:col('--accent')}));
svg.appendChild(txt(x+bw/2,base+15,d.m,{anchor:'middle',fs:10.5}));
svg.appendChild(txt(x+bw/2,base-h-5,d.g.toFixed(0),{anchor:'middle',fs:9.5,mono:true,fill:col('--muted')}));
rpts.push([x+bw/2, base-(d.r/ (80/60) )*sc]); // scale % on right (0-60 mapped)
});
// right axis % (gasto/ventas), scale 0-60 across same height
var rsc=(base-top)/60;
rpts=[];data.forEach(function(d,i){var x=padL+i*(bw+gap);rpts.push([x+bw/2, base-d.r*rsc]);});
var dp='M'+rpts.map(function(p){return p[0]+','+p[1]}).join(' L');
svg.appendChild(el('path',{d:dp,fill:'none',stroke:col('--pos'),'stroke-width':2.2,'stroke-dasharray':'1 0'}));
rpts.forEach(function(p,i){svg.appendChild(el('circle',{cx:p[0],cy:p[1],r:3,fill:col('--surface'),stroke:col('--pos'),'stroke-width':2}));
svg.appendChild(txt(p[0],p[1]-8,data[i].r.toFixed(0)+'%',{anchor:'middle',fs:9,mono:true,fill:col('--pos')}));});
svg.appendChild(txt(padL,top-4,'RD$ M',{fs:9.5,fill:col('--faint')}));
svg.appendChild(txt(W-padR+4,top+4,'% ventas',{fs:9.5,fill:col('--pos')}));
})();
/* ---------- BALANCE stacked ---------- */
(function(){
var svg=document.getElementById('bal'); if(!svg)return;
var W=380,base=250,top=20,bw=88;
var max=6435, sc=(base-top)/max;
// col 1 activos
var xA=60;
svg.appendChild(el('rect',{x:xA,y:base-6435*sc,width:bw,height:6435*sc,rx:4,fill:col('--accent'),opacity:.9}));
svg.appendChild(txt(xA+bw/2,base-6435*sc-8,'6,435',{anchor:'middle',fs:12,mono:true,fw:600,fill:col('--ink')}));
svg.appendChild(txt(xA+bw/2,base+16,'Activos',{anchor:'middle',fs:11}));
// col 2 pasivo+patrimonio+result
var xB=xA+bw+70, parts=[{l:'Pasivos',v:2322,c:col('--neg')},{l:'Patrimonio',v:3894,c:col('--pos')},{l:'Result. ejerc.',v:219,c:col('--warn')}];
var cy=base;
parts.forEach(function(p){var h=p.v*sc;cy-=h;
svg.appendChild(el('rect',{x:xB,y:cy,width:bw,height:h,fill:p.c,opacity:.85}));
svg.appendChild(txt(xB+bw+8,cy+h/2+4,p.l+' '+p.v.toLocaleString('en-US'),{fs:10.5,fill:col('--muted')}));
});
svg.appendChild(txt(xB+bw/2,base+16,'Pas. + Patrim.',{anchor:'middle',fs:11}));
})();
/* ---------- ACTIVO / PASIVO tables ---------- */
(function(){
function fill(id,rows,max){
var tb=document.getElementById(id); if(!tb)return;
rows.forEach(function(r){
var tr=document.createElement('tr');
var neg=r[1]<0;
tr.innerHTML='<td>'+r[0]+'</td><td class="r num" style="color:'+(neg?'var(--neg)':'var(--ink)')+'">'+(neg?'':'')+Math.abs(r[1]).toLocaleString('en-US')+'</td>';
tb.appendChild(tr);
});
}
fill('act',[['Activos corrientes',3250],['Activos fijos',4140],['Depreciación acum.',-1136],['Otros activos',181],['Total activos',6435]]);
fill('pas',[['Pasivos corrientes',1420],['Préstamos largo plazo',902],['Total pasivos',2322],['Patrimonio',3894]]);
// bold totals
['act','pas'].forEach(function(id){var t=document.getElementById(id);var rs=t.querySelectorAll('tr');
rs.forEach(function(tr){if(/Total/.test(tr.textContent)){tr.style.fontWeight='700';tr.querySelectorAll('td').forEach(function(td){td.style.borderTop='1px solid var(--line)';});}});});
})();
})();
</script>
@@ -0,0 +1,192 @@
# Dashboard Ejecutivo de Presidencia — Guía de armado en Power BI
> Radiografía financiera para el Presidente, basada en el **Mayor General Analítico** (`cgprrp008.4gl`) y el **Análisis de Gastos por Departamento** (`cgprrp009.4gl`).
> Datos servidos por la capa BI: **`192.168.40.8 / MarmotechBI`**, esquema **`presidencia`** (vistas creadas por el script `SQL_Vistas_Presidencia.sql`).
> Fecha: 2026-08-26 · Autor: jsoto.
---
## 0. Lo que ya está hecho (capa de datos)
Se crearon 3 vistas en `MarmotechBI` — restringidas a `jsoto` (`DENY SELECT` a `public` + `GRANT` explícito a `MARMOTECH\jsoto` y `jsoto`; el resto del equipo `db_datareader` **no** las ve):
| Vista | Grano | Para qué | Columnas clave |
|---|---|---|---|
| `presidencia.vwPLMensual` | año × mes × cuenta | Estado de Resultados (cgprrp008) | `ano, numero_mes, mes, AnoMes, fecha_mes, grupo, linea, orden_linea, subgrupo_cod, subgrupo_desc, cuenta, cuenta_desc, debito, credito, resultado, costo_gasto` |
| `presidencia.vwGastosDepto` | año × mes × depto × cuenta | Análisis de gastos (cgprrp009) | `…, gerencia_cod, gerencia, departamento, depto_nombre, subgrupo_cod, subgrupo_desc, cuenta, cuenta_desc, debito, credito, gasto` |
| `presidencia.vwBalanceMensual` | año × mes × cuenta | Balance / liquidez | `…, grupo, grupo_desc, subgrupo_cod, subgrupo_desc, cuenta, cuenta_desc, movimiento_mes, saldo_natural, saldo_presentacion` |
**Reglas de negocio ya aplicadas dentro de las vistas** (no hay que replicarlas en DAX):
- Estado de Resultados por **primer dígito** de la cuenta: `4` Ventas · `5` Costo · `7` Gastos · `8` Otros (incl. `88` = ISR).
- **`resultado = credito debito`** → la suma de todas las líneas del P&L = **Utilidad Neta**. Ventas suma positivo; costo, gastos e ISR restan.
- Solo transacciones activas (`status_t IS NULL`).
- Se **excluye el asiento de cierre anual** (`ref` tipo `ED.99-001/12AA`); sin esto, todo año cerrado neteaba a cero.
Validado contra el sistema: julio-2026 reproduce exactamente la imagen del `cgprrp008`.
---
## 1. Conexión y consultas (Power Query / M)
**Obtener datos → SQL Server** · Servidor `192.168.40.8` · Base `MarmotechBI` · Modo **Import** (o DirectQuery si se quiere en vivo).
Traer las 3 vistas del esquema `presidencia`. Ejemplo de la consulta base (las otras dos son idénticas cambiando el nombre):
```m
let
Origen = Sql.Database("192.168.40.8", "MarmotechBI"),
PL = Origen{[Schema="presidencia", Item="vwPLMensual"]}[Data]
in
PL
```
### Tabla de calendario (necesaria para YoY / YTD)
Crear una tabla calculada **DimCalendario** (grano día, contigua) y marcarla como *Tabla de fechas*:
```DAX
DimCalendario =
VAR MinD = DATE(2015,1,1) -- ajustar si se quiere histórico más largo
VAR MaxD = DATE(2028,12,31)
RETURN
ADDCOLUMNS(
CALENDAR(MinD, MaxD),
"Año", YEAR([Date]),
"MesNo", MONTH([Date]),
"Mes", FORMAT([Date],"MMM","es-DO"),
"AñoMes", FORMAT([Date],"yyyy-MM"),
"PrimerDiaMes", DATE(YEAR([Date]),MONTH([Date]),1),
"Trimestre", "T" & FORMAT([Date],"Q")
)
```
> Modelado → seleccionar DimCalendario → **Marcar como tabla de fechas** (columna `Date`).
---
## 2. Relaciones (vista *Modelo*)
Las vistas traen `fecha_mes` = **primer día del mes**. Relacionar **una sola** dirección, Dimensión (1) → Hecho (\*):
| Desde (1) | Columna | Hacia (\*) | Columna |
|---|---|---|---|
| `DimCalendario` | `Date` | `vwPLMensual` | `fecha_mes` |
| `DimCalendario` | `Date` | `vwGastosDepto` | `fecha_mes` |
| `DimCalendario` | `Date` | `vwBalanceMensual` | `fecha_mes` |
Como cada mes tiene un solo `fecha_mes` (día 1), la relación con el calendario diario funciona perfecto para `TOTALYTD` y `SAMEPERIODLASTYEAR`.
Los *slicers* de Año / Mes se ponen sobre **DimCalendario** (`Año`, `Mes`).
---
## 3. Medidas DAX
Crear una tabla de medidas vacía (`_Medidas`) o alojarlas en `vwPLMensual`.
### 3.1 Estado de Resultados (base)
```DAX
Ventas Netas = CALCULATE( SUM(vwPLMensual[resultado]), vwPLMensual[grupo] = "4" )
Costo de Ventas = CALCULATE( SUM(vwPLMensual[costo_gasto]), vwPLMensual[grupo] = "5" )
Gastos Operativos = CALCULATE( SUM(vwPLMensual[costo_gasto]), vwPLMensual[grupo] = "7" )
Otros Ing. y Gastos = CALCULATE( SUM(vwPLMensual[costo_gasto]), vwPLMensual[linea] = "Otros Ingresos y Gastos" )
ISR = CALCULATE( SUM(vwPLMensual[costo_gasto]), vwPLMensual[linea] = "Impuesto sobre la Renta" )
Utilidad Bruta = [Ventas Netas] - [Costo de Ventas]
Utilidad Operativa = [Utilidad Bruta] - [Gastos Operativos]
Utilidad antes ISR = [Utilidad Operativa] - [Otros Ing. y Gastos]
Utilidad Neta = SUM( vwPLMensual[resultado] ) -- suma de todas las líneas
```
### 3.2 Márgenes
```DAX
Margen Bruto % = DIVIDE( [Utilidad Bruta], [Ventas Netas] )
Margen Operativo % = DIVIDE( [Utilidad Operativa], [Ventas Netas] )
Margen Neto % = DIVIDE( [Utilidad Neta], [Ventas Netas] )
Costo % Ventas = DIVIDE( [Costo de Ventas], [Ventas Netas] )
Gastos % Ventas = DIVIDE( [Gastos Operativos], [Ventas Netas] )
```
### 3.3 Comparativos (YoY y YTD)
```DAX
Ventas AA = CALCULATE( [Ventas Netas], SAMEPERIODLASTYEAR(DimCalendario[Date]) )
Utilidad Neta AA = CALCULATE( [Utilidad Neta], SAMEPERIODLASTYEAR(DimCalendario[Date]) )
Gastos AA = CALCULATE( [Gastos Operativos], SAMEPERIODLASTYEAR(DimCalendario[Date]) )
Ventas YoY % = DIVIDE( [Ventas Netas] - [Ventas AA], [Ventas AA] )
Utilidad YoY % = DIVIDE( [Utilidad Neta] - [Utilidad Neta AA], [Utilidad Neta AA] )
Gastos YoY % = DIVIDE( [Gastos Operativos] - [Gastos AA], [Gastos AA] )
Ventas YTD = TOTALYTD( [Ventas Netas], DimCalendario[Date] )
Utilidad Neta YTD = TOTALYTD( [Utilidad Neta], DimCalendario[Date] )
```
### 3.4 Gastos (vwGastosDepto)
```DAX
Gasto Total = SUM( vwGastosDepto[gasto] )
Gasto AA = CALCULATE( [Gasto Total], SAMEPERIODLASTYEAR(DimCalendario[Date]) )
Gasto YoY % = DIVIDE( [Gasto Total] - [Gasto AA], [Gasto AA] )
Gasto % de Ventas = DIVIDE( [Gasto Total], [Ventas Netas] )
```
### 3.5 Balance / liquidez (vwBalanceMensual)
El saldo del balance es el **saldo acumulado al último mes del contexto**. Medidas:
```DAX
Saldo (fin periodo) =
VAR ultima = MAX( vwBalanceMensual[fecha_mes] )
RETURN CALCULATE( SUM(vwBalanceMensual[saldo_presentacion]),
vwBalanceMensual[fecha_mes] = ultima )
Activos = CALCULATE( [Saldo (fin periodo)], vwBalanceMensual[grupo]="1" )
Pasivos = CALCULATE( [Saldo (fin periodo)], vwBalanceMensual[grupo]="2" )
Patrimonio = CALCULATE( [Saldo (fin periodo)], vwBalanceMensual[grupo]="3" )
Activo Corriente = CALCULATE( [Saldo (fin periodo)], vwBalanceMensual[subgrupo_cod]="11" )
Pasivo Corriente = CALCULATE( [Saldo (fin periodo)],
vwBalanceMensual[subgrupo_cod] IN {"20","21"} )
Razón Corriente = DIVIDE( [Activo Corriente], [Pasivo Corriente] )
Capital de Trabajo= [Activo Corriente] - [Pasivo Corriente]
Endeudamiento % = DIVIDE( [Pasivos], [Activos] )
```
---
## 4. Formato de medidas
- Importes: moneda RD$, 0 decimales (o en millones con formato personalizado `#,##0.0,, " M"`).
- Porcentajes: 1 decimal.
- Activar **tabular-nums** implícito (Power BI ya alinea) y usar la fuente del tema corporativo.
---
## 5. Páginas y visuales (ver prototipo `Dashboard_Presidencia_Mockup.html`)
### Página 1 — **Radiografía** (resumen ejecutivo)
- **Fila de KPIs (tarjetas):** Ventas Netas + `Ventas YoY %` · Utilidad Bruta + Margen Bruto % · Utilidad Operativa + Margen Operativo % · Utilidad Neta + `Utilidad YoY %` + Margen Neto %.
- **Cascada (Waterfall):** eje = campo `linea` de `vwPLMensual` (orden por `orden_linea`), valor = `[Utilidad Neta]` con desglose por línea → arranca en Ventas y aterriza en Utilidad Neta. (El visual *Waterfall* nativo suma correctamente porque `resultado` ya trae el signo).
- **Comportamiento del periodo actual (columnas + línea):** eje `DimCalendario[Mes]`; columnas = `[Ventas Netas]`; línea = `[Utilidad Neta]`; línea secundaria opcional = `[Margen Neto %]`.
- **Trayectoria anual (barras agrupadas):** eje `DimCalendario[Año]`; valores `[Ventas Netas]` y `[Utilidad Neta]`.
- Slicers: Año, Mes.
### Página 2 — **Análisis de Gastos** (cgprrp009)
- KPIs: Gasto Total + `Gasto YoY %` · `Gasto % de Ventas` · gerencia de mayor peso · mayor alza YoY.
- **Barras por gerencia:** eje `vwGastosDepto[gerencia]`; valores `[Gasto Total]` y `[Gasto AA]`. *Drill-down* a `depto_nombre` y luego `cuenta_desc`.
- **Naturaleza del gasto:** tabla o *treemap* por `subgrupo_desc` con `[Gasto Total]` y `[Gasto % de Ventas]`. (Nota: subgrupo `76 Cierre gtos fabricación` es una absorción negativa a costo).
- **Gasto mensual + % de ventas:** columnas `[Gasto Total]` por `Mes` con línea `[Gasto % de Ventas]` (control de eficiencia).
### Página 3 — **Estructura Financiera** (balance / liquidez)
- KPIs: Activos · Razón Corriente · Capital de Trabajo · Endeudamiento %.
- **Activos = Pasivos + Patrimonio + resultado del ejercicio** (columnas apiladas): la diferencia contra Activos es la utilidad del año en curso aún no cerrada — es normal y se puede rotular como *"Resultado del ejercicio"*.
- **Composición del activo / pasivo:** tabla por `subgrupo_desc` con `[Saldo (fin periodo)]`.
- **Tendencia del patrimonio:** línea `[Patrimonio]` por `Año`.
---
## 6. Suscripción del Presidente
Publicar en el workspace corporativo → **Suscribir** al Presidente (envío por correo con captura del reporte, p. ej. cada lunes). Requiere que el dataset tenga **actualización programada** (gateway sobre `192.168.40.8`).
---
## 7. Ideas de ampliación (opcional, reutilizando vistas existentes)
Para una radiografía "de un vistazo" también semanal, se puede añadir una página **Panorama** que combine, con las vistas ya publicadas en `MarmotechBI` (sin crear nada nuevo):
- Ventas vs. presupuesto del mes (`vwFactVentas` + `vwFactPresupuesto`).
- Cuentas por cobrar y vencido (`vwFactCxC_Real`).
- Backlog / órdenes pendientes de despacho (`vwOrdenesPendientesDespacho`).
- Producción del periodo (`vwProduccion_pub`).
> Se maneja como **modelo aparte** (área comercial) para no mezclar granos; ver `Documentacion_MarmotechBI.md`.
Binary file not shown.
@@ -0,0 +1,32 @@
PROYECTO POWER BI — RADIOGRAFÍA MARMOTECH (Dashboard de Presidencia)
====================================================================
QUÉ ES ESTO
-----------
Un Proyecto de Power BI (.pbip) en formato de texto:
- Radiografia_Marmotech.SemanticModel -> modelo de datos + TODAS las medidas DAX
- Radiografia_Marmotech.Report -> 3 páginas (Radiografía / Gastos / Estructura)
Se conecta en vivo a las vistas del esquema "presidencia" en 192.168.40.8 / MarmotechBI.
CÓMO ABRIRLO Y CONVERTIRLO A .PBIX
----------------------------------
1. Abrir Power BI Desktop (v2.157 o superior).
2. Archivo -> Abrir -> seleccionar Radiografia_Marmotech.pbip
(o doble clic al .pbip).
3. Si pide credenciales de la base: usar autenticación de Windows (tu usuario jsoto).
4. Actualizar / cargar datos.
5. Archivo -> Guardar como -> elegir tipo ".pbix" -> listo, ya tienes el .pbix.
REQUISITO (una sola vez, si no abre el reporte)
-----------------------------------------------
Archivo -> Opciones -> Características de vista previa ->
activar "Almacenar informes con el formato de informe mejorado (PBIR)".
Reiniciar Power BI Desktop y volver a abrir el .pbip.
NOTA
----
El MODELO (tablas, relaciones y medidas DAX) está completo y es la parte reutilizable.
Si algún visual del reporte necesitara reacomodo, guíate por el prototipo
"Dashboard_Presidencia_Mockup.html" y la "Guia_Dashboard_Presidencia_PowerBI.md":
tienen la disposición exacta y las medidas ya existen en el modelo.
@@ -0,0 +1,5 @@
{
"$schema": "https://developer.microsoft.com/json-schemas/fabric/gitIntegration/platformProperties/2.0.0/schema.json",
"metadata": { "type": "Report", "displayName": "Radiografia_Marmotech" },
"config": { "version": "2.0", "logicalId": "554add93-b301-4779-a780-de4189e02962" }
}
@@ -0,0 +1,4 @@
{
"version": "4.0",
"datasetReference": { "byPath": { "path": "../Radiografia_Marmotech.SemanticModel" } }
}
@@ -0,0 +1,8 @@
{
"$schema": "https://developer.microsoft.com/json-schemas/fabric/item/report/definition/page/1.0.0/schema.json",
"name": "8b8f836c-adf6-4122-94af-ca83a95f1186",
"displayName": "Estructura Financiera",
"displayOption": "FitToPage",
"height": 720,
"width": 1280
}
@@ -0,0 +1,13 @@
{
"$schema": "https://developer.microsoft.com/json-schemas/fabric/item/report/definition/visualContainer/1.0.0/schema.json",
"name": "44ff5e53-b511-41e3-bdd8-48225cf9736d",
"position": { "x": 648, "y": 16, "z": 0, "width": 300, "height": 140, "tabOrder": 0 },
"visual": {
"visualType": "card",
"query": { "queryState": { "Values": { "projections": [ { "field": { "Measure": { "Expression": { "SourceRef": { "Entity": "vwPLMensual" } }, "Property": "Capital de Trabajo" } }, "queryRef": "vwPLMensual.Capital de Trabajo", "nativeQueryRef": "Capital de Trabajo" } ] } } },
"drillFilterOtherVisuals": true,
"objects": {
"title": [ { "properties": { "text": { "expr": { "Literal": { "Value": "'Capital de Trabajo'" } } }, "show": { "expr": { "Literal": { "Value": "true" } } } } } ]
}
}
}
@@ -0,0 +1,13 @@
{
"$schema": "https://developer.microsoft.com/json-schemas/fabric/item/report/definition/visualContainer/1.0.0/schema.json",
"name": "a03966b2-7a2b-4724-9b6f-9d2a82dfbf1a",
"position": { "x": 16, "y": 16, "z": 0, "width": 300, "height": 140, "tabOrder": 0 },
"visual": {
"visualType": "card",
"query": { "queryState": { "Values": { "projections": [ { "field": { "Measure": { "Expression": { "SourceRef": { "Entity": "vwPLMensual" } }, "Property": "Activos" } }, "queryRef": "vwPLMensual.Activos", "nativeQueryRef": "Activos" } ] } } },
"drillFilterOtherVisuals": true,
"objects": {
"title": [ { "properties": { "text": { "expr": { "Literal": { "Value": "'Activos Totales'" } } }, "show": { "expr": { "Literal": { "Value": "true" } } } } } ]
}
}
}
@@ -0,0 +1,13 @@
{
"$schema": "https://developer.microsoft.com/json-schemas/fabric/item/report/definition/visualContainer/1.0.0/schema.json",
"name": "aebdc78d-a3c5-45bd-a5a1-33956b52c2af",
"position": { "x": 964, "y": 16, "z": 0, "width": 300, "height": 140, "tabOrder": 0 },
"visual": {
"visualType": "card",
"query": { "queryState": { "Values": { "projections": [ { "field": { "Measure": { "Expression": { "SourceRef": { "Entity": "vwPLMensual" } }, "Property": "Endeudamiento %" } }, "queryRef": "vwPLMensual.Endeudamiento %", "nativeQueryRef": "Endeudamiento %" } ] } } },
"drillFilterOtherVisuals": true,
"objects": {
"title": [ { "properties": { "text": { "expr": { "Literal": { "Value": "'Endeudamiento'" } } }, "show": { "expr": { "Literal": { "Value": "true" } } } } } ]
}
}
}
@@ -0,0 +1,14 @@
{
"$schema": "https://developer.microsoft.com/json-schemas/fabric/item/report/definition/visualContainer/1.0.0/schema.json",
"name": "b45f71ca-ef58-4807-9ec8-b326c6cef118",
"position": { "x": 16, "y": 170, "z": 0, "width": 620, "height": 300, "tabOrder": 0 },
"visual": {
"visualType": "clusteredColumnChart",
"query": { "queryState": { "Category": { "projections": [ { "field": { "Column": { "Expression": { "SourceRef": { "Entity": "vwBalanceMensual" } }, "Property": "grupo_desc" } }, "queryRef": "vwBalanceMensual.grupo_desc", "nativeQueryRef": "grupo_desc" } ] },
"Y": { "projections": [ { "field": { "Measure": { "Expression": { "SourceRef": { "Entity": "vwPLMensual" } }, "Property": "Saldo (fin periodo)" } }, "queryRef": "vwPLMensual.Saldo (fin periodo)", "nativeQueryRef": "Saldo (fin periodo)" } ] } } },
"drillFilterOtherVisuals": true,
"objects": {
"title": [ { "properties": { "text": { "expr": { "Literal": { "Value": "'Activos / Pasivos / Patrimonio'" } } }, "show": { "expr": { "Literal": { "Value": "true" } } } } } ]
}
}
}
@@ -0,0 +1,14 @@
{
"$schema": "https://developer.microsoft.com/json-schemas/fabric/item/report/definition/visualContainer/1.0.0/schema.json",
"name": "b536c95a-a634-4891-9041-14d33dba3e31",
"position": { "x": 652, "y": 170, "z": 0, "width": 612, "height": 300, "tabOrder": 0 },
"visual": {
"visualType": "tableEx",
"query": { "queryState": { "Values": { "projections": [ { "field": { "Column": { "Expression": { "SourceRef": { "Entity": "vwBalanceMensual" } }, "Property": "subgrupo_desc" } }, "queryRef": "vwBalanceMensual.subgrupo_desc", "nativeQueryRef": "subgrupo_desc" },
{ "field": { "Measure": { "Expression": { "SourceRef": { "Entity": "vwPLMensual" } }, "Property": "Saldo (fin periodo)" } }, "queryRef": "vwPLMensual.Saldo (fin periodo)", "nativeQueryRef": "Saldo (fin periodo)" } ] } } },
"drillFilterOtherVisuals": true,
"objects": {
"title": [ { "properties": { "text": { "expr": { "Literal": { "Value": "'Composicion'" } } }, "show": { "expr": { "Literal": { "Value": "true" } } } } } ]
}
}
}
@@ -0,0 +1,13 @@
{
"$schema": "https://developer.microsoft.com/json-schemas/fabric/item/report/definition/visualContainer/1.0.0/schema.json",
"name": "dfc8665b-ed52-4a78-ad7a-ceec1bd415d6",
"position": { "x": 332, "y": 16, "z": 0, "width": 300, "height": 140, "tabOrder": 0 },
"visual": {
"visualType": "card",
"query": { "queryState": { "Values": { "projections": [ { "field": { "Measure": { "Expression": { "SourceRef": { "Entity": "vwPLMensual" } }, "Property": "Razon Corriente" } }, "queryRef": "vwPLMensual.Razon Corriente", "nativeQueryRef": "Razon Corriente" } ] } } },
"drillFilterOtherVisuals": true,
"objects": {
"title": [ { "properties": { "text": { "expr": { "Literal": { "Value": "'Razon Corriente'" } } }, "show": { "expr": { "Literal": { "Value": "true" } } } } } ]
}
}
}
@@ -0,0 +1,8 @@
{
"$schema": "https://developer.microsoft.com/json-schemas/fabric/item/report/definition/page/1.0.0/schema.json",
"name": "b1d7c3f7-19f4-4e53-9f46-39c24299735d",
"displayName": "Radiografia",
"displayOption": "FitToPage",
"height": 720,
"width": 1280
}
@@ -0,0 +1,13 @@
{
"$schema": "https://developer.microsoft.com/json-schemas/fabric/item/report/definition/visualContainer/1.0.0/schema.json",
"name": "0001fb8c-bfd0-48b4-8c87-ca2e91ded77e",
"position": { "x": 16, "y": 16, "z": 0, "width": 280, "height": 140, "tabOrder": 0 },
"visual": {
"visualType": "card",
"query": { "queryState": { "Values": { "projections": [ { "field": { "Measure": { "Expression": { "SourceRef": { "Entity": "vwPLMensual" } }, "Property": "Ventas Netas" } }, "queryRef": "vwPLMensual.Ventas Netas", "nativeQueryRef": "Ventas Netas" } ] } } },
"drillFilterOtherVisuals": true,
"objects": {
"title": [ { "properties": { "text": { "expr": { "Literal": { "Value": "'Ventas Netas'" } } }, "show": { "expr": { "Literal": { "Value": "true" } } } } } ]
}
}
}
@@ -0,0 +1,13 @@
{
"$schema": "https://developer.microsoft.com/json-schemas/fabric/item/report/definition/visualContainer/1.0.0/schema.json",
"name": "1c8b8064-507f-4d74-aafe-996d0e78abcc",
"position": { "x": 652, "y": 484, "z": 0, "width": 300, "height": 120, "tabOrder": 0 },
"visual": {
"visualType": "slicer",
"query": { "queryState": { "Values": { "projections": [ { "field": { "Column": { "Expression": { "SourceRef": { "Entity": "DimCalendario" } }, "Property": "Anio" } }, "queryRef": "DimCalendario.Anio", "nativeQueryRef": "Anio" } ] } } },
"drillFilterOtherVisuals": true,
"objects": {
"title": [ { "properties": { "text": { "expr": { "Literal": { "Value": "'Ano'" } } }, "show": { "expr": { "Literal": { "Value": "true" } } } } } ]
}
}
}
@@ -0,0 +1,14 @@
{
"$schema": "https://developer.microsoft.com/json-schemas/fabric/item/report/definition/visualContainer/1.0.0/schema.json",
"name": "41b6bf4e-59e7-43d8-bed5-00903f07cf14",
"position": { "x": 16, "y": 170, "z": 0, "width": 620, "height": 300, "tabOrder": 0 },
"visual": {
"visualType": "waterfallChart",
"query": { "queryState": { "Category": { "projections": [ { "field": { "Column": { "Expression": { "SourceRef": { "Entity": "vwPLMensual" } }, "Property": "linea" } }, "queryRef": "vwPLMensual.linea", "nativeQueryRef": "linea" } ] },
"Y": { "projections": [ { "field": { "Measure": { "Expression": { "SourceRef": { "Entity": "vwPLMensual" } }, "Property": "Utilidad Neta" } }, "queryRef": "vwPLMensual.Utilidad Neta", "nativeQueryRef": "Utilidad Neta" } ] } } },
"drillFilterOtherVisuals": true,
"objects": {
"title": [ { "properties": { "text": { "expr": { "Literal": { "Value": "'Estado de Resultados (cascada)'" } } }, "show": { "expr": { "Literal": { "Value": "true" } } } } } ]
}
}
}
@@ -0,0 +1,13 @@
{
"$schema": "https://developer.microsoft.com/json-schemas/fabric/item/report/definition/visualContainer/1.0.0/schema.json",
"name": "7bc4f47e-ea03-4a86-85bc-7b0589403ff3",
"position": { "x": 984, "y": 16, "z": 0, "width": 280, "height": 140, "tabOrder": 0 },
"visual": {
"visualType": "card",
"query": { "queryState": { "Values": { "projections": [ { "field": { "Measure": { "Expression": { "SourceRef": { "Entity": "vwPLMensual" } }, "Property": "Utilidad Neta" } }, "queryRef": "vwPLMensual.Utilidad Neta", "nativeQueryRef": "Utilidad Neta" } ] } } },
"drillFilterOtherVisuals": true,
"objects": {
"title": [ { "properties": { "text": { "expr": { "Literal": { "Value": "'Utilidad Neta'" } } }, "show": { "expr": { "Literal": { "Value": "true" } } } } } ]
}
}
}
@@ -0,0 +1,13 @@
{
"$schema": "https://developer.microsoft.com/json-schemas/fabric/item/report/definition/visualContainer/1.0.0/schema.json",
"name": "86346a78-aca0-4130-8fcc-df275187d223",
"position": { "x": 688, "y": 16, "z": 0, "width": 280, "height": 140, "tabOrder": 0 },
"visual": {
"visualType": "card",
"query": { "queryState": { "Values": { "projections": [ { "field": { "Measure": { "Expression": { "SourceRef": { "Entity": "vwPLMensual" } }, "Property": "Utilidad Operativa" } }, "queryRef": "vwPLMensual.Utilidad Operativa", "nativeQueryRef": "Utilidad Operativa" } ] } } },
"drillFilterOtherVisuals": true,
"objects": {
"title": [ { "properties": { "text": { "expr": { "Literal": { "Value": "'Utilidad Operativa'" } } }, "show": { "expr": { "Literal": { "Value": "true" } } } } } ]
}
}
}
@@ -0,0 +1,15 @@
{
"$schema": "https://developer.microsoft.com/json-schemas/fabric/item/report/definition/visualContainer/1.0.0/schema.json",
"name": "bf867728-d557-4369-a08f-96be202ea340",
"position": { "x": 652, "y": 170, "z": 0, "width": 612, "height": 300, "tabOrder": 0 },
"visual": {
"visualType": "lineClusteredColumnComboChart",
"query": { "queryState": { "Y": { "projections": [ { "field": { "Measure": { "Expression": { "SourceRef": { "Entity": "vwPLMensual" } }, "Property": "Ventas Netas" } }, "queryRef": "vwPLMensual.Ventas Netas", "nativeQueryRef": "Ventas Netas" } ] },
"Category": { "projections": [ { "field": { "Column": { "Expression": { "SourceRef": { "Entity": "DimCalendario" } }, "Property": "Mes" } }, "queryRef": "DimCalendario.Mes", "nativeQueryRef": "Mes" } ] },
"Y2": { "projections": [ { "field": { "Measure": { "Expression": { "SourceRef": { "Entity": "vwPLMensual" } }, "Property": "Utilidad Neta" } }, "queryRef": "vwPLMensual.Utilidad Neta", "nativeQueryRef": "Utilidad Neta" } ] } } },
"drillFilterOtherVisuals": true,
"objects": {
"title": [ { "properties": { "text": { "expr": { "Literal": { "Value": "'Comportamiento del periodo (Ventas vs Utilidad)'" } } }, "show": { "expr": { "Literal": { "Value": "true" } } } } } ]
}
}
}
@@ -0,0 +1,15 @@
{
"$schema": "https://developer.microsoft.com/json-schemas/fabric/item/report/definition/visualContainer/1.0.0/schema.json",
"name": "c16df67e-416b-4acb-8169-fbd21a916682",
"position": { "x": 16, "y": 484, "z": 0, "width": 620, "height": 220, "tabOrder": 0 },
"visual": {
"visualType": "clusteredColumnChart",
"query": { "queryState": { "Category": { "projections": [ { "field": { "Column": { "Expression": { "SourceRef": { "Entity": "DimCalendario" } }, "Property": "Anio" } }, "queryRef": "DimCalendario.Anio", "nativeQueryRef": "Anio" } ] },
"Y": { "projections": [ { "field": { "Measure": { "Expression": { "SourceRef": { "Entity": "vwPLMensual" } }, "Property": "Ventas Netas" } }, "queryRef": "vwPLMensual.Ventas Netas", "nativeQueryRef": "Ventas Netas" },
{ "field": { "Measure": { "Expression": { "SourceRef": { "Entity": "vwPLMensual" } }, "Property": "Utilidad Neta" } }, "queryRef": "vwPLMensual.Utilidad Neta", "nativeQueryRef": "Utilidad Neta" } ] } } },
"drillFilterOtherVisuals": true,
"objects": {
"title": [ { "properties": { "text": { "expr": { "Literal": { "Value": "'Trayectoria anual'" } } }, "show": { "expr": { "Literal": { "Value": "true" } } } } } ]
}
}
}
@@ -0,0 +1,13 @@
{
"$schema": "https://developer.microsoft.com/json-schemas/fabric/item/report/definition/visualContainer/1.0.0/schema.json",
"name": "ea44fc7d-26fd-4269-a605-7a460f6078bd",
"position": { "x": 312, "y": 16, "z": 0, "width": 280, "height": 140, "tabOrder": 0 },
"visual": {
"visualType": "card",
"query": { "queryState": { "Values": { "projections": [ { "field": { "Measure": { "Expression": { "SourceRef": { "Entity": "vwPLMensual" } }, "Property": "Utilidad Bruta" } }, "queryRef": "vwPLMensual.Utilidad Bruta", "nativeQueryRef": "Utilidad Bruta" } ] } } },
"drillFilterOtherVisuals": true,
"objects": {
"title": [ { "properties": { "text": { "expr": { "Literal": { "Value": "'Utilidad Bruta'" } } }, "show": { "expr": { "Literal": { "Value": "true" } } } } } ]
}
}
}
@@ -0,0 +1,8 @@
{
"$schema": "https://developer.microsoft.com/json-schemas/fabric/item/report/definition/page/1.0.0/schema.json",
"name": "e77fc1b2-6848-4091-910e-ab9079a10ae3",
"displayName": "Analisis de Gastos",
"displayOption": "FitToPage",
"height": 720,
"width": 1280
}
@@ -0,0 +1,13 @@
{
"$schema": "https://developer.microsoft.com/json-schemas/fabric/item/report/definition/visualContainer/1.0.0/schema.json",
"name": "2970078c-5cf9-40ff-ad03-436e7f9d90c1",
"position": { "x": 16, "y": 16, "z": 0, "width": 300, "height": 140, "tabOrder": 0 },
"visual": {
"visualType": "card",
"query": { "queryState": { "Values": { "projections": [ { "field": { "Measure": { "Expression": { "SourceRef": { "Entity": "vwPLMensual" } }, "Property": "Gasto Total" } }, "queryRef": "vwPLMensual.Gasto Total", "nativeQueryRef": "Gasto Total" } ] } } },
"drillFilterOtherVisuals": true,
"objects": {
"title": [ { "properties": { "text": { "expr": { "Literal": { "Value": "'Gastos Operativos'" } } }, "show": { "expr": { "Literal": { "Value": "true" } } } } } ]
}
}
}
@@ -0,0 +1,14 @@
{
"$schema": "https://developer.microsoft.com/json-schemas/fabric/item/report/definition/visualContainer/1.0.0/schema.json",
"name": "55c25809-9340-4b4f-ab44-ea947df2aa20",
"position": { "x": 16, "y": 484, "z": 0, "width": 1248, "height": 220, "tabOrder": 0 },
"visual": {
"visualType": "columnChart",
"query": { "queryState": { "Category": { "projections": [ { "field": { "Column": { "Expression": { "SourceRef": { "Entity": "DimCalendario" } }, "Property": "Mes" } }, "queryRef": "DimCalendario.Mes", "nativeQueryRef": "Mes" } ] },
"Y": { "projections": [ { "field": { "Measure": { "Expression": { "SourceRef": { "Entity": "vwPLMensual" } }, "Property": "Gasto Total" } }, "queryRef": "vwPLMensual.Gasto Total", "nativeQueryRef": "Gasto Total" } ] } } },
"drillFilterOtherVisuals": true,
"objects": {
"title": [ { "properties": { "text": { "expr": { "Literal": { "Value": "'Gasto mensual'" } } }, "show": { "expr": { "Literal": { "Value": "true" } } } } } ]
}
}
}
@@ -0,0 +1,14 @@
{
"$schema": "https://developer.microsoft.com/json-schemas/fabric/item/report/definition/visualContainer/1.0.0/schema.json",
"name": "c5d3dccf-c735-4552-97e3-3409f290ae2c",
"position": { "x": 16, "y": 170, "z": 0, "width": 620, "height": 300, "tabOrder": 0 },
"visual": {
"visualType": "barChart",
"query": { "queryState": { "Category": { "projections": [ { "field": { "Column": { "Expression": { "SourceRef": { "Entity": "vwGastosDepto" } }, "Property": "gerencia" } }, "queryRef": "vwGastosDepto.gerencia", "nativeQueryRef": "gerencia" } ] },
"Y": { "projections": [ { "field": { "Measure": { "Expression": { "SourceRef": { "Entity": "vwPLMensual" } }, "Property": "Gasto Total" } }, "queryRef": "vwPLMensual.Gasto Total", "nativeQueryRef": "Gasto Total" } ] } } },
"drillFilterOtherVisuals": true,
"objects": {
"title": [ { "properties": { "text": { "expr": { "Literal": { "Value": "'Gasto por gerencia'" } } }, "show": { "expr": { "Literal": { "Value": "true" } } } } } ]
}
}
}
@@ -0,0 +1,15 @@
{
"$schema": "https://developer.microsoft.com/json-schemas/fabric/item/report/definition/visualContainer/1.0.0/schema.json",
"name": "d56cf84c-d5d9-4d14-a4a5-c20b98f0aab6",
"position": { "x": 652, "y": 170, "z": 0, "width": 612, "height": 300, "tabOrder": 0 },
"visual": {
"visualType": "tableEx",
"query": { "queryState": { "Values": { "projections": [ { "field": { "Column": { "Expression": { "SourceRef": { "Entity": "vwGastosDepto" } }, "Property": "subgrupo_desc" } }, "queryRef": "vwGastosDepto.subgrupo_desc", "nativeQueryRef": "subgrupo_desc" },
{ "field": { "Measure": { "Expression": { "SourceRef": { "Entity": "vwPLMensual" } }, "Property": "Gasto Total" } }, "queryRef": "vwPLMensual.Gasto Total", "nativeQueryRef": "Gasto Total" },
{ "field": { "Measure": { "Expression": { "SourceRef": { "Entity": "vwPLMensual" } }, "Property": "Gasto % de Ventas" } }, "queryRef": "vwPLMensual.Gasto % de Ventas", "nativeQueryRef": "Gasto % de Ventas" } ] } } },
"drillFilterOtherVisuals": true,
"objects": {
"title": [ { "properties": { "text": { "expr": { "Literal": { "Value": "'Naturaleza del gasto'" } } }, "show": { "expr": { "Literal": { "Value": "true" } } } } } ]
}
}
}
@@ -0,0 +1,13 @@
{
"$schema": "https://developer.microsoft.com/json-schemas/fabric/item/report/definition/visualContainer/1.0.0/schema.json",
"name": "f2193414-6ebe-4c68-bb63-1badcb5ffd09",
"position": { "x": 332, "y": 16, "z": 0, "width": 300, "height": 140, "tabOrder": 0 },
"visual": {
"visualType": "card",
"query": { "queryState": { "Values": { "projections": [ { "field": { "Measure": { "Expression": { "SourceRef": { "Entity": "vwPLMensual" } }, "Property": "Gasto % de Ventas" } }, "queryRef": "vwPLMensual.Gasto % de Ventas", "nativeQueryRef": "Gasto % de Ventas" } ] } } },
"drillFilterOtherVisuals": true,
"objects": {
"title": [ { "properties": { "text": { "expr": { "Literal": { "Value": "'Gasto / Ventas'" } } }, "show": { "expr": { "Literal": { "Value": "true" } } } } } ]
}
}
}
@@ -0,0 +1,5 @@
{
"$schema": "https://developer.microsoft.com/json-schemas/fabric/item/report/definition/pagesMetadata/1.0.0/schema.json",
"pageOrder": [ "b1d7c3f7-19f4-4e53-9f46-39c24299735d", "e77fc1b2-6848-4091-910e-ab9079a10ae3", "8b8f836c-adf6-4122-94af-ca83a95f1186" ],
"activePageName": "b1d7c3f7-19f4-4e53-9f46-39c24299735d"
}
@@ -0,0 +1,5 @@
{
"$schema": "https://developer.microsoft.com/json-schemas/fabric/item/report/definition/report/1.0.0/schema.json",
"themeCollection": { "baseTheme": { "name": "CY24SU10" } },
"layoutOptimization": "None"
}
@@ -0,0 +1,5 @@
{
"$schema": "https://developer.microsoft.com/json-schemas/fabric/gitIntegration/platformProperties/2.0.0/schema.json",
"metadata": { "type": "SemanticModel", "displayName": "Radiografia_Marmotech" },
"config": { "version": "2.0", "logicalId": "9189c214-f719-4b14-9985-284d1156b518" }
}
@@ -0,0 +1,4 @@
{
"version": "4.0",
"settings": {}
}
@@ -0,0 +1,2 @@
database
compatibilityLevel: 1550
@@ -0,0 +1,7 @@
model Model
culture: es-ES
defaultPowerBIDataSourceVersion: powerBI_V3
sourceQueryCulture: es-ES
annotation PBI_QueryOrder = ["vwPLMensual","vwGastosDepto","vwBalanceMensual","DimCalendario"]
@@ -0,0 +1,12 @@
relationship a1bca91e-c68e-40d9-903e-6c0b3084d97e
fromColumn: vwPLMensual.fecha_mes
toColumn: DimCalendario.Date
relationship b8a2999c-fc54-47de-8f0b-85af0263c75b
fromColumn: vwGastosDepto.fecha_mes
toColumn: DimCalendario.Date
relationship ea96c86f-8345-4913-b1e6-6823b9a100f4
fromColumn: vwBalanceMensual.fecha_mes
toColumn: DimCalendario.Date
@@ -0,0 +1,42 @@
table DimCalendario
column Date
dataType: dateTime
sourceColumn: [Date]
formatString: General Date
summarizeBy: none
column Anio
dataType: int64
sourceColumn: [Anio]
summarizeBy: none
column MesNo
dataType: int64
sourceColumn: [MesNo]
summarizeBy: none
column Mes
dataType: string
sourceColumn: [Mes]
summarizeBy: none
column AnioMes
dataType: string
sourceColumn: [AnioMes]
summarizeBy: none
column PrimerDiaMes
dataType: dateTime
sourceColumn: [PrimerDiaMes]
formatString: General Date
summarizeBy: none
column Trimestre
dataType: string
sourceColumn: [Trimestre]
summarizeBy: none
partition DimCalendario = calculated
mode: import
source = ADDCOLUMNS ( CALENDAR ( DATE ( 2015, 1, 1 ), DATE ( 2028, 12, 31 ) ), "Anio", YEAR ( [Date] ), "MesNo", MONTH ( [Date] ), "Mes", FORMAT ( [Date], "MMM" ), "AnioMes", FORMAT ( [Date], "yyyy-MM" ), "PrimerDiaMes", DATE ( YEAR ( [Date] ), MONTH ( [Date] ), 1 ), "Trimestre", "T" & FORMAT ( [Date], "Q" ) )
@@ -0,0 +1,81 @@
table vwBalanceMensual
column 'ano'
dataType: int64
sourceColumn: ano
summarizeBy: none
column 'numero_mes'
dataType: int64
sourceColumn: numero_mes
summarizeBy: none
column 'mes'
dataType: string
sourceColumn: mes
summarizeBy: none
column 'AnoMes'
dataType: string
sourceColumn: AnoMes
summarizeBy: none
column 'fecha_mes'
dataType: dateTime
sourceColumn: fecha_mes
formatString: General Date
summarizeBy: none
column 'grupo'
dataType: string
sourceColumn: grupo
summarizeBy: none
column 'grupo_desc'
dataType: string
sourceColumn: grupo_desc
summarizeBy: none
column 'subgrupo_cod'
dataType: string
sourceColumn: subgrupo_cod
summarizeBy: none
column 'subgrupo_desc'
dataType: string
sourceColumn: subgrupo_desc
summarizeBy: none
column 'cuenta'
dataType: string
sourceColumn: cuenta
summarizeBy: none
column 'cuenta_desc'
dataType: string
sourceColumn: cuenta_desc
summarizeBy: none
column 'movimiento_mes'
dataType: decimal
sourceColumn: movimiento_mes
summarizeBy: none
column 'saldo_natural'
dataType: decimal
sourceColumn: saldo_natural
summarizeBy: none
column 'saldo_presentacion'
dataType: decimal
sourceColumn: saldo_presentacion
summarizeBy: none
partition vwBalanceMensual = m
mode: import
source =
let
Origen = Sql.Database("192.168.40.8", "MarmotechBI"),
Datos = Origen{[Schema="presidencia", Item="vwBalanceMensual"]}[Data]
in
Datos
@@ -0,0 +1,91 @@
table vwGastosDepto
column 'ano'
dataType: int64
sourceColumn: ano
summarizeBy: none
column 'numero_mes'
dataType: int64
sourceColumn: numero_mes
summarizeBy: none
column 'mes'
dataType: string
sourceColumn: mes
summarizeBy: none
column 'AnoMes'
dataType: string
sourceColumn: AnoMes
summarizeBy: none
column 'fecha_mes'
dataType: dateTime
sourceColumn: fecha_mes
formatString: General Date
summarizeBy: none
column 'gerencia_cod'
dataType: string
sourceColumn: gerencia_cod
summarizeBy: none
column 'gerencia'
dataType: string
sourceColumn: gerencia
summarizeBy: none
column 'departamento'
dataType: int64
sourceColumn: departamento
summarizeBy: none
column 'depto_nombre'
dataType: string
sourceColumn: depto_nombre
summarizeBy: none
column 'subgrupo_cod'
dataType: string
sourceColumn: subgrupo_cod
summarizeBy: none
column 'subgrupo_desc'
dataType: string
sourceColumn: subgrupo_desc
summarizeBy: none
column 'cuenta'
dataType: string
sourceColumn: cuenta
summarizeBy: none
column 'cuenta_desc'
dataType: string
sourceColumn: cuenta_desc
summarizeBy: none
column 'debito'
dataType: decimal
sourceColumn: debito
summarizeBy: none
column 'credito'
dataType: decimal
sourceColumn: credito
summarizeBy: none
column 'gasto'
dataType: decimal
sourceColumn: gasto
summarizeBy: none
partition vwGastosDepto = m
mode: import
source =
let
Origen = Sql.Database("192.168.40.8", "MarmotechBI"),
Datos = Origen{[Schema="presidencia", Item="vwGastosDepto"]}[Data]
in
Datos
@@ -0,0 +1,163 @@
table vwPLMensual
column 'ano'
dataType: int64
sourceColumn: ano
summarizeBy: none
column 'numero_mes'
dataType: int64
sourceColumn: numero_mes
summarizeBy: none
column 'mes'
dataType: string
sourceColumn: mes
summarizeBy: none
column 'AnoMes'
dataType: string
sourceColumn: AnoMes
summarizeBy: none
column 'fecha_mes'
dataType: dateTime
sourceColumn: fecha_mes
formatString: General Date
summarizeBy: none
column 'grupo'
dataType: string
sourceColumn: grupo
summarizeBy: none
column 'orden_linea'
dataType: int64
sourceColumn: orden_linea
summarizeBy: none
column 'linea'
dataType: string
sourceColumn: linea
summarizeBy: none
column 'subgrupo_cod'
dataType: string
sourceColumn: subgrupo_cod
summarizeBy: none
column 'subgrupo_desc'
dataType: string
sourceColumn: subgrupo_desc
summarizeBy: none
column 'cuenta'
dataType: string
sourceColumn: cuenta
summarizeBy: none
column 'cuenta_desc'
dataType: string
sourceColumn: cuenta_desc
summarizeBy: none
column 'nivel'
dataType: int64
sourceColumn: nivel
summarizeBy: none
column 'debito'
dataType: decimal
sourceColumn: debito
summarizeBy: none
column 'credito'
dataType: decimal
sourceColumn: credito
summarizeBy: none
column 'resultado'
dataType: decimal
sourceColumn: resultado
summarizeBy: none
column 'costo_gasto'
dataType: decimal
sourceColumn: costo_gasto
summarizeBy: none
measure 'Ventas Netas' = CALCULATE ( SUM ( vwPLMensual[resultado] ), vwPLMensual[grupo] = "4" )
formatString: \$ #,0,," M";\$ -#,0,," M";\$ 0
measure 'Costo de Ventas' = CALCULATE ( SUM ( vwPLMensual[costo_gasto] ), vwPLMensual[grupo] = "5" )
formatString: \$ #,0,," M"
measure 'Gastos Operativos' = CALCULATE ( SUM ( vwPLMensual[costo_gasto] ), vwPLMensual[grupo] = "7" )
formatString: \$ #,0,," M"
measure 'Otros Ing. y Gastos' = CALCULATE ( SUM ( vwPLMensual[costo_gasto] ), vwPLMensual[linea] = "Otros Ingresos y Gastos" )
formatString: \$ #,0,," M"
measure 'ISR' = CALCULATE ( SUM ( vwPLMensual[costo_gasto] ), vwPLMensual[linea] = "Impuesto sobre la Renta" )
formatString: \$ #,0,," M"
measure 'Utilidad Bruta' = [Ventas Netas] - [Costo de Ventas]
formatString: \$ #,0,," M"
measure 'Utilidad Operativa' = [Utilidad Bruta] - [Gastos Operativos]
formatString: \$ #,0,," M"
measure 'Utilidad antes ISR' = [Utilidad Operativa] - [Otros Ing. y Gastos]
formatString: \$ #,0,," M"
measure 'Utilidad Neta' = SUM ( vwPLMensual[resultado] )
formatString: \$ #,0,," M"
measure 'Margen Bruto %' = DIVIDE ( [Utilidad Bruta], [Ventas Netas] )
formatString: 0.0%
measure 'Margen Operativo %' = DIVIDE ( [Utilidad Operativa], [Ventas Netas] )
formatString: 0.0%
measure 'Margen Neto %' = DIVIDE ( [Utilidad Neta], [Ventas Netas] )
formatString: 0.0%
measure 'Costo % Ventas' = DIVIDE ( [Costo de Ventas], [Ventas Netas] )
formatString: 0.0%
measure 'Gastos % Ventas' = DIVIDE ( [Gastos Operativos], [Ventas Netas] )
formatString: 0.0%
measure 'Ventas AA' = CALCULATE ( [Ventas Netas], SAMEPERIODLASTYEAR ( DimCalendario[Date] ) )
formatString: \$ #,0,," M"
measure 'Utilidad Neta AA' = CALCULATE ( [Utilidad Neta], SAMEPERIODLASTYEAR ( DimCalendario[Date] ) )
formatString: \$ #,0,," M"
measure 'Ventas YoY %' = DIVIDE ( [Ventas Netas] - [Ventas AA], [Ventas AA] )
formatString: 0.0%
measure 'Utilidad YoY %' = DIVIDE ( [Utilidad Neta] - [Utilidad Neta AA], [Utilidad Neta AA] )
formatString: 0.0%
measure 'Ventas YTD' = TOTALYTD ( [Ventas Netas], DimCalendario[Date] )
formatString: \$ #,0,," M"
measure 'Utilidad Neta YTD' = TOTALYTD ( [Utilidad Neta], DimCalendario[Date] )
formatString: \$ #,0,," M"
measure 'Gasto Total' = SUM ( vwGastosDepto[gasto] )
formatString: \$ #,0,," M"
measure 'Gasto AA' = CALCULATE ( [Gasto Total], SAMEPERIODLASTYEAR ( DimCalendario[Date] ) )
formatString: \$ #,0,," M"
measure 'Gasto YoY %' = DIVIDE ( [Gasto Total] - [Gasto AA], [Gasto AA] )
formatString: 0.0%
measure 'Gasto % de Ventas' = DIVIDE ( [Gasto Total], [Ventas Netas] )
formatString: 0.0%
measure 'Saldo (fin periodo)' = VAR u = MAX ( vwBalanceMensual[fecha_mes] ) RETURN CALCULATE ( SUM ( vwBalanceMensual[saldo_presentacion] ), vwBalanceMensual[fecha_mes] = u )
formatString: \$ #,0,," M"
measure 'Activos' = CALCULATE ( [Saldo (fin periodo)], vwBalanceMensual[grupo] = "1" )
formatString: \$ #,0,," M"
measure 'Pasivos' = CALCULATE ( [Saldo (fin periodo)], vwBalanceMensual[grupo] = "2" )
formatString: \$ #,0,," M"
measure 'Patrimonio' = CALCULATE ( [Saldo (fin periodo)], vwBalanceMensual[grupo] = "3" )
formatString: \$ #,0,," M"
measure 'Activo Corriente' = CALCULATE ( [Saldo (fin periodo)], vwBalanceMensual[subgrupo_cod] = "11" )
formatString: \$ #,0,," M"
measure 'Pasivo Corriente' = CALCULATE ( [Saldo (fin periodo)], vwBalanceMensual[subgrupo_cod] IN {"20","21"} )
formatString: \$ #,0,," M"
measure 'Razon Corriente' = DIVIDE ( [Activo Corriente], [Pasivo Corriente] )
formatString: 0.00
measure 'Capital de Trabajo' = [Activo Corriente] - [Pasivo Corriente]
formatString: \$ #,0,," M"
measure 'Endeudamiento %' = DIVIDE ( [Pasivos], [Activos] )
formatString: 0.0%
partition vwPLMensual = m
mode: import
source =
let
Origen = Sql.Database("192.168.40.8", "MarmotechBI"),
Datos = Origen{[Schema="presidencia", Item="vwPLMensual"]}[Data]
in
Datos
@@ -0,0 +1,7 @@
{
"version": "1.0",
"artifacts": [
{ "report": { "path": "Radiografia_Marmotech.Report" } }
],
"settings": { "enableAutoRecovery": true }
}
@@ -0,0 +1,226 @@
/* ============================================================================
MarmotechBI — Vistas para el Dashboard Ejecutivo de Presidencia
----------------------------------------------------------------------------
Autor : Juan F. Soto (jsoto)
Fecha : 2026-08-26
Servidor : 192.168.40.8 (SQL Server 2016)
Base : MarmotechBI (capa semantica; los datos viven en marmotech.dbo.*)
Objetivo : Radiografia financiera para el Presidente, a partir del
Mayor General Analitico (cgprrp008) y el Analisis de Gastos
por Departamento (cgprrp009).
Fuente de datos:
marmotech.dbo.cgtb00004 = transacciones contables (debito/credito por cuenta/fecha)
marmotech.dbo.cgtb00001 = catalogo de cuentas (nivel, descripcion)
marmotech.dbo.prdtable = periodos contables (ano, mes -> rango de fechas)
marmotech.dbo.mestable = nombres de meses
marmotech.dbo.adtb00001 = departamentos (nom_dpto)
Reglas de negocio (validadas contra cgprrp008 / cgprrp009):
* Estado de Resultados por PRIMER DIGITO de la cuenta:
4 = VENTAS NETA (naturaleza credito)
5 = COSTO DE VENTAS (naturaleza debito)
7 = GASTOS (naturaleza debito)
8 = OTROS INGRESOS Y GASTOS (incluye 88 = IMPUESTO S/RENTA)
* resultado (aporte a la utilidad) = credito - debito
-> Ventas suma positivo; Costo/Gastos/ISR restan; la suma de todas
las lineas = Utilidad Neta del periodo.
* Solo transacciones activas: status_t IS NULL.
* Se EXCLUYE el asiento de cierre anual (ref 'ED.99-001/12YY' y
'ED.99-002/12YY'); de lo contrario cada ano cerrado netearia a 0 porque
el cierre revierte las cuentas de resultado contra Utilidades Retenidas.
(2026 aun no tiene cierre, por eso muestra saldo operativo real.)
Seguridad (requisito): estas vistas solo deben ser legibles por jsoto.
Se crean en el esquema [presidencia] y se hace DENY SELECT a [public].
jsoto (MARMOTECH\jsoto y login SQL jsoto) es sysadmin/db_owner, por lo que
ignora el DENY y conserva acceso; el resto del equipo (db_datareader) queda
bloqueado a nivel de esquema.
============================================================================ */
USE MarmotechBI;
GO
/* ---- 1. Esquema dedicado ------------------------------------------------- */
IF NOT EXISTS (SELECT 1 FROM sys.schemas WHERE name = 'presidencia')
EXEC('CREATE SCHEMA presidencia AUTHORIZATION dbo;');
GO
/* ---- 2. Estado de Resultados mensual (cgprrp008) ------------------------- */
/* Grano: ano x mes x cuenta. Un renglon por cuenta contable de resultado. */
IF OBJECT_ID('presidencia.vwPLMensual','V') IS NOT NULL
DROP VIEW presidencia.vwPLMensual;
GO
CREATE VIEW presidencia.vwPLMensual
AS
WITH mov AS (
SELECT p.ano,
p.mes AS numero_mes,
b.cuenta_no,
SUM(b.debito) AS debito,
SUM(b.credito) AS credito
FROM marmotech.dbo.cgtb00004 b
INNER JOIN marmotech.dbo.prdtable p
ON b.fecha BETWEEN p.fecha_inicio AND p.fecha_corte
WHERE b.status_t IS NULL
AND LEFT(b.cuenta_no,1) IN ('4','5','7','8')
AND b.ref NOT LIKE 'ED.99-001/12%' -- asiento de cierre anual
AND b.ref NOT LIKE 'ED.99-002/12%' -- asiento de cierre anual (variante)
GROUP BY p.ano, p.mes, b.cuenta_no
)
SELECT
m.ano,
m.numero_mes,
ms.descrip AS mes,
RIGHT('0000'+CONVERT(varchar(4),m.ano),4) + '-' +
RIGHT('00'+CONVERT(varchar(2),m.numero_mes),2) AS AnoMes,
DATEFROMPARTS(m.ano, m.numero_mes, 1) AS fecha_mes,
LEFT(m.cuenta_no,1) AS grupo,
CASE LEFT(m.cuenta_no,1)
WHEN '4' THEN 1 WHEN '5' THEN 2 WHEN '7' THEN 3
WHEN '8' THEN CASE WHEN m.cuenta_no LIKE '88%' THEN 5 ELSE 4 END
END AS orden_linea,
CASE LEFT(m.cuenta_no,1)
WHEN '4' THEN 'Ventas Netas'
WHEN '5' THEN 'Costo de Ventas'
WHEN '7' THEN 'Gastos Operativos'
WHEN '8' THEN CASE WHEN m.cuenta_no LIKE '88%'
THEN 'Impuesto sobre la Renta'
ELSE 'Otros Ingresos y Gastos' END
END AS linea,
LEFT(m.cuenta_no,2) AS subgrupo_cod,
sg.descripcion AS subgrupo_desc,
m.cuenta_no AS cuenta,
ct.descripcion AS cuenta_desc,
ct.nivel,
m.debito,
m.credito,
CAST(m.credito - m.debito AS decimal(19,2)) AS resultado, -- + = aporta utilidad
CAST(m.debito - m.credito AS decimal(19,2)) AS costo_gasto -- + = costo/gasto
FROM mov m
LEFT JOIN marmotech.dbo.cgtb00001 ct ON ct.cuenta_no = m.cuenta_no
LEFT JOIN marmotech.dbo.cgtb00001 sg ON sg.cuenta_no = LEFT(m.cuenta_no,2)
LEFT JOIN marmotech.dbo.mestable ms ON ms.mes = m.numero_mes;
GO
/* ---- 3. Analisis de Gastos por Departamento (cgprrp009) ------------------ */
/* Grano: ano x mes x departamento x cuenta. Solo grupo 7 (GASTOS). */
IF OBJECT_ID('presidencia.vwGastosDepto','V') IS NOT NULL
DROP VIEW presidencia.vwGastosDepto;
GO
CREATE VIEW presidencia.vwGastosDepto
AS
WITH mov AS (
SELECT p.ano,
p.mes AS numero_mes,
ISNULL(b.departamento,0) AS departamento,
b.cuenta_no,
SUM(b.debito) AS debito,
SUM(b.credito) AS credito
FROM marmotech.dbo.cgtb00004 b
INNER JOIN marmotech.dbo.prdtable p
ON b.fecha BETWEEN p.fecha_inicio AND p.fecha_corte
WHERE b.status_t IS NULL
AND LEFT(b.cuenta_no,1) = '7'
AND b.ref NOT LIKE 'ED.99-001/12%'
AND b.ref NOT LIKE 'ED.99-002/12%'
GROUP BY p.ano, p.mes, ISNULL(b.departamento,0), b.cuenta_no
)
SELECT
m.ano,
m.numero_mes,
ms.descrip AS mes,
RIGHT('0000'+CONVERT(varchar(4),m.ano),4) + '-' +
RIGHT('00'+CONVERT(varchar(2),m.numero_mes),2) AS AnoMes,
DATEFROMPARTS(m.ano, m.numero_mes, 1) AS fecha_mes,
LEFT(CONVERT(varchar(4),m.departamento),1) AS gerencia_cod,
CASE LEFT(CONVERT(varchar(4),m.departamento),1)
WHEN '1' THEN 'Gerencia General'
WHEN '2' THEN 'Administracion y Materiales'
WHEN '3' THEN 'Gestion Humana'
WHEN '4' THEN 'Ventas y Mercadeo'
WHEN '5' THEN 'Canteras'
WHEN '6' THEN 'Planta / Produccion'
WHEN '7' THEN 'Planta Agregados'
WHEN '8' THEN 'Marmotech en Caribbean'
ELSE 'Sin Departamento'
END AS gerencia,
m.departamento,
ISNULL(ad.nom_dpto,'SIN DEPARTAMENTO') AS depto_nombre,
LEFT(m.cuenta_no,2) AS subgrupo_cod,
sg.descripcion AS subgrupo_desc,
m.cuenta_no AS cuenta,
ct.descripcion AS cuenta_desc,
m.debito,
m.credito,
CAST(m.debito - m.credito AS decimal(19,2)) AS gasto -- + = gasto neto
FROM mov m
LEFT JOIN marmotech.dbo.cgtb00001 ct ON ct.cuenta_no = m.cuenta_no
LEFT JOIN marmotech.dbo.cgtb00001 sg ON sg.cuenta_no = LEFT(m.cuenta_no,2)
LEFT JOIN marmotech.dbo.adtb00001 ad ON ad.departamento = m.departamento
LEFT JOIN marmotech.dbo.mestable ms ON ms.mes = m.numero_mes;
GO
/* ---- 4. Balance General / Estructura financiera (saldo acumulado) -------- */
/* Grano: ano x mes x cuenta, grupos 1 (Activos) 2 (Pasivos) 3 (Capital). */
/* saldo_acumulado = saldo contable al cierre del mes (deb-cred acumulado). */
IF OBJECT_ID('presidencia.vwBalanceMensual','V') IS NOT NULL
DROP VIEW presidencia.vwBalanceMensual;
GO
CREATE VIEW presidencia.vwBalanceMensual
AS
WITH mov AS (
SELECT p.ano,
p.mes AS numero_mes,
b.cuenta_no,
SUM(b.debito - b.credito) AS movimiento
FROM marmotech.dbo.cgtb00004 b
INNER JOIN marmotech.dbo.prdtable p
ON b.fecha BETWEEN p.fecha_inicio AND p.fecha_corte
WHERE b.status_t IS NULL
AND LEFT(b.cuenta_no,1) IN ('1','2','3')
GROUP BY p.ano, p.mes, b.cuenta_no
),
acum AS (
SELECT m.*,
SUM(m.movimiento) OVER (PARTITION BY m.cuenta_no
ORDER BY m.ano, m.numero_mes
ROWS UNBOUNDED PRECEDING) AS saldo_acumulado
FROM mov m
)
SELECT
a.ano,
a.numero_mes,
ms.descrip AS mes,
RIGHT('0000'+CONVERT(varchar(4),a.ano),4) + '-' +
RIGHT('00'+CONVERT(varchar(2),a.numero_mes),2) AS AnoMes,
DATEFROMPARTS(a.ano, a.numero_mes, 1) AS fecha_mes,
LEFT(a.cuenta_no,1) AS grupo,
CASE LEFT(a.cuenta_no,1) WHEN '1' THEN 'Activos'
WHEN '2' THEN 'Pasivos'
WHEN '3' THEN 'Capital' END AS grupo_desc,
LEFT(a.cuenta_no,2) AS subgrupo_cod,
sg.descripcion AS subgrupo_desc,
a.cuenta_no AS cuenta,
ct.descripcion AS cuenta_desc,
CAST(a.movimiento AS decimal(19,2)) AS movimiento_mes,
CAST(a.saldo_acumulado AS decimal(19,2)) AS saldo_natural, -- deb-cred
/* Presentacion: Activos positivos por debito; Pasivos/Capital por credito */
CAST(CASE WHEN LEFT(a.cuenta_no,1) = '1' THEN a.saldo_acumulado
ELSE -a.saldo_acumulado END AS decimal(19,2)) AS saldo_presentacion
FROM acum a
LEFT JOIN marmotech.dbo.cgtb00001 ct ON ct.cuenta_no = a.cuenta_no
LEFT JOIN marmotech.dbo.cgtb00001 sg ON sg.cuenta_no = LEFT(a.cuenta_no,2)
LEFT JOIN marmotech.dbo.mestable ms ON ms.mes = a.numero_mes;
GO
/* ---- 5. Seguridad: solo jsoto ------------------------------------------- */
DENY SELECT ON SCHEMA::presidencia TO [public];
GO
/* Concesion explicita (documenta la intencion; jsoto ya es db_owner/sysadmin) */
IF EXISTS (SELECT 1 FROM sys.database_principals WHERE name = 'MARMOTECH\jsoto')
GRANT SELECT ON SCHEMA::presidencia TO [MARMOTECH\jsoto];
GO
IF EXISTS (SELECT 1 FROM sys.database_principals WHERE name = 'jsoto')
GRANT SELECT ON SCHEMA::presidencia TO [jsoto];
GO
@@ -0,0 +1,342 @@
<# Genera el proyecto Power BI (.pbip) "Radiografia_Marmotech"
Modelo (TMDL) con las 3 vistas de presidencia + DimCalendario + medidas DAX,
y un reporte (PBIR) con 3 paginas. Abrir el .pbip en Power BI Desktop y
Guardar como .pbix.
Autor: jsoto · 2026-08-26
#>
param(
[string]$Root = "C:\Users\jsoto\Documents\NACE\proyectos\Fuentes\MARMOTECH\Dashboards MARMOTECH\Radiografia_Marmotech"
)
$ErrorActionPreference = "Stop"
$Name = "Radiografia_Marmotech"
$SM = Join-Path $Root "$Name.SemanticModel"
$RP = Join-Path $Root "$Name.Report"
if(Test-Path $Root){ Remove-Item $Root -Recurse -Force }
New-Item -ItemType Directory -Force -Path $Root,$SM,(Join-Path $SM "definition"),(Join-Path $SM "definition\tables"),$RP,(Join-Path $RP "definition"),(Join-Path $RP "definition\pages") | Out-Null
function W($path,$content){
$dir = Split-Path $path -Parent
if(!(Test-Path $dir)){ New-Item -ItemType Directory -Force -Path $dir | Out-Null }
# UTF-8 sin BOM
[System.IO.File]::WriteAllText($path, $content, (New-Object System.Text.UTF8Encoding($false)))
}
function G(){ [guid]::NewGuid().ToString() }
$TAB = [char]9
# ---------------- .pbip ----------------
W (Join-Path $Root "$Name.pbip") @"
{
"version": "1.0",
"artifacts": [
{ "report": { "path": "$Name.Report" } }
],
"settings": { "enableAutoRecovery": true }
}
"@
# ---------------- SemanticModel/.platform ----------------
W (Join-Path $SM ".platform") @"
{
"`$schema": "https://developer.microsoft.com/json-schemas/fabric/gitIntegration/platformProperties/2.0.0/schema.json",
"metadata": { "type": "SemanticModel", "displayName": "$Name" },
"config": { "version": "2.0", "logicalId": "$(G)" }
}
"@
# ---------------- SemanticModel/definition.pbism ----------------
W (Join-Path $SM "definition.pbism") @"
{
"version": "4.0",
"settings": {}
}
"@
# ---------------- database.tmdl ----------------
W (Join-Path $SM "definition\database.tmdl") ("database`n${TAB}compatibilityLevel: 1550`n")
# ---------------- model.tmdl ----------------
$model = @()
$model += "model Model"
$model += "${TAB}culture: es-ES"
$model += "${TAB}defaultPowerBIDataSourceVersion: powerBI_V3"
$model += "${TAB}sourceQueryCulture: es-ES"
$model += ""
$model += "annotation PBI_QueryOrder = [`"vwPLMensual`",`"vwGastosDepto`",`"vwBalanceMensual`",`"DimCalendario`"]"
$model += ""
W (Join-Path $SM "definition\model.tmdl") (($model -join "`n") + "`n")
# ---------------- helper: tabla desde vista SQL ----------------
function Table-Sql($tname,$cols){
# cols: array de "nombre|tipo"
$l = @()
$l += "table $tname"
foreach($c in $cols){
$p = $c.Split("|"); $cn=$p[0]; $ct=$p[1]
$l += "${TAB}column '$cn'"
$l += "${TAB}${TAB}dataType: $ct"
$l += "${TAB}${TAB}sourceColumn: $cn"
if($ct -eq "dateTime"){ $l += "${TAB}${TAB}formatString: General Date" }
$l += "${TAB}${TAB}summarizeBy: none"
$l += ""
}
$l += "${TAB}partition $tname = m"
$l += "${TAB}${TAB}mode: import"
$l += "${TAB}${TAB}source ="
$l += "${TAB}${TAB}${TAB}${TAB}let"
$l += "${TAB}${TAB}${TAB}${TAB} Origen = Sql.Database(`"192.168.40.8`", `"MarmotechBI`"),"
$l += "${TAB}${TAB}${TAB}${TAB} Datos = Origen{[Schema=`"presidencia`", Item=`"$tname`"]}[Data]"
$l += "${TAB}${TAB}${TAB}${TAB}in"
$l += "${TAB}${TAB}${TAB}${TAB} Datos"
$l += ""
return ($l -join "`n") + "`n"
}
$plCols = @("ano|int64","numero_mes|int64","mes|string","AnoMes|string","fecha_mes|dateTime","grupo|string","orden_linea|int64","linea|string","subgrupo_cod|string","subgrupo_desc|string","cuenta|string","cuenta_desc|string","nivel|int64","debito|decimal","credito|decimal","resultado|decimal","costo_gasto|decimal")
$gsCols = @("ano|int64","numero_mes|int64","mes|string","AnoMes|string","fecha_mes|dateTime","gerencia_cod|string","gerencia|string","departamento|int64","depto_nombre|string","subgrupo_cod|string","subgrupo_desc|string","cuenta|string","cuenta_desc|string","debito|decimal","credito|decimal","gasto|decimal")
$blCols = @("ano|int64","numero_mes|int64","mes|string","AnoMes|string","fecha_mes|dateTime","grupo|string","grupo_desc|string","subgrupo_cod|string","subgrupo_desc|string","cuenta|string","cuenta_desc|string","movimiento_mes|decimal","saldo_natural|decimal","saldo_presentacion|decimal")
# --- vwPLMensual (aloja las medidas) ---
$pl = Table-Sql "vwPLMensual" $plCols
# medidas
$M = @()
function Meas($n,$expr,$fmt){
$s = @()
$s += "${TAB}measure '$n' = $expr"
if($fmt){ $s += "${TAB}${TAB}formatString: $fmt" }
$s += ""
return ($s -join "`n")
}
$m0 = "`n"
$m0 += Meas "Ventas Netas" "CALCULATE ( SUM ( vwPLMensual[resultado] ), vwPLMensual[grupo] = `"4`" )" "\`$ #,0,,`" M`";\`$ -#,0,,`" M`";\`$ 0"
$m0 += Meas "Costo de Ventas" "CALCULATE ( SUM ( vwPLMensual[costo_gasto] ), vwPLMensual[grupo] = `"5`" )" "\`$ #,0,,`" M`""
$m0 += Meas "Gastos Operativos" "CALCULATE ( SUM ( vwPLMensual[costo_gasto] ), vwPLMensual[grupo] = `"7`" )" "\`$ #,0,,`" M`""
$m0 += Meas "Otros Ing. y Gastos" "CALCULATE ( SUM ( vwPLMensual[costo_gasto] ), vwPLMensual[linea] = `"Otros Ingresos y Gastos`" )" "\`$ #,0,,`" M`""
$m0 += Meas "ISR" "CALCULATE ( SUM ( vwPLMensual[costo_gasto] ), vwPLMensual[linea] = `"Impuesto sobre la Renta`" )" "\`$ #,0,,`" M`""
$m0 += Meas "Utilidad Bruta" "[Ventas Netas] - [Costo de Ventas]" "\`$ #,0,,`" M`""
$m0 += Meas "Utilidad Operativa" "[Utilidad Bruta] - [Gastos Operativos]" "\`$ #,0,,`" M`""
$m0 += Meas "Utilidad antes ISR" "[Utilidad Operativa] - [Otros Ing. y Gastos]" "\`$ #,0,,`" M`""
$m0 += Meas "Utilidad Neta" "SUM ( vwPLMensual[resultado] )" "\`$ #,0,,`" M`""
$m0 += Meas "Margen Bruto %" "DIVIDE ( [Utilidad Bruta], [Ventas Netas] )" "0.0%"
$m0 += Meas "Margen Operativo %" "DIVIDE ( [Utilidad Operativa], [Ventas Netas] )" "0.0%"
$m0 += Meas "Margen Neto %" "DIVIDE ( [Utilidad Neta], [Ventas Netas] )" "0.0%"
$m0 += Meas "Costo % Ventas" "DIVIDE ( [Costo de Ventas], [Ventas Netas] )" "0.0%"
$m0 += Meas "Gastos % Ventas" "DIVIDE ( [Gastos Operativos], [Ventas Netas] )" "0.0%"
$m0 += Meas "Ventas AA" "CALCULATE ( [Ventas Netas], SAMEPERIODLASTYEAR ( DimCalendario[Date] ) )" "\`$ #,0,,`" M`""
$m0 += Meas "Utilidad Neta AA" "CALCULATE ( [Utilidad Neta], SAMEPERIODLASTYEAR ( DimCalendario[Date] ) )" "\`$ #,0,,`" M`""
$m0 += Meas "Ventas YoY %" "DIVIDE ( [Ventas Netas] - [Ventas AA], [Ventas AA] )" "0.0%"
$m0 += Meas "Utilidad YoY %" "DIVIDE ( [Utilidad Neta] - [Utilidad Neta AA], [Utilidad Neta AA] )" "0.0%"
$m0 += Meas "Ventas YTD" "TOTALYTD ( [Ventas Netas], DimCalendario[Date] )" "\`$ #,0,,`" M`""
$m0 += Meas "Utilidad Neta YTD" "TOTALYTD ( [Utilidad Neta], DimCalendario[Date] )" "\`$ #,0,,`" M`""
# medidas de gastos
$m0 += Meas "Gasto Total" "SUM ( vwGastosDepto[gasto] )" "\`$ #,0,,`" M`""
$m0 += Meas "Gasto AA" "CALCULATE ( [Gasto Total], SAMEPERIODLASTYEAR ( DimCalendario[Date] ) )" "\`$ #,0,,`" M`""
$m0 += Meas "Gasto YoY %" "DIVIDE ( [Gasto Total] - [Gasto AA], [Gasto AA] )" "0.0%"
$m0 += Meas "Gasto % de Ventas" "DIVIDE ( [Gasto Total], [Ventas Netas] )" "0.0%"
# medidas de balance
$m0 += Meas "Saldo (fin periodo)" "VAR u = MAX ( vwBalanceMensual[fecha_mes] ) RETURN CALCULATE ( SUM ( vwBalanceMensual[saldo_presentacion] ), vwBalanceMensual[fecha_mes] = u )" "\`$ #,0,,`" M`""
$m0 += Meas "Activos" "CALCULATE ( [Saldo (fin periodo)], vwBalanceMensual[grupo] = `"1`" )" "\`$ #,0,,`" M`""
$m0 += Meas "Pasivos" "CALCULATE ( [Saldo (fin periodo)], vwBalanceMensual[grupo] = `"2`" )" "\`$ #,0,,`" M`""
$m0 += Meas "Patrimonio" "CALCULATE ( [Saldo (fin periodo)], vwBalanceMensual[grupo] = `"3`" )" "\`$ #,0,,`" M`""
$m0 += Meas "Activo Corriente" "CALCULATE ( [Saldo (fin periodo)], vwBalanceMensual[subgrupo_cod] = `"11`" )" "\`$ #,0,,`" M`""
$m0 += Meas "Pasivo Corriente" "CALCULATE ( [Saldo (fin periodo)], vwBalanceMensual[subgrupo_cod] IN {`"20`",`"21`"} )" "\`$ #,0,,`" M`""
$m0 += Meas "Razon Corriente" "DIVIDE ( [Activo Corriente], [Pasivo Corriente] )" "0.00"
$m0 += Meas "Capital de Trabajo" "[Activo Corriente] - [Pasivo Corriente]" "\`$ #,0,,`" M`""
$m0 += Meas "Endeudamiento %" "DIVIDE ( [Pasivos], [Activos] )" "0.0%"
# insertar medidas antes de la particion (deben ir dentro de la tabla, antes de 'partition')
$pl = $pl -replace "(?s)(${TAB}partition vwPLMensual = m)", ($m0 + "`$1")
W (Join-Path $SM "definition\tables\vwPLMensual.tmdl") $pl
W (Join-Path $SM "definition\tables\vwGastosDepto.tmdl") (Table-Sql "vwGastosDepto" $gsCols)
W (Join-Path $SM "definition\tables\vwBalanceMensual.tmdl") (Table-Sql "vwBalanceMensual" $blCols)
# ---------------- DimCalendario (tabla calculada) ----------------
$dc = @()
$dc += "table DimCalendario"
foreach($c in @("Date|dateTime","Anio|int64","MesNo|int64","Mes|string","AnioMes|string","PrimerDiaMes|dateTime","Trimestre|string")){
$p=$c.Split("|");$cn=$p[0];$ct=$p[1]
$dc += "${TAB}column $cn"
$dc += "${TAB}${TAB}dataType: $ct"
$dc += "${TAB}${TAB}sourceColumn: [$cn]"
if($ct -eq "dateTime"){ $dc += "${TAB}${TAB}formatString: General Date" }
$dc += "${TAB}${TAB}summarizeBy: none"
$dc += ""
}
$dax = 'ADDCOLUMNS ( CALENDAR ( DATE ( 2015, 1, 1 ), DATE ( 2028, 12, 31 ) ), "Anio", YEAR ( [Date] ), "MesNo", MONTH ( [Date] ), "Mes", FORMAT ( [Date], "MMM" ), "AnioMes", FORMAT ( [Date], "yyyy-MM" ), "PrimerDiaMes", DATE ( YEAR ( [Date] ), MONTH ( [Date] ), 1 ), "Trimestre", "T" & FORMAT ( [Date], "Q" ) )'
$dc += "${TAB}partition DimCalendario = calculated"
$dc += "${TAB}${TAB}mode: import"
$dc += "${TAB}${TAB}source = $dax"
$dc += ""
W (Join-Path $SM "definition\tables\DimCalendario.tmdl") (($dc -join "`n") + "`n")
# ---------------- relationships.tmdl ----------------
$rel = @()
foreach($t in @("vwPLMensual","vwGastosDepto","vwBalanceMensual")){
$rel += "relationship $(G)"
$rel += "${TAB}fromColumn: $t.fecha_mes"
$rel += "${TAB}toColumn: DimCalendario.Date"
$rel += ""
}
W (Join-Path $SM "definition\relationships.tmdl") (($rel -join "`n") + "`n")
Write-Output "SemanticModel escrito."
# =====================================================================
# REPORTE (PBIR)
# =====================================================================
W (Join-Path $RP ".platform") @"
{
"`$schema": "https://developer.microsoft.com/json-schemas/fabric/gitIntegration/platformProperties/2.0.0/schema.json",
"metadata": { "type": "Report", "displayName": "$Name" },
"config": { "version": "2.0", "logicalId": "$(G)" }
}
"@
W (Join-Path $RP "definition.pbir") @"
{
"version": "4.0",
"datasetReference": { "byPath": { "path": "../$Name.SemanticModel" } }
}
"@
# PBIR exige version.json dentro de definition/
W (Join-Path $RP "definition\version.json") @"
{
"version": "2.0.0"
}
"@
W (Join-Path $RP "definition\report.json") @"
{
"`$schema": "https://developer.microsoft.com/json-schemas/fabric/item/report/definition/report/1.0.0/schema.json",
"themeCollection": { "baseTheme": { "name": "CY24SU10" } },
"layoutOptimization": "None"
}
"@
# ---- helpers de visuales PBIR ----
function Field-Measure($entity,$prop){
return @"
{ "field": { "Measure": { "Expression": { "SourceRef": { "Entity": "$entity" } }, "Property": "$prop" } }, "queryRef": "$entity.$prop", "nativeQueryRef": "$prop" }
"@
}
function Field-Column($entity,$prop){
return @"
{ "field": { "Column": { "Expression": { "SourceRef": { "Entity": "$entity" } }, "Property": "$prop" } }, "queryRef": "$entity.$prop", "nativeQueryRef": "$prop" }
"@
}
# roles: hashtable rol -> array de proyecciones json
function Visual($pageDir,$vtype,$x,$y,$w,$h,$roles,$title){
$vid = G
$vdir = Join-Path $pageDir "visuals\$vid"
New-Item -ItemType Directory -Force -Path $vdir | Out-Null
$projBlocks = @()
foreach($r in $roles.Keys){
$items = ($roles[$r] -join ",`n")
$projBlocks += "`"$r`": { `"projections`": [ $items ] }"
}
$queryState = ($projBlocks -join ",`n")
$titleObj = ""
if($title){
$titleObj = @"
,
"objects": {
"title": [ { "properties": { "text": { "expr": { "Literal": { "Value": "'$title'" } } }, "show": { "expr": { "Literal": { "Value": "true" } } } } } ]
}
"@
}
$json = @"
{
"`$schema": "https://developer.microsoft.com/json-schemas/fabric/item/report/definition/visualContainer/1.0.0/schema.json",
"name": "$vid",
"position": { "x": $x, "y": $y, "z": 0, "width": $w, "height": $h, "tabOrder": 0 },
"visual": {
"visualType": "$vtype",
"query": { "queryState": { $queryState } },
"drillFilterOtherVisuals": true$titleObj
}
}
"@
W (Join-Path $vdir "visual.json") $json
return $vid
}
$pageIds = @()
# ============ PAGINA 1: Radiografia ============
$p1 = G; $pageIds += $p1
$p1dir = Join-Path $RP "definition\pages\$p1"
New-Item -ItemType Directory -Force -Path $p1dir | Out-Null
W (Join-Path $p1dir "page.json") @"
{
"`$schema": "https://developer.microsoft.com/json-schemas/fabric/item/report/definition/page/1.0.0/schema.json",
"name": "$p1",
"displayName": "Radiografia",
"displayOption": "FitToPage",
"height": 720,
"width": 1280
}
"@
[void](Visual $p1dir "card" 16 16 280 140 @{ "Values" = @((Field-Measure "vwPLMensual" "Ventas Netas")) } "Ventas Netas")
[void](Visual $p1dir "card" 312 16 280 140 @{ "Values" = @((Field-Measure "vwPLMensual" "Utilidad Bruta")) } "Utilidad Bruta")
[void](Visual $p1dir "card" 688 16 280 140 @{ "Values" = @((Field-Measure "vwPLMensual" "Utilidad Operativa")) } "Utilidad Operativa")
[void](Visual $p1dir "card" 984 16 280 140 @{ "Values" = @((Field-Measure "vwPLMensual" "Utilidad Neta")) } "Utilidad Neta")
[void](Visual $p1dir "waterfallChart" 16 170 620 300 @{ "Category" = @((Field-Column "vwPLMensual" "linea")); "Y" = @((Field-Measure "vwPLMensual" "Utilidad Neta")) } "Estado de Resultados (cascada)")
[void](Visual $p1dir "lineClusteredColumnComboChart" 652 170 612 300 @{ "Category" = @((Field-Column "DimCalendario" "Mes")); "Y" = @((Field-Measure "vwPLMensual" "Ventas Netas")); "Y2" = @((Field-Measure "vwPLMensual" "Utilidad Neta")) } "Comportamiento del periodo (Ventas vs Utilidad)")
[void](Visual $p1dir "clusteredColumnChart" 16 484 620 220 @{ "Category" = @((Field-Column "DimCalendario" "Anio")); "Y" = @((Field-Measure "vwPLMensual" "Ventas Netas"),(Field-Measure "vwPLMensual" "Utilidad Neta")) } "Trayectoria anual")
[void](Visual $p1dir "slicer" 652 484 300 120 @{ "Values" = @((Field-Column "DimCalendario" "Anio")) } "Ano")
# ============ PAGINA 2: Gastos ============
$p2 = G; $pageIds += $p2
$p2dir = Join-Path $RP "definition\pages\$p2"
New-Item -ItemType Directory -Force -Path $p2dir | Out-Null
W (Join-Path $p2dir "page.json") @"
{
"`$schema": "https://developer.microsoft.com/json-schemas/fabric/item/report/definition/page/1.0.0/schema.json",
"name": "$p2",
"displayName": "Analisis de Gastos",
"displayOption": "FitToPage",
"height": 720,
"width": 1280
}
"@
[void](Visual $p2dir "card" 16 16 300 140 @{ "Values" = @((Field-Measure "vwPLMensual" "Gasto Total")) } "Gastos Operativos")
[void](Visual $p2dir "card" 332 16 300 140 @{ "Values" = @((Field-Measure "vwPLMensual" "Gasto % de Ventas")) } "Gasto / Ventas")
[void](Visual $p2dir "barChart" 16 170 620 300 @{ "Category" = @((Field-Column "vwGastosDepto" "gerencia")); "Y" = @((Field-Measure "vwPLMensual" "Gasto Total")) } "Gasto por gerencia")
[void](Visual $p2dir "tableEx" 652 170 612 300 @{ "Values" = @((Field-Column "vwGastosDepto" "subgrupo_desc"),(Field-Measure "vwPLMensual" "Gasto Total"),(Field-Measure "vwPLMensual" "Gasto % de Ventas")) } "Naturaleza del gasto")
[void](Visual $p2dir "columnChart" 16 484 1248 220 @{ "Category" = @((Field-Column "DimCalendario" "Mes")); "Y" = @((Field-Measure "vwPLMensual" "Gasto Total")) } "Gasto mensual")
# ============ PAGINA 3: Estructura ============
$p3 = G; $pageIds += $p3
$p3dir = Join-Path $RP "definition\pages\$p3"
New-Item -ItemType Directory -Force -Path $p3dir | Out-Null
W (Join-Path $p3dir "page.json") @"
{
"`$schema": "https://developer.microsoft.com/json-schemas/fabric/item/report/definition/page/1.0.0/schema.json",
"name": "$p3",
"displayName": "Estructura Financiera",
"displayOption": "FitToPage",
"height": 720,
"width": 1280
}
"@
[void](Visual $p3dir "card" 16 16 300 140 @{ "Values" = @((Field-Measure "vwPLMensual" "Activos")) } "Activos Totales")
[void](Visual $p3dir "card" 332 16 300 140 @{ "Values" = @((Field-Measure "vwPLMensual" "Razon Corriente")) } "Razon Corriente")
[void](Visual $p3dir "card" 648 16 300 140 @{ "Values" = @((Field-Measure "vwPLMensual" "Capital de Trabajo")) } "Capital de Trabajo")
[void](Visual $p3dir "card" 964 16 300 140 @{ "Values" = @((Field-Measure "vwPLMensual" "Endeudamiento %")) } "Endeudamiento")
[void](Visual $p3dir "clusteredColumnChart" 16 170 620 300 @{ "Category" = @((Field-Column "vwBalanceMensual" "grupo_desc")); "Y" = @((Field-Measure "vwPLMensual" "Saldo (fin periodo)")) } "Activos / Pasivos / Patrimonio")
[void](Visual $p3dir "tableEx" 652 170 612 300 @{ "Values" = @((Field-Column "vwBalanceMensual" "subgrupo_desc"),(Field-Measure "vwPLMensual" "Saldo (fin periodo)")) } "Composicion")
# ---------------- pages.json ----------------
$order = ($pageIds | ForEach-Object { "`"$_`"" }) -join ", "
W (Join-Path $RP "definition\pages\pages.json") @"
{
"`$schema": "https://developer.microsoft.com/json-schemas/fabric/item/report/definition/pagesMetadata/1.0.0/schema.json",
"pageOrder": [ $order ],
"activePageName": "$($pageIds[0])"
}
"@
Write-Output "Reporte escrito."
Write-Output ("PROYECTO GENERADO EN: " + $Root)
Get-ChildItem -Recurse $Root | Measure-Object | ForEach-Object { Write-Output ("Archivos: " + $_.Count) }
+1 -1
View File
@@ -70,7 +70,7 @@
</Settings>
<LanguageList version="1.0"/>
<PlatformList version="1.0"/>
<Group id="-2057013643" label="COMUNES" language="Genero">
<Group environment="value:CLASSPATH=$(ProjectDir)\\classes\\javax.mail.jar;$(CLASSPATH)" id="-2057013643" label="COMUNES" language="Genero">
<Library binaryName="Config" id="1765711908" label="Config">
<File filePath="config/Application.xcf"/>
</Library>