import { beforeEach, describe, expect, it, vi } from 'vitest' import type { Request, Response } from 'express' import { AppError } from '../utils/errors.js' // ---------- Prisma y Monday mockeados (los tests no tocan BD ni red) ---------- const prismaMock = vi.hoisted(() => ({ usuario: { findUnique: vi.fn(), findMany: vi.fn(), }, leadCaptura: { findMany: vi.fn(), }, cliente: { findMany: vi.fn(), }, actividadLocal: { findMany: vi.fn(), }, })) const mondayMock = vi.hoisted(() => ({ getUsuariosWorkspace: vi.fn(), getLeads: vi.fn(), getOportunidades: vi.fn(), getActividades: vi.fn(), getApartados: vi.fn(), getUltimosCambios: vi.fn(), })) vi.mock('../services/prisma.js', () => ({ prisma: prismaMock })) vi.mock('../services/monday.service.js', () => ({ mondayService: mondayMock })) import { resumenMiDiaEndpoint } from './mi-dia.controller.js' const usuarioFake = { id: 'u-1', nombre: 'Martin Zavala', rol: 'vendedor', activo: true, mondayUserId: 123, } function capturaFake(parcial: Record & { leadId: string }) { return { id: `row-${parcial.leadId}`, nombreLead: `Persona ${parcial.leadId}`, telefono: null, email: null, origen: null, desarrollo: null, modelo: null, board: 'LEADS', etapa: 'NUEVO', motivo: null, submotivo: null, datosJson: null, fechaLimite: null, mondayItemId: null, pendienteSync: false, usuarioId: 'u-1', createdAt: new Date('2026-09-03T10:00:00'), updatedAt: new Date('2026-09-04T10:00:00'), ...parcial, } } function resFake() { const json = vi.fn() const status = vi.fn().mockReturnValue({ json }) return { res: { json, status } as unknown as Response, status, json } } const reqFake = { usuarioToken: { sub: 'u-1' } } as unknown as Request beforeEach(() => { vi.clearAllMocks() prismaMock.usuario.findUnique.mockResolvedValue(usuarioFake) prismaMock.usuario.findMany.mockResolvedValue([]) prismaMock.leadCaptura.findMany.mockResolvedValue([]) prismaMock.cliente.findMany.mockResolvedValue([]) prismaMock.actividadLocal.findMany.mockResolvedValue([]) mondayMock.getUsuariosWorkspace.mockResolvedValue([]) mondayMock.getLeads.mockResolvedValue([]) mondayMock.getOportunidades.mockResolvedValue([]) mondayMock.getActividades.mockResolvedValue([]) mondayMock.getApartados.mockResolvedValue([]) mondayMock.getUltimosCambios.mockResolvedValue(new Map()) }) describe('resumenMiDiaEndpoint (siempre local, Monday purgado)', () => { it('responde 100% desde LeadCaptura: Monday no se llama nunca', async () => { prismaMock.leadCaptura.findMany.mockResolvedValue([ capturaFake({ leadId: 'loc_l1', board: 'LEADS', etapa: 'NUEVO', pendienteSync: true }), capturaFake({ leadId: 'loc_o1', board: 'OPORTUNIDADES', etapa: 'Afluencia', pendienteSync: true }), ]) const { res, json } = resFake() await resumenMiDiaEndpoint(reqFake, res) expect(mondayMock.getLeads).not.toHaveBeenCalled() expect(mondayMock.getOportunidades).not.toHaveBeenCalled() expect(mondayMock.getApartados).not.toHaveBeenCalled() expect(mondayMock.getUsuariosWorkspace).not.toHaveBeenCalled() const resumen = json.mock.calls[0]?.[0] as Record expect((resumen['misLeads'] as unknown[]).length).toBe(1) // solo leads, no oportunidades expect((resumen['misOportunidades'] as { items: unknown[] }).items).toHaveLength(1) expect(resumen['misActividadesHoy']).toEqual([]) expect(resumen['conteos']).toMatchObject({ actividadesAbiertas: 0 }) }) it('las capturas locales llegan con pendienteSync (badge ⏳ del front)', async () => { prismaMock.leadCaptura.findMany.mockResolvedValue([ capturaFake({ leadId: 'loc_l1', board: 'LEADS', pendienteSync: true }), ]) const { res, json } = resFake() await resumenMiDiaEndpoint(reqFake, res) const resumen = json.mock.calls[0]?.[0] as Record expect((resumen['misLeads'] as { id: string; pendienteSync?: boolean }[]).map((l) => l.id)).toEqual(['loc_l1']) expect((resumen['misLeads'] as { pendienteSync?: boolean }[])[0]?.pendienteSync).toBe(true) }) it('sin capturas: listas vacías y sin flags de sync', async () => { const { res, json } = resFake() await resumenMiDiaEndpoint(reqFake, res) const resumen = json.mock.calls[0]?.[0] as Record expect(resumen['misLeads']).toEqual([]) expect(JSON.stringify(resumen)).not.toContain('pendienteSync') }) it('los usuarios del equipo solo se consultan para roles coordinador/admin/corporativo', async () => { await resumenMiDiaEndpoint(reqFake, resFake().res) expect(prismaMock.usuario.findMany).not.toHaveBeenCalled() prismaMock.usuario.findUnique.mockResolvedValue({ ...usuarioFake, rol: 'coordinador' }) await resumenMiDiaEndpoint(reqFake, resFake().res) expect(prismaMock.usuario.findMany).toHaveBeenCalledWith({ where: { activo: true }, select: { id: true, nombre: true, rol: true, activo: true }, }) }) it('un error de BD se propaga (no se traga nada)', async () => { prismaMock.leadCaptura.findMany.mockRejectedValue(new AppError(500, 'bug')) await expect(resumenMiDiaEndpoint(reqFake, resFake().res)).rejects.toThrow('bug') }) })