ixiclinicDocs
Desarrolladores

Scheduling

Tareas recurrentes en background con @fastify/schedule (toad-scheduler) — recordatorios de citas, no-shows, limpieza de ocupación, resumen diario y canales WhatsApp/email.

El API corre tareas de fondo recurrentes con @fastify/schedule (que envuelve toad-scheduler). Cubren los recordatorios de citas, la detección de no-shows, la limpieza de sesiones de caja y posiciones de cola olvidadas, y el resumen diario de citas.

El plugin scheduler

Vive en apps/api/src/plugins/scheduler.ts y se registra en apps/api/src/app.ts justo después de @fastify/schedule. Define las tareas como AsyncTask y las agenda como SimpleIntervalJob (intervalos) o CronJob (hora fija). Todas se registran en app.ready().then(...), es decir, una vez que el servidor está listo, y usan preventOverrun: true para no solaparse consigo mismas.

Las dependencias se importan de forma perezosa (await import(...)) dentro del plugin para evitar ciclos de importación con los módulos que tocan el scheduler.

Jobs registrados

Job (id)CadenciaQué haceImplementación
reminder-processorcada 5 minEnvía los recordatorios de cita pendientes que ya vencenprocessPendingReminders
no-show-detectorcada 10 minMarca como no_show las citas confirmadas no atendidasdetectNoShows
occupancy-cleanupcada 15 minCierra sesiones de caja inactivas + libera posiciones/tickets de cola olvidadosautoCloseStaleSessions + autoReleaseStalePositions + autoCancelAbandonedTickets
daily-summarycron 0 7 * * * (7:00 AM)Notifica al staff el conteo de citas del díasendDailySummary

Ninguno corre al arrancar (runImmediately: false).

Recordatorios de citas

La lógica vive en apps/api/src/modules/appointments/reminder-service.ts. El flujo:

  1. Al confirmarse una cita, generateReminders crea filas en appointment_reminders por cada combinación de canal × tiempo de antelación, más un recordatorio de confirmación inmediato. Los defaults salen de appointmentSettings.reminderDefaults:
    • canales: ['whatsapp', 'email']
    • antelaciones: ['24h', '2h'] (se parsean con un patrón \d+(h|m))
    • Los recordatorios que caerían en el pasado se omiten.
  2. processPendingReminders (cada 5 min) toma hasta 50 recordatorios pending cuyo scheduledFor <= now y con retryCount < 3, y para cada uno:
    • Si el canal es whatsapp y el tenant tiene whatsappConfig (accessToken + phoneNumberId) y el paciente tiene móvil, envía la plantilla (appointment_confirmation o appointment_reminder).
    • Si el canal es email y el tenant tiene emailApiKey + emailFrom y el paciente tiene correo, envía el email de confirmación o de recordatorio.
    • Marca el recordatorio como sent (con su externalId) o, ante cualquier fallo o config faltante, como failed incrementando retryCount (markReminderFailed). Tras 3 intentos el job deja de tomarlo.

No-shows

detectNoShows (cada 10 min) recorre los tenants con noShowPolicyMinutes definido, busca las citas confirmadas de hoy y, si ya pasó el umbral hora + noShowPolicyMinutes (default 15 min), cambia su estado a no_show y dispara notifyAppointmentNoShow (fire-and-forget) para avisar al staff.

Resumen diario

sendDailySummary (cron 7:00 AM) cuenta las citas del día por tenant y, si hay alguna, notifica a los roles admin, doctor y receptionist con el total y cuántas están confirmadas, vía notifyUsersByRoles.

Canales de salida

Los recordatorios no envían directamente: delegan en dos librerías.

WhatsApp — apps/api/src/lib/whatsapp.ts

Usa la WhatsApp Cloud API (Graph API, v23.0). sendTemplate(config, options) envía una plantilla pre-aprobada (las plantillas son la única forma de iniciar conversación fuera de la ventana de 24 h), con bodyParams que rellenan los huecos en orden. La config (accessToken, phoneNumberId, appSecret) sale de appointmentSettings.whatsappConfig del tenant. El módulo también soporta verificación de firma de webhooks vía appSecret.

Email — apps/api/src/lib/email-reminders.ts

Envía con Resend (POST https://api.resend.com/emails). Expone sendAppointmentConfirmationEmail, sendAppointmentReminderEmail y sendAppointmentCancelledEmail, que construyen el HTML del correo. La config (apiKey, fromEmail) sale de tenants.settings (emailApiKey / emailFrom).

Nota: Ambos canales son opcionales por tenant. Si la config no está, el recordatorio de ese canal se marca failed con un mensaje claro ('WhatsApp not configured' / 'Email not configured') en vez de romper el job.

Notificaciones in-app

apps/api/src/modules/appointments/appointment-notifications.ts complementa lo anterior con notificaciones dentro de la app (la campana). Cubre eventos de ciclo de vida de la cita (solicitada, confirmada, no-show, etc.) y se entrega vía Socket.IO (ver Tiempo real).

Siguiente: DGII / Fiscal.

On this page