style: rediseno de layout profesional en las 7 apps

Headers sticky con marca y acciones, contenedores max-w-[1280px],
grids de contenido con rails laterales sticky (cotizador, mi-dia,
detalle de cobranza), stepper vertical en el wizard de alta, toolbar+
tabla unificadas en cobranza, tabs como segmented control en
dashboards, chat con barra de entrada fija y preguntas sugeridas,
landing con navbar + hero + grid de modulos + footer.
This commit is contained in:
2026-09-03 04:28:56 +00:00
parent 599dce2629
commit d59c8c0d7e
39 changed files with 1622 additions and 1235 deletions

View File

@@ -133,40 +133,24 @@ export default function PlanDetallePage() {
}
}
const avance = avancePlan(plan)
return (
<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-[#111827]">{plan.clienteNombre}</h1>
<p className="mt-1 text-sm text-[#64748B]">
{plan.unidadCodigo} · {plan.proyecto} · Venta {plan.ventaRef || '—'} ·{' '}
<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-[#64748B]">
Tel. {plan.clienteTelefono || '—'}
{tasaMora > 0 && <span> · Mora {tasaMora}% mensual</span>}
</p>
</div>
<div className="flex gap-3">
<button
onClick={() => descargar(plan)}
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="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>
)}
</div>
<div className="space-y-6">
<div>
<Link to="/" className="text-sm text-accent hover:underline">
← Volver al dashboard
</Link>
<h1 className="mt-2 text-xl font-semibold text-[#111827]">{plan.clienteNombre}</h1>
<p className="mt-1 text-sm text-[#64748B]">
{plan.unidadCodigo} · {plan.proyecto} · Venta {plan.ventaRef || '—'} ·{' '}
<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-[#64748B]">
Tel. {plan.clienteTelefono || '—'}
{tasaMora > 0 && <span> · Mora {tasaMora}% mensual</span>}
</p>
</div>
{offline && (
@@ -179,21 +163,52 @@ export default function PlanDetallePage() {
<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 */}
<div className="grid grid-cols-2 gap-6 lg:grid-cols-5">
<KpiCard titulo="Enganche total" valor={formatoMoneda(plan.engancheTotal)} />
<KpiCard titulo="Pagado" valor={formatoMoneda(pagadoTotal(plan))} />
<KpiCard titulo="Saldo" valor={formatoMoneda(saldo)} />
<KpiCard titulo="% avance" valor={formatoPorcentaje(avancePlan(plan))} />
<KpiCard titulo="Mensualidad" valor={formatoMoneda(plan.mensualidad)} detalle={`${plan.numPagos} pagos`} />
{mora > 0 && (
<KpiCard
titulo="Mora acumulada"
valor={<span className="text-red-700">{formatoMoneda(mora)}</span>}
detalle={`Tasa ${tasaMora}% mensual`}
/>
)}
</div>
{/* En lg: columna principal (flex-1) + rail derecho sticky de ~340px */}
<div className="space-y-6 lg:flex lg:items-start lg:gap-6 lg:space-y-0">
{/* Columna principal */}
<div className="min-w-0 flex-1 space-y-6">
{/* Resumen del plan */}
<section
className="rounded-xl border border-[#E5E7EB] bg-white p-5 shadow-sm sm:p-6"
aria-labelledby="titulo-resumen"
>
<h2 id="titulo-resumen" className="text-xs font-semibold uppercase tracking-wider text-[#64748B]">
Resumen del plan
</h2>
<div className="mt-5 grid grid-cols-2 gap-4 sm:grid-cols-3">
<KpiCard plano titulo="Enganche total" valor={formatoMoneda(plan.engancheTotal)} />
<KpiCard plano titulo="Pagado" valor={formatoMoneda(pagadoTotal(plan))} />
<KpiCard plano titulo="Saldo" valor={formatoMoneda(saldo)} />
<KpiCard plano titulo="% avance" valor={formatoPorcentaje(avance)} />
<KpiCard
plano
titulo="Mensualidad"
valor={formatoMoneda(plan.mensualidad)}
detalle={`${plan.numPagos} pagos`}
/>
{mora > 0 && (
<KpiCard
plano
titulo="Mora acumulada"
valor={<span className="text-red-700">{formatoMoneda(mora)}</span>}
detalle={`Tasa ${tasaMora}% mensual`}
/>
)}
</div>
{/* Barra de avance */}
<div className="mt-6">
<div className="flex items-center justify-between text-xs">
<span className="font-medium text-[#64748B]">Avance del plan</span>
<span className="font-semibold text-[#111827]">{formatoPorcentaje(avance)}</span>
</div>
<div className="mt-2 h-2 rounded-full bg-[#E5E7EB]">
<div
className="h-full rounded-full bg-accent"
style={{ width: `${Math.min(100, avance * 100)}%` }}
/>
</div>
</div>
</section>
{/* Tabla de pagos */}
<div className="overflow-x-auto rounded-xl border border-[#E5E7EB] bg-white shadow-sm">
@@ -299,10 +314,38 @@ export default function PlanDetallePage() {
})}
</tbody>
</table>
</div>
</div>
</div>
{/* Bitácora del plan */}
<section className="rounded-xl border border-[#E5E7EB] bg-white p-8 shadow-sm" aria-labelledby="titulo-bitacora">
{/* Rail derecho: acciones y bitácora */}
<aside className="space-y-6 lg:sticky lg:top-20 lg:w-[340px] lg:shrink-0">
<section
className="rounded-xl border border-[#E5E7EB] bg-white p-5 shadow-sm"
aria-labelledby="titulo-acciones"
>
<h2 id="titulo-acciones" className="text-xs font-semibold uppercase tracking-wider text-[#64748B]">
Acciones
</h2>
<div className="mt-4 space-y-3">
<button
onClick={() => descargar(plan)}
className="w-full 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="w-full 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>
)}
</div>
</section>
{/* Bitácora del plan */}
<section className="rounded-xl border border-[#E5E7EB] bg-white p-5 shadow-sm" aria-labelledby="titulo-bitacora">
<h2 id="titulo-bitacora" className="text-xs font-semibold uppercase tracking-wider text-[#64748B]">
Bitácora
</h2>
@@ -329,7 +372,7 @@ export default function PlanDetallePage() {
</ul>
)}
<form
className="mt-8"
className="mt-6"
onSubmit={(e) => {
e.preventDefault()
void guardarNota()
@@ -360,7 +403,9 @@ export default function PlanDetallePage() {
</button>
</div>
</form>
</section>
</section>
</aside>
</div>
{status === 'CANCELADO' && (
<button