Ir al contenido

Programaciones

Un Schedule describe qué plantilla enviarle a qué número, y cuándo.

// Puntual: se envía una vez en sendAt
{ "templateId": "...", "phone": "...", "variables": {}, "sendAt": "2026-01-01T15:00:00Z" }
// Recurrente: se recalcula nextRunAt tras cada envío según cronExpression
{ "templateId": "...", "phone": "...", "variables": {}, "recurring": true, "cronExpression": "0 9 * * 1", "timezone": "America/Lima" }

En modo recurrente, nextRunAt se recalcula con CronTime de la librería cron después de cada envío exitoso — no hay que recrear el schedule.

Estado Significado
pending Esperando su sendAt (o nextRunAt si es recurrente).
queued Ya recogido por el cron y esperando turno en la cola de envío de su sesión.
sent Enviado con éxito (solo aplica a no-recurrentes; los recurrentes vuelven a active/pending según corresponda).
failed Falló y agotó reintentos.
cancelled Cancelado manualmente (PATCH /:id/cancel).
active Recurrente, esperando su próxima ejecución.

Un cron corre cada minuto (SchedulerProcessor.processPendingSchedules) sobre todas las sesiones conectadas:

  1. Busca schedules pending (o active recurrentes) con su hora vencida.
  2. Los marca queued de inmediato — así el próximo tick del cron no los vuelve a recoger mientras esperan turno.
  3. Los encola en SendQueueService, que aplica el delay/jitter/cuota/validaciones anti-baneo antes de mandar de verdad (ver Envío masivo seguro).

Si el proceso se reinicia con schedules en queued (porque la cola vive en memoria), al arrancar se resetean automáticamente a pending para que no queden colgados.

Un envío fallido reintenta hasta 3 veces (MAX_RETRIES), incrementando retryCount y volviendo a pending. Al agotar los reintentos pasa a failed con el error guardado.

Si el motivo del rechazo es una cuota o pausa anti-baneo (no un error real), no cuenta como intento fallido: el schedule se reprograma a pending con un nuevo sendAt calculado según el motivo (fin de cuota, fin de quiet hours, etc).