feat(cotizador): tope de mensualidades = meses que faltan para la entrega (UI y API); nunca se rebasa la fecha de entrega
This commit is contained in:
@@ -43,7 +43,7 @@ const prismaMock = vi.hoisted(() => ({
|
||||
vi.mock('../services/prisma.js', () => ({ prisma: prismaMock }))
|
||||
|
||||
import { alertasInventarioEndpoint, crearCotizacion } from './cotizaciones.controller.js'
|
||||
import { DESARROLLOS } from '../services/cotizacion.service.js'
|
||||
import { DESARROLLOS, calcularNumMensualidades } from '../services/cotizacion.service.js'
|
||||
|
||||
/**
|
||||
* Cotizaciones 100% locales: la unidad sale del espejo (tabla Unidad), la
|
||||
@@ -260,7 +260,7 @@ describe('crearCotizacion (POST /api/cotizaciones) — local', () => {
|
||||
expect(prismaMock.leadCaptura.findFirst).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('sin numMensualidades usa el default del esquema (14)', async () => {
|
||||
it('sin numMensualidades usa el default (14), acotado a los meses que faltan para la entrega', async () => {
|
||||
prismaMock.capturaDepartamento.findMany.mockResolvedValue([
|
||||
{ unidadCodigo: 'U-101', campo: 'habitabilidad', valor: '2027-10-08' },
|
||||
])
|
||||
@@ -268,14 +268,15 @@ describe('crearCotizacion (POST /api/cotizaciones) — local', () => {
|
||||
|
||||
await crearCotizacion(reqFake(bodyBase), res)
|
||||
|
||||
const esperadas = Math.min(14, calcularNumMensualidades('2027-10-08'))
|
||||
const respuesta = json.mock.calls[0]?.[0] as {
|
||||
numMensualidades: number
|
||||
mensualidad: number
|
||||
esquema: { numMensualidades: number; mensualidad: number; baseDiferible: number }
|
||||
}
|
||||
expect(respuesta.numMensualidades).toBe(14)
|
||||
expect(respuesta.numMensualidades).toBe(esperadas)
|
||||
expect(respuesta.numMensualidades).toBe(respuesta.esquema.numMensualidades)
|
||||
expect(respuesta.esquema.mensualidad).toBeCloseTo(respuesta.esquema.baseDiferible / 14, 6)
|
||||
expect(respuesta.esquema.mensualidad).toBeCloseTo(respuesta.esquema.baseDiferible / esperadas, 6)
|
||||
})
|
||||
|
||||
it('las mensualidades elegidas por el vendedor viajan al cálculo y se persisten', async () => {
|
||||
@@ -299,6 +300,24 @@ describe('crearCotizacion (POST /api/cotizaciones) — local', () => {
|
||||
expect(prismaMock.cotizacion.create).not.toHaveBeenCalled()
|
||||
})
|
||||
|
||||
it('acota las mensualidades a los meses que faltan para la entrega estimada de la unidad', async () => {
|
||||
// La captura de obra marca la entrega de U-101 a ~6 meses (fija vs hoy)
|
||||
const entrega = new Date(Date.now() + 6 * 30.4 * 86400000).toISOString().slice(0, 10)
|
||||
prismaMock.capturaDepartamento.findMany.mockResolvedValue([
|
||||
{ unidadCodigo: 'U-101', campo: 'entrega_programada_fecha', valor: entrega },
|
||||
])
|
||||
const { res, json } = resFake()
|
||||
|
||||
await crearCotizacion(reqFake({ ...bodyBase, numMensualidades: 20 }), res)
|
||||
|
||||
const respuesta = json.mock.calls[0]?.[0] as { numMensualidades: number; esquema: { numMensualidades: number } }
|
||||
expect(respuesta.numMensualidades).toBe(6)
|
||||
expect(respuesta.esquema.numMensualidades).toBe(6)
|
||||
expect(prismaMock.cotizacion.create).toHaveBeenCalledWith({
|
||||
data: expect.objectContaining({ numMensualidades: 6 }),
|
||||
})
|
||||
})
|
||||
|
||||
it('persiste y devuelve las opciones de crédito que manda el cotizador', async () => {
|
||||
const opcionesCredito = JSON.stringify({
|
||||
elegida: 'CONTADO',
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
import type { Request, Response } from 'express'
|
||||
import { z } from 'zod'
|
||||
import { getUnidadLocal } from '../services/unidades-local.service.js'
|
||||
import { obtenerPrecioLista } from '../services/cotizacion.service.js'
|
||||
import { calcularNumMensualidades, obtenerPrecioLista } from '../services/cotizacion.service.js'
|
||||
import {
|
||||
calcularEsquemaFrasa,
|
||||
MENSUALIDADES_DEFAULT,
|
||||
@@ -109,7 +109,12 @@ export async function crearCotizacion(req: Request, res: Response): Promise<void
|
||||
// El API RECALCULA el esquema server-side con el precio de lista de la
|
||||
// unidad: no se confía en los montos que manda el cliente.
|
||||
const apartado = body.apartado ?? APARTADO_DEFAULT
|
||||
const numMensualidades = body.numMensualidades ?? MENSUALIDADES_DEFAULT
|
||||
// El diferimiento no puede rebasar la entrega estimada de la unidad (captura
|
||||
// de obra): se acota a los meses que faltan (36 sigue siendo el tope global)
|
||||
const topeMensualidades = unidad.fechaEntrega
|
||||
? Math.min(MENSUALIDADES_MAX, Math.max(MENSUALIDADES_MIN, calcularNumMensualidades(unidad.fechaEntrega)))
|
||||
: MENSUALIDADES_MAX
|
||||
const numMensualidades = Math.min(body.numMensualidades ?? MENSUALIDADES_DEFAULT, topeMensualidades)
|
||||
const escrituracionDiferida = body.escrituracionDiferida ?? false
|
||||
const precioLista = obtenerPrecioLista(unidad, body.acabados)
|
||||
const esquema: EsquemaFrasaResultado = calcularEsquemaFrasa({
|
||||
|
||||
Reference in New Issue
Block a user