- Motor de cálculo parametrizado por desarrollo (enganches, descuentos, splits, fuente de N, quirk Infonavit-lista); DIAGONAL byte-idéntico - 285 unidades (5 desarrollos) en seeds, API (?desarrollo=) y board Monday - Selector de desarrollo en UI, propuestas por config, PDF multi-desarrollo - Knowledge IA: catálogo por desarrollo + matriz de diferencias (10 docs) - Tests: cotizador 53, api 99 — fixtures de los Excel validados al centavo
92 lines
3.7 KiB
TypeScript
92 lines
3.7 KiB
TypeScript
import type { Request, Response } from 'express'
|
|
import { z } from 'zod'
|
|
import { mondayService } from '../services/monday.service.js'
|
|
import { calcularPropuesta, obtenerPrecioLista } from '../services/cotizacion.service.js'
|
|
import { prisma } from '../services/prisma.js'
|
|
import { NotFoundError } from '../utils/errors.js'
|
|
|
|
const crearCotizacionSchema = z.object({
|
|
unidadCodigo: z.string().min(1),
|
|
acabados: z.boolean(),
|
|
apartado: z.number().positive().optional(),
|
|
clienteNombre: z.string().min(1),
|
|
clienteEmail: z.string().email().optional(),
|
|
clienteTelefono: z.string().min(7).optional(),
|
|
tipoPropuesta: z.enum(['BANCARIO', 'INFONAVIT', 'COFINAVIT', 'CONTADO', 'SEMI_CONTADO']),
|
|
descuento: z.number().min(0).max(1).optional(), // fracción (0.02 = 2%)
|
|
})
|
|
|
|
const hoyISO = (): string => {
|
|
const d = new Date()
|
|
const m = String(d.getMonth() + 1).padStart(2, '0')
|
|
const dia = String(d.getDate()).padStart(2, '0')
|
|
return `${d.getFullYear()}-${m}-${dia}`
|
|
}
|
|
|
|
/** POST /api/cotizaciones — calcula, persiste y registra la oportunidad en Monday */
|
|
export async function crearCotizacion(req: Request, res: Response): Promise<void> {
|
|
const body = crearCotizacionSchema.parse(req.body)
|
|
|
|
const unidad = await mondayService.getUnidad(body.unidadCodigo)
|
|
if (!unidad) throw new NotFoundError(`Unidad ${body.unidadCodigo} no encontrada`)
|
|
|
|
const apartado = body.apartado ?? 20000 // APARTADO_DEFAULT
|
|
const propuesta = calcularPropuesta(unidad, body.tipoPropuesta, body.acabados, apartado, body.descuento)
|
|
const precioLista = obtenerPrecioLista(unidad, body.acabados)
|
|
|
|
const cotizacion = await prisma.cotizacion.create({
|
|
data: {
|
|
unidadCodigo: unidad.codigo,
|
|
clienteNombre: body.clienteNombre,
|
|
clienteEmail: body.clienteEmail ?? null,
|
|
clienteTelefono: body.clienteTelefono ?? null,
|
|
tipoPropuesta: body.tipoPropuesta,
|
|
acabados: body.acabados,
|
|
apartado,
|
|
descuento: propuesta.descuento,
|
|
precioLista,
|
|
precioFinal: propuesta.precioFinal,
|
|
enganche: propuesta.enganche,
|
|
numMensualidades: propuesta.numMensualidades ?? 0, // 0 => plazo desconocido (sin fecha de entrega)
|
|
mensualidad: propuesta.mensualidad,
|
|
pagoFinal: propuesta.pagoFinal,
|
|
},
|
|
})
|
|
|
|
// Registra la oportunidad en Monday; si Monday falla, la cotización ya quedó persistida
|
|
let mondayItemId: string | null = null
|
|
try {
|
|
mondayItemId = await mondayService.crearOportunidad({
|
|
clienteNombre: body.clienteNombre,
|
|
unidadCodigo: unidad.codigo,
|
|
tipoPropuestaLabel: body.tipoPropuesta,
|
|
precioLista,
|
|
descuentoPct: propuesta.descuento * 100,
|
|
precioFinal: propuesta.precioFinal,
|
|
fechaCotizacion: hoyISO(),
|
|
estadoLabel: 'COTIZADA',
|
|
...(body.clienteEmail !== undefined ? { clienteEmail: body.clienteEmail } : {}),
|
|
...(body.clienteTelefono !== undefined ? { clienteTelefono: body.clienteTelefono } : {}),
|
|
})
|
|
await prisma.cotizacion.update({ where: { id: cotizacion.id }, data: { mondayItemId } })
|
|
} catch (err) {
|
|
// eslint-disable-next-line no-console
|
|
console.error('No se pudo crear la oportunidad en Monday:', err)
|
|
}
|
|
|
|
res.status(201).json({ ...cotizacion, mondayItemId, propuesta })
|
|
}
|
|
|
|
/** GET /api/cotizaciones */
|
|
export async function listarCotizaciones(_req: Request, res: Response): Promise<void> {
|
|
const cotizaciones = await prisma.cotizacion.findMany({ orderBy: { createdAt: 'desc' } })
|
|
res.json(cotizaciones)
|
|
}
|
|
|
|
/** GET /api/cotizaciones/:id */
|
|
export async function obtenerCotizacion(req: Request, res: Response): Promise<void> {
|
|
const cotizacion = await prisma.cotizacion.findUnique({ where: { id: String(req.params['id']) } })
|
|
if (!cotizacion) throw new NotFoundError('Cotización no encontrada')
|
|
res.json(cotizacion)
|
|
}
|