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:
@@ -31,6 +31,20 @@ export interface ResumenReportes {
|
||||
nuevosSemana: number // últimos 7 días naturales
|
||||
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
|
||||
@@ -77,6 +91,26 @@ function contarPor(items: readonly string[]): Record<string, number> {
|
||||
return out
|
||||
}
|
||||
|
||||
/** Igual que contarPor pero sin bucket '(sin dato)': los vacíos se saltan (perfilamiento) */
|
||||
function contarPorNoVacios(items: readonly string[]): Record<string, number> {
|
||||
const out: Record<string, number> = {}
|
||||
for (const item of items) {
|
||||
const clave = item.trim()
|
||||
if (clave === '') continue
|
||||
out[clave] = (out[clave] ?? 0) + 1
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
/** Bucket de edad para la gráfica de perfilamiento; null (sin dato) se salta */
|
||||
function bucketEdad(edad: number): string {
|
||||
if (edad < 25) return '15–24'
|
||||
if (edad < 35) return '25–34'
|
||||
if (edad < 45) return '35–44'
|
||||
if (edad < 55) return '45–54'
|
||||
return '55+'
|
||||
}
|
||||
|
||||
/** Inicio del día local de `ahora` (para "nuevos hoy") */
|
||||
function inicioDelDia(d: Date): Date {
|
||||
return new Date(d.getFullYear(), d.getMonth(), d.getDate())
|
||||
@@ -153,6 +187,21 @@ export function construirResumen(datos: DatosResumen, ahora: Date): ResumenRepor
|
||||
nuevosSemana: creadosEn(hace7dias),
|
||||
porEstado: contarPor(datos.leads.map((l) => l.estado)),
|
||||
porOrigen: contarPor(datos.leads.map((l) => l.origen)),
|
||||
perfil: {
|
||||
porDesarrollo: contarPorNoVacios(datos.leads.map((l) => l.desarrollo ?? '')),
|
||||
porModelo: contarPorNoVacios(datos.leads.map((l) => l.modeloInteres ?? '')),
|
||||
porEstadoCivil: contarPorNoVacios(datos.leads.map((l) => l.estadoCivil ?? '')),
|
||||
porTipoEmpleo: contarPorNoVacios(datos.leads.map((l) => l.tipoEmpleo ?? '')),
|
||||
porTipoEconomia: contarPorNoVacios(datos.leads.map((l) => l.tipoEconomia ?? '')),
|
||||
porCalifica: contarPorNoVacios(datos.leads.map((l) => l.califica ?? '')),
|
||||
porCredito: contarPorNoVacios(datos.leads.map((l) => l.credito ?? '')),
|
||||
porRangoSalarial: contarPorNoVacios(datos.leads.map((l) => l.rangoSalarial ?? '')),
|
||||
porMedioContacto: contarPorNoVacios(datos.leads.map((l) => l.medioContacto ?? '')),
|
||||
porLoQuierePara: contarPorNoVacios(datos.leads.map((l) => l.loQuierePara ?? '')),
|
||||
porEdad: contarPorNoVacios(
|
||||
datos.leads.filter((l) => l.edad != null).map((l) => bucketEdad(l.edad!)),
|
||||
),
|
||||
},
|
||||
},
|
||||
oportunidades: {
|
||||
total: totalOps,
|
||||
|
||||
Reference in New Issue
Block a user