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:
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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} />
|
||||
|
||||
@@ -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>
|
||||
))}
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
)
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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}
|
||||
>
|
||||
|
||||
Reference in New Issue
Block a user