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.
This commit is contained in:
2026-09-08 07:42:54 +00:00
parent 5063ffe281
commit db18685872
34 changed files with 2950 additions and 33 deletions

View File

@@ -0,0 +1,89 @@
/**
* Base de datos local de códigos postales de la zona Frasa (Guanajuato / Querétaro).
* CPs más comunes de las ciudades donde opera Frasa Desarrollos.
* Para producción, reemplazar con una API de CPs de México (p. ej. sepomex).
*/
export interface DireccionPorCP {
cp: string
colonia: string
municipio: string
estado: string
}
const CP_DB: DireccionPorCP[] = [
// León, Gto
{ cp: '37000', colonia: 'Centro', municipio: 'León', estado: 'Guanajuato' },
{ cp: '37150', colonia: 'Los Santos', municipio: 'León', estado: 'Guanajuato' },
{ cp: '37200', colonia: 'La Aurora', municipio: 'León', estado: 'Guanajuato' },
{ cp: '37250', colonia: 'El Potrero', municipio: 'León', estado: 'Guanajuato' },
{ cp: '37270', colonia: 'Vista Hermosa', municipio: 'León', estado: 'Guanajuato' },
{ cp: '37300', colonia: 'San José de los Duranes', municipio: 'León', estado: 'Guanajuato' },
{ cp: '37350', colonia: 'San Francisco', municipio: 'León', estado: 'Guanajuato' },
{ cp: '37400', colonia: 'El Coecillo', municipio: 'León', estado: 'Guanajuato' },
{ cp: '37450', colonia: 'Revolución', municipio: 'León', estado: 'Guanajuato' },
{ cp: '37500', colonia: 'La Tierrita', municipio: 'León', estado: 'Guanajuato' },
{ cp: '37550', colonia: 'Los Freseros', municipio: 'León', estado: 'Guanajuato' },
{ cp: '37600', colonia: 'San Juan de Abajo', municipio: 'León', estado: 'Guanajuato' },
{ cp: '37650', colonia: 'San Felipe', municipio: 'León', estado: 'Guanajuato' },
{ cp: '37670', colonia: 'El Palote', municipio: 'León', estado: 'Guanajuato' },
{ cp: '37700', colonia: 'La Hacienda', municipio: 'León', estado: 'Guanajuato' },
// Silao, Gto
{ cp: '36100', colonia: 'Centro', municipio: 'Silao', estado: 'Guanajuato' },
{ cp: '36110', colonia: 'San Francisco', municipio: 'Silao', estado: 'Guanajuato' },
{ cp: '36120', colonia: 'El Tajo', municipio: 'Silao', estado: 'Guanajuato' },
// Celaya, Gto
{ cp: '38000', colonia: 'Centro', municipio: 'Celaya', estado: 'Guanajuato' },
{ cp: '38010', colonia: 'San Antonio', municipio: 'Celaya', estado: 'Guanajuato' },
{ cp: '38020', colonia: 'El Pueblito', municipio: 'Celaya', estado: 'Guanajuato' },
{ cp: '38060', colonia: 'Las Américas', municipio: 'Celaya', estado: 'Guanajuato' },
{ cp: '38070', colonia: 'Los Virreyes', municipio: 'Celaya', estado: 'Guanajuato' },
// Querétaro, Qro
{ cp: '76000', colonia: 'Centro', municipio: 'Querétaro', estado: 'Querétaro' },
{ cp: '76010', colonia: 'San Sebastián Buerenas', municipio: 'Querétaro', estado: 'Querétaro' },
{ cp: '76020', colonia: 'LaVertexAttribArray', municipio: 'Querétaro', estado: 'Querétaro' },
{ cp: '76030', colonia: 'San Francisco Galindo', municipio: 'Querétaro', estado: 'Querétaro' },
{ cp: '76040', colonia: 'San Pablo Xochimecas', municipio: 'Querétaro', estado: 'Querétaro' },
{ cp: '76060', colonia: 'Villas del Sol', municipio: 'Querétaro', estado: 'Querétaro' },
{ cp: '76070', colonia: 'Vista Alegre', municipio: 'Querétaro', estado: 'Querétaro' },
{ cp: '76080', colonia: 'El Paraíso', municipio: 'Querétaro', estado: 'Querétaro' },
{ cp: '76090', colonia: 'El Jacal', municipio: 'Querétaro', estado: 'Querétaro' },
{ cp: '76100', colonia: 'Los Aztecas', municipio: 'Querétaro', estado: 'Querétaro' },
{ cp: '76110', colonia: 'El Mirador', municipio: 'Querétaro', estado: 'Querétaro' },
{ cp: '76120', colonia: 'La Esperanza', municipio: 'Querétaro', estado: 'Querétaro' },
{ cp: '76125', colonia: 'Santa María de Aguayo', municipio: 'Querétaro', estado: 'Querétaro' },
{ cp: '76126', colonia: 'Villa los Álamos', municipio: 'Querétaro', estado: 'Querétaro' },
{ cp: '76127', colonia: 'Puerta del Sol', municipio: 'Querétaro', estado: 'Querétaro' },
{ cp: '76128', colonia: 'Vista Real', municipio: 'Querétaro', estado: 'Querétaro' },
{ cp: '76130', colonia: 'Los Robles', municipio: 'Querétaro', estado: 'Querétaro' },
{ cp: '76135', colonia: 'Arboledas del Cimatario', municipio: 'Querétaro', estado: 'Querétaro' },
{ cp: '76136', colonia: 'Condado de Sayavedra', municipio: 'Querétaro', estado: 'Querétaro' },
{ cp: '76137', colonia: 'Prados del Quetzal', municipio: 'Querétaro', estado: 'Querétaro' },
{ cp: '76138', colonia: 'Lomas del Cimatario', municipio: 'Querétaro', estado: 'Querétaro' },
// San Miguel de Allende, Gto
{ cp: '37700', colonia: 'Centro', municipio: 'San Miguel de Allende', estado: 'Guanajuato' },
// Irapuato, Gto
{ cp: '36500', colonia: 'Centro', municipio: 'Irapuato', estado: 'Guanajuato' },
{ cp: '36510', colonia: 'San José', municipio: 'Irapuato', estado: 'Guanajuato' },
// Guanajuato Capital
{ cp: '36000', colonia: 'Centro', municipio: 'Guanajuato', estado: 'Guanajuato' },
// SLP
{ cp: '78000', colonia: 'Centro', municipio: 'San Luis Potosí', estado: 'San Luis Potosí' },
]
/** Busca dirección por CP (devuelve todas las colonias que coinciden) */
export function buscarPorCP(cp: string): DireccionPorCP[] {
return CP_DB.filter((e) => e.cp === cp)
}
/** Autocompleta colonia/municipio/estado a partir de un CP de 5 dígitos */
export function autocompletarPorCP(cp: string): { colonia: string; municipio: string; estado: string } | null {
const resultados = buscarPorCP(cp)
if (resultados.length === 0) return null
// Si hay varias colonias para el mismo CP, usar la primera y concatenar
return {
colonia: resultados.map((r) => r.colonia).join(', '),
municipio: resultados[0].municipio,
estado: resultados[0].estado,
}
}