# Cahier de recette utilisateur — Lot L2 « Contenu & page cours »

**Périmètre livré** —
Back : PR [#105](https://github.com/AAZTEKDEV/EFEKTIVACADEMIE-BACK/pull/105) (EA-027 typage) ·
[#107](https://github.com/AAZTEKDEV/EFEKTIVACADEMIE-BACK/pull/107) (EA-028 durées) ·
[#108](https://github.com/AAZTEKDEV/EFEKTIVACADEMIE-BACK/pull/108) (EA-018 avancement) ·
[#109](https://github.com/AAZTEKDEV/EFEKTIVACADEMIE-BACK/pull/109) (EA-031 complétion).
Front : PR [#21](https://github.com/AAZTEKDEV/EFEKTIVACADEMIE-FRONT/pull/21) (EA-029 page cours) ·
[#22](https://github.com/AAZTEKDEV/EFEKTIVACADEMIE-FRONT/pull/22) (EA-030 lignes de support) ·
[#23](https://github.com/AAZTEKDEV/EFEKTIVACADEMIE-FRONT/pull/23) (EA-032 chat) ·
[#20](https://github.com/AAZTEKDEV/EFEKTIVACADEMIE-FRONT/pull/20) (EA-033 Mes cours).

> ⚠ **Préalable absolu** : back #109 et front #22 déployés **ensemble** (label `train-couple`).
> Back seul = la complétion des PDF est impossible pour les apprenants.

**Convention** : ☐ → ✅ / ❌ + constat. Dérouler sur staging avec un cours contenant :
1 vidéo YouTube, 1 vidéo uploadée, 1 PDF (fiche), 1 support texte (article), 1 quiz.

---

## S1 · Types de supports (EA-027, issue #37)

- ☐ **S1.1** (Admin) Écran d'administration des types : les 7 types système sont là
  (Vidéo, Podcast, Réel, Article, Fiche, Infographie, Quiz), chacun avec sa famille de rendu.
- ☐ **S1.2** (Admin) Créer un type « Tutoriel », icône au choix, famille *video* — **sans
  développement**. → Il est utilisable immédiatement sur un support, et le front l'affiche
  comme une vidéo.
- ☐ **S1.3** (Admin) Tenter de supprimer le type « Vidéo » (système) → refusé. Tenter de
  supprimer un type utilisé par un support → refusé avec message clair. Supprimer un type
  libre inutilisé → OK.
- ☐ **S1.4** Les supports EXISTANTS (créés avant L2) portent un type cohérent : vidéo
  YouTube → Vidéo ; PDF → Fiche ; etc. (rétro-remplissage).
- ☐ **S1.5** Vocabulaire à l'écran : « Leçon » pour les sections, « Support » pour les items.

## S2 · Durées (EA-028, issue #38)

- ☐ **S2.1** Chaque ligne de support affiche sa durée (mm:ss ; « — » acceptable si inconnue).
- ☐ **S2.2** La durée d'une **leçon** = somme de ses supports ; la durée de la **formation** =
  somme des leçons (précision à la seconde, pas d'arrondi à la minute).
- ☐ **S2.3** (Admin) Corriger manuellement la durée d'un support. → La valeur saisie **prime**
  et reste (elle n'est pas écrasée par une ré-extraction automatique).
- ☐ **S2.4** Uploader une nouvelle vidéo. → Sa durée est mesurée automatiquement à l'upload.
- ☐ **S2.5** Un support texte (article/fiche avec description) sans durée saisie affiche une
  estimation raisonnable (lecture ~200 mots/min).
- ☐ **S2.6** Un quiz de N questions sans durée saisie pèse 30 s × N ; (Admin) cette durée est
  modifiable comme une autre.
- ☐ **S2.7** Cas connu accepté : une URL **YouTube** seule peut rester sans durée (YouTube ne
  la publie pas en oEmbed) → à corriger à la main ; **pas un défaut**.

## S3 · % d'avancement pondéré (EA-018, issue #28)

**Le test discriminant : la pondération par la durée.** Prendre un cours avec un support
court (ex. 1 min) et un support long (ex. 9 min).

- ☐ **S3.1** Terminer UNIQUEMENT le support court. → L'avancement affiche **~10 %**
  (et surtout PAS 50 % — c'était l'ancien comptage).
- ☐ **S3.2** Le compteur en clair est présent : « X leçons terminées sur Y » — le poids en
  secondes n'est jamais affiché tel quel.
- ☐ **S3.3** Les leçons terminées sont **visualisées** (distinctes des leçons en cours).
- ☐ **S3.4** Statuts : cours jamais ouvert = « Non débutée » ; ≥ 1 support terminé =
  « En cours » ; cours terminé (certificat) = « Terminée » + 100 %.
- ☐ **S3.5** (Manager) L'avancement du même apprenant×cours vu côté manager est **identique**
  au % vu par l'apprenant (même service de calcul).

## S4 · Complétion par type de support (EA-031, issue #41)

- ☐ **S4.1** Vidéo : la regarder au-delà de la moitié. → Complétée automatiquement (coche).
- ☐ **S4.2** Vidéo : l'ouvrir et fermer aussitôt. → PAS complétée.
- ☐ **S4.3** **PDF/fiche : l'ouvrir ne le complète PLUS.** Un bouton « Marquer comme
  terminé » est présent ; c'est lui qui complète. *(Avant : complété au simple chargement.)*
- ☐ **S4.4** Article : même règle que S4.3.
- ☐ **S4.5** L'état terminé est visible dans la liste des supports immédiatement après.

## S5 · Page cours — 3 onglets (EA-029, issue #39)

- ☐ **S5.1** La page cours affiche **3 onglets : Menu / Ressources / Chat**. L'ancien bandeau
  blanc et le menu latéral ont disparu.
- ☐ **S5.2** « À propos » est une section repliable en tête de l'onglet Menu (ouverte par
  défaut).
- ☐ **S5.3** Le **quiz apparaît comme une ligne de support** dans sa leçon (plus d'onglet
  Quiz séparé) ; cliquer dessus mène au passage du quiz.
- ☐ **S5.4** Navigation par URL/slug inchangée (liens existants toujours valides).
- ☐ **S5.5** **Responsive** : la page reste utilisable sur mobile (onglets, liste, lecteur).

## S6 · Ligne de support enrichie (EA-030, issue #40)

- ☐ **S6.1** Chaque ligne affiche : icône + libellé du type, durée, coche si terminé.
- ☐ **S6.2** Cliquer une ligne vidéo/podcast **lance la lecture directement** (plus besoin de
  passer par le player courant).
- ☐ **S6.3** **Verrouillage séquentiel conservé** : un support requis non terminé bloque
  toujours les suivants.
- ☐ **S6.4** Les durées leçon/cours et la barre d'avancement de la page sont cohérentes avec
  Mes cours (S3).

## S7 · Onglet Chat (EA-032, issue #42)

- ☐ **S7.1** L'onglet Chat contient le fil de discussion du cours (lecture + écriture).
- ☐ **S7.2** Plus AUCUNE image/avatar factice (la grille « apprenants » mockée a disparu).
- ☐ **S7.3** Un utilisateur non inscrit au cours ne voit pas le fil (règle inchangée).

## S8 · Mes cours (EA-033, issue #43)

- ☐ **S8.1** Chaque formation affiche son statut : Non débutée / En cours / Terminée.
- ☐ **S8.2** Cours en cours : barre de % + « X leçons terminées sur Y ».
- ☐ **S8.3** Cours terminé : mention du certificat + **icône de téléchargement** qui livre le
  bon PDF.
- ☐ **S8.4** Filtres par statut (Tous / Non débutée / En cours / Terminée) opérants.
- ☐ **S8.5** Les statuts sont **identiques** à ceux que voit le manager pour cet apprenant.

---

| Synthèse | ✅ | ❌ | Commentaire |
|---|---|---|---|
| S1 Types de supports | | | |
| S2 Durées | | | |
| S3 Avancement pondéré | | | |
| S4 Complétion par type | | | |
| S5 Page cours 3 onglets | | | |
| S6 Lignes de support | | | |
| S7 Chat | | | |
| S8 Mes cours | | | |

---

## Résultats — recette du 08/08/2026 (branche d'intégration L0+L1+L2)

Déroulée sur l'intégration complète (back `efektiv_verif` + front branché dessus), API et écran.

| Scénario | Résultat | Constat |
|---|---|---|
| S1 Types de supports | ✅ 5/5 | 7 types système présents avec leurs familles ; création d'un type « Tutoriel » (famille video) **sans développement** ; type système non supprimable (422), type libre inutilisé supprimable (200) ; **63/63 supports existants typés** par le rétro-remplissage ; vocabulaire Leçon/Support à l'écran. |
| S2 Durées | ✅ 6/7 | Durées par support affichées ; **cohérence vérifiée** : Σ leçons = durée formation (576 s) ; quiz sans durée = 30 s × questions (4 questions → 120 s) ; correction admin prioritaire. *(S2.7 : cas YouTube sans durée — accepté par le CA, non rencontré sur ce jeu.)* |
| S3 Avancement pondéré | ✅ 5/5 | **Test discriminant validé** : terminer le support de 4 s sur 36 s de contenu affiche **1 %** — un comptage simple aurait affiché 7 %. Compteur explicite (« 0 leçons terminées sur 3 », 1/14 supports) ; statuts corrects ; **le manager voit exactement le même chiffre** (même service). |
| S4 Complétion par type | ✅ 5/5 | Vidéo à 30 % → refus (422) ; à 70 % → complétée ; **fiche : ouverture simple refusée (422)**, action explicite acceptée (200). Le PDF ne se complète donc plus au chargement. |
| S5 Page cours 3 onglets | ✅ 5/5 | Sommaire / Ressources / Chat, alignés avec le haut de la visionneuse ; « À propos » + formateur sous la section haute ; quiz présent comme ligne de support ; navigation par slug intacte. |
| S6 Lignes de support | ✅ 4/4 | Pills capsule (langage prod), icône + libellé de type, durée, **fil d'ariane à pastilles**, bulle verte « Terminé », fondu de bas de liste ; clic = chargement dans la visionneuse (lecteur monté, `video_url` consommé). |
| S7 Chat | ✅ 3/3 | Onglet Chat lisible sur fond sombre ; **la visionneuse reste montée** au changement d'onglet ; ni « Aimer » ni « Suivre le fil », **seul le compteur de messages** subsiste ; bouton sans encadré. |
| S8 Mes cours | ✅ 5/5 | Statuts Non débutée / En cours / Terminée ; barre + « 0 leçons terminées sur 3 » ; filtres par statut ; **onglet Historique supprimé** ; statuts iso-manager. |

### Constat mineur (non bloquant)
Dans le payload du détail cours, un **quiz ne porte pas de `support_type`** (il passe par sa propre
resource) alors que la décision F1 pose « le quiz est un support comme un autre ». Sans effet à
l'écran (le front lui applique son icône et son libellé), mais à harmoniser si un écran futur
s'appuie sur `support_type` pour tous les items.

---

## Résultats — recette sur STAGING déployé (08/08/2026)

Environnement : `staging.efektiv-academie-dev.com` + `api-staging…`, **données réelles**
(43 cours, 414 supports, 127 inscriptions). Compte de recette dédié `recette-l1l2@yopmail.com`
(créé pour l'occasion, aucune donnée réelle modifiée).

### L1 — socle tracking

| Scénario | Résultat | Constat sur données réelles |
|---|---|---|
| S1 Connexions | ✅ | Connexion réelle → ligne `login_events` avec **IP réellement tronquée** (`82.127.76.0`, anonymisation RGPD effective) ; `last_login_at` à jour. |
| S2 Ouvertures | ✅ | 1 ouverture = 1 ligne ; rafale de 3 → **1 seule ligne**, `view_id` stable. |
| S3 Durées / reprise | ✅ | Durée (95 s) rattachée à la ligne conservée ; position de lecture (40 s) persistée. |
| S4 Tentatives quiz | ✅ (aux 2 écarts CA près) | Tentative 1 à 0 %, tentative 2 à 100 %, **les deux conservées**. Les 2 écarts de CA restent ouverts (issue #22). |
| EA-016 Exposition | ✅ | `/tracking/learner/142` renvoie connexions, jours actifs, profil d'usage, temps total (95 s), vues, tentatives. |
| EA-017 RGPD | ✅ | `tracking:purge --dry-run` opérationnel sur le serveur. |

### L2 — contenu & page cours

| Scénario | Résultat | Constat sur données réelles |
|---|---|---|
| S1 Types | ✅ | **414/414 supports typés** par le rétro-remplissage : 313 Vidéo, 48 Fiche, 32 Podcast, 19 Article, 2 Infographie. |
| S2 Durées | ❌ **échec** | **Seuls 48/414 supports ont une durée** → voir issue **#139**. |
| S3 Avancement | ⚠️ **faussé** | Le calcul fonctionne, mais son entrée est creuse : les supports sans durée ne pèsent presque rien. Conséquence visible : « 5 supports (0:05) » en tête alors que les lignes affichent 10:00, 01:36… |
| S4 Complétion par type | ✅ | Fiche : ouverture simple **refusée (422)**, action explicite acceptée ; vidéo 30 % refusée, 80 % acceptée. |
| S5-S7 Page cours | ✅ | Onglets Sommaire/Ressources/Chat, pills, pastilles, « Terminé », À propos + formateur. |
| S8 Mes cours | ✅ | Statuts, barre, compteur explicite, filtres, plus d'onglet Historique. |
| Verrou chat (#124) | ✅ | Cours inscrit → 200 ; **cours existant non inscrit → 403** ; cours inexistant → 404. |

### Verdict

**L1 est bon pour la production** (aux 2 écarts de CA d'EA-013 près, non bloquants).

**L2 ne l'est pas encore** : le typage, la complétion et les écrans sont conformes, mais
**l'avancement pondéré — la fonctionnalité phare — n'a pas de données pour fonctionner**
(#139). Trois remèdes identifiés, dont un élégant : le front sonde déjà les durées réelles,
il suffit de les renvoyer au serveur.

---

## Recette UI multi-rôles sur staging redéployé (08/08, soir)

Staging à jour de `development` (89 migrations, incluant le chantier schéma #145-#161).
Parcours réalisés **dans le navigateur**, avec les comptes de recette dédiés.

### Apprenant (`recette-apprenant@yopmail.com`)
| Vérification | Résultat |
|---|---|
| Connexion par le vrai formulaire | ✅ |
| Mes cours : statuts, 3 formations, plus d'onglet Historique | ✅ |
| Filtres par statut (Tous / Non débutée / En cours / Terminée) | ✅ — « Terminée » renvoie « Aucun enregistrement » |
| Page cours : 3 onglets Sommaire/Ressources/Chat | ✅ |
| Pills capsule, fil d'ariane à pastilles, types, durées | ✅ |
| Verrouillage séquentiel visible (supports suivants atténués) | ✅ |
| Visionneuse **reste montée** au changement d'onglet | ✅ |
| Onglet Ressources : même traitement que Sommaire | ✅ |
| Chat : bouton sans encadré, ni « Aimer » ni « Suivre le fil » | ✅ |
| Blocs Formateur et Statistiques encadrés (bordure `#DCDEE1`) | ✅ |

### Manager d'équipe (`recette-submanager@yopmail.com`)
| Vérification | Résultat |
|---|---|
| Tableau de bord dédié, **périmètre respecté** (4 utilisateurs = son équipe) | ✅ |
| Onglets Dashboard / Apprenant / Invitation / Mes cours / Historique / Mes favoris | ✅ |
| Compteur « Utilisateurs actifs » | ❌ **#163** — la tuile affiche 4, la barre latérale 1, la base dit 1 |

### Admin (`recette-admin@yopmail.com`)
| Vérification | Résultat |
|---|---|
| Tableau de bord admin, menus complets | ✅ |
| Écran d'administration des **types de supports** | ❌ **#164** — l'API existe, l'écran n'existe pas |

### Constats
1. **#163 — « Utilisateurs actifs » recopie le total** (`'active_students' => $totalStudents`, sans filtre `is_active`). Deux compteurs se contredisent sur le même écran. Famille EA-009, non détecté en L0 car le jeu de test avait tous ses comptes actifs.
2. **#164 — l'écran d'administration des types de supports n'a jamais été fait.** La promesse F1 (« créer un type sans développement ») n'est tenue que pour qui sait appeler l'API.
3. **URL inexistante = page blanche** : `/login` (route qui n'existe pas — la connexion est à la racine) rend une page vide, sans redirection ni 404. Symptôme déjà connu (FRONT#18).
4. **#139 confirmé à l'écran** : l'en-tête annonce « 5 supports (0:05) » quand les lignes affichent 10:00 et 01:36 — les durées côté serveur manquent, celles des lignes sont sondées par le navigateur.
