# Plan actualisé — 10 août 2026

> Remplace [`08_PLAN_AOUT_V1_SEPTEMBRE.md`](./08_PLAN_AOUT_V1_SEPTEMBRE.md) pour les dates.
> Le périmètre V1 et les arbitrages de ce document restent valables ; **seul le calendrier
> était faux**, et il l'était de trois semaines.

---

## ⚠️ L4 N'EST PAS VALIDÉ (constat du 10/08, recette UI)

Le tableau ci-dessous disait « L4 : 18 issues en recette ». **C'était faux.** Quatre issues
avaient été marquées « en recette » sur la foi du back seul, sans que leur écran existe.

| Issue | Back | Écran | État réel |
|---|---|---|---|
| **#55** onboarding | livré | **absent** | 🔴 le lien du mail ouvre une page blanche — **aucun nouvel utilisateur ne peut entrer** |
| **#51** référentiels | livré | **absent** | référentiel impossible à remplir, tous les filtres Société restent vides |
| **#160** réaffectation | livré | **absent** | 14 apprenants sans manager, invisibles |
| **#183** délais | livré | **absent** | libellés en anglais, aucun champ de saisie |

Plus un défaut de navigation : les cinq écrans de pilotage n'ont **aucune barre**
(FRONT#53), donc pas de circulation entre eux.

**Ces quatre issues repassent en `statut: en dev` et restent dans L4.** Elles ne sont pas
reportées en L4.5 : ce sont des manques du lot en cours, pas de nouveaux besoins. Décaler
une omission vers le lot suivant permet de déclarer fini ce qui ne l'est pas — c'est
précisément le mécanisme à ne pas installer.

**Règle qui découle de cet épisode, et qui vaut pour tous les lots :**

> Une issue portant un besoin utilisateur n'est pas livrée quand son API répond. Elle est
> livrée quand quelqu'un peut s'en servir. Tant qu'aucun écran ne l'expose, elle reste
> `en dev`, quel que soit l'état du back et le nombre de tests verts.

Ce qui EST livré et vérifié à l'écran : les vagues C et D — les trois listes de
collaborateurs, le dashboard de pilotage, le cockpit, la fiche « formations suivies ».
Exercés écran par écran le 10/08, conformes aux maquettes validées.

---

## Où on en est réellement

| Lot | Date prévue au plan initial | Date réelle | Écart |
|---|---|---|---|
| L0 · Déblocage | 6 → 9 août | 6 → 8 août | conforme |
| L1 · Socle tracking | 10 → 16 août | **8 août** | 8 jours d'avance |
| L2 · Contenu & page cours | 17 → 23 août | **8 août** | 15 jours d'avance |
| L3 · Médias & listes | 24 → 30 août | **9 août** | 21 jours d'avance |
| L4 · Pilotage & organisation | 31 août → 6 sept | **en cours** | développement en avance, **4 écrans manquants** |

**14 des 18 issues de L4 sont en recette ; 4 sont repassées en dev, écran manquant.** Le cahier
[`RECETTE_L4.md`](./recette/RECETTE_L4.md) est écrit et attend d'être déroulé.

Autrement dit : la V1 était calée sur la première quinzaine de septembre, et le
développement des lots L0 à L4 est terminé le 10 août. **Il reste environ trois semaines
de marge avant la date d'engagement.**

## Ce que cette marge doit financer — et ce qu'elle ne doit pas

Cette avance n'est pas du temps libre. Elle a une affectation naturelle, dans cet ordre :

1. **Lever le risque de mise en production**, qui n'a pas bougé pendant que les lots
   avançaient. C'est le vrai chemin critique.
2. **Absorber le lot intermédiaire L4.5** (ci-dessous), qui rassemble ce qui doit être vrai
   avant que des utilisateurs réels arrivent.
3. **Dérouler les recettes** — L4 n'est pas encore exercée sur un serveur.

Ce qu'elle ne doit **pas** financer maintenant : l'optimisation du front. Elle ne vaut rien
tant que la plateforme n'est pas en production, et elle touche tous les écrans, donc elle
invaliderait une recette non encore jouée. Voir « Après la V1 » en fin de document.

---

## Le chemin critique : la mise en production

C'est le seul sujet qui n'a pas avancé au rythme des lots, et **rien ne part sans lui**.

| Issue | Sujet | État |
|---|---|---|
| **#156** | Répétition générale sur copie de `e-learning-prod` | **10-11/08 — maintenant** |
| **#157** | P0 · Les migrations L1/L2 ne s'appliquent pas : InnoDB ne peut pas référencer MyISAM | bloquant |
| **#136** | P0 · Base de production en MyISAM : les clés étrangères sont impossibles | bloquant |
| #137 | Migrations enregistrées comme appliquées alors qu'elles ne le sont pas | à faire |
| #139 | Durées absentes sur 366 des 414 supports : l'avancement pondéré est faussé | bloquant |
| #141 – #144 | Divergences de schéma entre la production et les migrations (D2–D11) | décisions |
| #181 | Bascule de la production vers notre git + protection de branche | à faire |
| **#152** | Garde-fou de suppression d'une sous-catégorie + `courses.subcategory_id` NOT NULL | **fenêtre — voir ci-dessous** |
| **#148** | Modèle des durées : colonnes « durée pédagogique » sur `courses` et `lessons` | **fenêtre — voir ci-dessous** |

### Deux sujets qui doivent être tranchés AVANT la conversion, pas après

Ils ne bloquent aucun utilisateur aujourd'hui. Ils sont pourtant ici, parce que la conversion
InnoDB fige ce qu'ils décident.

**#152 — suppression d'une sous-catégorie.** Aujourd'hui `SubcategoryController::destroy`
supprime **sans aucun contrôle** : en production MyISAM, où les clés étrangères n'existent pas,
les cours conservent une référence qui ne pointe plus sur rien. Pire qu'un `NULL`, puisque rien
ne le signale. Un administrateur ordinaire peut donc corrompre le catalogue d'un clic.

Surtout, l'issue impose que la clé `courses → subcategories` soit posée en `restrictOnDelete`
lors de la conversion, et **#136 doit être mise à jour en conséquence**. Poser cette clé en
`nullOnDelete` puis la corriger ensuite, c'est une seconde migration sur la base de production.

**#148 — modèle des durées.** La décision est **déjà prise** (08/08) ; ce qui reste est un
impact de schéma : un champ « durée pédagogique globale » sur `courses`, un champ optionnel sur
`lessons`. C'est aussi le modèle contre lequel **#139** — bloquant, 366 supports sur 414 sans
durée — doit être corrigé. Corriger #139 sans avoir arrêté le modèle, c'est risquer de remplir
366 lignes avec la mauvaise grandeur.

**Conclusion** : le volet schéma de #152 et #148 part avec la migration de production. Leurs
volets applicatifs (#153, affichage des durées) restent en L4.5.

Tant que #157 et #136 ne sont pas levés, **aucune date de V1 n'est tenable** — quel que soit
l'état du reste. C'est la première chose à regarder chaque matin.

---

## Calendrier actualisé

| Lot | Période | Contenu |
|---|---|---|
| **L0 → L4** | 6 → 10 août | ✅ développés, en recette |
| **Mise en production — préparation** | 10 → 13 août | #156, #157, #136, #137, #139, #141-144. Chemin critique, en parallèle du reste |
| **Recette L4** | 11 → 13 août | `RECETTE_L4.md`, 10 scénarios, profil par profil sur staging |
| **L4.5 · Sécurité, conformité et promesses tenues** | 14 → 27 août | voir ci-dessous |
| **L5 · Mise en production** | 28 août → 5 sept | recette complète, migration, bascule, surveillance. **Rien d'autre.** |
| **Marge** | 5 → 15 sept | ce qui déborde, et l'imprévu — il y en aura |

Deux changements de fond par rapport au plan initial :

- **L5 ne contient plus aucune fonctionnalité.** Le plan initial y mélangeait cinq
  fonctionnalités et la mise en production. Un lot qui livre et met en production en même
  temps ne sait plus, en cas d'incident, si la cause est le code neuf ou la bascule. Les cinq
  fonctionnalités descendent en L4.5, L5 devient **exclusivement** l'opération de production.
- **L4.5 existe.** Le plan initial n'avait pas de place pour la dette et les correctifs
  découverts en chemin. Ils s'accumulaient sans propriétaire de lot.

---

## L4.5 · Sécurité, conformité et promesses tenues

**Deux critères d'inclusion, à appliquer strictement.**

> **(A)** Entre dans L4.5 ce qui doit être vrai **avant que des utilisateurs réels arrivent**.
>
> **(B)** Entre aussi ce qui a une **fenêtre** : ce qui est peu coûteux maintenant et
> nettement plus coûteux après la migration de production.

Le premier critère écarte volontairement des choses qui font envie — sans lui, un lot
intermédiaire absorbe tout ce qui traîne et ne se termine jamais.

**Le second a été ajouté après coup, et il corrige une erreur de classement** (correction du
10/08). Le critère (A) seul ne regarde que les conséquences visibles par un utilisateur. Il
laisse donc passer les sujets qui touchent au **schéma** ou aux **clés étrangères** : ils ne
gênent personne aujourd'hui, mais la conversion InnoDB et la migration de production les
figent. Les traiter après, c'est une seconde migration sur une base de production fraîchement
convertie — l'opération qu'on cherche précisément à ne faire qu'une fois.

> **Note de lecture** — L4.5 est le nom du **lot**. Ce qui suit en sont les **blocs**,
> nommés par leur thème et non numérotés : « vague 4 » et « L4.5 » se ressemblaient trop pour
> qu'on sache de quoi on parlait.

### Bloc « Sécurité » — non négociable

| Issue | Sujet | Pourquoi avant la production |
|---|---|---|
| **#124** | Chat de cours : API ouverte à tout utilisateur authentifié, ni inscription ni périmètre vérifiés | N'importe quel compte lit les échanges de n'importe quelle formation. Inacceptable avec de vrais apprenants |
| **#115** | Un vieux token d'invitation ouvrait n'importe quel cours | Contournement du périmètre par un lien périmé |
| **#16** | EA-007 · Retrait de la visio ZegoCloud (secret exposé) | Un secret exposé dans un bundle public. Retrait, pas remplacement |

### Bloc « Conformité » — gouvernance des données

| Issue | Sujet | Pourquoi avant la production |
|---|---|---|
| **#186** | Journal d'audit des actions sensibles | Décision du 09/08 : bloquant avant production. Sans trace, aucune action sur un dossier n'est opposable |
| **#185** | Sortie d'un collaborateur : archivage, symétrique de l'invitation | Un départ arrive dès le premier mois d'exploitation. Sans processus, on improvise sur des données réelles |

### Bloc « Promesses V1 » — les fonctionnalités descendues de L5

| Issue | Sujet |
|---|---|
| **#34** | EA-024 · Socle notifications |
| **#35** | EA-025 · Centre de notifications |
| **#49** | EA-039 · Badge de leçon terminée → certificat |
| **#67** | EA-082 · Vue « Documents » — pilotage des téléchargements |
| **#164** | Écran d'administration des types de supports |

Ce sont des promesses du périmètre V1. Elles sortent de L5 pour que L5 ne fasse qu'une chose.

**#164 est ici par arbitrage d'Enguerran (10/08)**, et sa place est la bonne : c'est bien une
promesse, pas un correctif. La décision F1 annonce que « l'admin peut créer un nouveau type en
le rattachant à une famille existante, **sans développement** ». L'API est complète et gardée,
mais aucun écran ne l'expose — la promesse n'est donc tenue que pour quelqu'un capable
d'appeler l'API à la main. Rien ne se corrompt et personne n'est bloqué : c'est un engagement
qui n'est pas honoré, ce qui se découvre au pire moment, celui où l'on montre le produit.

### Bloc « Correctifs » — écarts vus en recette

| Issue | Sujet |
|---|---|
| **#112** | Le détail cours perdait `support_type` et les durées |
| **#121** | Lecteur : `video_url` ignoré au profit de `youtube_url` puis `image_url` |
| **#224** | Une route protégée sans en-tête `Accept` rend 500 au lieu de 401 |
| **FRONT#18** | Anciennes URLs cassées après la refonte du routage : page blanche, ni redirection ni 404 |
| **FRONT#27** | Temps réel du chat inopérant sur staging (clé Pusher factice) |
| **FRONT#45** | Aucun `.env.example` : un build sans `VITE_APP_API` réussit et produit un bundle inerte |
| **#153** | Écran de réaffectation des cours avant suppression d'une sous-catégorie — sans lui, la garde de #152 bloque l'admin sans lui donner d'issue |
| **FRONT#46** | Modal admin : ancien MultiSelect de rattachement, à contresens de #52 |

### Bloc « Filet » — rendre la suite de tests fiable

| Issue | Sujet |
|---|---|
| **#97** | 5 tests back en échec (`cta_url`) |
| **#127** | Dette de tests front |
| **FRONT#32** | Figer Node 24 et résoudre le conflit de peer deps |

**Pourquoi c'est dans le lot et pas « quand on aura le temps »** : la suite front compte
**102 échecs permanents**. Tant qu'ils sont là, elle ne protège de rien — un 103ᵉ passerait
inaperçu au milieu des autres. Une suite qu'on a appris à ignorer coûte le temps de
l'exécuter sans rien rendre en échange.

### Explicitement HORS de L4.5

| Issue | Pourquoi pas maintenant |
|---|---|
| **FRONT#49** temps d'affichage | Touche tous les écrans, invaliderait la recette L4. **Seule exception : la mesure**, un chiffre avant sur profil réseau contraint, qui ne modifie rien |
| ~~FRONT#46 MultiSelect~~ | **Reclassé dans le bloc « Correctifs »** (correction du 10/08). Deux mécaniques écrivent la même relation en sens opposés : ce n'est pas de l'esthétique, c'est un risque d'intégrité — un rattachement fait d'un côté se défait de l'autre |
| **#180** généraliser la DataTable | Exclusion confirmée : l'issue le dit elle-même, **aucun composant à développer** — c'est une passe de cohérence sur tous les écrans. Aucun risque, aucune fenêtre |
| ~~#152, #153~~ | **Reclassés (correction du 10/08).** #152 part avec la migration de production — la suppression d'une sous-catégorie ne contrôle rien aujourd'hui et la conversion InnoDB fige le type de clé. #153 entre dans le bloc « Correctifs » |
| ~~#148~~ | **Reclassé (correction du 10/08).** La décision est déjà prise depuis le 08/08 ; ce qui reste est un impact de schéma, et c'est le modèle contre lequel #139 doit être corrigé |
| **#184** cadrage du contrat MICROLEARNING ↔ LMS | Cadrage, pas développement. Mérite sa propre séance |
| **#113, #116, #117, #123, #125, #126, #147** | V2 assumée |
| **#119** rôles managériaux | **Prémisse périmée** : L4 a requalifié `manager` en Manager RH au lieu de le supprimer. À fermer ou à réécrire |

---

## Après la V1 — la session front

Trois axes, à ouvrir **une fois la plateforme en production** :

1. **Performance** (FRONT#49) — 3,3 Mo de JavaScript en un seul fichier, 919 Ko de CSS
   bloquants. Le découpage par route est le meilleur rapport effort/gain.
2. **Santé de la base de code** — les 102 échecs, `AdminUsers.jsx` à plus de 1 600 lignes,
   deux bibliothèques de graphiques et deux d'icônes qui cohabitent.
3. **Accessibilité** — `user-scalable=no` interdit le zoom aux malvoyants (WCAG 1.4.4). Ce
   n'est pas optionnel pour un organisme de formation.

**Première tâche de cette session : mesurer.** Un chiffre avant, un chiffre après. Sans quoi
on optimise à l'intuition et on ne saura pas si l'effort a servi.

---

## Convention de suivi sur deux dépôts (établie le 10/08)

**Le trou trouvé** : les jalons n'existaient que sur `EFEKTIVACADEMIE-BACK`. Le dépôt front
n'en avait **aucun**. Quand on disait « L4 = 20 issues », on lisait un jalon qui, par
construction, ne pouvait contenir aucun travail d'écran. Le front était donc hors sprint —
pas oublié par négligence, **invisible par structure**.

C'est ce qui a permis de croire L4 terminé alors que quatre écrans n'existaient pas.

### La règle

| | Où | Rôle |
|---|---|---|
| **Issue produit** | dépôt **back** | Porte le **besoin utilisateur**, le CA, la maquette. Elle **fait foi**. |
| **Issue écran** | dépôt **front** | Porte le volet interface. |
| **Jalon** | **les deux** | Même intitulé de lot des deux côtés. |
| **Référence croisée** | **les deux** | Chaque issue cite l'autre, dans les deux sens. |

**L'issue produit ne passe en recette que lorsque les deux volets sont livrés et l'écran
exercé.** C'est le point qui manquait : le statut suivait le back seul.

### Ce qui a été mis en place

- Jalons **L4**, **L4.5** et **L5** créés sur le dépôt front.
- Les six issues front de L4 y sont rattachées.
- Références croisées posées dans les deux sens sur les six paires.
- #183, qui n'avait **aucun jalon** même côté back, rattaché à L4.

### Le contrôle automatique associé

Un script liste les méthodes de service d'API qu'**aucun écran n'appelle**. Lancé le 10/08,
il sortait les cinq méthodes de #51 — `createCompany`, `updateCompany`, `deleteCompany`,
`companyAttachedUsers`, `reassignCompany`. L'omission aurait été visible le jour même.

À ajouter à l'intégration continue du dépôt front.

### Pourquoi ne pas fusionner les dépôts

La question s'est posée. Un dépôt unique n'aurait rien empêché : la cause est une définition
de « livré » appliquée **par dépôt** au lieu de **par besoin utilisateur**, et « back mergé →
en recette » aurait été tout aussi faux dans un monorepo.

Le découpage reste un **facteur aggravant** sur un point précis : il crée des jointures que
rien ne teste — le lien d'e-mail `{FRONT_URL}/onboarding/{token}` en est l'exemple exact,
juste des deux côtés et cassé entre les deux. La parade est un test dédié à ces jointures,
pas une fusion.
