Funciones de Información
ISBLANK, ISERROR, ISNUMBER, ISINSCOPE, HASONEVALUE, CUSTOMDATA y todas las funciones que te cuentan en qué contexto está tu medida
IS de tipo — verificar el tipo de dato
| FUNCIÓN | DEVUELVE TRUE CUANDO... | EJEMPLO |
|---|---|---|
ISNUMBER(x) |
x es un número | ISNUMBER(42) → TRUE |
ISTEXT(x) |
x es texto (string) | ISTEXT("hola") → TRUE |
ISLOGICAL(x) |
x es TRUE o FALSE | ISLOGICAL(TRUE) → TRUE |
ISDATE(x) |
x es una fecha/hora | ISDATE(TODAY()) → TRUE |
ISNONE(x) |
x es NONE (tipo especial interno) | Uso muy raro |
Ejemplo — datos mixtos:
// Columna 'Productos'[Precio] importada de Excel con algún texto ("N/A", "pendiente"):
Precio Seguro =
IF(
ISNUMBER('Productos'[Precio]),
'Productos'[Precio],
0
)
// Limpiar columna que debería ser número:
Precio Limpio =
IF(
ISNUMBER(VALUE('Productos'[Precio_texto])),
VALUE('Productos'[Precio_texto]),
BLANK()
)
// Detectar códigos que no son numéricos (posiblemente erróneos):
'Productos'[Código Inválido] =
IF(ISNUMBER(VALUE('Productos'[Código])), FALSE, TRUE)
IS de valor — verificar el valor
// Mostrar "Sin datos" cuando no hay ventas en el período:
Ventas Display =
IF(
ISBLANK([Total Ventas]),
"Sin datos en este período",
FORMAT([Total Ventas], "#,##0 €")
)
// Evitar calcular variaciones cuando no hay dato anterior:
Variación YoY =
VAR Anterior = CALCULATE([Total Ventas], SAMEPERIODLASTYEAR('Calendario'[Date]))
RETURN
IF(
ISBLANK(Anterior),
BLANK(), -- no mostrar nada si no hay dato del año anterior
DIVIDE([Total Ventas] - Anterior, Anterior)
)
// ISBLANK vs = BLANK():
ISBLANK([Ventas]) -- la forma correcta
[Ventas] = BLANK() -- también funciona pero menos idiomática
// ¿El cliente tiene pedidos en el período seleccionado?
Tiene Pedidos =
IF(
ISEMPTY(RELATEDTABLE('Pedidos')),
"Sin pedidos",
"Activo: " & COUNTROWS(RELATEDTABLE('Pedidos')) & " pedidos"
)
// Evitar errores al procesar tablas vacías:
Mejor Cliente =
VAR TablaFiltrada = FILTER('Clientes', [Total Ventas] > 10000)
RETURN
IF(
ISEMPTY(TablaFiltrada),
"Ningún cliente supera 10.000 €",
MAXX(TablaFiltrada, [Total Ventas])
)
// ¿Es la fila actual posterior a (2024, "Electrónica") en orden ascendente?
ISONORAFTER(
'Calendario'[Año], 2024, ASC,
'Productos'[Categoría], "Electrónica", ASC
)
// Útil para cursor-based pagination en API de DAX
IS de contexto de filtro — ¿Qué hay filtrado?
Las funciones más útiles para medidas dinámicas
// Título del visual que muestra el país si hay uno seleccionado:
Título País =
IF(
HASONEVALUE('Clientes'[País]),
"Ventas en " & VALUES('Clientes'[País]),
"Ventas por país"
)
// Actuar diferente según si hay uno o varios países:
KPI País =
IF(
HASONEVALUE('Clientes'[País]),
"País: " & SELECTEDVALUE('Clientes'[País]) & " — " & FORMAT([Total Ventas], "#,##0 €"),
FORMAT([Total Ventas], "#,##0 €") & " (múltiples países)"
)
Diferencia HASONEVALUE vs HASONEFILTER:
// Slicer con "España" seleccionado:
HASONEVALUE('Clientes'[País]) → TRUE (hay exactamente un valor visible)
HASONEFILTER('Clientes'[País]) → TRUE (hay exactamente un filtro directo)
// Slicer con "España" y "Francia" seleccionados:
HASONEVALUE('Clientes'[País]) → FALSE (hay dos valores visibles)
HASONEFILTER('Clientes'[País]) → TRUE (hay un filtro, que incluye dos valores)
// Sin slicer (todos los países):
HASONEVALUE('Clientes'[País]) → FALSE
HASONEFILTER('Clientes'[País]) → FALSE
// ¿Hay algún filtro en el país?
Hay Filtro País = ISFILTERED('Clientes'[País])
// TRUE si hay un slicer de País activo con cualquier selección
// Mensaje contextual:
Contexto Filtro =
SWITCH(
TRUE(),
ISFILTERED('Clientes'[País]) && ISFILTERED('Productos'[Categoría]),
"Filtrado por País y Categoría",
ISFILTERED('Clientes'[País]),
"Filtrado por País: " & SELECTEDVALUE('Clientes'[País], "varios"),
ISFILTERED('Productos'[Categoría]),
"Filtrado por Categoría: " & SELECTEDVALUE('Productos'[Categoría], "varias"),
"Sin filtros activos"
)
Diferencia ISFILTERED vs ISCROSSFILTERED:
// Si tienes filtro en Productos[Categoría] y hay relación Productos → Ventas:
ISFILTERED('Ventas'[Importe]) → FALSE (el filtro es indirecto)
ISCROSSFILTERED('Ventas'[Importe]) → TRUE (hay filtro a través de la relación)
ISCROSSFILTERED('Ventas') → TRUE (la tabla está siendo filtrada por relación)
IS de scope — saber en qué nivel de jerarquía estás
Por qué es tan importante:
Cuando tienes una matriz con jerarquías (País > Región > Ciudad), la misma medida se evalúa en diferentes contextos:
- Fila de Ciudad: contexto de Ciudad (el más detallado)
- Fila de Región: contexto de Región (subtotal)
- Fila de País: contexto de País (subtotal mayor)
- Gran total: sin contexto de ninguna columna de la jerarquía
ISINSCOPE te dice en qué nivel está evaluándose la medida en ese momento.
Ejemplo básico — diferente cálculo por nivel:
// En una matriz País > Región > Ciudad:
Métrica Inteligente =
SWITCH(
TRUE(),
ISINSCOPE('Geografía'[Ciudad]), [Total Ventas], -- nivel Ciudad
ISINSCOPE('Geografía'[Región]), [Total Ventas], -- nivel Región
ISINSCOPE('Geografía'[País]), [Total Ventas], -- nivel País
[Total Ventas] -- gran total
)
// Caso práctico — porcentaje solo en niveles de detalle:
% Contribución =
IF(
ISINSCOPE('Productos'[Producto]), -- solo a nivel producto
DIVIDE(
[Total Ventas],
CALCULATE([Total Ventas], ALLEXCEPT('Productos', 'Productos'[Categoría]))
),
BLANK() -- en subtotales y total: no mostrar porcentaje
)
// Mostrar icono distinto según nivel:
Indicador Nivel =
SWITCH(
TRUE(),
ISINSCOPE('Geografía'[Ciudad]), "📍 " & [Total Ventas],
ISINSCOPE('Geografía'[Región]), "🗺️ " & [Total Ventas],
ISINSCOPE('Geografía'[País]), "🌍 " & [Total Ventas],
"🌐 Gran Total: " & [Total Ventas]
)
// Mostrar margen % en detalle pero recuento en subtotales:
KPI Adaptativo =
IF(
ISINSCOPE('Productos'[SKU]),
FORMAT([Margen %], "0.0%"), -- en filas de producto: margen
FORMAT([Nº Pedidos], "#,##0") -- en subtotales: número de pedidos
)
ISINSCOPE es la solución correcta para medidas que deben mostrar cosas distintas en distintos niveles de jerarquía. Muchas personas intentan usar ISFILTERED o HASONEVALUE para esto con resultados inconsistentes — ISINSCOPE es la herramienta diseñada exactamente para este caso.
Diferencia con ISINSCOPE:
// Jerarquía: País > Región > Ciudad
// Fila de "Madrid" (nivel Ciudad):
ISINSCOPE('Geografía'[País]) → TRUE (Madrid está dentro de un País)
ISINSCOPE('Geografía'[Región]) → TRUE (Madrid está dentro de una Región)
ISINSCOPE('Geografía'[Ciudad]) → TRUE (estamos en nivel Ciudad)
ISATLEVEL('Geografía'[Ciudad]) → TRUE (estamos EN el nivel Ciudad)
ISATLEVEL('Geografía'[País]) → FALSE (no estamos en el nivel País)
Información del usuario y entorno
// Mostrar saludo personalizado:
Saludo = "Hola, " & USERNAME() & " 👋"
// Auditoría de quién está viendo qué:
// (Guardar en tabla de log con Power Automate)
Ejemplo — RLS dinámica:
// Tabla de permisos: Email | Región
// 'Permisos'[Email] = "maria@empresa.com", 'Permisos'[Región] = "Norte"
// Rol de seguridad dinámico:
// En la tabla Ventas, filtro de seguridad:
[RegionVenta] IN
CALCULATETABLE(
VALUES('Permisos'[Región]),
'Permisos'[Email] = USERPRINCIPALNAME()
)
// Cada usuario solo ve las ventas de su región asignada en la tabla Permisos
Ejemplo — RLS multi-tenant con Power BI Embedded:
// La aplicación pasa el TenantID en CustomData al conectarse
// La medida de seguridad usa ese valor:
// [TenantID] = CUSTOMDATA()
Búsqueda en tablas — CONTAINS y CONTAINSROW
// ¿Hay alguna venta del producto P001 al cliente C123?
Existe Combinación =
CONTAINS(
'Ventas',
'Ventas'[IDProducto], "P001",
'Ventas'[IDCliente], "C123"
)
// ¿Vendemos en España Y Francia?
Vendemos en Iberia =
AND(
CONTAINS(ALL('Clientes'), 'Clientes'[País], "España"),
CONTAINS(ALL('Clientes'), 'Clientes'[País], "Francia")
)
// ¿Existe la combinación (2024, "Norte") en la tabla de datos?
CONTAINSROW(
SUMMARIZE('Ventas', 'Calendario'[Año], 'Clientes'[Región]),
ROW("Año", 2024, "Región", "Norte")
)
INFO.VIEW — Consulta los metadatos de tu propio modelo
Las funciones INFO.VIEW son especiales dentro de DAX: en lugar de calcular datos de negocio, devuelven información sobre la estructura del propio modelo semántico. Son perfectas para crear documentación automática que se actualiza sola con cada refresh. Llegaron en octubre de 2024 y son una de las novedades más prácticas de los últimos tiempos. 🗂️
Estas funciones requieren permiso de escritura sobre el modelo semántico. No funcionan en Power BI Desktop en modo conexión en vivo (Live Connection). A diferencia de las funciones INFO.* sin VIEW (que solo funcionan en DAX Query View), las INFO.VIEW.* sí se pueden usar en tablas calculadas, columnas calculadas y medidas.
Columnas principales que devuelve:
| COLUMNA | TIPO | DESCRIPCIÓN |
|---|---|---|
| [Name] | Texto | Nombre de la tabla |
| [Description] | Texto | Descripción (si está documentada) |
| [StorageMode] | Texto | "Import", "DirectQuery", "Dual" |
| [IsHidden] | Booleano | TRUE si está oculta en el panel de campos |
| [IsDateTable] | Booleano | TRUE si está marcada como tabla de fechas |
Ejemplos:
// Ver todas las tablas del modelo
EVALUATE INFO.VIEW.TABLES()
// Solo tablas visibles
EVALUATE
FILTER(
INFO.VIEW.TABLES(),
NOT [IsHidden]
)
// Tabla calculada de documentación automática
Doc_Tablas =
SELECTCOLUMNS(
FILTER(INFO.VIEW.TABLES(), NOT [IsHidden]),
"Tabla", [Name],
"Descripción", [Description],
"Modo", [StorageMode],
"Tabla de fechas", [IsDateTable]
)
Columnas principales que devuelve:
| COLUMNA | TIPO | DESCRIPCIÓN |
|---|---|---|
| [TableName] | Texto | Nombre de la tabla que contiene la columna |
| [Name] | Texto | Nombre de la columna |
| [DataType] | Texto | "Integer", "Double", "String", "DateTime", "Boolean" |
| [IsHidden] | Booleano | TRUE si está oculta |
| [Description] | Texto | Descripción de la columna |
| [FormatString] | Texto | Formato aplicado |
| [Expression] | Texto | Si es columna calculada, su fórmula DAX |
Ejemplos:
// Diccionario de datos automático (tabla calculada)
Diccionario_Datos =
SELECTCOLUMNS(
FILTER(
INFO.VIEW.COLUMNS(),
NOT [IsHidden]
),
"Tabla", [TableName],
"Columna", [Name],
"Tipo", [DataType],
"Descripción", [Description],
"Formato", [FormatString]
)
// Contar columnas de texto del modelo
N Columnas Texto =
COUNTROWS(
FILTER(INFO.VIEW.COLUMNS(), [DataType] = "String")
)
// Detectar columnas calculadas (tienen expresión DAX)
Columnas_Calculadas =
FILTER(
INFO.VIEW.COLUMNS(),
NOT ISBLANK([Expression])
)
Columnas principales que devuelve:
| COLUMNA | TIPO | DESCRIPCIÓN |
|---|---|---|
| [Table] | Texto | Tabla donde está definida la medida |
| [Name] | Texto | Nombre de la medida |
| [Expression] | Texto | Fórmula DAX completa |
| [FormatString] | Texto | Formato (ej: "#,##0.00 €", "0.00%") |
| [Description] | Texto | Descripción de la medida |
| [IsHidden] | Booleano | TRUE si está oculta |
| [State] | Texto | "Ready" si es válida, o mensaje de error |
Ejemplos:
// Todas las medidas con su fórmula
EVALUATE INFO.VIEW.MEASURES()
// Medidas con errores (auditoría de calidad)
EVALUATE
FILTER(
INFO.VIEW.MEASURES(),
[State] <> "Ready"
)
// Medidas sin descripción — ¡a documentar!
Medidas Sin Documentar =
COUNTROWS(
FILTER(
INFO.VIEW.MEASURES(),
ISBLANK([Description])
)
)
// Exportar toda la documentación de medidas
Doc_Medidas =
SELECTCOLUMNS(
INFO.VIEW.MEASURES(),
"Tabla", [Table],
"Medida", [Name],
"Descripción", [Description],
"Fórmula DAX", [Expression],
"Formato", [FormatString],
"Estado", [State]
)
La columna [State] = 'Ready' indica que la medida es válida. Cualquier otro valor indica una medida con error — quizás una referencia rota que no sabías que existía. ¡INFO.VIEW.MEASURES es tu mejor aliada para hacer auditorías de calidad del modelo!
Columnas principales que devuelve:
| COLUMNA | TIPO | DESCRIPCIÓN |
|---|---|---|
| [FromTableName] | Texto | Tabla del lado "muchos" |
| [FromColumnName] | Texto | Columna de la tabla muchos |
| [ToTableName] | Texto | Tabla del lado "uno" |
| [ToColumnName] | Texto | Columna de la tabla uno |
| [CrossFilteringBehavior] | Texto | "OneDirection", "BothDirections" |
| [IsActive] | Booleano | TRUE si la relación está activa |
| [Cardinality] | Texto | "ManyToOne", "OneToOne", "ManyToMany" |
Ejemplos:
// Diagrama de relaciones en texto (tabla calculada)
Doc_Relaciones =
ADDCOLUMNS(
INFO.VIEW.RELATIONSHIPS(),
"Relación",
[FromTableName] & "[" & [FromColumnName] & "] → "
& [ToTableName] & "[" & [ToColumnName] & "]",
"Estado", IF([IsActive], "✅ Activa", "⚠️ Inactiva")
)
// Detectar relaciones bidireccionales (alerta de rendimiento)
Relaciones_Bidireccionales =
FILTER(
INFO.VIEW.RELATIONSHIPS(),
[CrossFilteringBehavior] = "BothDirections"
)
// Contar relaciones inactivas
Relaciones Inactivas =
COUNTROWS(
FILTER(INFO.VIEW.RELATIONSHIPS(), NOT [IsActive])
)
Si tienes muchas relaciones con BothDirections puedes estar afectando el rendimiento del modelo. INFO.VIEW.RELATIONSHIPS te permite detectarlas todas de un vistazo.
Combina las 4 funciones en un dashboard de calidad del modelo:
Total Tablas = COUNTROWS(INFO.VIEW.TABLES())Total Columnas = COUNTROWS(INFO.VIEW.COLUMNS())Total Medidas = COUNTROWS(INFO.VIEW.MEASURES())Total Relaciones = COUNTROWS(INFO.VIEW.RELATIONSHIPS())% Medidas documentadas = DIVIDE(COUNTROWS(FILTER(INFO.VIEW.MEASURES(), NOT ISBLANK([Description]))), COUNTROWS(INFO.VIEW.MEASURES()))Con estas 5 medidas en un visual de tarjeta tienes un scorecard del estado de documentación de tu modelo.
INFO.VIEW.* vs INFO.* — ¿cuál usar?
Además de las 4 funciones INFO.VIEW, DAX tiene más de 50 funciones INFO.* (sin VIEW) disponibles desde diciembre de 2023. Son los equivalentes exactos de los DMVs de Analysis Services. Aquí tienes la diferencia clave:
| CARACTERÍSTICA | INFO.VIEW.* | INFO.* |
|---|---|---|
| Disponible desde | Octubre 2024 | Diciembre 2023 |
| Tablas calculadas y medidas | ✅ Sí | ❌ No |
| Nombres amigables | ✅ Sí | ⚠️ Usa IDs numéricos |
| Número de funciones | 4 | 50+ |
| Para quién | Cualquier dev | Admins y power users |
| Dónde funciona | En todo el modelo | Solo DAX Query View |
Las INFO.* sin VIEW usan IDs numéricos en lugar de nombres — por ejemplo, INFO.MEASURES() muestra un TableID en vez del nombre de la tabla, así que necesitas hacer un JOIN con INFO.TABLES() para obtener el nombre legible. Las INFO.VIEW.* ya resuelven eso por ti.
Ejemplo comparativo:
// ❌ INFO.MEASURES — solo en DAX Query View, devuelve TableID
EVALUATE INFO.MEASURES()
// Para ver el nombre de la tabla tienes que hacer:
EVALUATE
NATURALLEFTOUTERJOIN(
INFO.MEASURES(),
SELECTCOLUMNS(INFO.TABLES(), "TableID", [ID], "Tabla", [Name])
)
// ✅ INFO.VIEW.MEASURES — funciona en tablas calculadas, ya
// incluye el nombre de la tabla directamente
EVALUATE INFO.VIEW.MEASURES()
Las INFO.* más útiles en DAX Query View (para admins):
INFO.MEASURES()→ medidas (con TableID)INFO.COLUMNS()→ columnasINFO.TABLES()→ tablasINFO.RELATIONSHIPS()→ relacionesINFO.PARTITIONS()→ particiones del modeloINFO.HIERARCHIES()→ jerarquíasINFO.CALCULATIONGROUPS()→ grupos de cálculoINFO.CALCULATIONITEMS()→ ítems de cálculoINFO.ROLES()→ roles de seguridad RLSINFO.KPIS()→ KPIs definidos en el modeloINFO.FUNCTIONS()→ todas las funciones DAX disponibles
Para el trabajo del día a día usa INFO.VIEW.* — son más simples y funcionan en cualquier contexto del modelo. Reserva INFO.* para consultas administrativas avanzadas en DAX Query View o DAX Studio.
Patrones de uso
Patrón 1 — Medida robusta con validación completa:
Métrica Robusta =
VAR Denominador = [Total Pedidos]
VAR Resultado = DIVIDE([Total Ventas], Denominador)
RETURN
SWITCH(
TRUE(),
ISBLANK([Total Ventas]), "—",
NOT ISNUMBER(Resultado), "Error",
ISFILTERED('Clientes'[País]),
"🌍 " & SELECTEDVALUE('Clientes'[País], "Varios") & ": " &
FORMAT(Resultado, "#,##0.00"),
FORMAT(Resultado, "#,##0.00")
)
Patrón 2 — Título dinámico con contexto:
Título Completo =
VAR Año =
IF(HASONEVALUE('Calendario'[Año]), SELECTEDVALUE('Calendario'[Año]), "Todos los años")
VAR País =
IF(HASONEVALUE('Clientes'[País]), SELECTEDVALUE('Clientes'[País]), "Global")
VAR Nivel = SWITCH(
TRUE(),
ISINSCOPE('Productos'[SKU]), "Detalle de producto",
ISINSCOPE('Productos'[Categoría]), "Por categoría",
"Resumen global"
)
RETURN País & " | " & Año & " | " & Nivel
Patrón 3 — RLS dinámica completa:
// Tabla de Permisos: Email, Región, Categoría
// Cada usuario ve solo sus filas autorizadas
// Filtro de seguridad en la tabla Ventas:
CALCULATE(
COUNTROWS('Ventas'),
TREATAS(
CALCULATETABLE(
SELECTCOLUMNS('Permisos', "Reg", 'Permisos'[Región]),
'Permisos'[Email] = USERPRINCIPALNAME()
),
'Clientes'[Región]
)
) > 0
Patrón 4 — Dashboard de calidad del modelo con INFO.VIEW:
// 5 medidas para un scorecard de documentación del modelo:
Total Tablas =
COUNTROWS(INFO.VIEW.TABLES())
Total Medidas =
COUNTROWS(INFO.VIEW.MEASURES())
Medidas con error =
COUNTROWS(FILTER(INFO.VIEW.MEASURES(), [State] <> "Ready"))
Medidas sin documentar =
COUNTROWS(FILTER(INFO.VIEW.MEASURES(), ISBLANK([Description])))
% Documentación completa =
DIVIDE(
COUNTROWS(FILTER(INFO.VIEW.MEASURES(), NOT ISBLANK([Description]))),
COUNTROWS(INFO.VIEW.MEASURES())
)
¡Las funciones de información ya son tuyas — ahora tus medidas son mucho más inteligentes y adaptativas! A continuación las Funciones Financieras — las más numerosas de DAX con sus 50 funciones para análisis de inversiones, préstamos y bonos.