/** * 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 (env var o DB) fuerza modo local aunque Monday * responda (pruebas / toggle admin). La DB se cachea 30 s en configService. * * 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 export class SaludMondayService { private _modoLocalDb = false setModoLocalDb(v: boolean) { this._modoLocalDb = v } 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. * Lee modo_local_forzado de la DB (via cache 30 s en configService) y de * process.env como fallback. Síncrono: la DB se precarga al iniciar y se * invalida al cambiar configs. */ estaDisponible(): boolean { // Lazy import para no crear ciclos en tests (configService importa prisma) let forzadoDb = false try { // eslint-disable-next-line @typescript-eslint/no-require-imports const cs = require('./config.service.js') as typeof import('./config.service.js') const syncVal = cs.obtenerConfigSync('modo_local_forzado') if (syncVal !== undefined) { forzadoDb = syncVal === 'true' } else { // Cache no cargado aún: intentar async (fire-and-forget para la próxima vez) cs.obtenerConfig('modo_local_forzado').then((v) => { if (v === 'true') this._modoLocalDb = true }).catch(() => {}) } } catch { // configService no disponible (tests aislados, DB no inicializada) } const forzadoEnv = process.env.MODO_LOCAL_FORZADO === 'true' return this.mondayOk && !forzadoDb && !this._modoLocalDb && !forzadoEnv } /** * 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 { 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()