style: reskin NovaSpark en las 7 apps

Del look editorial (verde #1B4332, esquinas a 0, botones en mayusculas)
al sistema NovaSpark: primario azul #2563EB, detalle naranja #F97316,
fondo #F8FAFC con tarjetas blancas rounded-xl + shadow-sm, chips
rounded-full semanticos, focus ring azul, transiciones 100-200ms.
Incluye headers de tabla en los PDFs (cotizador y estado de cuenta).
This commit is contained in:
2026-09-03 02:35:34 +00:00
parent 89fcd1c579
commit 599dce2629
78 changed files with 842 additions and 828 deletions

View File

@@ -63,17 +63,17 @@ export default function PlanDetallePage() {
}, [id])
if (cargando) {
return <p className="text-sm text-neutral-400">Cargando...</p>
return <p className="text-sm text-[#64748B]">Cargando...</p>
}
if (errorCarga) {
return <div className="border border-red-300 bg-white px-4 py-3 text-sm text-red-700">{errorCarga}</div>
return <div className="rounded-lg border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-700">{errorCarga}</div>
}
if (!plan) {
return (
<div className="border border-neutral-200 bg-white p-16 text-center">
<p className="text-neutral-500">Plan no encontrado.</p>
<div className="rounded-xl border border-[#E5E7EB] bg-white p-16 text-center shadow-sm">
<p className="text-[#64748B]">Plan no encontrado.</p>
<Link to="/" className="mt-4 inline-block text-accent hover:underline">
Volver al dashboard
</Link>
@@ -134,19 +134,19 @@ export default function PlanDetallePage() {
}
return (
<div className="space-y-16">
<div className="space-y-8">
<div className="flex flex-wrap items-start justify-between gap-6">
<div>
<Link to="/" className="text-sm text-accent hover:underline">
← Volver al dashboard
</Link>
<h1 className="mt-2 text-3xl font-light text-neutral-900">{plan.clienteNombre}</h1>
<p className="mt-1 text-sm text-neutral-500">
<h1 className="mt-2 text-3xl font-light text-[#111827]">{plan.clienteNombre}</h1>
<p className="mt-1 text-sm text-[#64748B]">
{plan.unidadCodigo} · {plan.proyecto} · Venta {plan.ventaRef || '—'} ·{' '}
<span className="font-bold text-neutral-900">{status}</span>
<span className="font-semibold text-[#111827]">{status}</span>
{atraso > 0 && <span className="text-red-700"> · {atraso} días de atraso</span>}
</p>
<p className="mt-1 text-sm text-neutral-500">
<p className="mt-1 text-sm text-[#64748B]">
Tel. {plan.clienteTelefono || '—'}
{tasaMora > 0 && <span> · Mora {tasaMora}% mensual</span>}
</p>
@@ -154,14 +154,14 @@ export default function PlanDetallePage() {
<div className="flex gap-3">
<button
onClick={() => descargar(plan)}
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] transition-all duration-100 ease-out hover:bg-[#F9FAFB]"
>
Estado de cuenta PDF
</button>
{status === 'ACTIVO' && saldo > 0 && (
<button
onClick={cancelar}
className="border border-red-300 bg-white px-5 py-2.5 text-xs font-bold uppercase tracking-widest text-red-700 transition-colors duration-150 hover:bg-red-700 hover:text-white"
className="rounded-lg border border-red-600 bg-white px-5 py-2.5 text-sm font-medium text-red-600 transition-all duration-100 ease-out hover:bg-red-600 hover:text-white"
>
Cancelar plan
</button>
@@ -176,7 +176,7 @@ export default function PlanDetallePage() {
)}
{error && (
<div className="border border-red-300 bg-white px-4 py-3 text-sm text-red-700">{error}</div>
<div className="rounded-lg border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-700">{error}</div>
)}
{/* KPIs del plan */}
@@ -196,22 +196,22 @@ export default function PlanDetallePage() {
</div>
{/* Tabla de pagos */}
<div className="overflow-x-auto">
<div className="overflow-x-auto rounded-xl border border-[#E5E7EB] bg-white shadow-sm">
<table className="w-full text-left text-sm">
<thead>
<tr className="border-b border-neutral-900 text-xs font-bold uppercase tracking-widest text-neutral-500">
<th scope="col" className="py-3 pr-4">#</th>
<th scope="col" className="py-3 pr-4">Vencimiento</th>
<th scope="col" className="py-3 pr-4 text-right">Monto</th>
<th scope="col" className="py-3 pr-4 text-right">Pagado</th>
<th scope="col" className="py-3 pr-4 text-right">Restante</th>
{tasaMora > 0 && <th scope="col" className="py-3 pr-4 text-right">Mora</th>}
<th scope="col" className="py-3 pr-4">Status</th>
<th scope="col" className="py-3 pr-4">Abonos</th>
<th scope="col" className="py-3" />
<tr className="border-b border-[#E5E7EB] bg-[#F8FAFC] text-xs font-semibold uppercase tracking-wider text-[#64748B]">
<th scope="col" className="px-4 py-3">#</th>
<th scope="col" className="px-4 py-3">Vencimiento</th>
<th scope="col" className="px-4 py-3 text-right">Monto</th>
<th scope="col" className="px-4 py-3 text-right">Pagado</th>
<th scope="col" className="px-4 py-3 text-right">Restante</th>
{tasaMora > 0 && <th scope="col" className="px-4 py-3 text-right">Mora</th>}
<th scope="col" className="px-4 py-3">Status</th>
<th scope="col" className="px-4 py-3">Abonos</th>
<th scope="col" className="px-4 py-3" />
</tr>
</thead>
<tbody>
<tbody className="divide-y divide-[#E5E7EB]">
{plan.pagos.map((pago) => {
const st = statusPago(pago, hoy)
const restante = round2(pago.monto - pago.montoPagado)
@@ -219,14 +219,14 @@ export default function PlanDetallePage() {
const expandido = expandidos.has(pago.id)
return (
<Fragment key={pago.id}>
<tr className="border-b border-neutral-200 transition-colors duration-150 hover:bg-neutral-50">
<td className="py-3 pr-4 font-medium">{pago.numPago}</td>
<td className="py-3 pr-4">{formatoFechaCorta(pago.fechaVencimiento)}</td>
<td className="py-3 pr-4 text-right">{formatoMoneda(pago.monto)}</td>
<td className="py-3 pr-4 text-right">{formatoMoneda(pago.montoPagado)}</td>
<td className="py-3 pr-4 text-right font-medium">{formatoMoneda(restante)}</td>
<tr className="transition-colors duration-100 ease-out hover:bg-[#F8FAFC]">
<td className="px-4 py-3 font-medium">{pago.numPago}</td>
<td className="px-4 py-3">{formatoFechaCorta(pago.fechaVencimiento)}</td>
<td className="px-4 py-3 text-right">{formatoMoneda(pago.monto)}</td>
<td className="px-4 py-3 text-right">{formatoMoneda(pago.montoPagado)}</td>
<td className="px-4 py-3 text-right font-medium">{formatoMoneda(restante)}</td>
{tasaMora > 0 && (
<td className="py-3 pr-4 text-right">
<td className="px-4 py-3 text-right">
{moraPago > 0 ? (
<span className="text-red-700">{formatoMoneda(moraPago)}</span>
) : (
@@ -234,10 +234,10 @@ export default function PlanDetallePage() {
)}
</td>
)}
<td className="py-3 pr-4">
<td className="px-4 py-3">
<BadgeStatus status={st} />
</td>
<td className="py-3 pr-4">
<td className="px-4 py-3">
{pago.aplicaciones.length > 0 ? (
<button
onClick={() => toggleExpandido(pago.id)}
@@ -249,11 +249,11 @@ export default function PlanDetallePage() {
<span className="text-neutral-400">—</span>
)}
</td>
<td className="py-3 text-right">
<td className="px-4 py-3 text-right">
{st !== 'PAGADO' && status === 'ACTIVO' && (
<button
onClick={() => setPagoAbono(pago)}
className="bg-accent px-4 py-2 text-xs font-bold uppercase tracking-widest text-white transition-colors duration-150 hover:opacity-90"
className="rounded-lg bg-accent px-4 py-2 text-xs font-medium text-white shadow-sm transition-all duration-100 ease-out hover:bg-accent-hover"
>
Registrar abono
</button>
@@ -261,11 +261,11 @@ export default function PlanDetallePage() {
</td>
</tr>
{expandido && (
<tr className="border-b border-neutral-200 bg-neutral-50">
<tr className="bg-[#F8FAFC]">
<td colSpan={tasaMora > 0 ? 9 : 8} className="px-4 py-4">
<table className="w-full text-xs">
<thead>
<tr className="text-left font-bold uppercase tracking-widest text-neutral-500">
<tr className="text-left font-semibold uppercase tracking-wider text-[#64748B]">
<th scope="col" className="py-1">Fecha</th>
<th scope="col" className="py-1 text-right">Monto</th>
<th scope="col" className="py-1">Referencia</th>
@@ -274,7 +274,7 @@ export default function PlanDetallePage() {
</thead>
<tbody>
{pago.aplicaciones.map((a) => (
<tr key={a.id} className="border-t border-neutral-200">
<tr key={a.id} className="border-t border-[#E5E7EB]">
<td className="py-1">{formatoFechaCorta(a.fechaPago)}</td>
<td className="py-1 text-right">{formatoMoneda(a.monto)}</td>
<td className="py-1">{a.referencia}</td>
@@ -302,19 +302,19 @@ export default function PlanDetallePage() {
</div>
{/* Bitácora del plan */}
<section className="border border-neutral-200 bg-white p-8" aria-labelledby="titulo-bitacora">
<h2 id="titulo-bitacora" className="text-xs font-bold uppercase tracking-widest text-neutral-500">
<section className="rounded-xl border border-[#E5E7EB] bg-white p-8 shadow-sm" aria-labelledby="titulo-bitacora">
<h2 id="titulo-bitacora" className="text-xs font-semibold uppercase tracking-wider text-[#64748B]">
Bitácora
</h2>
{(plan.notas ?? []).length === 0 ? (
<p className="mt-6 text-sm text-neutral-500">Sin notas registradas.</p>
<p className="mt-6 text-sm text-[#64748B]">Sin notas registradas.</p>
) : (
<ul className="mt-6 space-y-4">
{[...(plan.notas ?? [])]
.sort((a, b) => b.fecha.localeCompare(a.fecha))
.map((nota) => (
<li key={nota.id} className="border-l-2 border-neutral-200 pl-4">
<p className="text-xs text-neutral-500">
<li key={nota.id} className="border-l-2 border-[#E5E7EB] pl-4">
<p className="text-xs text-[#64748B]">
{new Date(nota.fecha).toLocaleString('es-MX', {
day: '2-digit',
month: '2-digit',
@@ -323,7 +323,7 @@ export default function PlanDetallePage() {
minute: '2-digit',
})}
</p>
<p className="mt-1 text-sm text-neutral-900">{nota.texto}</p>
<p className="mt-1 text-sm text-[#111827]">{nota.texto}</p>
</li>
))}
</ul>
@@ -337,7 +337,7 @@ export default function PlanDetallePage() {
>
<label
htmlFor="nota-texto"
className="block text-xs font-bold uppercase tracking-widest text-neutral-500"
className="block text-xs font-medium text-[#4B5563]"
>
Nueva nota
</label>
@@ -347,14 +347,14 @@ export default function PlanDetallePage() {
onChange={(e) => setNotaTexto(e.target.value)}
rows={3}
placeholder="Gestión realizada, acuerdos con el cliente…"
className="mt-1 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"
className="mt-1 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"
/>
{errorNota && <p className="mt-1 text-xs text-red-700">{errorNota}</p>}
<div className="mt-4 flex justify-end">
<button
type="submit"
disabled={notaTexto.trim().length < 3}
className="bg-accent px-5 py-2.5 text-xs font-bold uppercase tracking-widest text-white transition-colors duration-150 hover:opacity-90 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"
>
Agregar nota
</button>
@@ -365,7 +365,7 @@ export default function PlanDetallePage() {
{status === 'CANCELADO' && (
<button
onClick={() => void navigate('/')}
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] transition-all duration-100 ease-out hover:bg-[#F9FAFB]"
>
Volver
</button>