# Prompt — Module « Pilotage & Analytics » pour Efektiv Académie

*À coller tel quel dans le cahier des charges d'évolution globale, ou à donner à l'équipe dev / à un assistant IA de développement. Adapter les sections entre [crochets] si besoin.*

---

## Contexte technique (existant, à ne pas casser)

Tu interviens sur le LMS **Efektiv Académie** :
- **Backend** : Laravel 12, MySQL, authentification API stateless via Sanctum (`personal_access_tokens`), rôles Spatie (`admin`, `manager`, `manager_general`, `director`, `sub_manager`, `teacher`, `learner`) avec périmètre hiérarchique par `users.parent_id`.
- **Frontend** : SPA React (Vite), repo séparé, API consommée via token Bearer stocké en localStorage.
- **Modèle pédagogique** : `learning_paths` → `courses` (= modules) → `sections` → `lessons` + `quizzes` (→ `questions`/`options`). Inscriptions dans `enrollments`, complétions dans `completed_courses` (avec `completed_at` et certificat), validations de leçons dans `learner_lessons` (une ligne par couple apprenant×leçon, créée au clic « Terminer la leçon »), scores dans `quiz_scores` (⚠ dernière tentative uniquement, les précédentes sont supprimées).
- **Ressources téléchargeables** : `course_files` (upload admin), aujourd'hui servies en **lien statique public** (`/storage/course_resources/...`) sans passer par Laravel.

## Objectif

Doter la plateforme d'un **module de pilotage** répondant nominativement et par module aux questions : qui consulte quoi, quand, à quelle fréquence (one-shot vs récurrent), qui télécharge quels documents, avec quels résultats (quiz, complétions) — sans dégrader l'expérience apprenant ni les performances.

## Lot 1 — Collecte d'événements (socle, priorité absolue)

Créer une journalisation d'événements d'usage, nominatifs et horodatés :

1. **Table `lesson_views`** : `id, user_id (FK users), lesson_id (FK lessons), course_id (FK courses, dénormalisé), viewed_at, duration_seconds (nullable), source (web|mobile, nullable)`. Index `(course_id, viewed_at)` et `(user_id, viewed_at)`.
   - Écrite **à l'ouverture** de la leçon côté front (montage du composant de lecture), via `POST /api/track/lesson-view` — insertion systématique (pas d'updateOrCreate : on veut l'historique).
   - `duration_seconds` renseignée en fin de lecture (heartbeat ou beacon à la fermeture — `navigator.sendBeacon`), en best effort.
   - Anti-bruit : côté back, ignorer les vues du même user×lesson espacées de moins de [30] secondes.
2. **Table `resource_downloads`** : `id, user_id, course_file_id (FK), course_id, downloaded_at, kind (resource|certificate)`.
   - **Remplacer les liens statiques** par une route authentifiée `GET /api/course/file/{id}/download` qui journalise puis renvoie le fichier (`Storage::download` ou X-Sendfile). Mettre à jour le front (les 3 endroits qui construisent `VITE_APP_MEDIA_URL + stored_path`). Bloquer l'accès direct au dossier `storage/course_resources` (déplacer hors du disque `public` ou règle serveur).
   - Journaliser aussi les téléchargements de certificats dans la même table (`kind=certificate`), dans la route existante `/learner/certificates/download/{id}`.
3. **Table `login_events`** : `id, user_id, logged_at, ip_truncated, user_agent` — alimentée au login (là où le token Sanctum est créé). Ne pas dépendre des tokens (supprimés au logout).
4. **Historique des quiz** : nouvelle table `quiz_attempts` (`id, user_id, quiz_id, attempt_no, score_percent, submitted_at`) alimentée à chaque soumission, **sans supprimer** l'existant `quiz_scores` (qui reste la « dernière tentative » pour la rétro-compat).
5. **Exposer `completed_at`** : ajouter la date de complétion dans la réponse de `GET /manager/learner/detail/{id}` et dans l'export XLSX `POST /manager/users/export`.

Contraintes : écritures non bloquantes (queue Laravel si nécessaire), aucune régression sur les parcours actuels, migrations réversibles, seeds de démo, tests (unitaires sur la journalisation, feature sur la route de download authentifiée : un download crée exactement une ligne et renvoie bien le fichier).

## Lot 2 — Restitution (dashboards + exports)

Écrans d'administration (visibles admin ; manager/directeur/sub-manager restreints à leur périmètre `parent_id`) :

1. **Vue « Modules »** : tableau triable/filtrable par période — pour chaque module : inscrits, ayant démarré, ayant terminé, taux de complétion, **vues de leçons**, **apprenants uniques actifs**, temps moyen passé, téléchargements de ressources, score quiz moyen. Funnel inscrit → démarré → terminé.
2. **Vue « Apprenants » (nominatif)** : pour chaque apprenant — modules affectés/démarrés/terminés, certificats, dernière connexion, nombre de connexions, **jours actifs distincts**, et un badge de profil calculé : `Jamais actif` / `One-shot` (1 jour actif) / `Occasionnel` (2-3 jours) / `Récurrent` (≥ 4 jours) [seuils à valider]. Timeline d'activité individuelle (vues, quiz, complétions, téléchargements).
3. **Vue « Documents »** : téléchargements par fichier et par apprenant, avec dates.
4. **Exports** : chaque vue exportable en XLSX/CSV avec les filtres actifs (période, module, parcours, organisation/périmètre, profil d'usage).
5. **Définitions de KPI à figer** (les implémenter exactement ainsi) :
   - *Démarré* = ≥ 1 `lesson_view` OU ≥ 1 `learner_lesson` OU ≥ 1 tentative de quiz sur le module.
   - *Récurrence* = nombre de **jours calendaires distincts** avec au moins un événement d'usage.
   - *Taux de complétion* = apprenants ayant terminé ÷ apprenants inscrits (période et périmètre appliqués aux deux termes).
   - Les compteurs excluent par défaut les comptes de rôle ≠ learner et les comptes marqués « test » [liste à fournir].

## Lot 3 — Corrections des compteurs existants (dans la même vague)

- `DashBoardController::index` (mode `course_id`) : transmettre le périmètre managérial à `getSingleCourseStats` (aujourd'hui ignoré — signature à un seul paramètre).
- `CourseCompleRation` : rétablir la déduplication (`unique()` commentés) pour ne plus compter un apprenant N fois.
- `sidebarStatics` : ajouter les `break` manquants (`sub_manager`, `manager_general` tombent dans `director`).
- `EnrollmentController` : supprimer l'écriture vers `enrollments.is_complete` (colonne inexistante) ou créer la colonne.
- `users.is_active` : créer la migration manquante (colonne actuellement hors migrations).
- `UserController::index` : respecter `per_page` (pagination figée à 20 aujourd'hui).

## Exigences transverses

- **RGPD** : IP tronquée (/24) ou hashée dans `login_events` ; durée de rétention paramétrable [par défaut 24 mois] avec purge planifiée ; mention dans la politique de confidentialité ; accès aux données nominatives limité aux rôles habilités ; export/suppression sur demande (droit d'accès/effacement) couvrant les nouvelles tables.
- **Performance** : volumétrie cible [500-5 000] apprenants ; index cités ; pas de requête N+1 dans les dashboards (agrégations SQL, pas de boucles PHP) ; pagination systématique.
- **Rétro-compatibilité** : aucun changement de comportement pour l'apprenant hormis le lien de téléchargement ; les YAML/flux existants et les écrans actuels continuent de fonctionner pendant la transition.
- **Livrables** : migrations + modèles + routes + contrôleurs testés ; écrans React ; documentation courte (schéma des tables, définitions des KPI, guide d'usage des dashboards) ; jeu de données de démonstration.
- **Recette** : scénario bout-en-bout — un apprenant test ouvre 3 leçons sur 2 jours, télécharge 1 ressource, passe 2 fois un quiz, termine 1 module → chaque dashboard reflète exactement ces événements (2 jours actifs → profil « Occasionnel », 3 vues, 1 téléchargement, 2 tentatives, 1 complétion datée).

---

*Note d'arbitrage : si le budget impose de phaser, le Lot 1 seul a déjà de la valeur (les données s'accumulent dès la mise en prod, même si la restitution vient plus tard). L'ordre inverse n'a aucun sens : sans collecte, rien à restituer.*
