Se ha implementado un sistema de roles para restringir el acceso a diferentes vistas de la aplicación. Se soportan tres roles:
- Tutor: acceso limitado a Dashboard y Proyectos.
- Coordinador: acceso a todas las vistas excepto Gestión de Usuarios.
- Operador: acceso a todas las vistas excepto Gestión de Usuarios.
- Admin: acceso completo, incluyendo Gestión de Usuarios.
Proveedor de contexto de React que gestiona el estado del rol del usuario.
Funcionalidades:
RoleProvider: componente que envuelve la aplicación y proporciona el contexto de rol.useRole(): hook personalizado para acceder al rol actual y la función para cambiarlo.- Persistencia en
localStorage: el rol seleccionado se guarda automáticamente en el navegador.
Tipos:
type Role = "admin" | "coordinador" | "tutor" | "operador" | null
type RoleContextValue = {
role: Role
setRole: (r: Role) => void
}Componente que renderiza una pantalla modal de selección de rol.
Funcionalidades:
- Modal full-screen que aparece cuando no hay rol seleccionado.
- Botones para elegir entre "Soy Tutor" y "Soy Admin".
- Se cierra automáticamente después de la selección.
- Estilos tailwindcss integrados.
UI:
┌─────────────────────────────────────────────┐
│ Seleccioná tu rol │
│ Elige tu rol para acceder a la plataforma. │
│ │
│ [Tutor] Dashboard y Proyectos │
│ [Coordinador] Acceso completo excepto... │
│ [Admin (Acceso Total)] │
└─────────────────────────────────────────────┘
Componente wrapper que combina RoleProvider y RoleSelector.
Funcionalidades:
- Envuelve toda la aplicación con el proveedor de rol.
- Renderiza la pantalla de selección en overlay.
Se añadió el wrapper de roles en el RootLayout.
Cambio:
import { RoleWrapper } from "@/components/role-wrapper"
export default function RootLayout({ children }) {
return (
<html lang="es">
<body className="font-sans antialiased">
<RoleWrapper>{children}</RoleWrapper>
</body>
</html>
)
}Impacto: Todos los componentes hijos tienen acceso al contexto de rol a través del hook useRole().
Se filtran los ítems de navegación según el rol del usuario.
Cambio Principal:
// Items de navegación
const navItems = [
{ href: "/", label: "Dashboard", icon: LayoutDashboard },
{ href: "/postulaciones", label: "Postulaciones", icon: Inbox },
{ href: "/proyectos", label: "Proyectos", icon: FolderKanban },
{ href: "/evaluaciones", label: "Evaluaciones", icon: ClipboardCheck },
{ href: "/gestion-usuarios", label: "Gestion de Usuarios", icon: Users, adminOnly: true },
]
// Filtrado por rol
const { role } = useRole()
let items = navItems
if (role === "tutor") {
items = navItems.filter(i => i.href === "/" || i.href === "/proyectos")
} else if (role === "coordinador") {
items = navItems.filter(i => !i.adminOnly)
}
// Admin: todos los itemsImpacto:
- Tutor: solo ve Dashboard (
/) y Proyectos (/proyectos). - Coordinador: ve Dashboard, Postulaciones, Proyectos, Evaluaciones (todo menos Gestión de Usuarios).
- Admin: ve todas las opciones incluyendo Gestión de Usuarios (
/gestion-usuarios).
Nueva ruta solo disponible para administradores.
Descripción:
- Página placeholder para el panel de gestión de usuarios.
- Solo accesible con rol "admin".
App cargada
↓
RoleWrapper renderiza RoleSelector
↓
Usuario ve modal: "Seleccioná tu rol"
Usuario clickea "Soy Tutor" o "Soy Admin"
↓
setRole(rol) actualiza el contexto
↓
localStorage guarda el rol
↓
RoleSelector desaparece (condición: if (role) return null)
↓
Navegación se filtra según el rol
Usuario recarga la página
↓
useEffect en RoleProvider lee localStorage
↓
Rol restaurado automáticamente
↓
Modal NO aparece (localStorage tiene valor)
↓
User accede directamente a la app
/(Dashboard)/proyectos(Proyectos)/proyectos/[id](Detalle de Proyecto)
/(Dashboard)/postulaciones(Postulaciones)/proyectos(Proyectos)/proyectos/[id](Detalle de Proyecto)/evaluaciones(Evaluaciones)
/(Dashboard)/postulaciones(Postulaciones)/proyectos(Proyectos)/proyectos/[id](Detalle de Proyecto)/evaluaciones(Evaluaciones)/gestion-usuarios(Gestión de Usuarios)
components/
├── role-context.tsx (provider + hook)
├── role-selector.tsx (modal de selección)
├── role-wrapper.tsx (wrapper)
├── app-sidebar.tsx (filtrado de nav items)
└── ...
app/
├── layout.tsx (RoleWrapper integrado)
└── ...
- React Context API: gestión centralizada de estado del rol.
- localStorage: persistencia entre sesiones.
- Next.js: rutas dinámicas y server/client components.
- Tailwind CSS: estilos del modal y componentes.
- Lucide React: iconos en la navegación.
-
Protección de Rutas en Server:
- Implementar middleware en Next.js para validar acceso por rol.
- Redirigir a
/si un tutor intenta acceder a rutas no permitidas.
-
Logout / Switch de Rol:
- Botón para cambiar de rol sin recargar.
- Botón de logout que limpie localStorage.
-
Integración con Backend:
- Reemplazar selector modal con autenticación real.
- Guardar rol en base de datos o JWT.
- Validar rol en el servidor antes de procesar requests.
-
Tests:
- Tests de contexto de rol.
- Tests de filtrado de navegación.
- Tests E2E de flujo de selección.
-
Acceder al rol actual:
import { useRole } from "@/components/role-context" export function MyComponent() { const { role, setRole } = useRole() return <div>Rol: {role}</div> }
-
Condicionales por Rol:
const { role } = useRole() if (role === "tutor") { // mostrar solo para tutores } else if (role === "admin") { // mostrar solo para admins }
-
Cambiar Rol (debug):
const { setRole } = useRole() <button onClick={() => setRole("tutor")}>Cambiar a Tutor</button>
cd reto-summer-2026-ithaka-frontend
pnpm devhttp://localhost:3000
- Debe aparecer un modal con tres botones.
- Clickear "Tutor".
- Verificar que la sidebar muestre solo Dashboard y Proyectos.
- Verificar que localStorage tiene
role: "tutor".
- Abrir DevTools → Console.
- Ejecutar:
localStorage.removeItem('role'). - Recargar la página.
- Clickear "Coordinador".
- Verificar que la sidebar muestra Dashboard, Postulaciones, Proyectos, Evaluaciones (pero NO Gestión de Usuarios).
- Abrir DevTools → Console.
- Ejecutar:
localStorage.removeItem('role'). - Recargar la página.
- Clickear "Admin".
- Verificar que la sidebar muestra todos los ítems incluyendo Gestión de Usuarios.
- Navegar a
/gestion-usuariospara verificar que la ruta funciona.
- Rama Feature:
feature/role-views - Rama Dev:
dev(recibe pushes de feature/role-views) - Rama Main:
main(producción)
Commit:
feat(role): add role selector and restrict tutor navigation
- Create RoleProvider and useRole hook
- Add RoleSelector modal component
- Filter sidebar navigation by role
- Persist role in localStorage
- Wrap app with RoleWrapper in layout
- ✅ El rol se persiste en localStorage automáticamente.
- ✅ El modal desaparece después de seleccionar rol.
- ✅ La navegación se filtra dinámicamente.
⚠️ Los tutores pueden acceder a rutas no permitidas escribiendo en la URL (sin guardia en el servidor).⚠️ No hay logout/switch de rol fácil (requiere limpiar localStorage manualmente).
Para preguntas sobre esta implementación, referirse a:
- Rama:
feature/role-views/dev - Archivos:
components/role-context.tsx,components/role-selector.tsx,components/role-wrapper.tsx