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

@@ -238,9 +238,9 @@ export default function AltaClientePage() {
if (pantalla === 'cargando') {
return (
<div className="min-h-screen bg-white">
<div className="min-h-screen bg-[#F8FAFC]">
<main className="mx-auto max-w-6xl px-4 py-16">
<p className="text-sm text-neutral-500">Cargando…</p>
<p className="text-sm text-[#64748B]">Cargando…</p>
</main>
</div>
)
@@ -248,11 +248,11 @@ export default function AltaClientePage() {
if (pantalla === 'entrada') {
return (
<div className="min-h-screen bg-white">
<header className="border-b border-neutral-200 px-4 py-6">
<div className="min-h-screen bg-[#F8FAFC]">
<header className="border-b border-[#E5E7EB] bg-white px-4 py-6">
<div className="mx-auto flex max-w-6xl items-baseline gap-3">
<span className="text-sm font-bold uppercase tracking-widest">FRASA</span>
<h1 className="text-2xl font-light tracking-wide">Alta de Clientes</h1>
<span className="text-sm font-bold tracking-widest text-[#111827]">FRASA<span className="text-[#F97316]">.</span></span>
<h1 className="text-2xl font-semibold text-[#111827]">Alta de Clientes</h1>
</div>
</header>
<main className="mx-auto max-w-6xl px-4 py-16">
@@ -260,20 +260,20 @@ export default function AltaClientePage() {
<button
type="button"
onClick={() => setPantalla('afluencia')}
className="border border-neutral-200 bg-white p-8 text-left transition-colors duration-150 hover:border-accent"
className="rounded-xl border border-[#E5E7EB] bg-white p-8 text-left shadow-sm transition-all duration-200 ease-in-out hover:border-accent hover:shadow-md"
>
<h2 className="text-lg font-light tracking-wide text-neutral-900">Alta Afluencia</h2>
<p className="mt-2 text-xs font-light text-neutral-500">
<h2 className="text-lg font-semibold text-[#111827]">Alta Afluencia</h2>
<p className="mt-2 text-sm text-[#64748B]">
Captura rápida del interesado; entra al embudo de ventas.
</p>
</button>
<button
type="button"
onClick={() => setPantalla('picker')}
className="border border-neutral-200 bg-white p-8 text-left transition-colors duration-150 hover:border-accent"
className="rounded-xl border border-[#E5E7EB] bg-white p-8 text-left shadow-sm transition-all duration-200 ease-in-out hover:border-accent hover:shadow-md"
>
<h2 className="text-lg font-light tracking-wide text-neutral-900">Alta de Cliente</h2>
<p className="mt-2 text-xs font-light text-neutral-500">
<h2 className="text-lg font-semibold text-[#111827]">Alta de Cliente</h2>
<p className="mt-2 text-sm text-[#64748B]">
Formato completo para quien aparta.
</p>
</button>
@@ -285,11 +285,11 @@ export default function AltaClientePage() {
if (pantalla === 'afluencia') {
return (
<div className="min-h-screen bg-white">
<header className="border-b border-neutral-200 px-4 py-6">
<div className="min-h-screen bg-[#F8FAFC]">
<header className="border-b border-[#E5E7EB] bg-white px-4 py-6">
<div className="mx-auto flex max-w-6xl items-baseline gap-3">
<span className="text-sm font-bold uppercase tracking-widest">FRASA</span>
<h1 className="text-2xl font-light tracking-wide">Alta Afluencia</h1>
<span className="text-sm font-bold tracking-widest text-[#111827]">FRASA<span className="text-[#F97316]">.</span></span>
<h1 className="text-2xl font-semibold text-[#111827]">Alta Afluencia</h1>
</div>
</header>
<main className="mx-auto max-w-6xl px-4 py-16">
@@ -304,26 +304,26 @@ export default function AltaClientePage() {
if (pantalla === 'picker') {
return (
<div className="min-h-screen bg-white">
<header className="border-b border-neutral-200 px-4 py-6">
<div className="min-h-screen bg-[#F8FAFC]">
<header className="border-b border-[#E5E7EB] bg-white px-4 py-6">
<div className="mx-auto flex max-w-6xl items-baseline gap-3">
<span className="text-sm font-bold uppercase tracking-widest">FRASA</span>
<h1 className="text-2xl font-light tracking-wide">Alta de Clientes</h1>
<span className="text-sm font-bold tracking-widest text-[#111827]">FRASA<span className="text-[#F97316]">.</span></span>
<h1 className="text-2xl font-semibold text-[#111827]">Alta de Clientes</h1>
</div>
</header>
<main className="mx-auto max-w-6xl px-4 py-16">
{borradores.length > 0 && (
<>
<h2 className="text-xs font-bold uppercase tracking-widest text-neutral-500">
<h2 className="text-xs font-semibold uppercase tracking-wider text-[#64748B]">
Tienes {borradores.length} borrador{borradores.length === 1 ? '' : 'es'} guardado
{borradores.length === 1 ? '' : 's'}
</h2>
<ul className="mt-6 divide-y divide-neutral-200 border border-neutral-200 bg-white">
<ul className="mt-6 divide-y divide-[#E5E7EB] rounded-xl border border-[#E5E7EB] bg-white shadow-sm">
{borradores.map((b) => (
<li key={b.id} className="flex flex-wrap items-center justify-between gap-4 px-4 py-3">
<div className="min-w-0">
<p className="truncate text-sm text-neutral-900">{b.nombre}</p>
<p className="mt-1 text-xs font-light text-neutral-500">
<p className="truncate text-sm text-[#111827]">{b.nombre}</p>
<p className="mt-1 text-xs text-[#64748B]">
Paso {b.paso + 1} de {PASOS.length} · {formatoFechaHora(b.updatedAt)}
</p>
</div>
@@ -331,14 +331,14 @@ export default function AltaClientePage() {
<button
type="button"
onClick={() => void continuarBorrador(b.id)}
className="border border-accent bg-white px-3 py-1.5 text-xs font-bold uppercase tracking-widest text-accent transition-colors duration-150 hover:bg-accent hover:text-white"
className="rounded-lg border border-accent bg-white px-3 py-1.5 text-xs font-medium text-accent transition-all duration-100 ease-out hover:bg-accent hover:text-white"
>
Continuar
</button>
<button
type="button"
onClick={() => void borrarBorrador(b.id)}
className="border border-red-300 bg-white px-3 py-1.5 text-xs font-bold uppercase tracking-widest text-red-700 transition-colors duration-150 hover:bg-red-50"
className="rounded-lg border border-red-200 bg-white px-3 py-1.5 text-xs font-medium text-red-600 transition-all duration-100 ease-out hover:bg-red-50"
>
Eliminar
</button>
@@ -352,16 +352,16 @@ export default function AltaClientePage() {
<button
type="button"
onClick={empezarNuevo}
className="mt-8 bg-accent px-5 py-2.5 text-xs font-bold uppercase tracking-widest text-white transition-colors duration-150 hover:bg-neutral-900"
className="mt-8 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"
>
Empezar nuevo
</button>
<section aria-labelledby="titulo-lead" className="mt-12 border-t border-neutral-200 pt-8">
<h3 id="titulo-lead" className="text-xs font-bold uppercase tracking-widest text-neutral-500">
<section aria-labelledby="titulo-lead" className="mt-12 border-t border-[#E5E7EB] pt-8">
<h3 id="titulo-lead" className="text-xs font-semibold uppercase tracking-wider text-[#64748B]">
Desde un lead de Monday
</h3>
<p className="mt-2 text-xs font-light text-neutral-500">
<p className="mt-2 text-sm text-[#64748B]">
Empieza un alta nueva con el nombre, email y celular del lead ya capturados.
</p>
<div className="mt-4">
@@ -374,26 +374,26 @@ export default function AltaClientePage() {
}
return (
<div className="min-h-screen bg-white">
<header className="border-b border-neutral-200 px-4 py-6">
<div className="min-h-screen bg-[#F8FAFC]">
<header className="border-b border-[#E5E7EB] bg-white px-4 py-6">
<div className="mx-auto flex max-w-6xl flex-wrap items-baseline gap-3">
<span className="text-sm font-bold uppercase tracking-widest">FRASA</span>
<h1 className="text-2xl font-light tracking-wide">Alta de Clientes</h1>
<span className="text-sm font-bold tracking-widest text-[#111827]">FRASA<span className="text-[#F97316]">.</span></span>
<h1 className="text-2xl font-semibold text-[#111827]">Alta de Clientes</h1>
<div className="ml-auto flex items-center gap-3">
{avisoBorrador && (
<span role="status" className="text-xs font-light text-neutral-500">
<span role="status" className="text-xs text-[#64748B]">
{avisoBorrador}
</span>
)}
{avisoProgreso && (
<span role="status" className="text-xs font-light text-amber-700">
<span role="status" className="text-xs text-amber-700">
{avisoProgreso}
</span>
)}
<button
type="button"
onClick={() => void guardar()}
className="border border-neutral-900 bg-transparent px-5 py-2.5 text-xs font-bold uppercase tracking-widest text-neutral-900 transition-colors duration-150 hover:bg-neutral-900 hover:text-white"
className="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]"
>
Guardar borrador
</button>
@@ -405,7 +405,7 @@ export default function AltaClientePage() {
<Stepper pasos={[...PASOS]} pasoActual={paso} onIrAPaso={irAPaso} />
{errorCarga && (
<p role="alert" className="mt-8 border border-amber-300 bg-amber-50 px-4 py-3 text-xs text-amber-700">
<p role="alert" className="mt-8 rounded-lg border border-amber-200 bg-amber-50 px-4 py-3 text-xs text-amber-700">
{errorCarga}
</p>
)}
@@ -481,7 +481,7 @@ export default function AltaClientePage() {
{cargoDeBorrador && (
<p
role="note"
className="mb-8 border border-amber-300 bg-amber-50 px-4 py-3 text-xs text-amber-700"
className="mb-8 rounded-lg border border-amber-200 bg-amber-50 px-4 py-3 text-xs text-amber-700"
>
Retomaste un borrador: los archivos de los documentos no se guardan en el borrador,
vuelve a adjuntarlos.
@@ -507,14 +507,14 @@ export default function AltaClientePage() {
{paso === 7 && (
<>
{registro && (
<p className="mb-8 border border-accent bg-white px-4 py-3 text-xs font-bold uppercase tracking-widest text-accent">
<p className="mb-8 rounded-lg border border-green-200 bg-green-50 px-4 py-3 text-sm font-medium text-green-700">
Alta registrada en el servidor: {registro}
</p>
)}
{sinConexion && (
<p
role="alert"
className="mb-8 border border-amber-300 bg-amber-50 px-4 py-3 text-xs font-bold uppercase tracking-widest text-amber-700"
className="mb-8 rounded-lg border border-amber-200 bg-amber-50 px-4 py-3 text-xs text-amber-700"
>
Sin conexión al servidor — el expediente se generó solo localmente
</p>
@@ -522,32 +522,32 @@ export default function AltaClientePage() {
{errorValidacion && (
<p
role="alert"
className="mb-8 border border-red-300 bg-red-50 px-4 py-3 text-xs font-bold uppercase tracking-widest text-red-700"
className="mb-8 rounded-lg border border-red-200 bg-red-50 px-4 py-3 text-xs text-red-700"
>
El servidor rechazó el alta: {errorValidacion}
</p>
)}
{registro && (
<div className="mb-8 border border-neutral-200 bg-white px-4 py-4">
<p className="text-xs font-bold uppercase tracking-widest text-neutral-500">
<div className="mb-8 rounded-xl border border-[#E5E7EB] bg-white px-4 py-4 shadow-sm">
<p className="text-xs font-semibold uppercase tracking-wider text-[#64748B]">
Firma en remoto
</p>
<p className="mt-2 text-xs font-light text-neutral-500">
<p className="mt-2 text-sm text-[#64748B]">
Genera una liga pública (vigencia 72 h) para que el cliente firme y suba sus
documentos desde su celular, sin usuario ni contraseña.
</p>
<button
type="button"
onClick={() => void generarLiga()}
className="mt-4 border border-neutral-900 bg-transparent px-5 py-2.5 text-xs font-bold uppercase tracking-widest text-neutral-900 transition-colors duration-150 hover:bg-neutral-900 hover:text-white"
className="mt-4 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]"
>
Generar liga para firmar en remoto
</button>
{ligaUrl && (
<p className="mt-4 break-all text-xs text-neutral-900">
<p className="mt-4 break-all text-xs text-[#111827]">
{ligaUrl}
{ligaCopiada && (
<span role="status" className="ml-2 font-bold uppercase tracking-widest text-accent">
<span role="status" className="ml-2 font-medium text-accent">
Liga y mensaje de WhatsApp copiados
</span>
)}

View File

@@ -82,10 +82,10 @@ export default function FirmaRemotaPage() {
if (errorCarga) {
return (
<div className="min-h-screen bg-white text-neutral-900">
<div className="min-h-screen bg-[#F8FAFC] text-[#111827]">
<main className="mx-auto max-w-xl px-4 py-24 text-center">
<span className="text-sm font-bold uppercase tracking-widest">FRASA</span>
<p role="alert" className="mt-8 border border-red-300 bg-red-50 px-4 py-3 text-sm text-red-700">
<span className="text-sm font-bold tracking-widest text-[#111827]">FRASA<span className="text-[#F97316]">.</span></span>
<p role="alert" className="mt-8 rounded-lg border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-700">
{errorCarga}
</p>
</main>
@@ -95,29 +95,29 @@ export default function FirmaRemotaPage() {
if (!cliente) {
return (
<div className="min-h-screen bg-white">
<div className="min-h-screen bg-[#F8FAFC]">
<main className="mx-auto max-w-xl px-4 py-24">
<p className="text-sm text-neutral-500">Cargando…</p>
<p className="text-sm text-[#64748B]">Cargando…</p>
</main>
</div>
)
}
return (
<div className="min-h-screen bg-white text-neutral-900">
<header className="border-b border-neutral-200 px-4 py-6">
<div className="min-h-screen bg-[#F8FAFC] text-[#111827]">
<header className="border-b border-[#E5E7EB] bg-white px-4 py-6">
<div className="mx-auto flex max-w-xl items-baseline gap-3">
<span className="text-sm font-bold uppercase tracking-widest">FRASA</span>
<h1 className="text-2xl font-light tracking-wide">Expediente digital</h1>
<span className="text-sm font-bold tracking-widest text-[#111827]">FRASA<span className="text-[#F97316]">.</span></span>
<h1 className="text-2xl font-semibold text-[#111827]">Expediente digital</h1>
</div>
</header>
<main className="mx-auto max-w-xl space-y-12 px-4 py-12">
<section>
<h2 className="text-lg font-light">
Hola, <span className="font-medium">{cliente.nombre}</span>
<h2 className="text-lg font-semibold text-[#111827]">
Hola, <span className="font-semibold">{cliente.nombre}</span>
</h2>
<p className="mt-2 text-sm font-light text-neutral-500">
<p className="mt-2 text-sm text-[#64748B]">
Proyecto {cliente.proyecto}
{cliente.unidad ? ` · Unidad ${cliente.unidad}` : ''}. Firma y sube los documentos de tu
expediente desde aquí.
@@ -125,11 +125,11 @@ export default function FirmaRemotaPage() {
</section>
<section aria-labelledby="firma-titulo">
<h3 id="firma-titulo" className="text-xs font-bold uppercase tracking-widest text-neutral-500">
<h3 id="firma-titulo" className="text-xs font-semibold uppercase tracking-wider text-[#64748B]">
Tu firma
</h3>
{firmaOk ? (
<p role="status" className="mt-4 border border-accent bg-white px-4 py-3 text-xs font-bold uppercase tracking-widest text-accent">
<p role="status" className="mt-4 rounded-lg border border-green-200 bg-green-50 px-4 py-3 text-sm font-medium text-green-700">
Firma registrada correctamente
</p>
) : (
@@ -140,7 +140,7 @@ export default function FirmaRemotaPage() {
type="button"
disabled={enviandoFirma}
onClick={() => void enviarFirma()}
className="mt-4 bg-accent px-5 py-2.5 text-xs font-bold uppercase tracking-widest text-white transition-colors duration-150 hover:bg-neutral-900 disabled:opacity-40"
className="mt-4 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:opacity-40"
>
{enviandoFirma ? 'Enviando…' : 'Enviar firma'}
</button>
@@ -149,22 +149,22 @@ export default function FirmaRemotaPage() {
</section>
<section aria-labelledby="docs-titulo">
<h3 id="docs-titulo" className="text-xs font-bold uppercase tracking-widest text-neutral-500">
<h3 id="docs-titulo" className="text-xs font-semibold uppercase tracking-wider text-[#64748B]">
Tus documentos
</h3>
<ul className="mt-4 space-y-4">
{SLOTS_DOCUMENTOS.map(({ slot, etiqueta }) => {
const subido = cliente.documentos.find((d) => d.tipo === slot)
return (
<li key={slot} className="border border-neutral-200 bg-white px-4 py-3">
<p className="text-xs font-bold uppercase tracking-widest text-neutral-500">{etiqueta}</p>
<li key={slot} className="rounded-xl border border-[#E5E7EB] bg-white px-4 py-3 shadow-sm">
<p className="text-xs font-semibold uppercase tracking-wider text-[#64748B]">{etiqueta}</p>
{subido ? (
<p role="status" className="mt-2 text-sm text-neutral-900">
<p role="status" className="mt-2 text-sm text-[#111827]">
✓ {subido.nombreArchivo}
</p>
) : (
<div className="mt-2">
<label className="inline-block cursor-pointer border border-neutral-900 bg-transparent px-5 py-2.5 text-xs font-bold uppercase tracking-widest text-neutral-900 transition-colors duration-150 hover:bg-neutral-900 hover:text-white">
<label className="inline-block cursor-pointer 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]">
{subiendoSlot === slot ? 'Subiendo…' : 'Seleccionar archivo'}
<input
type="file"
@@ -178,7 +178,7 @@ export default function FirmaRemotaPage() {
}}
/>
</label>
<p className="mt-2 text-xs font-light text-neutral-500">PDF/JPG/PNG · máx 10 MB</p>
<p className="mt-2 text-xs text-[#64748B]">PDF/JPG/PNG · máx 10 MB</p>
</div>
)}
{erroresDoc[slot] && (