# Inventaire des écarts fonctionnels : prod → refonte

> **18/08/2026.** Réponse à la question d'Enguerran : *« vu tout ce qu'on a
> modifié, est-il possible qu'on ait des régressions vitales ou majeures par
> rapport à la version actuellement en prod ? »* — posée après la découverte,
> par accident, que la prod fait vieillir les mots de passe et que la refonte
> ne le fait plus.
>
> **Réponse courte : le risque est maintenant borné. Trois écarts réels, tous
> nommés ci-dessous. Aucun autre sur les surfaces comparées.**

## La méthode — comparer les surfaces, pas le code

Un diff ligne à ligne serait illisible : la refonte a tout réécrit, et 99 % du
diff serait du bruit voulu. On compare ce qui est mécanique et exhaustif, entre
**le code réellement déployé en prod** (`/var/www/api.efektiv-academie.com`, lu
sur le serveur — pas une branche git qui prétend le représenter) et notre
`development` :

| Surface | Prod | Refonte | Écarts réels |
|---|---|---|---|
| Routes API (paramètres normalisés) | 253 | 342 | **2** (détail ci-dessous) |
| Middlewares | — | — | **1** : `CheckPasswordAge` |
| Commandes console | **0** | plusieurs | aucun — la prod n'en a AUCUNE |
| Tâches planifiées | **0** | — | aucune — la prod n'en a AUCUNE |
| Paquets composer | — | — | 0 fonctionnel (`webmozart/assert`, transitif) |
| Tables | 43 | +9 nouvelles | **1** : `reset_password_codes` |
| Colonnes (tables communes) | 442 | 540 | **2** : `user_points.points`, `user_points.action_key` |

Le point le plus rassurant est la ligne « commandes / planification » : **la
prod n'a aucun traitement de fond**. Le risque « un cron ou un mail automatique
perdu que personne ne voit » — la famille d'écarts la plus dangereuse parce
qu'invisible en cliquant — est **vide par construction**.

## Les faux positifs éliminés — pour ne pas refaire l'analyse

Le diff brut des routes donnait 21 manquantes. Après vérification une à une,
l'essentiel est du **renommage** :

- `POST` → `PUT` sur toutes les routes de mise à jour (`user/update` ×5 rôles,
  `learning-path/update`, `points/update`, `password/change`,
  `enroll/status-update`) — mêmes chemins, verbe corrigé ;
- les `my-profile` par rôle existent **toujours** (12 routes GET/PUT), la
  refonte a *ajouté* `moi/profil` unifié (#396) sans retirer les anciennes ;
- `{course}` vs `{slug}` : simple renommage de paramètre.

## Écart 1 — le flux OTP de réinitialisation + la péremption de mot de passe

**Ce que la prod a** : `POST password/otp/resend`, `POST password/otp/verify`,
`POST password/reset` (par code), middleware `CheckPasswordAge`, table
`reset_password_codes`.

**Ce que la refonte a** : le flux Laravel classique par lien
(`password/email` + `password/reset/{token}`) — **la capacité de
réinitialiser existe donc toujours**, c'est le *mécanisme* qui change (code OTP
→ lien par e-mail). Ce qui disparaît vraiment : la **péremption** (forcer le
renouvellement après N jours) et le canal par code.

**Fait notable** : la colonne `users.password_changed_at` existe **des deux
côtés** — la refonte a gardé la donnée, seul le mécanisme est parti. Une
réintroduction post-L5 serait à coût réduit, sans migration.

→ Arbitrage provisoire d'Enguerran (18/08) : abandon acceptable sur la
nouvelle version. **À confirmer avec Lilian** — issue dédiée ouverte
(« Décision formelle — péremption de mot de passe »).

## Écart 2 — la vente de cours (`course-purchase`, Stripe)

`GET api/course-purchase` n'existe plus. La refonte ne garde que
`POST api/webhook/stripe` — un webhook sans parcours d'achat devant lui.

**Question produit à trancher** : la plateforme vend-elle des cours ?
Si la fonction est morte en pratique (à vérifier : y a-t-il eu des achats en
prod ?), l'abandon se documente et le webhook orphelin se retire. Si elle sert,
c'est un écart majeur à porter.

## Écart 3 — l'historique des points (`user_points`)

La prod **stocke** la valeur (`points`, `action_key`) au moment de
l'attribution ; la refonte la **dérive** de `point_master_id`. Normalisation
saine — mais avec une conséquence de migration : si un admin a changé le barème
en cours de route (la prod le permet, `points/update`), recalculer l'historique
depuis le barème actuel **falsifierait les points déjà acquis**.

→ Décision pour le runbook L5 : figer l'historique à la migration (copier les
valeurs de prod) ou assumer le recalcul. À trancher avant d'écrire le script.

## Ce que cet inventaire ne couvre pas — dit explicitement

Il compare des **surfaces**, pas des comportements. Une route présente des deux
côtés peut faire moins qu'avant à l'intérieur — ce versant est couvert par
d'autres filets : le cahier de recette (10 parcours, 134 étapes) pour les
parcours cibles, et les tests de la PR #455 le jour où les fonctions gardées
seront portées. Les trois filets sont complémentaires ; aucun ne remplace les
deux autres.
