feat(modo-local): fallback completo sin Monday + cola de sincronización
- Circuit breaker saludMonday (3 fallos → modo local; ping cada 60s)
- Endpoint GET /api/sistema/estado {mondayOk, pendientesSync}
- LeadCaptura extendida (board, etapa, origen, motivo, pendienteSync)
- Tablas Unidad (espejo inventario, 288 seed) + SyncPendiente
- crmLocal: crearLead, afluencia, cambio etapa, descarte, conversión, apartar
- Ruteo en controllers: fallback local ante 502 o Monday caído
- Mi día local: resumen completo desde SQLite + unión con Monday
- Fallbacks: unidades, contactos, reportes, prefill, buscador
- Sync worker cada 2min + handlers 15 tipos (con DependenciaPendienteError)
- Gate automatizaciones: skip si !mondayOk
- Banner 'Modo local' en 5 apps + badge pendiente en mi-dia
- 597 API tests + 263 front tests verdes
Env MODO_LOCAL_FORZADO=true para testing sin Monday.
This commit is contained in:
@@ -2,10 +2,44 @@ import cron from 'node-cron'
|
||||
import { crearApp } from './app.js'
|
||||
import { config } from './config/index.js'
|
||||
import { automatizacionesService } from './services/automatizaciones.service.js'
|
||||
import { mondayService } from './services/monday.service.js'
|
||||
import { recordatoriosCobranzaService } from './services/recordatorios-cobranza.service.js'
|
||||
import { saludMonday } from './services/salud-monday.service.js'
|
||||
import { syncMondayService } from './services/sync-monday.service.js'
|
||||
// Registra los handlers de cada tipo de SyncPendiente sobre el worker (efecto lateral)
|
||||
import './services/sync-handlers.js'
|
||||
|
||||
const app = crearApp()
|
||||
|
||||
// Wiring del circuit breaker: el ping vive en monday.service; se inyecta aquí
|
||||
// para que salud-monday.service no lo importe (ciclo de imports).
|
||||
saludMonday.registrarPing(() => mondayService.ping())
|
||||
|
||||
// Sondeo de salud de Monday (cada 60 s): tras FALLOS_PARA_CAER fallos GraphQL
|
||||
// seguidos el flag cae y la app opera en modo local; cuando el ping vuelve a
|
||||
// responder se recupera el flag y se drena la cola de sincronización.
|
||||
// Corre siempre (aunque MONDAY_AUTOMATIONS_ENABLED=false): es la infra del modo local.
|
||||
cron.schedule('* * * * *', () => {
|
||||
void (async () => {
|
||||
if (process.env.MODO_LOCAL_FORZADO === 'true') return // modo local forzado: no sondear
|
||||
if (saludMonday.estaDisponible()) return // Monday sano: nada que hacer
|
||||
const recuperado = await saludMonday.sondear()
|
||||
if (recuperado && saludMonday.estaDisponible()) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.log('[salud] Monday recuperado; drenando cola de sincronización')
|
||||
void syncMondayService.procesarPendientes() // fire-and-forget (mutex interno)
|
||||
}
|
||||
})().catch((err: unknown) => console.error('[cron salud] error en sondeo:', err))
|
||||
})
|
||||
|
||||
// Sync worker del modo local (cada 2 min): drena SyncPendiente FIFO hacia
|
||||
// Monday. No-op mientras Monday esté caído. Secuencial por diseño (SQLite).
|
||||
cron.schedule('*/2 * * * *', () => {
|
||||
syncMondayService
|
||||
.procesarPendientes()
|
||||
.catch((err: unknown) => console.error('[cron sync] error en la pasada:', err))
|
||||
})
|
||||
|
||||
// Crons de respaldo: los webhooks de Monday (registrados sobre
|
||||
// https://frasa.consultoria-as.com/api/webhooks/monday) disparan estas mismas
|
||||
// reglas en tiempo real; estos barridos quedan como red de seguridad por si un
|
||||
@@ -18,6 +52,12 @@ if (config.automatizaciones.enabled) {
|
||||
// escaneos en paralelo provoca timeouts P1008 en AutomatizacionState.
|
||||
cron.schedule(config.automatizaciones.pollCron, () => {
|
||||
void (async () => {
|
||||
// Modo local: Monday caído → saltar las reglas sin ruido (fallan seguro de todos modos)
|
||||
if (!saludMonday.estaDisponible()) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.debug('[cron poll] Monday no disponible (modo local); pasada omitida')
|
||||
return
|
||||
}
|
||||
try {
|
||||
const r = await automatizacionesService.runRoundRobin()
|
||||
if (r.asignados > 0) {
|
||||
@@ -55,6 +95,11 @@ if (config.automatizaciones.enabled) {
|
||||
|
||||
// Escaneo diario de leads inactivos (recordatorio vía update en el item)
|
||||
cron.schedule(config.automatizaciones.recordatorioCron, () => {
|
||||
if (!saludMonday.estaDisponible()) {
|
||||
// eslint-disable-next-line no-console
|
||||
console.debug('[cron inactividad] Monday no disponible (modo local); escaneo omitido')
|
||||
return
|
||||
}
|
||||
automatizacionesService
|
||||
.escaneoInactividad({ preview: false })
|
||||
.then((r) => {
|
||||
|
||||
Reference in New Issue
Block a user