XHealtXperience is built on a two-database, path-based multi-tenancy model powered by stancl/tenancy v3. Every clinic (tenant) lives in its own isolated database, accessed transparently by the framework once a request’s URL prefix is matched to a known tenant ID. This page explains how the central and tenant planes are structured, how requests flow through the system, and how the key packages fit together.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.
The Two-Database Model
The application maintains two distinct database tiers at all times.Central Database
The central database is the single source of truth for platform-level data. It contains:tenants— one row per clinic, storingid(UUID),nombre_clinica,codigo_clinica,plan,limite_cuentas,estado, andfecha_inicio_suscripciondomains— path prefix mappings from tenant IDs to their routing segmentusers— the Super Admin account (and any future central users)sessions,jobs,cache,failed_jobs— Laravel infrastructure tables- Spatie Permission tables (
roles,permissions,model_has_roles, …) — scoped to the central guard for theSuper Adminrole
database/database.sqlite). In production, the config/tenancy.php file sets 'central_connection' => 'mysql'.
Tenant Databases (One per Clinic)
When the Super Admin provisions a new clinic viaPOST /panel-global/clinicas, stancl/tenancy automatically creates a fresh database for that tenant and runs the tenant migration set located in database/migrations/tenant/. Each tenant database contains:
users— clinic staff and patient accounts (completely separate from central users)patients(pacientes),expedientes_clinicos— clinical recordscitas— appointment schedulingservicios,lineas_servicio,paciente_servicio_etapas— service catalogue and stage trackingsalas,procedimientos_quirurgicos— operating room and surgical procedure managementaudit_logs(bitacora) — immutable audit trailtenant_settings— per-clinic security and scheduling configuration- Spatie Permission tables — roles (
Administrador Clinica,Doctor,Recepcion,Enfermero,Auditor,Paciente, …) scoped to that tenant’s guard
config/tenancy.php:
Cache, filesystem storage (uploaded IDs, patient signatures), and queued jobs are all automatically scoped per tenant by the tenancy bootstrappers (
CacheTenancyBootstrapper, FilesystemTenancyBootstrapper, QueueTenancyBootstrapper). No manual namespacing is required in application code.Request Flow
Every HTTP request passes through the same pipeline before reaching a controller. The diagram below shows the key decision points for a tenant request:GET /panel-global/dashboard), InitializeTenancyByPath is never invoked and all database queries run against the central connection.
Routing Architecture
Routes are split across two files that serve completely different database contexts.Central Routes — routes/web.php
Handles all requests that do not belong to a tenant clinic:
Tenant Routes — routes/tenant.php
All tenant routes share a single top-level middleware group that runs InitializeTenancyByPath first:
The 2FA challenge routes are intentionally placed outside the
two_factor middleware group. Placing them inside would create an infinite redirect loop because the two_factor middleware would block access to the very page needed to complete 2FA verification.Route Summary
| URL Pattern | Route File | Auth Required | Middleware |
|---|---|---|---|
/ | web.php | No | web |
/login | web.php | No (guest) | web, guest |
/panel-global/* | web.php | Yes | auth, two_factor, role:Super Admin |
/{tenantId}/login | tenant.php | No | web, InitializeTenancyByPath |
/{tenantId}/two-factor/challenge | tenant.php | Auth only | web, InitializeTenancyByPath, auth |
/{tenantId}/dashboard | tenant.php | Yes | …, auth, two_factor, check_inactivity |
/{tenantId}/usuarios | tenant.php | Yes + role | …, auth, two_factor, role:Administrador Clinica |
Middleware Reference
The following custom and aliased middleware are registered inbootstrap/app.php:
| Alias | Class | Purpose |
|---|---|---|
two_factor | App\Http\Middleware\EnsureTwoFactorAuthenticated | Blocks requests if 2FA challenge not yet passed |
check_inactivity | App\Http\Middleware\CheckInactivity | Logs out sessions idle beyond tenant threshold |
role | Spatie\Permission\Middleware\RoleMiddleware | Restricts access to users with a specific role |
permission | Spatie\Permission\Middleware\PermissionMiddleware | Restricts access to users with a specific permission |
role_or_permission | Spatie\Permission\Middleware\RoleOrPermissionMiddleware | Combines role and permission checks |
InitializeTenancyByPath is given the highest priority in the middleware stack so it always runs before session, CSRF, and auth middleware — ensuring the correct tenant database is active when those layers execute.
Frontend Architecture
The frontend is a React 18 SPA driven by Inertia.js 2. There is no separate API layer — controllers returnInertia::render() calls which Inertia serialises as JSON for client-side navigation and as full HTML for the first page load.
postcss.config.js) with a standard tailwind.config.js configuration file. Key frontend packages:
| Package | Version | Role |
|---|---|---|
react / react-dom | ^18.2 | UI rendering |
@inertiajs/react | ^2.0 | Client-side routing and server-driven page props |
tailwindcss | ^3.2 | Utility-first CSS (PostCSS integration) |
@tailwindcss/forms | ^0.5 | Form styling plugin for Tailwind CSS |
@headlessui/react | ^2.0 | Accessible UI primitives (modals, dropdowns, etc.) |
tightenco/ziggy | ^2.0 | Named Laravel routes available in JavaScript |
qrcode / jsqr | ^1.x | QR code generation and scanning for 2FA setup |
axios | ^1.11 | HTTP client for non-Inertia requests (CSRF, etc.) |
laravel-vite-plugin) handles asset bundling and Hot Module Replacement. The stancl/tenancy ViteBundler feature (enabled in config/tenancy.php) ensures Vite’s asset manifest is resolved correctly inside tenant contexts.
Queue Worker Requirement
Several operations in XHealtXperience are dispatched asynchronously to the database queue:- Tenant database creation and initial migration when a new clinic is provisioned
- Any
QueueTenancyBootstrapper-managed jobs that must run inside a specific tenant context
Tenancy Bootstrappers
WhenInitializeTenancyByPath initialises a tenant, it fires the bootstrappers listed in config/tenancy.php. Each bootstrapper makes a Laravel feature tenant-aware for the duration of that request:
| Bootstrapper | Effect |
|---|---|
DatabaseTenancyBootstrapper | Switches the default DB connection to the tenant’s database |
CacheTenancyBootstrapper | Prefixes all cache keys with tenant{id} to prevent cross-tenant leaks |
FilesystemTenancyBootstrapper | Scopes storage_path() and disk roots to storage/app/tenantXXX/ |
QueueTenancyBootstrapper | Injects tenant context into queued jobs so they run in the right DB |
Explore Further
Tenancy Overview
Detailed guide to tenant lifecycle management: provisioning, migrations, seeding, and deletion.
Local Development Setup
Advanced local configuration: MySQL instead of SQLite, environment variables, and debugging tools.