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:
@@ -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')
|
||||
|
||||
@@ -12,6 +12,20 @@ export interface ResumenReportes {
|
||||
nuevosSemana: number
|
||||
porEstado: Record<string, number>
|
||||
porOrigen: Record<string, number>
|
||||
/** Perfilamiento de la afluencia: solo valores capturados (sin '(sin dato)') */
|
||||
perfil: {
|
||||
porDesarrollo: Record<string, number>
|
||||
porModelo: Record<string, number>
|
||||
porEstadoCivil: Record<string, number>
|
||||
porTipoEmpleo: Record<string, number>
|
||||
porTipoEconomia: Record<string, number>
|
||||
porCalifica: Record<string, number>
|
||||
porCredito: Record<string, number>
|
||||
porRangoSalarial: Record<string, number>
|
||||
porMedioContacto: Record<string, number>
|
||||
porLoQuierePara: Record<string, number>
|
||||
porEdad: Record<string, number> // buckets '15–24' | '25–34' | '35–44' | '45–54' | '55+'
|
||||
}
|
||||
}
|
||||
oportunidades: {
|
||||
total: number
|
||||
@@ -121,6 +135,17 @@ export function datosCobranza(resumen: ResumenReportes): Rebanada[] {
|
||||
]
|
||||
}
|
||||
|
||||
/**
|
||||
* Rebanadas de una dimensión de perfilamiento (barras horizontales): sin
|
||||
* valores en 0, ordenadas de mayor a menor.
|
||||
*/
|
||||
export function rebanadasDe(registro: Record<string, number>): Rebanada[] {
|
||||
return Object.entries(registro)
|
||||
.filter(([, valor]) => valor > 0)
|
||||
.map(([nombre, valor]) => ({ nombre, valor }))
|
||||
.sort((a, b) => b.valor - a.valor)
|
||||
}
|
||||
|
||||
const formatoMXN = new Intl.NumberFormat('es-MX', { style: 'currency', currency: 'MXN', maximumFractionDigits: 0 })
|
||||
|
||||
/** Formato monetario MXN sin centavos: $1,250,000 */
|
||||
|
||||
Reference in New Issue
Block a user