import { useCallback, useEffect, useState, type FormEvent } from 'react' import KpiCard from '../components/KpiCard' import DarkModeToggle from '../components/DarkModeToggle' import { SkeletonList } from "../components/Skeleton" import toast from "react-hot-toast" import type { AccionesSesion } from '../components/AuthGate' import { esActividadVencida, esRolConEquipo, formatoFechaCorta, hoyLocal, DESARROLLOS_LEAD, ESTADOS_LEAD, ETIQUETA_FASE_APARTADO, ETIQUETA_FASE_ESCRITURACION, ETIQUETA_FASE_OPORTUNIDAD, FASES_APARTADO, FASES_ESCRITURACION, FASES_OPORTUNIDAD, ORIGENES_LEAD, SLOTS_DOCS_APARTADO, componerTelefono, LADAS, SUBCATEGORIAS_NO_COMPRA, textoDiasRestantes, urlAfluenciaParaOportunidad, urlCotizadorParaOportunidad, urlWhatsApp, type ApartadoMiDia, type ClaveLada, type FaseApartado, type LeadMiDia, type MotivoNoCompra, type OportunidadMiDia, type ResumenMiDia, type SlotDocApartado, } from '../lib/mi-dia' import { cambiarEstadoApartado, cambiarEstadoLead, cambiarEtapaOportunidad, convertirLead, crearLead, descartarLead, fetchArchivoDocumentoApartado, fetchResumenMiDia, listarDocumentosApartado, moverLead as moverLeadApi, reasignarLead as reasignarLeadApi, subirDocumentoApartado, type DocumentoApartado, } from '../services/mi-dia' import { guardarResumen, cargarResumenLocal } from '../services/offline-db' import { suscribirPush } from '../services/push' import { exportarLeads, exportarOportunidades, exportarApartados } from '../services/exportar' import { FiltrosAvanzados, aplicarFiltros, FILTROS_VACIOS, type FiltrosCRM } from '../components/FiltrosAvanzados' import { getSesion } from '../services/auth' import DashboardVendedor from '../components/DashboardVendedor' import KanbanView from '../components/KanbanView' import CalendarioCitas from '../components/CalendarioCitas' import DetalleLeadModal from '../components/DetalleLeadModal' function TituloSeccion({ children }: { children: string }) { return

{children}

} /** Banner de resumen matutino: saludo + KPIs del día */ function ResumenMatutino({ nombre, leadsNuevos, oportunidadesActivas, apartadosPorVencer, }: { nombre: string leadsNuevos: number oportunidadesActivas: number apartadosPorVencer: number }) { const hora = new Date().getHours() const esManana = hora < 12 const saludo = esManana ? 'Buenos días' : 'Buenas tardes' const icono = esManana ? '☀️' : '🌙' return (
{icono}

{saludo}, {nombre}

Tienes {leadsNuevos} lead{leadsNuevos !== 1 ? 's' : ''} nuevos hoy,{' '} {oportunidadesActivas} oportunidade{oportunidadesActivas !== 1 ? 's' : ''} activa{oportunidadesActivas !== 1 ? 's' : ''},{' '} {apartadosPorVencer} apartado{apartadosPorVencer !== 1 ? 's' : ''} por vencer

) } function EstadoVacio({ mensaje }: { mensaje: string }) { return (

{mensaje}

) } const CLASE_BOTON_ACCION = 'rounded-lg border border-[#D1D5DB] bg-white px-3 dark:border-[#334155] dark:bg-[#0F172A] dark:text-[#E2E8F0] py-1.5 text-xs font-medium text-accent shadow-sm transition-all duration-100 ease-out hover:bg-blue-50 disabled:opacity-50' const CLASE_INPUT = 'w-full rounded-lg border border-[#D1D5DB] bg-white px-3 dark:border-[#334155] dark:bg-[#0F172A] dark:text-[#E2E8F0] py-2.5 text-sm transition-colors focus:border-accent focus:outline-none focus:ring-2 focus:ring-accent/20' const CLASE_LABEL = 'mb-2 block text-xs font-medium text-[#4B5563] dark:text-[#CBD5E1]' /** Motivos de no compra: valores EXACTOS que valida el API (z.enum MOTIVOS_NO_COMPRA) */ const MOTIVOS_NO_COMPRA = ['Precio', 'Producto', 'Ubicacion', 'Financiamiento', 'Competencia', 'Cliente', 'Timing'] as const /** Teléfono clicable (tel:) + botón de WhatsApp con saludo FRASA prellenado */ function EnlacesContacto({ nombre, telefono }: { nombre: string; telefono: string }) { const vendedor = getSesion()?.usuario.nombre ?? '' if (telefono.trim() === '') return Sin teléfono return ( <> {telefono} 💬 ) } /** Fila de oportunidad: acciones en orden (Agendo Cita lleva fecha opcional) */ function FilaOportunidad({ op, onEtapa, onCotizar, onAfluencia, onDescartar, onVerDetalle, enviando, acciones, }: { op: OportunidadMiDia onEtapa: (etapa: string, fecha?: string) => void onCotizar: () => void onAfluencia: () => void onDescartar: () => void onVerDetalle?: () => void enviando: boolean acciones?: string[] }) { const [conCita, setConCita] = useState(!!op.fechaCita) const [fechaCita, setFechaCita] = useState(op.fechaCita ?? '') return (
  • {op.desarrollo ? ` · ${op.desarrollo}` : ''} {op.origen ? ` · ${op.origen}` : ''} {op.modeloInteres ? ` · ${op.modeloInteres}` : ''} {op.unidadCodigo ? ` · ${op.unidadCodigo}` : ''}

    {op.fechaCita && (

    📅 Cita: {op.fechaCita.replace('T', ' ')}

    )} {op.ultimoCambio && (

    Último cambio: {op.ultimoCambio.detalle} · {op.ultimoCambio.usuario ?? '—'} ·{' '} {formatoFechaCorta(op.ultimoCambio.fechaISO.slice(0, 10))}

    )}
    {op.etapa || 'Sin etapa'}
    {/* 1. Confirmar cita (etapa Agendo Cita): al primer toque pide la fecha */} {conCita && ( setFechaCita(e.target.value)} className="rounded-lg border border-[#D1D5DB] bg-white px-2 dark:border-[#334155] dark:bg-[#0F172A] dark:text-[#E2E8F0] py-1.5 text-xs transition-colors focus:border-accent focus:outline-none focus:ring-2 focus:ring-accent/20" /> )} {/* 2. Afluencia: abre el formulario de alta afluencia prellenado */} {/* 3. Cotizar: cambia la etapa y abre el cotizador con los datos prellenados */} {/* 4. Aparto: marca la etapa (verde) */} {/* 5. Descartar */}
  • ) } /** Fila de apartado con botones de fase del pipeline Enkontrol */ function ModalDescarteGenerico({ item, titulo, onCancelar, onConfirmar, enviando, }: { item: { id: string; nombre: string } titulo: string onCancelar: () => void onConfirmar: (motivo: string, subcategoria: string | undefined, comentario: string) => void enviando: boolean }) { const [motivo, setMotivo] = useState('Timing') const [subcategoria, setSubcategoria] = useState('') const [comentario, setComentario] = useState('') // Subcategorías del catálogo para el motivo elegido; se limpia al cambiar de motivo const subcategorias = motivo in SUBCATEGORIAS_NO_COMPRA ? SUBCATEGORIAS_NO_COMPRA[motivo as MotivoNoCompra] : [] return (

    {titulo}

    {item.nombre}

    { e.preventDefault() onConfirmar(motivo, subcategoria === '' ? undefined : subcategoria, comentario.trim()) }} >