Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/gcsconsultores/centros-estrategicos-gcs/llms.txt

Use this file to discover all available pages before exploring further.

The GCS virtual office implements a two-tier access model that combines a user’s role with a boolean eligibility flag. The role determines which rooms a user can enter once inside the office; the eligibility flag acts as a prerequisite gate — a user must pass it before any room is rendered at all. Both values are stored in the authenticated User object managed by AuthContext and persisted to localStorage between sessions.

User Model

The full shape of an authenticated user is defined in lib/types.ts:
export interface User {
  name: string
  role: UserRole   // 'cliente' | 'consultor'
  eligible: boolean // acceso a la oficina virtual
}
UserRole is a union of exactly two string literals:
export type UserRole = 'cliente' | 'consultor'
  • cliente — External visitors, prospective customers, or existing clients accessing the office to explore services or run diagnostics.
  • consultor — GCS internal team members who need unrestricted access to all rooms including private meeting and strategy spaces.

Room Access Types

Every room in the ROOMS constant carries a type field of type RoomAccess:
export type RoomAccess = 'public' | 'private'
The access type for each room is as follows:
RoomRoomIdAccess Type
Lobby Principallobbypublic
Sala de Compliancecompliancepublic
Sala de Innovacióninnovacionpublic
Sala Ejecutivaejecutivaprivate
Sala de Estrategiaestrategiaprivate
Sala de Trainingtrainingprivate
Public rooms are open to any authenticated user regardless of role. Private rooms are restricted to users with the consultor role.

Access Logic

Room-level access is evaluated inside VirtualOffice each time currentRoom changes. The relevant line from VirtualOffice.tsx is:
const hasAccess =
  user?.role === 'consultor' || currentRoomData.type === 'public'
The rendering branch that follows is:
hasAccess ? (
  renderCurrentRoom()
) : (
  <div>
    <p className="text-red-500">No tienes acceso a esta sala</p>
    <button onClick={() => setCurrentRoom('lobby')}>
      Volver al Lobby
    </button>
  </div>
)
The logic is intentionally simple:
  • Consultoresuser?.role === 'consultor' is true, so hasAccess is always true. They can enter every room, including all private ones.
  • Clientes — Access is granted only when currentRoomData.type === 'public'. Attempting to navigate to a private room renders the “No tienes acceso” error and a button to return to the Lobby.
The Header and Lobby components apply the same role-based filter to their navigation items. A cliente user will not see private rooms in the nav bar or the Lobby card grid at all — so the access-denied screen is a fallback for direct URL manipulation or programmatic navigation rather than a primary UX path.

Eligibility Gate

The eligible boolean is checked in VirtualOffice before any room — including the Lobby — is rendered:
if (!user?.eligible) {
  return (
    <div className="flex items-center justify-center min-h-screen">
      <p className="text-lg font-semibold">
        Debes completar la evaluación para acceder
      </p>
    </div>
  )
}
Users who are authenticated (i.e., have a User object in localStorage) but whose eligible field is false see only this gate screen. The intended flow is that users complete the Revisoría Fiscal evaluation at /evaluacion, which sets eligible: true in their stored profile before they are admitted to the office.

Authentication Flow

1

Visit /login

The user navigates to /login, which renders LoginPage — a centered card with GCS branding, a name text input, and a role selector.
2

Enter name and select role

The user types their name and chooses either cliente or consultor from the dropdown. Both options are available with no server-side validation at this stage.
3

AuthContext.login() persists the User

Clicking Ingresar calls login({ name, role, eligible }), which saves the User object to localStorage under the key 'user' and updates the in-memory user state.
4

Redirect to / (the virtual office)

useRouter().push('/') sends the user to the root route, which mounts VirtualOffice.
5

VirtualOffice evaluates eligibility

VirtualOffice reads user?.eligible. If true, the full office renders starting at the Lobby. If false, the eligibility gate is displayed.

AuthContext API

All authentication state and actions are accessed via the useAuth() hook exported from context/AuthContext.tsx:
const { user, loading, login, logout } = useAuth()
ValueTypeDescription
userUser | nullThe currently authenticated user, or null if not logged in.
loadingbooleantrue while the initial localStorage read is in progress. Use this to avoid flash-of-unauthenticated-content on first render.
login(userData: User) => voidPersists the provided User to localStorage and sets it as the active user.
logout() => voidRemoves 'user' from localStorage and sets user to null.
The Lobby component consumes both user and loading:
const { user, loading } = useAuth()

if (loading) {
  return <p>Cargando oficina...</p>
}
This prevents the room grid from rendering before the persisted user has been read, avoiding a flicker where private rooms momentarily appear to a cliente user.
The current login implementation has no server-side authentication. Anyone can open /login and select the consultor role to gain access to all private rooms. Additionally, eligible is hardcoded to true in login/page.tsx, meaning the Revisoría Fiscal evaluation gate is bypassed entirely. Before deploying to production, replace this flow with a proper identity provider (e.g. Azure AD / Microsoft Entra ID) and tie the eligible flag to a backend evaluation record.

Extending Access Control

As the platform matures, you may need to add roles, granular permissions, or a backend-driven eligibility check. Here are the recommended extension points:
  • Add a new role — Extend the UserRole union in lib/types.ts (e.g. 'admin' | 'cliente' | 'consultor'), then update the hasAccess expression in VirtualOffice and the filter functions in Header and Lobby to handle the new value.
  • Per-room role requirements — Add an optional requiredRole?: UserRole field to the Room interface in lib/types.ts, populate it in lib/constants.ts, and reference it in the hasAccess check instead of the hardcoded 'consultor' string.
  • Backend eligibility check — In AuthContext, replace the localStorage-only useEffect with an API call (e.g. GET /api/user/eligibility) that validates the stored session token and returns the current eligible status from a database or SharePoint list.
  • Session tokens — Store a JWT or Azure AD access token alongside the User object in localStorage. Validate it server-side in each API route using the Microsoft Graph SDK that is already referenced in lib/types.ts via the MSGraphConfig interface.

Build docs developers (and LLMs) love