Files
FRASA-CAS/api-gateway/src/services/unidades-local.service.ts
Consult AS Technology 60649890eb 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.
2026-09-06 08:08:56 +00:00

101 lines
3.6 KiB
TypeScript

import { mondayService, type Unidad } from './monday.service.js'
import { prisma } from './prisma.js'
import { saludMonday } from './salud-monday.service.js'
import { AppError } from '../utils/errors.js'
/**
* Fallback de LECTURA del inventario (modo local): la tabla `Unidad` es el
* espejo local del tablero Unidades de Monday (la siembra
* scripts/seed-unidades-locales.js y la mantiene fresco el write-through de
* monday.service.getUnidades). Cuando Monday está caído (saludMonday) o lanza
* AppError 502, el cotizador lee de aquí con la MISMA forma de DTO.
*
* La tabla local no guarda fechaEntrega ni mesesParaFirma (no están en el
* seed curado): salen en null, que el cotizador ya tolera (plazo desconocido).
*/
/** Fila de la tabla Unidad tal como la devuelve Prisma (estructural) */
export interface FilaUnidad {
codigo: string
desarrollo: string
modelo: string
supConstruccion: number
supExcedente: number
supEstacionamiento: number
precioLista: number
precioAcabados: number
status: string
etapa: string
}
/** Fila local → DTO `Unidad` (UnidadCotizable) que consumen controllers y fronts */
export function unidadLocalADto(u: FilaUnidad): Unidad {
return {
codigo: u.codigo,
modelo: u.modelo,
fechaEntrega: null, // no está en el espejo local: plazo desconocido
supEstacionamiento: u.supEstacionamiento,
supConstruccion: u.supConstruccion,
supExcedente: u.supExcedente,
precioSinAcabados: u.precioLista,
precioConAcabados: u.precioAcabados,
status: u.status,
desarrollo: u.desarrollo,
mesesParaFirma: null,
etapa: u.etapa === '' ? null : u.etapa,
}
}
/** true si el error es un fallo de Monday (AppError 502) que amerita fallback local */
function esFalloMonday502(err: unknown): boolean {
return err instanceof AppError && err.status === 502
}
/** Unidades del espejo local; filtros equivalentes a los del controller */
export async function getUnidadesLocal(
filtro: { desarrollo?: string | undefined; soloDisponibles?: boolean | undefined } = {},
): Promise<Unidad[]> {
const rows = await prisma.unidad.findMany({ orderBy: { codigo: 'asc' } })
let lista = rows.map(unidadLocalADto)
if (filtro.soloDisponibles === true) {
lista = lista.filter((u) => u.status.trim().toUpperCase() === 'DISPONIBLE')
}
if (filtro.desarrollo !== undefined && filtro.desarrollo !== '') {
const desarrollo = filtro.desarrollo.toUpperCase()
lista = lista.filter((u) => (u.desarrollo || 'DIAGONAL').toUpperCase() === desarrollo)
}
return lista
}
/** Una unidad local por código; null si no existe en el espejo */
export async function getUnidadLocal(codigo: string): Promise<Unidad | null> {
const row = await prisma.unidad.findUnique({ where: { codigo } })
return row === null ? null : unidadLocalADto(row)
}
/**
* Lectura con fallback: Monday si está disponible; espejo local si el circuit
* breaker lo da por caído o si la llamada lanza AppError 502. Cualquier otro
* error se propaga intacto (no se traga nada del camino feliz).
*/
export async function listarUnidadesConFallback(): Promise<Unidad[]> {
if (!saludMonday.estaDisponible()) return getUnidadesLocal()
try {
return await mondayService.getUnidades()
} catch (err) {
if (esFalloMonday502(err)) return getUnidadesLocal()
throw err
}
}
/** Igual que listarUnidadesConFallback pero para una unidad por código */
export async function obtenerUnidadConFallback(codigo: string): Promise<Unidad | null> {
if (!saludMonday.estaDisponible()) return getUnidadLocal(codigo)
try {
return await mondayService.getUnidad(codigo)
} catch (err) {
if (esFalloMonday502(err)) return getUnidadLocal(codigo)
throw err
}
}