feat(mejoras): 14 mejoras al CRM Frasa
Fase 1 - UI: dark mode, skeletons, toasts Fase 2 - Filtros: avanzados Mi día, búsqueda global Ctrl+K Fase 3 - Datos: export Excel, notas apartados, auditoría Fase 4 - Dashboard: gráficas vendedor + métricas uso Fase 5 - Responsive: tablets optimizadas Fase 6 - PWA: IndexedDB offline, push notifications Backend: 3 tablas nuevas, 6 services, 6 endpoints nuevos 721 API tests + fronts verdes.
This commit is contained in:
163
api-gateway/src/services/buscar.service.test.ts
Normal file
163
api-gateway/src/services/buscar.service.test.ts
Normal file
@@ -0,0 +1,163 @@
|
||||
import { beforeEach, describe, expect, it, vi } from 'vitest'
|
||||
|
||||
// ---------- Prisma mockeado ----------
|
||||
|
||||
const prismaMock = vi.hoisted(() => ({
|
||||
leadCaptura: { findMany: vi.fn() },
|
||||
cliente: { findMany: vi.fn() },
|
||||
cotizacion: { findMany: vi.fn() },
|
||||
unidad: { findMany: vi.fn() },
|
||||
}))
|
||||
|
||||
vi.mock('./prisma.js', () => ({ prisma: prismaMock }))
|
||||
|
||||
import { buscar } from './buscar.service.js'
|
||||
|
||||
beforeEach(() => {
|
||||
vi.clearAllMocks()
|
||||
prismaMock.leadCaptura.findMany.mockResolvedValue([])
|
||||
prismaMock.cliente.findMany.mockResolvedValue([])
|
||||
prismaMock.cotizacion.findMany.mockResolvedValue([])
|
||||
prismaMock.unidad.findMany.mockResolvedValue([])
|
||||
})
|
||||
|
||||
describe('buscar (GET /api/buscar)', () => {
|
||||
it('término vacío retorna [] sin consultar la base', async () => {
|
||||
const r = await buscar('')
|
||||
expect(r).toEqual([])
|
||||
expect(prismaMock.leadCaptura.findMany).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('término con solo espacios también retorna []', async () => {
|
||||
const r = await buscar(' ')
|
||||
expect(r).toEqual([])
|
||||
expect(prismaMock.leadCaptura.findMany).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('consulta los 4 modelos con el patrón contains', async () => {
|
||||
prismaMock.leadCaptura.findMany.mockResolvedValue([
|
||||
{ leadId: 'l1', nombreLead: 'María López', telefono: '4771112233', email: null, origen: 'Facebook', board: 'LEADS', updatedAt: new Date() },
|
||||
])
|
||||
prismaMock.cliente.findMany.mockResolvedValue([
|
||||
{ id: 'c1', nombreCompleto: 'María López', curp: '', rfc: '', email: 'maria@x.com', celular: '4771112233', proyecto: 'DIAGONAL', createdAt: new Date() },
|
||||
])
|
||||
prismaMock.cotizacion.findMany.mockResolvedValue([])
|
||||
prismaMock.unidad.findMany.mockResolvedValue([])
|
||||
|
||||
const r = await buscar('María')
|
||||
expect(prismaMock.leadCaptura.findMany).toHaveBeenCalledTimes(1)
|
||||
expect(prismaMock.cliente.findMany).toHaveBeenCalledTimes(1)
|
||||
expect(prismaMock.cotizacion.findMany).toHaveBeenCalledTimes(1)
|
||||
expect(prismaMock.unidad.findMany).toHaveBeenCalledTimes(1)
|
||||
expect(r.length).toBe(2)
|
||||
expect(r[0]?.tipo).toBe('lead')
|
||||
expect(r[1]?.tipo).toBe('contacto')
|
||||
})
|
||||
|
||||
it('aplica take: limite por modelo', async () => {
|
||||
await buscar('test', 5)
|
||||
expect(prismaMock.leadCaptura.findMany).toHaveBeenCalledWith(
|
||||
expect.objectContaining({ take: 5 }),
|
||||
)
|
||||
})
|
||||
|
||||
it('prioriza leads sobre contactos, contactos sobre oportunidades, oportunidades sobre unidades', async () => {
|
||||
prismaMock.leadCaptura.findMany.mockResolvedValue([
|
||||
{ leadId: 'l1', nombreLead: 'X', telefono: null, email: null, origen: null, board: 'LEADS', updatedAt: new Date() },
|
||||
])
|
||||
prismaMock.cliente.findMany.mockResolvedValue([
|
||||
{ id: 'c1', nombreCompleto: 'X', curp: '', rfc: '', email: null, celular: null, proyecto: '', createdAt: new Date() },
|
||||
])
|
||||
prismaMock.cotizacion.findMany.mockResolvedValue([
|
||||
{ id: 'ct1', clienteNombre: 'X', clienteEmail: null, unidadCodigo: null, desarrollo: null, tipoPropuesta: null, createdAt: new Date() },
|
||||
])
|
||||
prismaMock.unidad.findMany.mockResolvedValue([
|
||||
{ id: 'u1', codigo: 'X', desarrollo: 'SENDA', modelo: null, status: null, updatedAt: new Date() },
|
||||
])
|
||||
|
||||
const r = await buscar('X')
|
||||
expect(r.map((i) => i.tipo)).toEqual(['lead', 'contacto', 'oportunidad', 'unidad'])
|
||||
})
|
||||
|
||||
it('mezcla los resultados y limita al total indicado', async () => {
|
||||
const leads = Array.from({ length: 10 }, (_, i) => ({
|
||||
leadId: `l${i}`, nombreLead: `L${i}`, telefono: null, email: null, origen: null, board: 'LEADS', updatedAt: new Date(),
|
||||
}))
|
||||
const clientes = Array.from({ length: 10 }, (_, i) => ({
|
||||
id: `c${i}`, nombreCompleto: `C${i}`, curp: '', rfc: '', email: null, celular: null, proyecto: '', createdAt: new Date(),
|
||||
}))
|
||||
prismaMock.leadCaptura.findMany.mockResolvedValue(leads)
|
||||
prismaMock.cliente.findMany.mockResolvedValue(clientes)
|
||||
|
||||
const r = await buscar('x', 15)
|
||||
expect(r).toHaveLength(15)
|
||||
// Primeros 10 son leads (prioridad), siguientes 5 son contactos
|
||||
expect(r.filter((i) => i.tipo === 'lead')).toHaveLength(10)
|
||||
expect(r.filter((i) => i.tipo === 'contacto')).toHaveLength(5)
|
||||
})
|
||||
|
||||
it('lead: detalle muestra teléfono · email, o origen como fallback', async () => {
|
||||
prismaMock.leadCaptura.findMany.mockResolvedValue([
|
||||
{ leadId: 'l1', nombreLead: 'Ana', telefono: '4771112233', email: 'ana@x.com', origen: null, board: 'LEADS', updatedAt: new Date() },
|
||||
])
|
||||
const r = await buscar('Ana')
|
||||
expect(r[0]?.detalle).toBe('4771112233 · ana@x.com')
|
||||
|
||||
prismaMock.leadCaptura.findMany.mockResolvedValue([
|
||||
{ leadId: 'l2', nombreLead: 'Bob', telefono: null, email: null, origen: 'Facebook', board: 'LEADS', updatedAt: new Date() },
|
||||
])
|
||||
const r2 = await buscar('Bob')
|
||||
expect(r2[0]?.detalle).toBe('Facebook')
|
||||
})
|
||||
|
||||
it('contacto: detalle muestra email · celular · proyecto', async () => {
|
||||
prismaMock.cliente.findMany.mockResolvedValue([
|
||||
{ id: 'c1', nombreCompleto: 'Luis', curp: '', rfc: '', email: 'luis@x.com', celular: '4772223333', proyecto: 'SENDA', createdAt: new Date() },
|
||||
])
|
||||
const r = await buscar('Luis')
|
||||
expect(r[0]?.detalle).toBe('luis@x.com · 4772223333 · SENDA')
|
||||
})
|
||||
|
||||
it('oportunidad: detalle muestra unidadCodigo · desarrollo, o tipoPropuesta como fallback', async () => {
|
||||
prismaMock.cotizacion.findMany.mockResolvedValue([
|
||||
{ id: 'ct1', clienteNombre: 'Carlos', clienteEmail: null, unidadCodigo: 'U-101', desarrollo: 'DIAGONAL', tipoPropuesta: null, createdAt: new Date() },
|
||||
])
|
||||
const r = await buscar('Carlos')
|
||||
expect(r[0]?.detalle).toBe('U-101 · DIAGONAL')
|
||||
|
||||
prismaMock.cotizacion.findMany.mockResolvedValue([
|
||||
{ id: 'ct2', clienteNombre: 'Diana', clienteEmail: null, unidadCodigo: null, desarrollo: null, tipoPropuesta: 'CONTADO', createdAt: new Date() },
|
||||
])
|
||||
const r2 = await buscar('Diana')
|
||||
expect(r2[0]?.detalle).toBe('CONTADO')
|
||||
})
|
||||
|
||||
it('unidad: detalle muestra desarrollo · modelo · status', async () => {
|
||||
prismaMock.unidad.findMany.mockResolvedValue([
|
||||
{ id: 'u1', codigo: 'U-202', desarrollo: 'ESTANZA', modelo: 'SOHO', status: 'Disponible', updatedAt: new Date() },
|
||||
])
|
||||
const r = await buscar('U-202')
|
||||
expect(r[0]?.detalle).toBe('ESTANZA · SOHO · Disponible')
|
||||
})
|
||||
|
||||
it('urls de navegación son correctas por tipo', async () => {
|
||||
prismaMock.leadCaptura.findMany.mockResolvedValue([
|
||||
{ leadId: 'l1', nombreLead: 'X', telefono: null, email: null, origen: null, board: 'LEADS', updatedAt: new Date() },
|
||||
])
|
||||
prismaMock.cliente.findMany.mockResolvedValue([
|
||||
{ id: 'c1', nombreCompleto: 'X', curp: '', rfc: '', email: null, celular: null, proyecto: '', createdAt: new Date() },
|
||||
])
|
||||
prismaMock.cotizacion.findMany.mockResolvedValue([
|
||||
{ id: 'ct1', clienteNombre: 'X', clienteEmail: null, unidadCodigo: null, desarrollo: null, tipoPropuesta: null, createdAt: new Date() },
|
||||
])
|
||||
prismaMock.unidad.findMany.mockResolvedValue([
|
||||
{ id: 'u1', codigo: 'X', desarrollo: null, modelo: null, status: null, updatedAt: new Date() },
|
||||
])
|
||||
|
||||
const r = await buscar('X')
|
||||
expect(r.find((i) => i.tipo === 'lead')?.url).toBe('/app/#leads')
|
||||
expect(r.find((i) => i.tipo === 'contacto')?.url).toBe('/app/#clientes')
|
||||
expect(r.find((i) => i.tipo === 'oportunidad')?.url).toBe('/quote/?cotizacion=ct1')
|
||||
expect(r.find((i) => i.tipo === 'unidad')?.url).toBe('/quote/?unidad=X')
|
||||
})
|
||||
})
|
||||
Reference in New Issue
Block a user