Jhonatan Pinheiro
Cargando página...
Jhonatan Pinheiro
Cargando página...
Jhonatan Pinheiro
Cargando página...
120 preguntas de entrevista y del día a día respondidas con ejemplos reales: Power Query y refresh, modelado, contexto de evaluación en DAX, SQL detrás del modelo, diseño de dashboards, Figma y métricas de uso.
Este es un post de consulta y repaso: 120 preguntas que aparecen en entrevistas, en la revisión de un tablero y en ese momento en que el número no cuadra y hay que encontrar la causa. Cada respuesta es directa y viene con un ejemplo real — nada de definición de manual sin aplicación.
Todos los ejemplos usan el mismo modelo de retail:
Casa & Construção Nordeste — 42 tiendas, 3 canales (tienda física, e-commerce, televenta), unos 4,2 millones de filas de venta al año. Tablas:
fVendas(grano = ítem del cupón),dProduto,dLoja,dCliente,dCalendarioyfMetas.
Cómo usarlo: léelo por bloque (Power Query, modelado, DAX, SQL, UX/UI, proceso) o úsalo como checklist la víspera de una entrevista. Las preguntas marcadas con 🔥 son las que más aparecen — y las que más tumban a quien solo memorizó la sintaxis.
La herramienta, la arquitectura de conexión y la etapa de preparación de los datos.
Es la herramienta gratuita donde se conecta, transforma, modela y diseña el informe. No es una base de datos ni un ETL corporativo: no guarda histórico propio, no orquesta cargas y no sustituye a un data warehouse. Cuando alguien usa Power BI como repositorio real de la empresa, el síntoma aparece rápido — archivos de 800 MB circulando por correo con números divergentes.
Desktop hace: conectar, transformar (M), modelar, medir (DAX), diseñar
Desktop no hace: guardar histórico, agendar cargas, controlar acceso corporativo
Eso es trabajo de la base/DW + del servicio Power BI.Desktop es la aplicación de Windows donde construyes. Service (app.powerbi.com) es la nube donde publicas, programas actualizaciones, compartes y controlas el acceso. Report Server es la versión instalada en el servidor de la empresa, para quien no puede publicar en la nube — con recursos más limitados y un ciclo de novedades más lento.
Construir -> Desktop (gratis)
Publicar -> Service (Pro/PPU/Fabric) o Report Server (on-premises)
Consumir -> navegador, app móvil, Teams, embebido en otro sistemaPara construir en Desktop, no: es gratis. La licencia Pro (o PPU/capacidad Fabric) hace falta para publicar y compartir con otras personas en un área de trabajo. Una excepción práctica: con capacidad Premium/Fabric, quien solo consume puede tener licencia gratuita.
Desktop gratis
Publicar en un área de trabajo Pro (por usuario)
Consumir en capacidad Premium gratis para el lector
Compartir el .pbix por correo funciona, pero es una pésima idea
(sin gobernanza, sin actualización, sin RLS)Es el conjunto de tablas + relaciones + medidas: la definición de qué significan los números. Separarlo hace que varios equipos construyan informes distintos sobre la misma verdad, vía Conexión dinámica. Sin eso, cada área crea su propia medida de facturación y la reunión se convierte en un debate sobre qué hoja de cálculo tiene razón.
1 modelo semántico "Vendas Corporativo"
<- informe de la Dirección (Conexión dinámica)
<- informe Comercial (Conexión dinámica)
<- informe de Tiendas (Conexión dinámica)
La facturación se define UNA vez. Todos leen el mismo número.Importación copia los datos a memoria — rápido, con todo el lenguaje DAX; es el estándar en el 90% de los casos. DirectQuery consulta el origen en cada interacción: úsalo solo cuando la exigencia de tiempo real sea de verdad, o cuando el dato no pueda salir del origen. Conexión dinámica conecta a un modelo semántico ya publicado (o a Analysis Services). Dual es para dimensiones pequeñas en modelo compuesto.
La pregunta que decide:
"¿Actualizar cada hora resuelve el problema de negocio?"
SÍ -> Importación (rápido, barato, DAX completo)
NO -> DirectQuery (lento, DAX limitado, carga en el origen)
Conexión dinámica -> reutilizar un modelo corporativo ya publicadoPor el VertiPaq, el motor columnar en memoria: guarda por columna, no por fila, y comprime con diccionario — la categoría Tintas, repetida 4 millones de veces, se vuelve un número que apunta a una única entrada. La consecuencia práctica: columnas de baja cardinalidad comprimen muchísimo, y las de alta cardinalidad (ID del cupón, una marca de tiempo con segundos) son las que inflan el modelo.
Lo que infla el modelo (en orden):
1. columna de alta cardinalidad (CupomID, un GUID, un datetime con segundos)
2. columnas de texto largo (la descripción completa del producto)
3. columnas que no usas y ni recuerdas haber importado
Un truco: separar fecha y hora en dos columnas.
Un datetime con segundos = millones de valores distintos.
Fecha (3.650 valores) + Hora (1.440) comprime muchísimo mejor.Apunta a una vista con las columnas y el período correctos, o escribe la consulta en la conexión. Marcar la tabla en la lista y hacer clic en Cargar trae todo — 60 columnas y 12 años — para responder sobre 24 meses.
Obtener datos > SQL Server > Opciones avanzadas > Instrucción SQL
SELECT DataVenda, LojaSK, ProdutoSK, Qtd, ValorLiquido, Custo
FROM dbo.vw_fato_vendas
WHERE DataVenda >= '2024-01-01';Es Power Query traduciendo tus pasos a una única consulta SQL y dejando que el servidor la ejecute. Mientras ocurre, filtrar 4 millones de filas casi no cuesta nada; cuando se rompe, Power BI descarga todo y filtra en tu máquina. Compruébalo con clic derecho en el paso → Ver consulta nativa: si está en gris, se rompió ahí.
Se pliega: filtrar filas, quitar columnas, agrupar, renombrar,
combinar tablas del mismo servidor
Lo rompe: Table.Buffer, columna de índice personalizada, funciones M sin
equivalente en SQL, combinar orígenes distintos
Estrategia: pon TODOS los pasos que se pliegan primero.M se ejecuta en la actualización y define cómo llega el dato: estructura, tipo, limpieza. DAX se ejecuta en el clic y define qué significa el número bajo aquel filtro. Una señal clásica de confusión: una columna llamada Total del Año creada en Power Query — no cambia cuando el usuario filtra marzo, y todos concluyen que el tablero está mal.
M (Power Query) DAX
corre en la actualización corre en cada interacción
resultado fijo el resultado depende del filtro
prepara la tabla calcula el indicador
lenguaje funcional lenguaje de expresiones analíticasLocale. El archivo se generó en pt-BR (coma decimal) y Power Query lo interpretó como en-US, tratando la coma como separador de miles. No da error visible — solo una facturación cien veces mayor. Tipifica explícitamente indicando la cultura, o usa Usar configuración regional en el cuadro de tipo.
= Table.TransformColumnTypes(
Fonte,
{{"ValorLiquido", type number}, {"DataVenda", type date}},
"pt-BR"
)Unpivot. Selecciona las columnas que deben quedarse (la clave) y usa Anular dinamización de columnas > Anular dinamización de otras columnas. Elegir otras es el detalle que importa: cuando la hoja gane la columna de un mes nuevo, la consulta sigue funcionando.
// Antes: Loja | Jan | Fev | Mar (la hoja de metas)
// Después: Loja | Mes | MetaValor
= Table.UnpivotOtherColumns(Fonte, {"Loja"}, "Mes", "MetaValor")Append apila tablas de la misma estructura: ventas de 2025 + ventas de 2026, más filas. Merge es el JOIN: trae columnas de otra tabla por la clave, más columnas. En un modelo estrella, combinar la dimensión dentro del hecho casi siempre es un error — el vínculo correcto es una relación.
Append -> mismas columnas, más filas
Merge -> mismas filas, más columnas
Tipos de join:
Left Outer = LEFT JOIN (el estándar, y lo que quieres el 90% de las veces)
Inner = INNER JOIN
Left Anti = lo que existe aquí y NO existe allá (excelente para auditar)Conecta a la carpeta, no a cada archivo. Power Query crea una función de ejemplo a partir del primer archivo y la aplica a todos. Si la regla de limpieza cambia, editas la función una vez. Conviene filtrar antes la extensión — un archivo temporal de Excel (~$) en la carpeta rompe la actualización.
= let
Pasta = Folder.Files("\\servidor\bi\inventario"),
SoXlsx = Table.SelectRows(Pasta, each
Text.EndsWith([Name], ".xlsx")
and not Text.StartsWith([Name], "~$")),
Limpo = Table.AddColumn(SoXlsx, "Dados", each
fxLimpaInventario([Content]))
in
LimpoPara sacar del código lo que cambia entre entornos o ejecuciones: nombre del servidor, base, ruta de la carpeta, cantidad de meses. Un parámetro de servidor permite promover de homologación a producción sin reeditar una sola consulta — y en el servicio se puede cambiar sin volver a publicar el archivo.
// Administrar parámetros > Nuevo
pServidor : Text = "srv-bi.casaeconstrucao.local"
pBanco : Text = "DW_Vendas"
let
Fonte = Sql.Database(pServidor, pBanco)
in
FonteDefines una ventana: el histórico queda archivado en particiones que no se recargan y solo se actualizan los últimos N días. Exige una columna de fecha/hora en el origen y los parámetros RangeStart y RangeEnd — con esos nombres exactos, de tipo Fecha/Hora — usados como filtro. Una actualización de 40 minutos suele bajar a menos de 2.
= Table.SelectRows(Fonte, each
[DataVenda] >= RangeStart and [DataVenda] < RangeEnd)
// Modelado > Actualización incremental
// Archivar datos a partir de 3 años antes de la fecha de actualización
// Actualizar de forma incremental los datos de los últimos 10 días
Cuidado: el filtro tiene que ser >= y < (intervalo semiabierto),
si no, la fila del límite del día cae en dos particiones.Casi siempre porque el servicio no ve el origen o no tiene la credencial. En Desktop usas tu cuenta de Windows y estás dentro de la red; en el servicio quien ejecuta es el propio servicio, que necesita gateway para origen local y una credencial configurada en el modelo semántico. Otras causas frecuentes: ruta de archivo local (C:) y uso de orígenes que no admiten actualización en la nube.
Checklist cuando solo se rompe en el Service:
[ ] ¿el origen es on-premises? -> gateway instalado, en línea y con el origen registrado
[ ] credencial configurada en Configuración del modelo semántico > Credenciales del origen de datos
[ ] ruta de red (\\servidor\pasta) en vez de C:\Users\voce\...
[ ] niveles de privacidad compatibles entre los orígenes (Organizativo vs Privado)8 al día en Power BI Pro y hasta 48 en capacidad Premium/PPU/Fabric. Si el negocio pide un intervalo menor, las salidas son DirectQuery, actualizar vía API/pipeline (que también respeta el límite del plan) o repensar la necesidad real — en la práctica, casi toda dirección decide bien con datos de D-1.
Pro 8 actualizaciones/día (~3 h de intervalo mínimo en la práctica)
Premium por usuario 48 actualizaciones/día (cada 30 min)
Capacidad/Fabric 48 + actualización vía API/pipeline
Pregunta siempre: ¿quién decide con este número y con qué frecuencia?Guardando como .pbip (Power BI Project): el modelo y el informe se vuelven archivos de texto (TMDL/JSON), así el diff muestra qué medida cambió. Con .pbix, cada commit es un binario nuevo — Git lo guarda, pero no hay nada que revisar.
Archivo > Guardar como > Proyecto de Power BI (.pbip)
meu-painel.Dataset/model.bim <- medidas, tablas, relaciones
meu-painel.Report/report.json <- páginas, visuales, posiciones
git diff muestra:
- Margem % = DIVIDE([Margem R$], [Faturamento Bruto])
+ Margem % = DIVIDE([Margem R$], [Faturamento Líquido])Sí, casi siempre. Activada, crea una tabla de fechas oculta para cada columna de fecha del modelo: memoria desperdiciada, jerarquías duplicadas e inteligencia de tiempo impredecible. Desactívala y crea una única dCalendario, marcada como tabla de fechas.
Archivo > Opciones > Archivo actual > Carga de datos
[ ] Fecha/hora automática <- desmarcar
Un modelo con 8 columnas de fecha:
activada -> 8 tablas ocultas, una por columna
correcto -> 1 dCalendario, relacionada y marcada como tabla de fechasEn orden de impacto: quitar columnas que no se usan, recortar el período, separar datetime en fecha + hora, cambiar columnas calculadas por medidas y evitar importar tablas de apoyo que solo sirvieron para una verificación. Usa DAX Studio > VertiPaq Analyzer para ver, en números, qué columna cuesta memoria.
DAX Studio > Advanced > View Metrics
Un caso real (modelo de 480 MB):
fVendas[CupomID] 182 MB <- alta cardinalidad, nadie la usaba
fVendas[DataHoraVenda] 96 MB <- pasó a Fecha + Hora
fVendas[ObsVendedor] 54 MB <- texto libre, sin uso en el tablero
Tras quitar las tres: 120 MB.La medida tiene el icono de calculadora y solo existe cuando se usa en un visual; la columna tiene el icono de columna y ocupa memoria en cada fila de la tabla. Regla práctica: si el campo va al eje, la leyenda o el filtro, tiene que ser columna; si va al valor, debería ser medida.
Eje / Leyenda / Segmentación -> columna (dProduto[Categoria])
Valores -> medida ([Faturamento])
Un error común: arrastrar fVendas[ValorLiquido] directo a Valores.
Funciona (suma implícita), pero pierdes el control del formato,
del nombre y de la reutilización — y no puedes referenciarla en otra medida.Un recurso nativo que permite al usuario cambiar la medida o la dimensión de un visual mediante una segmentación. Sustituye a aquel conjunto de tres gráficos casi idénticos y adelgaza la página. Se crea en Modelado > Nuevo parámetro > Campos.
// Modelado > Nuevo parámetro > Campos
Métricas = {
("Faturamento", NAMEOF('Medidas'[Faturamento]), 0),
("Margem R$", NAMEOF('Medidas'[Margem R$]), 1),
("Unidades", NAMEOF('Medidas'[Unidades]), 2)
}
Arrastra "Métricas" a una segmentación y al eje de valores.Por la pestaña Formato > Editar interacciones: para cada visual de origen, defines si el destino filtra, resalta o lo ignora. Es lo que evita ese comportamiento irritante de hacer clic en una barra y ver cambiar los KPI de arriba, que deberían mostrar el contexto general.
Selecciona el gráfico de tiendas > Formato > Editar interacciones
KPI Faturamento -> Ninguna (mantiene la visión total)
Gráfico de líneas -> Resaltar (enfatiza sin ocultar el resto)
Tabla de detalle -> Filtrar (para eso es el clic)Publica en un área de trabajo (no compartas el archivo), organiza el contenido y distribúyelo como aplicación. La aplicación es lo que el usuario debe abrir: tiene navegación con nombre, audiencia controlada y no expone borradores. El área de trabajo es el taller; la aplicación es el escaparate.
Desarrollo (área de trabajo)
-> Publicar
Área de trabajo "Vendas [PROD]"
-> Crear aplicación
Aplicación "Vendas" -> audiencia: el grupo de AD "Gestores Loja"
Nunca: enviar el .pbix por correo o WhatsApp
(sin actualización, sin RLS, sin control de versión).Un informe tiene páginas, visuales e interacción — es lo que construyes en Desktop. Un dashboard es la pantalla de mosaicos anclados, de uno o varios informes, con un único nivel de detalle. Una aplicación es el paquete publicado que agrupa informes y dashboards para una audiencia. En el día a día, la mayor parte del trabajo está en el informe.
Informe páginas, filtros, drill, interacción (Desktop -> Service)
Dashboard mosaicos anclados, 1 pantalla, sin filtros (solo Service)
Aplicación empaqueta y distribuye a una audiencia (solo Service)La capa donde nace la mayoría de los problemas que después aparecen disfrazados de error de DAX.
Es el modelo con una tabla de hechos en el centro (números y claves) rodeada de dimensiones (descripciones y jerarquías), unidas por relaciones uno a muchos. Importa porque el motor de Power BI fue optimizado exactamente para esa forma: los filtros se propagan por un único camino, la compresión es mejor y el DAX se mantiene simple. Casi toda medida excesivamente complicada es síntoma de un modelo equivocado.
dCalendario
|
dLoja -- fVendas -- dProduto
|
dCliente
fVendas : DataVenda, LojaSK, ProdutoSK, ClienteSK, Qtd, Valor, Custo
dimensiones: el texto por el que filtras y agrupasUn hecho responde cuánto y crece con el tiempo (una fila por evento: venta, ticket, movimiento). Una dimensión responde quién, qué, dónde, cuándo y crece despacio (una fila por entidad). Una prueba rápida: si sumar la columna no significa nada — un código postal, un código de producto — es atributo de dimensión, aunque sea numérico.
Hecho: 1 fila por ítem del cupón, 4,2 mi filas/año
Dimensión: 1 fila por producto, 38 mil filas, cambia poco
Métricas siempre en el hecho. Atributos siempre en la dimensión.Funciona para un prototipo con pocas filas, pero después se cobra caro: la descripción del producto se repite millones de veces (modelo grande), los filtros se vuelven lentos, no hay forma de listar el producto que no vendió, y cualquier corrección del catálogo exige recargar todo el hecho.
Plana (4,2 mi filas):
... | Categoria | Marca | DescricaoProduto | Cidade | Regional | ...
repetido repetido repetido repetido
Estrella:
fVendas guarda ProdutoSK (un número)
dProduto guarda la descripción UNA vez, en 38 mil filasEs el significado de una fila del hecho — en nuestro caso, el ítem del cupón. Siempre puedes agregar hacia arriba (día, mes, región), pero nunca bajar del grano que cargaste. Cargar ya agregado por día y tienda resuelve el rendimiento hoy y mata para siempre la pregunta qué producto provocó la caída.
Grano = ítem del cupón -> responde: producto, cupón, vendedor, hora
Grano = día + tienda -> responde: solo día y tienda
Antes de agregar en el origen, pregunta:
"¿alguien va a necesitar abrir este número?"
Si la respuesta es tal vez, mantén el grano fino.1:* es el estándar: un producto, muchas ventas. 1:1 es rara y casi siempre indica dos tablas que deberían ser una. : resuelve casos legítimos (presupuesto por categoría vs ventas por producto), pero crea ambigüedad y filas en blanco — prefiere resolverlo con una dimensión puente antes de recurrir a ella.
1:* dProduto[ProdutoSK] -> fVendas[ProdutoSK] el estándar sano
1:1 rara; junta las tablas
*:* usa un puente:
fMetas[CategoriaSK] -> dCategoria[CategoriaSK] <- dProduto[CategoriaSK]
|
fVendasEs el filtro que se propaga en los dos sentidos: de la dimensión al hecho y de vuelta. Resuelve casos específicos, pero crea caminos ambiguos en modelos con varias dimensiones, deja el motor más lento y produce totales que nadie sabe explicar. Cuando necesites el efecto, prefiere activarlo dentro de la medida, con CROSSFILTER.
// En lugar de dejar la relación bidireccional en el modelo:
Clientes que Compraram Tinta =
CALCULATE(
DISTINCTCOUNT( dCliente[ClienteSK] ),
dProduto[Categoria] = "Tintas",
CROSSFILTER( fVendas[ClienteSK], dCliente[ClienteSK], BOTH )
)
Un efecto local, controlado y documentado en la medida.Porque la columna de fecha del hecho solo tiene los días en que hubo venta, y las funciones de inteligencia de tiempo necesitan una secuencia continua de fechas cubriendo años completos. Sin eso, las comparaciones con el año anterior fallan en silencio — y una dimensión de fecha además te da año, trimestre, semana y feriado sin nada que recalcular.
dCalendario =
VAR MinData = MIN( fVendas[DataVenda] )
VAR MaxData = MAX( fVendas[DataVenda] )
RETURN
ADDCOLUMNS(
CALENDAR( DATE( YEAR(MinData), 1, 1 ), DATE( YEAR(MaxData), 12, 31 ) ),
"Ano", YEAR([Date]),
"MesNum", MONTH([Date]),
"MesNome", FORMAT([Date], "mmm"),
"AnoMes", FORMAT([Date], "yyyy-mm"),
"Trimestre", "T" & QUARTER([Date])
)Power BI pasa a tratar esa columna como el eje de tiempo oficial: las funciones de inteligencia de tiempo quitan automáticamente los demás filtros de la tabla de fechas al desplazar el período, y las jerarquías automáticas dejan de interferir. Sin marcarla, TOTALYTD y SAMEPERIODLASTYEAR pueden devolver un resultado incorrecto — sin ningún aviso.
Selecciona dCalendario > Herramientas de tablas > Marcar como tabla de fechas
Columna de fecha: Date
Requisitos: valores únicos, sin nulos, continua y cubriendo años completos.Una relación activa (la que más usas) y otra inactiva, activada bajo demanda con USERELATIONSHIP. La alternativa — dos tablas calendario (dimensiones de rol) — es útil cuando el usuario necesita filtrar por las dos a la vez, en segmentaciones separadas.
// Relación activa: fVendas[DataVenda] -> dCalendario[Date]
// Relación inactiva: fVendas[DataEntrega] -> dCalendario[Date]
Entregas =
CALCULATE(
[Faturamento],
USERELATIONSHIP( fVendas[DataEntrega], dCalendario[Date] )
)Una tabla sin ninguna relación, usada como fuente de opciones para el usuario: escenario de simulación, elección de métrica, franjas de valores. La medida lee lo seleccionado con SELECTEDVALUE y reacciona. Es la base de todo what-if en Power BI.
ReajustePreco = GENERATESERIES( 0, 0.20, 0.01 ) // 0% a 20%
Faturamento Simulado =
VAR Reajuste = SELECTEDVALUE( ReajustePreco[Valor], 0 )
RETURN
[Faturamento] * ( 1 + Reajuste )Power BI solo acepta relación por una columna. Cuando la clave real es compuesta (tienda + mes, en el caso de las metas), crea una columna concatenada en los dos lados — preferentemente en SQL o en Power Query, no como columna calculada, para no pagar memoria sin necesidad.
-- En SQL, en los dos lados:
SELECT CONCAT(LojaSK, '|', FORMAT(DataMeta, 'yyyy-MM')) AS ChaveLojaMes, ...
// En el modelo:
fMetas[ChaveLojaMes] *---1 dLojaMes[ChaveLojaMes]
Alternativa: crear la dimensión que falta (dLojaMes) y relacionar los dos
hechos con ella — más limpio y evita la concatenación.Es la dimensión cuyo atributo cambia con el tiempo: la tienda de Recife cambió de regional en 2025. Tipo 1 sobrescribe (el histórico desaparece, todo pasa a la regional nueva). Tipo 2 crea una fila nueva con período de validez, y el hecho apunta a la versión vigente en la fecha de la venta — eso es lo que preserva la verdad histórica. La elección es de negocio, no técnica.
Tipo 2 en dLoja:
LojaSK | LojaID | Nome | Regional | DataIni | DataFim | Atual
17 | 042 | Recife | Norte | 2020-01-01 | 2024-12-31 | N
118 | 042 | Recife | Nordeste | 2025-01-01 | 9999-12-31 | S
La pregunta que decide: "¿la venta de 2023 debe aparecer en la regional
antigua o en la actual?" La respuesta define tipo 1 o tipo 2.Aplánalas. Normalizar dProduto en producto → subcategoría → categoría (copo de nieve) añade saltos de relación, hace el filtrado más lento y complica el DAX. Las dimensiones son pequeñas: repetir el nombre de la categoría en 38 mil filas no cuesta nada frente a la ganancia en simplicidad.
Copo de nieve (evitar):
fVendas -> dProduto -> dSubcategoria -> dCategoria
Estrella (preferir):
fVendas -> dProduto [SKU, Descripción, Subcategoría, Categoría, Marca]
Excepción: dimensión gigantesca (decenas de millones de filas),
donde normalizar empieza a compensar.Son dos hechos de granos distintos; nunca los relaciones entre sí. Une los dos a las mismas dimensiones, en el nivel en que cada uno existe: la meta se relaciona con el mes y con la tienda, la venta con el día y con la tienda. Las medidas se encuentran en el visual, no en el modelo.
dCalendario --1---* fVendas (por día)
dCalendario --1---* fMetas (por el primer día del mes)
dLoja --1---* ambas
Atingimento % = DIVIDE( [Faturamento], [Meta] )
Cuidado al mostrarlo por día: la meta es mensual.
O prorrateas la meta por día hábil, o solo muestras la comparación a nivel
mensual (usa HASONEVALUE / ISINSCOPE para decidir).Oculta todas las claves y columnas técnicas, oculta las columnas numéricas crudas que ya se volvieron medidas, renombra todo en lenguaje de negocio, organiza las medidas en carpetas de visualización y define el formato estándar de cada medida (moneda, porcentaje, decimales). Un modelo con 12 elementos visibles es usable; con 140, el usuario vuelve a Excel.
Checklist de entrega del modelo
[ ] claves (SK) ocultas
[ ] columnas crudas del hecho ocultas (ValorLiquido, Custo)
[ ] nombres en lenguaje de negocio ("Faturamento", no "vlr_liq")
[ ] medidas en carpetas: Ventas / Rentabilidad / Comparaciones / Metas
[ ] formato definido por medida (R$, %, 0 o 1 decimal)
[ ] descripción rellenada en las medidas principales (aparece en el tooltip)Desde el primer SUM hasta el contexto de evaluación — el bloque que más separa a quien memorizó sintaxis de quien entiende el lenguaje.
DAX (Data Analysis Expressions) opera sobre tablas y columnas enteras dentro de un contexto de filtro, no sobre celdas. En Excel, A1+B1 suma siempre las mismas dos celdas; en DAX, [Faturamento] devuelve un valor distinto en cada celda del visual, según los filtros de esa celda. Eso es lo que hace que la misma medida sirva para el gráfico anual y para el detalle por tienda.
Excel: = SUM(B2:B5000) resultado fijo
DAX: Faturamento = SUM( fVendas[ValorLiquido] )
en el total -> 18.437.219
en "Recife" -> 412.980
la misma fórmula, contexto distintoUna columna calculada se evalúa en la actualización, se guarda fila a fila y ocupa memoria — úsala cuando necesites el valor para filtrar, agrupar o poner en un eje. Una medida se evalúa al vuelo, según el filtro, y no ocupa memoria — úsala para todo lo que sea indicador. En la duda, empieza por medida.
// Columna: sirve de eje/filtro
dProduto[FaixaPreco] =
SWITCH( TRUE(),
dProduto[PrecoLista] >= 500, "Alto",
dProduto[PrecoLista] >= 150, "Medio",
"Baixo"
)
// Medida: indicador que reacciona al filtro
Margem % = DIVIDE( [Margem R$], [Faturamento] )Es el conjunto de filtros activos en el momento en que se evalúa la medida: lo que viene de la fila y la columna del visual, de las segmentaciones, de los filtros de página e informe, de las relaciones y de cualquier CALCULATE en el camino. Cada celda de una matriz tiene el suyo, y la medida se ejecuta una vez por celda.
Matriz: filas = dLoja[Nome], columnas = dCalendario[MesNome]
Celda "Recife" x "Mar":
dLoja[Nome] = "Recife"
dCalendario[MesNome] = "Mar"
+ la segmentación de Canal, si la hay
+ el filtro de página (p. ej. Ano = 2026)
[Faturamento] se ejecuta dentro de ese conjunto.Es la existencia de una fila actual. Aparece en dos lugares: dentro de una columna calculada (que recorre la tabla) y dentro de un iterador (SUMX, AVERAGEX, FILTER). Sin contexto de fila no se puede referenciar una columna sin agregarla — de ahí el error clásico no se puede determinar un valor único para la columna.
// Tiene contexto de fila (iterador): OK
SUMX( fVendas, fVendas[Qtd] * fVendas[PrecoUnitario] )
// No lo tiene: error
Medida Errada = fVendas[Qtd] * 2
// Correcto sin iterador:
Medida Certa = SUM( fVendas[Qtd] ) * 2Es lo que ocurre cuando CALCULATE (o una medida, que ya trae un CALCULATE implícito) se llama dentro de un contexto de fila: la fila actual se convierte en filtro. Por eso SUMX(dLoja, [Faturamento]) funciona — para cada tienda, el contexto de filtro pasa a ser esa tienda. Es el concepto más poderoso y más confuso del lenguaje.
// Para cada fila de dLoja, [Faturamento] se filtra por esa tienda
Lojas Acima da Meta =
COUNTROWS(
FILTER( dLoja, [Faturamento] > [Meta] )
)
// Sin transición de contexto, [Faturamento] devolvería
// el total general en cada fila.Siempre que el cálculo tenga que ocurrir antes de la suma, fila a fila. Precio por cantidad es el ejemplo canónico: sumar precios y multiplicar por la suma de las cantidades da un número sin sentido. SUMX recorre, calcula y solo después suma.
// Correcto
Receita Bruta = SUMX( fVendas, fVendas[Qtd] * fVendas[PrecoUnitario] )
// Incorrecto (y el total hasta parece plausible)
Receita Errada = SUM( fVendas[Qtd] ) * SUM( fVendas[PrecoUnitario] )
Si la columna con el resultado de la multiplicación ya existe,
SUM de esa columna es más rápido que SUMX.Evalúa una expresión modificando el contexto de filtro: añade filtros, sustituye los existentes en esa columna y mantiene los demás. Además, dispara la transición de contexto cuando se usa dentro de un contexto de fila. Prácticamente toda medida no trivial pasa por él.
Faturamento E-commerce =
CALCULATE( [Faturamento], dLoja[Canal] = "E-commerce" )
// El filtro SUSTITUYE al de Canal y MANTIENE los de fecha, tienda, producto.
// Si el usuario ya filtró Canal = "Loja física" en la segmentación,
// esta medida sigue mostrando e-commerce — es el comportamiento esperado.El filtro simple (columna = valor) es azúcar sintáctico para un FILTER sobre los valores de esa columna: rápido y suficiente en la mayoría de los casos. Un FILTER explícito es necesario cuando la condición involucra una medida o compara columnas distintas. Atención a la tabla que eliges: FILTER(fVendas, ...) recorre millones de filas.
// Simple (preferible cuando se puede)
CALCULATE( [Faturamento], dProduto[Categoria] = "Tintas" )
// FILTER: obligatorio, porque compara con una medida
CALCULATE( [Faturamento], FILTER( dLoja, [Margem %] < 0.20 ) )
// Malo: itera todo el hecho
CALCULATE( [Faturamento], FILTER( fVendas, RELATED(dProduto[Categoria]) = "Tintas" ) )ALL/REMOVEFILTERS quitan filtros de la tabla o columna indicada (el total general). ALLEXCEPT quita todo excepto las columnas listadas. ALLSELECTED respeta lo que el usuario eligió en las segmentaciones e ignora solo el filtro propio del visual — es el correcto para participación dentro de la selección.
% do Total Geral = DIVIDE([Faturamento], CALCULATE([Faturamento], ALL(dProduto)))
% dentro da Marca = DIVIDE([Faturamento], CALCULATE([Faturamento], ALLEXCEPT(dProduto, dProduto[Marca])))
% do Selecionado = DIVIDE([Faturamento], CALCULATE([Faturamento], ALLSELECTED(dProduto)))
El usuario seleccionó 3 de 12 categorías:
ALL -> las 3 suman 41%
ALLSELECTED -> las 3 suman 100%Divide la medida por sí misma con el filtro de la dimensión eliminado. La pregunta que define qué función usar es: ¿el denominador debe considerar la selección del usuario? Si sí, ALLSELECTED; si es siempre el total de la empresa, ALL/REMOVEFILTERS.
% Participação Categoria =
VAR Atual = [Faturamento]
VAR TotalSelecionado =
CALCULATE( [Faturamento], ALLSELECTED( dProduto[Categoria] ) )
RETURN
DIVIDE( Atual, TotalSelecionado )SAMEPERIODLASTYEAR desplaza todo el período del contexto un año atrás; DATEADD permite desplazamientos libres. Muestra siempre la variación junto al valor — el número absoluto del año pasado, por sí solo, rara vez ayuda a decidir.
Faturamento AA =
CALCULATE( [Faturamento], SAMEPERIODLASTYEAR( dCalendario[Date] ) )
Var % AA =
VAR Anterior = [Faturamento AA]
RETURN
IF( NOT ISBLANK( Anterior ), DIVIDE( [Faturamento] - Anterior, Anterior ) )Con las funciones TOTALYTD, TOTALQTD y TOTALMTD, que acumulan desde el inicio del período hasta la última fecha del contexto. Si el año fiscal no empieza en enero, indica la fecha de cierre en el tercer argumento.
Faturamento YTD = TOTALYTD( [Faturamento], dCalendario[Date] )
Faturamento QTD = TOTALQTD( [Faturamento], dCalendario[Date] )
Faturamento MTD = TOTALMTD( [Faturamento], dCalendario[Date] )
// Año fiscal que cierra el 30 de junio
Fat YTD Fiscal = TOTALYTD( [Faturamento], dCalendario[Date], "06-30" )Tres causas, en este orden: la tabla de fechas no fue marcada como tabla de fechas; tiene huecos o no cubre años completos; o estás usando la columna de fecha del hecho en vez de la de la dimensión calendario. Una cuarta, más sutil: un filtro de mes aplicado sobre el hecho impide que el acumulado vea los meses anteriores.
Diagnóstico rápido
[ ] ¿dCalendario está marcada como tabla de fechas?
[ ] ¿CALENDAR cubre del 01/01 del primer año al 31/12 del último?
[ ] ¿la medida usa dCalendario[Date] y no fVendas[DataVenda]?
[ ] ¿el filtro de período está en dCalendario y no en fVendas?Con AVERAGEX sobre DATESINPERIOD, anclado en la última fecha del contexto. Sirve para quitar el ruido de una serie mensual y mostrar la tendencia — en retail, sin ella el pico de diciembre domina la lectura de todo el gráfico.
Média Móvel 3M =
AVERAGEX(
DATESINPERIOD( dCalendario[Date], MAX( dCalendario[Date] ), -3, MONTH ),
[Faturamento]
)Con CALCULATE más un filtro de fechas menores o iguales a la actual, liberando el filtro de la tabla de fechas con ALL. Cuidado: sin el ALL, el filtro del visual limita el rango y el acumulado se reinicia en cada fila.
Acumulado =
CALCULATE(
[Faturamento],
FILTER(
ALL( dCalendario[Date] ),
dCalendario[Date] <= MAX( dCalendario[Date] )
)
)Porque la tabla pasada como referencia ya viene filtrada por el contexto de la fila, así que cada tienda se clasifica contra una tabla de una sola tienda. La solución es quitar ese filtro con ALL (o ALLSELECTED, si quieres clasificar solo dentro de la selección del usuario).
// Incorrecto: 1 para todos
Posição = RANKX( dLoja, [Faturamento] )
// Correcto
Posição Loja = RANKX( ALL( dLoja[Nome] ), [Faturamento], , DESC, DENSE )
// Ranking que respeta la selección del usuario
Posição na Seleção = RANKX( ALLSELECTED( dLoja[Nome] ), [Faturamento], , DESC, DENSE )Calcula el total del Top N con TOPN dentro de CALCULATE y obtén Otros por diferencia. Así el gráfico se mantiene legible (10 barras, no 42) sin ocultar la facturación restante.
Fat Top 10 =
CALCULATE( [Faturamento], TOPN( 10, ALL( dLoja[Nome] ), [Faturamento], DESC ) )
Fat Outros =
CALCULATE( [Faturamento], ALL( dLoja ) ) - [Fat Top 10]Una tabla desconectada con las opciones + SWITCH leyendo SELECTEDVALUE. Define siempre un valor predeterminado en el SELECTEDVALUE, si no la pantalla nace vacía cuando no hay nada seleccionado — uno de los errores de UX más comunes en tableros con selector.
Métrica Escolhida =
SWITCH(
SELECTEDVALUE( Métricas[Nome], "Faturamento" ), // ¡el predeterminado!
"Faturamento", [Faturamento],
"Margem R$", [Margem R$],
"Unidades", [Unidades],
BLANK()
)Crea una medida de texto y vincúlala al título del visual con el botón fx (Formato > Título > Dar formato por campo). Es lo que hace que una captura enviada por WhatsApp siga teniendo sentido fuera del contexto del informe.
Título Página =
VAR Loja = SELECTEDVALUE( dLoja[Nome], "todas as lojas" )
VAR Periodo = SELECTEDVALUE( dCalendario[AnoMes], "o período selecionado" )
RETURN
"Faturamento de " & Loja & " em " & PeriodoUsa DIVIDE, que trata el denominador cero o vacío y además permite elegir el resultado alternativo. Piensa en el resultado que tiene sentido para el negocio: para margen %, vacío es más honesto que cero; para cumplimiento de meta, cero suele ser lo correcto.
Margem % = DIVIDE( [Margem R$], [Faturamento] ) // vacío si no hubo venta
Atingimento % = DIVIDE( [Faturamento], [Meta], 0 ) // cero tiene sentido
// Evita:
Margem Errada = [Margem R$] / [Faturamento] // Infinito / error en el visualPorque la medida se recalcula en el contexto del total, no se suma. Eso es correcto y esperado en razones (margen %, ticket medio) y en conteos distintos. Se vuelve problema cuando la medida tiene lógica condicional que se comporta distinto en el total — entonces controla qué mostrar con HASONEVALUE o ISINSCOPE.
Tienda A: ticket 210 | Tienda B: ticket 190 | Total: 204
No es 400, y está bien: el total es facturación total / cupones totales.
// Cuando el total realmente no tiene sentido:
Meta por Loja = IF( HASONEVALUE( dLoja[Nome] ), [Meta], BLANK() )
// Cuando el total sí debe ser la suma de los ítems:
Total Correto = SUMX( VALUES( dLoja[Nome] ), [Medida com lógica] )Porque un conteo distinto no es aditivo: quien compró en enero y en marzo es un cliente en el total, y dos si sumas los meses. No es un fallo. Si necesitas un número aditivo, cambia la definición (clientes nuevos en el mes, por ejemplo) y explícalo en el glosario del informe.
Clientes Ativos = DISTINCTCOUNT( fVendas[ClienteSK] )
Ene 1.200 | Feb 1.350 | Mar 1.410 | Trimestre 2.480
Una alternativa aditiva:
Clientes Novos =
CALCULATE(
DISTINCTCOUNT( fVendas[ClienteSK] ),
FILTER( dCliente, dCliente[DataPrimeiraCompra] IN VALUES( dCalendario[Date] ) )
)Para evaluar una expresión una vez y reutilizarla, lo que mejora el rendimiento y la legibilidad. Un detalle que cae en exámenes: la variable guarda el valor del contexto donde fue declarada — un CALCULATE posterior no la cambia.
Var % AA =
VAR Atual = [Faturamento]
VAR Anterior = CALCULATE( [Faturamento], SAMEPERIODLASTYEAR( dCalendario[Date] ) )
RETURN
DIVIDE( Atual - Anterior, Anterior )
// Anterior se calculó ANTES; ningún CALCULATE posterior cambia su valor.IF para una condición; SWITCH(TRUE(), ...) para tres o más, porque se lee muchísimo mejor que un IF anidado. En ambos, garantiza que todas las ramas devuelvan el mismo tipo — mezclar texto y número en una medida produce comportamientos raros en el visual.
Classificação =
SWITCH( TRUE(),
[Atingimento %] >= 1, "Meta batida",
[Atingimento %] >= 0.9, "Perto",
[Atingimento %] >= 0.7, "Atenção",
"Crítico"
)RELATED trae un valor del lado uno de la relación (de la venta al producto): úsalo en el contexto de fila del hecho. RELATEDTABLE trae la tabla del lado muchos (del producto a las ventas): úsalo en el contexto de fila de la dimensión, normalmente con un agregador.
// En el hecho, buscando el atributo de la dimensión
fVendas[Categoria] = RELATED( dProduto[Categoria] )
// En la dimensión, contando las filas del hecho
dProduto[QtdVendas] = COUNTROWS( RELATEDTABLE( fVendas ) )No se puede usar un filtro simple: hace falta FILTER sobre la dimensión, porque la condición depende de una medida evaluada por fila (transición de contexto). Filtra la dimensión, nunca el hecho — la diferencia de rendimiento es de órdenes de magnitud.
Fat Lojas Margem Baixa =
CALCULATE(
[Faturamento],
FILTER( ALL( dLoja[Nome] ), [Margem %] < 0.20 )
)
Qtd Lojas Margem Baixa =
COUNTROWS( FILTER( ALL( dLoja[Nome] ), [Margem %] < 0.20 ) )Crea un rol en Modelado > Administrar roles con un filtro DAX sobre la dimensión, y asigna usuarios o grupos en el servicio. Usa USERPRINCIPALNAME() para atarlo al usuario conectado. Prueba siempre con Ver como, en Desktop, antes de publicar.
// Rol "Gerente Loja", filtro en la tabla dLoja:
[EmailGerente] = USERPRINCIPALNAME()
// Jerarquía (un gerente regional ve todas las tiendas de su regional):
VAR Usuario = USERPRINCIPALNAME()
RETURN
dLoja[Regional] IN
SELECTCOLUMNS(
FILTER( dAcesso, dAcesso[Email] = Usuario ),
"R", dAcesso[Regional]
)
// Prueba: Modelado > Ver como > Gerente Loja + otro usuarioAíslala por partes: pon las medidas intermedias en una tabla junto a la dimensión sospechosa y mira en qué nivel el número se estropea. Herramientas: EVALUATE en DAX Studio, el Analizador de rendimiento (que muestra la consulta DAX generada por el visual) y mostrar las variables una a una.
// En DAX Studio
EVALUATE
SUMMARIZECOLUMNS(
dLoja[Nome],
"Faturamento", [Faturamento],
"Faturamento AA", [Faturamento AA],
"Var %", [Var % AA]
)
ORDER BY [Var %] ASC
// Donde el valor desaparece está la causa: filtro, relación o grano.Selecciona la medida y usa las Herramientas de medidas (formato, decimales, separador de miles). Defínelo en el modelo, no visual por visual — así nace formateada en cualquier informe. Para casos especiales existe la cadena de formato dinámica.
Herramientas de medidas
Faturamento -> Moneda, 0 decimales R$ 18.437.219
Margem % -> Porcentaje, 1 decimal 23,7%
Ticket Médio -> Moneda, 2 decimales R$ 214,38
// Cadena de formato dinámica (la misma medida, monedas distintas):
SWITCH( SELECTEDVALUE( dMoeda[Codigo] ), "BRL", "R$ #,##0", "USD", "$ #,##0" )En medidas, prefiere SUMMARIZE solo para agrupar y ADDCOLUMNS para añadir cálculos — agregar dentro de SUMMARIZE tiene trampas de contexto conocidas. SUMMARIZECOLUMNS es la función para consultas (DAX Studio, tablas virtuales), no para medidas del día a día.
// El patrón recomendado
VAR PorLoja =
ADDCOLUMNS(
SUMMARIZE( fVendas, dLoja[Nome] ),
"@Fat", [Faturamento]
)
RETURN
COUNTROWS( FILTER( PorLoja, [@Fat] > 500000 ) )
// El prefijo @ en las columnas creadas: una convención que evita ambigüedad.Marca el día hábil como columna en dCalendario (contando fines de semana y la tabla de feriados) y cuenta con CALCULATE + COUNTROWS. Hacerlo en la dimensión, una vez, es mucho mejor que recalcularlo en cada medida.
// Una columna en dCalendario
dCalendario[EhDiaUtil] =
IF(
WEEKDAY( dCalendario[Date], 2 ) <= 5
&& NOT( dCalendario[Date] IN VALUES( dFeriados[Data] ) ),
1, 0
)
// La medida
Dias Úteis = CALCULATE( COUNTROWS( dCalendario ), dCalendario[EhDiaUtil] = 1 )
Meta Diária = DIVIDE( [Meta], [Dias Úteis] )Cuatro reglas que resuelven la mayoría de los casos: filtra dimensiones, nunca el hecho; evita FILTER sobre tablas grandes; usa variables para no repetir cálculos; y prefiere columnas de baja cardinalidad en los filtros. Mide con el Analizador de rendimiento — optimizar por intuición suele estropear la legibilidad sin ganancia real.
// Lento: itera 4,2 millones de filas
CALCULATE([Faturamento], FILTER(fVendas, RELATED(dProduto[Categoria])="Tintas"))
// Rápido: filtra 38 mil filas (o usa el filtro simple)
CALCULATE([Faturamento], dProduto[Categoria] = "Tintas")
Objetivo: consulta DAX por debajo de 1 s por visual.Cinco campeones: usar SUM donde hace falta SUMX; olvidar el ALL en RANKX; creer que el total de una razón debe sumar; usar / en vez de DIVIDE; y no saber explicar la diferencia entre ALL y ALLSELECTED. Todos son de contexto, no de sintaxis — por eso memorizar funciones no aprueba la entrevista.
Si sabes responder esto, ya tienes la mayor parte:
1. "Explica qué hace CALCULATE."
2. "¿Por qué el total del margen % no es la suma de las filas?"
3. "¿Cuál es la diferencia entre ALL y ALLSELECTED?"
4. "¿Cuándo una columna calculada es mejor que una medida?"
5. "¿Qué es la transición de contexto?"La capa invisible que decide si el tablero se actualiza en 2 minutos o en 2 horas — y si el número está bien.
Para montar un informe simple, no. Para trabajar en serio, sí: es el SQL el que corta el volumen en el origen, el que permite auditar el número del tablero contra el origen y el que resuelve en segundos lo que tardaría minutos en Power Query. En la práctica, la oferta que pide Power BI y SQL juntos está diciendo que vas a tocar el dato antes de que se vuelva visual.
El mínimo que se pide en una entrevista de BI:
SELECT / WHERE / ORDER BY / GROUP BY / HAVING
los cuatro JOIN y por qué el LEFT duplica
funciones de ventana (RANK, LAG, SUM OVER)
CTEs
leer un plan de ejecución a nivel básicoEn SQL: filtrar, unir tablas, agregar en volumen y todo lo que el servidor hace mejor. En Power Query: dar forma al formato (unpivot, tipos, encabezados), tratar archivos y lo que no existe en SQL. La regla es empujar el trabajo lo más cerca posible del origen — también porque el SQL se versiona y se prueba, y los pasos de Power Query no.
Origen SQL -> filtrado, JOIN, GROUP BY, ventanas, incremental
Power Query -> tipos + locale, unpivot, carpeta de archivos, merge ligero
Modelo (DAX) -> todo lo que cambia con el filtro del usuarioPorque la vista es un contrato: nombres de negocio, las columnas correctas y las reglas ya aplicadas. Cuando la tabla física se renombra o gana una columna, lo arreglas en un solo sitio y los 30 informes siguen funcionando. También es donde viven las exclusiones que nadie debería olvidar — la tienda de pruebas, el cupón cancelado.
CREATE VIEW dbo.vw_fato_vendas AS
SELECT
v.DataVenda, v.LojaSK, v.ProdutoSK,
v.Quantidade AS Qtd,
v.ValorLiquido,
v.CustoUnitario * v.Quantidade AS Custo
FROM dbo.FatoVendasItem v
WHERE v.LojaSK <> 999
AND v.StatusCupom = 'FECHADO';WHERE filtra filas antes de agrupar; HAVING filtra grupos después de agregar. Por eso no se usa una función de agregación en el WHERE. Una regla de rendimiento: lo que se pueda filtrar en el WHERE, se filtra ahí — cuantas menos filas lleguen al GROUP BY, mejor.
SELECT LojaSK, SUM(ValorLiquido) AS Faturamento
FROM dbo.vw_fato_vendas
WHERE DataVenda >= '2026-01-01' -- filtra filas
GROUP BY LojaSK
HAVING SUM(ValorLiquido) > 500000; -- filtra gruposINNER trae solo lo que coincide en ambos lados. LEFT mantiene todo de la tabla de la izquierda, con nulo donde no hay correspondencia — es el más usado en BI, porque el hecho tiene que sobrevivir a un catálogo faltante. RIGHT es lo mismo al revés (raro; prefiere reescribirlo como LEFT). FULL trae los dos lados, útil para conciliación.
-- Venta con producto no catalogado: LEFT preserva la venta
SELECT v.DataVenda, v.ValorLiquido, COALESCE(p.Categoria, 'Sem cadastro') AS Categoria
FROM dbo.vw_fato_vendas v
LEFT JOIN dbo.dProduto p ON p.ProdutoSK = v.ProdutoSK;
-- Auditoría: lo que existe en el hecho y no en la dimensión
SELECT DISTINCT v.ProdutoSK
FROM dbo.vw_fato_vendas v
LEFT JOIN dbo.dProduto p ON p.ProdutoSK = v.ProdutoSK
WHERE p.ProdutoSK IS NULL;Porque el lado derecho tiene más de una fila para la misma clave: cada fila del hecho se multiplicó. Es el error que más infla números en BI. Causas típicas: dimensión con histórico (SCD tipo 2) sin filtro de vigencia, registro duplicado en el catálogo, o join por una clave que no es única.
-- Diagnóstico: ¿la clave es realmente única?
SELECT ProdutoSK, COUNT(*)
FROM dbo.dProduto
GROUP BY ProdutoSK
HAVING COUNT(*) > 1;
-- Una causa común: SCD tipo 2 sin filtro de vigencia
JOIN dbo.dLoja l
ON l.LojaID = v.LojaID
AND v.DataVenda BETWEEN l.DataIni AND l.DataFim; -- esto faltabaEnumera de antemano las preguntas que responde el tablero y agrega al grano más fino entre ellas. Si alguna pregunta menciona el producto, el grano tiene que incluir el producto. En la duda, mantén el grano fino y resuelve el rendimiento en el modelo — desagregar después significa recargar todo.
SELECT
v.DataVenda, v.LojaSK, p.Categoria,
SUM(v.Qtd) AS Qtd,
SUM(v.ValorLiquido) AS ValorLiquido,
SUM(v.Custo) AS Custo
FROM dbo.vw_fato_vendas v
JOIN dbo.dProduto p ON p.ProdutoSK = v.ProdutoSK
GROUP BY v.DataVenda, v.LojaSK, p.Categoria;
-- 4,2 mi -> 380 mil filas. Hazlo solo si nadie pide el SKU.Con funciones de ventana. ROW_NUMBER numera sin empates (bueno para deduplicar), RANK deja hueco tras el empate y DENSE_RANK no. PARTITION BY define el grupo dentro del cual el ranking se reinicia — en el ejemplo, un ranking por mes.
SELECT
AnoMes, LojaSK, Faturamento,
ROW_NUMBER() OVER (PARTITION BY AnoMes ORDER BY Faturamento DESC) AS Linha,
RANK() OVER (PARTITION BY AnoMes ORDER BY Faturamento DESC) AS Posicao,
DENSE_RANK() OVER (PARTITION BY AnoMes ORDER BY Faturamento DESC) AS PosicaoDensa
FROM dbo.vw_vendas_mes_loja;Con SUM() OVER y una ventana de filas. ROWS UNBOUNDED PRECEDING acumula desde el inicio de la partición hasta la fila actual — reiniciando cada año, si el año forma parte del PARTITION BY.
SELECT
LojaSK, AnoMes, Faturamento,
SUM(Faturamento) OVER (
PARTITION BY LojaSK, LEFT(AnoMes, 4)
ORDER BY AnoMes
ROWS UNBOUNDED PRECEDING
) AS AcumuladoAno
FROM dbo.vw_vendas_mes_loja;Con LAG desplazando 12 filas dentro de la partición de la tienda — siempre que la serie esté completa, sin meses faltantes. Si hay huecos, une la tabla consigo misma por una clave año-mes calculada, que es inmune a filas ausentes.
SELECT
LojaSK, AnoMes, Faturamento,
LAG(Faturamento, 12) OVER (PARTITION BY LojaSK ORDER BY AnoMes) AS FatAA,
Faturamento - LAG(Faturamento, 12) OVER (PARTITION BY LojaSK ORDER BY AnoMes) AS Delta
FROM dbo.vw_vendas_mes_loja;
-- ¿Serie con huecos? Prefiere el self join por clave de período.Es una consulta con nombre que existe solo durante la sentencia (WITH ... AS). Sirve para partir una consulta larga en etapas legibles y para recursión (jerarquía de gerentes, explosión de estructura de producto). No es una tabla temporal: no guarda nada ni crea índice.
WITH VendasMes AS (
SELECT LojaSK, FORMAT(DataVenda,'yyyy-MM') AS AnoMes, SUM(ValorLiquido) AS Fat
FROM dbo.vw_fato_vendas
GROUP BY LojaSK, FORMAT(DataVenda,'yyyy-MM')
),
ComMeta AS (
SELECT v.*, m.MetaValor
FROM VendasMes v
LEFT JOIN dbo.fMetas m ON m.LojaSK = v.LojaSK AND m.AnoMes = v.AnoMes
)
SELECT *, Fat / NULLIF(MetaValor, 0) AS Atingimento
FROM ComMeta;Con un CTE recursivo o una tabla de números. Tener el calendario en la base (y no solo en DAX) es mejor: otros informes, otras herramientas y el propio ETL pasan a usar la misma definición de trimestre, semana y feriado.
WITH Datas AS (
SELECT CAST('2020-01-01' AS date) AS Data
UNION ALL
SELECT DATEADD(DAY, 1, Data) FROM Datas WHERE Data < '2030-12-31'
)
SELECT
Data,
YEAR(Data) AS Ano,
MONTH(Data) AS MesNum,
FORMAT(Data, 'yyyy-MM') AS AnoMes,
DATEPART(QUARTER, Data) AS Trimestre,
CASE WHEN DATEPART(WEEKDAY, Data) IN (1,7) THEN 0 ELSE 1 END AS EhDiaUtil
FROM Datas
OPTION (MAXRECURSION 0);Con PIVOT o, de forma más portable, con CASE dentro de agregaciones condicionales. Para BI, eso sí, piénsalo dos veces: el modelo prefiere el dato alto (una fila por canal), porque así una categoría nueva no exige cambiar ni la consulta ni el visual. Pivotar es para un informe de conferencia, no para alimentar el modelo.
-- Agregación condicional (funciona en cualquier base)
SELECT
LojaSK,
SUM(CASE WHEN Canal = 'Loja' THEN ValorLiquido ELSE 0 END) AS FatLoja,
SUM(CASE WHEN Canal = 'E-commerce' THEN ValorLiquido ELSE 0 END) AS FatEcom,
SUM(CASE WHEN Canal = 'Televendas' THEN ValorLiquido ELSE 0 END) AS FatTele
FROM dbo.vw_fato_vendas
GROUP BY LojaSK;
-- Para el modelo, prefiere el formato alto: LojaSK | Canal | ValorLiquidoNULL no es cero ni vacío: es ausencia, y cualquier comparación con él da desconocido. Usa IS NULL, COALESCE para un valor por defecto y NULLIF para evitar la división por cero. En una dimensión, cambia el nulo por una etiqueta explícita — desaparecer de la pantalla es peor que aparecer como Sem categoria.
-- Incorrecto: nunca devuelve nada
WHERE Categoria = NULL
-- Correcto
WHERE Categoria IS NULL
SELECT
COALESCE(NULLIF(LTRIM(RTRIM(Categoria)), ''), 'Sem categoria') AS Categoria,
ValorLiquido / NULLIF(Qtd, 0) AS PrecoMedio
FROM dbo.vw_fato_vendas;Lee el plan de ejecución buscando un recorrido de tabla (table/clustered index scan) donde debería haber un seek, y observa la diferencia entre filas estimadas y reales — una discrepancia grande suele ser estadísticas desactualizadas. Después revisa lo básico: una función sobre la columna filtrada, que impide el uso del índice.
SET STATISTICS IO, TIME ON;
-- ejecuta la consulta y lee las lecturas lógicas
-- Mata el índice (una función sobre la columna):
WHERE YEAR(DataVenda) = 2026
-- Usa el índice (un rango, la columna intacta):
WHERE DataVenda >= '2026-01-01' AND DataVenda < '2027-01-01'Uno sobre la columna de filtro (la fecha), con las columnas del SELECT en INCLUDE, para que el servidor resuelva todo en el índice sin volver a la tabla (índice de cobertura). Mide antes y después; demasiados índices ralentizan la carga del ETL.
CREATE NONCLUSTERED INDEX IX_FatoVendas_Data
ON dbo.FatoVendasItem (DataVenda)
INCLUDE (LojaSK, ProdutoSK, Quantidade, ValorLiquido, CustoUnitario);
-- Antes: 4m12s | Después: 47s (la misma consulta de actualización)Agrupa por la clave de negocio (lo que debería ser único) y cuenta. La duplicación en un hecho suele venir de una carga ejecutada dos veces o de un JOIN mal construido en el ETL — y es la explicación más frecuente para que el tablero muestre más facturación que el sistema de origen.
SELECT CupomID, ItemSeq, COUNT(*) AS Vezes
FROM dbo.FatoVendasItem
GROUP BY CupomID, ItemSeq
HAVING COUNT(*) > 1
ORDER BY Vezes DESC;
-- Verificación rápida del total:
SELECT COUNT(*) AS Linhas, COUNT(DISTINCT CONCAT(CupomID,'-',ItemSeq)) AS Unicos
FROM dbo.FatoVendasItem;Ejecuta la misma agregación en la base y compárala con el visual, en el mismo período y con los mismos filtros. Una diferencia casi siempre tiene una de estas cuatro causas: un filtro extra en la vista, un período distinto, duplicación en la carga, o una relación equivocada en el modelo que deja filas huérfanas fuera.
-- En la base
SELECT FORMAT(DataVenda,'yyyy-MM') AS AnoMes, SUM(ValorLiquido) AS Fat
FROM dbo.vw_fato_vendas
WHERE DataVenda >= '2026-01-01' AND DataVenda < '2026-04-01'
GROUP BY FORMAT(DataVenda,'yyyy-MM')
ORDER BY 1;
-- En el tablero: una matriz de AnoMes x [Faturamento], sin segmentaciones.
-- Guarda ese par como prueba de regresión del informe.Guarda una marca de agua (la última fecha cargada) en una tabla de control y procesa solo el delta, dentro de una transacción. Es el mismo principio de la actualización incremental de Power BI, solo que bajo tu control — y permite reprocesar un período específico cuando el origen corrige un registro retroactivo.
BEGIN TRANSACTION;
DECLARE @UltimaCarga datetime =
(SELECT MAX(DataCarga) FROM dbo.ControleCarga WHERE Tabela = 'FatoVendasItem');
INSERT INTO dbo.FatoVendasItem (...)
SELECT ... FROM origem.Vendas WHERE DataAlteracao > @UltimaCarga;
UPDATE dbo.ControleCarga
SET DataCarga = GETDATE()
WHERE Tabela = 'FatoVendasItem';
COMMIT;Las decisiones visuales que determinan si el tablero se usa solo o se convierte en un correo pidiendo explicaciones.
Es diseñar para la decisión, no para la pantalla: entender quién usa, qué pregunta necesita responder, en cuánto tiempo y qué hace después. En la práctica se manifiesta en tres cosas: jerarquía (qué aparece primero), claridad (si se entiende sin leyenda) y recorrido (cómo se llega al detalle y cómo se vuelve).
Sin UX: "puse todos los indicadores que pidieron"
Con UX: "esta pantalla responde 3 preguntas, en este orden de importancia,
y llega al detalle en 2 clics"
Un síntoma de tablero sin UX: el usuario exporta a Excel
antes de tomar cualquier decisión.Por las preguntas, escritas en una frase cada una, ordenadas por importancia — y por quién las va a usar. Solo después vienen el boceto, los visuales y el color. Empezar arrastrando gráficos es el camino más rápido a una pantalla llena que no responde nada.
Un brief de 5 líneas (rellénalo con el cliente)
1. Quién usa: gerentes de tienda y el director comercial
2. Cuándo: cada lunes por la mañana y en el cierre de mes
3. Preguntas: (a) ¿alcancé la meta? (b) ¿qué cayó? (c) ¿dónde actúo?
4. Decisión: reasignar presupuesto y exigir plan de acción a la tienda
5. Restricciones: datos D-1, acceso solo a la propia tienda (RLS)Entre 4 y 6 en el bloque principal, y como máximo de 8 a 12 visuales en la página. El límite no es estético: cada visual es una consulta, y páginas con 20 visuales tardan mucho en cargar y en leerse. Si no cabe, probablemente sean dos públicos distintos — y deberían ser dos páginas.
Regla práctica
KPI arriba: 4 a 6
Visuales en la página: hasta 12 (más allá, la página se vuelve lenta)
Páginas: una por público/pregunta, no una por tabla
Si hay que hacer scroll en la página, el layout ya falló.Elige por intención, no por gusto. Comparar categorías: barras (horizontales, ordenadas por valor). Evolucionar en el tiempo: línea. Componer un todo: barra apilada. Relacionar dos medidas: dispersión. Distribuir: histograma. Un número contra la meta: tarjeta con la variación.
Pregunta Visual
¿Qué tienda vendió más? barras horizontales ordenadas
¿Cómo evolucionó en el año? línea (con media móvil)
¿Cuánto pesa cada canal? barra apilada al 100%
¿El margen alto justifica volumen? dispersión (margen x volumen)
¿Alcancé la meta? tarjeta + variación + indicador de meta
¿Dónde están las tiendas? mapa (solo si la geografía importa)Con 2 o 3 porciones, sí. Más allá, no: el ojo compara ángulos mucho peor que longitudes, y porciones parecidas se vuelven indistinguibles. Barras ordenadas responden mejor a la misma pregunta. Y un donut con 12 porciones y leyenda al lado es, en la práctica, una tabla mal dibujada.
Aceptable: Pagado vs No pagado (2 porciones)
Malo: 12 categorías de producto en una tarta
Mejor: barras horizontales ordenadas, top 8 + "Otros"
Nunca: tarta 3D. Distorsiona el área de las porciones frontales.El mínimo que resuelva. La mayor parte de la pantalla debe ser neutra (gris), con el color reservado para lo que exige atención. Series categóricas por encima de 6 colores se vuelven ilegibles — agrúpalas en Otros. Y fija el significado: si e-commerce es azul, es azul en todas las páginas del informe.
La regla 60-30-10
60% neutro (fondo, texto, elementos de apoyo)
30% color de apoyo (la serie principal)
10% destaque (lo que exige acción)
Categórica: hasta 6 colores
Secuencial: 1 tono variando en intensidad (volumen)
Divergente: 2 polos + neutro en el medio (variación vs año anterior)Cerca del 8% de los hombres tiene alguna deficiencia en la visión de los colores; rojo vs verde es justamente el par más problemático. Prefiere azul vs naranja para oposición, garantiza contraste 4,5:1 en texto y 3:1 en elementos gráficos, y nunca transmitas información solo por color: añade flecha, signo o etiqueta.
En vez de verde/rojo:
positivo #2B6CB0 (azul) negativo #C05621 (naranja)
+ flecha y signo: v +8,2% ^ -3,1%
Una prueba rápida: quita la saturación de la pantalla (imprímela en blanco y negro).
Si la información desapareció, dependía solo del color.Mostrar la pantalla durante 5 segundos, ocultarla y preguntar qué vio la persona, si el resultado es bueno o malo y qué haría ahora. Si no lo sabe, falta jerarquía — no datos. Bastan cinco personas: cuando tres se equivocan en lo mismo, el problema es el diseño.
Las preguntas, en este orden
1. ¿Qué te está diciendo esta pantalla?
2. ¿El resultado es bueno o malo?
3. ¿Qué harías con esta información?
4. ¿Qué te resultó confuso?
Cuesta 10 minutos por persona y evita semanas de retrabajo.En una franja fija, siempre en el mismo lugar de todas las páginas: arriba (horizontal) o en el lateral izquierdo. El usuario necesita ver de un vistazo qué está filtrado — filtros dispersos, u ocultos en un panel plegable, son la principal causa de que la gente lea el número equivocado sin darse cuenta.
Arriba (recomendado para 2 a 4 filtros):
[ Período v ] [ Región v ] [ Canal v ] [ Limpiar filtros ]
Lateral izquierdo (5+ filtros): ancho fijo de 240 px
Siempre visible: un texto con la selección actual
"Mostrando: Nordeste · E-commerce · Mar/2026"Combina tres canales: símbolo (flecha o triángulo), signo (+/-) y color. Quien ve colores lo lee más rápido; quien no, entiende igual. También vale para tablas: añade la columna de icono, no solo pintes el fondo de la celda.
Un buen formato de variación:
v -3,1% (caída) ^ +8,2% (subida)
Formato condicional por iconos en Power BI:
Formato > Formato condicional > Iconos > Dar formato por campo
Nunca: solo la celda en rojo, sin número y sin símbolo.En la vista ejecutiva, abrevia (R$ 18,4 mi): los centavos no cambian una decisión. En la tabla de trabajo, valor completo con 2 decimales. Números siempre alineados a la derecha, texto a la izquierda, la misma cantidad de decimales en toda la columna. Y ojo a la diferencia entre variación porcentual y punto porcentual.
Tarjeta: R$ 18,4 mi Tabla: 18.437.219,63
Porcentaje: 23,7% (1 decimal basta en la mayoría de los casos)
Variación: +8,2% (creció 8,2% respecto a la base)
Diferencia: +1,4 p.p. (el margen pasó de 22,3% a 23,7%)
Confundir las dos últimas es un error clásico en una reunión de resultados.Capas. La primera pantalla responde qué (si está bien o mal); el drill responde por qué y dónde. Poner el detalle en la pantalla principal lo vuelve todo lento y esconde lo que importa. Regla práctica: el detalle debe estar a dos clics como máximo — y con un botón de volver visible.
Nivel 1 Visión general KPI + tendencia + ranking
Nivel 2 Obtener detalles la página de la tienda seleccionada
Nivel 3 Detalle tabla de cupones (exportable)
Clic derecho en la barra de la tienda > Obtener detalles > Detalhe da Loja
En la página de destino: [ Volver ] siempre visible, en la misma esquina.En lenguaje de negocio, como habla la gente. El título de un visual debe ser la pregunta o la conclusión, no el nombre técnico del campo. Medidas con nombre claro (Faturamento, no vlr_liq_sum) porque aparecen en el tooltip, en la leyenda y en las exportaciones.
Malo Bueno
"Página 1" "Visão geral"
"Suma de ValorLiquido por..." "Faturamento por loja"
"Gráfico 3" "Evolução mensal x ano anterior"
"med_margem_pct" "Margem %"Escribe un mensaje explícito en lugar de la pantalla en blanco, diciendo qué pasó y qué hacer. En Power BI eso se resuelve con un cuadro de texto mostrado por una medida o con el estado vacío nativo del visual. Una pantalla en blanco se lee como tablero roto — y se convierte en un ticket de soporte.
Mensagem Vazio =
IF(
ISBLANK( [Faturamento] ),
"Nenhuma venda encontrada para os filtros selecionados. " &
"Tente ampliar o período ou limpar o filtro de canal.",
""
)
Ponlo en un cuadro de texto con fx en el valor, sobre el área del visual.Sí, siempre — es el elemento que más sostiene la confianza en el tablero. Sin él, cualquier número antiguo se convierte en sospecha de error. Muestra la fecha del dato, no la de la actualización: son cosas distintas cuando la carga de la base se retrasa y Power BI actualiza igual.
Última Venda Carregada = MAX( fVendas[DataVenda] )
Último Refresh = MAX( Auditoria[DataHoraCarga] )
Un pie de página en todas las páginas:
"Datos hasta 19/08/2026 · actualizado a las 06:12 · fuente: DW_Vendas"Usa el Diseño móvil (Ver > Diseño móvil) y monta una versión vertical con lo esencial: de 2 a 4 KPI y un gráfico simple. No es la misma pantalla encogida — es una curaduría. Una tabla ancha y un mapa detallado no funcionan en el móvil, y hacer drill con el dedo necesita un área de toque mayor.
Ver > Diseño móvil
Qué entra: los KPI principales, la tendencia, el top 5
Qué queda fuera: tabla ancha, dispersión, mapa detallado
Área de toque: al menos 44 x 44 px
Prueba real: ábrelo en la app de Power BI, en tu móvil, con datos móvilesPrimero, entender por qué — la mayoría de las veces el tablero no responde su pregunta, o falta un corte que necesita. Resuelto eso, ofrece una vía oficial de exportación (tabla de detalle o informe paginado). Prohibirlo sin resolver la causa solo produce una hoja de cálculo paralela, que es peor.
Las razones reales detrás del pedido
"Quiero enviarlo por correo" -> suscripción por correo en el servicio
"Necesito el detalle" -> página de detalles con la tabla
"Hago mi propio cálculo" -> la medida que necesita no existe
"No confío en el número" -> faltan glosario y fecha de actualizaciónQuita lo que no informa: sombras, bordes dobles, degradados, 3D, líneas de cuadrícula pesadas, la leyenda de una serie única, el eje redundante cuando ya hay etiquetas de datos. Después alinea todo en una cuadrícula de 8 px y garantiza respiración entre bloques. Un tablero limpio no es un tablero vacío: es aquel en el que todo lo que quedó tiene función.
Antes de publicar, quita:
[ ] efectos 3D y sombras
[ ] bordes decorativos y fondos de color por bloque
[ ] líneas de cuadrícula pesadas (déjalas suaves, o ninguna)
[ ] la leyenda cuando hay una única serie
[ ] el eje Y cuando ya hay etiquetas de datos
[ ] el icono que solo repite el texto de al ladoAntes de construir y después de publicar: las etapas que separan un tablero entregado de un tablero usado.
Para equivocarse barato. Cambiar un rectángulo en Figma cuesta segundos; cambiar un tablero listo cuesta remodelado, medidas nuevas y validación otra vez. Además, el boceto cambia la conversación: en vez de discutir el color de un gráfico, el cliente discute si la pregunta es la correcta — que es lo que importa en esa fase.
El coste de cambiar una decisión de layout
papel ~ 2 min
Figma ~ 10 a 30 min
tablero real ~ 2 a 5 días
Un extra: el wireframe aprobado se convierte en el alcance escrito del proyecto.Un frame de 1280x720 (la proporción del lienzo de Power BI), cajas grises, cuadrícula de 8 px y, dentro de cada caja, la pregunta que responde. Sin color y sin datos reales: la baja fidelidad impide que la discusión se desvíe hacia la estética demasiado pronto.
Frame 1280 x 720
[ franja ] título + filtros h 72
[ 4 cajas ] KPI h 120
[ 2 cajas ] tendencia | ranking h 260
[ 1 caja ] tabla de detalle h 200
Escribe en la caja: "¿Qué tienda cayó más?" — no "gráfico de barras".Trabajando en la misma proporción y en la misma cuadrícula: Power BI acepta posición y tamaño numéricos por visual, así que X, Y, ancho y alto de Figma se convierten en valores del panel de formato. Colores y fuentes van por el tema JSON, y las etiquetas del wireframe deben ser los nombres reales de las medidas.
Figma Power BI
frame 1280 x 720 -> Ver > Tamaño de página > 16:9
X/Y/An/Al en 8 px -> Formato > General > Posición y tamaño
estilos de color -> tema JSON (Ver > Temas > Buscar temas)
componente de KPI -> grupo de visuales copiado entre páginas
etiqueta "Margem %" -> el nombre de la medida [Margem %]Un conjunto de decisiones tomadas una vez y reutilizadas: paleta con significado, escala tipográfica, espaciado, formatos numéricos, el patrón de la tarjeta de KPI y las reglas de interacción. La ganancia aparece en el quinto tablero — todos parecen el mismo producto, y un cambio de marca no exige repintar 40 visuales.
Qué documentar (basta una página)
Colores 3 categóricos + secuencial + divergente + neutros
Tipografía título 14 / etiqueta 10 / KPI 28 (Segoe UI)
Espaciado cuadrícula de 8 px, al menos 16 de respiración entre bloques
Números R$ abreviado en el KPI, 2 decimales en la tabla, % con 1
Patrones KPI = valor + variación (flecha, signo, color)
Interacción la segmentación filtra; el clic resalta; detalle en drill-throughEscribe un archivo con los colores y las clases de texto e impórtalo en Ver > Temas > Buscar temas. Después se aplica a cada visual nuevo y se versiona en Git — a diferencia del formato hecho visual por visual, que se pierde en la primera página nueva.
{
"name": "Casa & Construção 2026",
"dataColors": ["#2B6CB0", "#2C7A7B", "#6B46C1", "#B7791F", "#B23A48"],
"background": "#FFFFFF",
"foreground": "#1A202C",
"tableAccent": "#2B6CB0",
"textClasses": {
"title": { "fontFace": "Segoe UI Semibold", "fontSize": 14, "color": "#1A202C" },
"label": { "fontFace": "Segoe UI", "fontSize": 10, "color": "#4A5568" }
}
}Es medir el comportamiento de quien usa el producto para decidir qué cambiar — en vez de decidir por opinión. Aplicado a BI: cuántos lo abren, cuántos vuelven, qué páginas se usan, cuánto tarda el primer filtro, dónde la gente se atasca. Ya construyes tableros para que otros midan el negocio; aquí haces lo mismo con tu propio tablero.
Fuentes de datos
Cuantitativa: métricas de uso en el servicio (visitas, páginas, usuarios)
Cualitativa: pruebas de usabilidad, entrevistas, observación
Decisión: cruza las dos — el número dice DÓNDE, la conversación dice POR QUÉAdopción (usuarios únicos sobre el público objetivo), retención (volvieron la semana siguiente), frecuencia, profundidad (páginas por sesión) y tiempo hasta el primer filtro. La retención es la que más importa: todo tablero recibe visitas en el lanzamiento, porque la curiosidad las trae.
Adopción > 60% del público en 30 días
Retención semanal > 40%
Frecuencia depende del ritmo de la decisión (semanal, diaria)
Profundidad ¿se usa el detalle, o solo el top?
Tiempo hasta filtro > 30 s indica que el usuario no descubrió qué hacer
Una página con 0 visitas en 60 días: quítala o investiga.Cinco personas, 15 minutos cada una, cuatro tareas. Da una tarea, no una pregunta, cronometra y quédate callado — las ganas de ayudar son lo que más estropea la prueba. Vas a ver a alguien buscar el filtro en tres sitios distintos, y eso vale más que una hora de reunión de requisitos.
T1. ¿Cuál fue la facturación de marzo? esperado < 10 s
T2. ¿Qué tienda cayó más contra el año anterior? esperado < 30 s
T3. En esa tienda, ¿qué categoría provocó la caída? esperado < 45 s
T4. Exporta la lista de tiendas por debajo de la meta. esperado < 30 s
Registra: tiempo, clics equivocados, titubeo, lo que dijo la persona.Separa lo que impide la tarea de lo que es preferencia. Lo que impide va primero; la preferencia entra si es barata o si se repite. Un pedido aislado de un usuario influyente no es prioridad; tres personas atascadas en el mismo punto, sí.
Una matriz simple
Muchos usuarios Pocos usuarios
Impide hazlo ahora hazlo después
Molesta hazlo después anótalo y observa
Una frase útil en la reunión: "¿eso te impide completar la tarea,
o es una preferencia de visualización?"Muestra el proceso, no solo la pantalla bonita: la pregunta de negocio, el modelo (estrella), dos o tres medidas que exigieron una decisión, el antes y después del layout y el número de rendimiento. Quien contrata para tableros ya vio pantallas bonitas de sobra; lo que destaca es saber explicar por qué se tomó cada decisión.
Un guion de 5 minutos (sirve en entrevista y en portafolio)
1. Problema: "42 tiendas, decisión semanal de presupuesto, datos solo en Excel"
2. Modelo: el diagrama del modelo estrella (por qué hechos y dimensiones)
3. Medida: una que exigió contexto (ALLSELECTED, ranking, metas)
4. Diseño: antes y después, y qué cambió la prueba con 5 personas
5. Resultado: actualización 38 min -> 6 min; la página en 2,4 s; 78% de adopción