feat(desarrollos): modulo de alta de desarrollos con inventario, reglas del cotizador por desarrollo y micrositio publico
Modulo nuevo (solo admin) para dar de alta desarrollos sin tocar codigo: - CRUD de la ficha, modelos e inventario (alta manual y generador por rango) - Reglas del cotizador por desarrollo, editables y persistidas en la BD - El cotizador consume las reglas desde /desarrollos/reglas-cotizador y cae a las constantes con aviso cuando un desarrollo aun no tiene reglas guardadas - Micrositio publico en /d/:clave (sin sesion) con ficha, modelos y disponibilidad; jamas expone datos de clientes ni cotizaciones Integracion: - La landing monta el modulo en el grid (clave 'desarrollos', solo admin) y el micrositio fuera del login; el rol manager NO accede (matriz y API con 403) - La tabla de roles de Configuracion ahora lista todos los modulos - Se alinea la FK de ReglaCotizadorDesarrollo con la BD viva (Desarrollo.id) Verificado: 708 pruebas del API, 115 del cotizador y 89 de la landing en verde; 12/12 cotizaciones identicas antes y despues del cambio de reglas; prueba en vivo del API con alta, modelos, inventario, reglas, micrositio y baja (30/30).
This commit is contained in:
@@ -48,10 +48,10 @@ export const DESCUENTOS_DEFAULT: DescuentosPropuestas = {
|
||||
|
||||
// ---------- Parametrización por desarrollo ----------
|
||||
|
||||
type BasePrecio = 'lista' | 'final'
|
||||
type ModoMensualidades = 'ninguna' | 'remanente' | 'remanenteLista' | 'porcentaje'
|
||||
export type BasePrecio = 'lista' | 'final'
|
||||
export type ModoMensualidades = 'ninguna' | 'remanente' | 'remanenteLista' | 'porcentaje'
|
||||
|
||||
interface PropuestaConfig {
|
||||
export interface PropuestaConfig {
|
||||
tipo: TipoPropuesta
|
||||
claveDescuento: keyof DescuentosPropuestas
|
||||
titulo: string
|
||||
@@ -62,10 +62,17 @@ interface PropuestaConfig {
|
||||
pagoFinalBase: BasePrecio
|
||||
modoMensualidades: ModoMensualidades
|
||||
engancheCeroSinApartado?: boolean
|
||||
/**
|
||||
* Regla del admin: false => la propuesta no se ofrece ni se puede elegir en
|
||||
* el cotizador. Ausente/true => vigente (comportamiento histórico).
|
||||
*/
|
||||
activa?: boolean
|
||||
}
|
||||
|
||||
interface ParametrosDesarrollo {
|
||||
export interface ParametrosDesarrollo {
|
||||
id: string
|
||||
/** Nombre para mostrar (solo lo traen las reglas guardadas por el admin) */
|
||||
nombre?: string
|
||||
precioUnico: boolean
|
||||
fuenteDeN: 'fechaEntrega' | 'fijo' | 'porUnidad'
|
||||
nFijo?: number
|
||||
@@ -75,6 +82,15 @@ interface ParametrosDesarrollo {
|
||||
* siempre se calculen (espejo de apps/cotizador/src/data/desarrollos.ts).
|
||||
*/
|
||||
nDefault?: number
|
||||
/** Cuota mensual de mantenimiento (MXN); null => pendiente */
|
||||
cuotaMantenimiento?: number | null
|
||||
/** Prefijo de la unidad en el PDF ('CASA', 'DEPTO') */
|
||||
prefijoUnidadPdf?: string
|
||||
/**
|
||||
* Regla del admin: false => el desarrollo no se ofrece en el cotizador.
|
||||
* Ausente/true => disponible (comportamiento histórico).
|
||||
*/
|
||||
activoEnCotizador?: boolean
|
||||
propuestas: PropuestaConfig[]
|
||||
}
|
||||
|
||||
@@ -153,10 +169,23 @@ export const DESARROLLOS: Record<string, ParametrosDesarrollo> = {
|
||||
|
||||
const DESARROLLO_DEFAULT = 'DIAGONAL'
|
||||
|
||||
function obtenerDesarrollo(id?: string | null): ParametrosDesarrollo {
|
||||
/**
|
||||
* Reglas comerciales del desarrollo: las configuradas por el admin (BD) ganan
|
||||
* cuando corresponden al id pedido; si no, se usan las constantes del módulo.
|
||||
*/
|
||||
function obtenerDesarrollo(
|
||||
id?: string | null,
|
||||
reglas?: ParametrosDesarrollo | null,
|
||||
): ParametrosDesarrollo {
|
||||
if (reglas && (id === undefined || id === null || id === '' || reglas.id === id)) return reglas
|
||||
return (id && DESARROLLOS[id]) || DESARROLLOS[DESARROLLO_DEFAULT]!
|
||||
}
|
||||
|
||||
/** Propuestas vigentes: el admin puede desactivar una propuesta por desarrollo */
|
||||
export function propuestasVigentes(params: ParametrosDesarrollo): PropuestaConfig[] {
|
||||
return params.propuestas.filter((p) => p.activa !== false)
|
||||
}
|
||||
|
||||
export interface PropuestaResultado {
|
||||
tipo: TipoPropuesta
|
||||
titulo: string
|
||||
@@ -177,6 +206,11 @@ export interface CotizacionInput {
|
||||
apartado: number
|
||||
descuentos: DescuentosPropuestas
|
||||
desarrollo?: string
|
||||
/**
|
||||
* Reglas comerciales efectivas del desarrollo (las configuradas por el admin
|
||||
* o su respaldo en constantes). Si no vienen, se usan las constantes.
|
||||
*/
|
||||
reglas?: ParametrosDesarrollo
|
||||
/** Permite forzar N en tests; por defecto se resuelve según la fuenteDeN del desarrollo */
|
||||
numMeses?: number
|
||||
}
|
||||
@@ -356,13 +390,16 @@ export function indiceMenorTotal(totales: number[]): number {
|
||||
return indice
|
||||
}
|
||||
|
||||
/** Calcula las propuestas comerciales del desarrollo de la unidad */
|
||||
/** Calcula las propuestas comerciales vigentes del desarrollo de la unidad */
|
||||
export function calcularPropuestas(input: CotizacionInput): PropuestaResultado[] {
|
||||
const { unidad, acabados, apartado, descuentos } = input
|
||||
const params = obtenerDesarrollo(input.desarrollo ?? unidad.desarrollo ?? DESARROLLO_DEFAULT)
|
||||
const params = obtenerDesarrollo(
|
||||
input.desarrollo ?? unidad.desarrollo ?? DESARROLLO_DEFAULT,
|
||||
input.reglas ?? null,
|
||||
)
|
||||
const precioLista = obtenerPrecioLista(unidad, acabados)
|
||||
const n = input.numMeses ?? resolverNumMensualidades(unidad, params)
|
||||
return params.propuestas.map((cfg) =>
|
||||
return propuestasVigentes(params).map((cfg) =>
|
||||
calcularPropuestaConfig(cfg, precioLista, apartado, descuentos[cfg.claveDescuento], n),
|
||||
)
|
||||
}
|
||||
@@ -386,6 +423,8 @@ export type TipoPropuestaApi = keyof typeof TIPO_PROPUESTA_API
|
||||
* crédito (bancario/Infonavit); Contado y Semi Contado usan su % fijo.
|
||||
* `numMeses` opcional: número de mensualidades elegido por el vendedor en el
|
||||
* cotizador; si no viene se resuelve con la fuente del desarrollo.
|
||||
* `reglas` opcional: reglas comerciales efectivas del desarrollo de la unidad
|
||||
* (BD del admin con respaldo en constantes); sin ellas se usan las constantes.
|
||||
*/
|
||||
export function calcularPropuesta(
|
||||
unidad: UnidadCotizable,
|
||||
@@ -395,12 +434,18 @@ export function calcularPropuesta(
|
||||
descuento?: number,
|
||||
enganchePct?: number,
|
||||
numMeses?: number,
|
||||
reglas?: ParametrosDesarrollo | null,
|
||||
): PropuestaResultado {
|
||||
const params = obtenerDesarrollo(unidad.desarrollo)
|
||||
const params = obtenerDesarrollo(unidad.desarrollo, reglas ?? null)
|
||||
const tipoInterno = TIPO_PROPUESTA_API[tipo]
|
||||
const cfg = params.propuestas.find((p) => p.tipo === tipoInterno)
|
||||
const cfg = propuestasVigentes(params).find((p) => p.tipo === tipoInterno)
|
||||
if (!cfg) {
|
||||
throw new Error(`La propuesta ${tipo} no aplica para el desarrollo ${params.id}`)
|
||||
const desactivada = params.propuestas.some((p) => p.tipo === tipoInterno)
|
||||
throw new Error(
|
||||
desactivada
|
||||
? `La propuesta ${tipo} está desactivada para el desarrollo ${params.id}`
|
||||
: `La propuesta ${tipo} no aplica para el desarrollo ${params.id}`,
|
||||
)
|
||||
}
|
||||
const precioLista = obtenerPrecioLista(unidad, acabados)
|
||||
const n = numMeses ?? resolverNumMensualidades(unidad, params)
|
||||
|
||||
Reference in New Issue
Block a user