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:
@@ -2,7 +2,7 @@ import { useMemo, useState } from 'react'
|
||||
import type { Unidad, TipoPropuesta } from '../types'
|
||||
import { compararUnidades, type ColumnaComparador } from '../lib/comparadorUnidades'
|
||||
import { formatoMoneda } from '../lib/formatters'
|
||||
import { LISTA_DESARROLLOS, obtenerDesarrollo } from '../data/desarrollos'
|
||||
import { SIN_REGLAS, desarrolloEfectivo, idsCotizables, type ReglasDesarrollo } from '../lib/reglas'
|
||||
|
||||
interface Props {
|
||||
unidades: Unidad[]
|
||||
@@ -10,6 +10,8 @@ interface Props {
|
||||
tipo: TipoPropuesta
|
||||
apartado: number
|
||||
acabados: boolean
|
||||
/** Reglas comerciales por desarrollo (vacío => constantes) */
|
||||
reglas?: ReglasDesarrollo
|
||||
}
|
||||
|
||||
const formatoM2 = (v: number | null): string => (v === null ? '—' : `${v.toLocaleString('es-MX')} m²`)
|
||||
@@ -23,17 +25,25 @@ interface Fila {
|
||||
/**
|
||||
* Comparador de 2 unidades (del mismo o distinto desarrollo), lado a lado,
|
||||
* con la propuesta activa calculada por el motor con los parámetros de cada
|
||||
* desarrollo. Solo ofrece unidades DISPONIBLES: no se compara (ni se cotiza)
|
||||
* inventario apartado, vendido o bloqueado.
|
||||
* desarrollo (las reglas del admin con respaldo en las constantes). Solo ofrece
|
||||
* unidades DISPONIBLES de desarrollos activos en el cotizador: no se compara
|
||||
* (ni se cotiza) inventario apartado, vendido, bloqueado o desactivado.
|
||||
*/
|
||||
export function ComparadorUnidades({ unidades, tipo, apartado, acabados }: Props) {
|
||||
export function ComparadorUnidades({ unidades, tipo, apartado, acabados, reglas = SIN_REGLAS }: Props) {
|
||||
const [abierto, setAbierto] = useState(false)
|
||||
const [codigoA, setCodigoA] = useState('')
|
||||
const [codigoB, setCodigoB] = useState('')
|
||||
|
||||
const desarrollos = useMemo(() => idsCotizables(reglas), [reglas])
|
||||
|
||||
const disponibles = useMemo(
|
||||
() => unidades.filter((u) => (u.status ?? '').trim().toUpperCase() === 'DISPONIBLE'),
|
||||
[unidades],
|
||||
() =>
|
||||
unidades.filter(
|
||||
(u) =>
|
||||
(u.status ?? '').trim().toUpperCase() === 'DISPONIBLE' &&
|
||||
desarrollos.includes(u.desarrollo ?? 'DIAGONAL'),
|
||||
),
|
||||
[unidades, desarrollos],
|
||||
)
|
||||
|
||||
const porCodigo = useMemo(() => {
|
||||
@@ -46,8 +56,11 @@ export function ComparadorUnidades({ unidades, tipo, apartado, acabados }: Props
|
||||
const unidadB = porCodigo.get(codigoB) ?? null
|
||||
|
||||
const comparacion = useMemo(
|
||||
() => (unidadA && unidadB ? compararUnidades(unidadA, unidadB, tipo, apartado, acabados) : null),
|
||||
[unidadA, unidadB, tipo, apartado, acabados],
|
||||
() =>
|
||||
unidadA && unidadB
|
||||
? compararUnidades(unidadA, unidadB, tipo, apartado, acabados, reglas)
|
||||
: null,
|
||||
[unidadA, unidadB, tipo, apartado, acabados, reglas],
|
||||
)
|
||||
|
||||
const filas: Fila[] = [
|
||||
@@ -87,11 +100,11 @@ export function ComparadorUnidades({ unidades, tipo, apartado, acabados }: Props
|
||||
className="w-full rounded-lg border border-[#D1D5DB] bg-white px-3 dark:border-[#334155] dark:bg-[#0F172A] dark:text-[#E2E8F0] py-2.5 text-sm transition-colors focus:border-accent focus:ring-2 focus:ring-accent/20 focus:outline-none"
|
||||
>
|
||||
<option value="">Selecciona una unidad…</option>
|
||||
{LISTA_DESARROLLOS.map((dev) => {
|
||||
{desarrollos.map((dev) => {
|
||||
const delDesarrollo = disponibles.filter((u) => (u.desarrollo ?? 'DIAGONAL') === dev)
|
||||
if (delDesarrollo.length === 0) return null
|
||||
return (
|
||||
<optgroup key={dev} label={obtenerDesarrollo(dev).nombre}>
|
||||
<optgroup key={dev} label={desarrolloEfectivo(reglas, dev).nombre}>
|
||||
{delDesarrollo.map((u) => (
|
||||
<option key={`${dev}|${u.codigo}`} value={`${dev}|${u.codigo}`}>
|
||||
{u.codigo} — {u.modelo}
|
||||
|
||||
Reference in New Issue
Block a user