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 maintains a per-tenant audit log — referred to internally as the bitácora — that records every significant action performed within a clinic. Each tenant database hosts its own dedicated audit_logs table, so one clinic’s activity is completely isolated from another’s. The log is append-only (no update or delete operations) and captures the actor, the target, the action type, a human-readable description, supporting metadata, and the originating IP address and user agent. The audit log serves two audiences: Administrador Clinica staff who need operational visibility, and Auditor role users who provide independent oversight of security-relevant events.

Database Schema

The audit_logs table is created by a tenant migration (2025_07_01_000001_create_audit_logs_table.php) that runs once per new clinic:
ColumnTypeDescription
idbigintAuto-incrementing primary key
user_idbigint|nullID of the actor (best-effort; actor may be from the central DB)
user_namestring|nullName snapshot of the actor at the time of the event
user_rolestring|nullRole snapshot of the actor at the time of the event
target_user_idbigint|nullID of the user the action was performed on, if applicable
target_user_namestring|nullName snapshot of the target user
actionstring(100)Machine-readable action slug, indexed. E.g. auth.login_failed
descriptionstringHuman-readable description of what happened
metadatajson|nullOptional structured data providing extra context
ip_addressstring(45)|nullIPv4 or IPv6 address of the request
user_agentstring|nullBrowser/client user agent string (truncated to 255 chars)
created_attimestampWhen the event was recorded (auto-set to current time)
user_id is stored as a plain unsignedBigInteger without a foreign key constraint. This is intentional: the actor may be the Super Admin, whose user record lives in the central database and does not exist in the tenant’s users table. The text snapshot fields user_name and user_role are the authoritative source of truth for display purposes.

The AuditLogService

All audit entries are created through a single central method: AuditLogService::log(). Controllers and other services should always use this method rather than creating AuditLog model instances directly.

Method Signature

AuditLogService::log(
    string $action,
    string $description,
    ?array $metadata = null,
    $targetUser = null,
    $actor = null,
    ?string $actorRoleName = null,
): void

Parameters

action
string
required
A dot-namespaced slug identifying the type of event. Use a consistent naming convention such as {resource}.{verb}. Examples from the codebase: user.created, auth.account_unlocked, two_factor.enabled, two_factor.disabled.
description
string
required
A human-readable sentence describing exactly what happened. Should be understandable without additional context. Example: "María López desbloqueó la cuenta de Juan Pérez".
metadata
array | null
Optional associative array of structured data providing additional context. Cast to JSON in the database. Use for machine-readable details that complement the description, such as changed field values or IDs.
targetUser
object | null
The user the action was performed on, if applicable. Must have id and name properties. Stored as target_user_id and target_user_name.
actor
object | null
The user who performed the action. Defaults to auth()->user() when omitted. Override this parameter when the acting user is not the currently authenticated user (e.g., system-initiated events).
actorRoleName
string | null
The role name of the actor, if already resolved. Providing this avoids an extra getRoleNames() query. Useful when the actor comes from the central database (e.g., during a Super Admin action that somehow reaches a tenant context).

Usage Examples

Logging a User Creation

// From UserController::store()
AuditLogService::log(
    action: 'user.created',
    description: auth()->user()->name . " creó la cuenta de {$user->name} con rol {$validated['role']}",
    targetUser: $user,
);

Logging an Account Unlock

// From UserController::desbloquear()
AuditLogService::log(
    action: 'auth.account_unlocked',
    description: auth()->user()->name . " desbloqueó la cuenta de {$usuario->name}",
    targetUser: $usuario,
);

Logging a 2FA State Change

// From TwoFactorController::confirm()
$method = $user->two_factor_method === 'email' ? 'correo electrónico' : 'Google Authenticator';

AuditLogService::log(
    action: 'two_factor.enabled',
    description: "{$user->name} activó su autenticación de dos factores vía {$method}",
    targetUser: $user,
);

Logging with Metadata

// Example: logging a settings change with before/after values
AuditLogService::log(
    action: 'settings.updated',
    description: auth()->user()->name . ' actualizó la configuración de seguridad',
    metadata: [
        'inactivity_timeout_minutes' => ['before' => 15, 'after' => 30],
        'max_login_attempts'         => ['before' => 5,  'after' => 3],
    ],
);
The metadata field is stored as a plain JSON blob with no encryption. Never log sensitive data such as passwords, TOTP secrets, raw OTP codes, payment card numbers, or any other credentials in this field. Use it only for non-sensitive contextual information like IDs, counts, status labels, and before/after values of non-sensitive settings.

Super Admin Actions Are Not Logged

AuditLogService::log() begins with a tenancy guard:
if (! tenancy()->initialized) {
    return; // silently skip
}
When the Super Admin performs actions from the central panel (/panel-global/...), tenancy is not initialized — there is no tenant context and no audit_logs table to write to. These calls return silently without throwing an exception. This is by design: the per-clinic audit log is a tenant-level concept. If your application later requires central-level auditing of Super Admin actions, a separate central audit_logs table and logging path would need to be introduced.

Viewing the Audit Log

Administrador Clinica and Auditor — Full Log

The full paginated audit log is accessible at:
GET /{tenant}/bitacora
Access is restricted to users with either the Administrador Clinica or Auditor role via the middleware:
Route::middleware(['auth', 'two_factor', 'check_inactivity', 'role:Administrador Clinica|Auditor'])
    ->group(function () {
        Route::get('/bitacora', [AuditLogController::class, 'index']);
    });
The log view is paginated (30 entries per page) and supports the following query-string filters:
ParameterTypeDescription
accionstringFilter by exact action slug (e.g. two_factor.disabled)
usuariostringCase-insensitive partial match on user_name
desdedateShow only entries on or after this date (Y-m-d)
hastadateShow only entries on or before this date (Y-m-d)
Example filtered request:
GET /{tenant}/bitacora?accion=auth.login_failed&desde=2025-07-01&hasta=2025-07-31

Auditor — Security Dashboard

Users with the Auditor role also have access to a dedicated security dashboard that aggregates metrics and highlights suspicious activity:
GET /{tenant}/auditor/dashboard
This endpoint is restricted exclusively to the Auditor role:
Route::middleware(['auth', 'two_factor', 'check_inactivity', 'role:Auditor'])
    ->group(function () {
        Route::get('/auditor/dashboard', [AuditorController::class, 'dashboard']);
    });
The dashboard provides the following real-time statistics:

Eventos hoy

Total number of audit events recorded since midnight today.

Logins fallidos (7 días)

Count of auth.login_failed events in the past 7 days — a leading indicator of brute-force attempts.

Cuentas bloqueadas

Current number of user accounts with is_locked = true.

2FA desactivados (30 días)

Count of two_factor.disabled events in the past 30 days — useful for detecting social-engineering patterns.
The dashboard also displays the 8 most recent audit entries and a bar chart of the top 6 most frequent action slugs over the past 7 days.

Common Action Slugs

The following action slugs are used across the application. Use these exact values when filtering or building tooling against the audit log:
SlugTriggered by
user.createdUserController::store() — new user account created
auth.account_unlockedUserController::desbloquear() — admin unlocked a locked account
two_factor.enabledTwoFactorController::confirm() — user activated 2FA
two_factor.disabledTwoFactorController::disable() — user deactivated 2FA
auth.login_failedFailed login attempt (tracked for lockout and auditor dashboard)

Build docs developers (and LLMs) love