import { beforeEach, describe, expect, it, vi } from 'vitest' // ---------- Prisma mockeado ---------- const prismaMock = vi.hoisted(() => ({ clienteMaestro: { findMany: vi.fn(), findUnique: vi.fn(), count: vi.fn(), upsert: vi.fn(), create: vi.fn(), update: vi.fn(), }, })) vi.mock('./prisma.js', () => ({ prisma: prismaMock })) import { listar, obtener, contar, catalogos, exportar, importarDesdeExcel, } from './cliente-maestro.service.js' beforeEach(() => { vi.clearAllMocks() }) // ── listar ─────────────────────────────────────────────────────────── describe('listar', () => { it('devuelve datos, total, pagina y limite', async () => { prismaMock.clienteMaestro.findMany.mockResolvedValue([ { id: '1', numCliente: 'C001', nombre: 'Juan', apellidoPaterno: 'Perez', importadoEn: new Date() }, ]) prismaMock.clienteMaestro.count.mockResolvedValue(1) const result = await listar({ pagina: 1, limite: 50 }) expect(result.datos).toHaveLength(1) expect(result.total).toBe(1) expect(result.pagina).toBe(1) expect(result.limite).toBe(50) }) it('aplica filtro de búsqueda con OR', async () => { prismaMock.clienteMaestro.findMany.mockResolvedValue([]) prismaMock.clienteMaestro.count.mockResolvedValue(0) await listar({ busqueda: 'Juan', pagina: 1, limite: 50 }) expect(prismaMock.clienteMaestro.findMany).toHaveBeenCalledWith( expect.objectContaining({ where: { AND: [ expect.objectContaining({ OR: expect.arrayContaining([{ nombre: { contains: 'Juan' } }]), }), ], }, }), ) }) it('aplica filtro de desarrollo (valor exacto o con sufijo " - ...")', async () => { prismaMock.clienteMaestro.findMany.mockResolvedValue([]) prismaMock.clienteMaestro.count.mockResolvedValue(0) await listar({ desarrollo: 'SENDA', pagina: 1, limite: 50 }) expect(prismaMock.clienteMaestro.findMany).toHaveBeenCalledWith( expect.objectContaining({ where: { AND: [ { OR: [{ desarrollo: 'SENDA' }, { desarrollo: { startsWith: 'SENDA -' } }], }, ], }, }), ) }) it('aplica filtro de estatus', async () => { prismaMock.clienteMaestro.findMany.mockResolvedValue([]) prismaMock.clienteMaestro.count.mockResolvedValue(0) await listar({ estatus: 'VENDIDO', pagina: 1, limite: 50 }) expect(prismaMock.clienteMaestro.findMany).toHaveBeenCalledWith( expect.objectContaining({ where: { AND: [{ estatus: 'VENDIDO' }] }, }), ) }) it('combina búsqueda, desarrollo y estatus en la misma consulta', async () => { prismaMock.clienteMaestro.findMany.mockResolvedValue([]) prismaMock.clienteMaestro.count.mockResolvedValue(0) await listar({ busqueda: 'Ana', desarrollo: 'SOLE', estatus: 'ENTREGA', pagina: 1, limite: 50 }) const llamada = prismaMock.clienteMaestro.findMany.mock.calls[0]![0] as { where: { AND: unknown[] } } expect(llamada.where.AND).toHaveLength(3) }) it('sin filtros no arma condiciones', async () => { prismaMock.clienteMaestro.findMany.mockResolvedValue([]) prismaMock.clienteMaestro.count.mockResolvedValue(0) await listar({ pagina: 1, limite: 50 }) expect(prismaMock.clienteMaestro.findMany).toHaveBeenCalledWith( expect.objectContaining({ where: {} }), ) }) }) // ── catalogos ──────────────────────────────────────────────────────── describe('catalogos', () => { it('devuelve los desarrollos y estatus reales, sin vacíos ni nulos', async () => { prismaMock.clienteMaestro.findMany .mockResolvedValueOnce([{ desarrollo: 'MUTUO - EDIFICACION' }, { desarrollo: 'SOLE' }, { desarrollo: null }, { desarrollo: ' ' }]) .mockResolvedValueOnce([{ estatus: 'ENTREGA' }, { estatus: 'VENDIDO' }, { estatus: null }]) const result = await catalogos() expect(result.desarrollos).toEqual(['MUTUO - EDIFICACION', 'SOLE']) expect(result.estatuses).toEqual(['ENTREGA', 'VENDIDO']) expect(prismaMock.clienteMaestro.findMany).toHaveBeenCalledWith({ distinct: ['desarrollo'], select: { desarrollo: true }, orderBy: { desarrollo: 'asc' }, }) }) }) // ── obtener ────────────────────────────────────────────────────────── describe('obtener', () => { it('devuelve el cliente si existe', async () => { prismaMock.clienteMaestro.findUnique.mockResolvedValue({ id: '1', numCliente: 'C001', nombre: 'Juan', importadoEn: new Date(), }) const result = await obtener('C001') expect(result).not.toBeNull() expect(result!.numCliente).toBe('C001') }) it('devuelve null si no existe', async () => { prismaMock.clienteMaestro.findUnique.mockResolvedValue(null) expect(await obtener('NOPE')).toBeNull() }) }) // ── contar ─────────────────────────────────────────────────────────── describe('contar', () => { it('devuelve el conteo total', async () => { prismaMock.clienteMaestro.count.mockResolvedValue(42) expect(await contar({})).toBe(42) }) it('cuenta con filtros', async () => { prismaMock.clienteMaestro.count.mockResolvedValue(5) expect(await contar({ desarrollo: 'SENDA' })).toBe(5) expect(prismaMock.clienteMaestro.count).toHaveBeenCalledWith( { where: { AND: [{ OR: [{ desarrollo: 'SENDA' }, { desarrollo: { startsWith: 'SENDA -' } }] }], }, }, ) }) }) // ── exportar ───────────────────────────────────────────────────────── describe('exportar', () => { it('devuelve array de clientes', async () => { prismaMock.clienteMaestro.findMany.mockResolvedValue([ { id: '1', numCliente: 'C001', nombre: 'Juan', importadoEn: new Date() }, { id: '2', numCliente: 'C002', nombre: 'Ana', importadoEn: new Date() }, ]) const result = await exportar({}) expect(result).toHaveLength(2) expect(prismaMock.clienteMaestro.findMany).toHaveBeenCalledWith( expect.objectContaining({ take: 50000 }), ) }) }) // ── importarDesdeExcel ─────────────────────────────────────────────── describe('importarDesdeExcel', () => { it('inserta clientes nuevos', async () => { prismaMock.clienteMaestro.findUnique.mockResolvedValue(null) prismaMock.clienteMaestro.create.mockResolvedValue({}) const result = await importarDesdeExcel([ { numCliente: 'C001', nombre: 'Juan', apellidoPaterno: 'Perez' }, ]) expect(result.insertados).toBe(1) expect(result.actualizados).toBe(0) expect(result.errores).toBe(0) expect(prismaMock.clienteMaestro.create).toHaveBeenCalled() }) it('actualiza clientes existentes', async () => { prismaMock.clienteMaestro.findUnique.mockResolvedValue({ id: '1', numCliente: 'C001' }) prismaMock.clienteMaestro.update.mockResolvedValue({}) const result = await importarDesdeExcel([ { numCliente: 'C001', nombre: 'Juan', apellidoPaterno: 'Perez' }, ]) expect(result.insertados).toBe(0) expect(result.actualizados).toBe(1) expect(result.errores).toBe(0) expect(prismaMock.clienteMaestro.update).toHaveBeenCalled() }) it('salta registros sin numCliente o nombre', async () => { const result = await importarDesdeExcel([ { numCliente: '', nombre: 'Juan' }, { numCliente: 'C001', nombre: '' }, ]) expect(result.errores).toBe(2) expect(prismaMock.clienteMaestro.create).not.toHaveBeenCalled() }) it('maneja errores de DB como errores', async () => { prismaMock.clienteMaestro.findUnique.mockResolvedValue(null) prismaMock.clienteMaestro.create.mockRejectedValue(new Error('DB error')) const result = await importarDesdeExcel([ { numCliente: 'C001', nombre: 'Juan', apellidoPaterno: 'Perez' }, ]) expect(result.errores).toBe(1) expect(result.insertados).toBe(0) }) })