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:
@@ -83,7 +83,7 @@ describe('resumenMiDia (GET /api/mi-dia/resumen)', () => {
|
||||
lead({ id: '6', estado: 'NUEVO', vendedorIds: [OTRO_ID] }),
|
||||
],
|
||||
})
|
||||
const r = await resumenMiDia(monday, vendedor, new Map(), [], AHORA)
|
||||
const r = await resumenMiDia(monday, vendedor, new Map(), [], [], AHORA)
|
||||
expect(r.sinMapeoMonday).toBe(false)
|
||||
expect(r.misLeads.map((l) => l.id)).toEqual(['1', '2'])
|
||||
expect(r.misLeads[1]?.telefono).toBe('4771234567')
|
||||
@@ -101,7 +101,7 @@ describe('resumenMiDia (GET /api/mi-dia/resumen)', () => {
|
||||
actividad({ id: 'a7', fecha: '' }), // sin fecha: no
|
||||
],
|
||||
})
|
||||
const r = await resumenMiDia(monday, vendedor, new Map(), [], AHORA)
|
||||
const r = await resumenMiDia(monday, vendedor, new Map(), [], [], AHORA)
|
||||
// Ordenadas por fecha: vencida primero
|
||||
expect(r.misActividadesHoy.map((a) => a.id)).toEqual(['a3', 'a1', 'a2'])
|
||||
expect(r.conteos.actividadesAbiertas).toBe(5) // abiertas mías: a1,a2,a3,a4,a7
|
||||
@@ -118,7 +118,7 @@ describe('resumenMiDia (GET /api/mi-dia/resumen)', () => {
|
||||
apartado({ id: 'p6', fechaLimite: '2026-09-01', estado: 'Cancelacion' }),
|
||||
],
|
||||
})
|
||||
const r = await resumenMiDia(monday, vendedor, new Map(), [], AHORA)
|
||||
const r = await resumenMiDia(monday, vendedor, new Map(), [], [], AHORA)
|
||||
expect(r.apartadosPorVencer.map((a) => a.id)).toEqual(['p1', 'p2'])
|
||||
expect(r.apartadosPorVencer[0]?.diasRestantes).toBe(0)
|
||||
expect(r.apartadosPorVencer[1]?.diasRestantes).toBe(7)
|
||||
@@ -130,7 +130,7 @@ describe('resumenMiDia (GET /api/mi-dia/resumen)', () => {
|
||||
actividades: [actividad({ id: 'a1' })],
|
||||
apartados: [apartado({ id: 'p1' })],
|
||||
})
|
||||
const r = await resumenMiDia(monday, sinMapeo, new Map(), [], AHORA)
|
||||
const r = await resumenMiDia(monday, sinMapeo, new Map(), [], [], AHORA)
|
||||
expect(r.sinMapeoMonday).toBe(true)
|
||||
expect(r.misLeads).toEqual([])
|
||||
expect(r.misActividadesHoy).toEqual([])
|
||||
@@ -148,12 +148,12 @@ describe('resumenMiDia (GET /api/mi-dia/resumen)', () => {
|
||||
lead({ id: '4', createdAt: '2026-08-27T08:00:00', vendedorIds: [OTRO_ID] }), // de otro
|
||||
],
|
||||
})
|
||||
const r = await resumenMiDia(monday, vendedor, new Map(), [], AHORA)
|
||||
const r = await resumenMiDia(monday, vendedor, new Map(), [], [], AHORA)
|
||||
expect(r.conteos.leadsNuevosSemana).toBe(2)
|
||||
})
|
||||
|
||||
it('oportunidades se omiten con nota (el board no tiene columna de asesor)', async () => {
|
||||
const r = await resumenMiDia(mondayMock({}), vendedor, new Map(), [], AHORA)
|
||||
const r = await resumenMiDia(mondayMock({}), vendedor, new Map(), [], [], AHORA)
|
||||
expect(r.misOportunidades).toBeNull()
|
||||
expect(r.notaOportunidades).toContain('asesor')
|
||||
})
|
||||
@@ -171,10 +171,10 @@ describe('resumenMiDia (GET /api/mi-dia/resumen)', () => {
|
||||
})
|
||||
const nombres = new Map([[MI_ID, 'Martin Zavala']])
|
||||
|
||||
const rVendedor = await resumenMiDia(monday, vendedor, nombres, [], AHORA)
|
||||
const rVendedor = await resumenMiDia(monday, vendedor, nombres, [], [], AHORA)
|
||||
expect(rVendedor.equipo).toBeUndefined()
|
||||
|
||||
const rCoord = await resumenMiDia(monday, coordinador, nombres, [], AHORA)
|
||||
const rCoord = await resumenMiDia(monday, coordinador, nombres, [], [], AHORA)
|
||||
expect(rCoord.equipo?.leadsNuevosSinAsignar).toBe(1)
|
||||
expect(rCoord.equipo?.porVendedor).toEqual([
|
||||
{ mondayUserId: OTRO_ID, nombre: null, leadsAsignados: 2, leadsNuevosSemana: 1 },
|
||||
@@ -185,7 +185,7 @@ describe('resumenMiDia (GET /api/mi-dia/resumen)', () => {
|
||||
it('corporativo y admin también ven equipo', async () => {
|
||||
const monday = mondayMock({ leads: [lead({ id: '1' })] })
|
||||
for (const rol of ['corporativo', 'admin']) {
|
||||
const r = await resumenMiDia(monday, { nombre: 'X', rol, mondayUserId: null }, new Map(), [], AHORA)
|
||||
const r = await resumenMiDia(monday, { nombre: 'X', rol, mondayUserId: null }, new Map(), [], [], AHORA)
|
||||
expect(r.equipo).toBeDefined()
|
||||
}
|
||||
})
|
||||
@@ -197,7 +197,7 @@ describe('resumenMiDia (GET /api/mi-dia/resumen)', () => {
|
||||
const capturas = [
|
||||
{ leadId: '100', nombreLead: 'Ana López', telefono: '6641112233', createdAt: new Date('2026-08-27T09:00:00Z') },
|
||||
]
|
||||
const r = await resumenMiDia(monday, vendedor, new Map(), capturas, AHORA)
|
||||
const r = await resumenMiDia(monday, vendedor, new Map(), capturas, [], AHORA)
|
||||
// Aunque el lead quedó asignado a OTRO por round-robin, la captura es mía
|
||||
expect(r.misCapturas).toEqual([
|
||||
{
|
||||
@@ -216,7 +216,7 @@ describe('resumenMiDia (GET /api/mi-dia/resumen)', () => {
|
||||
const capturas = [
|
||||
{ leadId: 'borrado-1', nombreLead: 'Lead Borrado', telefono: null, createdAt: new Date('2026-08-26T09:00:00Z') },
|
||||
]
|
||||
const r = await resumenMiDia(monday, vendedor, new Map(), capturas, AHORA)
|
||||
const r = await resumenMiDia(monday, vendedor, new Map(), capturas, [], AHORA)
|
||||
expect(r.misCapturas).toEqual([
|
||||
{ leadId: 'borrado-1', nombre: 'Lead Borrado', telefono: '', estado: '', origen: '', capturadoEn: '2026-08-26T09:00:00.000Z' },
|
||||
])
|
||||
@@ -228,14 +228,65 @@ describe('resumenMiDia (GET /api/mi-dia/resumen)', () => {
|
||||
{ leadId: '1', nombreLead: 'Antiguo', telefono: null, createdAt: new Date('2026-08-20T09:00:00Z') },
|
||||
{ leadId: '2', nombreLead: 'Reciente', telefono: null, createdAt: new Date('2026-08-27T09:00:00Z') },
|
||||
]
|
||||
const r = await resumenMiDia(monday, vendedor, new Map(), capturas, AHORA)
|
||||
const r = await resumenMiDia(monday, vendedor, new Map(), capturas, [], AHORA)
|
||||
expect(r.misCapturas.map((c) => c.leadId)).toEqual(['2', '1'])
|
||||
})
|
||||
|
||||
it('sin capturas: misCapturas va vacío', async () => {
|
||||
const r = await resumenMiDia(mondayMock({}), vendedor, new Map(), [], AHORA)
|
||||
const r = await resumenMiDia(mondayMock({}), vendedor, new Map(), [], [], AHORA)
|
||||
expect(r.misCapturas).toEqual([])
|
||||
})
|
||||
|
||||
it('mis clientes: mapeo con completado según proyecto (vacío = en captura)', async () => {
|
||||
const clientes = [
|
||||
{
|
||||
id: 'c1',
|
||||
nombreCompleto: 'Ana En Captura',
|
||||
proyecto: '',
|
||||
unidadCodigo: null,
|
||||
createdAt: new Date('2026-08-26T09:00:00Z'),
|
||||
},
|
||||
{
|
||||
id: 'c2',
|
||||
nombreCompleto: 'Luis Completado',
|
||||
proyecto: 'DIAGONAL',
|
||||
unidadCodigo: 'U-101',
|
||||
createdAt: new Date('2026-08-27T09:00:00Z'),
|
||||
},
|
||||
]
|
||||
const r = await resumenMiDia(mondayMock({}), vendedor, new Map(), [], clientes, AHORA)
|
||||
expect(r.misClientes).toEqual([
|
||||
{
|
||||
id: 'c2',
|
||||
nombre: 'Luis Completado',
|
||||
proyecto: 'DIAGONAL',
|
||||
unidadCodigo: 'U-101',
|
||||
completado: true,
|
||||
capturadoEn: '2026-08-27T09:00:00.000Z',
|
||||
},
|
||||
{
|
||||
id: 'c1',
|
||||
nombre: 'Ana En Captura',
|
||||
proyecto: '',
|
||||
unidadCodigo: '',
|
||||
completado: false,
|
||||
capturadoEn: '2026-08-26T09:00:00.000Z',
|
||||
},
|
||||
])
|
||||
})
|
||||
|
||||
it('mis clientes: proyecto con solo espacios cuenta como en captura', async () => {
|
||||
const clientes = [
|
||||
{ id: 'c1', nombreCompleto: 'Espacios', proyecto: ' ', unidadCodigo: null, createdAt: new Date('2026-08-26T09:00:00Z') },
|
||||
]
|
||||
const r = await resumenMiDia(mondayMock({}), vendedor, new Map(), [], clientes, AHORA)
|
||||
expect(r.misClientes[0]?.completado).toBe(false)
|
||||
})
|
||||
|
||||
it('sin clientes: misClientes va vacío (no requiere Monday)', async () => {
|
||||
const r = await resumenMiDia(mondayMock({}), vendedor, new Map(), [], [], AHORA)
|
||||
expect(r.misClientes).toEqual([])
|
||||
})
|
||||
})
|
||||
|
||||
describe('clasificadores de estado', () => {
|
||||
|
||||
Reference in New Issue
Block a user