import { describe, expect, it } from 'vitest' import { claveTelefono, detalleContacto } from './contactos-detalle.service.js' import type { LeadMonday, OportunidadMonday, ApartadoMonday } from './monday.service.js' const MI_ID = 106736727 const leadBase: LeadMonday = { id: 'l1', nombre: 'Ana Demo', createdAt: '', updatedAt: '', estado: 'Nuevo', origen: 'PASE', telefono: '+52 6641234567', email: 'ana@correo.com', vendedorIds: [MI_ID], } const opBase: OportunidadMonday = { id: 'o1', nombre: 'Ana Demo', createdAt: '', updatedAt: '', etapa: 'Cotizador', unidadCodigo: '', precioFinal: 1500000, asesorIds: [MI_ID], telefono: '6641234567', email: '', origen: 'PASE', desarrollo: 'SENDA', modeloInteres: 'SOHO', } const apBase: ApartadoMonday = { id: 'a1', nombre: 'Ana Demo', unidadCodigo: 'SENDA-409-1-2', fechaLimite: '2026-09-10', estado: 'Pago Apartado', vendedorIds: [MI_ID], telefono: '6641234567', email: '', precioFinal: 2646000, fechaCompra: '2026-09-01', } function mondayMock(overrides: Record = {}) { return { async getContactoDetalle() { return { nombre: 'Ana Demo', Teléfono: '+52 6641234567', Email: 'ana@correo.com', Estado: 'CLIENTE', CURP: 'XAXX010101XXXXXXX0' } }, async getLeads() { return [leadBase] }, async getOportunidades() { return [opBase] }, async getApartados() { return [apBase] }, async getHistorialItems(_b: string, ids: string[]) { return ids.map((id, i) => ({ itemId: id, fechaISO: `2026-09-0${(i % 9) + 1}T10:00:00.000Z`, usuarioId: MI_ID, evento: 'update_column_value', })) }, async getUnidad() { return { desarrollo: 'SENDA' } }, ...overrides, // eslint-disable-next-line @typescript-eslint/no-explicit-any } as any } const nombres = new Map([[MI_ID, 'Martin Zavala']]) describe('claveTelefono', () => { it('últimos 10 dígitos, tolerante a lada, espacios y símbolos', () => { expect(claveTelefono('+52 664 123 4567')).toBe('6641234567') expect(claveTelefono('+16195550134')).toBe('6195550134') expect(claveTelefono('6641234567')).toBe('6641234567') expect(claveTelefono('')).toBe('') expect(claveTelefono('123')).toBe('') }) }) describe('detalleContacto (GET /api/contactos/:id/detalle)', () => { it('arma contacto + propiedad + relaciones + historial con nombres resueltos', async () => { const d = await detalleContacto( mondayMock(), 'c1', nombres, [{ leadId: 'l1', nombreLead: 'Ana Demo', createdAt: new Date('2026-09-02T08:00:00Z'), usuario: { nombre: 'Martin Zavala' } }], { id: 'cl1', nombreCompleto: 'Ana Demo', proyecto: 'SENDA', unidadCodigo: 'SENDA-409-1-2', createdAt: new Date('2026-09-03T08:00:00Z'), capturadoPor: { nombre: 'Martin Zavala' }, }, ) expect(d).not.toBeNull() expect(d!.contacto['CURP']).toBe('XAXX010101XXXXXXX0') // Propiedad comprada (apartado relacionado, enriquecido con el desarrollo) expect(d!.propiedad).toMatchObject({ unidadCodigo: 'SENDA-409-1-2', desarrollo: 'SENDA', estado: 'Pago Apartado', precioFinal: 2646000, atendidoPor: 'Martin Zavala', }) // Relaciones por teléfono/email con quién atiende expect(d!.relaciones.leads[0]).toMatchObject({ id: 'l1', atendidoPor: 'Martin Zavala' }) expect(d!.relaciones.oportunidades[0]).toMatchObject({ id: 'o1', atendidoPor: 'Martin Zavala' }) expect(d!.relaciones.apartados[0]).toMatchObject({ id: 'a1', atendidoPor: 'Martin Zavala' }) // Historial: activity log + captura + alta local, ordenado desc expect(d!.historial.some((h) => h.fuente === 'App' && h.detalle.includes('Captura'))).toBe(true) expect(d!.historial.some((h) => h.detalle.includes('Alta de cliente'))).toBe(true) expect(d!.historial.every((h) => h.usuario === 'Martin Zavala')).toBe(true) const fechas = d!.historial.map((h) => h.fechaISO) expect(fechas).toEqual([...fechas].sort((a, b) => b.localeCompare(a))) }) it('sin relaciones ni registros locales: listas vacías y propiedad null', async () => { const d = await detalleContacto( mondayMock({ async getLeads() { return [] }, async getOportunidades() { return [] }, async getApartados() { return [] }, }), 'c1', new Map(), [], null, ) expect(d!.propiedad).toBeNull() expect(d!.relaciones).toEqual({ leads: [], oportunidades: [], apartados: [] }) // El historial al menos tiene los eventos del propio contacto expect(d!.historial).toHaveLength(1) expect(d!.historial[0]?.fuente).toBe('Contactos') }) it('contacto inexistente devuelve null', async () => { const d = await detalleContacto( mondayMock({ async getContactoDetalle() { return null } }), 'no-existe', new Map(), [], null, ) expect(d).toBeNull() }) })