feat(mi-dia): seccion 'Mis capturas de afluencia' en el panel del vendedor

Nueva tabla LeadCaptura (quien capturo cada lead, algo que Monday no
guarda porque el creador del item siempre es el token del API). El
POST /api/leads la registra con el usuario del token y el resumen de
Mi dia la enriquece con el estado/origen actual del lead en Monday.
La captura rapida refresca el panel al registrar.
This commit is contained in:
2026-09-02 02:13:13 +00:00
parent 57029bdd3d
commit 895a831ff8
9 changed files with 266 additions and 11 deletions

View File

@@ -5,6 +5,8 @@ import type { MondayService } from './monday.service.js'
* Todo se filtra por su mondayUserId (columna people de cada tablero); si el
* usuario aún no tiene cuenta Monday (mondayUserId null), las listas personales
* van vacías con sinMapeoMonday: true (las acciones del CRM siguen funcionando).
* "Mis capturas" viene del registro local (tabla LeadCaptura), no de Monday:
* es la lista de leads que el usuario dio de alta, con su estado actual.
*/
// Estados del board Leads en los que el lead ya no es trabajo activo del vendedor
@@ -48,10 +50,20 @@ export interface ApartadoPorVencer {
diasRestantes: number
}
export interface CapturaMiDia {
leadId: string
nombre: string
telefono: string
estado: string // estado ACTUAL en Monday; '' si el lead ya no existe en el board
origen: string
capturadoEn: string // ISO
}
export interface ResumenMiDia {
generadoEn: string
sinMapeoMonday: boolean
misLeads: LeadMiDia[]
misCapturas: CapturaMiDia[]
misActividadesHoy: ActividadMiDia[]
apartadosPorVencer: ApartadoPorVencer[]
// null = el tablero Oportunidades no tiene columna people de asesor (hoy no existe)
@@ -100,6 +112,8 @@ export async function resumenMiDia(
usuario: UsuarioMiDia,
/** mondayUserId -> nombre para mostrar (usuarios del ecosistema ya mapeados) */
nombresPorMondayId: Map<number, string>,
/** Capturas locales del usuario (tabla LeadCaptura), ya filtradas por él */
capturas: { leadId: string; nombreLead: string; telefono: string | null; createdAt: Date }[] = [],
ahora: Date = new Date(),
): Promise<ResumenMiDia> {
const miId = usuario.mondayUserId
@@ -130,6 +144,23 @@ export async function resumenMiDia(
updatedAt: l.updatedAt,
}))
// "Mis capturas" se enriquece con el estado/origen ACTUAL del lead en Monday
// (si ya no existe en el board, estado y origen van ''), más reciente primero
const leadsPorId = new Map(leads.map((l) => [l.id, l]))
const misCapturas: CapturaMiDia[] = capturas
.map((c) => {
const lead = leadsPorId.get(c.leadId)
return {
leadId: c.leadId,
nombre: c.nombreLead,
telefono: c.telefono ?? '',
estado: lead?.estado ?? '',
origen: lead?.origen ?? '',
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)
@@ -164,6 +195,7 @@ export async function resumenMiDia(
generadoEn: ahora.toISOString(),
sinMapeoMonday: sinMapeo,
misLeads,
misCapturas,
misActividadesHoy,
apartadosPorVencer,
// El tablero Oportunidades no tiene columna people de asesor: no se puede