DOCS/parity-manager-visitapp.md
Thomas Fransolet a5a8ecdb20 Documentation interne MyInfoMate / Unov
Import initial de la documentation : statut, roadmap, plans V1/V2,
specs verticales (creche, sport), audits securite, plan de test,
analyse concurrentielle et maquettes de design.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-08-11 11:17:01 +02:00

211 lines
20 KiB
Markdown
Raw Permalink Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

# Parité manager-app → apps visiteur
> **Question à laquelle ce document répond** : tout ce qu'un client peut configurer dans `manager-app` est-il réellement visible dans `mymuseum-visitapp` (mobile) et `visitapp-web` ?
>
> Audit du **2026-08-05**, fait sur le code réel (pas sur la doc). Méthode : extraction des champs de tous les `ToDTO()` du backend (`manager-service/ManagerService/Data/` + `Data/SubSection/`), puis vérification de chaque champ dans les 3 bases de code, puis lecture manuelle de chaque écart pour éliminer les faux positifs (renommages `snake_case` → `camelCase`, champs chargés via un endpoint dédié, champs dérivés type `imageResourceId` → `imageUrl`).
>
> `tablet-app` est hors périmètre de cet audit (kiosk, cycle de release séparé) mais apparaît en colonne indicative quand l'information est utile.
---
## 1. Couverture par type de section
Les **13 types de section** du backend (`DTOs/SectionType.cs`) sont dispatchés dans les deux apps visiteur — aucun type n'ouvre un écran vide.
| SectionType | Config manager-app | mymuseum-visitapp | visitapp-web |
|---|---|---|---|
| Map | `Map/map_config.dart` | `Screens/Sections/Map/` | `MapSection.tsx` |
| Slider | `Slider/` | `Sections/Slider/` | `SliderSection.tsx` |
| Video | `Video/` | `Sections/Video/` | `VideoSection.tsx` |
| Web | `Web/` | `Sections/Web/` | `WebSection.tsx` |
| Menu | `Menu/` | `Sections/Menu/` | `MenuSection.tsx` |
| Quiz | `Quizz/quizz_config.dart` | `Sections/Quiz/` | `QuizSection.tsx` |
| Article | `Article/` | `Sections/Article/` | `ArticleSection.tsx` |
| PDF | `Pdf/` | `Sections/PDF/` | `PdfSection.tsx` |
| Game | `Game/game_config.dart` | `Sections/Game/game_page.dart` | `GameSection.tsx` (+ `game/PuzzleGame`, `game/SlidingPuzzle`) |
| Agenda | `Agenda/agenda_config.dart` | `Sections/Agenda/` | `AgendaSection.tsx` |
| Weather | `Weather/` | `Sections/Weather/` | `WeatherSection.tsx` |
| Event | `Event/event_config.dart` | `Sections/Event/` | `EventSection.tsx` (+ `event/EventMap`) |
| Parcours | `SectionParcours/section_parcours_config.dart` | `Sections/Parcours/parcours_page.dart` | `ParcoursSection.tsx` |
**Conclusion niveau 1** : la couverture *par type de section* est complète dans les deux apps. Les écarts sont tous au niveau *champ* ou *sous-comportement*, décrits ci-dessous.
---
## 2. Écarts confirmés — mymuseum-visitapp (mobile)
Champ configurable dans manager-app, sans effet dans l'app mobile.
| # | Champ / comportement | Configuré dans | Réalité mobile | Web | Gravité |
|---|---|---|---|---|---|
| ~~**M1**~~ ✅ | `SectionMap.centerLatitude` / `centerLongitude` ("Centrer sur") | `map_config.dart:612-622` (picker sur mini-carte) | **Ignoré.** `flutter_map_view.dart:105`, `map_box_view.dart:202`, `google_map_view.dart:127` centrent sur `mapDTO.latitude/longitude` — c'est-à-dire le point GPS *de la section* (champ beacon/geofence), pas le centre choisi | ✅ `MapSection.tsx:62-63` | **Haute** — le client règle le cadrage de sa carte et rien ne bouge sur mobile |
| ~~**M2**~~ ✅ | `GeoPoint.schedules` (horaires d'un point d'intérêt) | `showNewOrUpdateGeoPoint.dart` | **Jamais affiché.** `marker_view.dart` affiche `title`, `description`, `contents`, `imageUrl`, `prices`, `phone`, `email`, `site` — pas `schedules` | ✅ `MapSection.tsx:419` (`InfoBlock` "Horaires") | **Haute** — donnée saisie et invisible |
| **M3** | `Section.meterZoneGPS` (rayon de déclenchement) | `section_detail_screen.dart:650` | **Ignoré.** `configuration_page.dart:43` utilise une constante en dur : `int meterToBeacon = 100;` | n/a (pas de BLE en web) | **Moyenne** — le rayon configuré par section n'a aucun effet |
| ~~**M4**~~ ✅ | `GuidedStep.isHiddenInitially` | `showNewOrUpdateGuidedStep.dart:203` (toggle) | **Aucun consommateur**, dans aucune des deux apps | ❌ idem | **Moyenne** — toggle mensonger dans l'UI client |
| **M5** | `ApplicationInstance.mainImageUrl` | manager-app | **Non utilisé** : `home_3.0.dart:117` affiche `config.imageSource` | ✅ web utilise `mainImageUrl` | **Moyenne** — l'image d'accueil diffère entre les deux canaux pour la même config |
| ~~**M6**~~ | ~~Watermark "Preview" pendant l'essai gratuit~~ | backend onboarding | **Non applicable** | ✅ `app/[slug]/layout.tsx:29``TrialWatermark` | ❌ **Faux positif — écarté le 2026-08-07** : l'essai gratuit ne concerne que le plan **Essentiel**, qui est *web + kiosk sans app native*. Un essai mobile ne peut pas exister, donc rien à afficher |
| ~~**M7**~~ ✅ | Mode contenu d'un parcours (`ShowMap = false`) : `isStepLocked` et `hideNextStepsUntilComplete` | `showNewOrUpdateGuidedStep.dart` / `showNewOrUpdateGuidedPath.dart` | **Non implémentés dans `guided_path_content_progression_page.dart`** : pas de verrouillage d'étape, pas de masquage des étapes futures. `guided_path_map_progression_page.dart` les gère (l.139, 409, 257). ✅ En revanche `isLinear` (l.119), `requireSuccessToAdvance` (l.78) et le timer (l.73) **sont** bien gérés en mode contenu | ❌ même trou côté web, plus large (voir W6/W7) | **Haute** — dans un escape indoor, le visiteur atteint les énigmes non résolues |
| ~~**M8**~~ ✅ | `isLinear = false` (navigation libre : « le visiteur choisit ses étapes dans l'ordre qu'il veut ») | `showNewOrUpdateGuidedPath.dart` | **N'existe dans aucune app** : `isLinear=false` ne fait qu'ajouter un bouton Précédent. Aucun moyen de sauter à l'étape de son choix — les pins de carte sélectionnent une étape sans y naviguer | ❌ idem | **Moyenne** — l'option est proposée mais ne produit pas le comportement attendu |
---
## 3. Écarts confirmés — visitapp-web
| # | Champ / comportement | Réalité web | Mobile | Gravité |
|---|---|---|---|---|
| **W1** | `SectionMap.mapProvider` / `mapType` / `mapTypeMapbox` | **Ignorés**`map/LeafletMap.tsx` a un `TileLayer` OSM en dur | ✅ 3 vues séparées (Google / Mapbox / flutter_map) | **Moyenne** — un client qui choisit "Google Hybrid" voit de l'OSM sur le web |
| ~~**W2**~~ ✅ | `Categorie.icon` / `Categorie.resourceDTO` (icône de catégorie) | **Ignorés** — les marqueurs sont des pastilles de couleur (`colorOf(p.categorieId)`, `MapSection.tsx:304`) | ✅ icône affichée | **Moyenne** — perte d'identité visuelle de la carte |
| **W3** | `AppConfigurationLink.roundedValue` (rayon des coins, theming instance) | **Aucun usage** | ✅ ~47 usages (popups agenda, audio player, cards…) | **Basse** — cosmétique, mais c'est du theming vendu |
| **W4** | `AppConfigurationLink.isSectionImageBackground` | **Aucun usage** | ✅ `section_page.dart:133` (MenuPage) | **Basse** |
| **W5** | `SectionArticle.isContentTop` | **Ignoré**`ArticleSection.tsx` place toujours le carrousel avant le HTML | ✅ | **Basse** |
| ~~**W6**~~ ✅ | `GuidedPath.isLinear` et `GuidedStep.isStepLocked` | **Zéro occurrence** dans `ParcoursSection.tsx` (2600+ lignes) — `prevStep` (l.86) autorise toujours le retour arrière, même quand l'ordre devrait être imposé | ✅ `isLinear` géré dans les deux modes ; `isStepLocked` en mode carte | **Moyenne** |
| ~~**W7**~~ ✅ | Mode contenu d'un parcours : `hideNextStepsUntilComplete` | Géré **uniquement** dans `ParcoursMapProgression` (l.332). `ProgressView` (mode sans carte, l.1275) ne masque rien | ❌ idem (M7) | **Haute** |
| ~~**W8**~~ ✅ | Assistant IA (`Instance.isAssistant` / `ApplicationInstance.isAssistant`) | **Implémenté le 2026-08-06**`components/assistant/AssistantBubble.tsx`, bulle flottante montée par le layout de configuration quand les deux drapeaux sont vrais. Consomme `POST /api/AI/chat` en `AppType.Web` | ✅ `AssistantChatSheet` | **Haute** — débloque l'add-on IA sur le plan Essentiel, qui est *web-only* |
| **W9** | `SectionAgenda.agendaMapProvider` | **Ignoré**`agenda/EventMiniMap.tsx` en Leaflet fixe | ✅ | **Basse** |
| **W10** | `SectionMap.iconResourceId` / `SectionEvent.iconResourceId` | **Ignorés** | ⚠️ 2 usages mobile seulement | **Basse** — vérifier d'abord si ce champ a encore un rôle (cf. §4) |
---
> ✅ **Corrigé le 2026-08-06 (M4, M7, M8, W6, W7)** — refonte de la progression des parcours :
> - `IsStepLocked`, `IsHiddenInitially` et `FactContent` **supprimés** du modèle (migration
> `RemoveDeadGuidedStepFlags`). `IsStepLocked` était non seulement redondant avec
> `RequireSuccessToAdvance`, mais rendait l'étape *définitivement* infranchissable même après
> réussite du défi (`_canAdvance` renvoyait `false`).
> - Le verrouillage d'une étape est désormais **dérivé de la progression réelle** du visiteur, dans
> les 4 vues (mobile carte/contenu, web carte/contenu).
> - `HideNextStepsUntilComplete` appliqué aussi en mode contenu, mobile et web.
> - `IsLinear` appliqué côté web (il ne l'était pas du tout), et **navigation libre réellement
> implémentée** : pins de carte tapables et segments de progression cliquables quand
> `IsLinear = false`.
> - Le retour arrière est désormais toujours possible vers une étape déjà atteinte, dans tous les modes.
---
> ✅ **Corrigé le 2026-08-06 (M1, M2, W2)** — `Helpers/mapCenter.dart` centralise la résolution du
> centre de carte (« Centrer sur » d'abord, puis le point de section, puis un défaut) pour les trois
> vues mobile ; les horaires d'un point d'intérêt sont affichés dans `marker_view.dart` ; les icônes
> de catégorie web utilisent `categorie.resourceDTO.url` — champ qui n'existait pas du tout dans le
> type web.
---
## 3bis. Écart de cohérence entre les deux apps — bascule carte / contenu d'un parcours
> ✅ **Corrigé le 2026-08-06** — les deux apps basculent maintenant sur **`ShowMap` seul**
> (`parcours_page.dart` `_showMap`, `ParcoursSection.tsx` `useMapView`). `BaseSectionMapId` est
> redevenu ce qu'il devait être : un fond de carte optionnel qui ne conditionne rien.
> Le constat d'origine est conservé ci-dessous.
**X1 — la condition d'affichage du mode carte n'était pas la même dans les deux apps.**
| App | Condition | Fichier |
|---|---|---|
| mymuseum-visitapp | `showMap == true` **et `baseSectionMapId` renseigné** (une SectionMap de référence doit exister et se charger) | `parcours_page.dart:419` (`_showMap`), navigation `l.723-733` |
| visitapp-web | `showMap === true` **et au moins une étape géolocalisée** (`stepsHaveGeo`) — `baseSectionMapId` n'entre pas en compte | `ParcoursSection.tsx:100-101` |
**Conséquence** : une même configuration peut s'afficher en mode carte sur un canal et en mode contenu
sur l'autre — donc avec géodéclenchement d'un côté et sans de l'autre (cf. M7/W7).
| Configuration | mymuseum-visitapp | visitapp-web |
|---|---|---|
| `showMap=true`, étapes géolocalisées, **sans** `baseSectionMapId` | mode **contenu** → aucune géoloc | mode **carte** → géoloc active |
| `showMap=true`, `baseSectionMapId` défini, étapes **sans** géométrie | mode **carte** (sans pins d'étape exploitables) | mode **contenu** |
| `showMap=true`, `baseSectionMapId` + étapes géolocalisées | carte ✅ | carte ✅ |
C'est le premier point à trancher avant même de décider du sort du mode contenu : tant que la règle
diverge, un même parcours est incomparable entre les deux canaux et les tests ne sont pas reproductibles.
**Piège associé côté manager-app** : `showNewOrUpdateGuidedStep()` reçoit `isEscapeMode` = `path.isGameMode`,
**pas** `showMap` (`showNewOrUpdateGuidedPath.dart:265-269`, `l.334-338`). Le formulaire d'étape ne sait donc
pas si la carte sera affichée, et propose le toggle « Déclenchement géolocalisé » avec le texte d'aide
*« L'étape se débloque automatiquement quand le visiteur entre dans la zone »* — une promesse non tenue
dès que le rendu tombe en mode contenu.
---
> Note : `AppConfigurationLink.gridColSpan` / `gridRowSpan` **ne sont pas un écart** — le web les mappe en `colSpan`/`rowSpan` (`client.ts:171-172`) et les rend en CSS Grid (`ConfigurationGrid.tsx:38-48`). Idem `SectionQuiz.bad_level`… → `badLevel`, et `SectionParcours.guidedPaths` (chargés par un endpoint dédié côté mobile).
---
## 4. Champs backend morts ou sans propriétaire
| Champ | État |
|---|---|
| `GuidedStep.FactContent` | Persisté (`MyInfoMateDbContext.cs:361`), **jamais édité, jamais affiché** — nulle part dans les 4 apps |
| `SectionEvent.iconResource` / `iconResourceId` | Zéro usage réel côté visiteur, rôle indéterminé |
| `SectionMap.MapResourceId` / `MapResource` | Déjà signalé "rôle flou" dans todo-features — l'audit confirme : aucun usage visiteur exploitable |
| `SectionEvent.ParcoursIds` | Déjà connu comme legacy (remplacé par `GuidedPath.SectionEventId`) |
| `GuidedStep.TriggerGeoPointId` | **N'existe pas** dans le modèle (`GuidedStep.cs`) — le géodéclenchement se fait via `Geometry` + `IsGeoTriggered` + `ZoneRadiusMeters`. Voir §5 |
---
## 5. Erreurs constatées dans `todo-features.md` (avant correction du 2026-08-05)
| Ce que disait la doc | Réalité du code |
|---|---|
| "Les 4 repos compilent (`dotnet build`, `flutter analyze`, `npm run build`)" — section onboarding | **Faux.** Voir §6 : `npm run build` échoue, le projet de tests backend ne compile pas, `flutter analyze` remonte 8 erreurs sur `mymuseum-visitapp` |
| "Gestion users par instance admin" ❌ | Scoping backend **fait** : `UserController` sous policy `InstanceAdmin`, filtrage par instance sur GET/GET{id}/PUT/DELETE, `InstanceId` forcé à l'instance de l'appelant au POST, protection contre l'escalade de rôle. **Manque uniquement** le plafond de 5 utilisateurs et le compteur "X / 5" dans l'UI |
| "`triggerGeoPointId` exposé en saisie texte ✅" + tâche "TriggerGeoPointId — picker intuitif" + tooltip associé | **Le champ n'existe pas.** Tâches fantômes à supprimer |
| "`showGeoZoneNotification()` dans `_checkGeoZone` des **deux** pages GuidedPath" | Uniquement dans `guided_path_map_progression_page.dart` |
| "`GuidedPathContentProgressionPage` : mode contenu — **même logique**" | Sous-ensemble : ni géo, ni lock, ni linéaire, ni masquage (voir M7) |
| Refacto §8 "Ajouter Parcours dans la liste des types de section" ⚠️ | **Fait** : `SectionParcours/section_parcours_config.dart` avec toggle `showMap` (l.63) et picker `baseSectionMapId` (l.81-90). Restent réellement à faire : les **tooltips ** (1 seul `Tooltip` dans tout le dossier Parcours) |
| Escape game : "`game_page.dart` check `gameDTO.gameType == GameTypes.Escape`" (et test-plan §5.1) | **Obsolète**`GameTypes` backend = `{ Puzzle, SlidingPuzzle }`, aucune référence à `Escape` dans les apps. ⚠️ Le client généré `manager_api_new/lib/model/game_types.dart:28` **contient encore** `Escape = 2` (désynchronisé) |
| `isHiddenInitially` "non requis côté serveur" | Mais exposé comme toggle dans manager-app → incohérence à trancher (retirer l'UI ou l'implémenter) |
---
## 6. Blocages de déploiement mesurés le 2026-08-05
> **Au début de cet audit, aucune des 4 apps ne buildait.**
>
> **État au 2026-08-06** : B0, B1 et B2 sont corrigés — les 3 apps Flutter buildent, `visitapp-web` build en prod, et `dotnet test` passe **124/124**. Seul **B3 (`tablet-app`, Gradle)** reste ouvert. Voir [STATUS.md §1bis](STATUS.md).
| # | Constat | Commande | État |
|---|---|---|---|
| **B0** | **Les 3 apps Flutter ne compilaient plus**`Error: The language version override has to be the same in the library and its part(s)`. Cause : `manager_api_new/lib/api/onboarding_api.dart`, ajouté à la main pour l'onboarding (fichier *untracked*, avec `part 'api/onboarding_api.dart';` ajouté dans `api.dart`), ne portait pas l'annotation `// @dart=2.18` que porte `api.dart`. Un seul fichier cassait manager-app **et** mymuseum-visitapp **et** tablet-app. ⚠️ `flutter analyze` ne le voit pas — c'est une erreur du compilateur kernel, pas de l'analyzer | `flutter build web` / `flutter build apk` | ✅ **corrigé** (annotation ajoutée) + typage explicite dans `guided_step_challenge.dart:83` (`<QuizQuestion>[...?step.quizQuestions]`). `manager-app``√ Built build\web` ; `mymuseum-visitapp` → 3 APK produits |
| **B1** | **`visitapp-web` ne buildait pas en production.** 7 erreurs TypeScript sur l'indexation de `geometry.coordinates` (typé `unknown` dans `lib/api/types.ts:191`) : `map/LeafletMap.tsx` (5) et `MapSection.tsx` (2). Ces deux fichiers indexaient les coordonnées sans le `Array.isArray()` qui narrow le `unknown` — narrowing que `AgendaSection.tsx:41-43` faisait déjà correctement | `npm run build``Failed to type check` | ✅ **corrigé** : helper `getGeoPointLatLng()` ajouté dans `src/lib/geo.ts` (à côté de `getStepGeometryCenter`) et utilisé sur les 3 sites. Build prod OK. ⚠️ invisible en `next dev` (Turbopack ne type-checke pas) |
| **B2** | **La suite de tests backend ne compile plus.** 3 × `CS7036` : `MyInfoMateDbContext` attend `IHttpContextAccessor`, `UserController` attend `IEmailService`, `AuthenticationController` attend `ProfileLogic` — les fakes de `ManagerService.Tests/` n'ont pas suivi | `dotnet test` | ✅ **corrigé le 2026-08-06** — 124/124. La remise en route a révélé un **vrai bug backend** : `BuildAuditEntries()` modifiait le `ChangeTracker` pendant son énumération → `InvalidOperationException` sur toute écriture d'entité auditée. Invisible tant que les tests ne compilaient pas |
| **B3** | `tablet-app` : échec Gradle sur `flutter_tools/gradle/build.gradle.kts:7` (pas lié au client API). Le seul APK présent date d'avril | `flutter build apk` | ❌ ouvert, à investiguer |
| **B4** | Client généré `manager_api_new/` : reliquats de désynchronisation, dont `model/game_types.dart:28` (`Escape = 2`) absent du backend | `flutter analyze` | Ouvert, non bloquant |
> **Règle qui en découle** : `flutter analyze` et `next dev` ne prouvent rien. Seuls `flutter build`,
> `npm run build` et `dotnet test` disent la vérité. Reprise en checklist dans [test-plan.md §0](test-plan.md).
**Fix suggéré pour B1** : typer explicitement les coordonnées plutôt que de caster au point d'usage —
`coordinates?: number[] | number[][] | number[][][]` dans `types.ts:191` (et `:121` pour `MapAnnotation.geometry`), puis vérifier les 7 sites. Alternative rapide : un helper `asPoint(coords): [number, number] | null` (le pattern existe déjà : `extractStepPoint()` dans `ParcoursSection.tsx:2448`) et l'utiliser partout.
---
## 7. Ordre de traitement suggéré avant déploiement
**Bloquant (à faire avant toute mise en prod)**
1. B1 — build web
2. Rotation des secrets Git (cf. `security/audit-securite-manager-service.md`, toujours ouvert)
3. Fermeture du port PostgreSQL public
4. M7 / W7 — géodéclenchement en mode contenu, **ou** décision assumée : "le mode escape indoor ne supporte pas la géolocalisation" + retirer les champs géo de l'UI quand `ShowMap = false`
**Important (écart client visible)**
5. M1 (centrage carte mobile), M2 (horaires mobile)
6. W8 — assistant IA en web, si l'add-on est vendu sur le plan Essentiel
7. ~~M6 — watermark trial mobile~~**écarté** (l'essai est web-only, cf. §2)
8. ~~M4 — trancher `isHiddenInitially`~~**résolu**, champ supprimé du modèle (migration `RemoveDeadGuidedStepFlags`)
9. ~~B2 — réparer les tests backend~~**fait** le 2026-08-06, 124/124
**Confort / cohérence**
10. W1, W2 (carte web : provider + icônes de catégorie)
11. M3, M5, W3, W4, W5, W6, W9, W10
12. Nettoyage des champs morts (§4)
13. Tooltips manager-app (le seul reliquat réel du refacto SectionParcours §8)
---
## 8. Comment refaire cet audit
Le script d'extraction est jetable mais la méthode se reproduit :
1. Extraire les champs de chaque `public XxxDTO ToDTO()` dans `manager-service/ManagerService/Data/**`.
2. Pour chaque champ, chercher le nom **et** sa variante `camelCase` dans :
`manager-app/lib` (hors `manager_api_new/` et `lib/api/`), `mymuseum-visitapp/lib`, `visitapp-web/src/lib` (couche API) et `visitapp-web/src/{components,app,hooks}` (couche UI) séparément — c'est la séparation API/UI qui révèle les champs "mappés mais jamais affichés".
3. Ne garder que `manager-app > 0` et (`mobile == 0` ou `web UI == 0`).
4. **Lire chaque candidat** : le taux de faux positifs est élevé (renommage, endpoint dédié, champ dérivé).