Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/Pewiz/ulagos360/llms.txt

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

All global application state in ULagos 360° lives in a single Zustand store defined in src/stores/spacesStore.js. The store uses Zustand’s persist middleware to automatically serialise a subset of state to localStorage under the key spaces-storage, so that space occupancy data and the current tutor’s identity survive page refreshes, accidental tab closes, and brief network outages without any extra saving logic in components.

State Shape

interface SpacesState {
  spaces: Record<string, Space>;  // keyed by space id (e.g. "aula-magna")
  currentUser: User | null;       // { id: string, name: string }
  connected: boolean;             // store field, NOT persisted; live status comes from useSocketConnection
  loading: boolean;               // true until initializeSpaces() completes
  sessionId: string;              // stable session token across reloads
}
connected and loading are intentionally excluded from persistence (see Persistence Configuration below). In practice, the live WebSocket status consumed by components comes from useSocketConnection’s own local useState(false) rather than this store field. loading resets to true on every page load and is set to false only after initializeSpaces() completes.

Space Object Shape

Each entry in the spaces map conforms to the following interface. Fields marked optional are only set for specific space categories.
interface Space {
  id: string;               // unique slug, e.g. "hall-principal"
  name: string;             // human-readable label shown in SpaceCard
  capacity?: number;        // max group size (omitted for some campus stops)
  status: SpaceStatus;      // see status values below
  occupiedBy: string | null;   // tutor name when status is "ocupado"
  reservedBy: string | null;   // tutor name when status is "reservado" | "en_camino"
  lastUpdate: string;          // ISO 8601 timestamp of last change
  updatedBy: string | null;    // name of the tutor who last changed status
  category: SpaceType;         // space group (bienvenida, coffee, etc.)
  type: SpaceType;             // mirrors category; kept for server compatibility
  supervisor?: string;         // Talleres only — assigned supervisor name
  location?: string;           // Talleres only — physical location label
  establishment?: string;      // Tours en Curso only — associated establishment
}
Valid SpaceStatus values (from constants/spaces.js):
ValueMeaning
disponibleSpace is free and ready to receive a group
en_caminoA group is on its way but has not yet arrived
reservadoSpace is reserved for a specific group
ocupadoSpace is currently in use
en_cursoActivity is actively running (Talleres / Tours)
terminadoActivity has finished; awaiting reset
mantenimientoSpace temporarily out of service

Store Actions

Every action is a synchronous function exposed from the Zustand store. Async side-effects (socket emissions) are handled externally in useRealTimeSpaces.
ActionSignatureDescription
initializeSpaces() => voidSeeds spaces from the SPACES_DATA catalogue if the map is empty; sets loading: false. No-op when persisted data already exists.
updateSpace(spaceId: string, updates: Partial<Space>) => voidShallow-merges updates into the target space, stamps lastUpdate with the current ISO timestamp, and writes the full spaces map to ulagos360_spaces_backup in localStorage.
updateSpaceStatus(spaceId: string, status: SpaceStatus, userData?: { name: string }) => Partial<Space>High-level status change that derives occupiedBy / reservedBy from status semantics, clears the opposing field, sets updatedBy, then writes the updated space directly to the store via set() and to ulagos360_spaces_backup in localStorage. Returns the updates object.
setUser(user: User) => voidWrites currentUser to the store and persists the full user object to ulagos360_current_user in localStorage.
setConnected(connected: boolean) => voidFlips the store’s connected flag. Defined in the store but not called by useSocketConnection — that hook manages its own local useState(false) for connection status and exposes it through its return value.
getSpacesByCategory(category: SpaceType) => Space[]Returns all spaces whose category matches the argument. Non-mutating selector.
getAvailableSpaces(category?: SpaceType) => Space[]Returns all spaces with status === "disponible", optionally filtered by category. Non-mutating selector.
clearAllData() => voidRemoves all five localStorage keys, resets every space to disponible, and clears currentUser. Does not regenerate sessionId.
logout() => voidSame as clearAllData plus clears lastStateSync from localStorage and generates a fresh sessionId via generateSessionId().
forceSync() => voidManually serialises the current spaces, currentUser, and sessionId to both spaces-storage and ulagos360_spaces_backup without waiting for Zustand’s automatic persist flush.
getLocalStateForServer() => { spaces, user, timestamp }Returns a plain-object snapshot of the current state, ready to be embedded in a sync_spaces socket payload.
shouldAcceptServerUpdate(serverData: { spaceId, lastUpdate }) => booleanConflict-resolution guard. Returns true if the server’s lastUpdate is newer than or within 1 second of the local timestamp; false if local data is strictly more recent by more than 1 second.
updateSpaceStatus returns the updates object it applied. The handleUpdateSpaceStatus wrapper in useRealTimeSpaces captures this return value and passes it directly to sendSpaceUpdate() — so the same derived data is used for both the optimistic local update and the Socket.IO emission, with zero risk of the two falling out of sync.

updateSpaceStatus Status Logic

The action automatically sets or clears occupiedBy and reservedBy based on which status is being applied:
switch (status) {
  case "ocupado":
    updates.occupiedBy = userData?.name || currentUser?.name;
    updates.reservedBy = null;
    break;
  case "reservado":
  case "en_camino":
    updates.reservedBy = userData?.name || currentUser?.name;
    updates.occupiedBy = null;
    break;
  case "disponible":
    updates.occupiedBy = null;
    updates.reservedBy = null;
    break;
}
The updatedBy field is always set to the current user’s name, or "Usuario anónimo" if no user is authenticated.

Persistence Configuration

The persist middleware is configured with the following options:
{
  name: "spaces-storage",           // localStorage key
  partialize: (state) => ({
    spaces:      state.spaces,      // full spaces map
    currentUser: state.currentUser, // authenticated tutor
    sessionId:   state.sessionId,   // stable session token
  }),
  version: 1,                       // schema version for future migrations
}
connected and loading are excluded from partialize. On startup loading is always true until initializeSpaces() resolves, and connected is always false until the socket handshake completes — persisting these flags would cause the UI to show stale connection state.

localStorage Key Inventory

The application writes to several localStorage keys, managed across the store, hooks, and backup system:
KeyWritten byContents
spaces-storageZustand persist + forceSyncPersisted store slice: spaces, currentUser, sessionId
ulagos360_current_usersetUser, useSocketConnectionRaw user object { id, name }
ulagos360_session_idgenerateSessionId()Stable session token string
ulagos360_spaces_backupupdateSpace, updateSpaceStatus, forceSync, usePersistentBackupRaw spaces map (no wrapper)
ulagos360_emergency_backupusePersistentBackup{ spaces, timestamp, version: "2.0" }
lastStateSyncuseSocketConnectionUnix ms timestamp of last server sync
sessionIduseSocketConnectionSocket-level session id (mirrors ulagos360_session_id)
currentUseruseSocketConnectionDuplicate user record for quick socket-layer access
clearAllData and logout both remove the five primary keys. However, useRealTimeSpaces.handleLogout performs a more thorough sweep, explicitly deleting all eight keys listed above before reloading the page.

Build docs developers (and LLMs) love