import { describe, expect, it } from 'vitest' import { detalleEventoUltimoCambio, esEstadoFinalApartado, esEstadoTerminalLead, esEtapaTerminal, resumenMiDia, type UsuarioMiDia, } from './mi-dia.service.js' import type { ActividadMonday, ApartadoMonday, LeadMonday, OportunidadMonday, UltimoCambioMonday, } from './monday.service.js' const MI_ID = 106736727 const OTRO_ID = 111111111 const AHORA = new Date('2026-08-28T12:00:00') // viernes const HOY = '2026-08-28' const MANANA = '2026-08-29' function lead(parcial: Partial & { id: string }): LeadMonday { return { nombre: `Lead ${parcial.id}`, createdAt: '2026-08-25T10:00:00Z', updatedAt: '2026-08-27T10:00:00Z', estado: 'Nuevo', origen: 'Facebook', telefono: '', email: '', vendedorIds: [MI_ID], ...parcial, } } function oportunidad(parcial: Partial & { id: string }): OportunidadMonday { return { nombre: `Lead ${parcial.id}`, createdAt: '2026-08-25T10:00:00Z', updatedAt: '2026-08-27T10:00:00Z', etapa: 'Afluencia', origen: 'Facebook', unidadCodigo: '', precioFinal: 0, asesorIds: [MI_ID], telefono: '', email: '', ...parcial, } } function actividad(parcial: Partial & { id: string }): ActividadMonday { return { nombre: `Actividad ${parcial.id}`, tipo: 'Llamada', fecha: HOY, estado: 'Abierto', responsableIds: [MI_ID], ...parcial, } } function apartado(parcial: Partial & { id: string }): ApartadoMonday { return { nombre: `Cliente ${parcial.id}`, unidadCodigo: 'U-101', fechaLimite: '2026-09-02', // dentro de 7 días estado: 'Pago Apartado', vendedorIds: [MI_ID], ...parcial, } } function mondayMock(datos: { leads?: LeadMonday[] oportunidades?: OportunidadMonday[] actividades?: ActividadMonday[] apartados?: ApartadoMonday[] /** boardId → (itemId → último cambio); boards de test: Leads '444', Oportunidades '555' */ ultimosCambios?: Record> }) { return { async getLeads() { return datos.leads ?? [] }, async getOportunidades() { return datos.oportunidades ?? [] }, async getActividades() { return datos.actividades ?? [] }, async getApartados() { return datos.apartados ?? [] }, async getUltimosCambios(boardId: string, _itemIds: string[]) { return datos.ultimosCambios?.[boardId] ?? new Map() }, } } const vendedor: UsuarioMiDia = { nombre: 'Martin Zavala', rol: 'vendedor', mondayUserId: MI_ID } const sinMapeo: UsuarioMiDia = { nombre: 'Erick Peña', rol: 'vendedor', mondayUserId: null } const coordinador: UsuarioMiDia = { nombre: 'Daniel Alvarado', rol: 'coordinador', mondayUserId: null } describe('resumenMiDia (GET /api/mi-dia/resumen)', () => { it('misLeads: merge de Leads (Vendedor=mío) y Oportunidades (Asesor=mío), sin terminales, con fuente, por updatedAt desc', async () => { const monday = mondayMock({ leads: [ lead({ id: 'l1', estado: 'Nuevo', updatedAt: '2026-08-26T10:00:00Z' }), lead({ id: 'l2', estado: 'Contactado', telefono: '4771234567', updatedAt: '2026-08-28T08:00:00Z' }), lead({ id: 'l3', estado: 'Descartado' }), // terminal Leads: fuera lead({ id: 'l4', estado: 'Calificado' }), // terminal Leads: fuera lead({ id: 'l5', estado: 'Nuevo', vendedorIds: [OTRO_ID] }), // de otro: fuera ], oportunidades: [ oportunidad({ id: 'o1', etapa: 'Afluencia', updatedAt: '2026-08-27T10:00:00Z' }), oportunidad({ id: 'o2', etapa: 'Calificado' }), // NO es terminal en Oportunidades oportunidad({ id: 'o3', etapa: 'Perdido' }), oportunidad({ id: 'o4', etapa: 'Abandono' }), oportunidad({ id: 'o5', etapa: 'Ganado' }), oportunidad({ id: 'o6', etapa: 'Afluencia', asesorIds: [OTRO_ID] }), ], }) const r = await resumenMiDia(monday, vendedor, new Map(), [], [], AHORA) expect(r.sinMapeoMonday).toBe(false) expect(r.misLeads.map((l) => l.id)).toEqual(['l2', 'o1', 'o2', 'l1']) // updatedAt desc mezclando tableros expect(r.misLeads.map((l) => l.fuente)).toEqual(['lead', 'oportunidad', 'oportunidad', 'lead']) expect(r.misLeads[0]?.telefono).toBe('4771234567') }) it('ultimoCambio: último evento por item, agrupando ids por fuente y resolviendo el nombre', async () => { const monday = mondayMock({ leads: [lead({ id: 'l1', updatedAt: '2026-08-28T08:00:00Z' })], oportunidades: [oportunidad({ id: 'o1', updatedAt: '2026-08-27T08:00:00Z' })], ultimosCambios: { '444': new Map([['l1', { fechaISO: '2026-08-28T10:00:00.000Z', usuarioId: MI_ID, evento: 'update_status' }]]), '555': new Map([['o1', { fechaISO: '2026-08-28T09:00:00.000Z', usuarioId: OTRO_ID, evento: 'create_pulse' }]]), }, }) const nombres = new Map([[MI_ID, 'Martin Zavala']]) // OTRO_ID no está mapeado const r = await resumenMiDia(monday, vendedor, nombres, [], [], AHORA) expect(r.misLeads.find((l) => l.id === 'l1')?.ultimoCambio).toEqual({ fechaISO: '2026-08-28T10:00:00.000Z', usuario: 'Martin Zavala', detalle: 'Actualización', }) // usuarioId sin nombre en el mapa → fallback null expect(r.misLeads.find((l) => l.id === 'o1')?.ultimoCambio).toEqual({ fechaISO: '2026-08-28T09:00:00.000Z', usuario: null, detalle: 'Alta', }) }) it('ultimoCambio: null cuando el item no tiene eventos en el activity log', async () => { const monday = mondayMock({ leads: [lead({ id: 'l1' })], ultimosCambios: { '444': new Map() }, // sin eventos }) const r = await resumenMiDia(monday, vendedor, new Map(), [], [], AHORA) expect(r.misLeads[0]?.ultimoCambio).toBeNull() }) it('ultimoCambio: usuarioId null (evento de automatización) → usuario null', async () => { const monday = mondayMock({ leads: [lead({ id: 'l1' })], ultimosCambios: { '444': new Map([['l1', { fechaISO: '2026-08-28T10:00:00.000Z', usuarioId: null, evento: 'move_to_group' }]]), }, }) const r = await resumenMiDia(monday, vendedor, new Map(), [], [], AHORA) expect(r.misLeads[0]?.ultimoCambio).toEqual({ fechaISO: '2026-08-28T10:00:00.000Z', usuario: null, detalle: 'Cambio de grupo', }) }) it('detalleEventoUltimoCambio: humaniza los eventos conocidos y pasa el crudo si no', () => { expect(detalleEventoUltimoCambio('create_pulse')).toBe('Alta') expect(detalleEventoUltimoCambio('create_update')).toBe('Nota agregada') expect(detalleEventoUltimoCambio('update_column_value')).toBe('Actualización') expect(detalleEventoUltimoCambio('update_status')).toBe('Actualización') expect(detalleEventoUltimoCambio('move_to_group')).toBe('Cambio de grupo') expect(detalleEventoUltimoCambio('update_name')).toBe('update_name') }) it('misOportunidades: conteo de MIS oportunidades por etapa (incluye terminales)', async () => { const monday = mondayMock({ oportunidades: [ oportunidad({ id: '1', etapa: 'Afluencia' }), oportunidad({ id: '2', etapa: 'Afluencia' }), oportunidad({ id: '3', etapa: 'Ganado' }), oportunidad({ id: '4', etapa: 'Afluencia', asesorIds: [OTRO_ID] }), // de otro: no cuenta ], }) const r = await resumenMiDia(monday, vendedor, new Map(), [], [], AHORA) expect(r.misOportunidades.porEtapa).toEqual({ Afluencia: 2, Ganado: 1 }) }) it('misOportunidades.items: lista de trabajo sin terminales, mías, por updatedAt desc, con perfil y último cambio', async () => { const monday = mondayMock({ oportunidades: [ oportunidad({ id: 'o1', etapa: 'Afluencia', updatedAt: '2026-08-27T10:00:00Z', desarrollo: 'SENDA', modeloInteres: 'SOHO' }), oportunidad({ id: 'o2', etapa: 'Cotizador', updatedAt: '2026-08-28T08:00:00Z', telefono: '4771234567', email: 'a@b.c', unidadCodigo: 'U-9' }), oportunidad({ id: 'o3', etapa: 'Ganado' }), // terminal: fuera de la lista (sí cuenta en porEtapa) oportunidad({ id: 'o4', etapa: 'Abandono' }), // terminal: fuera oportunidad({ id: 'o5', etapa: 'Afluencia', asesorIds: [OTRO_ID] }), // de otro: fuera oportunidad({ id: 'o6', etapa: 'Aparto' }), // ya es apartado: sale de la lista (sigue en Mis apartados) ], ultimosCambios: { '555': new Map([['o2', { fechaISO: '2026-08-28T09:00:00.000Z', usuarioId: MI_ID, evento: 'update_column_value' }]]), }, }) const nombres = new Map([[MI_ID, 'Martin Zavala']]) const r = await resumenMiDia(monday, vendedor, nombres, [], [], AHORA) expect(r.misOportunidades.items.map((o) => o.id)).toEqual(['o2', 'o1']) const o2 = r.misOportunidades.items[0] expect(o2?.etapa).toBe('Cotizador') expect(o2?.telefono).toBe('4771234567') expect(o2?.email).toBe('a@b.c') expect(o2?.unidadCodigo).toBe('U-9') expect(o2?.ultimoCambio).toEqual({ fechaISO: '2026-08-28T09:00:00.000Z', usuario: 'Martin Zavala', detalle: 'Actualización', }) const o1 = r.misOportunidades.items[1] expect(o1?.desarrollo).toBe('SENDA') expect(o1?.modeloInteres).toBe('SOHO') expect(o1?.ultimoCambio).toBeNull() }) it('misApartados: Vendedor = yo, sin estados finales, por fecha límite asc (sin fecha al final), con último cambio', async () => { const monday = mondayMock({ apartados: [ apartado({ id: 'p1', fechaLimite: '2026-09-05' }), apartado({ id: 'p2', fechaLimite: '2026-09-01', estado: 'Cita Clientes' }), apartado({ id: 'p3', fechaLimite: '', estado: 'Alta Enkontrol' }), apartado({ id: 'p4', estado: 'Firma Cliente (Venta)' }), // final: fuera apartado({ id: 'p5', estado: 'Cancelacion Apartado' }), // final: fuera apartado({ id: 'p6', vendedorIds: [OTRO_ID] }), // de otro: fuera ], ultimosCambios: { '666': new Map([['p1', { fechaISO: '2026-08-28T09:00:00.000Z', usuarioId: OTRO_ID, evento: 'create_update' }]]), }, }) const r = await resumenMiDia(monday, vendedor, new Map(), [], [], AHORA) expect(r.misApartados.map((a) => a.id)).toEqual(['p2', 'p1', 'p3']) expect(r.misApartados[0]?.estado).toBe('Cita Clientes') expect(r.misApartados[1]?.ultimoCambio).toEqual({ fechaISO: '2026-08-28T09:00:00.000Z', usuario: null, // OTRO_ID no está en el mapa de nombres detalle: 'Nota agregada', }) }) it('esEstadoFinalApartado: venta y cancelación cierran el pipeline', () => { expect(esEstadoFinalApartado('Firma Cliente (Venta)')).toBe(true) expect(esEstadoFinalApartado('Cancelacion Apartado')).toBe(true) expect(esEstadoFinalApartado('Escriturado')).toBe(true) expect(esEstadoFinalApartado('Pago Apartado')).toBe(false) expect(esEstadoFinalApartado('Contrato Enkontrol')).toBe(false) }) it('actividades de hoy: solo abiertas con fecha <= mañana y mías', async () => { const monday = mondayMock({ actividades: [ actividad({ id: 'a1', fecha: HOY }), actividad({ id: 'a2', fecha: MANANA, tipo: 'Reunion' }), actividad({ id: 'a3', fecha: '2026-08-20' }), // vencida: también entra actividad({ id: 'a4', fecha: '2026-09-05' }), // futura lejana: no actividad({ id: 'a5', fecha: HOY, estado: 'Listo' }), // cerrada: no actividad({ id: 'a6', fecha: HOY, responsableIds: [OTRO_ID] }), // de otro: no actividad({ id: 'a7', fecha: '' }), // sin fecha: no ], }) 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 }) it('apartados por vencer: próximos 7 días, excluye estados finales', async () => { const monday = mondayMock({ apartados: [ apartado({ id: 'p1', fechaLimite: HOY }), // vence hoy apartado({ id: 'p2', fechaLimite: '2026-09-04' }), // justo en 7 días apartado({ id: 'p3', fechaLimite: '2026-09-05' }), // fuera de rango apartado({ id: 'p4', fechaLimite: '2026-08-27' }), // ya venció apartado({ id: 'p5', fechaLimite: '2026-09-01', estado: 'Escriturado' }), apartado({ id: 'p6', fechaLimite: '2026-09-01', estado: 'Cancelacion' }), ], }) 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) }) it('sin mapeo Monday: listas personales vacías y sinMapeoMonday: true', async () => { const monday = mondayMock({ oportunidades: [oportunidad({ id: '1' })], actividades: [actividad({ id: 'a1' })], apartados: [apartado({ id: 'p1' })], }) const r = await resumenMiDia(monday, sinMapeo, new Map(), [], [], AHORA) expect(r.sinMapeoMonday).toBe(true) expect(r.misLeads).toEqual([]) expect(r.misOportunidades).toEqual({ porEtapa: {}, items: [] }) expect(r.misApartados).toEqual([]) expect(r.misActividadesHoy).toEqual([]) expect(r.conteos).toEqual({ leadsNuevosSemana: 0, actividadesAbiertas: 0 }) // Los apartados por vencer son del pipeline completo: sí se muestran expect(r.apartadosPorVencer).toHaveLength(0) // solo apartados propios }) it('conteos: leads nuevos de los últimos 7 días suman AMBOS tableros (incluye terminales recientes)', async () => { const monday = mondayMock({ leads: [ lead({ id: 'l1', createdAt: '2026-08-28T08:00:00' }), // hoy lead({ id: 'l2', createdAt: '2026-08-10T08:00:00' }), // viejo ], oportunidades: [ oportunidad({ id: '1', createdAt: '2026-08-28T08:00:00' }), // hoy oportunidad({ id: '2', createdAt: '2026-08-22T08:00:00' }), // hace 6 días oportunidad({ id: '3', createdAt: '2026-08-10T08:00:00' }), // viejo oportunidad({ id: '4', createdAt: '2026-08-27T08:00:00', asesorIds: [OTRO_ID] }), // de otro ], }) const r = await resumenMiDia(monday, vendedor, new Map(), [], [], AHORA) expect(r.conteos.leadsNuevosSemana).toBe(3) // 1 lead + 2 oportunidades }) it('vendedor no ve sección equipo; coordinador sí (conteos por persona de AMBOS tableros y sin asignar)', async () => { const monday = mondayMock({ leads: [ lead({ id: 'l1', vendedorIds: [] }), // sin asignar, Nuevo lead({ id: 'l2', vendedorIds: [], estado: 'Contactado' }), // sin asignar, no Nuevo lead({ id: 'l3', vendedorIds: [OTRO_ID] }), ], oportunidades: [ oportunidad({ id: '1', asesorIds: [] }), // sin asignar, Afluencia oportunidad({ id: '2', asesorIds: [], etapa: 'Contactado' }), // sin asignar, no Afluencia oportunidad({ id: '3', asesorIds: [MI_ID] }), oportunidad({ id: '4', asesorIds: [MI_ID], etapa: 'Perdido' }), // terminal: no cuenta oportunidad({ id: '5', asesorIds: [OTRO_ID] }), oportunidad({ id: '6', asesorIds: [OTRO_ID], createdAt: '2026-08-10T08:00:00' }), // viejo: no es nuevo de la semana ], }) const nombres = new Map([[MI_ID, 'Martin Zavala']]) const rVendedor = await resumenMiDia(monday, vendedor, nombres, [], [], AHORA) expect(rVendedor.equipo).toBeUndefined() const rCoord = await resumenMiDia(monday, coordinador, nombres, [], [], AHORA) expect(rCoord.equipo?.leadsNuevosSinAsignar).toBe(2) // 1 lead Nuevo + 1 afluencia sin asignar expect(rCoord.equipo?.porVendedor).toEqual([ { mondayUserId: OTRO_ID, nombre: null, leadsAsignados: 3, leadsNuevosSemana: 2 }, // 1 lead + 2 ops (1 vieja) { mondayUserId: MI_ID, nombre: 'Martin Zavala', leadsAsignados: 1, leadsNuevosSemana: 1 }, ]) }) it('corporativo y admin también ven equipo', async () => { const monday = mondayMock({ oportunidades: [oportunidad({ id: '1' })] }) for (const rol of ['corporativo', 'admin']) { const r = await resumenMiDia(monday, { nombre: 'X', rol, mondayUserId: null }, new Map(), [], [], AHORA) expect(r.equipo).toBeDefined() } }) it('mis capturas: items en misLeads/misOportunidades no se duplican en misCapturas', async () => { const monday = mondayMock({ oportunidades: [oportunidad({ id: '100', nombre: 'Ana López', etapa: 'Contactado', origen: 'WhatsApp', asesorIds: [OTRO_ID] })], }) 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) // La captura ya aparece en misLeads (via esMio → idsCapturados), asi que no se duplica expect(r.misCapturas).toEqual([]) }) it('mis capturas: captura rapida no se duplica si ya esta en misLeads', async () => { const monday = mondayMock({ leads: [lead({ id: 'lead-9', nombre: 'Pepe Rápido', estado: 'Contactado', origen: 'Web', vendedorIds: [OTRO_ID] })], }) const capturas = [ { leadId: 'lead-9', nombreLead: 'Pepe Rápido', telefono: '4771112233', createdAt: new Date('2026-08-27T09:00:00Z') }, ] const r = await resumenMiDia(monday, vendedor, new Map(), capturas, [], AHORA) // La captura ya aparece en misLeads (via esMio → idsCapturados), asi que no se duplica expect(r.misCapturas).toEqual([]) }) it('mis capturas: si el item ya no existe en ningún tablero, estado y origen van vacíos', async () => { const monday = mondayMock({}) 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) expect(r.misCapturas).toEqual([ { leadId: 'borrado-1', nombre: 'Lead Borrado', telefono: '', estado: '', origen: '', capturadoEn: '2026-08-26T09:00:00.000Z' }, ]) }) it('mis capturas: ordenadas por fecha de captura descendente', async () => { const monday = mondayMock({}) const capturas = [ { 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) 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) 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, mondayApartadoId: null, createdAt: new Date('2026-08-26T09:00:00Z'), }, { id: 'c2', nombreCompleto: 'Luis Completado', proyecto: 'DIAGONAL', unidadCodigo: 'U-101', mondayApartadoId: null, 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', estadoApartado: null, }, { id: 'c1', nombre: 'Ana En Captura', proyecto: '', unidadCodigo: '', completado: false, capturadoEn: '2026-08-26T09:00:00.000Z', estadoApartado: null, }, ]) }) it('mis clientes: estadoApartado cruza mondayApartadoId contra el tablero Apartado', async () => { const clientes = [ { id: 'c1', nombreCompleto: 'Con Apartado', proyecto: 'DIAGONAL', unidadCodigo: 'U-101', mondayApartadoId: 'ap-1', createdAt: new Date('2026-08-27T09:00:00Z'), }, { id: 'c2', nombreCompleto: 'Apartado Borrado', proyecto: 'DIAGONAL', unidadCodigo: 'U-102', mondayApartadoId: 'ap-fantasma', // ya no existe en Monday createdAt: new Date('2026-08-26T09:00:00Z'), }, { id: 'c3', nombreCompleto: 'Sin Apartado', proyecto: '', unidadCodigo: null, mondayApartadoId: null, createdAt: new Date('2026-08-25T09:00:00Z'), }, ] const monday = mondayMock({ apartados: [apartado({ id: 'ap-1', estado: 'Alta Enkontrol' })] }) const r = await resumenMiDia(monday, vendedor, new Map(), [], clientes, AHORA) expect(r.misClientes.map((c) => [c.id, c.estadoApartado])).toEqual([ ['c1', 'Alta Enkontrol'], // estado real del item en Monday ['c2', null], // el apartado ya no existe ['c3', null], // sin apartado ligado ]) }) it('mis clientes: proyecto con solo espacios cuenta como en captura', async () => { const clientes = [ { id: 'c1', nombreCompleto: 'Espacios', proyecto: ' ', unidadCodigo: null, mondayApartadoId: 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', () => { it('etapas terminales de oportunidad (case-insensitive)', () => { expect(esEtapaTerminal('GANADO')).toBe(true) expect(esEtapaTerminal('perdido')).toBe(true) expect(esEtapaTerminal('Abandono')).toBe(true) expect(esEtapaTerminal('Afluencia')).toBe(false) expect(esEtapaTerminal('Calificado')).toBe(false) expect(esEtapaTerminal('Intento de contacto')).toBe(false) }) it('estados terminales de lead (tablero Leads, case-insensitive)', () => { expect(esEstadoTerminalLead('DESCARTADO')).toBe(true) expect(esEstadoTerminalLead('calificado')).toBe(true) expect(esEstadoTerminalLead('Abandono')).toBe(true) expect(esEstadoTerminalLead('Nuevo')).toBe(false) expect(esEstadoTerminalLead('Intento de contacto')).toBe(false) }) it('estados finales de apartado', () => { expect(esEstadoFinalApartado('Escriturado')).toBe(true) expect(esEstadoFinalApartado('CANCELADO')).toBe(true) expect(esEstadoFinalApartado('Pago Apartado')).toBe(false) }) }) describe('resumenMiDia — cuenta compartida / uso solo-app (sin Monday)', () => { const COMPARTIDO = 999000111 it('sin vínculo Monday: ve lo que capturó en la app (registro local)', async () => { const monday = mondayMock({ leads: [ lead({ id: 'l1' }), // capturado por mí (abajo) lead({ id: 'l2' }), // no es mío ], oportunidades: [oportunidad({ id: 'o1', updatedAt: '2026-08-28T10:00:00Z' })], // capturada por mí }) const capturas = [ { leadId: 'l1', nombreLead: 'Lead l1', telefono: null, createdAt: new Date('2026-08-27') }, { leadId: 'o1', nombreLead: 'Lead o1', telefono: null, createdAt: new Date('2026-08-27') }, ] const r = await resumenMiDia(monday, sinMapeo, new Map(), capturas, [], AHORA) expect(r.misLeads.map((l) => l.id)).toEqual(['o1', 'l1']) expect(r.misOportunidades.items.map((o) => o.id)).toEqual(['o1']) }) it('cuenta compartida: NO ve lo asignado a la cuenta compartida, solo su captura local', async () => { const compartido: UsuarioMiDia = { nombre: 'Vendedor Piso', rol: 'vendedor', mondayUserId: COMPARTIDO } const monday = mondayMock({ leads: [ lead({ id: 'l1', vendedorIds: [COMPARTIDO] }), // asignado a la cuenta compartida, capturado por otro lead({ id: 'l2', vendedorIds: [COMPARTIDO] }), // asignado a la compartida, capturado por MÍ ], }) const capturas = [{ leadId: 'l2', nombreLead: 'Lead l2', telefono: null, createdAt: new Date('2026-08-27') }] const r = await resumenMiDia(monday, compartido, new Map(), capturas, [], AHORA, COMPARTIDO) expect(r.misLeads.map((l) => l.id)).toEqual(['l2']) // l1 queda fuera }) it('cuenta NO compartida: ve capturas + asignados (comportamiento de siempre)', async () => { const monday = mondayMock({ leads: [ lead({ id: 'l1', vendedorIds: [MI_ID] }), // asignado a mí lead({ id: 'l2', vendedorIds: [OTRO_ID] }), // capturado por mí (abajo) lead({ id: 'l3', vendedorIds: [OTRO_ID] }), // ni asignado ni capturado ], }) const capturas = [{ leadId: 'l2', nombreLead: 'Lead l2', telefono: null, createdAt: new Date('2026-08-27') }] const r = await resumenMiDia(monday, vendedor, new Map(), capturas, [], AHORA, COMPARTIDO) expect(r.misLeads.map((l) => l.id).sort()).toEqual(['l1', 'l2']) }) it('mis apartados incluye los ligados a mis altas de cliente aunque el Vendedor sea la cuenta compartida', async () => { const compartido: UsuarioMiDia = { nombre: 'Vendedor Piso', rol: 'vendedor', mondayUserId: COMPARTIDO } const monday = mondayMock({ apartados: [ apartado({ id: 'p1', vendedorIds: [COMPARTIDO] }), // apartado de MI alta (abajo) apartado({ id: 'p2', vendedorIds: [COMPARTIDO] }), // apartado de otro ], }) const clientes = [ { id: 'c1', nombreCompleto: 'Cliente p1', proyecto: 'SENDA', unidadCodigo: 'U-1', mondayApartadoId: 'p1', createdAt: new Date('2026-08-27') }, ] const r = await resumenMiDia(monday, compartido, new Map(), [], clientes, AHORA, COMPARTIDO) expect(r.misApartados.map((a) => a.id)).toEqual(['p1']) }) })