# SIOP V2 — GMAO ascenseurs (refondation design-first)

## Contexte

Projet **réel** (SPELEV, maintenance d'ascenseurs, Maroc) mené par le Pr. Daaif (ENSET) avec ses étudiants. **V2 = refondation** de `siop-spelev/siop` (v1, gelée, référence de lecture seulement) décidée le 15 juillet 2026 : 100 % nouveau code, design en premier, documentation en « playbook » pédagogique. Double objectif : **produit** complet déployé (web + mobile + IA, Dokploy) et **playbook** du cycle de vie logiciel (cours ENSET + méthode de la future entreprise du référent).

**Langue de travail : français** (docs, échanges, commits descriptifs). Code et identifiants en anglais.

## Principes non négociables (V2)

1. **Design-first** : aucune ligne de code applicatif avant validation par le référent de la charte graphique, des tokens et des maquettes HD (`docs/02-design/`). Chaque release subit une revue « pixel » écrans ↔ maquettes.
2. **Playbook vivant** : chaque phase du cycle de vie a son dossier `docs/0X-*/` (template réutilisable + artefacts réels). Le journal quotidien vit dans `docs/journal/` — **jamais** dans le livre.
3. **Périmètre fermé par release** : R0 Fondations → R1 Référentiel → R2 Exploitation → R3 Gestion → R4 Mobile → R5 IA. On n'ouvre pas Rn+1 avant recette **et** déploiement de Rn.
4. **Déployer tôt** : chaque release part sur le serveur de production (Dokploy) dès sa recette.
5. **Le développeur est le premier utilisateur** : `DEMO_MODE=true` active un **sélecteur de compte démo** (connexion 1 clic sur les comptes seedés, endpoint `POST /auth/demo-login` strictement absent si l'env ne l'active pas). Critère : changer de rôle en < 3 s sans mot de passe.

## Stack (ADR-001 — reconduite de v1 car éprouvée, code neuf)

pnpm + Turborepo. `apps/api` : NestJS, Prisma, PostgreSQL (pgvector + PostGIS), Redis, MinIO derrière l'interface `FileStorage` (jamais d'import direct du SDK). `apps/web` : React 19, Vite, TanStack Query/Table, **shadcn/ui + Tailwind v4** thématisés par les tokens de `docs/02-design/tokens.css`, Lucide. `apps/mobile` : Expo (Expo Go). `apps/ai` : FastAPI (Python, uv). **Règle d'or du contrat** : schémas Zod dans `packages/shared` → OpenAPI committée → clients typés régénérés dans le même commit (CI bloquante).

## Conventions

- **Journal de bord** : toute session se clôt par une entrée en tête de `docs/journal/journal.md` (date — auteur — release/tâche ; Actions / Décisions / Prochaine étape).
- Mode solo temporaire : commits directs sur `main` tolérés ; à l'arrivée des étudiants → PR + CI obligatoires.
- Commits conventionnels ; branches courtes `feat/…`, `fix/…`.
- Tests écrits avec le code : Jest (api), Vitest (web), jest-expo (mobile), pytest (ai) ; couverture backend ≥ 70 % bloquante ; parcours Playwright par release.
- API fermée par défaut (guard JWT global, `@Public()` explicite) ; matrice de permissions en base.
- Conformité loi 09-08 : géolocalisation limitée au service, purge des audios, consentements.
- IA : l'IA propose, un humain valide (sauf urgence « personne bloquée ») ; toute réponse RAG cite sa source.

## Conventions d'infrastructure

- **Docker/Dokploy** : tous les services et conteneurs sont préfixés **`siop2-`** (demande du référent, leçon v1 : sur le réseau partagé Dokploy, un service nommé `postgres`/`api` collisionne avec les autres projets).

## État d'avancement

- ✅ **R0 (1/2)** : playbook 00-vision, 01-cadrage, 02-design — **charte + tokens + maquettes HD VALIDÉES par le référent le 15/07/2026** (artefact : maquette-web.html ; 7 écrans, bi-thème). 03-architecture rédigé (ADR-001 stack, ADR-002 démo-login, C4, modèle R0). Racine monorepo posée (package.json/pnpm-workspace/turbo/.nvmrc) + `infra/` (compose PostgreSQL pgvector+PostGIS, Redis, MinIO — services préfixés `siop2-`).
- ✅ **R0.10 `apps/api`** : NestJS + Prisma (migration `r0_identity`), guard JWT global fermé par défaut + `@Public()`, PermissionsGuard (matrice en base, cache 60 s), démo-login ADR-002 (module conditionnel, 404 sinon, double verrou prod), seed idempotent (7 rôles, 70 lignes de matrice, 7 comptes démo), `FileStorage`/health ; `packages/shared` (Zod) + `pnpm contract` → `docs/openapi.json` committée ; 19 tests Jest verts + smoke test build prod.
- ✅ **R0.11 `apps/web`** : React 19 + Vite + Tailwind v4, `tokens.css` copié tel quel + classes extraites de la maquette validée, Manrope auto-hébergée ; connexion + sélecteur démo (masqué si 404 — une seule source de vérité : l'API), coquille sidebar (rail safran, écrans futurs marqués R1-R3) / topbar (thème, chip DÉMO, sélecteur de rôle), page /design ; client typé `schema.d.ts` généré depuis `docs/openapi.json` et committé ; vérifié en navigateur réel (bascule de rôle 101 ms, bi-thème, 0 erreur console).
- ✅ **R0.12 — tests + CI** (5 jobs verts) : `lint` (ESLint 10 flat config racine, règle ADR-001 anti-import MinIO codée et vérifiée), `ci-contract` (régénération spec+client, diff bloquant), `api` (PostgreSQL 18 + Redis, migrate deploy, Jest couverture ≥ 70 % bloquante — mesurée 97,5 %), `web` (typecheck + vitest + build), `e2e` (Playwright : parcours démo, bascule < 3 s chronométrée, déconnexion). Badge au README.
- ✅ **R0.13 — Dockerfiles + runbook Dokploy** : image api (multi-stage pnpm deploy, `prisma migrate deploy` au boot, seed optionnel `SEED_ON_START`, non-root, healthcheck ; `binaryTargets` explicites) ; image web (nginx, proxy `/api` résolu à la requête, fallback SPA, cache assets) ; `infra/docker-compose.dokploy.yml` (5 services `siop2-`, seul le web sur `dokploy-network`) ; runbook `docs/06-production/runbook-dokploy.md`. **Répétition locale complète validée** (migrate+seed au boot, parcours via nginx conteneurisé, double verrou ADR-002 observé). Le déploiement réel attend les accès au serveur du partenaire.
- ✅ **R0 CLOSE** (tag `release/r0`) — en production : `https://siop2.apps.enset.top` (Dokploy ENSET, profil démo, vérifiée en ligne). Restes non bloquants : secret `DOKPLOY_WEBHOOK_URL` (CD), sauvegardes PostgreSQL Dokploy. Production client SPELEV : attend les accès au serveur du partenaire.
- ✅ **R1 — maquettes validées** (16/07) : `maquette-r1.html`, 7 écrans + 4 décisions (statuts d'équipement ≠ statuts OT ; organe sans emplacement par construction ; invitation par lien 7 j ; catégories jamais supprimées si utilisées).
- ✅ **R1.1 — socle backend** : migration `r1_referentiel` (Category, Location site→zone + PostGIS générée, Asset, AssetComponent, Team, invitation User), 21 nouvelles opérations au contrat (26 total), 4 modules API + invitations/activation sous `@RequirePermission`, seed parc maquette (5 sites, 8 appareils, organes, 2 équipes), 36 tests (96 %/85 %), CI sur `postgis/postgis:18-3.6`.
- ✅ **R1.2 — écrans web du référentiel** : 7 écrans fidèles à maquette-r1.html (Sites + carte Leaflet/OSM, fiche site, ascenseurs, fiche appareil + QR réel, création, étiquette A6 imprimable, personnes & équipes avec lien d'activation à copier, catégories) + page /activation ; navigation et actions pilotées par la matrice (`usePermissions`) ; recette R1 rejouée en e2e Playwright (dont activation d'un invité) ; alias Vite `@siop/shared` → source TS (leçon CJS/workspace).
- 🏁 **R1 CLOSE (16/07/2026, tag `release/r1`)** : recettée par le référent, déployée et vérifiée en ligne (migration + seed au boot, 5 sites / 8 appareils sur l'instance).
- ✅ **R2 — maquettes validées** (16/07) : `maquette-r2.html` (demandes/approbation, nouvel OT, préventif, checklist, compteurs) + 4 décisions actées.
- ✅ **R2.1 — socle backend exploitation** : migration `r2_exploitation` (10 tables), 15 opérations au contrat (41 total, transitions + champs requis du bilan dans `@siop/shared`), machine à états stricte avec garde de clôture (bilan 3 champs + checklist), demande→OT 1-1, rejet à motif, scoping « voir autre » (listes + accès directs), seed maquette (31 valeurs de bilan, 8 gabarits, OT/demandes/compteurs), 45 tests (95 %/79 %).
- ✅ **R2.2 — préventif + compteurs** : `underContract` + `periodKey` (unicité `[assetId, periodKey]` = idempotence EN BASE), génération mensuelle (ancrage mise en service, premier contrôle, gabarits administrables, statut du mois), compteurs strictement croissants ; +7 opérations (48), 50 tests (94,7 %/78 %). Automatisation cron/BullMQ notée pour le durcissement production.
- ✅ **R2.3 — écrans web exploitation** : liste/fiche OT (transitions via `allowedTransitions`, garde visible, bilan codé, checklist cliquable, activité), demandes+approbation/rejet motivé, nouvel OT (interrupteur urgence), préventif (tuiles+générer+gabarits), compteurs, tableau de bord réel, urgence traversante (chip topbar, badges, bandeau) ; `GET /assets/options` (trou Demandeur corrigé) ; retry sur collision de référence dans la génération ; 9 Playwright verts (recette R2 complète), 50 tests API.
- 🏁 **R2 CLOSE (16/07/2026, tag `release/r2`)** : recettée (1 anomalie corrigée en recette : tri « Interventions récentes »), déployée et vérifiée en ligne (portail `/q/A1`, urgence en tête, préventif de juillet généré).
- ✅ **R3 — maquettes validées** (16/07) + **R3.1 socle backend gestion** : migration `r3_gestion` (Partner, Part sans colonne de quantité, StockMovement signé/tracé/PU figé, PurchaseOrder, LaborTime taux figé, Document, User.hourlyRate) ; 66 opérations ; stock = Σ mouvements (jamais négatif, en transaction), réception BC → RECEIPT + lastUnitPrice, conso/MO à prix/taux FIGÉS, `WorkOrderDetail.costs` immuable après clôture ; seed maquette (OT-0341 = 505 MAD, testé) ; 55 tests (92 %/74,9 %). **Durcissement : références par séquences Postgres** (fin des courses max+1).
- ✅ **R3.2 — bibliothèque + analytics** : `FileStorage` complet (put/stream/remove, bucket auto), upload multipart typé (20 Mo, rattachement requis, permission sur la cible), download **streamé par l'API**, octets vérifiés à l'identique en e2e ; `GET /analytics/summary` dérivé du réel (pannes par organe via bilans codés, coûts figés, taux préventif, top équipements) ; MinIO en CI ; 71 opérations, 58 tests (6 runs consécutifs verts).
- ✅ **R3.3** : 7 écrans web fidèles à maquette-r3 (stock/alertes, fiche pièce, BC + réception, création BC préremplie depuis l'alerte, tiers, bibliothèque, statistiques) + fiche OT complète (coûts réels : consommer à prix figé / saisir temps à taux figé ; carte Documents) + fiche ascenseur (Documents) + taux horaire dans Personnes + nav Ressources/Statistiques activée par la matrice. Recette R3 automatisée (13/13 e2e Playwright).
- ✅ **R3.4 — corrections de recette + recherche globale** (17/07) : revue pixel (artefact) → 8 écarts corrigés sur arbitrage du référent (filtre fournisseur + tri sous-seuil + entrée de stock en liste, fournisseur cliquable, sélecteur de période 3/6/12 sur les stats, rattachements syndic→site via `Location.partnerId` (migration `r3_recette_fixes`), filtre « Rattaché à » + glisser-déposer en bibliothèque, aperçus/ouverture des documents) ; **recherche globale ⌘K** (`GET /search`, 73 opérations, familles filtrées par la matrice, « voir autre » respecté). 74 tests API, 14/14 Playwright.
- 🏁 **R3 CLOSE (17/07/2026, tag `release/r3`)** : recettée par le référent (revue pixel + 8 corrections + recherche globale, CI verte). Reste : redéploiement Dokploy (manuel, webhook CD absent) et vérification en ligne.
- ✅ **R4 — maquettes rédigées** (17/07) : `maquette-r4.html`, 7 écrans mobile technicien offline-first (Ma journée bi-état, fiche OT, clôture terrain avec garde, scan QR des étiquettes R1, fiche ascenseur, checklist en file, synchro & conflits à verrou optimiste) + 5 décisions à acter (offline-first en file, verrou optimiste tranché par l'humain, périmètre fermé technicien, scan local, photos en file — ni audio ni géoloc en R4).
- ✅ **R4 — maquettes + décisions D1-D5 VALIDÉES par le référent (17/07)** ; **R4.1 socle mobile** : `apps/mobile` (Expo SDK 57, TS strict), connexion + sélecteur démo ADR-002, tabbar (onglets futurs marqués), « Ma journée » triée priorité/échéance, cache TanStack persisté (lecture hors-ligne D1), jeton SecureStore, client typé du contrat, `CORS_ORIGINS` opt-in côté API (Expo web/debug seulement), 6 tests jest-expo + job CI `mobile`. Vérifié 10/10 en Expo web (connexion démo → Ma journée → hors-ligne/retour).
- ✅ **R4.2 — terrain en ligne** : scanner QR réel (expo-camera, `analyseScan` testée, résolution locale D4, QR étrangers refusés), fiche ascenseur (D3, historique scopé), fiche OT (machine à états, coûts figés, garde par `closureBlockers` API), clôture terrain (bilan 6 champs au pouce), préventif → grille cochable (appui long = N/A, `aria-checked`). Écritures en ligne assumées (bandeaux) — la file est R4.3. Vérifié 12/12 en Expo web (dont clôture complète d'un OT de test), 12 tests jest-expo.
- ✅ **R4.3 — file & verrou** : verrou optimiste serveur (`baseUpdatedAt` → 409 contextualisé, toute écriture avance la version, web inchangé, ADR-003 sécurité/routage mobile) ; file persistée rejouée dans l'ordre (propagation de version intra-lot, arrêt sur conflit, patchs optimistes, photos D5 compressées en file), écran Synchro & conflits (rejouer sur version à jour / abandonner), préchargement parc+référentiels, purge complète à la déconnexion. Recette « mode avion » 13/13 en Expo web (conflit tranché par l'humain, serveur DONE + bilan), 17 tests jest-expo, 74 tests API.
- 🏁 **R4 CLOSE (17/07/2026, tag `release/r4`)** : posé sur décision du référent avec la recette « mode avion » validée 13/13 en Expo web piloté ; **la recette sur téléphone (Expo Go — vrai mode avion, scan caméra) est reportée et reste due avant toute production client mobile**.
- 🔄 **Reprise ici** : R5 IA — design d'abord : maquettes (assistant RAG sourcé, suggestion de codes de bilan) + décisions à acter, AUCUN code `apps/ai` avant validation. Restes : recette R4 sur appareil, redéploiement Dokploy de `release/r3`.
- Détail quotidien : `docs/journal/journal.md`. Dépôt : `siop-spelev/siop2` (privé), jalons R0→R5.
