@startuml uml-sequence-login-rbac-dashboard !include _style.iuml title 7. Login admin + RBAC + carga de dashboard ' Fuente: apps/web/src/app/api/auth/login/route.ts:1-75 ' Fuente: apps/web/src/lib/dashboard/auth-gate.ts (requirePermiso) ' Fuente: apps/web/src/lib/dashboard/rbac.ts (tienePermiso) ' Fuente: 00.sql usuarios_admin / roles_admin / roles_permisos autoactivate on actor "Admin" as Adm box "Browser (Next.js)" #F0F0F0 participant "Página /login" as Login participant "Layout /dashboard\n+ AppsMenu" as Dash end box box "API REST" #F5F5F5 boundary "POST /api/auth/login" as Route control "defineEndpoint\nauth=public" as H end box box "Edge runtime" #F5F5F5 control "middleware.ts\n(cookie auth_token)" as Mid control "requirePermiso(...)\nrequireAdminSession" as Gate end box box "Persistencia Postgres" #EAEAEA database "usuarios" as DBU database "user_credentials" as DBC database "roles_admin /\nroles_permisos" as DBR end box == Login == Adm -> Login ++ : email + password Login ->> Route ++ : POST /api/auth/login Route -> H ++ : delegar H -> DBU ++ : SELECT u.*, c.password_hash\nJOIN user_credentials DBU --> H -- : usuario H -> H : bcrypt.compare(password, hash) note right of H login/route.ts:27-28 401 UnauthorizedError si falla end note H -> DBR ++ : EXISTS usuarios_admin\n+ roles_admin activos DBR --> H -- : ok=true H -> H : jwt.sign({sub, email, rol}, JWT_SECRET, 7d) H -> Route -- : NextResponse + cookie\nauth_token (httpOnly, secure, sameSite=lax) Route --> Login -- : 200 + Set-Cookie Login -->> Dash : navigate /dashboard == Carga del dashboard == Dash ->> Mid ++ : GET /dashboard\nCookie: auth_token Mid -> Mid : verifyJWT(token) note right of Mid Edge runtime intercepta rutas protegidas. end note Mid --> Dash -- : pasa SessionPayload Dash -> Gate ++ : requireAdminSession() Gate -> DBR ++ : SELECT permisos del rol DBR --> Gate -- : Set Gate --> Dash -- : viewer {userId, permisos} Dash -> Gate ++ : requirePermiso('dashboard.ver') Gate -> Gate : tienePermiso(viewer, clave) note right of Gate dashboard/rbac.ts 403 ForbiddenError si falta. end note Gate --> Dash -- Dash -> Dash : AppsMenu filtra workspaces\npor permisos del rol note over Dash #FEF3C7 7 workspaces persona-based (Operaciones, Soporte, Finanzas, RRHH, Compliance, etc.) end note Dash --> Adm -- : Render con menú filtrado footer Fuente: apps/web/src/app/api/auth/login/route.ts | apps/web/src/lib/dashboard/auth-gate.ts | apps/web/src/lib/dashboard/rbac.ts | 00.sql roles_admin @enduml