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

@@ -59,11 +59,21 @@ export interface CapturaMiDia {
capturadoEn: string // ISO
}
export interface ClienteMiDia {
id: string
nombre: string
proyecto: string // '' = alta aún en captura
unidadCodigo: string
completado: boolean // true cuando el alta ya tiene proyecto (pasó a CLIENTE)
capturadoEn: string // ISO
}
export interface ResumenMiDia {
generadoEn: string
sinMapeoMonday: boolean
misLeads: LeadMiDia[]
misCapturas: CapturaMiDia[]
misClientes: ClienteMiDia[]
misActividadesHoy: ActividadMiDia[]
apartadosPorVencer: ApartadoPorVencer[]
// null = el tablero Oportunidades no tiene columna people de asesor (hoy no existe)
@@ -114,6 +124,8 @@ export async function resumenMiDia(
nombresPorMondayId: Map<number, string>,
/** Capturas locales del usuario (tabla LeadCaptura), ya filtradas por él */
capturas: { leadId: string; nombreLead: string; telefono: string | null; createdAt: Date }[] = [],
/** Clientes cuyo alta inició el usuario (tabla Cliente), ya filtrados por él */
clientes: { id: string; nombreCompleto: string; proyecto: string; unidadCodigo: string | null; createdAt: Date }[] = [],
ahora: Date = new Date(),
): Promise<ResumenMiDia> {
const miId = usuario.mondayUserId
@@ -161,6 +173,19 @@ export async function resumenMiDia(
})
.sort((a, b) => b.capturadoEn.localeCompare(a.capturadoEn))
// "Mis clientes" es 100% local (no requiere Monday): el alta en progreso
// lleva proyecto '', el alta completada lleva el proyecto lleno
const misClientes: ClienteMiDia[] = clientes
.map((c) => ({
id: c.id,
nombre: c.nombreCompleto,
proyecto: c.proyecto,
unidadCodigo: c.unidadCodigo ?? '',
completado: c.proyecto.trim() !== '',
capturadoEn: c.createdAt.toISOString(),
}))
.sort((a, b) => b.capturadoEn.localeCompare(a.capturadoEn))
const misActividades = sinMapeo ? [] : actividades.filter((a) => a.responsableIds.includes(miId))
const misActividadesHoy: ActividadMiDia[] = misActividades
.filter((a) => a.estado.trim().toUpperCase() === 'ABIERTO' && a.fecha !== '' && a.fecha <= manana)
@@ -196,6 +221,7 @@ export async function resumenMiDia(
sinMapeoMonday: sinMapeo,
misLeads,
misCapturas,
misClientes,
misActividadesHoy,
apartadosPorVencer,
// El tablero Oportunidades no tiene columna people de asesor: no se puede