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

@@ -1,6 +1,7 @@
import { useState } from 'react'
import MiTablero from '../components/MiTablero'
import TableroGeneral from '../components/TableroGeneral'
import type { AccionesTablero } from '../components/comunes'
import { esRolConEquipo } from '../lib/mi-tablero'
import { getSesion } from '../services/auth'
@@ -8,42 +9,70 @@ type Vista = 'mia' | 'general'
const VISTAS: { id: Vista; etiqueta: string }[] = [
{ id: 'mia', etiqueta: 'Mi tablero' },
{ id: 'general', etiqueta: 'General' },
{ id: 'general', etiqueta: 'Tablero general' },
]
/**
* Tableros con vista por rol: el vendedor entra a "Mi tablero" (sus KPIs y el
* funnel de sus leads); coordinador/corporativo/admin entran a "General" con
* la sección Equipo. El toggle permite cambiar de vista en cualquier momento.
* la sección Equipo. El header sticky concentra la marca, el segmented control
* de vistas y el botón "Actualizar" de la vista activa.
*/
export default function DashboardPage() {
const rol = getSesion()?.usuario.rol ?? 'vendedor'
const conEquipo = esRolConEquipo(rol)
const [vista, setVista] = useState<Vista>(conEquipo ? 'general' : 'mia')
const [acciones, setAcciones] = useState<AccionesTablero | null>(null)
return (
<div>
<div className="border-b border-[#E5E7EB] bg-white">
<div className="mx-auto flex max-w-6xl gap-2 px-4 py-3" role="tablist" aria-label="Vista del tablero">
{VISTAS.map((v) => (
<button
key={v.id}
type="button"
role="tab"
aria-selected={vista === v.id}
onClick={() => setVista(v.id)}
className={
vista === v.id
? 'rounded-lg bg-accent px-5 py-2 text-xs font-medium text-white shadow-sm transition-all duration-100 ease-out hover:bg-accent-hover'
: 'rounded-lg border border-[#D1D5DB] bg-white px-5 py-2 text-xs font-medium text-[#374151] transition-all duration-100 ease-out hover:bg-[#F9FAFB]'
}
<header className="sticky top-0 z-10 border-b border-[#E5E7EB] bg-white/95 backdrop-blur">
<div className="mx-auto flex min-h-14 max-w-[1280px] flex-wrap items-center justify-between gap-x-4 gap-y-2 px-4 py-2 sm:px-6">
<div className="flex items-center gap-2.5">
<span className="flex h-8 w-8 items-center justify-center rounded-lg bg-accent text-xs font-bold text-white">
F
</span>
<span className="text-sm font-semibold text-[#111827]">FRASA Desarrollos</span>
</div>
<div className="flex items-center gap-3">
<div
className="inline-flex rounded-lg border border-[#E5E7EB] bg-[#F1F5F9] p-1"
role="tablist"
aria-label="Vista del tablero"
>
{v.etiqueta}
{VISTAS.map((v) => (
<button
key={v.id}
type="button"
role="tab"
aria-selected={vista === v.id}
onClick={() => setVista(v.id)}
className={
vista === v.id
? 'rounded-md bg-white px-3 py-1.5 text-xs font-medium text-[#111827] shadow-sm transition-all duration-100 ease-out'
: 'rounded-md px-3 py-1.5 text-xs font-medium text-[#64748B] transition-all duration-100 ease-out hover:text-[#111827]'
}
>
{v.etiqueta}
</button>
))}
</div>
<button
type="button"
onClick={() => acciones?.actualizar()}
disabled={!acciones || acciones.cargando}
className="rounded-lg border border-[#D1D5DB] bg-white px-4 py-1.5 text-xs font-medium text-accent shadow-sm transition-all duration-100 ease-out hover:bg-blue-50 disabled:opacity-50"
>
{acciones?.cargando ? 'Actualizando…' : 'Actualizar'}
</button>
))}
</div>
</div>
</div>
{vista === 'mia' ? <MiTablero /> : <TableroGeneral mostrarEquipo={conEquipo} />}
</header>
{vista === 'mia' ? (
<MiTablero alCambiarAcciones={setAcciones} />
) : (
<TableroGeneral mostrarEquipo={conEquipo} alCambiarAcciones={setAcciones} />
)}
</div>
)
}