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ísDSTAproximadamente
MéxicoSí (mayoría)Mar/Abr → Oct/Nov
EspañaMar → Oct
ChileSep → Abr
ParaguayOct → Mar
Resto LATAMNo

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:

  1. Manualmente verifica que workflows críticos siguen configurados con timezone correcta
  2. 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 $now en 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