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:
@@ -1,5 +1,7 @@
|
||||
import { useCallback, useEffect, useState, type FormEvent } from 'react'
|
||||
import KpiCard from '../components/KpiCard'
|
||||
import BannerModoLocal from '../components/BannerModoLocal'
|
||||
import ChipPendienteSync from '../components/ChipPendienteSync'
|
||||
import type { AccionesSesion } from '../components/AuthGate'
|
||||
import {
|
||||
esActividadVencida,
|
||||
@@ -187,7 +189,10 @@ function FilaOportunidad({
|
||||
<li className="rounded-xl border border-[#E5E7EB] bg-white p-4 shadow-sm">
|
||||
<div className="flex flex-wrap items-center justify-between gap-3">
|
||||
<div>
|
||||
<p className="text-sm font-medium">{op.nombre}</p>
|
||||
<p className="flex items-center gap-2 text-sm font-medium">
|
||||
{op.nombre}
|
||||
{op.pendienteSync === true && <ChipPendienteSync />}
|
||||
</p>
|
||||
<p className="mt-1 text-xs text-[#64748B]">
|
||||
{op.telefono || 'Sin teléfono'}
|
||||
{op.desarrollo ? ` · ${op.desarrollo}` : ''}
|
||||
@@ -285,10 +290,11 @@ function FilaApartado({
|
||||
<li className="rounded-xl border border-[#E5E7EB] bg-white p-4 shadow-sm">
|
||||
<div className="flex flex-wrap items-center justify-between gap-3">
|
||||
<div>
|
||||
<p className="text-sm font-medium">
|
||||
<p className="flex items-center gap-2 text-sm font-medium">
|
||||
{apartado.nombre}
|
||||
{apartado.pendienteSync === true && <ChipPendienteSync />}
|
||||
{apartado.unidadCodigo ? (
|
||||
<span className="ml-2 text-xs font-normal text-[#94A3B8]">{apartado.unidadCodigo}</span>
|
||||
<span className="text-xs font-normal text-[#94A3B8]">{apartado.unidadCodigo}</span>
|
||||
) : null}
|
||||
</p>
|
||||
<p className="mt-1 text-xs text-[#64748B]">
|
||||
@@ -510,6 +516,7 @@ function FilaLead({
|
||||
<div>
|
||||
<p className="flex items-center gap-2 text-sm font-medium">
|
||||
{lead.nombre}
|
||||
{lead.pendienteSync === true && <ChipPendienteSync />}
|
||||
{/* Insignia de procedencia: gris = tablero Leads, azul = Oportunidades */}
|
||||
<span
|
||||
className={
|
||||
@@ -757,6 +764,8 @@ export default function MiDiaPage({ accionesSesion }: { accionesSesion: Acciones
|
||||
</div>
|
||||
</header>
|
||||
|
||||
<BannerModoLocal />
|
||||
|
||||
<main className="mx-auto max-w-[1280px] px-4 py-8 sm:px-6">
|
||||
<div className="mb-6">
|
||||
<h1 className="text-xl font-semibold text-[#111827]">{nombreVendedor}</h1>
|
||||
@@ -884,7 +893,10 @@ export default function MiDiaPage({ accionesSesion }: { accionesSesion: Acciones
|
||||
className="flex flex-wrap items-center justify-between gap-3 rounded-xl border border-[#E5E7EB] bg-white px-4 py-3 shadow-sm"
|
||||
>
|
||||
<div>
|
||||
<p className="text-sm font-medium">{c.nombre}</p>
|
||||
<p className="flex items-center gap-2 text-sm font-medium">
|
||||
{c.nombre}
|
||||
{c.pendienteSync === true && <ChipPendienteSync />}
|
||||
</p>
|
||||
<p className="mt-1 text-xs text-[#64748B]">
|
||||
{c.telefono || 'Sin teléfono'}
|
||||
{c.origen ? ` · ${c.origen}` : ''}
|
||||
|
||||
Reference in New Issue
Block a user