De Power BI al correo, sin capacidad Premium

Cómo automatizar el envío diario de una tabla de Power BI por correo usando solo Pro y conectores estándar de Power Automate.

ARQUITECTURAS DE AUTOMATIZACIÓN · 01 De Power BI al correo, sin capacidad Premium Dos formas de hacerlo que probé en producción, y las catorce cosas que tuve que resolver para que funcionara. Recurrence Run a query Parse JSON HTML table Send email Inbox Power BI Pro · Power Automate estándar · destinatarios sin licencia

Un lunes cualquiera me llegó una petición que suena trivial:

«¿Puedes hacer que esta tabla del reporte llegue a las 10:30 pm al correo del equipo?»

Trivial no era. Tres días después tenía un flujo funcionando, y por el camino me encontré con catorce problemas distintos que nadie había documentado juntos. Este artículo es lo que me hubiera gustado leer el lunes por la mañana.

Resumen

  • Si no tienes capacidad Premium o Fabric, la exportación de imagen del reporte no está disponible y las suscripciones nativas fallan en varios escenarios muy comunes.
  • La salida es dejar de pensar en «mandar una imagen del visual» y empezar a consultar el modelo semántico directamente con DAX, reconstruyendo la tabla como HTML propio dentro de Power Automate.
  • Con eso, los destinatarios dejan de necesitar licencia. Solo la necesita la cuenta que ejecuta la consulta.
  • Hay dos patrones: A (tabla simple, 5 pasos) y B (formato condicional y varias secciones, más control y más mantenimiento).
  • Al final tienes el catálogo de fallas con causa raíz y solución, para que diagnostiques en minutos lo que a mí me costó días.

Sección 01La restricción que lo condiciona todo

Antes de escribir una línea de DAX hay que ser honesto sobre con qué contamos realmente para resolver el problema. La restricción era esta:

La solución debe operar únicamente con Power BI Pro y Power Automate en su nivel estándar. Sin capacidad Premium, Embedded o Fabric. Sin conectores premium de Power Automate.

Esta frase no es un detalle administrativo. Es la que descarta los dos caminos obvios y obliga al diseño que verás más abajo. Si tú tienes capacidad de pago, no leas el resto: usa las suscripciones nativas y ahórrate el mantenimiento.

Sección 02Lo que probé primero, y por qué no funcionó

Vale la pena contarlo, porque probablemente vas a intentar lo mismo.

2.1 · Exportar la imagen del reporte a archivo

Descartado. La acción de exportación de reportes a archivo (imagen o PDF) exige que el reporte viva en un workspace respaldado por capacidad Premium, Embedded o Fabric. En capacidad compartida (Pro estándar) la acción simplemente no está disponible.

No es un problema de permisos ni de configuración: es de infraestructura. No hay workaround.

Y cuidado con una confusión que sale cara: Premium Per User tampoco sirve. Suena a Premium, pero es una licencia por usuario, no una capacidad, y la exportación a archivo no funciona con ella. Más de uno ha pagado PPU para resolver justo esto y se ha quedado igual.

2.2 · Suscripción nativa de Power BI

Descartado en la mayoría de entornos reales. Las suscripciones nativas pueden enviar una imagen de la página del reporte con Pro. Pero fallan cuando concurre alguna de estas tres condiciones, que son sorprendentemente frecuentes:

  1. La identidad corporativa no tiene buzón asociado. La cuenta existe en el directorio, pero no tiene Exchange. No hay a dónde enviar.
  2. El buzón real está fuera del tenant. El correo del equipo vive en un proveedor externo, y Pro no permite suscribir destinatarios externos.
  3. Hay destinatarios sin licencia. Suscribirlos exige capacidad de pago.

Y aunque ninguna aplique, queda un límite de diseño: el formato del correo es fijo. No puedes maquetar, no puedes poner tu propio encabezado, no puedes agrupar tres tablas en un mismo envío.

2.3 · La decisión de diseño

Aquí está el cambio mental que resuelve todo:

Deja de intentar enviar el visual. Consulta el modelo.

El visual es una representación. El dato vive en el modelo semántico, y hay una API pública para preguntarle cosas: la ejecución de consultas (executeQueries), que Power Automate expone como la acción Run a query against a dataset.

Consecuencia bonita: como el correo sale desde una cuenta con buzón real mediante un conector de correo estándar, los destinatarios no necesitan licencia de Power BI. Reciben un correo, no un reporte.

Sección 03Antes de empezar: entorno y precondiciones

El entorno exacto con el que validé esto

Power BI Desktop
2.156.951.0 (Microsoft Store, actualizada el 21/07/2026)
Power BI Service
Licencia Pro, capacidad compartida
Power Automate
Nivel estándar, sin conectores premium
Modelo semántico
Modo Import, refresco programado
Envío de correo
Conector estándar desde una cuenta con buzón real
Última verificación
Agosto de 2026

Una precisión que importa: la versión de Desktop es donde modelé y de donde salió el DAX del Performance Analyzer. El flujo en sí corre contra el Service y contra Power Automate, que son servicios evergreen y no tienen número de versión. Si Microsoft cambia el comportamiento de una acción, no lo verás en este número: lo verás en tu flujo.

Y estas son las precondiciones. No pierdas una tarde para descubrirlas al final.

RequisitoDetalle
Modo del modeloImport. La documentación solo descarta de forma explícita los modelos alojados en Analysis Services, pero en la práctica DirectQuery, Direct Lake y las conexiones en vivo devuelven un «no encontrado» que despista muchísimo, porque el modelo evidentemente sí existe.
Configuración del tenantUn administrador debe activar Semantic Model Execute Queries REST API (en tenants más antiguos aparece como Dataset Execute Queries REST API), dentro de Configuración de integración. No es el endpoint XMLA: ese es otro ajuste distinto, y activarlo no habilita esto. Merece la pena decírselo con ese nombre exacto a quien te lo tenga que abrir.
LicenciaPro para la cuenta que ejecuta la consulta. Los destinatarios, ninguna.
Cuenta de envíoCon buzón real: Exchange con licencia, o un proveedor externo vía conector estándar.
PermisosLectura y compilación (Build) sobre el modelo semántico. Y una trampa fácil de sufrir: aunque tengas Build, si tu rol en el workspace es solo Visor, el modelo no llega a aparecer en el desplegable de Power Automate. Hace falta rol de colaborador para poder seleccionarlo.
Límites de la API100.000 filas o 1.000.000 de valores por consulta, lo que se alcance primero. Más 15 MB por respuesta y 120 consultas por minuto y usuario.
Ventana de refrescoEl envío debe ocurrir después del último refresco programado del modelo.

Sección 04Patrón A — Tabla simple, formato uniforme

Aplica cuando la tabla tiene una sola sección y un estilo uniforme: sin colores por celda o fila que dependan del dato.

PATRÓN A Tabla simple, formato uniforme Modelo semántico (modo Import) Power BI Service · refresco programado · la consulta lee el modelo, no el visual Power Automate · flujo programado 1 Recurrence Dispara el flujo a hora fija, en zona horaria local Estándar 2 Run a query against a dataset Ejecuta la consulta DAX contra el modelo publicado Estándar + Pro 3 Parse JSON → Select Tipa el resultado, renombra columnas y aplica formato de presentación Built-in 4 Create HTML table → Compose Genera la tabla e inyecta el CSS reemplazando las etiquetas table, th y td Built-in 5 Compose fecha → Send an email Fecha dinámica localizada y envío del cuerpo HTML desde una cuenta con buzón Estándar Destinatarios No necesitan licencia de Power BI: reciben un correo, no un reporte La única licencia Pro requerida es la de la cuenta que ejecuta la consulta.
Cinco pasos de principio a fin. La única acción que consume licencia Pro es la consulta.

Son cinco pasos. Vamos uno por uno.

Paso 1 · Recurrence

El disparador programado.

  • Interval / Frequency: el que necesites (Day / 1).
  • Time zone: tu zona horaria local. No lo dejes en UTC. Este campo evita la mitad de los problemas de «ayer/hoy» que verás en el catálogo de fallas.
  • At these hours / minutes: la hora fija de envío.

Regla de oro: la hora de envío va después del último refresco programado del modelo, con margen. Si el refresco es a las 22:00 y suele tardar 20 minutos, no programes el envío a las 22:15.

Paso 2 · Run a query against a dataset

Aquí ocurre lo importante. La acción pide workspace, dataset y una consulta DAX. Este es el esquema que uso como base:

DAX
DEFINE
    VAR _FiltroBase =
        FILTER (
            KEEPFILTERS ( VALUES ( 'Tabla'[Dimension] ) ),
            NOT ( 'Tabla'[Dimension] IN { BLANK() } )
        )

    VAR _Core =
        SUMMARIZECOLUMNS (
            ROLLUPADDISSUBTOTAL ( 'Tabla'[Dimension], "EsTotal" ),
            _FiltroBase,
            "M1", [Medida1],
            "M2", [Medida2]
        )

    VAR _Ventana =
        TOPN ( 100000, _Core, [EsTotal], 1, [M1], 0 )

EVALUATE
    _Ventana
ORDER BY
    [EsTotal] ASC,
    [M1] DESC

Tres convenciones que importan:

  1. ROLLUPADDISSUBTOTAL añade la fila de total y una bandera (EsTotal) que te dice si esa fila es el subtotal.
  2. Ordenar la bandera en ascendente manda el total al final. Las filas de detalle traen 0, el total trae 1. ASC lo empuja abajo. Si lo pones DESC, el total te aparece arriba (falla P6).
  3. Un TOPN holgado evita truncar filas. Ponlo cerca del límite de la API, no en 1000 «por si acaso». Eso sí, el techo real no se mide solo en filas; en la falla P8 explico por qué.

Un truco que ahorra tiempo: construye el visual en Power BI Desktop, abre el Performance Analyzer, copia la consulta DAX generada y úsala como punto de partida. Pero léela antes de pegarla, porque suele traer basura (ver falla P4).

Paso 3 · Parse JSON y Select

Van juntos porque son la misma idea en dos mitades: el Parse JSON le da estructura tipada al resultado y el Select decide qué se ve y cómo.

3a · Parse JSON

La acción devuelve las filas en firstTableRows. En la entrada del Parse JSON pon:

Expresión
body('Run_a_query_against_a_dataset')?['firstTableRows']

Y el esquema, con las propiedades anulables. Esto es crítico: si declaras "type": "string" a secas y llega un null, el flujo revienta.

JSON
{
  "type": "array",
  "items": {
    "type": "object",
    "properties": {
      "Tabla[Dimension]": { "type": ["string", "null"] },
      "[M1]":             { "type": ["number", "null"] },
      "[M2]":             { "type": ["number", "null"] },
      "[EsTotal]":        { "type": ["integer", "null"] }
    }
  }
}

Fíjate en la forma de los nombres: las columnas del modelo llegan como Tabla[Columna] y las medidas como [Medida], con los corchetes incluidos. Cópialos exactos desde una ejecución de prueba.

3b · Select

Esta acción hace dos trabajos: renombrar las columnas a los títulos que verá el destinatario, y formatear números y fechas.

Clave (lo que se ve en el correo)Valor
Segmentoitem()?['Tabla[Dimension]']
VentasformatNumber(item()?['[M1]'], 'N0', 'es-ES')
CumplimientoformatNumber(item()?['[M2]'], 'P2', 'es-ES')

El formateo se hace aquí, no en el DAX. Así el DAX devuelve números limpios y el flujo decide la presentación. El sufijo del formato es el de .NET: N0 para miles sin decimales, P2 para porcentaje con dos.

Si vas a usar el Patrón B, este es el momento de arrastrar la columna de color. Se añade una fila más al Select, con un nombre que empiece por guion bajo para recordar que es auxiliar:

ClaveValor
_coloritem()?['[Color Fila]']

En el Patrón A esa fila sobra. En el Patrón B es la que hace todo el trabajo, y por eso se explica más abajo.

Paso 4 · Create HTML table + Compose

Create HTML table con From = salida del Select y Columns = Automatic. Te devuelve una tabla HTML sin ningún estilo, literalmente <table><tr><th>…

Para que no parezca un correo de 1998, un Compose que inyecta CSS reemplazando las etiquetas:

Expresión
replace(
  replace(
    replace(
      body('Create_HTML_table'),
      '<table>',
      '<table style="border-collapse:collapse;width:100%;font-family:Segoe UI,Arial,sans-serif;font-size:13px;">'
    ),
    '<th>',
    '<th style="background-color:#0E2233;color:#FFFFFF;padding:8px 10px;text-align:left;border:1px solid #D7DFE6;">'
  ),
  '<td>',
  '<td style="padding:7px 10px;border:1px solid #D7DFE6;color:#0E2233;">'
)

Es feo, pero es la forma más corta de estilizar una tabla generada automáticamente.

Paso 5 · La fecha y el envío

Un Compose para la fecha, convertida a zona local antes de formatear:

Expresión
formatDateTime(
  convertTimeZone(utcNow(), 'UTC', 'SA Pacific Standard Time'),
  'dd/MM/yyyy'
)

Sustituye el identificador de zona por el tuyo (E. South America Standard Time, Central Standard Time (Mexico), Romance Standard Time…). Y después, Send an email (V2), pegando el HTML en la vista de código del cuerpo (el botón </>), no en el editor visual.

Y esto es lo que recibe el equipo

Este es el resultado, con datos de ejemplo. Nadie en la lista de destinatarios tiene licencia de Power BI: para ellos esto es un correo más, y esa es exactamente la idea.

RESULTADO · PATRÓN A Reporte diario de campañas · 28/08/2026 Externo Alertas Cuenta de envío automatizada para: lista de distribución del equipo 22:30 Buenas noches, Les comparto el resultado del cierre del día 28/08/2026. Saludos. Fecha Campaña Registros Atendidos Sin atender % sin atender 28/08/2026campana_alfa_directo 42.18040.955 1.2252,90 % 28/08/2026campana_beta_social 18.64017.902 7383,96 % 28/08/2026campana_gamma_reactivacion 9.4159.180 2352,50 % 28/08/2026campana_delta_web 6.3026.121 1812,87 % 28/08/2026campana_epsilon_referidos 3.8773.790 872,24 % 28/08/2026campana_zeta_retencion 1.2041.166 383,16 % TOTAL 81.618 79.114 2.504 3,07 % Mensaje automatizado, favor de no responder. Una sección · un estilo para todas las celdas · fila de total al final gracias al orden ascendente de la bandera Datos de ejemplo. Los destinatarios no tienen licencia de Power BI.
Una sección, un estilo para todas las celdas y la fila de total abajo del todo.

Cuatro cosas sobre HTML en correo que aprendí a golpes

  1. Outlook clásico renderiza con el motor de Word. Olvídate de flexbox, grid y position: maqueta con tablas anidadas, como en 2005. El Outlook nuevo de Windows ya usa un motor de navegador y admite CSS actual, pero esto no se arregla solo: Microsoft mantiene el soporte del Outlook clásico hasta 2029 como mínimo, y las empresas grandes son justo las últimas de la fila en migrar. Vas a tener los dos motores en la misma bandeja durante años, así que escribe para el peor.
  2. CSS inline siempre. Un bloque <style> en el <head> se descarta en varios clientes.
  3. Ancho en píxeles, no en porcentaje, si necesitas control real. Y no pases de unos 600 px de ancho útil.
  4. Prueba en móvil. Más de la mitad de tus destinatarios van a abrir ese correo desde el teléfono, fuera del horario de oficina.

Sección 05Patrón B — Formato condicional y múltiples secciones

Extiende el Patrón A cuando necesitas (a) resaltar filas o celdas según el dato, o (b) meter varias tablas en un mismo correo, una por segmento.

PATRÓN B Formato condicional y múltiples secciones Modelo semántico (modo Import) Incluye una medida que devuelve el color de cada fila en hexadecimal Power Automate · flujo programado Recurrence Disparo diario en zona horaria local Compose fecha + Initialize variable (una por sección) Todas las variables se declaran al inicio y arrancan con el encabezado de la tabla: <table><tr><th>…</th></tr> SE REPITE POR CADA SECCIÓN / SEGMENTO Run a query → Parse JSON → Select Consulta filtrada al segmento; el Select arrastra la columna de color como campo auxiliar Apply to each → Append to string variable Construye cada <tr> leyendo el color de esa fila para decidir fondo y color de texto. La columna de color se usa en el atributo style; nunca se pinta como celda visible. Append '</table>' Fuera del bucle: cierra la tabla de esa sección una sola vez Send an email Cuerpo con título, fecha y la variable de cada sección, separadas y maquetadas Destinatarios Un solo correo con varias tablas, cada fila coloreada según su propio dato Create HTML table no puede pintar por valor de fila: por eso aquí el HTML se construye a mano.
El bloque punteado se repite tal cual por cada segmento; solo cambia el filtro de la consulta y la variable de destino.

5.1 · Por qué el Patrón A no alcanza

Create HTML table aplica el mismo estilo a todas las celdas. No puede leer el valor de cada fila para decidir un color. No hay parámetro, no hay opción escondida: la acción genera la tabla completa de una vez y tú solo puedes hacer reemplazos globales sobre el resultado.

Para pintar por valor, hay que construir el HTML a mano, fila por fila.

5.2 · El truco: que el color venga del modelo

En vez de meter la lógica de color en el flujo, donde es horrible de mantener, la pongo como medida en el modelo:

DAX
Color Fila =
SWITCH (
    TRUE (),
    [Cumplimiento] >= 1,    "#2F6B4F",
    [Cumplimiento] >= 0.85, "#B4531A",
    "#A03030"
)

Ventajas: la regla vive junto al resto de la lógica de negocio, se prueba en Desktop y si cambian los umbrales no hay que tocar el flujo.

La medida entra en la consulta como una columna más y llega al Parse JSON como [Color Fila]. De ahí la recoge el Select en la clave _color que viste en el paso 3b, y es esa clave la que se lee en el bucle. Si te saltas ese paso, todas las filas te van a salir blancas y vas a revisar el bucle durante media hora sin encontrar nada.

5.3 · Construcción manual, paso a paso

Al inicio del flujo, un Initialize variable de tipo String por cada sección, arrancando con el encabezado:

Expresión
concat(
  '<table style="border-collapse:collapse;width:100%;font-family:Segoe UI,Arial,sans-serif;font-size:13px;">',
  '<tr>',
    '<th style="background-color:#0E2233;color:#FFFFFF;padding:8px 10px;text-align:left;">Segmento</th>',
    '<th style="background-color:#0E2233;color:#FFFFFF;padding:8px 10px;text-align:right;">Ventas</th>',
  '</tr>'
)

Dentro de un Apply to each sobre la salida del Select, un Append to string variable:

Expresión
concat(
  '<tr style="background-color:',
    if(equals(item()?['_color'], null), '#FFFFFF', item()?['_color']),
  ';color:',
    if(equals(item()?['_color'], null), '#111111', '#FFFFFF'),
  ';">',
  '<td style="padding:7px 10px;border:1px solid #D7DFE6;">',
    coalesce(item()?['Segmento'], ''),
  '</td>',
  '<td style="padding:7px 10px;border:1px solid #D7DFE6;text-align:right;">',
    coalesce(item()?['Ventas'], ''),
  '</td>',
  '</tr>'
)

Fuera del bucle, un último Append con '</table>'.

Dos detalles que separan un flujo que funciona de uno que funciona siempre:

  • La columna de color viaja en el Select como campo auxiliar (yo la llamo _color) y se consume solo dentro del atributo style. Nunca se emite como celda visible.
  • El control de concurrencia del Apply to each tiene que estar desactivado. Viene así de fábrica, pero se activa con un clic y es tentador cuando el bucle va lento. Si lo activas, varias iteraciones escriben sobre la misma variable a la vez y no solo se desordenan las filas: se pierden. Dos iteraciones leen el mismo valor y una pisa a la otra. Lo peor es que el flujo termina en verde, sin error, así que lo descubres cuando alguien te dice que en el correo faltan clientes.

El correo que sale de aquí

Mismo flujo de fondo, resultado muy distinto: dos tablas separadas en un solo envío y una fila en rojo que salta a la vista antes de leer un solo número.

RESULTADO · PATRÓN B Alertas de atención por segmento · 28/08/2026 Externo Cuenta de envío automatizada para: lista de distribución del equipo 22:30 Buenas noches, Les comparto el detalle del día 28/08/2026. Se resaltan en rojo las campañas que superan el umbral acordado. SEGMENTO NORTE FechaCampaña Registros Sin atender % sin atender 28/08/2026campana_alfa_directo 12.4804023,22 % 28/08/2026campana_beta_social 6.1151682,75 % 28/08/2026campana_gamma_reactivacion 2.94063121,46 % 28/08/2026campana_delta_web 1.877542,88 % TOTAL 23.412 1.255 5,36 % SEGMENTO SUR FechaCampaña Registros Sin atender % sin atender 28/08/2026campana_epsilon_referidos 8.2041902,32 % 28/08/2026campana_zeta_retencion 4.0371213,00 % 28/08/2026campana_eta_cobranza 1.559422,69 % TOTAL 13.800 353 2,56 % Mensaje automatizado, favor de no responder. Dos secciones, dos variables · el rojo lo decide una medida del modelo, no el flujo · datos de ejemplo
El rojo no lo decide el flujo: viene de una medida del modelo. Cambiar el umbral no obliga a tocar Power Automate.

5.4 · Optimización opcional: elimina el bucle

Si la tabla tiene cientos de filas, el Apply to each se vuelve lento: es una acción por fila. Hay una alternativa mucho más rápida.

Usa un Select en modo texto (el botón que cambia de mapa a expresión única) donde cada elemento ya sea el <tr> completo, y luego únelos:

Expresión
join(body('Select_filas_HTML'), '')

Un solo paso, sin bucle. La lógica del concat es idéntica; solo cambia dónde vive.

5.5 · Patrón A o Patrón B: cómo decidir

COMPARATIVA Qué cambia entre un patrón y el otro Aspecto PATRÓN A PATRÓN B Construcción de la tabla Create HTML table, automática Manual, fila por fila con Apply to each Estilo Uniforme, inyectado con un Compose Condicional por fila, el color viene de una medida Secciones en el correo Una Varias: una variable HTML por segmento Variables de estado Ninguna imprescindible Una cadena por sección, declarada al inicio Costo de mantenimiento Bajo Medio más pasos, más frágil Regla práctica: no pagues la complejidad del Patrón B si nadie va a mirar el color. El Patrón B se justifica cuando el color comunica una decisión, no cuando decora.
El Patrón B no es «el bueno». Es el caro.

Y si prefieres decidirlo en veinte segundos:

DECISIÓN Qué camino te toca a ti ¿Tienes capacidad Premium o Fabric, y todos los destinatarios están en el tenant? NO Usa lo nativo Suscripción de Power BI o exportación a archivo. Menos piezas que mantener. ¿El correo necesita color que dependa del dato, o varias tablas separadas por segmento? NO PATRÓN A Tabla simple, formato uniforme 5 pasos. Es el que deberías querer. Empieza aquí aunque acabes migrando. PATRÓN B HTML fila a fila Más control y más mantenimiento. Condiciones que aplican a los dos patrones Modelo en modo Import · ejecución de consultas habilitada en el tenant · licencia Pro para quien ejecuta la consulta · cuenta de envío con buzón real · envío después del último refresco programado del modelo Límite de la API por ejecución: 100.000 filas / 15 MB
Si la primera respuesta es «sí», este artículo no es para ti — y eso es una buena noticia.

Sección 06El catálogo de fallas

Esta es la parte que más me hubiera servido tener. Cada problema con su causa raíz y su solución, para que lo uses como guía de diagnóstico rápido.

P1

La imagen exacta del reporte no es exportable con Pro

Causa

La exportación a archivo exige capacidad Premium o Fabric.

Solución

Abandonar la idea de «imagen» y consultar el modelo para reconstruir la tabla como HTML propio.

P2

La suscripción nativa no entrega el correo

Causa

La identidad de Power BI no tiene buzón, y el buzón real es externo al tenant: destinatario externo, no permitido en Pro.

Solución

Enviar desde una cuenta con buzón real mediante conector de correo estándar. Los destinatarios dejan de necesitar licencia.

P3

empty() falla sobre valores numéricos

Causa

Hablo de la opción incluir nulos de la propia acción de consulta, no del esquema del Parse JSON. Con esa opción desactivada, las celdas con dato llegan como enteros, y empty() solo acepta texto, arrays u objetos.

Solución

Sustituir empty(x) por equals(x, null) en las expresiones de formato.

P4

La consulta traía dos EVALUATE

Causa

El DAX copiado del Performance Analyzer incluía un segundo EVALUATE destinado al formato condicional del visual.

Solución

La acción ejecuta un solo EVALUATE. Elimina el bloque auxiliar y conserva únicamente el que devuelve la tabla.

P5

La fila de Total no cuadraba con las filas visibles

Causa

El filtro de limpieza (excluir filas vacías) se aplicaba después del rollup, así que el total seguía sumando filas ocultas.

Solución

Aplicar el filtro antes del rollup, como subconsulta de filtro por valor, para que el total refleje exactamente lo mostrado.

DIAGNÓSTICO · P5 El total no cuadra con las filas visibles El mismo DAX, cambiando solo el momento en que se aplica el filtro de limpieza. Filtro DESPUÉS del rollup Dimensión Medida Segmento A 120 Segmento B 80 (fila vacía, filtrada de la vista) 50 Total 250 El rollup ya había sumado la fila vacía. Se ocultó la fila, pero no su aporte al total. Filtro ANTES del rollup Dimensión Medida Segmento A 120 Segmento B 80 la fila vacía nunca entró al cálculo Total 200 El total refleja exactamente lo que se ve. El filtro viaja como subconsulta dentro del rollup. Un total que no cuadra destruye la confianza en el reporte completo, aunque el resto esté perfecto.
El mismo DAX. Lo único que cambia es cuándo entra el filtro.
P6

El Total aparecía arriba en lugar de abajo

Causa

La bandera de total se ordenaba en sentido descendente.

Solución

Ordenarla en ascendente, y ajustar el criterio equivalente en el TOPN, para enviarla al final.

P7

Diferencias de cifras entre capturas

Causa

En reportes de «día en curso» el modelo se refresca durante el día, así que dos lecturas en momentos distintos difieren. Y ojo: refrescar el reporte no refresca el modelo.

Solución

Validar siempre contra el Service tras el mismo refresco, y alinear la hora de envío para que ocurra después del último refresco programado.

P8

Truncamiento de filas al agregar una dimensión

Causa

Al cruzar por más dimensiones —añadir fecha, por ejemplo— el número de filas se multiplica y supera el límite del TOPN. Y hay un segundo techo que casi nadie menciona: la API no cuenta filas, cuenta celdas. Con 5 columnas caben 100.000 filas; con 20 columnas, solo 50.000.

Solución

Elevar el TOPN, y contar columnas además de filas. Si la tabla es ancha, tu techo real está muy por debajo de las 100.000 filas que aparecen en la documentación.

P9

El formato condicional no se reflejaba en el correo

Causa

Create HTML table aplica estilo uniforme; no puede pintar por valor de fila.

Solución

Migrar al Patrón B, construyendo el HTML fila por fila y leyendo una medida de color incluida en la consulta.

P10

Columnas vacías por nombres con caracteres especiales

Causa

Al referenciar columnas con item()?['Nombre'], los nombres con punto, símbolo de porcentaje u otros caracteres especiales no hacen match exacto entre el Select y el Append.

Solución

Nombrar las columnas internas sin caracteres especiales y garantizar coincidencia carácter a carácter entre Select y Append. Los títulos visibles del encabezado sí pueden llevar tildes: solo se muestran, no se referencian.

P11

Fecha literal interpretada como expresión

Causa

Escribir una fecha entre llaves de expresión hace que el motor intente evaluarla como una operación aritmética.

Solución

Usar una fecha dinámica generada por un Compose, convertida a zona local, o texto plano sin llaves.

P12

«Ayer / hoy» incorrecto por zona horaria

Causa

La hora del sistema está en UTC. De madrugada hora local, en UTC el día ya cambió.

Solución

Convertir a zona horaria local antes de calcular el día, con convertTimeZone.

P13

Copia de flujo inactiva y bloques cruzados

Causa

Una copia de flujo nace apagada. Y al duplicar bloques es fácil que una etapa apunte a la fuente equivocada, o que las variables no se inicialicen al inicio.

Solución

Activar la copia, revisar conexiones, declarar todas las variables al inicio del flujo, y verificar que cada Parse JSON consuma la salida de su propia consulta.

P14

La consulta corre bien en Desktop pero muere en el flujo

Causa

La acción tiene su propio tiempo máximo de ejecución, de alrededor de dos minutos, y no se amplía tocando la configuración del flujo. Una consulta que en Desktop tarda tres minutos aquí no llega a terminar nunca.

Solución

Aligerar la consulta: menos columnas, más agregación resuelta en el modelo, filtros más estrechos. Si aun así no baja de dos minutos, suele ser señal de que le estás pidiendo al correo algo que en realidad debería ser un reporte.

Sección 07Checklist de operación y mantenimiento

Imprímela y pégala al lado del flujo.

  • La hora de envío queda después del refresco programado del modelo, con margen.
  • Las cifras se validan contra el Service, no contra el entorno de desarrollo, y tras el mismo refresco.
  • En Patrón B: los nombres de columna son idénticos entre Select y Append.
  • En Patrón B: todas las variables se declaran al inicio del flujo.
  • En Patrón B: cada tabla se cierra fuera del bucle.
  • En Patrón B: el control de concurrencia del Apply to each está desactivado.
  • Si cambian nombres de columnas en el modelo, se revisa el esquema del Parse JSON y las referencias del Select/Append.
  • En copias de un flujo: activarlas, revisar conexiones y verificar que cada etapa consuma su propia fuente.
  • Las fechas del cuerpo del correo son dinámicas y localizadas, nunca valores fijos.
  • Hay una alerta o un Configure run after que avise si el flujo falla. Un correo que deja de llegar es más peligroso que uno con un error visible.

Sección 08Glosario

TérminoQué significa
Ejecución de consultas (executeQueries)API que corre una consulta DAX contra un modelo publicado y devuelve datos. Es la base de la acción Run a query against a dataset.
Exportación a archivoGenera imagen o PDF del reporte. Requiere capacidad de pago; no se usa aquí.
Conector estándarConector de Power Automate disponible sin licencia premium.
Rollup / subtotalFila de total agregada al resultado por la función de rollup.
Modelo ImportDatos cargados en el modelo; cambian solo al refrescar, no en tiempo real.
Dataset / modelo semánticoEl mismo objeto. Microsoft renombró «dataset» a «modelo semántico», pero el nombre antiguo sigue vivo en muchos sitios: la acción de Power Automate todavía se llama Run a query against a dataset. Si ves los dos términos mezclados, no te estás perdiendo nada.
Formato condicionalEstilo, por ejemplo un color, que depende del valor de cada fila o celda.

Sección 09Lo que me llevé de todo esto

Tres ideas que aplico ahora en cualquier automatización, no solo en esta.

La restricción define la arquitectura

No perdí tiempo buscando cómo saltarme el licenciamiento; lo acepté como dato de entrada y diseñé desde ahí. La solución elegante casi nunca es la que pelea contra el terreno.

El visual no es el dato

Cada vez que te pidan «mandar lo que se ve en el reporte», pregúntate si de verdad necesitas la imagen o solo los números. Casi siempre son los números, y los números tienen una API.

Un total que no cuadra cuesta más que un correo que no llega

Si el correo falla, alguien te escribe. Si el total está mal por 50 unidades durante tres meses, nadie te escribe y todos toman decisiones con datos rotos. Valida los totales primero.

Sección 10Fuentes oficiales

¿Te has topado con alguna de estas catorce? ¿O con una decimoquinta que debería estar en la lista? Cuéntamela en los comentarios y la incorporo al artículo con tu crédito.

Glenda Piñero Muñoz

Especialista en Power BI y análisis de datos. Construyo modelos, y también los flujos de Power Automate que hacen que esos datos lleguen solos a quien los necesita: alertas, integraciones y entrega automatizada. Escribo sobre los problemas reales que me encuentro y cómo los resuelvo.