Serie: Power BI para Gestión de Programas Cloud
Prerrequisito: Artículo 1 — Power Query: conexión y transformaciones del Excel

Introducción

En el artículo anterior conectamos el Excel al Power BI y preparamos los datos en Power Query. Ahora toca tomar la decisión más importante antes de escribir una sola medida DAX: ¿cómo estructuramos el modelo de datos?

Esta decisión afecta al rendimiento, a la legibilidad del DAX, a la facilidad de mantenimiento y a cómo escala el modelo si el programa crece. No hay una respuesta única correcta — depende del contexto. En este artículo presento los dos enfoques principales, sus ventajas e inconvenientes, y los criterios para elegir uno u otro.

Los dos enfoques

Opción A: Tabla plana (Flat Table)

Todos los atributos del servicio en una sola tabla. Cada fila es un servicio; cada columna, un atributo.

AllServices
├── service_name
├── WAVE
├── MIGRATION SCOPE
├── MIGRATION STRATEGY
├── Environment
├── Infra_type
├── Criticality
├── PROVEEDOR Implementation Status
├── Fecha esperada de migración
├── Fecha de migración/monitorización
└── … (31 columnas)

El modelo queda así: dos tablasAllServices y Calendario — con una (o varias) relaciones entre ellas.


Opción B: Star Schema (Esquema en estrella)

Los atributos categóricos se separan en tablas de dimensiones independientes. La tabla central (FactServicios) contiene solo hechos medibles y claves foráneas.

dim_Wave          dim_Scope         dim_Strategy
    │                 │                  │
    └─────────────────┴──────────────────┘
                       │
                 FactServicios         Calendario
                 (claves + fechas          │
                  + métricas)         ────┘

Las tablas de dimensiones típicas para nuestro modelo serían:

Tabla dimensiónColumnas clave
dim_WaveWaveID, Wave, OrdenWave
dim_ScopeScopeID, MigrationScope, TipoScope (ThirdParty / InHouse)
dim_StrategyStrategyID, MigrationStrategy
dim_EnvironmentEnvironmentID, Environment
dim_CriticalityCriticalityID, Criticality, NivelPrioridad
dim_ProveedorProveedorID, ProveedorNombre
CalendarioDate, Año, Trimestre, Mes, Semana…

Comparativa directa

CriterioTabla planaStar Schema
Complejidad inicialBaja — una tabla, listoAlta — hay que crear y relacionar dimensiones
RendimientoBueno hasta ~100k filasÓptimo a cualquier escala; VertiPaq comprime mejor columnas de baja cardinalidad en dims separadas
Legibilidad del DAXMedidas más largas con filtros explícitosMedidas más limpias; los filtros viajan por las relaciones automáticamente
MantenimientoFácil si el Excel no cambia de estructuraMás robusto ante cambios: modificar una dimensión no afecta a la tabla de hechos
Valores duplicadosSí — «ZEUS (ThirdParty)» se repite en 8 filasNo — cada scope aparece una sola vez en dim_Scope
Filtros cruzadosRequieren CROSSFILTER o BOTH en relacionesFluyen naturalmente de dim → fact
Curva de aprendizajeMenorMayor — hay que entender cardinalidad y dirección de filtros
Fuente única (Excel)IdealRequiere generar las dims en Power Query o DAX

¿Cuándo usar cada uno?

Usa tabla plana cuando:

  • El origen de datos es un único Excel que controlas tú directamente.
  • El volumen de datos es menor de 100.000 filas.
  • El equipo que mantiene el modelo tiene experiencia limitada en Power BI.
  • El programa tiene fecha de fin definida y el modelo no necesita escalar indefinidamente.
  • La velocidad de desarrollo importa más que la arquitectura perfecta.

Usa star schema cuando:

  • Los datos vienen de múltiples fuentes (Excel + Jira + ServiceNow + Azure Cost Management).
  • El volumen supera las 100.000 filas o crece de forma continua.
  • Varios equipos van a construir medidas DAX sobre el mismo modelo.
  • Las dimensiones cambian con frecuencia (nuevos scopes, nuevas waves) y quieres gestionar esos cambios en un único sitio.
  • El rendimiento de las consultas empieza a ser perceptible.

El modelo de esta serie

En este programa trabajamos con un Excel de control único, ~500 servicios, y un equipo técnico pequeño. La tabla plana es la elección correcta por pragmatismo: nos permite ir directo a construir los dashboards sin invertir tiempo en arquitectura.

Aun así, introducimos un elemento del star schema que sí vale la pena en cualquier caso: la tabla Calendario separada. Esta tabla es obligatoria para las funciones de Time Intelligence de DAX (DATEADD, TOTALYTD, acumulados…) y no tiene coste de complejidad significativo.

El modelo final de esta serie tiene esta estructura:

Calendario ──(activa)──────► AllServices[Fecha esperada de migración]
           ──(inactiva)────► AllServices[Fecha de migración/monitorización]
           ──(inactiva)────► AllServices[Fecha de decomisionado]
           ──(inactiva)────► AllServices[Migration window]

Las relaciones inactivas se activan dentro de cada medida DAX con USERELATIONSHIP(). Este patrón — una tabla de fechas con múltiples roles — es el más habitual en modelos de seguimiento de programas y lo veremos en detalle en el artículo del Burn Up.

Nota sobre escalabilidad: Si en el futuro el programa incorpora datos de coste (AWS Cost Explorer, Azure Billing) o de gestión de incidencias (Jira, ServiceNow), ese sería el momento natural de migrar a star schema. La tabla AllServices se convertiría en FactServicios, y las columnas categóricas se extraerían a dimensiones. La migración es mecánica si el DAX está bien escrito.


Crear la tabla Calendario en DAX

Si no la creaste en el Artículo 1, aquí está la definición completa. En Power BI Desktop: Home → New Table y pega este código:

dax

Calendario =
ADDCOLUMNS(
    CALENDAR(DATE(2025, 1, 1), DATE(2026, 12, 31)),
    "Año",          YEAR([Date]),
    "Trimestre",    "Q" & QUARTER([Date]),
    "Mes Num",      MONTH([Date]),
    "Mes Nombre",   FORMAT([Date], "MMMM"),
    "Mes Corto",    FORMAT([Date], "MMM"),
    "Semana",       WEEKNUM([Date], 2),
    "Día Semana",   WEEKDAY([Date], 2),
    "Día Nombre",   FORMAT([Date], "dddd"),
    "AñoMes",       FORMAT([Date], "YYYY-MM"),
    "AñoTrimestre", FORMAT([Date], "YYYY") & "-" & "Q" & QUARTER([Date])
)

Una vez creada, ve a la vista de Modelo → clic derecho en CalendarioMark as Date Table → selecciona la columna Date. Esto es obligatorio para que las funciones de Time Intelligence funcionen correctamente.

Por qué CALENDAR y no CALENDARAUTO

CALENDARAUTO genera automáticamente el rango de fechas basándose en las columnas de fecha del modelo. Es cómoda pero frágil: si una fecha errónea llega en el Excel (por ejemplo, un valor 01/01/1900 por un error de formato), la tabla Calendario se expandirá hasta esa fecha y las funciones de acumulado dejarán de funcionar bien. CALENDAR con fechas explícitas es más predecible y segura en modelos alimentados por Excel.


Establecer las relaciones

Con la tabla Calendario creada y marcada, ve a la vista de Modelo y arrastra:

1. Relación activa (la que Power BI usa por defecto):

  • Calendario[Date]AllServices[Fecha esperada de migración]
  • Cardinalidad: Uno a muchos (1:*)
  • Dirección de filtro: Single (Calendario filtra AllServices)

2. Relaciones inactivas (se activarán en DAX con USERELATIONSHIP):

  • Calendario[Date]AllServices[Fecha de migración/monitorización]
  • Calendario[Date]AllServices[Fecha de decomisionado]
  • Calendario[Date]AllServices[Migration window]

Power BI no permite más de una relación activa entre el mismo par de tablas. Las relaciones inactivas existen en el modelo pero no filtran datos por defecto — solo se activan cuando una medida DAX las invoca explícitamente con USERELATIONSHIP().

Dirección de filtro: Single vs. Both

Mantén todas las relaciones en dirección Single (Calendario → AllServices). La dirección Both (bidireccional) puede parecer conveniente pero introduce ambigüedad en el filtrado y es una fuente habitual de resultados incorrectos en modelos con múltiples relaciones. En un modelo de tabla plana con una sola tabla de hechos no hay ningún escenario que requiera filtrado bidireccional.


Verificación del modelo

Antes de pasar a escribir medidas DAX, comprueba en la vista de Modelo que:

  • La tabla Calendario tiene el icono de calendario (📅) en la columna Date — indica que está marcada como Date Table.
  • Hay exactamente una línea continua (relación activa) y tres líneas discontinuas (relaciones inactivas) entre Calendario y AllServices.
  • Todas las relaciones son de cardinalidad 1:* (uno en Calendario, muchos en AllServices).
  • No hay relaciones entre AllServices y sí misma.

Próximo artículo

Con el modelo de datos listo y las relaciones establecidas, en el Artículo 3 construiremos las medidas DAX fundamentales del programa:

  • # Total Elementos — base de todos los ratios
  • # Migrados — conteo real de servicios en estado DONE
  • # Por Migrar — lo que queda
  • % Completado — ratio de progreso
  • # Planificado Acumulado — línea planificada del Burn Up
  • # Real Acumulado — línea real del Burn Up
  • # Fecha Estimada Fin — proyección lineal hacia el fin de contrato

Cada medida vendrá acompañada del código DAX y la explicación de por qué está escrita así — incluyendo los patrones de CALCULATE, USERELATIONSHIP y FILTER que aparecen una y otra vez en este tipo de modelos.


Recursos del artículo: Excel mock (All_Services_AWS_MOCK.xlsx) disponible para descarga. Los datos son ficticios y no contienen información sensible.


Deja una respuesta