# review.md — Journal des décisions EME

> Ce fichier trace chaque étape réalisée et chaque choix technique, avec sa justification.
> Il est mis à jour au fil de l'avancement. Lecture du plus ancien (haut) au plus récent (bas).
>
> Règles métier -> `docs/regles-metier/regles-gestion.md` · Contexte -> `CONTEXT.md` · Plan -> `TODO.md`

---

## Conventions et choix transverses

Ces décisions s'appliquent à tout le projet, sauf mention contraire.

| Décision | Pourquoi |
|---|---|
| Statuts métier en `String` côté Prisma (pas d'enum natif) | SQL Server ne supporte pas les enums Prisma. Les valeurs sont validées côté TypeScript via `src/models/enums.ts`. |
| Timestamps automatiques : `createdAt @default(now())`, `updatedAt @updatedAt` | Évite d'avoir à fournir ces valeurs manuellement ; `updatedAt` est géré par Prisma au runtime. |
| `actif Boolean @default(true)` | Une entité créée est active par défaut. |
| `@unique` sur les identifiants fonctionnels | Intégrité : un identifiant en double casse l'authentification / l'unicité métier. |
| Champs nullable selon les cardinalités `0..1` du diagramme de classes | Un champ non connu à la création (ex. date de retour) ou optionnel doit être `NULL`, sinon le workflow métier est bloqué. |
| `onDelete: NoAction, onUpdate: NoAction` sur les données de traçabilité (Emprunt, Checklist, ChecklistElement, Anomalie, Historique) | Ces données ne doivent jamais être supprimées/altérées automatiquement en cascade. Toute suppression sera explicite côté service. Évite aussi l'erreur SQL Server "multiple cascade paths". |
| ERD tenu à jour en miroir du schéma (`docs/conceptions/uml/ERD.md`) | La source de vérité doit refléter le code réel (nullable, `UK`, etc.). |
| Workflow Git : `develop` comme branche d'intégration + branches dédiées `feat/...`, `fix/...`, `docs/...` | `master` reste figé ; chaque changement logique est isolé sur une branche puis mergé dans `develop`, conformément à `CLAUDE.md`. |
| Process avant chaque migration : `prisma format` -> `prisma validate` -> `npm run build` -> migration | Détecter toute erreur de schéma/typage avant de toucher la base. |
| DTO nommés `XxxResponse` (sortie) et `XxxRequest` (entrée) | Le sens est explicite dans le nom. Sortie = mapping (`toXxxResponse`), entrée = validation. |

---

## État actuel synthétique

Cette section résume l'état courant du projet. Le journal chronologique plus bas conserve
l'historique complet, y compris des étapes devenues obsolètes après branchement API.

| Bloc | État | Commentaire |
|---|---|---|
| Infrastructure | Terminé | Docker Compose SQL Server + Adminer, monorepo, backend Node/TS, frontend Flutter Web. |
| Base de données | Terminé pour la V1 | Schéma Prisma 16 entités, migrations, seeds et fixtures de démonstration. |
| API étudiant | Terminé pour la V1 | Catalogue, détail matériel, création d'emprunt, mes emprunts, restitution, anomalie automatique. |
| Frontend étudiant | Terminé pour la V1 | Parcours emprunt et restitution branchés sur l'API et testés en réel. |
| Authentification | Simulée | `x-user-email` côté backend et identité démo côté frontend ; Azure AD reste à faire. |
| Responsable matériel | Non démarré | Dashboard, stock, anomalies, historique à développer. |
| Tests automatisés | Non démarré | Tests backend/frontend/E2E à ajouter ; tests runtime manuels effectués. |
| Documentation | Partielle | `CONTEXT.md`, `TODO.md`, `review.md` existent ; README principal et OpenAPI restent à créer. |

## Commandes validées

Commandes utilisées et validées dans l'environnement de développement local :

```bash
docker compose up -d
cd eme-backend
npm run dev
npm run build
npm run lint
npm run seed
npm run fixtures
cd ../eme-frontend
flutter run -d web-server --web-port 5000
```

Notes :
- le backend écoute sur `http://localhost:3000` ;
- le frontend web de démo écoute sur `http://localhost:5000` ;
- Adminer est disponible sur `http://localhost:8081` ;
- dans l'environnement Codex, `flutter analyze`, `flutter analyze --no-pub` et `dart format` ont déjà bloqué au timeout ; à relancer dans un terminal local Flutter.

## Scénario de démo validé

Scénario étudiant validé avec SQL Server Docker et backend compilé :

1. Afficher le catalogue des matériels disponibles du campus.
2. Ouvrir le détail d'un matériel et vérifier les accessoires.
3. Créer un emprunt avec checklist de départ.
4. Vérifier que l'emprunt apparaît dans `mes-emprunts`.
5. Restituer conforme : l'emprunt passe `CLOTURE` et le matériel redevient disponible.
6. Restituer non conforme : l'emprunt passe `RETOUR_NON_CONFORME` et le matériel sort du catalogue disponible.

### Note outillage — migrations en environnement non-interactif

`prisma migrate dev` se bloque quand il doit demander une confirmation (ex. ajout de contrainte `@unique` sur table existante) car le terminal est non-interactif.
Contournement utilisé : générer le SQL avec `prisma migrate diff --from-config-datasource --to-schema ... --script`, écrire le fichier de migration, puis appliquer avec `prisma migrate deploy`. Résultat strictement identique à `migrate dev`, sans perte de données.
En terminal interactif (VS Code), `migrate dev` fonctionne normalement (taper `y`).

---

## Journal chronologique

### Étape 0 — Audit initial
- Constat : Block 0 (bootstrap) terminé mais non coché ; schéma à 4/16 entités ; aucune migration ; aucun commit Git.
- Décision : compléter le schéma par petits blocs (et non d'un coup) pour valider/committer au fur et à mesure.

### Étape 1 — Configuration des migrations Prisma
- Correction `DATABASE_URL` (mot de passe désaligné avec `DB_PASSWORD`).
- Découverte : Prisma 7 interdit `url` dans `schema.prisma` -> la connexion vit dans `prisma.config.ts`. Ligne `url` retirée du datasource.

### Étape 2 — Modèles de référence (Campus, Role, Utilisateur, CategorieMateriel)
- Defaults + timestamps auto ajoutés sur Campus, Utilisateur, CategorieMateriel (Role n'a pas de timestamps -> non modifié).
- `@unique` sur `Role.code`, `Utilisateur.microsoftId`, `Utilisateur.email`.
- `Utilisateur.classe` -> nullable : un `RESPONSABLE` n'a pas de classe (seuls les étudiants en ont).
- Ajout dans l'ERD des annotations `UK` (sur `code`, `microsoft_id`, `email`) et `nullable` (sur `classe`).
- Choix laissé de côté : tailles `@db.NVarChar(n)` (colonnes restées en `NVARCHAR(1000)` par défaut). À reconsidérer plus tard.

### Étape 3 — Bloc A : Infrastructure (SallePret, PosteEmprunt) — commit `d026b75`
- `SallePret` rattachée à `Campus` (FK `campus_id`) ; `PosteEmprunt` rattaché à `SallePret` (FK `salle_pret_id`).

### Étape 4 — Bloc B : CarteEtudiante — commit `dfa73cd`
- Rattachée à `Utilisateur`.
- Dette notée : `qr_code` devrait être `@unique` (sert à l'identification, RG02) — non posé pour rester fidèle à l'ERD.

### Étape 5 — Bloc C : Matériel (Materiel, Accessoire, MaterielAccessoire) — commit `0774ee3`
- Table de liaison `MaterielAccessoire` entre `Materiel` et `Accessoire`, sans colonnes `created_at`/`updated_at`.
- Dette notée : `@@unique([materielId, accessoireId])` et `Materiel.statut @default("DISPONIBLE")`.

### Étape 6 — Bloc D : Transactions (Emprunt, Checklist, ChecklistElement) — commit `6908a2b`
- Champs de retour/optionnels nullable : `dateRetourReelle`, `modeIdentificationRetour`, `commentaireDepart`, `commentaireRetour`, `Checklist.commentaire`, `ChecklistElement.commentaire`.
  - Pourquoi : RG12 crée l'emprunt au départ ; les infos de retour n'existent pas encore. En `NOT NULL`, la création serait impossible.
- `ChecklistElement.accessoireId` rendu nullable : un élément de checklist peut être un libellé libre, sans accessoire catalogué associé (cardinalité 0..1).
- Les 9 FK en `NoAction` : traçabilité + évitement des cascade paths.
- Annotation `nullable` ajoutée dans l'ERD sur les champs de retour/commentaires d'`Emprunt` et `accessoire_id`/`commentaire` de checklist.

### Étape 7 — Stratégie Git : branche `develop`
- Création de `develop` depuis `master`. Désormais tous les commits vont sur `develop` ; `master` figé au Bloc C.

### Étape 8 — Bloc E : Suivi (Anomalie, Notification, Historique) — commit `0fc9f9e`
- Schéma 16/16 complet.
- Nullable selon `0..1` : `Anomalie.traiteeParId` (null tant que non pris en charge), `observation`, `dateResolution` ; `Notification.anomalieId` ; les 5 FK optionnelles d'`Historique` (un événement n'a pas toujours d'emprunt/matériel/etc.).
- Double relation `Anomalie` vers `Utilisateur` nommée : `@relation("AnomalieEtudiant")` (étudiant concerné) et `@relation("AnomalieResponsable")` (responsable qui traite).
- Defaults : `Anomalie.detecteeAutomatiquement @default(true)`, `Notification.lu @default(false)`, `Notification.dateCreation @default(now())`.
- Les 12 FK en `NoAction` (traçabilité).
- `Notification` et `Historique` créées sans colonne `updatedAt` (seulement `date_creation`/`created_at`).
- Annotation `nullable` ajoutée dans l'ERD sur `traitee_par_id`, `observation`, `date_resolution`, `anomalie_id` et les 5 FK optionnelles d'`Historique`.

### Étape 9 — Seeds de référence — commit `2c3a5db`
- Fichier `prisma/seed.ts` autonome (adapter MSSQL, lecture directe des variables DB), lancé via `npm run seed` (script ajouté au `package.json`).
- Choix `npm run seed` (ts-node) plutôt que `prisma db seed` : l'API de configuration du seed en Prisma 7 (`prisma.config.ts`) n'étant pas confirmée, un script direct est plus fiable.
- Données : 2 rôles (`ETUDIANT`, `RESPONSABLE`), 1 campus (`Campus Saint-Christophe`, Cergy, 95800, adresse provisoire), 1 salle (`Salle de prêt principale`), 1 poste (`POSTE-01`), 6 catégories (Ordinateur portable, Vidéoprojecteur, Tablette, Caméra, Périphérique audio, Câble / Adaptateur).
- Idempotence : `upsert` sur `Role.code` ; `findFirst` avant `create` pour les entités sans clé unique. Relance vérifiée (aucun doublon).
- Incident : le client Prisma était obsolète (ne connaissait que les 4 modèles de référence). Régénéré avec `npx prisma generate` avant le seed. Leçon : après des migrations, régénérer/vérifier le client avant d'exécuter du code qui l'utilise.

### Étape 10 — Fixtures de test
- Fichier `prisma/fixtures.ts` + script `npm run fixtures` (séparé du seed : données de dev/test, non installées en production).
- Non destructif : détection via un email témoin (`marie.dupont@ensitech.eu`) ; si présent, le script s'arrête (cohérent avec le principe "pas de suppression automatique").
- Contenu : 4 utilisateurs (3 étudiants + 1 responsable), 3 cartes, 6 matériels (statuts variés), 3 accessoires + liaisons, 4 emprunts couvrant `EN_COURS` / `EN_RETARD` / `CLOTURE` / `RETOUR_NON_CONFORME`, 6 checklists, 1 anomalie + 1 notification, 2 entrées d'historique.
- Idempotence vérifiée (2e exécution = skip).

#### Contexte métier clarifié — structure ENSUP (impacte la modélisation)
- ENSUP est un groupe **multi-campus** (Saint-Christophe/Cergy, Saint-Quentin, Nantes, Marseille...).
- Chaque campus héberge **deux écoles** : ENSUP (filières généralistes) et Ensitech (informatique). Bâtiments et salles **partagés**.
- La distinction d'école se fait par le **domaine email** : `ensitech.eu` (info) et `ensup.eu` (reste). La validation de domaine (RG, à venir) doit accepter les **deux**.
- Le modèle n'a pas d'entité "École" : seule `Campus` existe. Un campus = un lieu physique partagé par les deux écoles (et non une école).
- MVP : on démarre avec le **seul campus Saint-Christophe (Cergy)** ; les autres campus viendront ensuite.

### Étape 11 — Block 1 : middlewares backend
- Structure en couches : `utils/logger.ts`, `errors/app-error.ts`, `middlewares/` (request-logger, not-found, error-handler).
- Gestionnaire d'erreurs global : une `AppError` renvoie son statut + message ; toute autre erreur devient une 500 (stack exposée uniquement en `development`) et est tracée.
- Logger de requêtes (méthode, URL, statut, durée) + logger applicatif horodaté.
- CORS restreint à `env.frontendUrl` (était ouvert à toutes les origines). `SIGTERM` géré en plus de `SIGINT` (arrêt propre du container Docker).
- Express 5 propage nativement les erreurs des handlers async : pas de wrapper `asyncHandler` nécessaire.
- Vérifié au runtime : `/health` 200, route inconnue 404 en JSON, requêtes journalisées.

### Étape 12 — Block 1 : ESLint + Prettier
- ESLint 10 (flat config `eslint.config.mjs`) + typescript-eslint 8 ; Prettier 3 (`.prettierrc.json`).
- Règles de qualité strictes encodées : `no-explicit-any`, `no-non-null-assertion`, `explicit-module-boundary-types`, `no-unused-vars` (ignore le préfixe `_`), `prefer-const`. `eslint-config-prettier` désactive les règles en conflit avec Prettier.
- Prettier : quotes simples, point-virgule, 100 colonnes, 2 espaces, trailing commas.
- Scripts : `lint`, `lint:fix`, `format`, `format:check`.
- État : lint vert sur tout le code existant ; formatage appliqué (3 fichiers) ; build OK.

### Étape 13 — Correctif tooling : type-check des scripts Prisma
- Problème : `prisma/seed.ts` et `fixtures.ts` étaient hors du `include` du tsconfig ; l'IDE ne chargeait pas les types Node (`process` non reconnu, 2 erreurs).
- Correctif : `tsconfig.json` élargi à `src` + `prisma` en `noEmit` (IDE et `tsc --noEmit`) ; nouveau `tsconfig.build.json` dédié à la compilation `src` -> `dist` (`npm run build`).
- Bénéfice : les scripts Prisma sont désormais type-checkés (angle mort comblé).

### Étape 14 — Block 4 : architecture en couches + 1er endpoint (catalogue matériel)
- Mise en place de l'architecture en couches : `routes/` -> `controllers/` -> `services/` -> `repositories/` (un dossier par couche).
- Auth simulée temporaire : middleware `current-user` qui résout l'utilisateur via l'en-tête `x-user-email` et pose `req.user` (typé via `types/authenticated-user.ts` + augmentation `Express.Request`). À remplacer par la validation JWT au Block 3.
- Endpoint `GET /api/materiels` (catalogue) : applique RG10 (matériels `DISPONIBLE` du campus de l'étudiant), filtres `categorieId` et `q` (recherche nom/marque/modèle/référence).
- Format de réponse standardisé : `{ "data": ... }` (cohérent avec `{ "error": ... }`).
- Vérifié au runtime : 401 sans en-tête / utilisateur inconnu, 200 avec les disponibles du campus, filtres OK, 400 sur `categorieId` invalide.

### Étape 15 — Block 4 : endpoint "mes emprunts en cours"
- Endpoint `GET /api/mes-emprunts` : retourne les emprunts non restitués (`EN_COURS`, `EN_RETARD`) de l'utilisateur courant, triés par date de retour prévue, avec le matériel et sa catégorie inclus.
- RG15 : filtrage par `utilisateurId` (chacun ne voit que ses propres emprunts).
- Réutilise l'architecture en couches et l'auth simulée déjà en place.
- Vérifié au runtime : Marie 1 (`EN_COURS`, son `CLOTURE` exclu), Lucas 1 (`EN_RETARD`), Sofia 0 (son `RETOUR_NON_CONFORME` exclu), 401 sans en-tête.

### Étape 16 — Block 4 : profil et identification
- `GET /api/auth/me` (protégé) : renvoie le profil complet de l'utilisateur courant (role + campus).
- `GET /api/auth/carte/:qr` (public) : identification par QR code (RG02/RG03) ; contrôles carte active, non expirée et compte actif ; renvoie le profil.
- Refactor du routage : `currentUser` appliqué par sous-routeur (au lieu d'un middleware global) pour laisser la route d'identification publique.
- Vérifié au runtime : `/me` 200 (profil Marie) et 401 sans en-tête ; `/carte` QR valide 200 sans en-tête (route publique), QR inconnu 404.

### Étape 17 — DTO de sortie sur les endpoints existants
- Dossier `src/dtos/` : par ressource, une interface `XxxResponse` + un mapper pur `toXxxResponse` (utilisateur, matériel, emprunt).
- Mapping effectué dans le controller (couche présentation) ; les services continuent de renvoyer les entités (réutilisables).
- Champs internes désormais masqués : `microsoftId`, timestamps, FK brutes, `numeroInventaire`/`numeroSerie`.
- Vérifié au runtime sur `/me`, `/materiels`, `/mes-emprunts`.

### Étape 18 — Durcissement du .gitignore (sécurité)
- Trou comblé : `**/.env` ne couvrait pas les variantes (`.env.local`, `.env.production`...). Ajout de `**/.env.*` avec exception `!**/.env.example`.
- Préventif : clés/certificats (`*.pem`, `*.key`, `*.crt`, `*.cert`, `*.pfx`, `*.p12`) pour Azure AD/TLS à venir ; dumps de base (`*.bak`, `*.dump`) ; `**/coverage/` ; `*.tsbuildinfo`.
- Vérifié : aucun secret n'était déjà suivi ; les vrais `.env` restent ignorés ; `.env.example` (placeholders) reste versionné.

### Étape 19 — Block 4 : création d'emprunt (POST /api/emprunts)
- Écriture transactionnelle : réservation atomique du matériel (`updateMany where statut=DISPONIBLE`), création de l'emprunt (`EN_COURS`), de la checklist de départ et de ses éléments. Rollback global si une étape échoue.
- Règles : RG07 (matériel disponible), RG10 (matériel du campus de l'étudiant), RG11 (checklist obligatoire non vide), RG12 (matériel -> `EMPRUNTE`, horodatage).
- Validation d'entrée manuelle (`CreerEmpruntRequest`, `parseCreerEmpruntRequest`) : aucune dépendance ajoutée. Durée d'emprunt par défaut : +14 jours.
- Contrôle du poste (existe + même campus, RG05). Routage : `POST /api/emprunts` et `GET /api/mes-emprunts` sur des routeurs séparés.
- Vérifié au runtime : 401 sans auth, 400 checklist vide, 404 matériel inconnu, 201 création (matériel -> `EMPRUNTE`, +14j), 409 doublon (réservation atomique).

### Étape 20 — Block 4 : restitution + anomalie automatique (POST /api/emprunts/:id/restitution)
- Comparaison automatique départ/retour (RG17) : un élément présent au départ mais absent/détérioré au retour rend la restitution non conforme (RG20). Appariement par `accessoireId` sinon par `nomElement`.
- Restitution atomique : checklist de retour + mise à jour emprunt/matériel + (si non conforme) anomalie `DETECTEE` + notifications.
- RG15 (propriétaire), statut restituable, RG18 (conforme -> `CLOTURE` + matériel `DISPONIBLE`), RG19 (non conforme -> `RETOUR_NON_CONFORME` + matériel `DETERIORE`/`NON_CONFORME`).
- RG24 : notification à tous les responsables (`RESPONSABLE`) du campus de l'emprunt.
- Vérifié au runtime : 404/403/409 ; restitution conforme (CLOTURE + DISPONIBLE) ; non conforme (RETOUR_NON_CONFORME + NON_CONFORME + anomalie + notification à Karim).
- **Block 4 (API Étudiant) terminé : 6/6 endpoints.**

### Étape 21 — Frontend : setup Flutter Web + écran d'accueil ENSUP
- Projet Flutter Web créé (`eme-frontend/`), `google_fonts` ajouté (Darker Grotesque pour les titres, Titillium Web pour le corps).
- Thème ENSUP (`theme/ensup_colors.dart`, `theme/ensup_theme.dart`) : couleurs de la charte graphique.
- Composants réutilisables : `EnsupTopBar`, `ProfileCard`, `ActionCard`.
- Écran d'accueil (`screens/home_screen.dart`) fidèle à la maquette : topbar bleu foncé, carte profil, deux cartes Emprunter/Restituer. Données statiques (pas encore branché à l'API).
- Convention `CLAUDE.md` respectée : guillemets doubles en Dart (règle lint `prefer_single_quotes` désactivée en conséquence).
- `flutter analyze` : aucun problème. App lancée dans Chrome (http://localhost:5000).

### Étape 22 — Suppression du PDF de charte graphique
- PDF de charte ENSUP retiré du dépôt (redondant : la maquette validée applique la charte, et les couleurs/typos sont dans `CONTEXT.md` §7).
- Références mises à jour dans `CLAUDE.md` et `CONTEXT.md` pour pointer vers la maquette.
- Le PDF reste récupérable dans l'historique Git si besoin (logo officiel source).

### Étape 23 — Frontend : écran d'identification (écran de démarrage)
- Correction : l'écran de démarrage est l'identification (`s-login` de la maquette), pas l'accueil étudiant. Logo, "Bienvenue sur EME", deux options (scanner carte / compte ENSUP Microsoft 365), bouton accès responsable.
- `EnsupTopBar` rendue réutilisable (utilisateur optionnel, marque paramétrable) ; nouveau widget `IdentificationOption` ; coins ENSUP aussi sur cet écran.
- Navigation identification -> accueil étudiant (`HomeScreen`).

### Étape 24 — Frontend : écran catalogue matériel
- Écran catalogue (`s-catalogue`) : recherche **fonctionnelle** + filtres par catégorie (chips) + liste de matériels avec statut (Disponible/Emprunté) et état vide.
- Nouveaux composants : `MaterielCard`, `StatusPill`, modèle `MaterielItem`. Données statiques (4 matériels de la maquette).
- Navigation : Accueil (Emprunter) -> Catalogue ; bouton retour vers l'accueil. « Sélectionner » -> placeholder (détail à venir).

### Étape 25 — Frontend : écran détail matériel
- Écran détail (`s-detail`) : caractéristiques (référence, catégorie, marque, modèle, état, campus, statut) + accessoires du kit (tags) + aperçu + boutons « Démarrer l'emprunt » / « Retour au catalogue ».
- Modèle `MaterielItem` enrichi (marque, modèle, état, accessoires) ; données statiques mises à jour.
- Navigation : Catalogue (Sélectionner) -> Détail ; « Démarrer l'emprunt » -> placeholder (checklist à venir).

> Note historique : les étapes 21 à 25 décrivent l'état initial statique du frontend.
> Elles ont été remplacées fonctionnellement par les branchements API des étapes 26 à 32.

---

## Dette technique en attente

À traiter ensemble plus tard (avec mise à jour ERD en miroir) :

| Dette | Statut | Impact V1 |
|---|---|---|
| `CarteEtudiante.qr_code` -> `@unique` | À faire avec migration + ERD | Moyen : sécurise l'identification QR. |
| `MaterielAccessoire` -> `@@unique([materielId, accessoireId])` | À faire avec migration + ERD | Moyen : évite les doublons d'accessoires. |
| `Materiel.statut` -> `@default("DISPONIBLE")` | À faire avec migration + ERD | Faible : le code fournit déjà le statut explicitement. |
| Tailles de colonnes `@db.NVarChar(n)` | À cadrer | Faible V1, important pour qualité BDD. |
| Swagger/OpenAPI | À faire | Moyen : utile pour tester/documenter l'API. |
| Auth réelle Azure AD | À faire | Fort : requis pour sortir de la démo avec auth simulée. |
| Limite d'emprunts actifs par étudiant | À valider métier | Non bloquant V1 ; décision métier non présente dans les règles actuelles. |

---

### Étape 26 — Connexion frontend ↔ API (catalogue)
- Package `http` ajouté. Couche `services/api_client.dart` : URL de base, en-tête d'auth simulée (`x-user-email: lucas.martin@ensitech.eu`), gestion des erreurs (serveur injoignable, message `{error:{message}}`).
- `services/materiel_service.dart` (`getCatalogue`) + `MaterielItem.fromJson` (icône déduite de la catégorie, champs absents complétés).
- Écran catalogue branché via `FutureBuilder` : chargement / données / erreur + bouton Réessayer. Filtres de catégorie déduits des vraies données.
- CORS backend assoupli en développement (`origin: true` si `NODE_ENV=development`), strict en production.
- Testé en réel côté backend : Docker SQL Server OK, backend compilé OK, `/health`, `/api/auth/me`, `/api/materiels` OK, 401 sans `x-user-email` OK.

### Étape 27 — Connexion frontend ↔ API (détail matériel)
- Correctif runtime backend : `npm run dev` lance maintenant `ts-node --files`, sinon l'augmentation `Express.Request.user` n'était pas chargée par `ts-node` malgré un build TypeScript vert.
- Endpoint `GET /api/materiels/:id` ajouté : détail filtré par campus utilisateur, avec catégorie, campus et accessoires du kit.
- Frontend : `MaterielItem` porte maintenant l'`id`, `MaterielService.getDetail(id)` consomme le nouvel endpoint, et le catalogue charge le détail réel avant de naviguer vers l'écran détail.
- Testé en réel côté backend : `/api/materiels/1` renvoie le PC avec accessoires (`Chargeur`, `Souris`) ; `/api/materiels/abc` renvoie 400.
- Limite environnement : `flutter analyze`, `flutter analyze --no-pub` et `dart format` restent bloqués jusqu'au timeout dans le sandbox. À relancer dans un terminal Flutter local.

### Étape 28 — Connexion frontend ↔ API (création d'emprunt)
- Branche dédiée `feat/frontend-emprunt-api` créée après merge de `feat/catalogue-detail-api` dans `develop`.
- `ApiClient.post` ajouté et `EmpruntService.creerEmprunt` consomme `POST /api/emprunts` avec l'auth simulée.
- Checklist de départ branchée sur l'API : construction du payload (`materielId`, `posteEmpruntId`, `modeIdentification`, commentaire, éléments), état de chargement, affichage d'erreur via snackbar.
- Un matériel sans accessoire génère un élément de checklist portant le nom du matériel, afin de respecter RG11 côté backend (checklist obligatoire non vide).
- Confirmation alimentée par la réponse API (`id`, `dateEmprunt`, matériel renvoyé).
- Testé sans mutation de données : `POST /api/emprunts` avec checklist vide renvoie bien 400 `La checklist de depart est obligatoire (RG11)`.

### Étape 29 — Données de démonstration catalogue
- Branche dédiée `feat/demo-catalogue-fixtures` créée après merge de `feat/frontend-emprunt-api` dans `develop`.
- `prisma/fixtures.ts` enrichi avec un catalogue de démonstration idempotent : si les fixtures de base existent déjà, le script assure seulement les matériels de catalogue manquants.
- 8 matériels disponibles ajoutés : Lenovo ThinkPad, MacBook Air, Surface Pro, vidéoprojecteur Epson, caméra Canon, casque Jabra, kit câbles HDMI/USB-C, enceinte JBL.
- Accessoires associés assurés sans doublon logique par recherche de nom/référence avant création.
- Vérifié en réel : `npm run fixtures` OK ; `GET /api/materiels` pour Lucas renvoie 10 matériels disponibles.

### Étape 30 — Connexion frontend ↔ API (restitution)
- Branche dédiée `feat/frontend-restitution-api` créée après merge de `feat/demo-catalogue-fixtures` dans `develop`.
- `EmpruntItem.fromJson` ajouté pour mapper `GET /api/mes-emprunts`.
- `EmpruntService.getMesEmprunts()` et `EmpruntService.restituerEmprunt()` ajoutés.
- Écran de sélection restitution branché sur les vrais emprunts en cours, avec chargement, erreur, état vide et chargement du détail matériel avant la checklist.
- Checklist retour branchée sur `POST /api/emprunts/:id/restitution`, avec commentaire optionnel, état de chargement et erreurs via snackbar.
- Le résultat affiché se base sur le statut renvoyé par le backend (`CLOTURE` => conforme, sinon anomalie), afin de rester cohérent avec la comparaison serveur.
- Vérifié sans mutation : `GET /api/mes-emprunts` renvoie les emprunts de Lucas ; restitution d'un identifiant inexistant renvoie 404.

### Étape 31 — Stabilisation parcours étudiant complet
- Branche dédiée `feat/student-flow-stabilization` créée après merge de `feat/frontend-restitution-api` dans `develop`.
- Docker SQL Server relancé puis tests runtime faits avec le backend compilé.
- Flux conforme validé en réel : catalogue -> détail -> création emprunt -> apparition dans `mes-emprunts` -> restitution conforme -> statut `CLOTURE` -> matériel de nouveau visible dans le catalogue.
- Flux non conforme validé en réel : création emprunt -> restitution avec élément absent -> statut `RETOUR_NON_CONFORME` -> matériel retiré du catalogue disponible.
- Aucun correctif code nécessaire après ces tests ; backend `build` et `lint` restent verts.

### Étape 32 — Polish V1 étudiant
- Branche dédiée `feat/v1-student-polish` créée après merge de `feat/student-flow-stabilization` dans `develop`.
- Identité simulée et campus MVP centralisés dans `lib/demo_identity.dart` tant que l'auth Azure AD n'est pas branchée.
- Libellés UI harmonisés sur le contexte réel du MVP : `Saint-Christophe · Cergy` / `Ensitech Cergy` au lieu de `Paris · Ensitech`.
- Commentaire obsolète du modèle `MaterielItem` corrigé : les données viennent maintenant de l'API.
- `TODO.md` mis à jour : les parcours emprunt et restitution étudiant sont maintenant branchés API.

### Étape 33 — Clarification du journal de décisions
- Branche dédiée `docs/review-current-state` créée depuis `develop`.
- Ajout d'une synthèse d'état courant pour éviter de devoir relire tout l'historique.
- Correction de la convention Git : `develop` est la branche d'intégration, les travaux passent par branches dédiées.
- Ajout des commandes validées et du scénario de démo étudiant testé.
- Dette technique reformatée en tableau avec statut et impact V1.
- Clarification des étapes frontend statiques historiques : elles sont conservées pour mémoire mais remplacées par les branchements API ultérieurs.

### Étape 34 — API responsable : dashboard
- Branche dédiée `feat/responsable-dashboard-api` créée depuis `develop`.
- Endpoint `GET /api/responsable/dashboard` ajouté, protégé par rôle `RESPONSABLE`.
- Périmètre campus appliqué via `user.campusId` (RG27).
- KPIs exposés : matériels par statut, emprunts par statut, anomalies par statut, notifications non lues.
- Activité récente exposée depuis l'historique, avec utilisateur, matériel et emprunt associé.
- Vérifié au runtime : Karim (`RESPONSABLE`) obtient 200 avec KPIs ; Lucas (`ETUDIANT`) obtient 403.

### Étape 35 — API responsable : consultation des emprunts
- Branche dédiée `feat/responsable-emprunts-api` créée après merge du dashboard responsable dans `develop`.
- Endpoint `GET /api/responsable/emprunts` ajouté, protégé par rôle `RESPONSABLE`.
- Filtrage par campus du responsable appliqué systématiquement (RG27).
- Filtre optionnel `statut` ajouté pour RG28 : `EN_COURS`, `EN_RETARD`, `CLOTURE`, `RETOUR_NON_CONFORME`, `ANNULE`.
- Réponse enrichie avec l'étudiant, le matériel, la catégorie, les dates et le statut.
- Vérifié au runtime : Karim obtient les emprunts du campus, `statut=EN_COURS` filtre correctement, statut invalide renvoie 400, Lucas obtient 403.

### Étape 36 — API responsable : consultation du stock
- Branche dédiée `feat/responsable-stock-api` créée après merge de la consultation des emprunts responsable dans `develop`.
- Endpoint `GET /api/responsable/materiels` ajouté, protégé par rôle `RESPONSABLE`.
- Filtrage par campus du responsable appliqué systématiquement (RG27/RG29).
- Filtres optionnels ajoutés : `statut`, `categorieId`, `q` (nom, marque, modèle, référence).
- Contrairement au catalogue étudiant, cet endpoint retourne tout le stock actif du campus, quel que soit le statut.
- Réponse enrichie avec catégorie et accessoires attendus.
- Vérifié au runtime : Karim obtient tout le stock du campus, `statut=DISPONIBLE` et `q=MacBook` filtrent correctement, statut invalide renvoie 400, Lucas obtient 403.

### Étape 37 — API responsable : consultation des anomalies
- Branche dédiée `feat/responsable-anomalies-api` créée après merge de la consultation du stock responsable dans `develop`.
- Endpoint `GET /api/responsable/anomalies` ajouté, protégé par rôle `RESPONSABLE`.
- Filtrage par campus du responsable appliqué systématiquement via l'emprunt lié à l'anomalie (RG27).
- Filtres optionnels ajoutés : `statut` (`DETECTEE`, `EN_COURS_TRAITEMENT`, `RESOLUE`, `CLOTUREE`) et `type`.
- Réponse enrichie avec anomalie, emprunt, étudiant concerné, matériel, catégorie et responsable de traitement si renseigné.
- Limite volontaire : consultation uniquement ; le cycle de traitement/résolution reste à développer.

### Étape 38 — API responsable : notifications
- Branche dédiée `feat/responsable-notifications-api` créée après merge de la consultation des anomalies responsable dans `develop`.
- Endpoint `GET /api/responsable/notifications` ajouté, protégé par rôle `RESPONSABLE`.
- Filtre optionnel `lu=true|false` ajouté pour séparer les notifications lues et non lues.
- Endpoint `PATCH /api/responsable/notifications/:id/lu` ajouté pour marquer une notification du responsable connecté comme lue.
- Endpoint `PATCH /api/responsable/notifications/lu-toutes` ajouté pour marquer toutes les notifications non lues du responsable connecté comme lues.
- Réponse enrichie avec l'anomalie liée et le matériel concerné lorsque la notification pointe vers une anomalie.

### Étape 39 — API responsable : historique et export
- Branche dédiée `feat/responsable-historique-api` créée après merge des notifications responsable dans `develop`.
- Endpoint `GET /api/responsable/historique` ajouté, protégé par rôle `RESPONSABLE`.
- Filtrage par campus du responsable appliqué systématiquement (RG27).
- Filtres optionnels ajoutés : `action`, `utilisateurId`, `materielId`, `empruntId`, `dateDebut`, `dateFin`.
- Endpoint `GET /api/responsable/historique/export.csv` ajouté avec les mêmes filtres.
- Export CSV généré côté API avec les colonnes principales : date, action, description, utilisateur, email, matériel, emprunt.

---

*Dernière mise à jour : 2026-07-16 — Bloc API responsable complété côté consultation, notifications et historique/export.*
