import { describe, expect, it, vi, beforeEach } from 'vitest' import { construirMiTablero } from './reporte-mi-tablero.service.js' // ---------- Fixtures ---------- function captura( overrides: Partial<{ leadId: string board: string etapa: string origen: string | null desarrollo: string | null createdAt: Date updatedAt: Date datosJson: string | null }> = {}, ) { return { leadId: overrides.leadId ?? `cap-${Math.random().toString(36).slice(2, 8)}`, board: overrides.board ?? 'LEADS', etapa: overrides.etapa ?? 'NUEVO', origen: overrides.origen ?? 'Facebook', desarrollo: overrides.desarrollo ?? 'DIAGONAL', createdAt: overrides.createdAt ?? new Date('2026-09-05T10:00:00'), updatedAt: overrides.updatedAt ?? overrides.createdAt ?? new Date('2026-09-05T10:00:00'), datosJson: overrides.datosJson ?? null, } } function cotizacion( overrides: Partial<{ precioFinal: number; vendedorId: string | null; createdAt: Date }> = {}, ) { return { precioFinal: overrides.precioFinal ?? 850000, vendedorId: overrides.vendedorId ?? 'u-1', createdAt: overrides.createdAt ?? new Date('2026-09-01T12:00:00'), } } function actividad( overrides: Partial<{ usuarioId: string; estado: string; fechaVencimiento: Date | null }> = {}, ) { return { usuarioId: overrides.usuarioId ?? 'u-1', estado: overrides.estado ?? 'ABIERTA', fechaVencimiento: overrides.fechaVencimiento ?? null, } } const AHORA = new Date('2026-09-07T12:00:00') // ---------- construirMiTablero (puro) ---------- describe('construirMiTablero', () => { it('genera 30 entradas en leadsPorDia con ceros para días sin leads', () => { const tablero = construirMiTablero([], [], [], AHORA) expect(tablero.leadsPorDia.length).toBe(30) // Todos los días deben tener cantidad 0 expect(tablero.leadsPorDia.every((d) => d.cantidad === 0)).toBe(true) }) it('cuenta leads por día correctamente', () => { const hace2Dias = new Date(AHORA.getTime() - 2 * 24 * 60 * 60 * 1000) const capturas = [ captura({ createdAt: hace2Dias }), captura({ createdAt: hace2Dias }), captura({ createdAt: hace2Dias }), ] const tablero = construirMiTablero(capturas, [], [], AHORA) const entrada = tablero.leadsPorDia.find((d) => d.cantidad > 0) expect(entrada).toBeDefined() expect(entrada!.cantidad).toBe(3) }) it('cuenta conversión correctamente', () => { const capturas = [ captura({ board: 'LEADS' }), captura({ board: 'LEADS' }), captura({ board: 'OPORTUNIDADES' }), captura({ board: 'APARTADO', etapa: 'FIRMA CLIENTE' }), captura({ board: 'APARTADO', etapa: 'CANCELO' }), captura({ board: 'APARTADO', etapa: 'ESCRITURADO' }), captura({ board: 'LEADS', etapa: 'DESCARTADO' }), ] const tablero = construirMiTablero(capturas, [], [], AHORA) expect(tablero.conversion.leads).toBe(3) // 2 activos + 1 descartado expect(tablero.conversion.oportunidades).toBe(1) // Cancelo no cuenta; Firma Cliente y Escriturado ya son ventas expect(tablero.conversion.apartados).toBe(0) expect(tablero.conversion.vendidos).toBe(2) expect(tablero.conversion.descartados).toBe(1) // etapa terminal DESCARTADO }) it('calcula ventas del mes con ventas CERRADAS (no cotizaciones)', () => { // Las cotizaciones ya no cuentan como ventas: se cuentan los apartados con // firma de cliente o posteriores, sin repetir la misma unidad. const cots = [ cotizacion({ precioFinal: 850000, createdAt: new Date('2026-09-01T12:00:00') }), ] const capturas = [ captura({ leadId: 'loc_apt_1', board: 'APARTADO', etapa: 'Firma Cliente', updatedAt: new Date('2026-09-10T12:00:00'), datosJson: JSON.stringify({ precioFinal: 1_500_000 }), }), captura({ leadId: 'loc_apt_2', board: 'APARTADO', etapa: 'Venta', updatedAt: new Date('2026-09-12T12:00:00'), datosJson: JSON.stringify({ precioFinal: 2_000_000 }), }), // Mismo apartado recotizado: no debe duplicarse (mismo leadId) captura({ leadId: 'loc_apt_1', board: 'APARTADO', etapa: 'Firma Cliente', updatedAt: new Date('2026-09-15T12:00:00'), datosJson: JSON.stringify({ precioFinal: 1_500_000 }), }), captura({ board: 'APARTADO', etapa: 'Pago Apartado', updatedAt: new Date('2026-09-11T12:00:00') }), captura({ board: 'APARTADO', etapa: 'Cancelacion Apartado', updatedAt: new Date('2026-09-11T12:00:00') }), ] const tablero = construirMiTablero(capturas, cots, [], AHORA) expect(tablero.ventasMes.unidades).toBe(2) expect(tablero.ventasMes.monto).toBe(3_500_000) }) it('agrupa por origen con conteo de convertidos', () => { const capturas = [ captura({ board: 'LEADS', origen: 'Facebook' }), captura({ board: 'LEADS', origen: 'Facebook' }), captura({ board: 'OPORTUNIDADES', origen: 'Facebook' }), captura({ board: 'LEADS', origen: 'Referido' }), ] const tablero = construirMiTablero(capturas, [], [], AHORA) const fb = tablero.porOrigen.find((o) => o.origen === 'Facebook') expect(fb).toBeDefined() expect(fb!.total).toBe(3) expect(fb!.convertidos).toBe(1) const ref = tablero.porOrigen.find((o) => o.origen === 'Referido') expect(ref).toBeDefined() expect(ref!.total).toBe(1) expect(ref!.convertidos).toBe(0) }) it('agrupa por desarrollo solo con vendidos', () => { const capturas = [ captura({ board: 'APARTADO', etapa: 'FIRMA CLIENTE', desarrollo: 'DIAGONAL' }), captura({ board: 'APARTADO', etapa: 'ESCRITURADO', desarrollo: 'DIAGONAL' }), captura({ board: 'APARTADO', etapa: 'FIRMA CLIENTE', desarrollo: 'ATELIER' }), captura({ board: 'APARTADO', etapa: 'NUEVO', desarrollo: 'SENDA' }), // no vendido ] const tablero = construirMiTablero(capturas, [], [], AHORA) expect(tablero.porDesarrollo).toEqual([ { desarrollo: 'DIAGONAL', ventas: 2 }, { desarrollo: 'ATELIER', ventas: 1 }, ]) }) it('cuenta actividades hoy y pendientes', () => { const actividades = [ actividad({ estado: 'ABIERTA', fechaVencimiento: new Date('2026-09-07T10:00:00') }), actividad({ estado: 'ABIERTA', fechaVencimiento: new Date('2026-09-08T10:00:00') }), actividad({ estado: 'ABIERTA' }), actividad({ estado: 'CERRADA' }), ] const tablero = construirMiTablero([], [], actividades, AHORA) expect(tablero.actividadesHoy).toBe(2) // 1 vencida + 1 sin fecha (hoy) expect(tablero.actividadesPendientes).toBe(3) // 3 ABIERTA }) it('normaliza orígenes vacíos a (sin origen)', () => { const capturas = [captura({ board: 'LEADS', origen: '' })] const tablero = construirMiTablero(capturas, [], [], AHORA) expect(tablero.porOrigen.length).toBe(1) expect(tablero.porOrigen[0].origen).toBe('(sin origen)') }) })