feat(cotizador): enganche adicional hasta 90% de anticipo total (UI, opciones de credito, PDF, API y persistencia)
This commit is contained in:
@@ -516,3 +516,27 @@ describe('alertasInventarioEndpoint (POST /api/cotizaciones/alertas-inventario)
|
||||
expect(prismaMock.actividadLocal.create).not.toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
|
||||
describe('crearCotizacion — enganche adicional', () => {
|
||||
it('persiste el enganche adicional y recalcula el saldo a financiar', async () => {
|
||||
const { res, json } = resFake()
|
||||
|
||||
await crearCotizacion(reqFake({ ...bodyBase, engancheAdicional: 400000 }), res)
|
||||
|
||||
expect(prismaMock.cotizacion.create).toHaveBeenCalledWith({
|
||||
data: expect.objectContaining({ engancheAdicional: 400000 }),
|
||||
})
|
||||
const respuesta = json.mock.calls[0]?.[0] as { esquema: { engancheAdicional: number; saldoAFinanciar: number } }
|
||||
expect(respuesta.esquema.engancheAdicional).toBe(400000)
|
||||
expect(respuesta.esquema.saldoAFinanciar).toBeCloseTo(445000, 2)
|
||||
})
|
||||
|
||||
it('rechaza con 400 un adicional que pase del 90% de enganche total', async () => {
|
||||
const { res } = resFake()
|
||||
|
||||
await expect(
|
||||
crearCotizacion(reqFake({ ...bodyBase, engancheAdicional: 900000 }), res),
|
||||
).rejects.toBeDefined()
|
||||
expect(prismaMock.cotizacion.create).not.toHaveBeenCalled()
|
||||
})
|
||||
})
|
||||
|
||||
@@ -41,6 +41,9 @@ const crearCotizacionSchema = z.object({
|
||||
.max(MENSUALIDADES_MAX, `Máximo ${MENSUALIDADES_MAX} mensualidades`)
|
||||
.optional(),
|
||||
escrituracionDiferida: z.boolean().optional(),
|
||||
// Enganche adicional (anticipo extra): el total de anticipos (esquema +
|
||||
// adicional) no puede superar el 90% del precio; el espejo lo valida
|
||||
engancheAdicional: z.number().min(0, 'El enganche adicional no puede ser negativo').optional(),
|
||||
// Opciones de credito del saldo (84.5%): JSON serializado {elegida, ajustes}
|
||||
// que manda el cotizador; se persiste crudo y se devuelve en los GET
|
||||
opcionesCredito: z.string().max(4000).optional(),
|
||||
@@ -117,12 +120,21 @@ export async function crearCotizacion(req: Request, res: Response): Promise<void
|
||||
const numMensualidades = Math.min(body.numMensualidades ?? MENSUALIDADES_DEFAULT, topeMensualidades)
|
||||
const escrituracionDiferida = body.escrituracionDiferida ?? false
|
||||
const precioLista = obtenerPrecioLista(unidad, body.acabados)
|
||||
const esquema: EsquemaFrasaResultado = calcularEsquemaFrasa({
|
||||
precio: precioLista,
|
||||
apartado,
|
||||
numMensualidades,
|
||||
escrituracionDiferida,
|
||||
})
|
||||
// Enganche adicional: el total de anticipos no puede pasar del 90% del
|
||||
// precio (espejo valida; se traduce a 400 con el mensaje del negocio)
|
||||
const esquema: EsquemaFrasaResultado = (() => {
|
||||
try {
|
||||
return calcularEsquemaFrasa({
|
||||
precio: precioLista,
|
||||
apartado,
|
||||
numMensualidades,
|
||||
escrituracionDiferida,
|
||||
...(body.engancheAdicional !== undefined ? { engancheAdicional: body.engancheAdicional } : {}),
|
||||
})
|
||||
} catch (err) {
|
||||
throw new AppError(400, err instanceof Error ? err.message : 'Datos del esquema inválidos')
|
||||
}
|
||||
})()
|
||||
|
||||
const cotizacion = await prisma.cotizacion.create({
|
||||
data: {
|
||||
@@ -143,6 +155,7 @@ export async function crearCotizacion(req: Request, res: Response): Promise<void
|
||||
pctEscrituracion: PCT_ESCRITURACION,
|
||||
escrituracionDiferida,
|
||||
escrituracion: esquema.escrituracion,
|
||||
engancheAdicional: esquema.engancheAdicional,
|
||||
// Opciones de credito elegidas por el vendedor (JSON crudo)
|
||||
opcionesCredito: body.opcionesCredito ?? null,
|
||||
// Columnas legacy, rellenadas para compatibilidad de reportes:
|
||||
|
||||
Reference in New Issue
Block a user