DAX User-Defined Functions: la referencia que guardas y reutilizas
En resumen — Desde junio 2026, Power BI Desktop incluye funciones personalizadas en DAX con disponibilidad general. Esta página es la referencia que yo quiero tener a mano: sintaxis, tipos de parámetros, modos de evaluación, utilidades y bugs conocidos — todo verificado contra las fuentes primarias.
Nota
¿Tienes prisa? Salta directo a la tabla de tipos de parámetros o a la sección de bugs conocidos y workarounds. El resto lo lees cuando lo necesitas.
1. Sintaxis básica
Una función personalizada en DAX usa la palabra clave FUNCTION con una flecha => para separar la firma del cuerpo.
En DAX Query View (para probar)
DEFINE
FUNCTION NombreFuncion = (
param1 : TIPO,
param2 : TIPO = ValorDefault
) =>
<cuerpo de la función>
EVALUATE NombreFuncion( ... ) En TMDL View (para guardar al modelo)
createOrReplace
function NombreFuncion = (
param1 : TIPO,
param2 : TIPO = ValorDefault
) =>
<cuerpo> Nota
Guardar al modelo desde DAX Query View: usa “Update model with changes” para guardar todas las funciones del query, o “Update model: Add new function” para guardar solo la función donde está el cursor.
2. Los 8 tipos de parámetros
Los tipos se dividen en dos familias con comportamientos distintos:
| Tipo | Familia | Modo por defecto | Casting implícito | Descripción |
|---|---|---|---|---|
AnyVal | Value | VAL | ✅ | Abstracto: cubre Scalar o Table |
Scalar | Value | VAL | ✅ | Solo escalares. Combinable con subtype |
Table | Value | VAL | ✅ | Solo tablas (incluye expresiones de tabla) |
AnyRef | Expression | EXPR | ❌ | Cualquier expresión — el más permisivo |
ColumnRef | Expression | EXPR | ❌ | Solo referencias a columnas del modelo |
MeasureRef | Expression | EXPR | ❌ | Solo referencias a medidas del modelo |
TableRef | Expression | EXPR | ❌ | Solo tablas del modelo (no expresiones como FILTER) |
CalendarRef | Expression | EXPR | ❌ | Solo referencias a calendarios |
Regla clave: los tipos *Ref siempre son EXPR — no puedes especificar VAL para ellos. Los tipos Value son VAL por defecto.
Subtypes de Scalar
| Subtype | Acepta |
|---|---|
Variant | Cualquier escalar |
Int64 / Integer | Número entero |
Decimal / Currency | Decimal de precisión fija |
Double | Decimal punto flotante |
String / Text | Texto |
DateTime | Fecha/hora |
Boolean / Logical | TRUE/FALSE |
Numeric | Cualquier número (Int64, Decimal, Double) |
3. VAL vs EXPR — el concepto más importante
Este es el punto donde más confusión se genera. La diferencia afecta directamente cómo se comporta tu función con CALCULATE.
VAL — evaluación inmediata (eager)
El argumento se evalúa antes de entrar a la función. Hereda el row context y el filter context del caller.
FUNCTION TotalFilas = ( t : TABLE VAL ) =>
COUNTROWS( CALCULATETABLE( t, ALL( 'Date' ) ) )
-- El filtro de año YA está aplicado cuando entra la tabla
-- CALCULATETABLE con ALL('Date') no puede quitarlo
CALCULATE( TotalFilas( 'Sales' ), 'Date'[Year] = 2025 )
-- → cuenta solo las filas de 2025 EXPR — evaluación diferida (lazy)
El argumento se evalúa dentro de la función. Solo hereda el filter context — no el row context.
FUNCTION TotalFilas = ( t : TABLE EXPR ) =>
COUNTROWS( CALCULATETABLE( t, ALL( 'Date' ) ) )
-- La tabla se evalúa dentro, CALCULATETABLE puede modificar el contexto
CALCULATE( TotalFilas( 'Sales' ), 'Date'[Year] = 2025 )
-- → cuenta TODAS las filas (ALL('Date') elimina el filtro externo) La regla con CALCULATE
-- ❌ Incorrecto: parámetro VAL → CALCULATE no puede modificar el contexto del valor
FUNCTION VentasFiltradas = ( tipo ) =>
CALCULATE( SUM( Ventas[Monto] ), Ventas[Tipo] = tipo )
-- ✅ Correcto: ANYREF implica EXPR → CALCULATE funciona
FUNCTION VentasFiltradas = ( tipo : ANYREF ) =>
CALCULATE( SUM( Ventas[Monto] ), Ventas[Tipo] = tipo ) Advertencia
AnyRef con o sin EXPR: escribir ANYREF o ANYREF EXPR produce exactamente el mismo resultado. Todos los tipos *Ref son forzosamente EXPR — escribirlo explícito es redundante pero válido.
4. Funciones de utilidad para UDFs
TABLEOF()
Retorna la tabla completa asociada a una columna, medida o calendario. Imprescindible cuando recibes un ANYREF y necesitas iterar la tabla padre.
FUNCTION ContarFilas = ( col : ANYREF ) =>
COUNTROWS( TABLEOF( col ) )
-- Llamada:
EVALUATE ContarFilas( 'Sales'[Amount] ) -- retorna el total de filas de Sales NAMEOF()
Retorna el nombre de una tabla, columna, medida o calendario como texto. Útil para validaciones y mensajes de error descriptivos.
-- Sintaxis completa:
NAMEOF( <referencia>, <component>, <escaped> )
-- component: TABLE, COLUMN, MEASURE, CALENDAR, FULL (default), SELF, PARENT
-- escaped: ESCAPED (default), UNESCAPED, MINIMALLYESCAPED
NAMEOF( 'Sales'[Amount] ) -- "'Sales'[Amount]"
NAMEOF( 'Sales'[Amount], TABLE ) -- "'Sales'"
NAMEOF( 'Sales'[Amount], COLUMN ) -- "[Amount]"
NAMEOF( TABLEOF( colParam ) ) -- nombre de la tabla del parámetro Nota
Limitación: NAMEOF no acepta variables ni expresiones dinámicas como argumento — solo referencias directas a objetos del modelo.
5. Parámetros opcionales
Cualquier parámetro puede tener un valor por defecto. Cuando el caller lo omite, se usa el default.
FUNCTION AgregarImpuesto = (
monto : NUMERIC,
tasa : NUMERIC = 0.19 -- opcional, default 19%
) =>
monto * ( 1 + tasa )
-- Formas de llamar:
AgregarImpuesto( 100 ) -- usa tasa = 0.19 → 119
AgregarImpuesto( 100, 0.05 ) -- usa tasa = 0.05 → 105
AgregarImpuesto( 100, ) -- argumento vacío → 119 Reglas:
- Pueden estar en cualquier posición (no solo al final)
- El default no puede referenciar otros parámetros de la misma función
- El default solo puede referenciar nombres visibles donde se define la UDF, no donde se llama
6. Type checking dentro del cuerpo
Cuando un parámetro es AnyVal o sin tipo, puedes verificar el tipo en tiempo de ejecución:
ISNUMERIC( x ) ISNUMBER( x ) -- numérico (cualquier tipo)
ISDOUBLE( x ) -- double
ISINT64( x ) ISINTEGER( x ) -- entero
ISDECIMAL( x ) ISCURRENCY( x ) -- decimal
ISSTRING( x ) ISTEXT( x ) -- texto
ISBOOLEAN( x ) ISLOGICAL( x ) -- booleano
ISDATETIME( x ) -- fecha/hora Ejemplo — aceptar clave numérica o código texto:
FUNCTION ObtenerMoneda = ( moneda ) =>
IF(
ISINT64( moneda ),
LOOKUPVALUE( 'Moneda'[Nombre], 'Moneda'[ClaveMoneda], moneda ),
LOOKUPVALUE( 'Moneda'[Nombre], 'Moneda'[Codigo], moneda )
)
ObtenerMoneda( 36 ) -- busca por clave numérica
ObtenerMoneda( "USD" ) -- busca por código texto 7. Bugs conocidos y workarounds
Advertencia
Bug 1 — Tipos específicos no aceptados en todos los call sites
ColumnRef, MeasureRef, CalendarRef y TableRef pueden producir errores de parser en algunos puntos de llamada.
Workaround documentado por Microsoft: usa AnyRef en lugar de los tipos específicos si aparecen líneas rojas o errores de validación.
Advertencia
Bug 2 — Líneas rojas en IntelliSense (falsos positivos)
Ciertos escenarios avanzados muestran líneas rojas o errores de validación al pasar columnas como parámetros EXPR o al usar referencias sin cualificar.
Las líneas rojas pueden ser falsos positivos — el código puede ejecutar correctamente aunque el editor lo marque como error.
Limitaciones generales (GA junio 2026):
- Solo se pueden crear UDFs desde Power BI Desktop — no desde el Service
- No se pueden ocultar, organizar en carpetas ni añadir traducciones
- No soportan recursión ni sobrecarga de funciones (overloading)
- Sin IntelliSense para Live Connect / Composite Models
- OLS no se transfiere automáticamente hacia o desde funciones
8. Inspeccionar las UDFs del modelo
-- Ver todas las UDFs guardadas en el modelo
EVALUATE INFO.USERDEFINEDFUNCTIONS()
-- Ver funciones con metadata de origen
EVALUATE INFO.FUNCTIONS("ORIGIN", "2") 9. Convenciones de nomenclatura
Sin estándar oficial de casing, estas son las convenciones más usadas por la comunidad:
| Tipo de parámetro | Sufijo sugerido |
|---|---|
ColumnRef | _Column |
MeasureRef | _Measure |
TableRef | _Table |
CalendarRef | _Calendar |
AnyRef | _Expr |
Namespacing con dot-notation para bibliotecas y funciones compartidas:
-- Patrón recomendado para equipos y librerías
Empresa.Finance.CrecimientoYoY
DAXLib.SVG.BarraHorizontal
MiOrg.Scorecard.Promedio4Qtrs 10. Ejemplo magistral: PerformanceBand — descarga el PBIP
Nota
Código listo para usar → El proyecto completo está en GitHub: powerbi-pbip-tools / examples / DAX-User-Defined-Functions. Clona el repo, abre el .pbip y haz Refresh — la data se carga automáticamente desde GitHub, sin configurar rutas locales.
Esta función condensa todo lo que hace especiales a las DAX UDFs: recibe cualquier medida y cualquier dimensión como parámetros (ANYREF), usa TABLEOF() para navegar al contexto correcto, limpia el filtro externo con CALCULATETABLE + ALL() y clasifica el valor actual contra la distribución estadística real del grupo — sin umbrales arbitrarios.
Nota
Patrón clave: CALCULATETABLE( ADDCOLUMNS( DISTINCT(groupCol), "@val", CALCULATE(metric) ), ALL( TABLEOF(groupCol) ) ) — primero limpia el contexto externo, luego itera. Sin este orden, todos los productos heredan el filtro activo y el cálculo estadístico se contamina.
FUNCTION PerformanceBand = (
metric : ANYREF, -- cualquier medida del modelo
groupCol : ANYREF -- cualquier columna de dimensión
) =>
VAR _AllRows = CALCULATETABLE(
ADDCOLUMNS( DISTINCT( groupCol ), "@val", CALCULATE( metric ) ),
ALL( TABLEOF( groupCol ) ) -- limpia filtros externos antes de iterar
)
VAR _Mean = AVERAGEX( _AllRows, [@val] )
VAR _StdDev = STDEVX.P( _AllRows, [@val] )
VAR _Current = metric
RETURN
SWITCH(
TRUE(),
ISBLANK( _Current ), "—",
_Current >= _Mean + _StdDev, "★ Top",
_Current >= _Mean, "↑ Sobre promedio",
_Current >= _Mean - _StdDev, "↓ Bajo promedio",
"⚠ Rezagado"
) Las medidas que la usan son de una línea:
-- Clasifica productos por rentabilidad vs. el grupo completo
Profit Band = PerformanceBand( [Total Profit], financials[Product] )
-- Misma lógica, otra métrica — reutilización total
Sales Band = PerformanceBand( [Total Sales], financials[Product] ) Resultado real con datos financials (6 productos, 2013-2014):
| Producto | Total Profit | Profit Band | Total Sales | Sales Band |
|---|---|---|---|---|
| Paseo | 4.8M | ★ Top | 33M | ★ Top |
| VTT | 3.0M | ↑ Sobre promedio | 20.5M | ↑ Sobre promedio |
| Amarilla | 2.8M | ↓ Bajo promedio | 17.7M | ↓ Bajo promedio |
| Velo | 2.3M | ↓ Bajo promedio | 18.2M | ↓ Bajo promedio |
| Montana | 2.1M | ↓ Bajo promedio | 15.4M | ↓ Bajo promedio |
| Carretera | 1.8M | ⚠ Rezagado | 13.8M | ↓ Bajo promedio |
Lo que resuelve: el mismo corte estadístico que tomaría una docena de medidas hardcodeadas se generaliza en una función + dos medidas de una línea, aplicable a cualquier métrica y cualquier dimensión del modelo.
11. DAX Lib y recursos de la comunidad
DAX Lib — lanzado por Marco Russo (SQLBI) en diciembre 2025: repositorio comunitario de UDFs reutilizables distribuidas como scripts TMDL.
Cómo importar una función de DAX Lib:
- Copia el script TMDL desde daxlib.org
- Pégalo en la vista TMDL de tu modelo en Power BI Desktop
- Guarda y listo
Alternativa con Tabular Editor 3: usa el DAX Package Manager para importar funciones de DAX Lib de forma automatizada.
Otros repositorios y referencias:
- Dashboard-Design/Power-BI-UDF-Library — colección de UDFs en GitHub
- evaluationcontext.github.io — UDF Colours — visualización del comportamiento VAL/EXPR
- Gist comunidad (eugman) — ejemplos prácticos
Fuentes
Esta referencia está construida sobre las siguientes fuentes primarias, verificadas independientemente:
- Power BI UDFs — Overview (Microsoft Learn)
- DAX FUNCTION Statement (Microsoft Learn)
- Best Practices: DAX UDFs (Microsoft Learn)
- TABLEOF Function (Microsoft Learn)
- NAMEOF Function (Microsoft Learn)
- Understanding parameter types in DAX UDFs — SQLBI
- Introducing User-Defined Functions in DAX — SQLBI
- Model-Dependent vs Model-Independent UDFs — SQLBI
- Introducing DAX Lib — SQLBI (Marco Russo)
- DAX UDFs en términos simples — Tabular Editor Blog
- 8 tips para escribir UDFs en DAX — Tabular Editor Blog
- MicrosoftDocs/query-docs — GitHub
- DAX Naming Conventions — docs.sqlbi.com
- DAX Lib — Add to Power BI (docs.daxlib.org)
- Proyecto PBIP de este artículo — GitHub (CSalcedoDataBI/powerbi-pbip-tools)
¿Te resultó útil este artículo?
Tu apoyo me permite seguir creando contenido de calidad.