feat(pendientes): cotizador (mensualidades, bloqueo de unidad, totales, numMensualidades), cobranza (recibos, atrasos, mora configurable, recordatorios), soporte (adjuntos, filtros, estados), clientes maestro (catalogos reales), contactos (ficha y duplicados) y permisos de captura por rol

This commit is contained in:
2026-09-21 08:34:53 +00:00
parent b5a20aed4e
commit 2fc80e7f9e
66 changed files with 3792 additions and 436 deletions

View File

@@ -7,6 +7,9 @@ const prismaMock = vi.hoisted(() => ({
unidad: {
findUnique: vi.fn(),
},
capturaDepartamento: {
findMany: vi.fn(),
},
leadCaptura: {
findFirst: vi.fn(),
findUnique: vi.fn(),
@@ -77,6 +80,7 @@ function resFake() {
beforeEach(() => {
vi.clearAllMocks()
prismaMock.unidad.findUnique.mockResolvedValue(filaUnidad)
prismaMock.capturaDepartamento.findMany.mockResolvedValue([])
prismaMock.leadCaptura.findFirst.mockResolvedValue(null)
prismaMock.leadCaptura.findUnique.mockResolvedValue(null)
prismaMock.leadCaptura.update.mockImplementation(async ({ data }) => ({ ...data }))
@@ -184,6 +188,49 @@ describe('crearCotizacion (POST /api/cotizaciones) — local', () => {
expect(prismaMock.leadCaptura.findFirst).not.toHaveBeenCalled()
})
it('toma la fecha de entrega de la captura: mensualidad y total ya no salen nulos', async () => {
prismaMock.capturaDepartamento.findMany.mockResolvedValue([
{ unidadCodigo: 'U-101', campo: 'habitabilidad', valor: '2027-10-08' },
])
const { res, json } = resFake()
await crearCotizacion(reqFake(bodyBase), res)
const respuesta = json.mock.calls[0]?.[0] as {
numMensualidades: number
mensualidad: number
propuesta: { numMensualidades: number; mensualidad: number; mensualidadesTotal: number }
}
expect(respuesta.numMensualidades).toBeGreaterThan(0)
expect(respuesta.numMensualidades).toBe(respuesta.propuesta.numMensualidades)
expect(respuesta.propuesta.mensualidad).toBeGreaterThan(0)
expect(respuesta.propuesta.mensualidadesTotal).toBeGreaterThan(0)
})
it('las mensualidades elegidas por el vendedor viajan al cálculo y se persisten', async () => {
const { res, json } = resFake()
await crearCotizacion(reqFake({ ...bodyBase, numMensualidades: 12 }), res)
// DIAGONAL bancario sin acabados: remanente 75,000 en 12 pagos
expect(prismaMock.cotizacion.create).toHaveBeenCalledWith({
data: expect.objectContaining({ numMensualidades: 12, mensualidad: 6250 }),
})
const respuesta = json.mock.calls[0]?.[0] as { numMensualidades: number; propuesta: { numMensualidades: number } }
expect(respuesta.numMensualidades).toBe(12)
expect(respuesta.propuesta.numMensualidades).toBe(12)
})
it('unidad no DISPONIBLE (apartada/vendida) → 409 y no se crea cotización', async () => {
prismaMock.unidad.findUnique.mockResolvedValue({ ...filaUnidad, codigo: 'ATELIER-10', status: 'VENDIDO' })
const { res } = resFake()
await expect(crearCotizacion(reqFake({ ...bodyBase, unidadCodigo: 'ATELIER-10' }), res)).rejects.toMatchObject({
status: 409,
})
expect(prismaMock.cotizacion.create).not.toHaveBeenCalled()
})
})
describe('alertasInventarioEndpoint (POST /api/cotizaciones/alertas-inventario) — actividades locales', () => {