Files
FRASA-CAS/api-gateway/src/services/mi-dia.service.test.ts
Consult AS Technology dc5e99ad98 fix(mi-dia): oportunidad en Aparto sale de Mis oportunidades
Al apartar, el seguimiento continúa en Mis apartados (pipeline Enkontrol),
así que 'APARTO' pasa a ser etapa terminal para la lista de trabajo (Mis
oportunidades y el merge de Mis leads). El conteo porEtapa del pipeline
personal la sigue incluyendo.
2026-09-05 01:07:18 +00:00

565 lines
23 KiB
TypeScript

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<LeadMonday> & { 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<OportunidadMonday> & { 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<ActividadMonday> & { id: string }): ActividadMonday {
return {
nombre: `Actividad ${parcial.id}`,
tipo: 'Llamada',
fecha: HOY,
estado: 'Abierto',
responsableIds: [MI_ID],
...parcial,
}
}
function apartado(parcial: Partial<ApartadoMonday> & { 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<string, Map<string, UltimoCambioMonday>>
}) {
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<string, UltimoCambioMonday>()
},
}
}
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(1)
})
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: enriquecidas con la etapa/origen actual de la oportunidad en Monday', 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)
// Aunque la afluencia quedó asignada a OTRO por round-robin, la captura es mía
expect(r.misCapturas).toEqual([
{
leadId: '100',
nombre: 'Ana López',
telefono: '6641112233',
estado: 'Contactado',
origen: 'WhatsApp',
capturadoEn: '2026-08-27T09:00:00.000Z',
},
])
})
it('mis capturas: capturas rápidas cruzan contra el tablero Leads por id', 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)
expect(r.misCapturas).toEqual([
{
leadId: 'lead-9',
nombre: 'Pepe Rápido',
telefono: '4771112233',
estado: 'Contactado',
origen: 'Web',
capturadoEn: '2026-08-27T09:00:00.000Z',
},
])
})
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)
})
})