Módulo 5: Scheduling y Tiempo
DST y Edge Cases
Descripción de la cápsula
Daylight Saving Time (DST), cambios de año, transiciones entre meses, y otros casos especiales pueden romper workflows que funcionan perfecto el 99% del tiempo. Esta cápsula cubre los edge cases que importan en producción: qué pasa exactamente cuando ocurre DST, cómo manejar el "29 de febrero", workflows que cruzan medianoche, y los patrones defensivos para hacer tu scheduling robusto.
Lo que vas a aprender
- ✅ Entender DST y cómo afecta workflows
- ✅ Manejar transiciones (cambio de año, mes, día)
- ✅ Diseñar workflows resilientes a edge cases
- ✅ Aplicar patrones defensivos anti-edge-case
¿Qué es DST exactamente?
Daylight Saving Time = adelantar reloj 1 hora en primavera, retrasar 1 hora en otoño. Diseñado para aprovechar luz solar.
Qué pasa en el cambio
Spring forward (primavera):
- A las 2am, el reloj salta a las 3am
- La hora "2:30am" no existe ese día
- El día tiene 23 horas
Fall back (otoño):
- A las 2am, el reloj vuelve a 1am
- La hora "1:30am" ocurre dos veces ese día
- El día tiene 25 horas
Países LATAM/España con DST
| País | DST | Aproximadamente |
|---|---|---|
| México | Sí (mayoría) | Mar/Abr → Oct/Nov |
| España | Sí | Mar → Oct |
| Chile | Sí | Sep → Abr |
| Paraguay | Sí | Oct → Mar |
| Resto LATAM | No | — |
Impacto de DST en workflows
Caso 1: Schedule durante el cambio
Tu schedule es 0 2 * * * (2am diario, hora local) durante DST forward day:
- Hora 2am no existe ese día
- ¿Qué hace n8n? Depende de la versión:
- Algunas: saltan la ejecución
- Otras: ejecutan a las 3am
- Otras: ejecutan a las 1am (antes del cambio)
Resultado: comportamiento impredecible 2 días al año.
Caso 2: Schedule durante DST backward
Tu schedule es 30 1 * * * (1:30am diario) durante DST backward:
- 1:30am ocurre dos veces ese día
- ¿Workflow ejecuta 1 vez o 2?
Similar: depende de implementación.
Patrones defensivos
Pattern 1: Evitar 1am-3am en cron
Si tu workflow no es crítico que corra exactamente en esas horas, schedule a horas como 4am o 6am — fuera de la ventana DST. Sin sorpresas.
Pattern 2: Idempotencia
Si tu workflow podría ejecutar 2 veces el mismo día (DST backward), asegurar que ejecuciones duplicadas no causan daño:
- Verificar si ya se procesó (flag en Sheet)
- Operaciones idempotentes (INSERT IF NOT EXISTS)
- Si detecta segunda ejecución, terminar sin acción
Pattern 3: Logging del timestamp UTC
En tu Sheet de logs o tracking:
{{ DateTime.utc().toISO() }} // siempre UTC, no afectado por DST
UTC no tiene DST. Es estable.
Pattern 4: Test antes del cambio
Días antes del cambio DST conocido:
- Manualmente verifica que workflows críticos siguen configurados con timezone correcta
- Confirmar después del cambio que ejecutaron como esperabas
29 de febrero
Año bisiesto. Cada 4 años hay un 29 de febrero. Si tu cron es 0 9 29 2 *, solo ejecuta cada 4 años.
Implicación
- Workflows configurados para "todos los días del mes" no tienen issue
- Workflows configurados para "día 29-31 del mes" se ejecutan distinto en febrero
Fix
Si necesitas "último día del mes" (que en febrero es 28 o 29), usar lógica:
Cron: 0 9 28-31 * *
│
[IF: hoy === último día del mes actual]
{{ DateTime.now().endOf('month').day === DateTime.now().day }}
Cambio de año
A las 11:59pm del 31 de diciembre → 12:00am del 1 de enero. Cambia year, month, day. Trampa: queries SQL o expressions que usan year actual sin parametrizar bien.
Ejemplo bug
SELECT * FROM orders WHERE year = '2025'
Después del cambio, devuelve nada del nuevo año. Bug obvio en retro pero pasa.
Fix
{{ DateTime.now().year }} // siempre el año actual
Workflows que cruzan medianoche
Caso: workflow inicia a las 11:55pm. Tarda 10 minutos. Termina a las 12:05am del día siguiente.
Implicaciones
- "Fecha de hoy" cambia entre el inicio y el final
- Logs pueden quedar inconsistentes
- Si hay loops, items procesados ayer y hoy
Fix
- Capturar timestamp al inicio del workflow
- Usar ese timestamp consistentemente en todo el flow
- No re-calcular
$nowen cada paso
[Schedule]
│
[Set: execution_date = $now] ← capturar una vez
│
[procesamiento usa $json.execution_date, no $now]
Fin de mes con meses de diferentes tamaños
Cron 0 6 31 * * solo ejecuta en meses con 31 días (enero, marzo, mayo, julio, agosto, octubre, diciembre). Meses con 30 días o 28-29 (febrero): no ejecuta.
Fix
Para "último día del mes":
Cron: 0 6 28-31 * *
│
[IF: hoy === último día del mes]
Trampas comunes
Trampa 1: No verificar workflows después de DST
Qué pasa: DST cambia, no revisas. Algo se descalibra silenciosamente.
Cómo evitar: Recordatorio en calendar 1 día después de DST conocido: "Verificar workflows críticos".
Trampa 2: Asumir 24 horas en un día
Qué pasa: Cálculo endOfDay - startOfDay = 24 hours. En día DST forward, son 23. En backward, 25.
Cómo evitar: Usar funciones de Luxon que manejan eso (startOf('day'), endOf('day')) — calculan correctamente, no asumir 24.
Trampa 3: Strings de fecha sin year
Qué pasa: Tu workflow procesa "05-11" (5 de noviembre). Funciona en 2026. Llega 2027 y... aún funciona (porque no tiene year). Pero cuando comparas con $now, los años no coinciden.
Cómo evitar: Siempre incluir year en fechas. Sin year, hay ambigüedad.
Trampa 4: Workflow muy largo que cruza días
Qué pasa: Workflow tarda 8 horas. Inicia 8pm. Termina 4am del día siguiente. Logs confusos.
Cómo evitar: Capturar start_time al inicio. Usar consistentemente.
Resumen
- DST afecta 2 días al año en países con DST
- Spring forward: ventana 2am-3am no existe ese día
- Fall back: ventana 1am-2am ocurre 2 veces
- Patrones defensivos: evitar 1am-3am, idempotencia, log UTC, test antes
- 29 feb existe cada 4 años
- Cruzar medianoche: capturar timestamp al inicio
- 4 trampas: no verificar post-DST, asumir 24h, fechas sin year, workflows largos
Lo que sigue: Mini-proyecto integrador del módulo.
Creado: Mayo 11, 2026 Versión: 1.0