Inteligencia de Tiempo
TOTALYTD, SAMEPERIODLASTYEAR, DATESINPERIOD, PARALLELPERIOD y las 40 funciones para comparativas temporales — la categoría más poderosa de DAX
Antes de empezar: la tabla de calendario marcada
Este es el prerrequisito más importante de toda la inteligencia de tiempo. Sin él, nada funciona.
Para que las funciones de inteligencia de tiempo funcionen, NECESITAS obligatoriamente:
- Debe tener una columna de tipo fecha continua (sin huecos)
- Debe cubrir todo el rango de fechas de tus datos
- Debe tener UNA FILA por cada día
Calendario =
ADDCOLUMNS(
CALENDARAUTO(),
"Año", YEAR([Date]),
"Mes", MONTH([Date]),
"Trimestre", QUARTER([Date]),
"Año-Mes", FORMAT([Date], "YYYY-MM")
)
- Seleccionar la tabla Calendario en el panel de campos
- Clic derecho → "Marcar como tabla de fechas"
- Seleccionar la columna de fecha como columna de fecha única
- ✅ Aparecerá un icono de calendario junto a la tabla
- La columna de fecha de tus ventas → columna Date del Calendario
- Relación de muchos a uno
- Dirección de filtro: de Calendario hacia Ventas
¿Por qué es obligatorio?
Cuando Power BI ve que una tabla está marcada como tabla de fechas:
- Desactiva la jerarquía de fechas automática
- Permite que las funciones de Time Intelligence usen esa tabla correctamente
- Garantiza que el contexto de filtro funcione bien con los períodos
// ❌ Esta medida puede dar resultados incorrectos o BLANK
Ventas YTD = TOTALYTD(SUM('Ventas'[Importe]), 'Ventas'[Fecha])
// Usas la columna de fecha directamente de la tabla de hechos
// ✅ Correcto:
Ventas YTD = TOTALYTD(SUM('Ventas'[Importe]), 'Calendario'[Date])
// Usas la tabla de calendario marcada
El 90% de las veces es porque:
- No tienes tabla de calendario marcada
- La relación entre Ventas y Calendario no existe o está inactiva
- El rango de fechas del calendario no cubre todas las fechas de los datos
Totales acumulados — Year-to-Date, Quarter-to-Date, Month-to-Date
YTD (Year-to-Date) significa "desde el inicio del año hasta hoy". QTD = desde el inicio del trimestre. MTD = desde el inicio del mes. Son las métricas más usadas en cualquier cuadro de mando: "¿Cómo vamos en lo que llevamos del año?"
Ejemplo básico:
// ✅ Ventas acumuladas en el año (desde 1 enero hasta la fecha del contexto)
Ventas YTD = TOTALYTD([Total Ventas], 'Calendario'[Date])
// En un visual de línea mes a mes:
// Enero: ventas de enero
// Febrero: ventas de enero + febrero
// Marzo: ventas de enero + febrero + marzo
// ... (siempre acumulando desde el 1 de enero)
Con año fiscal:
// ✅ Con año fiscal que termina el 31 de marzo
Ventas YTD Fiscal = TOTALYTD([Total Ventas], 'Calendario'[Date], "3/31")
// El "año" va del 1 de abril al 31 de marzo
Con filtro adicional:
// ✅ Con filtro adicional
Ventas YTD Electrónica =
TOTALYTD(
[Total Ventas],
'Calendario'[Date],
'Productos'[Categoría] = "Electrónica"
)
// ✅ Equivalente usando DATESYTD (más flexible)
Ventas YTD v2 = CALCULATE([Total Ventas], DATESYTD('Calendario'[Date]))
Diferencia entre TOTAL... y DATES...:
// TOTALYTD es un atajo para:
CALCULATE([Total Ventas], DATESYTD('Calendario'[Date]))
// Ambas dan el mismo resultado — elige la que prefieras leer
// ✅ La ventaja de DATES... es que puedes combinarlo con otras cosas
Margen YTD = CALCULATE([Total Margen], DATESYTD('Calendario'[Date]))
Nº Clientes YTD = CALCULATE([Clientes Únicos], DATESYTD('Calendario'[Date]))
// Reutilizas el mismo patrón para cualquier medida
// ✅ ¿Cómo vamos en lo que llevamos del trimestre?
Ventas QTD = TOTALQTD([Total Ventas], 'Calendario'[Date])
// ✅ ¿Cómo vamos en lo que llevamos del mes?
Ventas MTD = TOTALMTD([Total Ventas], 'Calendario'[Date])
// Tabla resumen de todos los acumulados:
// Medida | Ene | Feb | Mar | Q1 Total | Año Total
// Ventas MTD | 10K | 12K | 8K | |
// Ventas QTD | 10K | 22K | 30K | 30K |
// Ventas YTD | 10K | 22K | 30K | 30K | (en Q1 es lo mismo que QTD)
Comparar con el mismo período del año / trimestre / mes anterior
Ejemplos básicos:
// ✅ Ventas del mismo período del año anterior
Ventas Año Anterior = CALCULATE([Total Ventas], SAMEPERIODLASTYEAR('Calendario'[Date]))
// ✅ Variación absoluta vs año anterior
Variación YoY = [Total Ventas] - [Ventas Año Anterior]
// ✅ Variación porcentual vs año anterior
Variación YoY % = DIVIDE([Total Ventas] - [Ventas Año Anterior], [Ventas Año Anterior])
KPI completo con formato:
// ✅ Con formato bonito para mostrar
KPI YoY =
VAR Actual = [Total Ventas]
VAR Anterior = [Ventas Año Anterior]
VAR Var = DIVIDE(Actual - Anterior, Anterior)
RETURN
FORMAT(Actual, "#,##0 €") & " (" & FORMAT(Var, "+0.0%;-0.0%") & " vs año ant.)"
// ✅ Funciona también con YTD — comparativa YTD vs YTD año anterior
Ventas YTD Año Anterior =
CALCULATE(
[Total Ventas],
SAMEPERIODLASTYEAR(DATESYTD('Calendario'[Date]))
)
Diferencia clave SAMEPERIODLASTYEAR vs PREVIOUSYEAR:
// Si el contexto es Q1 2024 (enero, febrero, marzo):
SAMEPERIODLASTYEAR → Q1 2023 (mismo trimestre, año anterior)
PREVIOUSYEAR → todo 2023 (el año anterior completo)
// Si el contexto es marzo 2024:
PREVIOUSMONTH → febrero 2024 (el mes anterior completo)
SAMEPERIODLASTYEAR → marzo 2023 (el mismo mes, año anterior)
Ejemplos prácticos:
// ✅ Ventas del mes anterior (para comparativa MoM)
Ventas Mes Anterior = CALCULATE([Total Ventas], PREVIOUSMONTH('Calendario'[Date]))
// ✅ Variación MoM (Month-over-Month)
Variación MoM = [Total Ventas] - [Ventas Mes Anterior]
Variación MoM % = DIVIDE([Total Ventas] - [Ventas Mes Anterior], [Ventas Mes Anterior])
// ✅ Ventas del trimestre anterior
Ventas Q Anterior = CALCULATE([Total Ventas], PREVIOUSQUARTER('Calendario'[Date]))
// ✅ Ventas del año anterior completo
Ventas Año Anterior Completo = CALCULATE([Total Ventas], PREVIOUSYEAR('Calendario'[Date]))
Mirar al futuro: NEXT...
// ✅ ¿Cuál es el presupuesto del mes que viene?
Presupuesto Mes Siguiente = CALCULATE(SUM('Presupuesto'[Importe]), NEXTMONTH('Calendario'[Date]))
// ✅ Diferencia entre lo que llevamos del año y lo que viene el año próximo
Gap vs Próximo Año = [Total Ventas] - CALCULATE([Total Ventas], NEXTYEAR('Calendario'[Date]))
Las funciones NEXT... tienen sentido principalmente cuando tu tabla de datos incluye datos futuros (presupuestos, forecasts). Si solo tienes datos históricos, NEXT... devolverá BLANK porque no hay datos en el futuro.
DATEADD y PARALLELPERIOD — mover el tiempo con precisión
// ✅ Las mismas fechas de hace 1 año (equivalente a SAMEPERIODLASTYEAR)
DATEADD('Calendario'[Date], -1, YEAR)
// ✅ Las mismas fechas de hace 3 meses
DATEADD('Calendario'[Date], -3, MONTH)
// ✅ El mismo día de la semana anterior
DATEADD('Calendario'[Date], -7, DAY)
// ✅ Ventas hace 2 trimestres
Ventas Hace 2Q = CALCULATE([Total Ventas], DATEADD('Calendario'[Date], -2, QUARTER))
// ✅ Comparativa con hace N períodos (dinámico con slicer)
Ventas Período Anterior =
VAR N = SELECTEDVALUE('Selector Períodos'[Valor], 1)
RETURN
CALCULATE([Total Ventas], DATEADD('Calendario'[Date], -N, MONTH))
Diferencia DATEADD vs PARALLELPERIOD:
// Si el contexto es del 1 al 15 de marzo (media quincena):
DATEADD('Calendario'[Date], -1, MONTH)
→ del 1 al 15 de FEBRERO (mismas fechas, mes anterior)
PARALLELPERIOD('Calendario'[Date], -1, MONTH)
→ TODO FEBRERO (el mes completo anterior)
// Cuándo usar cada una:
// DATEADD: cuando quieres comparar exactamente el mismo período
// PARALLELPERIOD: cuando quieres el período completo
// (ej: comparar con el mes anterior completo aunque el mes actual esté a medias)
En un gráfico de líneas mes a mes, si el mes actual solo tiene 15 días:
• DATEADD → compara esos 15 días con los primeros 15 del mes anterior (justo)
• PARALLELPERIOD → compara esos 15 días con el mes anterior completo (puede ser engañoso)
Para cuadros de mando ejecutivos donde no quieres medias partidas, usa PARALLELPERIOD.
Para análisis precisos día a día, usa DATEADD.
DATESINPERIOD: Devuelve fechas para un período de N intervalos a partir de una fecha de inicio. Perfecta para ventanas móviles (rolling windows).
// ✅ DATESBETWEEN — Ventas en un rango de fechas específico
Ventas Q1 2024 =
CALCULATE(
[Total Ventas],
DATESBETWEEN('Calendario'[Date], DATE(2024,1,1), DATE(2024,3,31))
)
// ✅ Ventas en los últimos 30 días exactos
Ventas Últimos 30d =
CALCULATE(
[Total Ventas],
DATESBETWEEN('Calendario'[Date], TODAY() - 30, TODAY())
)
// ✅ DATESINPERIOD — Últimos 12 meses desde la fecha máxima del contexto
Ventas Últimos 12M =
CALCULATE(
[Total Ventas],
DATESINPERIOD('Calendario'[Date], MAX('Calendario'[Date]), -12, MONTH)
)
// ✅ Media móvil de 3 meses (rolling average)
Media Móvil 3M =
CALCULATE(
AVERAGE('Ventas'[Importe]),
DATESINPERIOD('Calendario'[Date], MAX('Calendario'[Date]), -3, MONTH)
)
Saldos de apertura y cierre — para contabilidad y stock
Estas funciones son esenciales para análisis financieros y de inventario donde necesitas saber el valor al INICIO o al FINAL de un período, no el total acumulado. Piensa en el saldo de una cuenta bancaria: no quieres la suma de todos los movimientos, quieres el saldo al principio y al final del mes.
CLOSINGBALANCE...: evalúa la expresión en el último día del período ACTUAL (= el saldo de cierre)
Variantes: MONTH, QUARTER, YEAR
// ✅ Stock al inicio del mes (lo que había el último día del mes anterior)
Stock Apertura Mes =
OPENINGBALANCEMONTH(
LASTNONBLANK('Inventario'[Fecha], SUM('Inventario'[Stock])),
'Calendario'[Date]
)
// ✅ Stock al cierre del mes
Stock Cierre Mes =
CLOSINGBALANCEMONTH(
SUM('Inventario'[Stock]),
'Calendario'[Date]
)
// ✅ Saldo bancario al cierre del trimestre
Saldo Cierre Q =
CLOSINGBALANCEQUARTER(
SUM('Cuentas'[Saldo]),
'Calendario'[Date]
)
// ✅ Saldo al cierre del año fiscal (que termina en marzo)
Saldo Cierre Año Fiscal =
CLOSINGBALANCEYEAR(
SUM('Cuentas'[Saldo]),
'Calendario'[Date],
"3/31"
)
La diferencia entre CLOSINGBALANCE y un simple MAX de fecha es que CLOSING... entiende el concepto de "período". Si el contexto es el trimestre Q1, CLOSINGBALANCEMONTH en marzo da el saldo del 31 de marzo, no el máximo global de todo el modelo.
FIRSTDATE, LASTDATE y variantes — encontrar fechas clave
Tabla comparativa
| Función | Qué devuelve | Usa contexto |
|---|---|---|
FIRSTDATE |
Tabla con la primera fecha del contexto | Sí |
LASTDATE |
Tabla con la última fecha del contexto | Sí |
FIRSTNONBLANK |
Primera fecha donde la expresión no es BLANK | Sí |
LASTNONBLANK |
Última fecha donde la expresión no es BLANK | Sí |
FIRSTNONBLANKVALUE |
Valor de la expresión en la primera fecha no BLANK | Sí |
LASTNONBLANKVALUE |
Valor de la expresión en la última fecha no BLANK | Sí |
// ✅ Saldo al último día del período visible
Saldo Última Fecha = CALCULATE(SUM('Cuentas'[Saldo]), LASTDATE('Calendario'[Date]))
// ✅ Valor al primer día del año actual
Stock Inicio Año = CALCULATE(SUM('Inventario'[Stock]), FIRSTDATE(DATESYTD('Calendario'[Date])))
// ✅ Última fecha con ventas registradas (no el último día del calendario)
Última Fecha con Ventas = LASTNONBLANK('Calendario'[Date], CALCULATE(COUNTROWS('Ventas')))
// ✅ Último precio registrado de un producto
Último Precio = LASTNONBLANKVALUE('Calendario'[Date], MAX('Precios'[Precio]))
// ✅ Stock actual (el de la fecha más reciente con datos)
Stock Actual = LASTNONBLANKVALUE('Calendario'[Date], SUM('Inventario'[Stock]))
Todos los períodos
// ✅ Total de todos los años (sin filtro de fecha)
Total Histórico = CALCULATE([Total Ventas], ALL('Calendario'))
// ✅ Porcentaje sobre el total histórico
% del Total Histórico = DIVIDE([Total Ventas], CALCULATE([Total Ventas], ALL('Calendario')))
// ✅ Total del año completo (aunque el contexto sea un solo mes)
Total Año Completo =
CALCULATE(
[Total Ventas],
DATESYTD(LASTDATE('Calendario'[Date]))
)
Funciones de semana (WEEK)
Las funciones de semana permiten comparar datos con la semana anterior, la siguiente, o acumular desde el inicio de la semana actual. Menos usadas que mes y año, pero muy útiles en análisis operativos y logísticos donde la semana es la unidad natural de planificación.
Ventas de la semana anterior:
// Ventas de la semana anterior:
Ventas Semana Anterior =
CALCULATE(
[Total Ventas],
PREVIOUSWEEK('Calendario'[Fecha])
)
Diferencia semana actual vs anterior:
Diferencia Semanal =
VAR SemanaActual = [Total Ventas]
VAR SemanaAnt = CALCULATE([Total Ventas], PREVIOUSWEEK('Calendario'[Fecha]))
RETURN
IF(
ISBLANK(SemanaAnt),
BLANK(),
SemanaActual - SemanaAnt
)
Pedidos registrados para la próxima semana:
// Pedidos registrados para la próxima semana:
Pedidos Próxima Semana =
CALCULATE(
COUNTROWS(Pedidos),
NEXTWEEK('Calendario'[Fecha])
)
% de variación previsto semana siguiente:
Crecimiento Previsto Semanal =
VAR VentasActual = [Total Ventas]
VAR VentasSiguiente = CALCULATE([Total Ventas], NEXTWEEK('Calendario'[Fecha]))
RETURN
DIVIDE(VentasSiguiente - VentasActual, VentasActual)
fin_semana indica qué día es el último de la semana
("0" = sábado, "1" = domingo…).
Ventas acumuladas desde el inicio de la semana:
// Ventas acumuladas desde el inicio de la semana actual:
Ventas WTD =
TOTALWTD(
[Total Ventas],
'Calendario'[Fecha]
)
// WTD con semana que termina el domingo (lunes a domingo):
Ventas WTD (Lun-Dom) =
TOTALWTD(
[Total Ventas],
'Calendario'[Fecha],
"0"
)
Variación WTD vs WTD semana anterior:
WTD vs WTD Anterior =
VAR WTD_Actual = [Ventas WTD]
VAR WTD_Anterior = CALCULATE([Ventas WTD], PREVIOUSWEEK('Calendario'[Fecha]))
RETURN DIVIDE(WTD_Actual - WTD_Anterior, WTD_Anterior)
STARTOF — Primer día del período
Las funciones STARTOF devuelven el primer día del período que contiene la fecha del contexto actual. Devuelven un valor escalar de tipo fecha (no una tabla), lo que las hace perfectas dentro de cálculos, columnas calculadas o como argumento de otras funciones DAX.
fin_semana permite ajustar qué día es el último (y por tanto cuál es el primero).
Primer día de la semana y usos habituales:
// Primer día de la semana actual (valor escalar):
Inicio Semana Actual =
STARTOFWEEK('Calendario'[Fecha])
// Columna calculada en la tabla Calendario
// (útil para agrupar días por semana en visuales):
'Calendario'[Inicio de Semana] =
STARTOFWEEK('Calendario'[Fecha])
// Ventas desde el inicio de la semana (WTD manual con control total):
Ventas Desde Inicio Semana =
CALCULATE(
[Total Ventas],
DATESBETWEEN(
'Calendario'[Fecha],
STARTOFWEEK('Calendario'[Fecha]),
LASTDATE('Calendario'[Fecha])
)
)
// Primer día del mes actual:
Inicio Mes Actual =
STARTOFMONTH('Calendario'[Fecha])
// MTD manual con control total del rango:
Ventas MTD Manual =
CALCULATE(
[Total Ventas],
DATESBETWEEN(
'Calendario'[Fecha],
STARTOFMONTH(LASTDATE('Calendario'[Fecha])),
LASTDATE('Calendario'[Fecha])
)
)
// Días transcurridos desde inicio de mes:
Días Transcurridos Mes =
DATEDIFF(
STARTOFMONTH(TODAY()),
TODAY(),
DAY
) + 1
// Primer día del trimestre actual:
Inicio Trimestre Actual =
STARTOFQUARTER('Calendario'[Fecha])
// QTD manual:
Ventas QTD Manual =
CALCULATE(
[Total Ventas],
DATESBETWEEN(
'Calendario'[Fecha],
STARTOFQUARTER(LASTDATE('Calendario'[Fecha])),
LASTDATE('Calendario'[Fecha])
)
)
// Comparar trimestre actual vs mismo período año anterior:
Ventas Mismo Período Q Año Anterior =
VAR InicioQ = STARTOFQUARTER(LASTDATE('Calendario'[Fecha]))
VAR InicioQAnt = DATE(YEAR(InicioQ) - 1, MONTH(InicioQ), DAY(InicioQ))
VAR FinQAnt = LASTDATE(SAMEPERIODLASTYEAR('Calendario'[Fecha]))
RETURN
CALCULATE(
[Total Ventas],
DATESBETWEEN('Calendario'[Fecha], InicioQAnt, FinQAnt)
)
fin_año define el último día del año fiscal (por ejemplo "06-30" si tu año fiscal termina el 30 de junio).
Sin parámetro, usa el año natural (1 de enero).
Año natural y YTD manual:
// Primer día del año natural:
Inicio Año Actual =
STARTOFYEAR('Calendario'[Fecha])
// → devuelve 01/01/[año actual]
// YTD manual con control total:
Ventas YTD Manual =
CALCULATE(
[Total Ventas],
DATESBETWEEN(
'Calendario'[Fecha],
STARTOFYEAR(LASTDATE('Calendario'[Fecha])),
LASTDATE('Calendario'[Fecha])
)
)
Con año fiscal y días transcurridos:
// Con año fiscal que termina el 30 de junio:
Inicio Año Fiscal =
STARTOFYEAR('Calendario'[Fecha], "06-30")
// Si estás en noviembre 2024 → devuelve 01/07/2024
// Días del año transcurridos:
Días del Año Transcurridos =
DATEDIFF(
STARTOFYEAR(TODAY()),
TODAY(),
DAY
) + 1
Los 10 patrones de Time Intelligence que usarás en casi todos los proyectos
Ventas YoY =
VAR Actual = [Total Ventas]
VAR Anterior = CALCULATE([Total Ventas], SAMEPERIODLASTYEAR('Calendario'[Date]))
VAR Variacion = DIVIDE(Actual - Anterior, Anterior)
RETURN
IF(
NOT ISBLANK(Anterior),
FORMAT(Actual, "#,##0 €") & " (" & FORMAT(Variacion, "+0.0%;-0.0%") & " vs AA)",
FORMAT(Actual, "#,##0 €") & " (sin dato año anterior)"
)
Ventas YTD = TOTALYTD([Total Ventas], 'Calendario'[Date])
Ventas YTD AA = CALCULATE([Ventas YTD], SAMEPERIODLASTYEAR('Calendario'[Date]))
Crecimiento YTD = DIVIDE([Ventas YTD] - [Ventas YTD AA], [Ventas YTD AA])
Media Móvil 3M =
CALCULATE(
AVERAGEX(
VALUES('Calendario'[Año-Mes]),
CALCULATE([Total Ventas])
),
DATESINPERIOD('Calendario'[Date], LASTDATE('Calendario'[Date]), -3, MONTH)
)
Ventas MoM =
VAR Actual = [Total Ventas]
VAR MesAnterior = CALCULATE([Total Ventas], PREVIOUSMONTH('Calendario'[Date]))
RETURN
DIVIDE(Actual - MesAnterior, MesAnterior)
Record Histórico =
CALCULATE(
MAXX(VALUES('Calendario'[Año-Mes]), CALCULATE([Total Ventas])),
ALL('Calendario')
)
Es Record = IF([Total Ventas] >= [Record Histórico], "🏆 RÉCORD", "")
Ventas YTD Hasta Hoy =
CALCULATE(
[Total Ventas],
DATESYTD('Calendario'[Date]),
'Calendario'[Date] <= TODAY()
)
Semáforo YTD =
VAR CumplimientoYTD = DIVIDE([Ventas YTD], [Presupuesto YTD])
RETURN
SWITCH(
TRUE(),
CumplimientoYTD >= 1.00, "🟢 En objetivo",
CumplimientoYTD >= 0.90, "🟡 Cerca del objetivo",
"🔴 Por debajo del objetivo"
)
Días Transcurridos = DATEDIFF(DATE(YEAR(TODAY()), 1, 1), TODAY(), DAY)
Días Restantes Año = DATEDIFF(TODAY(), DATE(YEAR(TODAY()), 12, 31), DAY)
% Año Transcurrido = DIVIDE([Días Transcurridos], 365)
Forecast Mes =
VAR TasaCrecimiento = DIVIDE([Ventas YoY], CALCULATE([Total Ventas], PREVIOUSYEAR('Calendario'[Date])))
VAR BaseAnoAnterior = CALCULATE([Total Ventas], SAMEPERIODLASTYEAR('Calendario'[Date]))
RETURN
IF(ISBLANK([Total Ventas]), BaseAnoAnterior * (1 + TasaCrecimiento), [Total Ventas])
Rolling 12M =
CALCULATE(
[Total Ventas],
DATESINPERIOD('Calendario'[Date], LASTDATE('Calendario'[Date]), -12, MONTH)
)
¡La inteligencia de tiempo ya es tuya! Con estos 10 patrones puedes construir prácticamente cualquier análisis temporal que un cuadro de mando ejecutivo pueda necesitar. Ahora vamos con las funciones de Relaciones — menos glamurosas pero absolutamente fundamentales para modelos de datos avanzados.