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

@@ -22,9 +22,9 @@ interface Props {
const emailSchema = z.string().trim().email()
const inputClase =
'w-full border border-neutral-300 bg-white px-3 py-2.5 text-sm transition-colors duration-150 focus:border-neutral-900 focus:outline-none'
'w-full rounded-lg border border-[#D1D5DB] bg-white px-3 py-2.5 text-sm transition-colors focus:border-accent focus:outline-none focus:ring-2 focus:ring-accent/20'
const labelClase = 'mb-1 block text-xs font-bold uppercase tracking-widest text-neutral-500'
const labelClase = 'mb-1 block text-xs font-medium text-[#4B5563]'
interface Errores {
nombre?: string
@@ -141,7 +141,7 @@ export default function AltaAfluenciaForm({ onVolver, onPasarAAltaCliente }: Pro
<div className="max-w-md">
<p
role="status"
className="border border-accent bg-white px-4 py-3 text-xs font-bold uppercase tracking-widest text-accent"
className="rounded-lg border border-green-200 bg-green-50 px-4 py-3 text-sm font-medium text-green-700"
>
Registrado en afluencia — se asignará por round-robin
</p>
@@ -149,21 +149,21 @@ export default function AltaAfluenciaForm({ onVolver, onPasarAAltaCliente }: Pro
<button
type="button"
onClick={() => onPasarAAltaCliente(leadCreado)}
className="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="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"
>
Pasar a Alta de Cliente
</button>
<button
type="button"
onClick={capturarOtro}
className="border border-accent bg-white px-5 py-2.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-5 py-2.5 text-sm font-medium text-accent shadow-sm transition-all duration-100 ease-out hover:bg-accent hover:text-white"
>
Capturar otro
</button>
<button
type="button"
onClick={onVolver}
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]"
>
Volver
</button>
@@ -239,7 +239,7 @@ export default function AltaAfluenciaForm({ onVolver, onPasarAAltaCliente }: Pro
value={form.modelo}
onChange={cambiar('modelo')}
disabled={form.desarrollo === ''}
className={`${inputClase} disabled:bg-neutral-100 disabled:text-neutral-400`}
className={`${inputClase} disabled:bg-[#F1F5F9] disabled:text-[#94A3B8]`}
>
<option value="">
{form.desarrollo === '' ? 'Selecciona primero el desarrollo' : '— Selecciona —'}
@@ -424,7 +424,7 @@ export default function AltaAfluenciaForm({ onVolver, onPasarAAltaCliente }: Pro
</div>
{aviso && (
<p role="alert" className="mt-6 border border-red-300 bg-red-50 px-4 py-3 text-xs text-red-700">
<p role="alert" className="mt-6 rounded-lg border border-red-200 bg-red-50 px-4 py-3 text-xs text-red-700">
{aviso}
</p>
)}
@@ -433,14 +433,14 @@ export default function AltaAfluenciaForm({ onVolver, onPasarAAltaCliente }: Pro
<button
type="submit"
disabled={enviando}
className="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-50"
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:opacity-50"
>
{enviando ? 'Registrando…' : 'Registrar en afluencia'}
</button>
<button
type="button"
onClick={onVolver}
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]"
>
Volver
</button>

View File

@@ -17,17 +17,17 @@ export default function AuthGate({ children }: { children: ReactNode }) {
}
return (
<div className="min-h-screen bg-white text-neutral-900">
<div className="border-b border-neutral-200">
<div className="min-h-screen bg-[#F8FAFC] text-[#111827]">
<div className="border-b border-[#E5E7EB] bg-white">
<div className="mx-auto flex max-w-6xl items-center justify-between px-4 py-2">
<span className="text-xs font-bold uppercase tracking-widest text-neutral-500">
<span className="text-xs font-medium text-[#64748B]">
{sesion.usuario.nombre} · {sesion.usuario.rol}
</span>
<div className="flex items-center gap-4">
<button
type="button"
onClick={() => setModalPassword(true)}
className="text-xs font-bold uppercase tracking-widest text-neutral-500 transition-colors duration-150 hover:text-neutral-900"
className="rounded-lg px-2 py-1 text-xs font-medium text-[#64748B] transition-colors duration-100 hover:text-[#111827]"
>
Contraseña
</button>
@@ -37,7 +37,7 @@ export default function AuthGate({ children }: { children: ReactNode }) {
logout()
setSesion(null)
}}
className="text-xs font-bold uppercase tracking-widest text-accent transition-colors duration-150 hover:text-accent-hover"
className="rounded-lg px-2 py-1 text-xs font-medium text-accent transition-colors duration-100 hover:text-accent-hover"
>
Salir
</button>

View File

@@ -84,53 +84,53 @@ export default function BotonOcrIne({ archivo, onAplicar }: Props) {
}
return (
<div className="border border-neutral-200 bg-white px-4 py-3">
<div className="rounded-xl border border-[#E5E7EB] bg-white px-4 py-3 shadow-sm">
<div className="flex items-center justify-between gap-4">
<p className="text-xs font-bold uppercase tracking-widest text-neutral-500">Lectura automática (OCR)</p>
<p className="text-xs font-semibold uppercase tracking-wider text-[#64748B]">Lectura automática (OCR)</p>
<button
type="button"
onClick={() => void leer()}
disabled={estado.fase === 'procesando'}
className="shrink-0 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 disabled:opacity-40"
className="shrink-0 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] disabled:opacity-40"
>
{estado.fase === 'procesando' ? `Leyendo… ${estado.progreso}%` : 'Leer INE con OCR'}
</button>
</div>
{estado.fase === 'procesando' && (
<p className="mt-3 text-xs font-light text-neutral-500" role="status">
<p className="mt-3 text-xs text-[#64748B]" role="status">
Reconociendo texto de la credencial… {estado.progreso}%
</p>
)}
{estado.fase === 'error' && (
<p className="mt-3 border border-red-400 px-3 py-2 text-xs text-red-700" role="alert">
<p className="mt-3 rounded-lg border border-red-200 bg-red-50 px-3 py-2 text-xs text-red-700" role="alert">
{estado.mensaje}
</p>
)}
{estado.fase === 'listo' && (
<div className="mt-3">
<p className="text-xs font-light text-neutral-500">
<p className="text-xs text-[#64748B]">
Sugerencias detectadas — nada se sobreescribe sin tu confirmación:
</p>
<ul className="mt-2 space-y-2">
{aVisibles(estado.sugerencias).map((s) => (
<li key={s.clave} className="flex items-baseline justify-between gap-4 text-sm">
<span className="font-light text-neutral-900">
<span className="mr-2 text-xs font-bold uppercase tracking-widest text-neutral-500">
<span className="text-[#111827]">
<span className="mr-2 text-xs font-semibold uppercase tracking-wider text-[#64748B]">
{s.etiqueta}
</span>
{s.valor}
</span>
{s.aplicable &&
(aplicados.includes(s.clave) ? (
<span className="text-xs font-bold uppercase tracking-widest text-accent">✓</span>
<span className="rounded-full bg-green-50 px-2.5 py-0.5 text-xs font-medium text-green-700">✓</span>
) : (
<button
type="button"
onClick={() => aplicar(s)}
className="shrink-0 border border-neutral-900 bg-transparent px-3 py-1.5 text-xs font-bold uppercase tracking-widest text-neutral-900 transition-colors duration-150 hover:bg-neutral-900 hover:text-white"
className="shrink-0 rounded-lg border border-[#D1D5DB] bg-white px-3 py-1.5 text-xs font-medium text-[#374151] shadow-sm transition-all duration-100 ease-out hover:bg-[#F9FAFB]"
>
Aplicar
</button>

View File

@@ -45,7 +45,7 @@ export default function BusquedaLead({ onSeleccion }: Props) {
<div>
<label
htmlFor="busqueda-lead"
className="mb-1 block text-xs font-bold uppercase tracking-widest text-neutral-500"
className="mb-1 block text-xs font-medium text-[#4B5563]"
>
Buscar lead (nombre, teléfono o email)
</label>
@@ -55,30 +55,30 @@ export default function BusquedaLead({ onSeleccion }: Props) {
value={q}
onChange={(e) => setQ(e.target.value)}
placeholder="Escribe al menos 2 caracteres…"
className="w-full max-w-md border border-neutral-300 bg-white px-3 py-2 text-sm text-neutral-900 focus:border-neutral-900"
className="w-full max-w-md rounded-lg border border-[#D1D5DB] bg-white px-3 py-2.5 text-sm text-[#111827] transition-colors focus:border-accent focus:outline-none focus:ring-2 focus:ring-accent/20"
/>
{buscando && <p className="mt-3 text-xs font-light text-neutral-500">Buscando…</p>}
{buscando && <p className="mt-3 text-xs text-[#64748B]">Buscando…</p>}
{error && (
<p role="alert" className="mt-3 text-xs text-red-700">
{error}
</p>
)}
{!buscando && !error && resultados !== null && resultados.length === 0 && (
<p className="mt-3 text-xs font-light text-neutral-500">Sin leads que coincidan.</p>
<p className="mt-3 text-xs text-[#64748B]">Sin leads que coincidan.</p>
)}
{resultados !== null && resultados.length > 0 && (
<ul className="mt-3 max-w-md divide-y divide-neutral-200 border border-neutral-200 bg-white">
<ul className="mt-3 max-w-md divide-y divide-[#E5E7EB] rounded-xl border border-[#E5E7EB] bg-white shadow-sm">
{resultados.map((lead) => (
<li key={lead.id}>
<button
type="button"
onClick={() => onSeleccion(lead)}
className="flex w-full flex-wrap items-baseline justify-between gap-2 px-4 py-3 text-left transition-colors duration-150 hover:bg-neutral-50"
className="flex w-full flex-wrap items-baseline justify-between gap-2 px-4 py-3 text-left transition-colors duration-100 hover:bg-[#F8FAFC]"
>
<span className="text-sm text-neutral-900">{lead.nombre}</span>
<span className="text-xs font-light text-neutral-500">
<span className="text-sm text-[#111827]">{lead.nombre}</span>
<span className="text-xs text-[#64748B]">
{lead.telefono || 'sin teléfono'}
{lead.estado ? ` · ${lead.estado}` : ''}
</span>

View File

@@ -3,8 +3,8 @@ import { validarConfirmacion, validarPasswordNueva } from '../lib/password'
import { cambiarPassword } from '../services/auth'
const CLASE_INPUT =
'w-full border border-neutral-300 px-4 py-3 text-sm focus:border-accent focus:outline-none'
const CLASE_LABEL = 'mb-2 block text-xs font-bold uppercase tracking-widest text-neutral-500'
'w-full rounded-lg border border-[#D1D5DB] bg-white px-4 py-3 text-sm transition-colors focus:border-accent focus:outline-none focus:ring-2 focus:ring-accent/20'
const CLASE_LABEL = 'mb-2 block text-xs font-medium text-[#4B5563]'
/**
* Modal "Cambiar contraseña": actual + nueva + confirmar. Valida en cliente
@@ -45,19 +45,19 @@ export default function CambiarPasswordModal({ onCerrar }: { onCerrar: () => voi
aria-modal="true"
aria-label="Cambiar contraseña"
>
<div className="w-full max-w-sm border border-neutral-200 bg-white p-6">
<h2 className="mb-6 text-xs font-bold uppercase tracking-widest text-neutral-500">
<div className="w-full max-w-sm rounded-2xl border border-[#E5E7EB] bg-white p-6 shadow-xl">
<h2 className="mb-6 text-xs font-semibold uppercase tracking-wider text-[#64748B]">
Cambiar contraseña
</h2>
{exito ? (
<>
<p className="border border-accent px-4 py-3 text-xs font-bold uppercase tracking-widest text-accent">
<p className="rounded-lg border border-green-200 bg-green-50 px-4 py-3 text-sm font-medium text-green-700">
Contraseña actualizada
</p>
<button
type="button"
onClick={onCerrar}
className="mt-6 w-full bg-accent px-5 py-3 text-xs font-bold uppercase tracking-widest text-white transition-colors duration-150 hover:bg-accent-hover"
className="mt-6 w-full rounded-lg bg-accent px-5 py-3 text-sm font-medium text-white shadow-sm transition-all duration-100 ease-out hover:bg-accent-hover"
>
Cerrar
</button>
@@ -92,7 +92,7 @@ export default function CambiarPasswordModal({ onCerrar }: { onCerrar: () => voi
required
className={CLASE_INPUT}
/>
<p className="mt-2 text-xs text-neutral-400">Mínimo 8 caracteres y un número</p>
<p className="mt-2 text-xs text-[#64748B]">Mínimo 8 caracteres y un número</p>
</div>
<div>
<label htmlFor="password-confirmar" className={CLASE_LABEL}>
@@ -111,7 +111,7 @@ export default function CambiarPasswordModal({ onCerrar }: { onCerrar: () => voi
{error && (
<p
role="alert"
className="border border-red-700 px-4 py-3 text-xs font-bold uppercase tracking-widest text-red-700"
className="rounded-lg border border-red-200 bg-red-50 px-4 py-3 text-xs text-red-700"
>
{error}
</p>
@@ -120,14 +120,14 @@ export default function CambiarPasswordModal({ onCerrar }: { onCerrar: () => voi
<button
type="submit"
disabled={cargando}
className="flex-1 bg-accent px-5 py-3 text-xs font-bold uppercase tracking-widest text-white transition-colors duration-150 hover:bg-accent-hover disabled:opacity-50"
className="flex-1 rounded-lg bg-accent px-5 py-3 text-sm font-medium text-white shadow-sm transition-all duration-100 ease-out hover:bg-accent-hover disabled:opacity-50"
>
{cargando ? 'Guardando…' : 'Guardar'}
</button>
<button
type="button"
onClick={onCerrar}
className="border border-neutral-300 px-5 py-3 text-xs font-bold uppercase tracking-widest text-neutral-500 transition-colors duration-150 hover:border-neutral-500"
className="rounded-lg border border-[#D1D5DB] bg-white px-5 py-3 text-sm font-medium text-[#374151] shadow-sm transition-all duration-100 ease-out hover:bg-[#F9FAFB]"
>
Cancelar
</button>

View File

@@ -8,9 +8,9 @@ export interface CampoBaseProps {
}
const inputClase =
'w-full border border-neutral-300 bg-white px-3 py-2.5 text-sm uppercase transition-colors duration-150 focus:border-neutral-900 focus:outline-none'
'w-full rounded-lg border border-[#D1D5DB] bg-white px-3 py-2.5 text-sm uppercase transition-colors focus:border-accent focus:outline-none focus:ring-2 focus:ring-accent/20'
const labelClase = 'mb-1 block text-xs font-bold uppercase tracking-widest text-neutral-500'
const labelClase = 'mb-1 block text-xs font-medium text-[#4B5563]'
function Etiqueta({ etiqueta, requerido }: { etiqueta: string; requerido?: boolean }) {
return (
@@ -52,7 +52,7 @@ export function CampoTexto({ nombre, etiqueta, tipo = 'text', placeholder, reque
type={tipo}
placeholder={placeholder}
readOnly={soloLectura}
className={`${claseInput(nombre, errors)} ${soloLectura ? 'bg-neutral-50' : ''}`}
className={`${claseInput(nombre, errors)} ${soloLectura ? 'bg-[#F8FAFC]' : ''}`}
{...register(nombre)}
/>
<MensajeError nombre={nombre} />

View File

@@ -12,19 +12,19 @@ export default function ChecklistExpediente({ documentos, firmaDataUrl }: Props)
return (
<section
aria-label="Checklist del expediente"
className="border border-neutral-200 bg-white px-4 py-3"
className="rounded-xl border border-[#E5E7EB] bg-white px-4 py-3 shadow-sm"
>
<p className="text-xs font-bold uppercase tracking-widest text-neutral-500">
<p className="text-xs font-semibold uppercase tracking-wider text-[#64748B]">
Expediente: {estado.completos}/{estado.total}
</p>
<ul className="mt-3 space-y-2">
{estado.items.map((item) => (
<li key={item.clave} className="flex items-baseline justify-between gap-4 text-sm">
<span className="font-light text-neutral-900">{item.etiqueta}</span>
<span className="text-[#111827]">{item.etiqueta}</span>
{item.completo ? (
<span className="text-xs font-bold uppercase tracking-widest text-accent">✓</span>
<span className="rounded-full bg-green-50 px-2.5 py-0.5 text-xs font-medium text-green-700">✓</span>
) : (
<span className="text-xs uppercase tracking-widest text-neutral-400">Pendiente</span>
<span className="rounded-full bg-gray-100 px-2.5 py-0.5 text-xs font-medium text-gray-600">Pendiente</span>
)}
</li>
))}

View File

@@ -25,14 +25,14 @@ function Miniatura({ documento, url }: { documento: DocumentoCargado; url: strin
rel="noreferrer"
title={`Abrir ${documento.nombre} en una pestaña nueva`}
aria-label={`Abrir ${documento.nombre} en una pestaña nueva`}
className="block h-20 w-20 shrink-0 border border-neutral-200 bg-neutral-50"
className="block h-20 w-20 shrink-0 overflow-hidden rounded-lg border border-[#E5E7EB] bg-[#F8FAFC]"
>
{esImagen ? (
<img src={url} alt={`Vista previa de ${documento.nombre}`} className="h-full w-full object-cover" />
) : (
<span className="flex h-full w-full flex-col items-center justify-center gap-1">
<span className="text-xs font-bold uppercase tracking-widest text-neutral-900">PDF</span>
<span className="max-w-full truncate px-1 text-[10px] font-light text-neutral-500">{documento.nombre}</span>
<span className="text-xs font-semibold text-[#111827]">PDF</span>
<span className="max-w-full truncate px-1 text-[10px] text-[#64748B]">{documento.nombre}</span>
</span>
)}
</a>
@@ -76,12 +76,12 @@ export default function FileUploader({ slot, etiqueta, documento, onAgregar, onQ
if (documento) {
return (
<div className="flex items-center justify-between gap-4 border border-neutral-200 bg-white px-4 py-3">
<div className="flex items-center justify-between gap-4 rounded-xl border border-[#E5E7EB] bg-white px-4 py-3 shadow-sm">
<div className="flex min-w-0 items-center gap-4">
{urlObjeto && <Miniatura documento={documento} url={urlObjeto} />}
<div className="min-w-0">
<p className="text-xs font-bold uppercase tracking-widest text-neutral-500">{etiqueta}</p>
<p className="mt-1 truncate text-sm text-neutral-900">
<p className="text-xs font-semibold uppercase tracking-wider text-[#64748B]">{etiqueta}</p>
<p className="mt-1 truncate text-sm text-[#111827]">
{documento.nombre} · {formatoTamano(documento.tamano)}
</p>
</div>
@@ -89,7 +89,7 @@ export default function FileUploader({ slot, etiqueta, documento, onAgregar, onQ
<button
type="button"
onClick={() => onQuitar(slot)}
className="shrink-0 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="shrink-0 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"
>
Quitar
</button>
@@ -101,16 +101,16 @@ export default function FileUploader({ slot, etiqueta, documento, onAgregar, onQ
<div>
<div
{...getRootProps()}
className={`cursor-pointer border border-dashed border-neutral-300 bg-white px-4 py-6 text-center transition-colors duration-150 ${
isDragActive ? 'border-neutral-900 bg-neutral-50' : 'hover:border-neutral-500'
className={`cursor-pointer rounded-xl border border-dashed border-[#D1D5DB] bg-white px-4 py-6 text-center transition-colors duration-200 ${
isDragActive ? 'border-accent bg-accent/5' : 'hover:border-accent'
}`}
>
<input {...getInputProps()} aria-label={`Seleccionar archivo para ${etiqueta}`} />
<p className="text-xs font-bold uppercase tracking-widest text-neutral-500">{etiqueta}</p>
<p className="mt-2 text-xs font-light text-neutral-500">Arrastra el archivo aquí · PDF/JPG/PNG · máx 10 MB</p>
<p className="text-xs font-semibold uppercase tracking-wider text-[#64748B]">{etiqueta}</p>
<p className="mt-2 text-xs text-[#64748B]">Arrastra el archivo aquí · PDF/JPG/PNG · máx 10 MB</p>
<button
type="button"
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]"
>
Seleccionar archivo
</button>

View File

@@ -22,11 +22,11 @@ export default function LoginScreen({ onLogin }: { onLogin: (usuario: UsuarioSes
}
return (
<div className="flex min-h-screen items-center justify-center bg-white px-4">
<div className="w-full max-w-sm">
<div className="flex min-h-screen items-center justify-center bg-[#F8FAFC] px-4">
<div className="w-full max-w-sm rounded-xl border border-[#E5E7EB] bg-white p-8 shadow-sm">
<div className="mb-10 text-center">
<p className="text-lg font-bold tracking-widest">FRASA</p>
<p className="mt-2 text-xs font-bold uppercase tracking-widest text-neutral-500">
<p className="text-lg font-bold tracking-widest text-[#111827]">FRASA<span className="text-[#F97316]">.</span></p>
<p className="mt-2 text-xs font-medium uppercase tracking-wider text-[#64748B]">
Acceso al ecosistema
</p>
</div>
@@ -34,7 +34,7 @@ export default function LoginScreen({ onLogin }: { onLogin: (usuario: UsuarioSes
<div>
<label
htmlFor="login-usuario"
className="mb-2 block text-xs font-bold uppercase tracking-widest text-neutral-500"
className="mb-2 block text-xs font-medium text-[#4B5563]"
>
Usuario
</label>
@@ -46,13 +46,13 @@ export default function LoginScreen({ onLogin }: { onLogin: (usuario: UsuarioSes
autoComplete="username"
autoFocus
required
className="w-full border border-neutral-300 px-4 py-3 text-sm focus:border-accent focus:outline-none"
className="w-full rounded-lg border border-[#D1D5DB] bg-white px-4 py-3 text-sm transition-colors focus:border-accent focus:outline-none focus:ring-2 focus:ring-accent/20"
/>
</div>
<div>
<label
htmlFor="login-password"
className="mb-2 block text-xs font-bold uppercase tracking-widest text-neutral-500"
className="mb-2 block text-xs font-medium text-[#4B5563]"
>
Contraseña
</label>
@@ -63,13 +63,13 @@ export default function LoginScreen({ onLogin }: { onLogin: (usuario: UsuarioSes
onChange={(e) => setPassword(e.target.value)}
autoComplete="current-password"
required
className="w-full border border-neutral-300 px-4 py-3 text-sm focus:border-accent focus:outline-none"
className="w-full rounded-lg border border-[#D1D5DB] bg-white px-4 py-3 text-sm transition-colors focus:border-accent focus:outline-none focus:ring-2 focus:ring-accent/20"
/>
</div>
{error && (
<p
role="alert"
className="border border-red-700 px-4 py-3 text-xs font-bold uppercase tracking-widest text-red-700"
className="rounded-lg border border-red-200 bg-red-50 px-4 py-3 text-xs text-red-700"
>
{error}
</p>
@@ -77,7 +77,7 @@ export default function LoginScreen({ onLogin }: { onLogin: (usuario: UsuarioSes
<button
type="submit"
disabled={cargando}
className="w-full bg-accent px-5 py-3 text-xs font-bold uppercase tracking-widest text-white transition-colors duration-150 hover:bg-accent-hover disabled:opacity-50"
className="w-full rounded-lg bg-accent px-5 py-3 text-sm font-medium text-white shadow-sm transition-all duration-100 ease-out hover:bg-accent-hover disabled:opacity-50"
>
{cargando ? 'Entrando…' : 'Entrar'}
</button>

View File

@@ -9,9 +9,9 @@ interface Props {
/** Tarjeta de sección del wizard */
export default function SectionCard({ titulo, descripcion, children }: Props) {
return (
<section className="border border-neutral-200 bg-white p-6">
<h3 className="text-xs font-bold uppercase tracking-widest text-neutral-500">{titulo}</h3>
{descripcion && <p className="mt-2 text-sm font-light text-neutral-500">{descripcion}</p>}
<section className="rounded-xl border border-[#E5E7EB] bg-white p-6 shadow-sm">
<h3 className="text-xs font-semibold uppercase tracking-wider text-[#64748B]">{titulo}</h3>
{descripcion && <p className="mt-2 text-sm text-[#64748B]">{descripcion}</p>}
<div className="mt-8 grid grid-cols-1 gap-6 sm:grid-cols-2 lg:grid-cols-3">{children}</div>
</section>
)

View File

@@ -28,7 +28,7 @@ export default function SignaturePad({ onFirma }: Props) {
return (
<div>
<div className="border border-neutral-900 bg-white">
<div className="rounded-lg border border-[#D1D5DB] bg-white shadow-sm">
<SignatureCanvas
ref={ref}
onEnd={alTerminar}
@@ -39,7 +39,7 @@ export default function SignaturePad({ onFirma }: Props) {
<button
type="button"
onClick={limpiar}
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]"
>
Limpiar firma
</button>

View File

@@ -8,7 +8,7 @@ interface Props {
export default function Stepper({ pasos, pasoActual, onIrAPaso }: Props) {
return (
<nav aria-label="Progreso">
<ol className="flex flex-wrap items-baseline gap-x-6 gap-y-3 border-b border-neutral-200 pb-4">
<ol className="flex flex-wrap items-baseline gap-x-6 gap-y-3 border-b border-[#E5E7EB] pb-4">
{pasos.map((nombre, i) => {
const activo = i === pasoActual
const completado = i < pasoActual
@@ -19,12 +19,12 @@ export default function Stepper({ pasos, pasoActual, onIrAPaso }: Props) {
onClick={() => completado && onIrAPaso(i)}
disabled={!completado}
aria-current={activo ? 'step' : undefined}
className={`text-xs uppercase tracking-widest transition-colors duration-150 ${
className={`text-xs font-medium transition-colors duration-150 ${
activo
? 'font-bold text-accent'
? 'font-semibold text-accent'
: completado
? 'font-medium text-neutral-900 hover:text-accent'
: 'font-light text-neutral-400'
? 'text-[#111827] hover:text-accent'
: 'text-[#94A3B8]'
}`}
title={nombre}
>

View File

@@ -20,14 +20,14 @@ export default function CoacreditadoForm({ valores, onAnterior, onSiguiente }: P
return (
<FormProvider {...metodos}>
<form onSubmit={metodos.handleSubmit(onSiguiente)} noValidate>
<label htmlFor="tieneCoacreditado" className="flex items-center gap-3 border border-neutral-200 bg-white p-6">
<label htmlFor="tieneCoacreditado" className="flex items-center gap-3 rounded-xl border border-[#E5E7EB] bg-white p-6 shadow-sm">
<input
id="tieneCoacreditado"
type="checkbox"
className="h-5 w-5 accent-accent"
{...metodos.register('tieneCoacreditado')}
/>
<span className="text-xs font-bold uppercase tracking-widest text-neutral-900">¿Hay co-acreditado o cónyuge?</span>
<span className="text-sm font-medium text-[#111827]">¿Hay co-acreditado o cónyuge?</span>
</label>
{tieneCoacreditado && (

View File

@@ -29,7 +29,7 @@ export default function DatosGeneralesCampos({ prefix, compacto = false }: Props
<CampoTexto nombre={p('nivelEstudios')} etiqueta="Nivel de estudios" />
<CampoTexto nombre={p('curp')} etiqueta="CURP" requerido={!compacto} placeholder="XXXX000000XXXXXXX0" />
{advertenciasCurp.length > 0 && (
<div role="alert" className="border border-amber-300 bg-amber-50 px-4 py-3 sm:col-span-2">
<div role="alert" className="rounded-lg border border-amber-200 bg-amber-50 px-4 py-3 sm:col-span-2">
{advertenciasCurp.map((aviso) => (
<p key={aviso} className="text-xs text-amber-700">
{aviso}

View File

@@ -22,25 +22,25 @@ export default function EconomicosCampos({ prefix }: Props) {
<CampoNumero nombre={p('ingresosMensuales')} etiqueta="Ingresos mensuales" />
<CampoNumero nombre={p('otrosIngresos')} etiqueta="Otros ingresos" />
<label htmlFor={`${prefix}totalIngresos`} className="block">
<span className="mb-1 block text-xs font-bold uppercase tracking-widest text-neutral-500">Total ingresos</span>
<span className="mb-1 block text-xs font-medium text-[#4B5563]">Total ingresos</span>
<input
id={`${prefix}totalIngresos`}
type="text"
readOnly
value={formatoMoneda(ingresosMensuales + otrosIngresos)}
className="w-full border border-neutral-200 bg-neutral-50 px-3 py-2.5 text-sm font-bold text-neutral-900"
className="w-full rounded-lg border border-[#E5E7EB] bg-[#F8FAFC] px-3 py-2.5 text-sm font-semibold text-[#111827]"
/>
</label>
<CampoNumero nombre={p('gastosLuz')} etiqueta="Gastos luz" />
<CampoNumero nombre={p('gastosOtros')} etiqueta="Otros gastos" />
<label htmlFor={`${prefix}gastosTotal`} className="block">
<span className="mb-1 block text-xs font-bold uppercase tracking-widest text-neutral-500">Gastos total</span>
<span className="mb-1 block text-xs font-medium text-[#4B5563]">Gastos total</span>
<input
id={`${prefix}gastosTotal`}
type="text"
readOnly
value={formatoMoneda(gastosLuz + gastosOtros)}
className="w-full border border-neutral-200 bg-neutral-50 px-3 py-2.5 text-sm font-bold text-neutral-900"
className="w-full rounded-lg border border-[#E5E7EB] bg-[#F8FAFC] px-3 py-2.5 text-sm font-semibold text-[#111827]"
/>
</label>
<CampoTexto nombre={p('dependientes.personas')} etiqueta="Dependientes económicos (personas)" />

View File

@@ -66,42 +66,42 @@ export default function FirmasForm({ alta, onCambiarFirma, onAnterior }: Props)
<SectionCard titulo="Resumen del alta">
<dl className="grid grid-cols-2 gap-x-6 gap-y-4 text-sm sm:col-span-2 lg:col-span-3 lg:grid-cols-3">
<div>
<dt className="text-xs font-bold uppercase tracking-widest text-neutral-500">Cliente</dt>
<dd className="mt-1 font-light text-neutral-900">{g.nombreCompleto}</dd>
<dt className="text-xs font-semibold uppercase tracking-wider text-[#64748B]">Cliente</dt>
<dd className="mt-1 text-[#111827]">{g.nombreCompleto}</dd>
</div>
<div>
<dt className="text-xs font-bold uppercase tracking-widest text-neutral-500">CURP</dt>
<dd className="mt-1 font-light text-neutral-900">{g.curp}</dd>
<dt className="text-xs font-semibold uppercase tracking-wider text-[#64748B]">CURP</dt>
<dd className="mt-1 text-[#111827]">{g.curp}</dd>
</div>
<div>
<dt className="text-xs font-bold uppercase tracking-widest text-neutral-500">RFC</dt>
<dd className="mt-1 font-light text-neutral-900">{g.rfc}</dd>
<dt className="text-xs font-semibold uppercase tracking-wider text-[#64748B]">RFC</dt>
<dd className="mt-1 text-[#111827]">{g.rfc}</dd>
</div>
<div>
<dt className="text-xs font-bold uppercase tracking-widest text-neutral-500">Proyecto</dt>
<dd className="mt-1 font-light text-neutral-900">{proyecto || '—'}</dd>
<dt className="text-xs font-semibold uppercase tracking-wider text-[#64748B]">Proyecto</dt>
<dd className="mt-1 text-[#111827]">{proyecto || '—'}</dd>
</div>
<div>
<dt className="text-xs font-bold uppercase tracking-widest text-neutral-500">Ubicación / Modelo</dt>
<dd className="mt-1 font-light text-neutral-900">
<dt className="text-xs font-semibold uppercase tracking-wider text-[#64748B]">Ubicación / Modelo</dt>
<dd className="mt-1 text-[#111827]">
{v.ubicacion || '—'} {v.modelo}
</dd>
</div>
<div>
<dt className="text-xs font-bold uppercase tracking-widest text-neutral-500">Valor venta</dt>
<dd className="mt-1 font-light text-neutral-900">{formatoMoneda(Number(v.valorVenta) || 0)}</dd>
<dt className="text-xs font-semibold uppercase tracking-wider text-[#64748B]">Valor venta</dt>
<dd className="mt-1 text-[#111827]">{formatoMoneda(Number(v.valorVenta) || 0)}</dd>
</div>
<div>
<dt className="text-xs font-bold uppercase tracking-widest text-neutral-500">Total ingresos</dt>
<dd className="mt-1 font-light text-neutral-900">{formatoMoneda(calcularTotalIngresos(economicos))}</dd>
<dt className="text-xs font-semibold uppercase tracking-wider text-[#64748B]">Total ingresos</dt>
<dd className="mt-1 text-[#111827]">{formatoMoneda(calcularTotalIngresos(economicos))}</dd>
</div>
<div>
<dt className="text-xs font-bold uppercase tracking-widest text-neutral-500">Fecha</dt>
<dd className="mt-1 font-light text-neutral-900">{formatoFechaCorta(g.fecha)}</dd>
<dt className="text-xs font-semibold uppercase tracking-wider text-[#64748B]">Fecha</dt>
<dd className="mt-1 text-[#111827]">{formatoFechaCorta(g.fecha)}</dd>
</div>
<div>
<dt className="text-xs font-bold uppercase tracking-widest text-neutral-500">Documentos</dt>
<dd className="mt-1 font-light text-neutral-900">{alta.documentos.length} de 5</dd>
<dt className="text-xs font-semibold uppercase tracking-wider text-[#64748B]">Documentos</dt>
<dd className="mt-1 text-[#111827]">{alta.documentos.length} de 5</dd>
</div>
</dl>
</SectionCard>
@@ -113,7 +113,7 @@ export default function FirmasForm({ alta, onCambiarFirma, onAnterior }: Props)
<SignaturePad onFirma={(dataUrl) => onCambiarFirma({ ...firma, firmaDataUrl: dataUrl })} />
</div>
<label htmlFor="asesorNombre" className="block">
<span className="mb-1 block text-xs font-bold uppercase tracking-widest text-neutral-500">
<span className="mb-1 block text-xs font-semibold uppercase tracking-wider text-[#64748B]">
Nombre del asesor <span className="text-red-700">*</span>
</span>
<input
@@ -121,7 +121,7 @@ export default function FirmasForm({ alta, onCambiarFirma, onAnterior }: Props)
type="text"
value={firma.asesorNombre}
onChange={(e) => onCambiarFirma({ ...firma, asesorNombre: e.target.value.toUpperCase() })}
className="w-full border border-neutral-300 bg-white px-3 py-2.5 text-sm uppercase transition-colors duration-150 focus:border-neutral-900 focus:outline-none"
className="w-full rounded-lg border border-[#D1D5DB] bg-white px-3 py-2.5 text-sm uppercase transition-colors focus:border-accent focus:outline-none focus:ring-2 focus:ring-accent/20"
/>
</label>
</SectionCard>
@@ -132,7 +132,7 @@ export default function FirmasForm({ alta, onCambiarFirma, onAnterior }: Props)
{avisoExpediente && (
<p
role="alert"
className="border border-amber-300 bg-amber-50 px-4 py-3 text-xs text-amber-700 sm:col-span-2 lg:col-span-3"
className="rounded-lg border border-amber-200 bg-amber-50 px-4 py-3 text-xs text-amber-700 sm:col-span-2 lg:col-span-3"
>
{avisoExpediente}
</p>
@@ -143,7 +143,7 @@ export default function FirmasForm({ alta, onCambiarFirma, onAnterior }: Props)
key={g3.tipo}
type="button"
onClick={() => generar(g3.descargar)}
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]"
>
{g3.tipo}
</button>
@@ -151,7 +151,7 @@ export default function FirmasForm({ alta, onCambiarFirma, onAnterior }: Props)
<button
type="button"
onClick={generarTodos}
className="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="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"
>
Generar todos
</button>
@@ -162,7 +162,7 @@ export default function FirmasForm({ alta, onCambiarFirma, onAnterior }: Props)
<button
type="button"
onClick={onAnterior}
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]"
>
Anterior
</button>

View File

@@ -13,7 +13,7 @@ export default function Navegacion({ onAnterior, onSiguiente, textoSiguiente = '
<button
type="button"
onClick={onAnterior}
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]"
>
Anterior
</button>
@@ -23,7 +23,7 @@ export default function Navegacion({ onAnterior, onSiguiente, textoSiguiente = '
<button
type={onSiguiente ? 'button' : 'submit'}
onClick={onSiguiente}
className="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="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"
>
{textoSiguiente}
</button>

View File

@@ -31,8 +31,8 @@ export default function ReferenciasForm({ valores, onAnterior, onSiguiente }: Pa
descripcion="Captura al menos 1 referencia con nombre y teléfono."
>
{[0, 1, 2, 3].map((i) => (
<div key={i} className="border border-neutral-200 p-6">
<p className="mb-4 text-xs font-bold uppercase tracking-widest text-neutral-500">Referencia {i + 1}</p>
<div key={i} className="rounded-xl border border-[#E5E7EB] p-6">
<p className="mb-4 text-xs font-semibold uppercase tracking-wider text-[#64748B]">Referencia {i + 1}</p>
<div className="space-y-3">
<CampoTexto nombre={`referencias.${i}.nombre`} etiqueta="Nombre completo" />
<CampoTexto nombre={`referencias.${i}.telefono`} etiqueta="Teléfono" />

View File

@@ -80,10 +80,10 @@ export default function ViviendaCreditoForm({ valores, onAnterior, onSiguiente }
<label
key={tc.valor}
htmlFor={`tipoCredito-${tc.valor}`}
className={`cursor-pointer border px-3 py-1.5 text-xs font-bold uppercase tracking-wider transition-colors duration-150 ${
className={`cursor-pointer rounded-full border px-3 py-1.5 text-xs font-medium transition-all duration-100 ease-out ${
tiposSeleccionados.includes(tc.valor)
? 'border-accent bg-accent text-white'
: 'border-neutral-300 bg-white text-neutral-600 hover:border-neutral-900'
? 'border-accent bg-accent text-white shadow-sm'
: 'border-[#D1D5DB] bg-white text-[#4B5563] hover:border-accent hover:text-accent'
}`}
>
<input

View File

@@ -2,22 +2,17 @@
@theme {
--font-sans: "Inter", ui-sans-serif, system-ui, sans-serif;
--color-accent: #1B4332;
--radius-sm: 0;
--radius-md: 0;
--radius-lg: 0;
--radius-xl: 0;
--radius-2xl: 0;
--radius-3xl: 0;
--color-accent: #2563EB;
--color-accent-hover: #1D4ED8;
}
@layer base {
body {
@apply bg-white text-neutral-900 antialiased;
@apply bg-[#F8FAFC] text-[#111827] antialiased;
}
:focus-visible {
outline: 2px solid #1b4332;
outline-offset: 2px;
outline: none;
box-shadow: 0 0 0 3px rgba(37, 99, 235, 0.35);
}
}

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] && (