Cómo leer una expresión cron sin morir en el intento
El formato estándar tiene cinco campos separados por espacios: minuto hora día-mes mes día-semana. Por ejemplo, 0 9 * * 1-5 se lee de izquierda a derecha: minuto 0, hora 9, cualquier día del mes, cualquier mes, días 1 a 5 de la semana (lunes a viernes). Resultado: 9 AM en días laborables.
Los caracteres especiales más usados son asterisco (*) que significa "cualquier valor", barra (/) para intervalos (*/15 = cada 15), coma (,) para listas (1,3,5 = lunes, miércoles, viernes), y guion (-) para rangos (9-17 = de 9 a 17). En cron extendido (Quartz) hay un sexto campo de segundos al inicio y caracteres extras como L (último) y # (n-ésimo).
Los días de la semana van de 0 a 6, donde 0 es domingo en la mayoría de implementaciones (incluyendo Linux cron y crontab). Algunos sistemas como Quartz cuentan 1-7 con 1=domingo. Eso causa el bug clásico: programás "todos los lunes" con * * * * 1 y termina ejecutándose los domingos. Verificá la documentación de tu sistema. cron-utils en Java tiene flags para cambiar entre dialectos.
Cron en distintos sistemas: las trampas más comunes
En Linux crontab, los trabajos corren con el shell del usuario y heredan PATH limitado. Si tu script usa node y crontab no encuentra node en su PATH, falla silenciosamente. Solución: usar paths absolutos (/usr/local/bin/node /home/user/script.js) o setear PATH=... al inicio del crontab.
En GitHub Actions, el cron acepta sintaxis estándar pero solo se ejecuta en el branch default. schedule: - cron: '0 9 * * 1-5' dispara workflow a las 9 UTC, no en tu zona horaria. GitHub corre todo en UTC sin opción de cambiarlo. Para 9 AM Argentina (UTC-3), programá 0 12 * * 1-5. Otra trampa: GitHub puede retrasar ejecuciones bajo carga, así que cron crítico no es ideal allí.
En Kubernetes CronJob, el campo schedule usa cron estándar pero también respeta timeZone (desde k8s 1.27). Antes de eso, era todo UTC. Otra particularidad: concurrencyPolicy: Forbid evita que un job se solape con su instancia anterior si corre largo. Para tareas que pueden correr más de un minuto en cron de cada minuto, esto es crítico para evitar runaway jobs.
Errores típicos y cómo evitarlos
Error #1: confundir día del mes con día de la semana. 0 9 1 * 1 NO es "los lunes 1 del mes". Es "día 1 del mes O cualquier lunes" (operador OR implícito). Si querés el primer lunes específicamente, usá 0 9 * * 1#1 en cron extendido o calculalo en código.
Error #2: asumir que */N divide exactamente. */7 * * * * no es "cada 7 minutos" en sentido estricto: ejecuta en 0, 7, 14, 21, 28, 35, 42, 49, 56 y vuelve a 0 al cambiar de hora, dejando 4 minutos de gap entre 56 y 0 siguiente. Si necesitás intervalos exactos, usá un scheduler con duración configurable, no cron.
Error #3: scheduled tasks que dependen de zonas horarias con DST. Una tarea programada a las 2:30 AM puede ejecutarse dos veces o ninguna en cambios de hora. Linux cron en sistemas con TZ local tiene este bug. Solución: usar TZ=UTC en crontab para tareas críticas, o usar systemd timers que manejan DST correctamente. Atlassian, Stripe y GitHub tuvieron incidentes públicos por esto.
Atajos cron y alternativas modernas
Cron tiene atajos predefinidos que muchos no conocen: @yearly (= 0 0 1 1 *), @monthly, @weekly, @daily, @hourly y @reboot. Son legibles pero menos flexibles. @reboot ejecuta una vez al iniciar el sistema, útil para scripts de bootstrap pero peligroso si tu sistema reinicia frecuentemente.
Para casos donde cron se queda corto, hay alternativas. systemd timers en Linux modernos permiten persistencia (re-ejecutar si el sistema estaba apagado), randomización (RandomizedDelaySec evita thundering herd) y dependencias entre tareas. Apache Airflow y Prefect manejan DAGs con dependencias, retries y observabilidad que cron nunca tuvo.
Para apps modernas, frameworks como BullMQ (Node), Celery (Python) y Sidekiq (Ruby) ofrecen scheduling con monitoring incorporado. Si tu cron empieza a tener 30+ tareas, lógica condicional o necesita observabilidad, mudate a uno de esos. Cron está pensado para tareas simples y aisladas. Slack, GitHub y Stripe usan combinaciones de cron + queue systems para que cron solo dispare workers, sin lógica de negocio.