Acompañar la evolución del modelo
La documentación debería mantenerse alineada con los cambios del modelo para no describir una versión que ya no existe.
¿Cómo podemos construir documentación en Power BI que acompañe la evolución de nuestra solución analítica?
Una guía para diseñar documentación que vive dentro del modelo semántico de Power BI y se consulta desde el tablero, con buenas prácticas y prompts listos para copiar y pegar.
Primero entendemos el problema antes de construir una solución.
El dashboard está al día. El documento que explica cómo funciona sigue describiendo la versión anterior. Seis meses después, alguien —quizás vos— abre el modelo y necesita responder:
El modelo cambia a medida que evoluciona el tablero. Por eso, la documentación no debería tratarse como un entregable cerrado, sino como un activo que acompaña esa evolución: una parte puede generarse y actualizarse desde el propio modelo y otra necesita el criterio del equipo.
| Si documentamos fuera del modelo | Si documentamos en el modelo |
|---|---|
| Hay que acordarse de actualizarla | Los metadatos se actualizan con cada refresh |
| Puede describir una versión anterior | Refleja el modelo semántico tal como está |
| Hay que ir a buscarla | Se puede consultar desde el tablero |
| Todo depende del mantenimiento manual | Algunas partes pueden sistematizarse o automatizarse |
Documentar dentro de Power BI no significa que todo se genere automáticamente. Significa diseñar un sistema para no volver a escribir a mano lo que el modelo ya sabe de sí mismo.
Lo que el modelo sabe de sí mismo.
Lo que aportamos junto con cada cambio, antes de llevarlo a producción.
Lo que conviene mantener fuera del modelo.
Hasta acá entendimos el problema y distinguimos qué parte de la documentación puede acompañar al modelo y qué parte sigue necesitando criterio. Antes de pensar cómo resolverlo, bajemos eso a necesidades concretas.
La documentación debería mantenerse alineada con los cambios del modelo para no describir una versión que ya no existe.
Con estas necesidades en mente, podemos empezar a imaginar una página de documentación que viva dentro de la propia solución analítica.
El objetivo de esta documentación es que puedas entender cómo está organizado el modelo, qué representa cada elemento y qué reglas necesitás conocer para interpretarlo correctamente.
| Columna | Tipo | Descripción |
|---|---|---|
| Units | Int64 | Unidades vendidas por línea de transacción. |
| Revenue | Decimal | — |
| ProductID | Int64 | Clave hacia dim_product. |
Prototipo simplificado de la página de documentación. Los vacíos permanecen visibles cuando todavía falta contexto por completar.
Con una idea acerca de nuestro destino, el siguiente paso es probar cuánto de esa página podemos construir directamente a partir del modelo.
Llevamos la idea al modelo y vemos qué pasa.
Pasos: Chequeamos el modelo (04), implementamos (05), completamos las definiciones (06), detectamos lo que falta (07) y armamos la página completa (08).
Cinco cosas antes de documentar. No son del contenido de la documentación: son del modelo. Se arreglan una vez y quedan.
Documentar un modelo desordenado te da documentación de un desorden. Y buena parte de por qué la página del ejemplo sale prolija es que ese modelo ya estaba ordenado. Este paso pone a todos en la misma posición de partida.
Cómo lo ves: en la vista de modelo, si aparecen tablas LocalDateTable_ con un GUID, están activas.
Cómo lo arreglás: Archivo › Opciones y configuración › Opciones, sección Inteligencia de tiempo. Antes de desactivarla, revisá que ningún visual use esas jerarquías de fecha, porque se rompen.
Por qué importa: vienen ocultas, así que no ensucian la documentación. Pero inflan el archivo y llenan la vista de relaciones de líneas que no son del negocio.
Cómo lo ves: clic derecho en tu tabla de calendario. Si dice “Marcar como tabla de fechas”, no está marcada.
Cómo lo arreglás: marcala. Necesita una columna de fecha continua, sin huecos ni repetidos.
Por qué importa: sin eso, la inteligencia de tiempo puede devolver resultados incorrectos sin avisar.
Cómo lo ves: ¿tenés una o más tablas que solo contienen medidas, o están desparramadas dentro de las tablas de datos?
Cómo lo arreglás: creá una tabla vacía con Entrada de datos, borrale la columna y mové las medidas ahí.
Lo que recomiendo: dos tablas. Una para la lógica del modelo semántico y otra para las medidas que resuelven visualización o interfaz: colores, textos dinámicos, HTML, SVG. Son dos cosas distintas y conviene leerlas separadas. La documentación agrupa las medidas por la tabla que las contiene, así que con una tabla ves un bloque y con dos la separación aparece sola.
Cómo lo ves: en el panel Datos, ¿hay medidas colgando sueltas fuera de toda carpeta?
Cómo lo arreglás: asigná una carpeta por familia de métricas. En lote se hace desde la vista TMDL con la propiedad displayFolder.
El criterio: un único lugar por medida y ninguna sin clasificar. Power BI permite que una medida aparezca en varias carpetas separándolas con punto y coma, pero la documentación agrupa por el texto de DisplayFolder y una medida en dos carpetas aparece como una categoría combinada.
Cómo lo ves: una medida con error muestra un triángulo en el panel Datos, o devuelve error al usarla en un visual.
Cómo lo arreglás: revisá la expresión. Suele ser una referencia a una columna o tabla que se renombró o se borró.
Por qué importa: documentar una medida que no evalúa deja la documentación describiendo algo que no funciona.
Cuatro pasos hasta ver la primera página. Esta versión no decide nada: muestra lo que el modelo ya sabe de sí mismo.
Creá una tabla calculada por cada función INFO.VIEW, con el prefijo DOC_.
DOC_tablas = INFO.VIEW.TABLES()
DOC_columnas = INFO.VIEW.COLUMNS()
DOC_medidas = INFO.VIEW.MEASURES()
DOC_relaciones = INFO.VIEW.RELATIONSHIPS()
Agrupalas en una zona reconocible de la vista de modelo, por ejemplo a la izquierda: así son lo primero que encuentra otra persona del equipo.
En Obtener más objetos visuales, buscá “html content” y elegí HTML Content (lite) de Daniel Marsh-Patrick, el que tiene la insignia azul.
La versión lite sanea lo que recibe. Conviene saberlo antes de editar la medida, porque lo que no admite no da error: lo elimina y sigue.
div, p, span, títulos, tablas, listasuse, script y foreignObjectclass y bloques <style>on*iframe, link, form@import, @font-face// dentro de <style>Fuente: HTML Content · Sanitization y Accepted Tags.
Va en tu tabla de medidas de visualización. Esta primera versión hace algo deliberadamente simple: toma los metadatos que acabamos de exponer y los renderiza sin intentar decidir qué función cumple cada tabla. Si falta una descripción, el hueco queda visible.
HTML Documentación = // Para reutilizar esta lógica con otras tablas de introspección, ajustá UNICAMENTE este bloque del principio. // Vamos a estandarizar las tablas de introspección a una estructura interna. Desde este punto, el resto de la medida trabaja únicamente con estos nombres claros. VAR DocumentacionTablas = //Reemplaza todos los DOC_tablas de esta variable por tu tabla de documentación de tablas.Te la toma cuando es azul, sino es gris. SELECTCOLUMNS ( DOC_tablas, "NombreTabla", DOC_tablas[Name], "DescripcionTabla", COALESCE ( DOC_tablas[Description], "" ), "TablaOculta", DOC_tablas[IsHidden] ) VAR DocumentacionColumnas = //Reemplaza todos los DOC_columnas de esta variable por tu tabla de documentación de columnas.Te la toma cuando es azul, sino es gris. SELECTCOLUMNS ( DOC_columnas, "NombreTabla", DOC_columnas[Table], "NombreColumna", DOC_columnas[Name], "TipoDato", DOC_columnas[DataType], "DescripcionColumna", COALESCE ( DOC_columnas[Description], "" ) ) VAR DocumentacionMedidas = //Reemplaza todos los DOC_medidas de esta variable por tu tabla de documentación de medidas. SELECTCOLUMNS ( DOC_medidas, "NombreTabla", DOC_medidas[Table], "NombreMedida", DOC_medidas[Name], "Carpeta", COALESCE ( DOC_medidas[DisplayFolder], "" ), "DescripcionMedida", COALESCE ( DOC_medidas[Description], "" ), "MedidaOculta", DOC_medidas[IsHidden] ) VAR DocumentacionRelaciones = //Reemplaza todos los DOC_relaciones de esta variable por tu tabla de documentación de relaciones. SELECTCOLUMNS ( DOC_relaciones, "TablaOrigen", DOC_relaciones[FromTable], "ColumnaOrigen", DOC_relaciones[FromColumn], "CardinalidadOrigen", DOC_relaciones[FromCardinality], "TablaDestino", DOC_relaciones[ToTable], "ColumnaDestino", DOC_relaciones[ToColumn], "CardinalidadDestino", DOC_relaciones[ToCardinality], "DireccionFiltro", DOC_relaciones[CrossFilteringBehavior], "RelacionActiva", DOC_relaciones[IsActive] ) // Listo. Acá empiezan los bloques que levantan la documentación de manera automática. Espero que te sirva! Loa 💛 VAR TablasADocumentar = FILTER ( DocumentacionTablas, NOT [TablaOculta] && LEFT ( [NombreTabla], 4 ) <> "doc_" ) VAR NombresTablas = SELECTCOLUMNS ( TablasADocumentar, "NombreTabla", [NombreTabla] ) VAR ColumnasADocumentar = FILTER ( DocumentacionColumnas, [NombreTabla] IN NombresTablas ) VAR MedidasADocumentar = FILTER ( DocumentacionMedidas, NOT [MedidaOculta] && [NombreTabla] IN NombresTablas ) VAR RelacionesADocumentar = FILTER ( DocumentacionRelaciones, [TablaOrigen] IN NombresTablas && [TablaDestino] IN NombresTablas ) VAR Fuente = "Segoe UI Variable Text,Segoe UI,system-ui,Arial,sans-serif" VAR FuenteMono = "Consolas,Menlo,monospace" VAR EstiloReset = "<style> html,body{ margin:0; padding:0; height:100%; width:100%; overflow:hidden; } *{ box-sizing:border-box; } p{ margin:0; } table{ table-layout:fixed; } </style>" VAR EstiloContenedor = "position:fixed; inset:0; overflow:hidden; background:transparent;" VAR EstiloTarjeta = "background:#FFFFFF; border:0.5px solid #E4E4E4; border-radius:12px; padding:20px 3%; width:100%; height:100%; overflow-y:auto; overflow-x:hidden; font-family:" & Fuente & ";" VAR EstiloTitulo = "font-size:19px; font-weight:600; color:#1A1A1A; margin:0 0 6px;" VAR EstiloSubtitulo = "font-size:15px; font-weight:600; color:#1A1A1A; margin:26px 0 8px;" VAR EstiloParrafo = "font-size:12.5px; line-height:1.55; color:#6B6B6B; margin:0 0 10px;" VAR EstiloTabla = "width:100%; border-collapse:collapse; margin:6px 0 4px;" VAR EstiloEncabezado = "text-align:left; font-size:11px; font-weight:600; color:#6B6B6B; text-transform:uppercase; letter-spacing:0.4px; padding:7px 10px; background:#FAFBFF; border-bottom:1px solid #E4E4E4; overflow-wrap:anywhere;" VAR EstiloNombre = "font-family:" & FuenteMono & "; font-size:11.5px; color:#4752CE; padding:7px 10px; border-bottom:1px solid #F0F0F0; vertical-align:top; word-break:break-word; overflow-wrap:anywhere;" VAR EstiloMetadata = "font-size:11px; color:#9B9B9B; padding:7px 10px; border-bottom:1px solid #F0F0F0; vertical-align:top; word-break:break-word; overflow-wrap:anywhere;" VAR EstiloDescripcion = "font-size:12px; color:#6B6B6B; padding:7px 10px; border-bottom:1px solid #F0F0F0; vertical-align:top; line-height:1.45; word-break:break-word; overflow-wrap:anywhere;" VAR EstiloFalta = "font-size:11.5px; color:#B9B9C6; font-style:italic;" VAR EstiloEstado = "display:inline-block; background:#F8EFDD; color:#8A5A12; border-radius:4px; padding:1px 5px; font-size:10px; font-weight:600;" VAR FaltaDescripcion = "<span style='" & EstiloFalta & "'>Falta la descripción</span>" VAR SinTablas = "<p style='" & EstiloParrafo & "'>No hay tablas visibles para documentar.</p>" VAR SinColumnas = "<p style='" & EstiloParrafo & "'>No hay columnas para mostrar en las tablas documentadas.</p>" VAR SinRelaciones = "<p style='" & EstiloParrafo & "'>No hay relaciones entre las tablas documentadas.</p>" VAR SinMedidas = "<p style='" & EstiloParrafo & "'>No hay medidas visibles para documentar.</p>" VAR HTMLTablas = CONCATENATEX ( TablasADocumentar, VAR NombreTabla = [NombreTabla] VAR DescripcionTabla = [DescripcionTabla] VAR NombreTablaSeguro = SUBSTITUTE ( SUBSTITUTE ( SUBSTITUTE ( NombreTabla, "&", "&" ), "<", "<" ), ">", ">" ) VAR DescripcionTablaSegura = SUBSTITUTE ( SUBSTITUTE ( SUBSTITUTE ( DescripcionTabla, "&", "&" ), "<", "<" ), ">", ">" ) RETURN "<tr>" & "<td style='" & EstiloNombre & "'>" & NombreTablaSeguro & "</td>" & "<td style='" & EstiloDescripcion & "'>" & IF ( DescripcionTabla = "", FaltaDescripcion, DescripcionTablaSegura ) & "</td>" & "</tr>", "", [NombreTabla], ASC ) VAR HTMLColumnas = CONCATENATEX ( ColumnasADocumentar, VAR NombreTabla = [NombreTabla] VAR NombreColumna = [NombreColumna] VAR TipoDato = [TipoDato] VAR DescripcionColumna = [DescripcionColumna] VAR NombreTablaSeguro = SUBSTITUTE ( SUBSTITUTE ( SUBSTITUTE ( NombreTabla, "&", "&" ), "<", "<" ), ">", ">" ) VAR NombreColumnaSeguro = SUBSTITUTE ( SUBSTITUTE ( SUBSTITUTE ( NombreColumna, "&", "&" ), "<", "<" ), ">", ">" ) VAR TipoDatoSeguro = SUBSTITUTE ( SUBSTITUTE ( SUBSTITUTE ( TipoDato, "&", "&" ), "<", "<" ), ">", ">" ) VAR DescripcionColumnaSegura = SUBSTITUTE ( SUBSTITUTE ( SUBSTITUTE ( DescripcionColumna, "&", "&" ), "<", "<" ), ">", ">" ) RETURN "<tr>" & "<td style='" & EstiloMetadata & "'>" & NombreTablaSeguro & "</td>" & "<td style='" & EstiloNombre & "'>" & NombreColumnaSeguro & "</td>" & "<td style='" & EstiloMetadata & "'>" & TipoDatoSeguro & "</td>" & "<td style='" & EstiloDescripcion & "'>" & IF ( DescripcionColumna = "", FaltaDescripcion, DescripcionColumnaSegura ) & "</td>" & "</tr>", "", [NombreTabla] & [NombreColumna], ASC ) VAR HTMLRelaciones = CONCATENATEX ( RelacionesADocumentar, VAR TablaOrigen = [TablaOrigen] VAR ColumnaOrigen = [ColumnaOrigen] VAR CardinalidadOrigen = [CardinalidadOrigen] VAR TablaDestino = [TablaDestino] VAR ColumnaDestino = [ColumnaDestino] VAR CardinalidadDestino = [CardinalidadDestino] VAR DireccionFiltro = [DireccionFiltro] VAR RelacionActiva = [RelacionActiva] VAR OrigenSeguro = SUBSTITUTE ( SUBSTITUTE ( SUBSTITUTE ( TablaOrigen & "[" & ColumnaOrigen & "]", "&", "&" ), "<", "<" ), ">", ">" ) VAR DestinoSeguro = SUBSTITUTE ( SUBSTITUTE ( SUBSTITUTE ( TablaDestino & "[" & ColumnaDestino & "]", "&", "&" ), "<", "<" ), ">", ">" ) RETURN "<tr>" & "<td style='" & EstiloNombre & "'>" & OrigenSeguro & "</td>" & "<td style='" & EstiloMetadata & "'>" & CardinalidadOrigen & " → " & CardinalidadDestino & "</td>" & "<td style='" & EstiloNombre & "'>" & DestinoSeguro & "</td>" & "<td style='" & EstiloMetadata & "'>" & DireccionFiltro & "</td>" & "<td style='" & EstiloMetadata & "'>" & IF ( RelacionActiva, "Activa", "<span style='" & EstiloEstado & "'>INACTIVA</span>" ) & "</td>" & "</tr>", "", [TablaOrigen] & [ColumnaOrigen] & [TablaDestino] & [ColumnaDestino], ASC ) VAR HTMLMedidas = CONCATENATEX ( MedidasADocumentar, VAR NombreTabla = [NombreTabla] VAR NombreMedida = [NombreMedida] VAR Carpeta = [Carpeta] VAR DescripcionMedida = [DescripcionMedida] VAR NombreTablaSeguro = SUBSTITUTE ( SUBSTITUTE ( SUBSTITUTE ( NombreTabla, "&", "&" ), "<", "<" ), ">", ">" ) VAR NombreMedidaSeguro = SUBSTITUTE ( SUBSTITUTE ( SUBSTITUTE ( NombreMedida, "&", "&" ), "<", "<" ), ">", ">" ) VAR CarpetaSegura = SUBSTITUTE ( SUBSTITUTE ( SUBSTITUTE ( Carpeta, "&", "&" ), "<", "<" ), ">", ">" ) VAR DescripcionMedidaSegura = SUBSTITUTE ( SUBSTITUTE ( SUBSTITUTE ( DescripcionMedida, "&", "&" ), "<", "<" ), ">", ">" ) RETURN "<tr>" & "<td style='" & EstiloMetadata & "'>" & NombreTablaSeguro & "</td>" & "<td style='" & EstiloMetadata & "'>" & IF ( Carpeta = "", "<span style='" & EstiloFalta & "'>Sin carpeta</span>", CarpetaSegura ) & "</td>" & "<td style='" & EstiloNombre & "'>" & NombreMedidaSeguro & "</td>" & "<td style='" & EstiloDescripcion & "'>" & IF ( DescripcionMedida = "", FaltaDescripcion, DescripcionMedidaSegura ) & "</td>" & "</tr>", "", [NombreTabla] & [Carpeta] & [NombreMedida], ASC ) RETURN EstiloReset & "<div style='" & EstiloContenedor & "'>" & "<div style='" & EstiloTarjeta & "'>" & "<p style='" & EstiloTitulo & "'>Documentación del modelo</p>" & "<p style='" & EstiloParrafo & "'>" & "Esta primera versión lee los metadatos disponibles y los muestra sin interpretar la arquitectura del modelo." & "</p>" & "<p style='" & EstiloSubtitulo & "'>1. Tablas</p>" & IF ( HTMLTablas = "", SinTablas, "<table style='" & EstiloTabla & "'>" & "<tr>" & "<th style='" & EstiloEncabezado & "'>Tabla</th>" & "<th style='" & EstiloEncabezado & "'>Descripción</th>" & "</tr>" & HTMLTablas & "</table>" ) & "<p style='" & EstiloSubtitulo & "'>2. Columnas</p>" & IF ( HTMLColumnas = "", SinColumnas, "<table style='" & EstiloTabla & "'>" & "<tr>" & "<th style='" & EstiloEncabezado & "'>Tabla</th>" & "<th style='" & EstiloEncabezado & "'>Columna</th>" & "<th style='" & EstiloEncabezado & "'>Tipo</th>" & "<th style='" & EstiloEncabezado & "'>Descripción</th>" & "</tr>" & HTMLColumnas & "</table>" ) & "<p style='" & EstiloSubtitulo & "'>3. Relaciones</p>" & IF ( HTMLRelaciones = "", SinRelaciones, "<table style='" & EstiloTabla & "'>" & "<tr>" & "<th style='" & EstiloEncabezado & "'>Desde</th>" & "<th style='" & EstiloEncabezado & "'>Cardinalidad</th>" & "<th style='" & EstiloEncabezado & "'>Hacia</th>" & "<th style='" & EstiloEncabezado & "'>Filtro cruzado</th>" & "<th style='" & EstiloEncabezado & "'>Estado</th>" & "</tr>" & HTMLRelaciones & "</table>" ) & "<p style='" & EstiloSubtitulo & "'>4. Medidas</p>" & IF ( HTMLMedidas = "", SinMedidas, "<table style='" & EstiloTabla & "'>" & "<tr>" & "<th style='" & EstiloEncabezado & "'>Tabla</th>" & "<th style='" & EstiloEncabezado & "'>Carpeta</th>" & "<th style='" & EstiloEncabezado & "'>Medida</th>" & "<th style='" & EstiloEncabezado & "'>Descripción</th>" & "</tr>" & HTMLMedidas & "</table>" ) & "</div>" & "</div>"
Página nueva, agregás el visual HTML Content (lite) y arrastrás la medida al campo del visual.
Si no se ve bien
Revisá el mensaje de error de la medida si tira error, te indica dónde hay que corregir.
DOC_ son calculadas, así que se vuelven a evaluar cuando actualizás el modelo completo (esquema y datos). Si agregaste o cambiaste descripciones, actualizá antes de revisar la página. Conviene refrescar las cuatro juntas para que tablas, columnas, medidas y relaciones representen el mismo estado del modelo.Las últimas actualizaciones de Power BI permiten consultar metadata de las UDF del modelo semántico. En este caso no se crea una tabla calculada como las anteriores: la consulta se ejecuta desde la vista DAX cuando necesitás revisar esa información.
EVALUATE INFO.USERDEFINEDFUNCTIONS()
Resultado: una tabla que podés exportar con información sobre cada una de tus UDF. Si alguna aparece con la descripción vacía, podés completarla desde la vista de modelo, entrando al árbol del modelo y al nodo Funciones. El criterio es el mismo que para una medida.
Hasta acá la página muestra todo lo que el modelo sabe de sí mismo, y nombra cada hueco. Ahora hay que llenarlos.
Todo vive en propiedades que Power BI ya tiene. No hay tabla nueva ni texto adentro de la medida. Se completan a mano en el panel Datos con la propiedad Descripción, o con /// arriba de cada objeto en la vista TMDL.
Una buena descripción agrega el contexto que el nombre no puede contar. No hace falta escribir un párrafo distinto desde cero para cada objeto: podemos usar una estructura estable según estemos documentando una tabla, una columna o una medida.
Estructura
Qué representa → qué rol cumple → qué representa una fila.
Ejemplofct_ventas
Descripción: Registra las ventas confirmadas del negocio. Funciona como tabla de hechos del proceso comercial. Una fila representa una línea de producto dentro de una operación.
Preguntas para escribirla
Estructura
Qué representa → cómo interpretar sus valores → definición estable que haga falta conocer.
EjemploEstado Pedido
Descripción: Estado operativo asignado al pedido. Permite identificar en qué instancia del proceso se encuentra. Sus valores distinguen pedidos pendientes, completados o anulados.
Preguntas para escribirla
En una medida, cada propiedad aporta una parte distinta de la documentación: el nombre identifica qué resultado devuelve, la Description explica su significado y la Display Folder indica dónde encontrarla. En este patrón, cada medida tiene una única carpeta y ninguna queda sin clasificar.
No buscamos traducir el DAX línea por línea. El código conserva la implementación; la descripción conserva el significado, la forma de leer el resultado y la regla que debería seguir siendo válida aunque cambie la expresión.
Estructura
Qué representa → cómo se interpreta → regla de negocio general.
EjemploAverage Revenue per Unit
Descripción: Revenue promedio generado por cada unidad vendida en el contexto de filtros actual. Permite comparar cuánto revenue aporta, en promedio, cada unidad entre períodos, productos o regiones. Se calcula dividiendo Revenue por Units Sold.
Preguntas para escribirla
Power BI no permite cargar una descripción propia en una relación. Su legibilidad depende de la arquitectura y de cómo estén nombradas las tablas. Los nombres consistentes ayudan a leer el modelo, pero la página no usa esos prefijos para clasificar las tablas: esa función la vamos a declarar explícitamente en 08.
En un proyecto PBIP, Power BI separa el modelo semántico del reporte: una capa organiza y define los datos; la otra construye la experiencia para explorarlos. Son partes de una misma solución, pero cumplen funciones distintas.
Podemos aplicar ese mismo criterio dentro del modelo semántico. En este Data Lab usamos una tabla Measures para la lógica analítica y de negocio, y una tabla Visualizations para las medidas que resuelven necesidades de presentación o interacción en el reporte.
MeasuresRevenue, unidades vendidas, promedios, porcentajes, inteligencia temporal y otras métricas que definen cómo medimos el negocio.
Descripción: Reúne las medidas que definen la lógica analítica y de negocio del modelo. Contiene los cálculos utilizados para medir e interpretar sus principales indicadores.
VisualizationsHTML, SVG, títulos dinámicos, estado de filtros, switches de KPI, documentación y otros helpers que definen cómo mostramos o hacemos interactuar esa información.
Descripción: Reúne las medidas creadas para resolver necesidades de presentación e interacción en el reporte. Incluye recursos como HTML, SVG, títulos dinámicos, estados de filtros, switches de KPI y otros helpers visuales.
Separarlas no cambia cómo Power BI ejecuta las medidas. Hace explícita su función y evita mezclar la lógica que calcula el dato con la lógica que determina cómo verlo.
Tené en cuenta que la descripción debería sobrevivir al próximo cambio.
Ya sabemos qué queremos documentar. Ahora necesitamos detectar qué todavía falta explicar.
Cuando trabajamos hace tiempo sobre un modelo, muchas cosas se vuelven obvias para nosotros: sabemos qué representa una tabla, qué significa un código o por qué una medida existe. El problema aparece cuando ese conocimiento no quedó en el modelo.
El TMDL nos permite mostrarle a la IA la estructura que existe. En este caso, el trabajo de la IA no va a ser completar los huecos con una interpretación plausible, sino encontrarlos y preguntarnos por ellos.
La IA puede señalar qué falta explicar. El significado sigue viniendo de las personas que conocen el modelo.
DescriptionDisplay FolderNo le pedimos que deduzca esas respuestas desde el DAX, los nombres o las relaciones. Si el modelo no contiene el significado que necesita, queremos que lo deje visible y formule una pregunta.
Copiá desde la vista TMDL los objetos que quieras revisar. Podés pasar el modelo completo o trabajar por partes, si preferís resolver una tabla a la vez.
El prompt lleva incorporado el mismo criterio de la sección 06. Así la IA no evalúa el modelo contra sus propias preferencias: lo compara contra las convenciones que definimos nosotros.
Te paso el TMDL de un modelo semántico de Power BI. Quiero que actúes como copiloto de documentación. Tu tarea es revisar la metadata existente, detectar qué información falta para documentar el modelo con el criterio que te doy y hacerme las preguntas necesarias para completarla. IMPORTANTE Este es un ejercicio de revisión y documentación. No modifiques el TMDL. No generes una versión modificada del TMDL. No escribas código TMDL nuevo. No propongas cambios directamente sobre el modelo. No redactes metadata para pegar automáticamente. Tu salida debe ser únicamente un diagnóstico documental y las preguntas que necesito responder. ALCANCE Revisá únicamente los objetos que forman parte de este alcance de documentación: [PEGÁ ACÁ LAS TABLAS U OBJETOS QUE QUERÉS REVISAR] No incluyas otras tablas aunque aparezcan en el TMDL. En este modelo, las tablas DOC_ pertenecen a la infraestructura de esta documentación y quedan fuera del alcance. No infieras exclusiones a partir de otros prefijos. CRITERIO DE DOCUMENTACIÓN Usamos estas convenciones. 1. NOMBRES TABLAS El nombre debería permitir reconocer qué conjunto de información contiene y, cuando corresponda, qué rol técnico cumple. En este modelo usamos: - hechos_ para hechos - dim_ para dimensiones - subdim_ para intermedias - param_ para parámetros No asumas el rol de una tabla solamente por su contenido. COLUMNAS El nombre debería identificar con precisión el atributo dentro de su tabla. Revisá especialmente nombres genéricos que puedan necesitar contexto, por ejemplo Estado, Tipo o Valor. No asumas qué representan. Si el significado no está explícito, señalá que necesita contexto. MEDIDAS El nombre debería permitir reconocer qué resultado devuelve sin intentar explicar toda la lógica del cálculo. Usamos este patrón: [Métrica] + [contexto necesario] + [modificador temporal] Los modificadores temporales se ubican como sufijo. No señales un nombre solamente porque vos usarías otra convención. Comparalo únicamente contra la convención que te proporcioné. 2. CARPETAS DE MEDIDAS Usamos Display Folders para agrupar las medidas por familia de métricas. En este modelo seguimos un criterio MECE: - cada medida pertenece a una sola carpeta; - ninguna medida queda sin clasificar. Además distinguimos dos capas: Measures Lógica analítica y de negocio del modelo semántico. Visualizations Medidas creadas para resolver necesidades de presentación o interacción en el reporte, por ejemplo documentación, estado de filtros, HTML, SVG, switches de KPI o helpers visuales. Regla conceptual: Tabla = capa. Display Folder = categoría dentro de esa capa. No decidas por tu cuenta a qué capa pertenece una medida si esa intención no está explícitamente documentada. 3. DESCRIPCIONES Una descripción debería agregar significado que no se obtiene solamente leyendo la metadata técnica. Para una TABLA buscamos: qué representa → qué rol cumple → qué representa una fila. Debería permitir responder: - ¿Qué información reúne? - ¿Para qué existe dentro del modelo? - ¿Cuál es su granularidad? Para una COLUMNA buscamos: qué representa → cómo interpretar sus valores → definición estable que haga falta conocer. Debería permitir responder: - ¿Qué atributo representa? - ¿Hay valores, códigos o unidades que necesitan interpretación? - ¿Existe alguna definición estable que no sea evidente por el nombre? Para una MEDIDA buscamos: qué representa → cómo se interpreta → regla de negocio general. Debería permitir responder: - ¿Qué resultado devuelve? - ¿Cómo debería interpretarse? - ¿Cuál es la regla general que lo define? REGLA PRINCIPAL: NO INFERIR CONTEXTO DE NEGOCIO Trabajá únicamente con información explícita en el TMDL y con el contexto que yo te proporcione. No infieras contexto de negocio a partir de: - expresiones DAX; - nombres de objetos; - relaciones; - filtros; - condiciones; - nombres de columnas o tablas; - patrones que reconozcas por experiencia. No asumas: - qué significa una métrica; - cómo debería interpretarse; - la granularidad real de una tabla; - qué significan códigos o categorías; - por qué existe una regla; - por qué existe una relación; - si una condición es permanente, temporal o excepcional; - si una definición cambió históricamente; - la intención con la que fue diseñado un objeto. Tampoco decidas por tu cuenta qué función semántica cumple una tabla. Si necesitás cualquiera de esas respuestas para completar la documentación, no la deduzcas. Marcá el objeto como NECESITA CONTEXTO y haceme una pregunta concreta. DESCRIPCIONES EXISTENTES No evalúes si una afirmación de negocio es verdadera o falsa. Solo verificá si la descripción existente contiene las partes que definimos en nuestro criterio. Por ejemplo: - si una tabla explica qué representa pero no informa granularidad, señalá que falta granularidad; - si una medida explica qué representa pero no cómo se interpreta, señalá qué parte falta; - si una columna no tiene Description, señalalo. No completes la información faltante. RELACIONES Las relaciones no tienen una propiedad Description propia en Power BI. No inventes descripciones para ellas. Solo registrá la metadata técnica que esté explícitamente disponible: - tabla y columna de origen; - tabla y columna de destino; - cardinalidad; - dirección de filtro; - estado activa/inactiva. No infieras por qué existe una relación. No evalúes si está bien o mal diseñada. No propongas modificarla. QUÉ TENÉS QUE REVISAR Para cada objeto dentro del alcance, verificá: TABLAS - si tiene Description; - si esa Description cubre qué representa, rol y granularidad; - si su nombre respeta la convención definida. COLUMNAS - si tiene Description; - si esa Description permite entender qué representa y cómo interpretar sus valores cuando hace falta; - si su nombre necesita contexto adicional para ser entendido dentro de su tabla. MEDIDAS - si tiene Description; - si esa Description cubre qué representa, cómo se interpreta y regla general; - si tiene Display Folder; - si está clasificada en una única carpeta; - si su nombre respeta la convención definida; - si su capa actual puede determinarse con información explícita. RELACIONES - registrá solamente la metadata técnica disponible. SALIDA Devolvé una tabla con estas columnas: 1. Objeto 2. Tipo de objeto 3. Tabla 4. Metadata actual 5. Qué falta completar 6. Estado 7. Pregunta para mí Usá solamente estos estados: OK La metadata observable ya cubre el criterio que te proporcioné. FALTA METADATA Falta una propiedad técnica observable, por ejemplo Description o Display Folder. NECESITA CONTEXTO Hace falta información humana para completar la documentación sin inventar. REVISAR CONVENCIÓN Existe una diferencia observable respecto de la convención que te proporcioné. REGLAS DE RESPUESTA - No modifiques el TMDL. - No generes TMDL nuevo. - No devuelvas una versión corregida del TMDL. - No redactes código para aplicar cambios. - No propongas nuevas descripciones todavía. - No reescribas las descripciones existentes. - No propongas reglas de negocio. - No interpretes el DAX como contexto de negocio. - No deduzcas granularidad. - No deduzcas significado de códigos. - No deduzcas intención de diseño. - No propongas cambios en relaciones. - No completes silenciosamente información faltante. - No compares el modelo contra prácticas generales que yo no haya definido. - Compará únicamente contra el criterio que te proporcioné. - Cuando falte contexto, hacé una pregunta breve, concreta y respondible. - Si no encontrás un problema observable, no inventes uno. Al final agregá: PREGUNTAS PENDIENTES Agrupá las preguntas que necesito responder para completar la documentación. RESUMEN Indicá: - cantidad de objetos OK; - cantidad con FALTA METADATA; - cantidad que NECESITA CONTEXTO; - cantidad para REVISAR CONVENCIÓN. TMDL: [PEGÁ ACÁ EL TMDL]
Tres cosas del prompt son tuyas y conviene revisarlas antes de mandarlo: los prefijos de tablas, el patrón de nombres de medidas y los nombres de las dos capas son los que usa tu equipo. Si los cambiás ahí, todo el diagnóstico se mide contra tu convención y no contra la mía.
La IA no completa la documentación ni se convierte en una nueva fuente de verdad. Solo señala qué metadata falta y qué preguntas necesitamos responder.
Usá esa lista para recuperar el contexto, completar las propiedades correspondientes en Power BI y volver a pasar el TMDL si querés comprobar que los huecos quedaron resueltos.
Cuando un objeto aparece como NECESITA CONTEXTO, no significa que la IA deba intentar otra vez. Significa que ese conocimiento todavía no quedó registrado en el modelo.
Un hueco visible es mejor que una definición inventada.
En otros contextos, la IA también puede generar o modificar TMDL. Ese flujo puede ser útil, pero necesita guardrails adicionales para controlar qué cambia y validar el resultado.
No vamos a cubrir ese flujo en este Data Lab. Acá usamos el TMDL como entrada para revisar y preguntar; los cambios sobre el modelo quedan bajo nuestro control. Estos dos prompts son el punto de partida si igual querés probarlo: devuelven TMDL con descripciones agregadas y nada más.
Te paso el TMDL de las tablas de datos de un modelo de Power BI. Contexto del dominio: [dos o tres líneas de qué se trata el modelo]. Tarea: agregar una descripción con /// arriba de cada declaración. - Tabla: empezá con la granularidad en la forma "Una fila por…", y seguí con qué representa y cuál es su propósito en el modelo. - Columna: qué representa el campo, cómo se interpreta y, si es clave primaria o foránea, hacia qué tabla. Reglas estrictas: - No cambies nombres, tipos, expresiones, sourceColumn, lineageTag ni ninguna otra propiedad existente. - No agregues, borres ni reordenes tablas ni columnas. - Lo único que podés agregar son líneas ///. - Sin excepciones ni condiciones que puedan cambiar con el tiempo. - Si no podés inferir qué representa algo a partir del nombre, el tipo y las relaciones, escribí [COMPLETAR]. No supongas. - Devolvé el TMDL completo, listo para pegar en la vista TMDL. - Al final, listá lo que quedó en [COMPLETAR].
Te paso el TMDL de mi tabla de medidas. Tarea: agregar a cada medida una descripción con /// justo arriba de su declaración: qué representa, cómo se interpreta y cuál es la regla de negocio general que la define. Reglas estrictas: - No cambies ninguna expresión DAX. - No cambies nombres, formatString, lineageTag ni ninguna otra propiedad. - No agregues, borres ni reordenes medidas. - Lo único que podés agregar son líneas ///. - Sin excepciones ni condiciones que puedan cambiar con el tiempo: si detectás alguna, listala al final. - Si no podés entender qué hace una medida, escribí [COMPLETAR]. - Devolvé el TMDL completo, listo para pegar. - Al final, listá las medidas que quedaron en [COMPLETAR].
Antes de aplicar: pegá la respuesta en una pestaña nueva de la vista TMDL y usá Vista previa para confirmar que solo cambiaron descripciones. Así cada cambio pasa por tu aprobación (human-in-the-loop).
El marcador [COMPLETAR] es a propósito. Un hueco marcado es honesto; una descripción inventada es peor que ninguna. Cuando la IA no puede inferir algo, que lo diga.
La medida que instalamos en 05 hace algo a propósito: lee el modelo sin interpretarlo. Tablas, columnas, relaciones y medidas aparecen aunque todavía no hemos decidido cómo organizarlas.
Ahora tenemos más información. Definimos el criterio de documentación, preparamos el modelo, completamos metadata y usamos IA como segunda mirada donde hacía falta. Ya podemos volver a esa medida y hacerla crecer.
La diferencia es pequeña en concepto, aunque el resultado sea bastante más completo: seguimos automatizando lo que el modelo puede decirnos solo, y declaramos lo que necesita criterio.
La medida de 05 podía detectar que una tabla existía. Lo que no podía saber era qué lugar ocupa esa tabla dentro de nuestro modelo.
No queremos deducirlo por el nombre ni por la dirección de una relación. Una tabla que empieza con hechos_ puede haberse nombrado mal; una relación tampoco alcanza para decidir por sí sola qué función semántica cumple cada objeto. Como pistas, sin embargo, valen mucho: el prefijo y el lugar que ocupa la tabla entre las relaciones son lo primero que miramos cuando escribimos las descripciones. Sirven para orientar el criterio, no para reemplazarlo.
Por eso, en esta versión vamos a agregar una pequeña capa de configuración. Cada tabla recibe una etiqueta, y cada etiqueta declara cómo quiere comportarse.
La versión completa tiene cuatro bloques de configuración arriba y, debajo, el motor que arma la página. Los cuatro bloques son lo único que se toca.
1 · FUENTES DE METADATA de dónde sale la metadata 2 · ÚLTIMA ACTUALIZACIÓN cuándo se actualizó por última vez 3 · CONFIGURACIÓN DE TABLAS qué etiqueta le ponemos a cada tabla 4 · SEMÁNTICA DE ETIQUETAS qué mostramos según la etiqueta ─────────────────────────────────────────────────────────── de acá para abajo, la página se genera sola
Vamos a armarla en ese orden. Volvé a HTML Documentación, la medida que creamos en 05, borrá su expresión y pegá los bloques uno atrás del otro: cuando termines, la medida está completa.
Si preferís trabajarla de una sola vez —en VS Code, en Tabular Editor o pegándola directo en Power BI— al final de la sección está la medida entera en un solo bloque.
Son las mismas cuatro tablas de introspección que creamos en 05. Acá les damos nombres de columna en castellano, para que el resto de la medida se lea sin traducir.
HTML Documentación = // PARA REUTILIZAR ESTA MEDIDA: // 1. Indicá tus tablas de introspección en FUENTES DE METADATA. // 2. Asigná una etiqueta a cada tabla en CONFIGURACIÓN DE TABLAS. // 3. Definí el comportamiento y el orden de esas etiquetas en SEMÁNTICA DE ETIQUETAS. // // Si sumaste una nueva tabla y no está configurada: // → no desaparece; // → no se clasifica automáticamente; // → aparece como "Sin clasificar" al final. // ------------------------------------------------------------------------------------------------------ // 1. FUENTES DE METADATA VAR DocumentacionTablas = // Si tu tabla de documentación de tablas se llama distinto, cambiá DOC_tablas acá. SELECTCOLUMNS ( DOC_tablas, "NombreTabla", DOC_tablas[Name], "DescripcionTabla", COALESCE ( DOC_tablas[Description], "" ), "TablaOculta", DOC_tablas[IsHidden] ) VAR DocumentacionColumnas = // Si tu tabla de documentación de columnas se llama distinto, cambiá DOC_columnas acá. SELECTCOLUMNS ( DOC_columnas, "NombreTabla", DOC_columnas[Table], "NombreColumna", DOC_columnas[Name], "TipoDato", DOC_columnas[DataType], "DescripcionColumna", COALESCE ( DOC_columnas[Description], "" ) ) VAR DocumentacionMedidas = // Si tu tabla de documentación de medidas se llama distinto, cambiá DOC_medidas acá. SELECTCOLUMNS ( DOC_medidas, "NombreTabla", DOC_medidas[Table], "NombreMedida", DOC_medidas[Name], "Carpeta", COALESCE ( DOC_medidas[DisplayFolder], "" ), "DescripcionMedida", COALESCE ( DOC_medidas[Description], "" ), "MedidaOculta", DOC_medidas[IsHidden], "EstadoMedida", DOC_medidas[State] ) VAR DocumentacionRelaciones = // Si tu tabla de documentación de relaciones se llama distinto, cambiá DOC_relaciones acá. SELECTCOLUMNS ( DOC_relaciones, "TablaOrigen", DOC_relaciones[FromTable], "ColumnaOrigen", DOC_relaciones[FromColumn], "CardinalidadOrigen", DOC_relaciones[FromCardinality], "TablaDestino", DOC_relaciones[ToTable], "ColumnaDestino", DOC_relaciones[ToColumn], "CardinalidadDestino", DOC_relaciones[ToCardinality], "DireccionFiltro", DOC_relaciones[CrossFilteringBehavior], "RelacionActiva", DOC_relaciones[IsActive] )
El bloque ya viene con los nombres de 05: DOC_tablas, DOC_columnas, DOC_medidas y DOC_relaciones. Si seguiste esa convención no tenés que tocar nada acá. Si tus tablas de introspección se llaman de otra manera, reemplazá esos cuatro nombres por los tuyos donde lo indican los comentarios: es lo único que hay que ajustar en este bloque.
Las tablas que empiezan con DOC_ quedan fuera de la documentación automáticamente, porque pertenecen a la infraestructura de esta misma página.
Este bloque viene preparado para funcionar sin fecha: si no lo tocás, la página anda igual y el pie no muestra ninguna.
// ------------------------------------------------------------------------------------------------------ // 2. ÚLTIMA ACTUALIZACIÓN · FUENTE AUXILIAR OPCIONAL // // Si sumas la hora de actualización vas a poder tener registro de cuándo fue la última vez en actualizarse los datos. Esto es clave cuando sumaste o cambiaste descripciones y te encontras con las definiciones previas. Scrolleando al final del visual renderizado chequeas la fecha y hora. Esto es UX en acción. // Descomentá este bloque e indicá el nombre de tu medida con su tabla correspondiente. // VAR DocumentacionActualizacion = // 'Nombre tabla'[Nombre medida] // Si armaste la medida, una vez que descomentaste la anterior, comentas esta! Si no vas a poner hora de última actualización, dejá este bloque como está así no se rompe la medida. VAR DocumentacionActualizacion = BLANK ()
Para que aparezca hacen falta dos cosas. Primero, una consulta en Power Query que guarde el momento del refresco. Creala en blanco, pegá esto en el Editor avanzado y llamala aux_last_refresh:
let
Source =
#table(
type table [FechaActualizacion = datetimezone],
{
{
DateTimeZone.SwitchZone(
DateTimeZone.FixedUtcNow(),
-3
)
}
}
)
in
Source
Tiene que resolverse durante el refresco, no al abrir el informe: así el valor queda congelado en el momento en que los datos entraron al modelo, que es justo lo que querés decir. Por eso DateTimeZone.FixedUtcNow y no DateTime.LocalNow: el primero devuelve UTC y queda fijo durante toda la evaluación de la consulta; el segundo toma la hora de la máquina que ejecuta el refresco, que no es la misma en tu Desktop que en el Servicio. El -3 de SwitchZone es la diferencia horaria de Buenos Aires: cambiala por la tuya.
Segundo, una medida que lea esa tabla, que es lo que el bloque 2 espera:
Fecha Última Actualización = MAX ( 'aux_last_refresh'[FechaActualizacion] )
Como aux_last_refresh no empieza con DOC_, no queda excluida sola de la documentación: acordate de declararla en el bloque 3, con la etiqueta que uses para las tablas auxiliares.
Con la medida creada, en el bloque 2 descomentá las dos líneas de arriba, poné ahí su nombre con la tabla donde vive, y comentá el BLANK ().
Acá va la primera de las dos decisiones que no se pueden automatizar: qué lugar ocupa cada tabla dentro del modelo. Reemplazá el ejemplo por tus tablas.
// ------------------------------------------------------------------------------------------------------ // 3. CONFIGURACIÓN DE TABLAS // // Declaramos qué etiqueta describe a cada tabla. // // No repetís una tabla. // Si una tabla no aparece acá, recibe la etiqueta marcada como fallback en el bloque 4. VAR ConfiguracionTablas = //Acá reemplazá con tus tablas, yo puse un ejemplo DATATABLE ( "TablaConfig", STRING, "EtiquetaConfig", STRING, { { "fct_sales", "Hechos" }, { "fct_sentiment", "Hechos" }, { "dim_calendar", "Dimensión" }, { "dim_geography", "Dimensión" }, { "dim_product", "Dimensión" }, { "Visualizations", "Medidas" }, { "⚙️ Measures", "Medidas" }, { "param_key_insigths", "Parámetro" }, { "param_userguide", "Parámetro" }, { "Documentación Tablas", "Excluir" }, { "aux_last_refresh", "Auxiliar" } } )
Las tablas que empiezan con DOC_ se excluyen automáticamente. Si alguna tabla que sostiene la documentación usa otro nombre, conviene declararla acá con la etiqueta Excluir; por ejemplo: { "Documentación Tablas", "Excluir" }.
Las etiquetas del ejemplo no son una taxonomía universal: son una forma de describir un modelo. Otro modelo podría necesitar categorías como Tabla puente, Configuración, Snapshot o cualquier otra que ayude a entender su estructura. Lo importante es que la clasificación queda declarada, no inferida.
ConfiguracionTablas, la medida le asigna Sin clasificar y la manda al final. Eso deja una señal visible de que el modelo cambió y todavía falta tomar una decisión sobre esa tabla.La segunda decisión. Cada tabla ya recibió una etiqueta; acá declaramos qué significa esa etiqueta para la página. Así podés llamarla Medidas, KPIs o como tenga sentido en tu equipo: la lógica no depende de un texto fijo.
// ------------------------------------------------------------------------------------------------------ // 4. SEMÁNTICA DE ETIQUETAS // // Acá definís qué significa cada etiqueta para la página. // // DocumentarEstructura: // 1 = muestra sus columnas en el diccionario de estructuras // 0 = no las muestra (por ejemplo, una tabla que solo contiene medidas) // // Incluir: // 1 = forma parte de la documentación // 0 = queda completamente afuera // // EsFallback: // marcá una sola etiqueta con 1. Esa recibe cualquier tabla nueva no configurada. // // Podés renombrar "Medidas" o cualquier otra etiqueta. // La lógica de render no depende del texto: reutiliza esta configuración. VAR ConfiguracionEtiquetas = DATATABLE ( "EtiquetaConfig", STRING, "Orden", INTEGER, "DocumentarEstructura", INTEGER, "Incluir", INTEGER, "EsFallback", INTEGER, { // Etiqueta Orden Estructura Incluir Fallback { "Hechos", 1, 1, 1, 0 }, { "Dimensión", 2, 1, 1, 0 }, { "Dimensión auxiliar", 3, 1, 1, 0 }, { "Auxiliar", 4, 1, 1, 0 }, { "Parámetro", 5, 1, 1, 0 }, { "Medidas", 6, 0, 1, 0 }, { "Sin clasificar", 999, 1, 1, 1 }, { "Excluir", 1000, 0, 0, 0 } } )
Posición de presentación.
En 0, esa etiqueta no entra al diccionario de estructuras.
En 0, las tablas con esa etiqueta no se documentan.
En 1, es la etiqueta que reciben las tablas nuevas que todavía no fueron configuradas.
En este ejemplo, Medidas se muestra como categoría pero no entra al diccionario de estructuras. Excluir queda fuera de la documentación y Sin clasificar funciona como fallback para las tablas que todavía no tienen una decisión explícita.
Debajo del bloque 4 empieza el motor: el que cuenta objetos, calcula la cobertura, ordena las tablas por su etiqueta y arma el HTML. No hay nada que configurar ahí. Pegalo tal cual, al final de los cuatro bloques anteriores, y la medida queda lista.
// Listo. No necesitamos que toques más la medida. // Desde este punto la documentación se genera automáticamente. Espero que te sea de utilidad, Loa 💛 VAR EtiquetaFallback = MAXX ( FILTER ( ConfiguracionEtiquetas, [EsFallback] = 1 ), [EtiquetaConfig] ) VAR TablasBase = FILTER ( DocumentacionTablas, NOT [TablaOculta] && LEFT ( LOWER ( [NombreTabla] ), 4 ) <> "doc_" ) VAR TablasConEtiqueta = ADDCOLUMNS ( TablasBase, "EtiquetaTabla", VAR NombreTablaActual = [NombreTabla] VAR EtiquetaEncontrada = MAXX ( FILTER ( ConfiguracionTablas, [TablaConfig] = NombreTablaActual ), [EtiquetaConfig] ) RETURN COALESCE ( EtiquetaEncontrada, EtiquetaFallback ) ) VAR TablasConSemantica = ADDCOLUMNS ( TablasConEtiqueta, "OrdenEtiqueta", VAR EtiquetaActual = [EtiquetaTabla] RETURN COALESCE ( MAXX ( FILTER ( ConfiguracionEtiquetas, [EtiquetaConfig] = EtiquetaActual ), [Orden] ), 998 ), "DocumentarEstructuraTabla", VAR EtiquetaActual = [EtiquetaTabla] RETURN COALESCE ( MAXX ( FILTER ( ConfiguracionEtiquetas, [EtiquetaConfig] = EtiquetaActual ), [DocumentarEstructura] ), 1 ), "IncluirTabla", VAR EtiquetaActual = [EtiquetaTabla] RETURN COALESCE ( MAXX ( FILTER ( ConfiguracionEtiquetas, [EtiquetaConfig] = EtiquetaActual ), [Incluir] ), 1 ), "EsFallbackTabla", VAR EtiquetaActual = [EtiquetaTabla] RETURN COALESCE ( MAXX ( FILTER ( ConfiguracionEtiquetas, [EtiquetaConfig] = EtiquetaActual ), [EsFallback] ), 0 ) ) VAR TablasADocumentar = FILTER ( TablasConSemantica, [IncluirTabla] = 1 ) VAR NombresTablas = SELECTCOLUMNS ( TablasADocumentar, "NombreTabla", [NombreTabla] ) VAR TablasConColumnasDocumentables = FILTER ( TablasADocumentar, [DocumentarEstructuraTabla] = 1 ) VAR NombresTablasConColumnasDocumentables = SELECTCOLUMNS ( TablasConColumnasDocumentables, "NombreTabla", [NombreTabla] ) VAR ColumnasADocumentar = FILTER ( DocumentacionColumnas, [NombreTabla] IN NombresTablasConColumnasDocumentables && NOT CONTAINSSTRING ( [NombreColumna], "RowNumber-" ) ) VAR MedidasADocumentar = FILTER ( DocumentacionMedidas, NOT [MedidaOculta] && [NombreTabla] IN NombresTablas ) VAR RelacionesADocumentar = FILTER ( DocumentacionRelaciones, [TablaOrigen] IN NombresTablas && [TablaDestino] IN NombresTablas ) VAR TablasConColumnas = DISTINCT ( SELECTCOLUMNS ( ColumnasADocumentar, "NombreTabla", [NombreTabla] ) ) VAR TablasEstructuras = FILTER ( TablasADocumentar, [DocumentarEstructuraTabla] = 1 && [NombreTabla] IN TablasConColumnas ) VAR CantidadTablas = COUNTROWS ( TablasADocumentar ) + 0 VAR CantidadColumnas = COUNTROWS ( ColumnasADocumentar ) + 0 VAR CantidadMedidas = COUNTROWS ( MedidasADocumentar ) + 0 VAR CantidadRelaciones = COUNTROWS ( RelacionesADocumentar ) + 0 VAR CantidadSinClasificar = COUNTROWS ( FILTER ( TablasADocumentar, [EsFallbackTabla] = 1 ) ) + 0 VAR CantidadTablasConDescripcion = COUNTROWS ( FILTER ( TablasADocumentar, [DescripcionTabla] <> "" ) ) VAR CantidadColumnasConDescripcion = COUNTROWS ( FILTER ( ColumnasADocumentar, [DescripcionColumna] <> "" ) ) VAR CantidadMedidasConDescripcion = COUNTROWS ( FILTER ( MedidasADocumentar, [DescripcionMedida] <> "" ) ) VAR CantidadObjetos = CantidadTablas + CantidadColumnas + CantidadMedidas VAR CantidadObjetosDocumentados = CantidadTablasConDescripcion + CantidadColumnasConDescripcion + CantidadMedidasConDescripcion VAR CantidadHuecos = CantidadObjetos - CantidadObjetosDocumentados VAR CoberturaDocumentacion = DIVIDE ( CantidadObjetosDocumentados, CantidadObjetos, 0 ) VAR CoberturaTexto = FORMAT ( COALESCE ( CoberturaDocumentacion, 0 ), "0%" ) VAR EtiquetasPresentes = DISTINCT ( SELECTCOLUMNS ( TablasADocumentar, "EtiquetaResumen", [EtiquetaTabla] ) ) VAR EtiquetasConResumen = ADDCOLUMNS ( EtiquetasPresentes, "OrdenResumen", VAR EtiquetaActual = [EtiquetaResumen] VAR OrdenActual = MAXX ( FILTER ( ConfiguracionEtiquetas, [EtiquetaConfig] = EtiquetaActual ), [Orden] ) RETURN COALESCE ( OrdenActual, 998 ), "CantidadResumen", VAR EtiquetaActual = [EtiquetaResumen] RETURN COUNTROWS ( FILTER ( TablasADocumentar, [EtiquetaTabla] = EtiquetaActual ) ) ) VAR ResumenEtiquetas = CONCATENATEX ( EtiquetasConResumen, [EtiquetaResumen] & ": " & [CantidadResumen], " · ", FORMAT ( [OrdenResumen], "000" ) & "|" & [EtiquetaResumen], ASC ) VAR ResumenEstructura = "El alcance documentado contiene " & CantidadTablas & IF ( CantidadTablas = 1, " tabla. ", " tablas. " ) & ResumenEtiquetas & ". Entre las tablas incluidas hay " & CantidadRelaciones & IF ( CantidadRelaciones = 1, " relación.", " relaciones." ) VAR Fuente = "Segoe UI Variable Text,Segoe UI,system-ui,Arial,sans-serif" VAR FuenteMono = "Consolas,Menlo,monospace" VAR EstiloReset = "<style> html,body{ margin:0; padding:0; height:100%; width:100%; overflow:hidden; } *{ box-sizing:border-box; } p{ margin:0; } table{ table-layout:fixed; } </style>" VAR EstiloContenedor = "position:fixed; inset:0; overflow:hidden; background:transparent;" VAR EstiloTarjeta = "background:#FFFFFF; border:0.5px solid #E4E4E4; border-radius:12px; padding:20px 3%; width:100%; height:100%; overflow-y:auto; overflow-x:hidden; font-family:" & Fuente & ";" VAR EstiloTitulo = "font-size:19px; font-weight:600; color:#1A1A1A; margin:0 0 6px;" VAR EstiloSubtitulo = "font-size:15px; font-weight:600; color:#1A1A1A; margin:26px 0 8px;" VAR EstiloNombreSeccion = "font-size:13px; font-weight:600; color:#1A1A1A; margin:20px 0 4px;" VAR EstiloParrafo = "font-size:12.5px; line-height:1.55; color:#6B6B6B; margin:0 0 10px;" VAR EstiloTabla = "width:100%; border-collapse:collapse; margin:6px 0 4px;" VAR EstiloEncabezado = "text-align:left; font-size:11px; font-weight:600; color:#6B6B6B; text-transform:uppercase; letter-spacing:0.4px; padding:7px 10px; background:#FAFBFF; border-bottom:1px solid #E4E4E4; overflow-wrap:anywhere;" VAR EstiloNombre = "font-family:" & FuenteMono & "; font-size:11.5px; color:#4752CE; padding:7px 10px; border-bottom:1px solid #F0F0F0; vertical-align:top; word-break:break-word; overflow-wrap:anywhere;" VAR EstiloMetadata = "font-size:11px; color:#9B9B9B; padding:7px 10px; border-bottom:1px solid #F0F0F0; vertical-align:top; word-break:break-word; overflow-wrap:anywhere;" VAR EstiloDescripcion = "font-size:12px; color:#6B6B6B; padding:7px 10px; border-bottom:1px solid #F0F0F0; vertical-align:top; line-height:1.45; word-break:break-word; overflow-wrap:anywhere;" VAR EstiloRol = "font-size:11px; color:#9B9B9B; font-weight:400; padding-left:8px;" VAR EstiloFalta = "font-size:11.5px; color:#B9B9C6; font-style:italic;" VAR EstiloAviso = "font-size:11.5px; color:#8A5A12; background:#F8EFDD; border:1px solid #F0DCB4; border-radius:6px; padding:7px 11px; margin:10px 0 0; line-height:1.5;" VAR EstiloEtiqueta = "display:inline-block; background:#F8EFDD; color:#8A5A12; border-radius:4px; padding:1px 5px; font-size:10px; font-weight:600; margin-left:5px; white-space:nowrap;" VAR EstiloError = "display:inline-block; background:#FAECE7; color:#993C1D; border-radius:4px; padding:1px 5px; font-size:10px; font-weight:600; margin-left:5px; white-space:nowrap;" VAR EstiloContadores = "display:flex; flex-wrap:wrap; gap:12px; margin:14px 0 4px;" VAR EstiloContador = "flex:1 1 110px; min-width:0; background:#FAFBFF; border:1px solid #EAEDFF; border-radius:10px; padding:12px 14px; text-align:center;" VAR EstiloNumero = "font-size:22px; font-weight:600; color:#1A1A1A; margin:0;" VAR EstiloEtiquetaContador = "font-size:10.5px; color:#9B9B9B; text-transform:uppercase; letter-spacing:0.4px; margin:2px 0 0;" VAR EstiloPie = "font-size:11px; color:#9B9B9B; border-top:1px solid #F0F0F0; margin:24px 0 0; padding-top:10px;" VAR FaltaDescripcionTabla = "<span style='" & EstiloFalta & "'>Falta el propósito y la granularidad de esta tabla</span>" VAR FaltaDescripcionColumna = "<span style='" & EstiloFalta & "'>Falta la descripción</span>" VAR FaltaDescripcionMedida = "<span style='" & EstiloFalta & "'>Falta qué representa y su regla de negocio</span>" VAR FaltaDescripcionCapa = "<span style='" & EstiloFalta & "'>Falta la descripción de esta capa de medidas</span>" VAR HTMLEstructuras = CONCATENATEX ( TablasEstructuras, VAR NombreTablaActual = [NombreTabla] VAR EtiquetaTablaActual = [EtiquetaTabla] VAR DescripcionTablaActual = [DescripcionTabla] VAR NombreTablaSeguro = SUBSTITUTE ( SUBSTITUTE ( SUBSTITUTE ( NombreTablaActual, "&", "&" ), "<", "<" ), ">", ">" ) VAR EtiquetaTablaSegura = SUBSTITUTE ( SUBSTITUTE ( SUBSTITUTE ( EtiquetaTablaActual, "&", "&" ), "<", "<" ), ">", ">" ) VAR DescripcionTablaSegura = SUBSTITUTE ( SUBSTITUTE ( SUBSTITUTE ( DescripcionTablaActual, "&", "&" ), "<", "<" ), ">", ">" ) VAR FilasColumnas = CONCATENATEX ( FILTER ( ColumnasADocumentar, [NombreTabla] = NombreTablaActual ), VAR NombreColumnaActual = [NombreColumna] VAR TipoDatoActual = [TipoDato] VAR DescripcionColumnaActual = [DescripcionColumna] VAR NombreColumnaSeguro = SUBSTITUTE ( SUBSTITUTE ( SUBSTITUTE ( NombreColumnaActual, "&", "&" ), "<", "<" ), ">", ">" ) VAR TipoDatoSeguro = SUBSTITUTE ( SUBSTITUTE ( SUBSTITUTE ( TipoDatoActual, "&", "&" ), "<", "<" ), ">", ">" ) VAR DescripcionColumnaSegura = SUBSTITUTE ( SUBSTITUTE ( SUBSTITUTE ( DescripcionColumnaActual, "&", "&" ), "<", "<" ), ">", ">" ) RETURN "<tr>" & "<td style='" & EstiloNombre & "'>" & NombreColumnaSeguro & "</td>" & "<td style='" & EstiloMetadata & "'>" & TipoDatoSeguro & "</td>" & "<td style='" & EstiloDescripcion & "'>" & IF ( DescripcionColumnaActual = "", FaltaDescripcionColumna, DescripcionColumnaSegura ) & "</td>" & "</tr>", "", [NombreColumna], ASC ) RETURN "<p style='" & EstiloNombreSeccion & "'>" & NombreTablaSeguro & "<span style='" & EstiloRol & "'>" & EtiquetaTablaSegura & "</span>" & "</p>" & "<p style='" & EstiloParrafo & "'>" & IF ( DescripcionTablaActual = "", FaltaDescripcionTabla, DescripcionTablaSegura ) & "</p>" & IF ( FilasColumnas = "", "<p style='" & EstiloParrafo & "'>No hay columnas para mostrar.</p>", "<table style='" & EstiloTabla & "'>" & "<tr>" & "<th style='" & EstiloEncabezado & "'>Columna</th>" & "<th style='" & EstiloEncabezado & "'>Tipo</th>" & "<th style='" & EstiloEncabezado & "'>Descripción</th>" & "</tr>" & FilasColumnas & "</table>" ), "", FORMAT ( [OrdenEtiqueta], "000" ) & "|" & [NombreTabla], ASC ) VAR HTMLRelaciones = CONCATENATEX ( RelacionesADocumentar, VAR TablaOrigenActual = [TablaOrigen] VAR ColumnaOrigenActual = [ColumnaOrigen] VAR CardinalidadOrigenActual = [CardinalidadOrigen] VAR TablaDestinoActual = [TablaDestino] VAR ColumnaDestinoActual = [ColumnaDestino] VAR CardinalidadDestinoActual = [CardinalidadDestino] VAR DireccionFiltroActual = [DireccionFiltro] VAR RelacionActivaActual = [RelacionActiva] VAR OrigenSeguro = SUBSTITUTE ( SUBSTITUTE ( SUBSTITUTE ( TablaOrigenActual & "[" & ColumnaOrigenActual & "]", "&", "&" ), "<", "<" ), ">", ">" ) VAR DestinoSeguro = SUBSTITUTE ( SUBSTITUTE ( SUBSTITUTE ( TablaDestinoActual & "[" & ColumnaDestinoActual & "]", "&", "&" ), "<", "<" ), ">", ">" ) RETURN "<tr>" & "<td style='" & EstiloNombre & "'>" & OrigenSeguro & "</td>" & "<td style='" & EstiloMetadata & "'>" & CardinalidadOrigenActual & " → " & CardinalidadDestinoActual & "</td>" & "<td style='" & EstiloNombre & "'>" & DestinoSeguro & "</td>" & "<td style='" & EstiloMetadata & "'>" & DireccionFiltroActual & IF ( RelacionActivaActual, "", "<span style='" & EstiloEtiqueta & "'>INACTIVA</span>" ) & "</td>" & "</tr>", "", [TablaOrigen] & [ColumnaOrigen] & [TablaDestino] & [ColumnaDestino], ASC ) VAR TablasConMedidas = FILTER ( SELECTCOLUMNS ( TablasADocumentar, "NombreTabla", [NombreTabla], "EtiquetaTabla", [EtiquetaTabla], "OrdenEtiqueta", [OrdenEtiqueta] ), VAR NombreTablaActual = [NombreTabla] RETURN COUNTROWS ( FILTER ( MedidasADocumentar, [NombreTabla] = NombreTablaActual ) ) > 0 ) VAR HTMLMedidas = CONCATENATEX ( TablasConMedidas, VAR NombreTablaMedidas = [NombreTabla] VAR EtiquetaTablaMedidas = [EtiquetaTabla] VAR DescripcionCapa = MAXX ( FILTER ( TablasADocumentar, [NombreTabla] = NombreTablaMedidas ), [DescripcionTabla] ) VAR NombreTablaSeguro = SUBSTITUTE ( SUBSTITUTE ( SUBSTITUTE ( NombreTablaMedidas, "&", "&" ), "<", "<" ), ">", ">" ) VAR EtiquetaTablaSegura = SUBSTITUTE ( SUBSTITUTE ( SUBSTITUTE ( EtiquetaTablaMedidas, "&", "&" ), "<", "<" ), ">", ">" ) VAR DescripcionCapaSegura = SUBSTITUTE ( SUBSTITUTE ( SUBSTITUTE ( COALESCE ( DescripcionCapa, "" ), "&", "&" ), "<", "<" ), ">", ">" ) VAR MedidasDeTabla = FILTER ( MedidasADocumentar, [NombreTabla] = NombreTablaMedidas ) VAR Carpetas = DISTINCT ( SELECTCOLUMNS ( MedidasDeTabla, "Carpeta", [Carpeta] ) ) VAR HTMLCarpetas = CONCATENATEX ( Carpetas, VAR CarpetaActual = [Carpeta] VAR CarpetaSegura = SUBSTITUTE ( SUBSTITUTE ( SUBSTITUTE ( CarpetaActual, "&", "&" ), "<", "<" ), ">", ">" ) VAR FilasMedidas = CONCATENATEX ( FILTER ( MedidasDeTabla, [Carpeta] = CarpetaActual ), VAR NombreMedidaActual = [NombreMedida] VAR DescripcionMedidaActual = [DescripcionMedida] VAR EstadoMedidaActual = [EstadoMedida] VAR NombreMedidaSeguro = SUBSTITUTE ( SUBSTITUTE ( SUBSTITUTE ( NombreMedidaActual, "&", "&" ), "<", "<" ), ">", ">" ) VAR DescripcionMedidaSegura = SUBSTITUTE ( SUBSTITUTE ( SUBSTITUTE ( DescripcionMedidaActual, "&", "&" ), "<", "<" ), ">", ">" ) RETURN "<tr>" & "<td style='" & EstiloNombre & "'>" & NombreMedidaSeguro & IF ( EstadoMedidaActual IN { "Valid", "Ready" }, "", "<span style='" & EstiloError & "'>ERROR</span>" ) & "</td>" & "<td style='" & EstiloDescripcion & "'>" & IF ( DescripcionMedidaActual = "", FaltaDescripcionMedida, DescripcionMedidaSegura ) & "</td>" & "</tr>", "", [NombreMedida], ASC ) RETURN "<p style='" & EstiloNombreSeccion & "'>" & IF ( CarpetaActual = "", "<span style='" & EstiloFalta & "'>Sin carpeta</span>", CarpetaSegura ) & "</p>" & "<table style='" & EstiloTabla & "'>" & "<tr>" & "<th style='" & EstiloEncabezado & "'>Medida</th>" & "<th style='" & EstiloEncabezado & "'>Descripción</th>" & "</tr>" & FilasMedidas & "</table>", "", IF ( [Carpeta] = "", "zzzz", [Carpeta] ), ASC ) RETURN "<p style='" & EstiloSubtitulo & "'>" & NombreTablaSeguro & "<span style='" & EstiloRol & "'>" & EtiquetaTablaSegura & "</span>" & "</p>" & "<p style='" & EstiloParrafo & "'>" & IF ( COALESCE ( DescripcionCapa, "" ) = "", FaltaDescripcionCapa, DescripcionCapaSegura ) & "</p>" & HTMLCarpetas, "", FORMAT ( [OrdenEtiqueta], "000" ) & "|" & [NombreTabla], ASC ) RETURN EstiloReset & "<div style='" & EstiloContenedor & "'>" & "<div style='" & EstiloTarjeta & "'>" & "<p style='" & EstiloTitulo & "'>Documentación del modelo</p>" & "<p style='" & EstiloParrafo & "'>" & "La información brindada se genera a partir de los metadatos del modelo semántico." & "Las etiquetas de las tablas se toman de la configuración declarada en la medida." & "</p>" & "<div style='" & EstiloContadores & "'>" & "<div style='" & EstiloContador & "'>" & "<p style='" & EstiloNumero & "'>" & CantidadTablas & "</p>" & "<p style='" & EstiloEtiquetaContador & "'>Tablas</p>" & "</div>" & "<div style='" & EstiloContador & "'>" & "<p style='" & EstiloNumero & "'>" & CantidadColumnas & "</p>" & "<p style='" & EstiloEtiquetaContador & "'>Columnas</p>" & "</div>" & "<div style='" & EstiloContador & "'>" & "<p style='" & EstiloNumero & "'>" & CantidadMedidas & "</p>" & "<p style='" & EstiloEtiquetaContador & "'>Medidas</p>" & "</div>" & "<div style='" & EstiloContador & "'>" & "<p style='" & EstiloNumero & "'>" & CantidadRelaciones & "</p>" & "<p style='" & EstiloEtiquetaContador & "'>Relaciones</p>" & "</div>" & "<div style='" & EstiloContador & "'>" & "<p style='" & EstiloNumero & "'>" & CoberturaTexto & "</p>" & "<p style='" & EstiloEtiquetaContador & "'>Documentado</p>" & "</div>" & "</div>" & IF ( CantidadHuecos = 0, "", "<p style='" & EstiloAviso & "'>" & CantidadHuecos & " de " & CantidadObjetos & " objetos todavía no tienen descripción. " & "Los huecos quedan visibles para saber qué contexto falta." & "</p>" ) & IF ( CantidadSinClasificar = 0, "", "<p style='" & EstiloAviso & "'>" & CantidadSinClasificar & IF ( CantidadSinClasificar = 1, " tabla todavía no tiene una etiqueta asignada. ", " tablas todavía no tienen una etiqueta asignada. " ) & "Revisá el bloque CONFIGURACIÓN DE TABLAS." & "</p>" ) & "<p style='" & EstiloSubtitulo & "'>1. Estructura del modelo</p>" & "<p style='" & EstiloParrafo & "'>" & ResumenEstructura & "</p>" & "<p style='" & EstiloSubtitulo & "'>2. Estructuras de datos</p>" & IF ( HTMLEstructuras = "", "<p style='" & EstiloParrafo & "'>No hay estructuras de datos para mostrar.</p>", HTMLEstructuras ) & "<p style='" & EstiloSubtitulo & "'>3. Relaciones</p>" & IF ( HTMLRelaciones = "", "<p style='" & EstiloParrafo & "'>No hay relaciones entre las tablas documentadas.</p>", "<table style='" & EstiloTabla & "'>" & "<tr>" & "<th style='" & EstiloEncabezado & "'>Desde</th>" & "<th style='" & EstiloEncabezado & "'>Cardinalidad</th>" & "<th style='" & EstiloEncabezado & "'>Hacia</th>" & "<th style='" & EstiloEncabezado & "'>Filtro cruzado</th>" & "</tr>" & HTMLRelaciones & "</table>" ) & "<p style='" & EstiloSubtitulo & "'>4. Medidas</p>" & IF ( HTMLMedidas = "", "<p style='" & EstiloParrafo & "'>No hay medidas visibles para documentar.</p>", HTMLMedidas ) & "<p style='" & EstiloPie & "'>" & "Diseñado por Loana Ibañez 💛 datavizargentina.com.ar" & IF ( ISBLANK ( DocumentacionActualizacion ), "", " · Última actualización: " & FORMAT ( DocumentacionActualizacion, "dd/mm/yyyy HH:mm" ) ) & "</p>" & "</div>" & "</div>"
La misma medida, con los cuatro bloques y el motor juntos, para ajustarla en VS Code o en Tabular Editor y pegarla de una sola vez.
HTML Documentación = // PARA REUTILIZAR ESTA MEDIDA: // 1. Indicá tus tablas de introspección en FUENTES DE METADATA. // 2. Asigná una etiqueta a cada tabla en CONFIGURACIÓN DE TABLAS. // 3. Definí el comportamiento y el orden de esas etiquetas en SEMÁNTICA DE ETIQUETAS. // // Si sumaste una nueva tabla y no está configurada: // → no desaparece; // → no se clasifica automáticamente; // → aparece como "Sin clasificar" al final. // ------------------------------------------------------------------------------------------------------ // 1. FUENTES DE METADATA VAR DocumentacionTablas = // Si tu tabla de documentación de tablas se llama distinto, cambiá DOC_tablas acá. SELECTCOLUMNS ( DOC_tablas, "NombreTabla", DOC_tablas[Name], "DescripcionTabla", COALESCE ( DOC_tablas[Description], "" ), "TablaOculta", DOC_tablas[IsHidden] ) VAR DocumentacionColumnas = // Si tu tabla de documentación de columnas se llama distinto, cambiá DOC_columnas acá. SELECTCOLUMNS ( DOC_columnas, "NombreTabla", DOC_columnas[Table], "NombreColumna", DOC_columnas[Name], "TipoDato", DOC_columnas[DataType], "DescripcionColumna", COALESCE ( DOC_columnas[Description], "" ) ) VAR DocumentacionMedidas = // Si tu tabla de documentación de medidas se llama distinto, cambiá DOC_medidas acá. SELECTCOLUMNS ( DOC_medidas, "NombreTabla", DOC_medidas[Table], "NombreMedida", DOC_medidas[Name], "Carpeta", COALESCE ( DOC_medidas[DisplayFolder], "" ), "DescripcionMedida", COALESCE ( DOC_medidas[Description], "" ), "MedidaOculta", DOC_medidas[IsHidden], "EstadoMedida", DOC_medidas[State] ) VAR DocumentacionRelaciones = // Si tu tabla de documentación de relaciones se llama distinto, cambiá DOC_relaciones acá. SELECTCOLUMNS ( DOC_relaciones, "TablaOrigen", DOC_relaciones[FromTable], "ColumnaOrigen", DOC_relaciones[FromColumn], "CardinalidadOrigen", DOC_relaciones[FromCardinality], "TablaDestino", DOC_relaciones[ToTable], "ColumnaDestino", DOC_relaciones[ToColumn], "CardinalidadDestino", DOC_relaciones[ToCardinality], "DireccionFiltro", DOC_relaciones[CrossFilteringBehavior], "RelacionActiva", DOC_relaciones[IsActive] ) // ------------------------------------------------------------------------------------------------------ // 2. ÚLTIMA ACTUALIZACIÓN · FUENTE AUXILIAR OPCIONAL // // Si sumas la hora de actualización vas a poder tener registro de cuándo fue la última vez en actualizarse los datos. Esto es clave cuando sumaste o cambiaste descripciones y te encontras con las definiciones previas. Scrolleando al final del visual renderizado chequeas la fecha y hora. Esto es UX en acción. // Descomentá este bloque e indicá el nombre de tu medida con su tabla correspondiente. // VAR DocumentacionActualizacion = // 'Nombre tabla'[Nombre medida] // Si armaste la medida, una vez que descomentaste la anterior, comentas esta! Si no vas a poner hora de última actualización, dejá este bloque como está así no se rompe la medida. VAR DocumentacionActualizacion = BLANK () // ------------------------------------------------------------------------------------------------------ // 3. CONFIGURACIÓN DE TABLAS // // Declaramos qué etiqueta describe a cada tabla. // // No repetís una tabla. // Si una tabla no aparece acá, recibe la etiqueta marcada como fallback en el bloque 4. VAR ConfiguracionTablas = //Acá reemplazá con tus tablas, yo puse un ejemplo DATATABLE ( "TablaConfig", STRING, "EtiquetaConfig", STRING, { { "fct_sales", "Hechos" }, { "fct_sentiment", "Hechos" }, { "dim_calendar", "Dimensión" }, { "dim_geography", "Dimensión" }, { "dim_product", "Dimensión" }, { "Visualizations", "Medidas" }, { "⚙️ Measures", "Medidas" }, { "param_key_insigths", "Parámetro" }, { "param_userguide", "Parámetro" }, { "Documentación Tablas", "Excluir" }, { "aux_last_refresh", "Auxiliar" } } ) // ------------------------------------------------------------------------------------------------------ // 4. SEMÁNTICA DE ETIQUETAS // // Acá definís qué significa cada etiqueta para la página. // // DocumentarEstructura: // 1 = muestra sus columnas en el diccionario de estructuras // 0 = no las muestra (por ejemplo, una tabla que solo contiene medidas) // // Incluir: // 1 = forma parte de la documentación // 0 = queda completamente afuera // // EsFallback: // marcá una sola etiqueta con 1. Esa recibe cualquier tabla nueva no configurada. // // Podés renombrar "Medidas" o cualquier otra etiqueta. // La lógica de render no depende del texto: reutiliza esta configuración. VAR ConfiguracionEtiquetas = DATATABLE ( "EtiquetaConfig", STRING, "Orden", INTEGER, "DocumentarEstructura", INTEGER, "Incluir", INTEGER, "EsFallback", INTEGER, { // Etiqueta Orden Estructura Incluir Fallback { "Hechos", 1, 1, 1, 0 }, { "Dimensión", 2, 1, 1, 0 }, { "Dimensión auxiliar", 3, 1, 1, 0 }, { "Auxiliar", 4, 1, 1, 0 }, { "Parámetro", 5, 1, 1, 0 }, { "Medidas", 6, 0, 1, 0 }, { "Sin clasificar", 999, 1, 1, 1 }, { "Excluir", 1000, 0, 0, 0 } } ) // Listo. No necesitamos que toques más la medida. // Desde este punto la documentación se genera automáticamente. Espero que te sea de utilidad, Loa 💛 VAR EtiquetaFallback = MAXX ( FILTER ( ConfiguracionEtiquetas, [EsFallback] = 1 ), [EtiquetaConfig] ) VAR TablasBase = FILTER ( DocumentacionTablas, NOT [TablaOculta] && LEFT ( LOWER ( [NombreTabla] ), 4 ) <> "doc_" ) VAR TablasConEtiqueta = ADDCOLUMNS ( TablasBase, "EtiquetaTabla", VAR NombreTablaActual = [NombreTabla] VAR EtiquetaEncontrada = MAXX ( FILTER ( ConfiguracionTablas, [TablaConfig] = NombreTablaActual ), [EtiquetaConfig] ) RETURN COALESCE ( EtiquetaEncontrada, EtiquetaFallback ) ) VAR TablasConSemantica = ADDCOLUMNS ( TablasConEtiqueta, "OrdenEtiqueta", VAR EtiquetaActual = [EtiquetaTabla] RETURN COALESCE ( MAXX ( FILTER ( ConfiguracionEtiquetas, [EtiquetaConfig] = EtiquetaActual ), [Orden] ), 998 ), "DocumentarEstructuraTabla", VAR EtiquetaActual = [EtiquetaTabla] RETURN COALESCE ( MAXX ( FILTER ( ConfiguracionEtiquetas, [EtiquetaConfig] = EtiquetaActual ), [DocumentarEstructura] ), 1 ), "IncluirTabla", VAR EtiquetaActual = [EtiquetaTabla] RETURN COALESCE ( MAXX ( FILTER ( ConfiguracionEtiquetas, [EtiquetaConfig] = EtiquetaActual ), [Incluir] ), 1 ), "EsFallbackTabla", VAR EtiquetaActual = [EtiquetaTabla] RETURN COALESCE ( MAXX ( FILTER ( ConfiguracionEtiquetas, [EtiquetaConfig] = EtiquetaActual ), [EsFallback] ), 0 ) ) VAR TablasADocumentar = FILTER ( TablasConSemantica, [IncluirTabla] = 1 ) VAR NombresTablas = SELECTCOLUMNS ( TablasADocumentar, "NombreTabla", [NombreTabla] ) VAR TablasConColumnasDocumentables = FILTER ( TablasADocumentar, [DocumentarEstructuraTabla] = 1 ) VAR NombresTablasConColumnasDocumentables = SELECTCOLUMNS ( TablasConColumnasDocumentables, "NombreTabla", [NombreTabla] ) VAR ColumnasADocumentar = FILTER ( DocumentacionColumnas, [NombreTabla] IN NombresTablasConColumnasDocumentables && NOT CONTAINSSTRING ( [NombreColumna], "RowNumber-" ) ) VAR MedidasADocumentar = FILTER ( DocumentacionMedidas, NOT [MedidaOculta] && [NombreTabla] IN NombresTablas ) VAR RelacionesADocumentar = FILTER ( DocumentacionRelaciones, [TablaOrigen] IN NombresTablas && [TablaDestino] IN NombresTablas ) VAR TablasConColumnas = DISTINCT ( SELECTCOLUMNS ( ColumnasADocumentar, "NombreTabla", [NombreTabla] ) ) VAR TablasEstructuras = FILTER ( TablasADocumentar, [DocumentarEstructuraTabla] = 1 && [NombreTabla] IN TablasConColumnas ) VAR CantidadTablas = COUNTROWS ( TablasADocumentar ) + 0 VAR CantidadColumnas = COUNTROWS ( ColumnasADocumentar ) + 0 VAR CantidadMedidas = COUNTROWS ( MedidasADocumentar ) + 0 VAR CantidadRelaciones = COUNTROWS ( RelacionesADocumentar ) + 0 VAR CantidadSinClasificar = COUNTROWS ( FILTER ( TablasADocumentar, [EsFallbackTabla] = 1 ) ) + 0 VAR CantidadTablasConDescripcion = COUNTROWS ( FILTER ( TablasADocumentar, [DescripcionTabla] <> "" ) ) VAR CantidadColumnasConDescripcion = COUNTROWS ( FILTER ( ColumnasADocumentar, [DescripcionColumna] <> "" ) ) VAR CantidadMedidasConDescripcion = COUNTROWS ( FILTER ( MedidasADocumentar, [DescripcionMedida] <> "" ) ) VAR CantidadObjetos = CantidadTablas + CantidadColumnas + CantidadMedidas VAR CantidadObjetosDocumentados = CantidadTablasConDescripcion + CantidadColumnasConDescripcion + CantidadMedidasConDescripcion VAR CantidadHuecos = CantidadObjetos - CantidadObjetosDocumentados VAR CoberturaDocumentacion = DIVIDE ( CantidadObjetosDocumentados, CantidadObjetos, 0 ) VAR CoberturaTexto = FORMAT ( COALESCE ( CoberturaDocumentacion, 0 ), "0%" ) VAR EtiquetasPresentes = DISTINCT ( SELECTCOLUMNS ( TablasADocumentar, "EtiquetaResumen", [EtiquetaTabla] ) ) VAR EtiquetasConResumen = ADDCOLUMNS ( EtiquetasPresentes, "OrdenResumen", VAR EtiquetaActual = [EtiquetaResumen] VAR OrdenActual = MAXX ( FILTER ( ConfiguracionEtiquetas, [EtiquetaConfig] = EtiquetaActual ), [Orden] ) RETURN COALESCE ( OrdenActual, 998 ), "CantidadResumen", VAR EtiquetaActual = [EtiquetaResumen] RETURN COUNTROWS ( FILTER ( TablasADocumentar, [EtiquetaTabla] = EtiquetaActual ) ) ) VAR ResumenEtiquetas = CONCATENATEX ( EtiquetasConResumen, [EtiquetaResumen] & ": " & [CantidadResumen], " · ", FORMAT ( [OrdenResumen], "000" ) & "|" & [EtiquetaResumen], ASC ) VAR ResumenEstructura = "El alcance documentado contiene " & CantidadTablas & IF ( CantidadTablas = 1, " tabla. ", " tablas. " ) & ResumenEtiquetas & ". Entre las tablas incluidas hay " & CantidadRelaciones & IF ( CantidadRelaciones = 1, " relación.", " relaciones." ) VAR Fuente = "Segoe UI Variable Text,Segoe UI,system-ui,Arial,sans-serif" VAR FuenteMono = "Consolas,Menlo,monospace" VAR EstiloReset = "<style> html,body{ margin:0; padding:0; height:100%; width:100%; overflow:hidden; } *{ box-sizing:border-box; } p{ margin:0; } table{ table-layout:fixed; } </style>" VAR EstiloContenedor = "position:fixed; inset:0; overflow:hidden; background:transparent;" VAR EstiloTarjeta = "background:#FFFFFF; border:0.5px solid #E4E4E4; border-radius:12px; padding:20px 3%; width:100%; height:100%; overflow-y:auto; overflow-x:hidden; font-family:" & Fuente & ";" VAR EstiloTitulo = "font-size:19px; font-weight:600; color:#1A1A1A; margin:0 0 6px;" VAR EstiloSubtitulo = "font-size:15px; font-weight:600; color:#1A1A1A; margin:26px 0 8px;" VAR EstiloNombreSeccion = "font-size:13px; font-weight:600; color:#1A1A1A; margin:20px 0 4px;" VAR EstiloParrafo = "font-size:12.5px; line-height:1.55; color:#6B6B6B; margin:0 0 10px;" VAR EstiloTabla = "width:100%; border-collapse:collapse; margin:6px 0 4px;" VAR EstiloEncabezado = "text-align:left; font-size:11px; font-weight:600; color:#6B6B6B; text-transform:uppercase; letter-spacing:0.4px; padding:7px 10px; background:#FAFBFF; border-bottom:1px solid #E4E4E4; overflow-wrap:anywhere;" VAR EstiloNombre = "font-family:" & FuenteMono & "; font-size:11.5px; color:#4752CE; padding:7px 10px; border-bottom:1px solid #F0F0F0; vertical-align:top; word-break:break-word; overflow-wrap:anywhere;" VAR EstiloMetadata = "font-size:11px; color:#9B9B9B; padding:7px 10px; border-bottom:1px solid #F0F0F0; vertical-align:top; word-break:break-word; overflow-wrap:anywhere;" VAR EstiloDescripcion = "font-size:12px; color:#6B6B6B; padding:7px 10px; border-bottom:1px solid #F0F0F0; vertical-align:top; line-height:1.45; word-break:break-word; overflow-wrap:anywhere;" VAR EstiloRol = "font-size:11px; color:#9B9B9B; font-weight:400; padding-left:8px;" VAR EstiloFalta = "font-size:11.5px; color:#B9B9C6; font-style:italic;" VAR EstiloAviso = "font-size:11.5px; color:#8A5A12; background:#F8EFDD; border:1px solid #F0DCB4; border-radius:6px; padding:7px 11px; margin:10px 0 0; line-height:1.5;" VAR EstiloEtiqueta = "display:inline-block; background:#F8EFDD; color:#8A5A12; border-radius:4px; padding:1px 5px; font-size:10px; font-weight:600; margin-left:5px; white-space:nowrap;" VAR EstiloError = "display:inline-block; background:#FAECE7; color:#993C1D; border-radius:4px; padding:1px 5px; font-size:10px; font-weight:600; margin-left:5px; white-space:nowrap;" VAR EstiloContadores = "display:flex; flex-wrap:wrap; gap:12px; margin:14px 0 4px;" VAR EstiloContador = "flex:1 1 110px; min-width:0; background:#FAFBFF; border:1px solid #EAEDFF; border-radius:10px; padding:12px 14px; text-align:center;" VAR EstiloNumero = "font-size:22px; font-weight:600; color:#1A1A1A; margin:0;" VAR EstiloEtiquetaContador = "font-size:10.5px; color:#9B9B9B; text-transform:uppercase; letter-spacing:0.4px; margin:2px 0 0;" VAR EstiloPie = "font-size:11px; color:#9B9B9B; border-top:1px solid #F0F0F0; margin:24px 0 0; padding-top:10px;" VAR FaltaDescripcionTabla = "<span style='" & EstiloFalta & "'>Falta el propósito y la granularidad de esta tabla</span>" VAR FaltaDescripcionColumna = "<span style='" & EstiloFalta & "'>Falta la descripción</span>" VAR FaltaDescripcionMedida = "<span style='" & EstiloFalta & "'>Falta qué representa y su regla de negocio</span>" VAR FaltaDescripcionCapa = "<span style='" & EstiloFalta & "'>Falta la descripción de esta capa de medidas</span>" VAR HTMLEstructuras = CONCATENATEX ( TablasEstructuras, VAR NombreTablaActual = [NombreTabla] VAR EtiquetaTablaActual = [EtiquetaTabla] VAR DescripcionTablaActual = [DescripcionTabla] VAR NombreTablaSeguro = SUBSTITUTE ( SUBSTITUTE ( SUBSTITUTE ( NombreTablaActual, "&", "&" ), "<", "<" ), ">", ">" ) VAR EtiquetaTablaSegura = SUBSTITUTE ( SUBSTITUTE ( SUBSTITUTE ( EtiquetaTablaActual, "&", "&" ), "<", "<" ), ">", ">" ) VAR DescripcionTablaSegura = SUBSTITUTE ( SUBSTITUTE ( SUBSTITUTE ( DescripcionTablaActual, "&", "&" ), "<", "<" ), ">", ">" ) VAR FilasColumnas = CONCATENATEX ( FILTER ( ColumnasADocumentar, [NombreTabla] = NombreTablaActual ), VAR NombreColumnaActual = [NombreColumna] VAR TipoDatoActual = [TipoDato] VAR DescripcionColumnaActual = [DescripcionColumna] VAR NombreColumnaSeguro = SUBSTITUTE ( SUBSTITUTE ( SUBSTITUTE ( NombreColumnaActual, "&", "&" ), "<", "<" ), ">", ">" ) VAR TipoDatoSeguro = SUBSTITUTE ( SUBSTITUTE ( SUBSTITUTE ( TipoDatoActual, "&", "&" ), "<", "<" ), ">", ">" ) VAR DescripcionColumnaSegura = SUBSTITUTE ( SUBSTITUTE ( SUBSTITUTE ( DescripcionColumnaActual, "&", "&" ), "<", "<" ), ">", ">" ) RETURN "<tr>" & "<td style='" & EstiloNombre & "'>" & NombreColumnaSeguro & "</td>" & "<td style='" & EstiloMetadata & "'>" & TipoDatoSeguro & "</td>" & "<td style='" & EstiloDescripcion & "'>" & IF ( DescripcionColumnaActual = "", FaltaDescripcionColumna, DescripcionColumnaSegura ) & "</td>" & "</tr>", "", [NombreColumna], ASC ) RETURN "<p style='" & EstiloNombreSeccion & "'>" & NombreTablaSeguro & "<span style='" & EstiloRol & "'>" & EtiquetaTablaSegura & "</span>" & "</p>" & "<p style='" & EstiloParrafo & "'>" & IF ( DescripcionTablaActual = "", FaltaDescripcionTabla, DescripcionTablaSegura ) & "</p>" & IF ( FilasColumnas = "", "<p style='" & EstiloParrafo & "'>No hay columnas para mostrar.</p>", "<table style='" & EstiloTabla & "'>" & "<tr>" & "<th style='" & EstiloEncabezado & "'>Columna</th>" & "<th style='" & EstiloEncabezado & "'>Tipo</th>" & "<th style='" & EstiloEncabezado & "'>Descripción</th>" & "</tr>" & FilasColumnas & "</table>" ), "", FORMAT ( [OrdenEtiqueta], "000" ) & "|" & [NombreTabla], ASC ) VAR HTMLRelaciones = CONCATENATEX ( RelacionesADocumentar, VAR TablaOrigenActual = [TablaOrigen] VAR ColumnaOrigenActual = [ColumnaOrigen] VAR CardinalidadOrigenActual = [CardinalidadOrigen] VAR TablaDestinoActual = [TablaDestino] VAR ColumnaDestinoActual = [ColumnaDestino] VAR CardinalidadDestinoActual = [CardinalidadDestino] VAR DireccionFiltroActual = [DireccionFiltro] VAR RelacionActivaActual = [RelacionActiva] VAR OrigenSeguro = SUBSTITUTE ( SUBSTITUTE ( SUBSTITUTE ( TablaOrigenActual & "[" & ColumnaOrigenActual & "]", "&", "&" ), "<", "<" ), ">", ">" ) VAR DestinoSeguro = SUBSTITUTE ( SUBSTITUTE ( SUBSTITUTE ( TablaDestinoActual & "[" & ColumnaDestinoActual & "]", "&", "&" ), "<", "<" ), ">", ">" ) RETURN "<tr>" & "<td style='" & EstiloNombre & "'>" & OrigenSeguro & "</td>" & "<td style='" & EstiloMetadata & "'>" & CardinalidadOrigenActual & " → " & CardinalidadDestinoActual & "</td>" & "<td style='" & EstiloNombre & "'>" & DestinoSeguro & "</td>" & "<td style='" & EstiloMetadata & "'>" & DireccionFiltroActual & IF ( RelacionActivaActual, "", "<span style='" & EstiloEtiqueta & "'>INACTIVA</span>" ) & "</td>" & "</tr>", "", [TablaOrigen] & [ColumnaOrigen] & [TablaDestino] & [ColumnaDestino], ASC ) VAR TablasConMedidas = FILTER ( SELECTCOLUMNS ( TablasADocumentar, "NombreTabla", [NombreTabla], "EtiquetaTabla", [EtiquetaTabla], "OrdenEtiqueta", [OrdenEtiqueta] ), VAR NombreTablaActual = [NombreTabla] RETURN COUNTROWS ( FILTER ( MedidasADocumentar, [NombreTabla] = NombreTablaActual ) ) > 0 ) VAR HTMLMedidas = CONCATENATEX ( TablasConMedidas, VAR NombreTablaMedidas = [NombreTabla] VAR EtiquetaTablaMedidas = [EtiquetaTabla] VAR DescripcionCapa = MAXX ( FILTER ( TablasADocumentar, [NombreTabla] = NombreTablaMedidas ), [DescripcionTabla] ) VAR NombreTablaSeguro = SUBSTITUTE ( SUBSTITUTE ( SUBSTITUTE ( NombreTablaMedidas, "&", "&" ), "<", "<" ), ">", ">" ) VAR EtiquetaTablaSegura = SUBSTITUTE ( SUBSTITUTE ( SUBSTITUTE ( EtiquetaTablaMedidas, "&", "&" ), "<", "<" ), ">", ">" ) VAR DescripcionCapaSegura = SUBSTITUTE ( SUBSTITUTE ( SUBSTITUTE ( COALESCE ( DescripcionCapa, "" ), "&", "&" ), "<", "<" ), ">", ">" ) VAR MedidasDeTabla = FILTER ( MedidasADocumentar, [NombreTabla] = NombreTablaMedidas ) VAR Carpetas = DISTINCT ( SELECTCOLUMNS ( MedidasDeTabla, "Carpeta", [Carpeta] ) ) VAR HTMLCarpetas = CONCATENATEX ( Carpetas, VAR CarpetaActual = [Carpeta] VAR CarpetaSegura = SUBSTITUTE ( SUBSTITUTE ( SUBSTITUTE ( CarpetaActual, "&", "&" ), "<", "<" ), ">", ">" ) VAR FilasMedidas = CONCATENATEX ( FILTER ( MedidasDeTabla, [Carpeta] = CarpetaActual ), VAR NombreMedidaActual = [NombreMedida] VAR DescripcionMedidaActual = [DescripcionMedida] VAR EstadoMedidaActual = [EstadoMedida] VAR NombreMedidaSeguro = SUBSTITUTE ( SUBSTITUTE ( SUBSTITUTE ( NombreMedidaActual, "&", "&" ), "<", "<" ), ">", ">" ) VAR DescripcionMedidaSegura = SUBSTITUTE ( SUBSTITUTE ( SUBSTITUTE ( DescripcionMedidaActual, "&", "&" ), "<", "<" ), ">", ">" ) RETURN "<tr>" & "<td style='" & EstiloNombre & "'>" & NombreMedidaSeguro & IF ( EstadoMedidaActual IN { "Valid", "Ready" }, "", "<span style='" & EstiloError & "'>ERROR</span>" ) & "</td>" & "<td style='" & EstiloDescripcion & "'>" & IF ( DescripcionMedidaActual = "", FaltaDescripcionMedida, DescripcionMedidaSegura ) & "</td>" & "</tr>", "", [NombreMedida], ASC ) RETURN "<p style='" & EstiloNombreSeccion & "'>" & IF ( CarpetaActual = "", "<span style='" & EstiloFalta & "'>Sin carpeta</span>", CarpetaSegura ) & "</p>" & "<table style='" & EstiloTabla & "'>" & "<tr>" & "<th style='" & EstiloEncabezado & "'>Medida</th>" & "<th style='" & EstiloEncabezado & "'>Descripción</th>" & "</tr>" & FilasMedidas & "</table>", "", IF ( [Carpeta] = "", "zzzz", [Carpeta] ), ASC ) RETURN "<p style='" & EstiloSubtitulo & "'>" & NombreTablaSeguro & "<span style='" & EstiloRol & "'>" & EtiquetaTablaSegura & "</span>" & "</p>" & "<p style='" & EstiloParrafo & "'>" & IF ( COALESCE ( DescripcionCapa, "" ) = "", FaltaDescripcionCapa, DescripcionCapaSegura ) & "</p>" & HTMLCarpetas, "", FORMAT ( [OrdenEtiqueta], "000" ) & "|" & [NombreTabla], ASC ) RETURN EstiloReset & "<div style='" & EstiloContenedor & "'>" & "<div style='" & EstiloTarjeta & "'>" & "<p style='" & EstiloTitulo & "'>Documentación del modelo</p>" & "<p style='" & EstiloParrafo & "'>" & "La información brindada se genera a partir de los metadatos del modelo semántico." & "Las etiquetas de las tablas se toman de la configuración declarada en la medida." & "</p>" & "<div style='" & EstiloContadores & "'>" & "<div style='" & EstiloContador & "'>" & "<p style='" & EstiloNumero & "'>" & CantidadTablas & "</p>" & "<p style='" & EstiloEtiquetaContador & "'>Tablas</p>" & "</div>" & "<div style='" & EstiloContador & "'>" & "<p style='" & EstiloNumero & "'>" & CantidadColumnas & "</p>" & "<p style='" & EstiloEtiquetaContador & "'>Columnas</p>" & "</div>" & "<div style='" & EstiloContador & "'>" & "<p style='" & EstiloNumero & "'>" & CantidadMedidas & "</p>" & "<p style='" & EstiloEtiquetaContador & "'>Medidas</p>" & "</div>" & "<div style='" & EstiloContador & "'>" & "<p style='" & EstiloNumero & "'>" & CantidadRelaciones & "</p>" & "<p style='" & EstiloEtiquetaContador & "'>Relaciones</p>" & "</div>" & "<div style='" & EstiloContador & "'>" & "<p style='" & EstiloNumero & "'>" & CoberturaTexto & "</p>" & "<p style='" & EstiloEtiquetaContador & "'>Documentado</p>" & "</div>" & "</div>" & IF ( CantidadHuecos = 0, "", "<p style='" & EstiloAviso & "'>" & CantidadHuecos & " de " & CantidadObjetos & " objetos todavía no tienen descripción. " & "Los huecos quedan visibles para saber qué contexto falta." & "</p>" ) & IF ( CantidadSinClasificar = 0, "", "<p style='" & EstiloAviso & "'>" & CantidadSinClasificar & IF ( CantidadSinClasificar = 1, " tabla todavía no tiene una etiqueta asignada. ", " tablas todavía no tienen una etiqueta asignada. " ) & "Revisá el bloque CONFIGURACIÓN DE TABLAS." & "</p>" ) & "<p style='" & EstiloSubtitulo & "'>1. Estructura del modelo</p>" & "<p style='" & EstiloParrafo & "'>" & ResumenEstructura & "</p>" & "<p style='" & EstiloSubtitulo & "'>2. Estructuras de datos</p>" & IF ( HTMLEstructuras = "", "<p style='" & EstiloParrafo & "'>No hay estructuras de datos para mostrar.</p>", HTMLEstructuras ) & "<p style='" & EstiloSubtitulo & "'>3. Relaciones</p>" & IF ( HTMLRelaciones = "", "<p style='" & EstiloParrafo & "'>No hay relaciones entre las tablas documentadas.</p>", "<table style='" & EstiloTabla & "'>" & "<tr>" & "<th style='" & EstiloEncabezado & "'>Desde</th>" & "<th style='" & EstiloEncabezado & "'>Cardinalidad</th>" & "<th style='" & EstiloEncabezado & "'>Hacia</th>" & "<th style='" & EstiloEncabezado & "'>Filtro cruzado</th>" & "</tr>" & HTMLRelaciones & "</table>" ) & "<p style='" & EstiloSubtitulo & "'>4. Medidas</p>" & IF ( HTMLMedidas = "", "<p style='" & EstiloParrafo & "'>No hay medidas visibles para documentar.</p>", HTMLMedidas ) & "<p style='" & EstiloPie & "'>" & "Diseñado por Loana Ibañez 💛 datavizargentina.com.ar" & IF ( ISBLANK ( DocumentacionActualizacion ), "", " · Última actualización: " & FORMAT ( DocumentacionActualizacion, "dd/mm/yyyy HH:mm" ) ) & "</p>" & "</div>" & "</div>"
En 05 la pregunta era: ¿podemos leer el modelo sin importar cómo esté armado? Ahora la pregunta cambia: ¿podemos usar las decisiones que ya tomamos para organizar esa información sin volver a escribirla a mano?
La página completa agrega orientación antes del detalle: cuenta qué contiene el modelo, hace visibles los huecos, ordena las tablas según su función, muestra sus descripciones y columnas, explicita las relaciones y organiza las medidas por sus capas y carpetas.
Pero la fuente sigue siendo la misma.
Una descripción cambia en el modelo → cambia acá Aparece una medida → aparece acá Se agrega una relación → aparece acá Se suma una tabla nueva → aparece acá, aunque quede Sin clasificar
Comprobamos si el cambio realmente hizo más fácil trabajar con el modelo.
¿Alguien que desarrolla soluciones de BI y no construyó este modelo puede modificarlo sin hablar con quien lo creó?
Probalo. Dale el modelo a alguien que no lo construyó y pedile que:
No hace falta montar una prueba formal: una sola persona y una de estas tareas ya pueden darte una primera señal. Si después querés comparar con más casos, repetí el mismo ejercicio.
Por cada tarea, registrá tres cosas:
Repetí la prueba antes y después de documentar. Si el resultado mejora y bajan el tiempo y la ayuda requerida, tenés evidencia, no solo una sensación.
Antes de cerrar cada entrega, una pasada rápida:
Ponemos esta solución en contexto y abrimos la conversación.
Con esta página cubro lo que necesito documentar dentro del tablero. Eso no agota todas las posibilidades: otros equipos la acompañan con una bitácora de cambios, el historial de commits o herramientas de gobierno como Microsoft Purview.
¿Dónde vive hoy la documentación de tus tableros?
Esta es una forma de hacerlo que podemos usar como punto de partida y adaptar. Si en tu equipo lo resuelven de otra manera, me interesa conocerla.