Skip to content

Cobranza — Estado actual

El worker que ya corre

DunningWhatsAppJob vive en crm-backend/api/internal/jobs/dunning_whatsapp_job.go. Se registra en el scheduler global (internal/jobs/scheduler.go) con una expresión cron 0 * * * * (cada hora en punto).

Lo que hace, paso a paso

1. Resuelve lista de tenants activos (hoy: Semtec).
2. Para cada tenant:
   a. GET billing /invoices?company_id=…&status=overdue
   b. Dedupe por contact_id (un contacto puede tener varias facturas)
   c. Para cada contacto:
      - Carga contact de Mongo (wa_id, preferred_channel)
      - Skip si preferred_channel != "whatsapp"
      - Lee last_dunning_sent_at del contact
      - Elige template según days_overdue
      - Check cooldown (48h mismo template)
      - Encola job a whatsapp-outbound-jobs
      - Actualiza last_dunning_sent_at + contador intentos
3. Publica métrica dunning_messages_sent_total{template}

Configuración

Vars de entorno (deploy-dev/.env):

DUNNING_ENABLED=true
DUNNING_WINDOW_START=08
DUNNING_WINDOW_END=20
DUNNING_COOLDOWN_HOURS=48
DUNNING_MAX_ATTEMPTS=3

Métricas

Expuestas en /metrics del crm-api:

  • dunning_cycle_duration_seconds (histogram).
  • dunning_invoices_scanned_total.
  • dunning_messages_sent_total{template="payment_reminder|payment_overdue|service_suspended"}.
  • dunning_skipped_total{reason="cooldown|no_wa_id|out_of_window|not_preferred_channel"}.

Por qué no es "billing-driven"

El worker pregunta a billing cada hora. Lo que queremos es que billing avise cuando algo pasa. Esa diferencia es el próximo paso.

Ver gaps para saber qué falta.

Impulse Tech · Documentación interna