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:
@@ -3,6 +3,7 @@ import { MODULOS, esModuloVisible } from './data/modulos'
|
||||
import { Navbar } from './components/Navbar'
|
||||
import { TarjetaModulo } from './components/TarjetaModulo'
|
||||
import { ConfiguracionCuenta } from './components/ConfiguracionCuenta'
|
||||
import BannerModoLocal from './components/BannerModoLocal'
|
||||
import { VistaContactos } from './components/VistaContactos'
|
||||
import { getToken } from './services/auth'
|
||||
import { getMe } from './services/api'
|
||||
@@ -40,6 +41,7 @@ export default function App() {
|
||||
return (
|
||||
<div className="flex min-h-screen flex-col">
|
||||
<Navbar />
|
||||
<BannerModoLocal />
|
||||
<VistaContactos onVolver={() => setVista('home')} />
|
||||
</div>
|
||||
)
|
||||
@@ -48,6 +50,7 @@ export default function App() {
|
||||
return (
|
||||
<div className="flex min-h-screen flex-col">
|
||||
<Navbar />
|
||||
<BannerModoLocal />
|
||||
|
||||
<main className="mx-auto w-full max-w-[1280px] flex-1 px-6">
|
||||
<section aria-labelledby="titulo-hero" className="py-12 sm:py-16">
|
||||
|
||||
32
apps/landing/src/components/BannerModoLocal.tsx
Normal file
32
apps/landing/src/components/BannerModoLocal.tsx
Normal file
@@ -0,0 +1,32 @@
|
||||
import { useEffect, useState } from 'react'
|
||||
import { fetchEstadoSistema } from '../services/sistema'
|
||||
|
||||
const INTERVALO_POLL_MS = 60_000
|
||||
|
||||
export function useModoLocal(): boolean {
|
||||
const [modoLocal, setModoLocal] = useState(false)
|
||||
useEffect(() => {
|
||||
let activo = true
|
||||
const consultar = () => {
|
||||
void fetchEstadoSistema().then((estado) => {
|
||||
if (activo) setModoLocal(estado !== null && !estado.mondayOk)
|
||||
})
|
||||
}
|
||||
consultar()
|
||||
const id = setInterval(consultar, INTERVALO_POLL_MS)
|
||||
return () => { activo = false; clearInterval(id) }
|
||||
}, [])
|
||||
return modoLocal
|
||||
}
|
||||
|
||||
export default function BannerModoLocal() {
|
||||
const modoLocal = useModoLocal()
|
||||
if (!modoLocal) return null
|
||||
return (
|
||||
<div role="status" className="border-b border-amber-200 bg-amber-50">
|
||||
<p className="mx-auto max-w-[1280px] px-4 py-2 text-xs font-medium text-amber-700 sm:px-6">
|
||||
Modo local — Monday no disponible. Tus capturas se guardan y se sincronizarán automáticamente.
|
||||
</p>
|
||||
</div>
|
||||
)
|
||||
}
|
||||
17
apps/landing/src/services/sistema.ts
Normal file
17
apps/landing/src/services/sistema.ts
Normal file
@@ -0,0 +1,17 @@
|
||||
import { API_URL, conAuth, getToken } from './auth'
|
||||
|
||||
export interface EstadoSistema {
|
||||
mondayOk: boolean
|
||||
pendientesSync: number
|
||||
}
|
||||
|
||||
export async function fetchEstadoSistema(): Promise<EstadoSistema | null> {
|
||||
if (!getToken()) return null
|
||||
try {
|
||||
const res = await fetch(`${API_URL}/sistema/estado`, conAuth())
|
||||
if (!res.ok) return null
|
||||
return (await res.json()) as EstadoSistema
|
||||
} catch {
|
||||
return null
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user