Files
MBS/Dashboards MARMOTECH/build_pbip_presidencia.ps1

343 lines
17 KiB
PowerShell

<# 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) }