Guía9 min

Cron en UTC para un agente IA: Vercel, Workers y Actions

Resumen

Cómo no mandar el digest a las 3 AM en Guatemala: crons de Vercel, Cloudflare y GitHub Actions corren en UTC. America/Guatemala es UTC−6 todo el año, sin DST. Convierte 9:00 CST a 15:00 UTC y apaga el cron en Preview.

VercelCloudflareGitHub
Reloj UTC frente a un horario local de Guatemala para un cron de agente

Qué resuelve

Esta pieza se queda en la decisión práctica: qué instalar, qué riesgo agrega y cómo aplicarlo sin romper operación.

El cron no habla español. Habla UTC. Si pones 0 9 * * * creyendo 9 AM en Puerto Barrios, el digest sale a las 3 AM local (UTC−6). Kill switch apaga el canal. Esta guía cubre a qué hora dispara. Fuentes oficiales consultadas el 3 de septiembre de 2026.

Guatemala no tiene DST. America/Guatemala = UTC−6 todo el año. No copies un snippet de America/New_York.

La regla

Escribe el cron en UTC. Documenta la hora local al lado. Preview sin cron de prod (preview). Workers: cron solo en el env de prod.

9:00 CST → 0 15 * * * (15:00 UTC).

Vercel

Docs de Cron Jobs (consultadas hoy): la zona horaria del cron es siempre UTC. Vercel dispara un HTTP GET a la URL de production con el path de vercel.json. User-Agent vercel-cron/1.0. Header x-vercel-cron-schedule con la expresión (ejemplo de la doc 0 5 * * *). El handler es un Route Handler; maxDuration tiene que caber el turno (CPU). Un cron a las 15:00 UTC que tarda 4 min no es “9:04 AM”: es wall clock del Function.

No pongas el cron en Preview: cada PR dispara el digest. Production only. Si el GET llega a un preview, el digest sale dos veces.

Workers

Docs de Cron Triggers: el disparo va en hora UTC. Handler scheduled(). Limits (cuenta, Free vs Paid): 5 Cron Triggers en Free, 250 en Paid. CPU time por Cron Trigger: Free 10 ms; Paid 30 s si el intervalo es menor a 1 h, 15 min si el intervalo es de 1 h o más. [triggers].crons en el Worker de prod, no en my-worker-dev. El mismo digest a clientes desde dev es un incidente.

GitHub Actions

Docs de events: POSIX cron. Por defecto el schedule corre en UTC. Intervalo mínimo: una vez cada 5 minutos. Opcional: timezone: con string IANA. Ejemplo de la doc: cron: '30 5 * * 1-5' + timezone: "America/New_York". Guatemala no tiene DST; si copias ese snippet, cambia a America/Guatemala o deja UTC y suma 6. TZ=America/Guatemala en un step no mueve el schedule: solo la hora dentro del script.

Actions sirve para jobs que no caben en Workers Free. No para “cada minuto”: el techo documentado es 5 min.

Tabla

Conversión de 9 AM Guatemala a 15:00 UTC

Querías (CST)Cron UTCPlataforma (docs 2026-09-03)
09:000 15 * * *Vercel UTC / Workers UTC / Actions UTC
21:000 3 * * *al día siguiente UTC
cada 15 min*/15 * * * *Actions: techo 5 min; Workers Free: 5 crons
una vez domingo 90 15 * * 0POSIX: 0 = domingo
09:00 IANA0 9 * * * + timezone: America/Guatemalasolo Actions; Vercel/Workers no

Errores comunes

Digest a las 3 AM porque el cron estaba en UTC mal leído

SíntomaCausaFix
Sale 6 h tempranocron pensado en CST+6 al hour UTC
Preview spameacron en todos los deployssolo Production / --env
Worker Free 1102digest pesado en 10 ms CPUPaid o Actions
Actions “TZ=Guatemala” y sigue malTZ no mueve el scheduleUTC en cron:
Domingo vs lunes0 vs 1POSIX: 0 domingo

Relación con el resto

Checklist

  • Tabla CST → UTC en el README del agente
  • Cron solo en Production
  • maxDuration / CPU de cron ≥ un turno
  • Ensayo: un cron de prueba a +10 min UTC, luego bórralo
  • Guatemala sin DST: no “ajusta en marzo”
  • Workers: cron no está en dev

FAQ

¿CRON_TZ en Vercel? No. La doc fija UTC y no hay override.

¿timezone: en Actions? Sí, IANA. En Vercel y Workers, no. Tres runtimes, tres reglas.

¿Fly/Railway? Un cron del sistema o un scheduler. TZ=America/Guatemala en el contenedor afecta a cron de Linux. No mezcles eso con vercel.json.

¿9 AM “hora México”? México tiene DST en algunas zonas. Guatemala no. Docs de Actions avisan: en spring-forward, un job a las 2:30 salta a las 3:00. En Guatemala ese aviso no aplica. No copies la tabla de America/New_York.

El ensayo: programa un ping a UTC ahora+10 min, mira el log con timestamp UTC y CST. Si no coinciden con +6, el cron está mal.

Si el job es “cada hora en horario de oficina CST”, no uses 24 slots. Usa 15–21 UTC (0 15-21 * * *) y listo. Fuera de esa ventana el agente duerme: menos factura y menos sorpresas.

Una sola fuente de verdad: un comentario // 09:00 America/Guatemala = 15:00 UTC junto al cron. Si el comentario y la expresión no cuadran, gana UTC y corriges el comentario. No “arregles” a ojo a las 2 AM.

Vercel no dispara el cron contra Preview: el GET va a production. Si ves invocaciones en un PR, no es el scheduler de Vercel; es un test o un rewrite. Workers sí puede tener cron en un env dev si lo declaraste ahí: revísalo en wrangler.

Fly/Railway con cron de Linux dentro del contenedor: ahí sí TZ. Vercel/Workers/Actions: nunca. Mezclar los tres en un mismo repo sin etiquetar cuál es cuál es el bug.

Siguiente paso: si la hora ya es correcta y el job no corre, logs y healthchecks. Sin runtime: curso.