subDays($days - 1)->startOfDay(); return [ 'kpis' => [ 'total_users' => User::count(), 'active_sessions' => Token::query()->where('revoked', false)->where('expires_at', '>', now())->count(), 'logins_today' => LoginEvent::whereDate('created_at', today())->count(), 'logins_7d' => LoginEvent::where('created_at', '>=', now()->subDays(7))->count(), ], 'logins_by_day' => self::loginsByDay($since, $days), 'logins_by_app' => self::loginsByApp($since), 'top_users' => self::topUsers($since), 'recent' => self::recent(), ]; } private static function loginsByDay(Carbon $since, int $days): array { $rows = LoginEvent::query() ->selectRaw("date(created_at) as day, type, count(*) as count") ->where('created_at', '>=', $since) ->groupBy('day', 'type') ->get() ->groupBy('day'); $out = []; for ($i = 0; $i < $days; $i++) { $date = $since->copy()->addDays($i)->toDateString(); $dayRows = $rows->get($date, collect()); $out[] = [ 'date' => $date, 'session' => (int) $dayRows->firstWhere('type', 'session')?->count, 'oauth' => (int) $dayRows->firstWhere('type', 'oauth')?->count, ]; } return $out; } private static function loginsByApp(Carbon $since): array { return LoginEvent::query() ->join('oauth_clients', 'oauth_clients.id', '=', 'login_events.client_id') ->where('login_events.type', 'oauth') ->where('login_events.created_at', '>=', $since) ->groupBy('oauth_clients.id', 'oauth_clients.name') ->orderByDesc('count') ->limit(8) ->get(['oauth_clients.name as client_name', DB::raw('count(*) as count')]) ->toArray(); } private static function topUsers(Carbon $since): array { return LoginEvent::query() ->join('users', 'users.id', '=', 'login_events.user_id') ->where('login_events.created_at', '>=', $since) ->groupBy('users.id', 'users.name', 'users.email') ->orderByDesc('count') ->limit(8) ->get(['users.name', 'users.email', DB::raw('count(*) as count')]) ->toArray(); } private static function recent(int $limit = 15): array { return LoginEvent::with(['user:id,name,email', 'client:id,name']) ->latest('created_at') ->limit($limit) ->get() ->map(fn (LoginEvent $event) => [ 'user_name' => $event->user?->name, 'user_email' => $event->user?->email, // null para logins de sesión (el frontend lo traduce como "Panel admin"/"Admin // panel" según el idioma) y para apps borradas (el frontend traduce ese caso // también) — así el backend no filtra texto sin traducir a la UI. 'client_name' => $event->type === 'session' ? null : $event->client?->name, 'type' => $event->type, 'ip_address' => $event->ip_address, 'created_at' => $event->created_at, ]) ->toArray(); } }