Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/Arthurr23/XHealtXperience/llms.txt

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

XHealtXperience enforces access control through Spatie Laravel Permission, a battle-tested RBAC package for Laravel. Every user in the system carries exactly one role, and that role determines which routes, UI sections, and data they can reach. Roles are scoped per-tenant — a Doctor in one clinic has no visibility into another clinic’s data whatsoever, because each tenant runs on its own isolated database.

Role Overview

The platform ships with eight roles. Seven are tenant-level roles that live in each clinic’s database; Super Admin is the sole central role that lives in the shared central database and is never assignable by a clinic administrator.

Super Admin

Central database only. Manages the multi-tenant platform itself: creates/suspends clinics, sets per-clinic user limits, and manages global administrators.

Administrador Clinica

Full clinic administrator. Manages users, patients, appointments, services, surgical suites, settings, and can view the audit log.

Doctor

Manages their own agenda, blocks time slots, schedules and edits surgeries, and views patients and services within their clinic.

Recepcion

Manages patients and appointments, performs check-in, and views services. No access to administrative settings.

Enfermero

Read-only access to patients and the surgical suite view. Cannot create or modify records.

Auditor

Read-only access to the full audit log (bitácora) and a dedicated security dashboard showing suspicious activity metrics.

Paciente

Access to their own dashboard only. Cannot navigate any administrative or clinical area.

Practicante Externo

External trainee with limited access scoped by the clinic administrator on a case-by-case basis.

Role Reference Table

RoleDB ScopeKey Capabilities
Super AdminCentral DBCreate/suspend tenants, manage global admins, set clinic limits
Administrador ClinicaTenant DBFull user CRUD, patients, appointments, services, surgical suite, settings, audit log
Administrador ProfesionistaTenant DBProfessional-level admin — scoped subset of clinic administration
DoctorTenant DBOwn agenda, block slots, schedule/edit/cancel surgeries, view patients & services
RecepcionTenant DBPatients, appointments, check-in, view services, confirm surgical staff
EnfermeroTenant DBView patients, view surgical suite
AuditorTenant DBRead-only audit log, dedicated auditor dashboard with security metrics
PacienteTenant DBOwn patient dashboard only
Practicante ExternoTenant DBLimited access assigned by clinic administrator
Super Admin is never seeded into tenant databases and must never appear in the ROLES_ASIGNABLES whitelist inside UserController. A clinic administrator cannot create or assign this role. The Super Admin account must also have its own 2FA configured before accessing the /panel-global/dashboard — the two_factor middleware is enforced on all Super Admin routes.

Checking Roles in Controllers

XHealtXperience uses Spatie’s built-in helpers to authorize actions at the controller layer. The UserController restricts its entire surface to Administrador Clinica via Laravel’s Middleware class:
// Restricting a whole controller to a single role
public static function middleware(): array
{
    return [
        new Middleware('role:Administrador Clinica'),
    ];
}
For finer-grained checks inside a method, use the helpers provided by the HasRoles trait:
// Check for a single role
if ($user->hasRole('Doctor')) {
    // Doctor-only logic
}

// Check for any of several roles
if ($user->hasAnyRole(['Administrador Clinica', 'Doctor'])) {
    // Admin or Doctor logic
}

// Retrieve all role names (returns a Collection)
$roles = $user->getRoleNames(); // e.g. collect(['Doctor'])

Middleware Stack on Routes

Every protected route in routes/tenant.php is wrapped in a layered middleware stack. The order matters: auth confirms the user is logged in, two_factor confirms the 2FA challenge has been passed, check_inactivity enforces the idle-session timeout, and the role: middleware confirms the user has the required role.
// General protected routes (all authenticated + 2FA users)
Route::middleware(['auth', 'two_factor', 'check_inactivity'])->group(function () {
    Route::get('/dashboard', [DashboardController::class, 'index']);
});

// Doctor-exclusive routes
Route::middleware(['auth', 'two_factor', 'check_inactivity', 'role:Doctor'])->group(function () {
    Route::post('/bloqueos-agenda', [BloqueoAgendaController::class, 'store']);
    Route::delete('/bloqueos-agenda/{bloqueoAgenda}', [BloqueoAgendaController::class, 'destroy']);
});

// Clinic admin-exclusive routes
Route::middleware(['auth', 'two_factor', 'check_inactivity', 'role:Administrador Clinica'])->group(function () {
    Route::resource('usuarios', UserController::class);
    Route::patch('/usuarios/{usuario}/desbloquear', [UserController::class, 'desbloquear']);
    // ... other admin routes
});

// Multiple roles sharing a route group
Route::middleware(['auth', 'two_factor', 'check_inactivity', 'role:Doctor|Administrador Clinica'])->group(function () {
    Route::post('/quirofanos', [QuirofanoController::class, 'store']);
    Route::patch('/quirofanos/{cita}', [QuirofanoController::class, 'update']);
});

// Audit log access
Route::middleware(['auth', 'two_factor', 'check_inactivity', 'role:Administrador Clinica|Auditor'])->group(function () {
    Route::get('/bitacora', [AuditLogController::class, 'index']);
});

Account Lockout

XHealtXperience protects against brute-force attacks by locking accounts after a configurable number of consecutive failed login attempts. How it works: The User model tracks two fields:
FieldTypePurpose
failed_login_attemptsintegerCount of consecutive failed logins since last successful login
is_lockedbooleanSet to true when the attempt count reaches the configured maximum
The maximum number of attempts is read from the max_login_attempts column on the TenantSetting model (default: 3). When a user successfully authenticates, resetFailedAttempts() is called automatically:
// Called on every failed login attempt — auto-locks when threshold is reached
$user->incrementFailedAttempts($maxAttempts);

// Called after a successful login — resets the counter and unlocks the account
$user->resetFailedAttempts();
Unlocking a locked account: Only an Administrador Clinica can unlock a locked account via the dedicated endpoint:
PATCH /{tenant}/usuarios/{usuario}/desbloquear
This action is logged in the audit log with the action slug auth.account_unlocked.

Session Inactivity Timeout

The check_inactivity middleware (CheckInactivity) automatically logs out users who have been idle for too long. The timeout duration is read from the inactivity_timeout_minutes column of the TenantSetting model (default: 15 minutes if not configured). Behavior:
  • On every authenticated Inertia/web request, the session key ultima_actividad is refreshed.
  • If the time elapsed since the last activity equals or exceeds inactivity_timeout_minutes, the middleware:
    1. Logs the user out and invalidates the session.
    2. Regenerates the CSRF token.
    3. Redirects to /{tenant}/login with a status message.
  • For Inertia XHR requests, it returns an HTTP 409 response with an X-Inertia-Location header pointing to the login page so the browser performs a full page reload.
// Reading the timeout from tenant settings (CheckInactivity middleware)
$config = TenantSetting::first();
$minutosPermitidos = $config?->inactivity_timeout_minutes ?? 15;
Clinic administrators can tune both max_login_attempts and inactivity_timeout_minutes from the Security Settings panel at POST /{tenant}/configuracion/seguridad, without needing a deployment or code change.

Build docs developers (and LLMs) love