- 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.
101 lines
3.6 KiB
TypeScript
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
|
|
}
|
|
}
|