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}
|
||||
>
|
||||
|
||||
@@ -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 && (
|
||||
|
||||
@@ -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}
|
||||
|
||||
@@ -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)" />
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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>
|
||||
|
||||
@@ -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" />
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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>
|
||||
)}
|
||||
|
||||
@@ -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] && (
|
||||
|
||||
Reference in New Issue
Block a user