feat(api): automatizaciones Monday — round-robin de leads, recordatorio inactividad 10d, webhooks reales y polling cron

- Servicio de automatizaciones con estado persistido en SQLite (índice RR, dedup de recordatorios)
- Webhook Monday procesa create_item del board Leads y asigna vendedor
- Crons: polling cada 5 min (respaldo sin webhook público) + escaneo diario 9am
- Endpoints admin: /api/automatizaciones/estado, /round-robin/run, /inactividad/preview
- 15 tests nuevos (70/70 verdes)
This commit is contained in:
2026-08-22 02:37:16 +00:00
parent 05f6ceb01a
commit 8407fc24c3
13 changed files with 781 additions and 12 deletions

View File

@@ -41,7 +41,16 @@ npm test # vitest: fórmulas del cotizador (fixture U-101), cobranza, monday.s
| POST | `/api/planes/:id/abonos` | Aplica abono (estricto al centavo) |
| POST | `/api/planes/:id/notas` | Agrega nota a la bitácora |
| POST | `/api/planes/:id/cancelar` | Cancela el plan |
| POST | `/api/webhooks/monday` | Handshake `challenge` + log de eventos |
| POST | `/api/webhooks/monday` | Handshake `challenge`; `create_item` en Leads dispara round-robin |
| GET | `/api/automatizaciones/estado` | Índice RR actual, vendedores, último escaneo |
| POST | `/api/automatizaciones/round-robin/run` | Pasada manual de asignación round-robin |
| GET | `/api/automatizaciones/inactividad/preview` | Leads que serían recordados hoy (solo lectura) |
## Automatizaciones de Monday (tablero Leads)
- **Round-robin de vendedores**: leads sin persona en la columna people `Vendedor` reciben el siguiente vendedor de la lista rotativa. La lista viene de `MONDAY_RR_USER_IDS` (coma-separados) o, si está vacía, de los usuarios no-guest del workspace (cacheados en BD). El índice se persiste en la tabla `AutomatizacionState` (sobrevive reinicios).
- **Recordatorio de inactividad**: leads con estado no terminal (`DESCARTADO` es el único terminal) y más de `INACTIVIDAD_DIAS` (default 10) sin actualizarse reciben un update en el item. Dedup por día: clave `recordatorio:<itemId>:<fecha>` en `AutomatizacionState`.
- **Disparadores**: webhook `POST /api/webhooks/monday` (evento `create_item` en el board Leads) y, como respaldo mientras no haya URL pública, crons con node-cron: `POLL_CRON` (default `*/5 * * * *`) para round-robin y `0 9 * * *` para inactividad. Los crons se apagan con `MONDAY_AUTOMATIONS_ENABLED=false`.
## Notas de arquitectura
@@ -49,7 +58,7 @@ npm test # vitest: fórmulas del cotizador (fixture U-101), cobranza, monday.s
- **Cálculos del cotizador** (`src/services/cotizacion.service.ts`): port exacto del Excel de producción (quirks incluidos: Infonavit usa precio de lista en mensualidades; enganche bancario sobre precio lista). NO corregir.
- **Cobranza** (`src/services/cobranza.service.ts`): espejo de la lógica del frontend; los controllers mapean Prisma → dominio, ejecutan las funciones puras y persisten.
- **Dinero como `Float`** por ahora — TODO prod: `Decimal` en Postgres.
- **Webhooks de Monday no están firmados** → el payload se trata como no confiable; antes de actuar, re-consultar vía API (por ahora solo se loggea).
- **Webhooks de Monday no están firmados** → el payload se trata como no confiable; el evento solo es un disparador y el round-robin re-consulta el tablero vía API antes de escribir.
## TODO producción (pendientes deliberados)