Skip to Content
UNIMAST

Rutas y Layouts

UNIMAST ERP utiliza Next.js 15 App Router con un sistema sofisticado de layouts anidados, navegación dinámica basada en permisos y componentes de UI especializados.

🏗️ Arquitectura de Routing

Estructura de Directorios

app/ ├── app/ # Next.js App Router │ ├── layout.tsx # Layout raíz de la aplicación │ ├── (dashboard)/ # Grupo de rutas del dashboard │ │ ├── layout.tsx # Layout principal del dashboard │ │ ├── page.tsx # Página de inicio │ │ ├── invoices/ # Módulo de facturas │ │ ├── transactions/ # Módulo de transacciones │ │ ├── ecf/ # Módulo de facturación electrónica │ │ ├── settings/ # Configuraciones del sistema │ │ └── layout/ # Componentes específicos del layout │ ├── api/ # Endpoints de API │ └── login/ # Página de autenticación

Convenciones de Next.js 15

  • Grupos de rutas: (dashboard) agrupa rutas bajo un layout común
  • Layouts anidados: Cada directorio puede tener su propio layout.tsx
  • Páginas server-first: Renderizado en servidor por defecto
  • Componentes de cliente: Solo cuando es necesario interactividad

🔐 Layout Raíz

Configuración Base (app/app/layout.tsx)

import './globals.css'; import { Analytics } from '@vercel/analytics/react'; import { Toaster } from '@/components/ui/sonner'; export const metadata = { title: 'Unimast ERP', description: 'Un sistema de gestión empresarial' }; export default function RootLayout({ children }: { children: React.ReactNode; }) { return ( <html lang="en"> <body className="flex min-h-screen w-full flex-col"> <Toaster richColors={false} toastOptions={{ classNames: { title: 'text-sm font-medium', description: 'text-muted-foreground text-xs' } }} theme="light" /> {children} </body> <Analytics /> </html> ); }

Características del Layout Raíz

  • Metadatos globales para SEO y configuración
  • Toaster global para notificaciones del sistema
  • Analytics integrado con Vercel
  • CSS global y estilos base
  • Estructura HTML consistente

🎯 Layout del Dashboard

Estructura Principal (app/app/(dashboard)/layout.tsx)

export default async function DashboardLayout({ children }: { children: React.ReactNode; }) { // 1. Verificación de autenticación const session = await getSession(); if (!session) { redirect('/login'); } // 2. Verificación de scopes especiales const isTerminalOp = await hasRequiredScopes([SCOPE_FLAGS.TERMINAL_OP]); // 3. Definición de rutas con permisos const routes = [ { href: '/', label: 'Inicio', icon: <Home className="h-5 w-5" />, children: [] }, { href: '#', label: 'Finanzas', expanded: true, icon: <HandCoins className="h-5 w-5" />, children: [ { href: '/invoices', label: 'Facturas', icon: <FileClock className="h-5 w-5" />, requiredScopes: [SCOPE.READ_INVOICES], children: [] } // ... más rutas ] } ]; return ( <SessionProvider session={session}> <Providers> <TooltipProvider> <main className="flex min-h-screen w-full flex-col bg-muted/40"> <SidebarProvider> <CollapsibleSidebar routes={routes} /> <SidebarContentWrapper> <Header /> <main className="grid flex-1 items-start gap-2 p-4 md:gap-4 bg-muted/40"> {children} </main> </SidebarContentWrapper> </SidebarProvider> <Analytics /> </main> </TooltipProvider> </Providers> </SessionProvider> ); }

Características del Layout del Dashboard

  • Autenticación obligatoria con redirección automática
  • Control de scopes para autorización granular
  • Navegación dinámica basada en permisos del usuario
  • Providers anidados para estado global
  • Sidebar responsive con rutas organizadas

🧭 Sistema de Navegación

Estructura de Rutas

interface Route { href: string; label: string; icon?: React.ReactNode; expanded?: boolean; requiredScopes?: string[]; children: Route[]; }

Categorías de Navegación

1. Inicio

  • Ruta: /
  • Acceso: Todos los usuarios autenticados
  • Funcionalidad: Dashboard principal con resumen del sistema

2. Finanzas

  • Ruta: # (grupo expandible)
  • Acceso: Basado en scopes específicos
  • Submódulos:
    • Facturas (/invoices) - Requiere SCOPE.READ_INVOICES
    • Pago (/customer-pay) - Requiere SCOPE.CREATE_PAYMENTS
    • Histórico de Turnos - Requiere SCOPE.READ_CASH_SHIFT
    • Transacciones (/transactions) - Requiere SCOPE.READ_CASH_SHIFT

3. eCF (Facturación Electrónica)

  • Ruta: # (grupo expandible)
  • Acceso: Basado en scopes de documentos fiscales
  • Submódulos:
    • Comprobantes Emitidos (/ecf/issued) - Requiere SCOPE.READ_TAX_DOCUMENT
    • Comprobantes Recibidos (/ecf/received) - Requiere SCOPE.READ_RECEIVED_TAX_DOCUMENT

4. Configuración

  • Ruta: # (grupo expandible)
  • Acceso: Solo usuarios con permisos administrativos
  • Submódulos:
    • Usuarios (/settings/users) - Gestión de usuarios y roles
    • Terminales (/settings/terminals) - Configuración de POS
    • Métodos de Pago (/settings/payment-methods) - Configuración de pagos
    • Roles (/settings/roles) - Gestión de permisos

Control de Acceso por Scopes

// Verificación de scopes en tiempo de renderizado const isTerminalOp = await hasRequiredScopes([SCOPE_FLAGS.TERMINAL_OP]); // Rutas condicionales basadas en permisos { href: isTerminalOp ? '/shift-history' : '/cash-shifts', label: 'Histórico de turno', requiredScopes: [SCOPE.READ_CASH_SHIFT] }

🧩 Componentes de Layout

interface CollapsibleSidebarProps { routes: Route[]; } export function CollapsibleSidebar({ routes }: CollapsibleSidebarProps) { return ( <aside className="..."> <nav className="..."> {routes.map((route) => ( <NavItem key={route.href} route={route} /> ))} </nav> </aside> ); }

Características:

  • Navegación jerárquica con grupos expandibles
  • Iconos visuales para cada sección
  • Control de permisos integrado
  • Responsive design con colapso automático
const PATHNAME_LABEL: {[key: string]: string} = { 'finances': 'Finanzas', 'customer-pay': 'Pagar', 'transactions': 'Transacciones', 'shift-history': 'Histórico de Turnos', 'invoices': 'Facturas', 'settings': 'Ajustes', 'ecf': 'Facturación Electrónica', 'received': 'Recibidos', 'issued': 'Emitidos' }; export function DashboardBreadcrumb() { const paths = usePathname(); const pathNames = paths.split('/').filter((path) => path); return ( <Breadcrumb className="hidden md:flex"> <BreadcrumbList> <BreadcrumbItem> <BreadcrumbLink asChild> <Link href="/">Inicio</Link> </BreadcrumbLink> </BreadcrumbItem> {pathNames.map((pathname, index) => ( <React.Fragment key={pathname}> <BreadcrumbSeparator /> {index === pathNames.length - 1 ? ( <BreadcrumbItem> <BreadcrumbPage> {PATHNAME_LABEL?.[pathname] || pathname} </BreadcrumbPage> </BreadcrumbItem> ) : ( <BreadcrumbItem> <BreadcrumbLink asChild> <Link href={`/${pathNames.slice(0, index + 1).join('/')}`}> {PATHNAME_LABEL?.[pathname] || pathname} </Link> </BreadcrumbLink> </BreadcrumbItem> )} </React.Fragment> ))} </BreadcrumbList> </Breadcrumb> ); }

Características:

  • Mapeo automático de rutas a etiquetas legibles
  • Navegación jerárquica con enlaces activos
  • Responsive design (oculto en móvil)
  • Separadores visuales entre niveles

Header del Dashboard

<header className="sticky top-0 z-30 flex h-14 items-center gap-4 border-b bg-background px-4"> <DashboardBreadcrumb /> <SearchInput /> <TerminalBadge /> <Payment /> <User /> </header>

Componentes del Header:

  • Breadcrumb: Navegación de la página actual
  • SearchInput: Búsqueda global del sistema
  • TerminalBadge: Indicador de terminal activo
  • Payment: Acceso rápido a pagos
  • User: Menú de usuario y configuración

🔄 Sistema de Providers

Jerarquía de Providers

<SessionProvider session={session}> <Providers> <TooltipProvider> <SidebarProvider> {/* Contenido del dashboard */} </SidebarProvider> </TooltipProvider> </Providers> </SessionProvider>

Providers Principales

SessionProvider

  • Propósito: Gestión de sesión de usuario
  • Datos: Información del usuario, roles y scopes
  • Acceso: Disponible en toda la aplicación

SidebarProvider

  • Propósito: Estado del sidebar (colapsado/expandido)
  • Funcionalidad: Control de navegación móvil
  • Contexto: Solo en el dashboard

TooltipProvider

  • Propósito: Sistema de tooltips global
  • Componentes: Todos los elementos con tooltips
  • Accesibilidad: Soporte para lectores de pantalla

📱 Navegación Responsive

export function MobileNav() { const { isOpen, toggle } = useSidebar(); return ( <Sheet open={isOpen} onOpenChange={toggle}> <SheetContent side="left" className="w-80 p-0"> <CollapsibleSidebar routes={routes} /> </SheetContent> </Sheet> ); }

Características:

  • Sheet lateral para dispositivos móviles
  • Ancho optimizado para pantallas pequeñas
  • Sincronización con estado del sidebar
  • Gestos táctiles nativos

Adaptación Automática

  • Desktop: Sidebar fijo con navegación completa
  • Tablet: Sidebar colapsable con overlay
  • Móvil: Navegación en sheet lateral

🛡️ Seguridad y Autorización

Middleware de Autenticación

// middleware.ts export function middleware(request: NextRequest) { const token = request.cookies.get('accessToken'); if (!token && !request.nextUrl.pathname.startsWith('/login')) { return NextResponse.redirect(new URL('/login', request.url)); } return NextResponse.next(); }

Verificación de Scopes

// Verificación en tiempo de renderizado const hasAccess = await hasRequiredScopes([SCOPE.READ_INVOICES]); // Verificación en componentes de cliente const { hasScope } = useScopes(); const canCreateInvoice = hasScope(SCOPE.CREATE_INVOICES);

Protección de Rutas

  • Nivel de layout: Verificación automática en DashboardLayout
  • Nivel de componente: Verificación granular con hooks
  • Nivel de API: Verificación en endpoints

📋 Patrones de Implementación

1. Layouts Anidados

// app/app/(dashboard)/invoices/layout.tsx export default function InvoicesLayout({ children }: { children: React.ReactNode; }) { return ( <div className="space-y-4"> <div className="flex items-center justify-between"> <h1 className="text-2xl font-bold">Facturas</h1> <CreateInvoiceButton /> </div> {children} </div> ); }

2. Páginas con Metadata

// app/app/(dashboard)/invoices/page.tsx export const metadata = { title: 'Facturas - UNIMAST ERP', description: 'Gestión de facturas del sistema' }; export default function InvoicesPage() { return ( <div className="space-y-4"> <InvoicesTable /> <InvoicesPagination /> </div> ); }

3. Componentes de Navegación

// app/app/(dashboard)/nav-item.tsx export function NavItem({ route }: { route: Route }) { const { hasScope } = useScopes(); // Verificar permisos antes de renderizar if (route.requiredScopes && !route.requiredScopes.every(hasScope)) { return null; } return ( <div className="..."> <Link href={route.href}> {route.icon} <span>{route.label}</span> </Link> {route.children.length > 0 && ( <NavGroup routes={route.children} /> )} </div> ); }

🚀 Mejores Prácticas

1. Organización de Rutas

  • Agrupar por funcionalidad usando grupos de rutas
  • Separar lógica de negocio de componentes de UI
  • Usar layouts anidados para funcionalidad común

2. Control de Acceso

  • Verificar permisos en el nivel más alto posible
  • Usar scopes granulares para control fino
  • Implementar fallbacks para usuarios sin permisos

3. Performance

  • Lazy loading de componentes pesados
  • Code splitting automático con Next.js
  • Optimización de imágenes con next/image

4. Accesibilidad

  • Navegación por teclado completa
  • Labels semánticos para lectores de pantalla
  • Contraste adecuado en todos los elementos

5. Responsive Design

  • Mobile-first approach en el diseño
  • Breakpoints consistentes en toda la aplicación
  • Gestos táctiles optimizados para móvil

🔧 Troubleshooting

Problemas Comunes

Rutas no se renderizan

# Verificar que el archivo page.tsx existe ls app/app/(dashboard)/invoices/page.tsx # Verificar permisos del usuario console.log('User scopes:', user.scopes);
# Verificar estado del SidebarProvider const { isOpen, toggle } = useSidebar(); console.log('Sidebar state:', isOpen);
# Verificar mapeo de rutas console.log('Current pathname:', usePathname()); console.log('PATHNAME_LABEL:', PATHNAME_LABEL);

Debug de Navegación

// Agregar logging para debugging useEffect(() => { console.log('Current route:', pathname); console.log('Available routes:', routes); console.log('User scopes:', user.scopes); }, [pathname, routes, user.scopes]);

¿Necesitas más detalles sobre algún aspecto específico? Revisa la documentación de autenticación para entender mejor el sistema de permisos, o la documentación de componentes para aprender sobre los componentes de UI utilizados.