'array', ]; public function actor(): BelongsTo { return $this->belongsTo(User::class, 'actor_id'); } /** * Listado paginado y filtrable — compartido por Admin\AuditLogController * (sesión) y Api\AuditLogController (token), igual que AnalyticsSummary. */ public static function paginatedFor(Request $request) { $perPage = min(max((int) $request->integer('per_page', 25), 5), 100); return self::query() ->with('actor:id,name,email') ->when($request->filled('action'), fn ($q) => $q->where('action', $request->string('action'))) ->when($request->filled('subject_type'), fn ($q) => $q->where('subject_type', $request->string('subject_type'))) ->latest('created_at') ->paginate($perPage) ->through(fn ($log) => [ 'id' => $log->id, 'actor_name' => $log->actor?->name, 'actor_email' => $log->actor?->email, 'action' => $log->action, 'subject_type' => $log->subject_type, 'subject_id' => $log->subject_id, 'subject_label' => $log->subject_label, 'metadata' => $log->metadata, 'ip_address' => $log->ip_address, 'created_at' => $log->created_at, ]); } /** * Registra un evento de auditoría. $subjectId se castea a string a propósito: * cubre tanto ids autoincrementales (User) como uuid (Client) sin necesitar * una relación polimórfica real (que mezclaría tipos de clave entre modelos). */ public static function record( Request $request, string $action, string $subjectType, int|string $subjectId, ?string $subjectLabel = null, ?array $metadata = null, ): void { self::create([ 'actor_id' => $request->user()?->id, 'action' => $action, 'subject_type' => $subjectType, 'subject_id' => (string) $subjectId, 'subject_label' => $subjectLabel, 'metadata' => $metadata, 'ip_address' => $request->ip(), 'user_agent' => $request->userAgent(), 'created_at' => now(), ]); } }