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}
>