feat: mis clientes en Mi dia + perfilamiento de la afluencia en dashboards

- Cliente.capturadoPorId: el alta (directa y progresiva) registra al
  usuario que la inicio; el panel Mi dia muestra 'Mis clientes' con su
  estado (En captura / Cliente) sin depender de Monday.
- LeadMonday ahora lee las 11 columnas de perfil del tablero Leads
  (estado civil, edad, rango salarial, tipo de empleo/economia,
  califica, credito, modelo, etc.).
- GET /api/reportes/resumen agrega leads.perfil con conteos por
  dimension (valores vacios se omiten; edad en buckets).
- /dash/ gana la seccion 'Perfilamiento de la afluencia' con
  mini-graficas de barras por dimension.
This commit is contained in:
2026-09-02 02:39:19 +00:00
parent 895a831ff8
commit 4030faee25
18 changed files with 551 additions and 14 deletions

View File

@@ -5,6 +5,7 @@ import {
datosPipeline,
formatoMoneda,
formatoPorcentaje,
rebanadasDe,
unidadesApiladas,
unidadesEnEtapa,
type ResumenReportes,
@@ -13,7 +14,26 @@ import {
function resumen(overrides: Partial<ResumenReportes> = {}): ResumenReportes {
return {
generadoEn: '2026-08-22T12:00:00.000Z',
leads: { total: 40, nuevosHoy: 3, nuevosSemana: 12, porEstado: {}, porOrigen: {} },
leads: {
total: 40,
nuevosHoy: 3,
nuevosSemana: 12,
porEstado: {},
porOrigen: {},
perfil: {
porDesarrollo: { ATELIER: 10, DIAGONAL: 8 },
porModelo: {},
porEstadoCivil: { Soltero: 15, Casado: 9 },
porTipoEmpleo: {},
porTipoEconomia: {},
porCalifica: { Si: 20 },
porCredito: {},
porRangoSalarial: {},
porMedioContacto: {},
porLoQuierePara: {},
porEdad: { '25–34': 12, '15–24': 5 },
},
},
oportunidades: {
total: 20,
activas: 15,
@@ -121,6 +141,20 @@ describe('datosCobranza', () => {
})
})
describe('rebanadasDe (perfilamiento)', () => {
it('ordena desc y filtra los valores en 0', () => {
expect(rebanadasDe({ Soltero: 15, Casado: 9, Viudo: 0, 'Union Libre': 22 })).toEqual([
{ nombre: 'Union Libre', valor: 22 },
{ nombre: 'Soltero', valor: 15 },
{ nombre: 'Casado', valor: 9 },
])
})
it('registro vacío devuelve lista vacía (la tarjeta muestra "Sin datos")', () => {
expect(rebanadasDe({})).toEqual([])
})
})
describe('formatos', () => {
it('moneda MXN sin centavos', () => {
expect(formatoMoneda(125000)).toBe('$125,000')