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:
73
api-gateway/src/services/salud-monday.service.ts
Normal file
73
api-gateway/src/services/salud-monday.service.ts
Normal file
@@ -0,0 +1,73 @@
|
||||
/**
|
||||
* Salud de Monday (circuit breaker ligero).
|
||||
*
|
||||
* Estado en memoria: `mondayOk` arranca en true (optimista) y pasa a false tras
|
||||
* FALLOS_PARA_CAER fallos GraphQL consecutivos reportados por monday.service.
|
||||
* Mientras está caído, los callers deben saltarse Monday de inmediato (modo
|
||||
* local) en lugar de pagar los reintentos por request; un cron sondea cada 60 s
|
||||
* y al recuperarse dispara el sync worker.
|
||||
*
|
||||
* MODO_LOCAL_FORZADO=true fuerza modo local aunque Monday responda (pruebas).
|
||||
*
|
||||
* El ping se inyecta con `registrarPing()` (wiring en index.ts) para no
|
||||
* importar monday.service aquí y evitar ciclos de imports.
|
||||
*/
|
||||
|
||||
/** Fallos GraphQL consecutivos que tumban el flag */
|
||||
export const FALLOS_PARA_CAER = 3
|
||||
|
||||
export type PingMonday = () => Promise<boolean>
|
||||
|
||||
export class SaludMondayService {
|
||||
private mondayOk = true
|
||||
private fallosConsecutivos = 0
|
||||
private pingFn: PingMonday | null = null
|
||||
|
||||
/** Registra el callback de ping (en index.ts: () => mondayService.ping()) */
|
||||
registrarPing(fn: PingMonday): void {
|
||||
this.pingFn = fn
|
||||
}
|
||||
|
||||
/** Llamar tras cada llamada GraphQL exitosa */
|
||||
registrarExito(): void {
|
||||
this.fallosConsecutivos = 0
|
||||
this.mondayOk = true
|
||||
}
|
||||
|
||||
/** Llamar tras cada fallo definitivo de Monday (AppError 502 de graphql()) */
|
||||
registrarFallo(): void {
|
||||
this.fallosConsecutivos += 1
|
||||
if (this.fallosConsecutivos >= FALLOS_PARA_CAER) this.mondayOk = false
|
||||
}
|
||||
|
||||
/** Flag crudo (sin el override MODO_LOCAL_FORZADO); útil para el cron de sondeo */
|
||||
ok(): boolean {
|
||||
return this.mondayOk
|
||||
}
|
||||
|
||||
/** true si los callers pueden hablar con Monday ahora mismo */
|
||||
estaDisponible(): boolean {
|
||||
return this.mondayOk && process.env.MODO_LOCAL_FORZADO !== 'true'
|
||||
}
|
||||
|
||||
/**
|
||||
* Ping a Monday. Si responde, recupera el flag y devuelve true (el caller
|
||||
* decide si dispara el sync). Sin ping registrado devuelve el flag actual.
|
||||
*/
|
||||
async sondear(): Promise<boolean> {
|
||||
if (this.pingFn === null) return this.mondayOk
|
||||
const vivo = await this.pingFn()
|
||||
if (vivo) this.registrarExito()
|
||||
return vivo
|
||||
}
|
||||
|
||||
/** Reset completo (tests) */
|
||||
restablecer(): void {
|
||||
this.mondayOk = true
|
||||
this.fallosConsecutivos = 0
|
||||
this.pingFn = null
|
||||
}
|
||||
}
|
||||
|
||||
/** Singleton compartido por monday.service, controllers y crons */
|
||||
export const saludMonday = new SaludMondayService()
|
||||
Reference in New Issue
Block a user