style: reskin NovaSpark en las 7 apps

Del look editorial (verde #1B4332, esquinas a 0, botones en mayusculas)
al sistema NovaSpark: primario azul #2563EB, detalle naranja #F97316,
fondo #F8FAFC con tarjetas blancas rounded-xl + shadow-sm, chips
rounded-full semanticos, focus ring azul, transiciones 100-200ms.
Incluye headers de tabla en los PDFs (cotizador y estado de cuenta).
This commit is contained in:
2026-09-03 02:35:34 +00:00
parent 89fcd1c579
commit 599dce2629
78 changed files with 842 additions and 828 deletions

View File

@@ -39,11 +39,11 @@ const esquemaCliente = z.object({
type FormularioCliente = z.infer<typeof esquemaCliente>
const campoInputBase =
'w-full border bg-white px-3 py-2.5 text-sm transition-colors duration-150 focus:border-neutral-900 focus:outline-none'
const campoInput = `${campoInputBase} border-neutral-300`
'w-full rounded-lg border bg-white px-3 py-2.5 text-sm transition-colors focus:border-accent focus:ring-2 focus:ring-accent/20 focus:outline-none'
const campoInput = `${campoInputBase} border-[#D1D5DB]`
const campoInputError = `${campoInputBase} border-red-400`
const etiquetaCampo = 'mb-2 block text-xs font-bold uppercase tracking-widest text-neutral-500'
const etiquetaCampo = 'mb-2 block text-xs font-medium text-[#4B5563]'
export default function CotizadorPage() {
const { unidades, cargando, error, usandoSeed } = useUnidades()
@@ -254,18 +254,18 @@ export default function CotizadorPage() {
}
return (
<div className="min-h-screen bg-white">
<header className="border-b border-neutral-200 bg-white">
<div className="min-h-screen bg-[#F8FAFC]">
<header className="border-b border-[#E5E7EB] bg-white">
<div className="mx-auto flex max-w-7xl flex-wrap items-baseline justify-between gap-2 px-4 py-6">
<h1 className="text-lg tracking-widest text-neutral-900">
<span className="font-bold">FRASA DESARROLLOS</span>{' '}
<span className="font-light text-neutral-500">— Cotizador</span>
<h1 className="text-lg text-[#111827]">
<span className="font-bold">FRASA DESARROLLOS<span className="text-[#F97316]">.</span></span>{' '}
<span className="font-normal text-[#64748B]">— Cotizador</span>
</h1>
<div className="text-right">
<p className="text-xs font-bold tracking-widest text-neutral-500 uppercase capitalize">
<p className="text-xs font-semibold uppercase tracking-wider text-[#64748B] capitalize">
{formatoFechaLarga(new Date())}
</p>
<p className="mt-1 text-xs text-neutral-500">
<p className="mt-1 text-xs text-[#64748B]">
Vigencia: hasta el {formatoFechaLarga(calcularVigencia())}
</p>
</div>
@@ -275,18 +275,18 @@ export default function CotizadorPage() {
<main className="mx-auto grid max-w-7xl gap-x-8 gap-y-16 px-4 py-16 lg:grid-cols-[minmax(320px,380px)_1fr]">
{/* Panel de configuración */}
<aside className="space-y-16">
<section className="space-y-8 border border-neutral-200 bg-white p-6">
<section className="space-y-8 rounded-xl border border-[#E5E7EB] bg-white p-6 shadow-sm">
{error && <p className="text-xs text-red-700">{error}</p>}
{usandoSeed && !cargando && (
<p className="text-xs text-amber-700">Datos de demostración (sin conexión al servidor)</p>
)}
{cargando ? (
<p className="text-sm text-neutral-500">Cargando unidades…</p>
<p className="text-sm text-[#64748B]">Cargando unidades…</p>
) : (
<>
<div>
<span className={etiquetaCampo}>Desarrollo</span>
<div className="flex flex-wrap border border-neutral-300" role="group">
<div className="flex flex-wrap overflow-hidden rounded-lg border border-[#D1D5DB]" role="group">
{LISTA_DESARROLLOS.map((id) => {
const activo = id === desarrollo
const total = unidades.filter((u) => (u.desarrollo ?? 'DIAGONAL') === id).length
@@ -299,21 +299,21 @@ export default function CotizadorPage() {
type="button"
aria-pressed={activo}
onClick={() => setDesarrollo(id)}
className={`px-4 py-2.5 text-xs font-bold tracking-widest uppercase transition-colors duration-150 ${
className={`px-4 py-2.5 text-xs font-medium transition-colors duration-100 ${
activo
? 'bg-accent text-white'
: 'bg-white text-neutral-600 hover:bg-neutral-100'
: 'bg-white text-[#4B5563] hover:bg-[#F9FAFB]'
}`}
>
{obtenerDesarrollo(id).nombre}
<span className={`ml-1 font-normal ${activo ? 'text-white' : 'text-neutral-400'}`}>
<span className={`ml-1 font-normal ${activo ? 'text-blue-100' : 'text-[#64748B]'}`}>
{disponibles}/{total}
</span>
</button>
)
})}
</div>
<p className="mt-2 text-xs text-neutral-500">
<p className="mt-2 text-xs text-[#64748B]">
{unidadesDesarrollo.length} unidades · {disponiblesDesarrollo} disponibles
</p>
</div>
@@ -330,7 +330,7 @@ export default function CotizadorPage() {
{!params.precioUnico && (
<div>
<span className={etiquetaCampo}>Cocina y closet</span>
<div className="inline-flex border border-neutral-300" role="group">
<div className="inline-flex overflow-hidden rounded-lg border border-[#D1D5DB]" role="group">
{(['SI', 'NO'] as const).map((opcion) => {
const activo = (opcion === 'SI') === acabados
return (
@@ -339,10 +339,10 @@ export default function CotizadorPage() {
type="button"
aria-pressed={activo}
onClick={() => setAcabados(opcion === 'SI')}
className={`px-5 py-2.5 text-xs font-bold tracking-widest uppercase transition-colors duration-150 ${
className={`px-5 py-2.5 text-xs font-medium transition-colors duration-100 ${
activo
? 'bg-accent text-white'
: 'bg-white text-neutral-600 hover:bg-neutral-100'
: 'bg-white text-[#4B5563] hover:bg-[#F9FAFB]'
}`}
>
{opcion}
@@ -372,7 +372,7 @@ export default function CotizadorPage() {
{errorApartado ? (
<p className="mt-1 text-xs text-red-700">{errorApartado}</p>
) : (
<p className="mt-1 text-xs text-neutral-500">{formatoMoneda(apartado)}</p>
<p className="mt-1 text-xs text-[#64748B]">{formatoMoneda(apartado)}</p>
)}
</div>
@@ -418,11 +418,11 @@ export default function CotizadorPage() {
</div>
{unidad && (
<div className="border-t border-neutral-200 pt-8">
<p className="text-xs font-bold tracking-widest text-neutral-500 uppercase">
<div className="border-t border-[#E5E7EB] pt-8">
<p className="text-xs font-semibold uppercase tracking-wider text-[#64748B]">
Precio de lista
</p>
<p className="mt-2 text-3xl font-light text-neutral-900">
<p className="mt-2 text-3xl font-semibold text-[#111827]">
{formatoMoneda(precioLista)}
</p>
</div>
@@ -432,7 +432,7 @@ export default function CotizadorPage() {
<button
type="button"
onClick={handleNueva}
className="w-full border border-neutral-300 bg-white px-5 py-2.5 text-xs font-bold tracking-widest text-neutral-600 uppercase transition-colors duration-150 hover:bg-neutral-100"
className="w-full rounded-lg border border-[#D1D5DB] bg-white px-5 py-2.5 text-sm font-medium text-[#374151] shadow-sm transition-all duration-100 ease-out hover:bg-[#F9FAFB]"
>
Nueva cotización
</button>
@@ -452,8 +452,8 @@ export default function CotizadorPage() {
/>
{!unidad ? (
<div className="mt-16 flex h-64 items-center justify-center border border-dashed border-neutral-300 bg-white">
<p className="text-sm text-neutral-500">
<div className="mt-16 flex h-64 items-center justify-center rounded-xl border border-dashed border-[#D1D5DB] bg-white">
<p className="text-sm text-[#64748B]">
Selecciona una unidad para ver las propuestas comerciales.
</p>
</div>
@@ -485,7 +485,7 @@ export default function CotizadorPage() {
id="propuesta-elegida"
value={Math.min(propuestaIndex, propuestas.length - 1)}
onChange={(e) => setPropuestaIndex(Number(e.target.value))}
className="border border-neutral-300 bg-white px-3 py-2.5 text-sm transition-colors duration-150 focus:border-neutral-900 focus:outline-none"
className="rounded-lg border border-[#D1D5DB] bg-white px-3 py-2.5 text-sm transition-colors focus:border-accent focus:ring-2 focus:ring-accent/20 focus:outline-none"
>
{propuestas.map((p, i) => (
<option key={p.tipo} value={i}>
@@ -498,7 +498,7 @@ export default function CotizadorPage() {
type="button"
onClick={handleCompartir}
disabled={!puedeGenerarPdf}
className="border border-accent bg-white px-5 py-2.5 text-xs font-bold tracking-widest text-accent uppercase transition-colors duration-150 hover:bg-accent hover:text-white disabled:cursor-not-allowed disabled:opacity-40"
className="rounded-lg border border-accent bg-white px-5 py-2.5 text-sm font-medium text-accent shadow-sm transition-all duration-100 ease-out hover:bg-accent hover:text-white disabled:cursor-not-allowed disabled:opacity-40"
>
Compartir
</button>
@@ -506,14 +506,14 @@ export default function CotizadorPage() {
type="button"
onClick={handleGenerarPdf}
disabled={!puedeGenerarPdf}
className="bg-accent px-5 py-2.5 text-xs font-bold tracking-widest text-white uppercase transition-colors duration-150 hover:bg-accent-hover disabled:cursor-not-allowed disabled:opacity-40"
className="rounded-lg bg-accent px-5 py-2.5 text-sm font-medium text-white shadow-sm transition-all duration-100 ease-out hover:bg-accent-hover disabled:cursor-not-allowed disabled:opacity-40"
>
{guardando ? 'Guardando…' : 'Guardar y generar PDF'}
</button>
</div>
{aviso && <p className="mt-2 text-right text-xs text-neutral-500">{aviso}</p>}
{aviso && <p className="mt-2 text-right text-xs text-[#64748B]">{aviso}</p>}
{!puedeGenerarPdf && !guardando && (
<p className="mt-2 text-right text-xs text-neutral-400">
<p className="mt-2 text-right text-xs text-[#64748B]">
Selecciona una unidad para guardar y generar el PDF.
</p>
)}