Files
FRASA-CAS/apps/landing/src/components/publico/SeccionDescripcion.tsx
Consult AS Technology d6548999bf 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).
2026-09-21 09:52:47 +00:00

70 lines
2.6 KiB
TypeScript

import { Check, Sparkles } from 'lucide-react'
import { separarAmenidades } from './formato'
interface Props {
descripcion?: string | null
amenidades?: string | null
}
/** Descripción del desarrollo a dos columnas + amenidades como chips con palomita */
export function SeccionDescripcion({ descripcion, amenidades }: Props) {
const parrafos = (descripcion ?? '')
.split(/\n+/)
.map((p) => p.trim())
.filter(Boolean)
const listaAmenidades = separarAmenidades(amenidades)
if (parrafos.length === 0 && listaAmenidades.length === 0) return null
return (
<section
aria-labelledby="titulo-descripcion"
className="mx-auto w-full max-w-6xl px-4 py-16 sm:px-6 sm:py-20 lg:px-8"
>
<div className="grid gap-10 lg:grid-cols-2 lg:gap-14">
<div>
<p className="text-xs font-semibold tracking-[0.18em] text-accent uppercase">El desarrollo</p>
<h2
id="titulo-descripcion"
className="mt-3 text-2xl font-semibold tracking-tight text-[#111827] sm:text-3xl dark:text-white"
>
Sobre el desarrollo
</h2>
<div className="mt-5 space-y-4 text-base leading-relaxed text-[#4B5563] dark:text-[#CBD5E1]">
{parrafos.length > 0 ? (
parrafos.map((parrafo, indice) => <p key={indice}>{parrafo}</p>)
) : (
<p>
Escríbenos para conocer a detalle las características, acabados y formas de pago de
este desarrollo.
</p>
)}
</div>
</div>
{listaAmenidades.length > 0 ? (
<div className="rounded-2xl border border-[#E5E7EB] bg-white p-6 shadow-sm sm:p-8 dark:border-[#1E293B] dark:bg-[#1E293B]">
<h3 className="flex items-center gap-2 text-base font-semibold text-[#111827] dark:text-white">
<Sparkles aria-hidden="true" className="h-4 w-4 text-accent" />
Amenidades
</h3>
<ul className="mt-5 grid gap-3 sm:grid-cols-2">
{listaAmenidades.map((amenidad) => (
<li key={amenidad} className="flex items-start gap-2.5 text-sm text-[#374151] dark:text-[#CBD5E1]">
<span
aria-hidden="true"
className="mt-0.5 flex h-5 w-5 shrink-0 items-center justify-center rounded-full bg-accent/10 text-accent dark:bg-accent/20"
>
<Check className="h-3.5 w-3.5" strokeWidth={3} />
</span>
<span>{amenidad}</span>
</li>
))}
</ul>
</div>
) : null}
</div>
</section>
)
}