Files
FRASA-CAS/apps/alta-clientes/src/components/ChecklistDocumentos.tsx
Consult AS Technology db18685872 feat(ux): 17 mejoras de experiencia de usuario
Backend (10 endpoints nuevos):
- Recordatorios personales (CRUD + tabla RecordatorioVendedor)
- Lead scoring 0-100 (etapa+recencia+datos)
- Dashboard mora (pagos vencidos, monto, días atraso)
- Timeline de pagos (pagado/vence/futuro)
- Perfil completo contacto (pagos+cotizaciones+docs+score)
- Exportar contactos a CSV

Frontend Mi día:
- Resumen matutino (saludo+KPIs)
- Historial visual lead (timeline)
- Score badge 0-100 (verde/amarillo/rojo)
- Vista kanban (drag & drop)

Frontend Cobranza:
- Dashboard mora (tabla+WhatsApp)
- Timeline pagos visual (nodos coloreados)

Frontend Alta Clientes:
- Checklist documentos visual
- Foto cliente (cámara)

Frontend Landing:
- Perfil contacto completo
- Exportar contactos CSV

Frontend Mi día (extras):
- Comparativa mensual
- KPIs dashboard (leads hoy, ventas, conversión)

806 tests, 5 apps construidas.
2026-09-08 07:42:54 +00:00

107 lines
4.0 KiB
TypeScript

import { useState } from 'react'
/** Documento requerido en el checklist */
export interface DocumentoChecklist {
tipo: string
nombre: string
obligatorio: boolean
}
const DOCUMENTOS_DEFAULT: DocumentoChecklist[] = [
{ tipo: 'INE', nombre: 'INE / Identificación oficial', obligatorio: true },
{ tipo: 'RFC', nombre: 'RFC', obligatorio: true },
{ tipo: 'CURP', nombre: 'CURP', obligatorio: true },
{ tipo: 'COMPROBANTE_DOMICILIO', nombre: 'Comprobante de domicilio', obligatorio: true },
{ tipo: 'ACTA_NACIMIENTO', nombre: 'Acta de nacimiento', obligatorio: true },
]
function iconoDoc(recibido: boolean, obligatorio: boolean): string {
if (recibido) return '✅'
return obligatorio ? '⏳' : '📄'
}
interface ChecklistDocumentosProps {
documentos?: DocumentoChecklist[]
recibidos?: string[] // tipos ya marcados como recibidos
onToggle?: (tipo: string, recibido: boolean) => void
soloLectura?: boolean
}
export default function ChecklistDocumentos({
documentos = DOCUMENTOS_DEFAULT,
recibidos: recibidosProp,
onToggle,
soloLectura = false,
}: ChecklistDocumentosProps) {
const [recibidosInterno, setRecibidosInterno] = useState<Set<string>>(() => new Set(recibidosProp ?? []))
const recibidos = recibidosProp ? new Set(recibidosProp) : recibidosInterno
const toggle = (tipo: string) => {
if (soloLectura) return
const nuevo = new Set(recibidosInterno)
if (nuevo.has(tipo)) nuevo.delete(tipo)
else nuevo.add(tipo)
setRecibidosInterno(nuevo)
onToggle?.(tipo, nuevo.has(tipo))
}
const total = documentos.length
const completados = documentos.filter((d) => recibidos.has(d.tipo)).length
return (
<div className="rounded-xl border border-[#E5E7EB] bg-white p-5 dark:border-[#1E293B] dark:bg-[#1E293B] shadow-sm">
<div className="flex items-center justify-between">
<h3 className="text-xs font-semibold uppercase tracking-wider text-[#64748B] dark:text-[#94A3B8]">
Documentos requeridos
</h3>
<span className="rounded-full bg-blue-50 dark:bg-blue-900/30 px-2.5 py-0.5 text-xs font-semibold text-blue-700 dark:text-blue-400">
{completados} de {total}
</span>
</div>
{/* Barra de progreso */}
<div className="mt-3 h-2 rounded-full bg-gray-100 dark:bg-[#334155]">
<div
className="h-full rounded-full bg-green-500 transition-all duration-300"
style={{ width: `${total > 0 ? (completados / total) * 100 : 0}%` }}
/>
</div>
<ul className="mt-4 space-y-2">
{documentos.map((doc) => {
const recibido = recibidos.has(doc.tipo)
return (
<li
key={doc.tipo}
onClick={() => toggle(doc.tipo)}
className={`flex items-center gap-3 rounded-lg border px-4 py-3 transition-colors ${
recibido
? 'border-green-200 bg-green-50 dark:border-green-900/40 dark:bg-green-950/20'
: 'border-[#E5E7EB] bg-white dark:border-[#334155] dark:bg-[#0F172A]'
} ${soloLectura ? '' : 'cursor-pointer hover:border-accent'}`}
>
<span className="text-lg">{iconoDoc(recibido, doc.obligatorio)}</span>
<div className="flex-1">
<p className={`text-sm font-medium ${recibido ? 'text-green-700 dark:text-green-400 line-through' : 'text-[#111827] dark:text-[#E2E8F0]'}`}>
{doc.nombre}
</p>
{doc.obligatorio && !recibido && (
<p className="text-[10px] text-amber-600 dark:text-amber-400">Requerido</p>
)}
</div>
{!soloLectura && (
<input
type="checkbox"
checked={recibido}
onChange={() => toggle(doc.tipo)}
className="h-4 w-4 rounded border-gray-300 text-green-600 focus:ring-green-500"
/>
)}
</li>
)
})}
</ul>
</div>
)
}