V 1.004 ToDoList et Journal découpé

This commit is contained in:
2026-08-31 21:43:41 +00:00
parent 00c58fb125
commit c4356d4541
31 changed files with 2410 additions and 5 deletions
+1
View File
@@ -0,0 +1 @@
Require all denied
@@ -0,0 +1,7 @@
# ASPIC
*CRM sur mesure — PHP + MySQL + JavaScript vanilla*
Journal de développement — démarré le 18 août 2026
Ce document rassemble tout ce qui concerne le développement de l'application ASPIC elle-même — décisions d'architecture, modèle de données, code, points de friction résolus, pistes pour la suite. Les aspects liés à l'installation ou à la maintenance du serveur VPS (Nginx Proxy Manager, MySQL, DNS, pare-feu…) ne sont pas repris ici ; ils restent documentés dans le projet dédié à la gestion du serveur.
@@ -0,0 +1,28 @@
## 1. Contexte et décision d'architecture
ASPIC est né du besoin de disposer d'un CRM simple (gestion de contacts) que Jean-Marc puisse déployer chez ses futurs clients — des artisans et petites structures qui n'ont ni VPS, ni volonté d'en gérer un. Plusieurs pistes ont été explorées avant d'arrêter le choix final.
**Portée de l'application (précisée le 19 août 2026) :** ASPIC est destinée à un usage interne uniquement pour l'instant. Son développement sert aussi de terrain d'apprentissage pour Jean-Marc (montée en compétence PHP/MySQL/JS, et sur la façon de travailler avec Claude sur un projet applicatif dans la durée). Principe assumé : rester agile — pas de sur-ingénierie pour un usage interne (ex. pas de module ajout/modification/suppression d'utilisateur dans l'appli, géré directement via phpMyAdmin).
### Options évaluées
| Piste | Verdict |
|---|---|
| Budibase | Testé sur le VPS — fonctionne, mais reste un moteur applicatif à héberger et maintenir en permanence. |
| Appsmith, ToolJet, NocoBase, Baserow | Étudiés — même limite structurelle : outils no-code/low-code nécessitant un serveur applicatif dédié. |
| PocketBase | Prototype fonctionnel construit et déployé (crm.mvl-tls.fr). Le pipeline Docker/NPM/HTTPS a bien fonctionné techniquement, mais l'architecture ne répond pas au besoin de portabilité chez les clients. |
| Directus, NocoDB | Étudiés comme alternatives orientées MySQL — même limite : moteur applicatif à héberger. |
| PHP + MySQL + JS vanilla | Choix retenu — voir ci-dessous. |
### Décision retenue
**PHP + MySQL + JavaScript simple, sans framework, sur mesure.**
- PHP + MySQL est disponible en standard sur n'importe quel hébergement mutualisé bas de gamme (OVH et équivalents), pour quelques euros par mois — le socle technique le plus universellement disponible, cohérent avec les moyens réels des clients visés.
- Jean-Marc maîtrise déjà MySQL et sait écrire des scripts PHP basiques (PDO) — pas de nouvelle techno lourde à apprendre.
- React/Vue ont été étudiés et écartés : ils ajoutent une étape de compilation (npm, bundler), source de dépendances et de risques de "casse" dans le temps, sans bénéfice réel pour des applications de type CRUD métier classique.
- JavaScript simple, sans framework, reste le choix retenu pour l'affichage : aucune étape de build, code lisible d'un bloc, aucune dépendance à surveiller dans le temps.
- Le métier cible de Jean-Marc étant l'automatisation avec des agents IA (pas la livraison d'applicatifs sur-mesure riches), investir dans un framework front n'est pas jugé rentable.
PocketBase a été abandonné et son conteneur supprimé (19 août 2026) une fois ASPIC confirmé fonctionnel.
@@ -0,0 +1,195 @@
## 2. Modèle de données
**Structure définitive reçue le 20 août 2026** (export SQL phpMyAdmin transmis par Jean-Marc, fichier "Tables V01.txt", puis version corrigée "Tables V02.txt" — voir corrections ci-dessous), qui remplace la version simplifiée utilisée pour le tout premier prototype (CRUD contacts minimal — CNT_Incr/CNT_Nom/CNT_Prenom/CNT_Mail — voir section 3-5 pour ce prototype initial, encore en place à ce stade). Cinq tables : **ASP_Contacts**, **ASP_Annees**, **ASP_Statuts**, **ASP_Realisations**, **ASP_Actions**.
**Précision de vocabulaire importante :** ASP_Realisations est la table qui journalise une ou plusieurs actions par contact (ce que ce journal appelait auparavant, de façon imprécise, "ASP_Actions" pour désigner la relation 1-N avec les contacts — voir sections 6-7 pour les mentions antérieures à corriger mentalement). **ASP_Actions**, elle, est la table de référence des *types* d'action possibles (gérée à la main), pas le journal des actions réalisées — confirmé par Jean-Marc le 20 août 2026.
**Deux niveaux de commentaire distincts, à bien garder en tête pour l'écran de détail du contact (précisé par Jean-Marc le 21 août 2026, retour d'expérience terrain) :** une **action** (ASP_Actions, le type) peut porter son propre commentaire générique décrivant l'action elle-même, ou pas — ex. "Création de la fiche" n'en a pas besoin, tandis que "Prospection automatisée sur LinkedIn 01" en a un décrivant les paramètres de recherche utilisés par ce type de prospection. Une **réalisation** (ASP_Realisations, l'instance concrète de cette action appliquée à un contact donné, à un moment donné) porte elle aussi son propre commentaire, spécifique à cette occurrence précise — ex. pour l'action "Modification de la fiche" (qui elle-même n'a pas de commentaire de type), une réalisation donnée pourra porter "Il a changé de portable" ou "L'entreprise a déménagé". **Conséquence directe sur l'ergonomie de l'écran de détail du contact :** la liste des réalisations sous les informations du contact ne pourra afficher que des éléments basiques par ligne (type d'action, date) — la saisie/consultation du commentaire d'une réalisation ne peut pas se faire "à la volée" dans la ligne, elle nécessite d'ouvrir un panneau dédié par-dessus le panneau contact (voir ci-dessous, empilement de panneaux). Objectif métier explicite derrière cette exigence : pouvoir retrouver rapidement de petits détails de la vie professionnelle d'un contact (ex. une panne machine mentionnée par le contact) constitue un vrai avantage relationnel en usage terrain (un autre commercial qui relance le contact quelques semaines plus tard peut y faire référence) — ce n'est pas une complexité gratuite, c'est une fonctionnalité voulue et mûrement réfléchie. **⚠️ Mise à jour du 23 août 2026 : cette sous-liste des réalisations, bien qu'ergonomiquement pensée dès le départ, est finalement retirée du périmètre de la première version de l'écran de détail — voir section 6, "Précisions du 23 août 2026 (suite de session)". L'analyse ci-dessus reste valable pour le jour où cette fonctionnalité sera reprise.**
**Empilement de panneaux pour l'écran de détail du contact — principe validé (21 août 2026) :** panneau contact ouvert depuis la liste (par-dessus l'écran liste des contacts, lui-même plein écran), avec édition directe des champs basiques (nom, prénom, etc.) dans ce même panneau ; un second panneau, ouvert par-dessus le premier, pour ajouter/consulter une réalisation (avec son commentaire propre à l'occurrence). Deux niveaux d'empilement jugés raisonnables et cohérents avec le principe déjà en place "Échap ferme le panneau le plus prioritaire d'abord" (voir section 6), qui s'adapte naturellement à un empilement à plusieurs niveaux sans mécanisme nouveau à inventer. Point de vigilance pour la suite (pas encore tranché) : si une action lancée depuis une réalisation nécessitait elle-même un troisième niveau de panneau, mieux vaudra chercher une alternative (fermer un panneau parent, remonter l'information) plutôt que d'empiler à trois — au-delà de deux niveaux, le risque de perte de repère pour l'utilisateur devient significatif. **⚠️ Mise à jour du 23 août 2026 : le second niveau de panneau (réalisation) est reporté — voir section 6. L'écran de détail du contact reste à un seul niveau d'empilement pour l'instant (visualisation/modification comme deux états du même panneau, pas deux panneaux distincts).**
**Documentation utilisateur ASPIC — démarrée (21 août 2026).** Jean-Marc a validé l'idée d'une documentation utilisateur tenue en parallèle du journal de développement, alimentée par les explications fonctionnelles qu'il donne au fil de l'eau (au-delà des simples instructions de gestion de données ou d'écrans) — utile pour ASPIC, mais surtout vu comme un vrai avantage concurrentiel pour ses futurs projets clients (documentation utilisateur rarement livrée par les prestataires, faute de temps). Nouveau document créé dans le projet : **"ASPIC - Documentation utilisateur.md"**, langage utilisateur uniquement (aucun détail technique), organisé par écran. Premier jet couvrant l'écran d'identification et l'écran liste des contacts livré le 21 août 2026, à compléter au fur et à mesure des écrans suivants. Convention retenue : Claude reconnaît de lui-même le contenu à vocation "doc utilisateur" dans les explications fonctionnelles de Jean-Marc (comme celle sur les réalisations/actions ci-dessus) et propose l'extrait correspondant pour validation ; Jean-Marc garde la possibilité d'utiliser une balise explicite (ex. `#Utilisateur#`) pour lever toute ambiguïté si besoin, mais ce n'est pas la convention par défaut.
**Réflexion sur la capitalisation des méthodes de travail ASPIC pour de futurs projets (21 août 2026).** Jean-Marc souhaite, une fois ASPIC "ficelé", pouvoir réutiliser l'ensemble des conventions mises en place ici (choix technique PHP+MySQL+JS vanilla sans framework, structure des écrans en panneaux empilés sur fond plein écran, conventions de nommage/livraison de fichiers, discipline de test systématique avant livraison, pattern de mutualisation des assets, mode de secours "double mode", tenue d'un journal de développement) pour de futurs projets, via un mécanisme de **"compétence" (skill)** — pouvoir dire "on part sur le modèle ASPIC" et que cela porte l'ensemble de ces conventions, avec la possibilité d'avoir plusieurs modèles différents à l'avenir (ex. une interface plus soignée graphiquement). Explications données par Claude : une compétence est un ensemble d'instructions packagées que Claude charge quand c'est pertinent ; Claude ne peut pas créer/enregistrer une compétence directement dans une session (limite technique de l'environnement), mais peut rédiger un fichier de compétence (SKILL.md ou .skill) et le transmettre à Jean-Marc en téléchargement pour qu'il l'enregistre lui-même dans son compte. **Décision : à faire plus tard**, une fois les conventions ASPIC plus éprouvées (plusieurs écrans supplémentaires construits), plutôt que de figer un modèle encore incomplet — le journal actuel sert déjà de matière première brute pour cette future extraction. À reprendre quand Jean-Marc estimera ASPIC suffisamment abouti pour ça.
**Méthode retenue pour la conception de l'écran de détail (21 août 2026) :** Jean-Marc fournira une maquette approximative (structure, regroupement des champs, pas nécessairement au format exact d'un écran) accompagnée d'instructions textuelles de dimensionnement par référence à l'existant déjà codé et validé (ex. "la ligne 3 à la même hauteur que la ligne X de liste_contacts.html") plutôt qu'une maquette pixel-perfect. Approche jugée plus fiable qu'une maquette isolée : elle s'appuie sur le vocabulaire de mesures déjà en place dans le code (hauteur de ligne 16px, bandeau de titre 12px avec son padding, tailles de police 10-12px selon contexte) et préserve la cohérence visuelle entre écrans, plutôt que de risquer d'introduire involontairement une valeur légèrement différente de l'existant.
**Table ASP_Contacts**
```sql
CREATE TABLE `ASP_Contacts` (
`CNT_Incr` int NOT NULL AUTO_INCREMENT,
`CNT_Code` varchar(15) DEFAULT NULL,
`CNT_Nom` varchar(40) NOT NULL,
`CNT_Prenom` varchar(40) DEFAULT NULL,
`CNT_Statut` varchar(3) DEFAULT NULL,
`CNT_Société` varchar(40) DEFAULT NULL,
`CNT_Service` varchar(40) DEFAULT NULL,
`CNT_Fonction` varchar(40) DEFAULT NULL,
`CNT_AdrLig1` varchar(40) DEFAULT NULL,
`CNT_AdrLig2` varchar(40) DEFAULT NULL,
`CNT_AdrLig3` varchar(40) DEFAULT NULL,
`CNT_AdrCP` varchar(6) DEFAULT NULL,
`CNT_AdrVille` varchar(40) DEFAULT NULL,
`CNT_SIRET` varchar(20) DEFAULT NULL,
`CNT_Tel` varchar(15) DEFAULT NULL,
`CNT_Portable` varchar(15) DEFAULT NULL,
`CNT_Mail` varchar(40) DEFAULT NULL,
`CNT_Notes` text,
`CNT_CodeProfession` int DEFAULT NULL,
`CNT_Actif` tinyint(1) NOT NULL DEFAULT '1',
PRIMARY KEY (`CNT_Incr`),
KEY `CNT_Statut` (`CNT_Statut`),
KEY `CNT_CodeProfession` (`CNT_CodeProfession`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
```
**⚠️ Champ CNT_CodeProfession ajouté le 23 août 2026** (voir "Table ASP_Professions, nouvelle le 23 août 2026" plus bas dans cette section pour le détail de la table référencée et la décision d'ergonomie associée) — la position exacte de la colonne dans la vraie table (avant ou après CNT_Notes) n'a pas été précisée par Jean-Marc, celle indiquée ci-dessus est une supposition raisonnable, sans incidence pratique puisque les requêtes nomment toujours leurs colonnes explicitement. **Index KEY `CNT_CodeProfession` ajouté par Jean-Marc le même jour** (ALTER TABLE transmis, aux côtés d'un rappel de la PRIMARY KEY et de la KEY CNT_Statut déjà en place) — cohérent avec l'index déjà présent sur CNT_Statut pour la même raison (colonne de type "référence vers table gérée à la main", potentiellement utile en filtre/tri à l'usage).
Règles métier précisées par Jean-Marc (20 août 2026) :
- **CNT_Code** : code composite auto-généré à la création du contact, format `xxx-yyyyzzz` (ex. VSX-20654). *xxx* = code de l'année de création, lu dans ASP_Annees (table gérée à la main, donc une simple lecture, pas un calcul). **Logique du code année (révélée par Jean-Marc, 20 août 2026) :** V (pour "Vingt") + première lettre du chiffre des unités + dernière lettre de ce même chiffre, le tout prononcé "Vingt-[chiffre]". Ex. 2026 = Vingt-Six → V-S-X ; 2027 = Vingt-Sept → V-S-T ; 2028 = Vingt-Huit → V-H-T. Amusant, mais sans incidence sur le code à écrire puisque c'est une table gérée à la main (simple lecture en base, pas de formule à implémenter). *yyyy* = CNT_Incr du contact, sans zéro de tête. *zzz* = nombre aléatoire entre 1 et 999, **toujours sur exactement 3 caractères, zéro-padé** (ex. 001, 005, 654 — confirmé explicitement par Jean-Marc le 20 août 2026 : "005 et pas 5"). Comme CNT_Incr est déjà unique de par l'auto-increment, la partie xxx-yyyy suffit déjà à garantir l'unicité du code ; le suffixe zzz n'a donc pas de rôle d'unicité, seulement un rôle d'obfuscation/lecture. *Point d'implémentation à garder en tête :* CNT_Code dépend de CNT_Incr, qui n'est connu qu'après l'INSERT (auto-increment) — la génération du code se fera donc en deux temps (INSERT puis UPDATE juste après avec le code calculé à partir de lastInsertId()), pas en un seul INSERT.
- **CNT_Actif** : tinyint(1), 1 = actif, 0 = supprimé (suppression logique — voir section 6). **Valeur par défaut à 1 confirmée dans la structure V02** (`DEFAULT '1'`) — chaque nouveau contact est actif sans qu'il faille y penser à chaque INSERT. Toujours ajouter `WHERE CNT_Actif = 1` dans les requêtes d'affichage.
- **CNT_AdrCP** : varchar(6), donc capable de stocker un zéro de tête ("06110") — mais Jean-Marc signale un problème récurrent constaté par expérience : ce champ se retrouve parfois amputé de son zéro de tête (typiquement lors d'un import Excel, qui traite spontanément un code postal comme un nombre). Conséquence concrète : un CP stocké "6110" au lieu de "06110" fausse le tri par code postal et fausse le département affiché en liste (voir ci-dessous). **Point de vigilance pour l'import et la saisie : toujours traiter CNT_AdrCP comme du texte, jamais comme un nombre, et le remettre à 5 caractères avec un zéro de tête si besoin (LPAD en SQL, ou validation à la saisie).** Le même risque existe en théorie sur CNT_Tel et CNT_Portable (numéros français commençant aussi par 0) — à garder en tête sur les mêmes imports.
- **Dépt** (colonne affichée dans l'écran liste des contacts, section 9) = les 2 premiers caractères à gauche de CNT_AdrCP (LEFT(CNT_AdrCP, 2)). Directement impacté par le point ci-dessus : un CP tronqué de son zéro de tête donnerait un département faux.
- **Pas de gestion de contacts à l'étranger, ni de multi-contacts par entreprise** : choix assumé de Jean-Marc, cohérent avec la structure de ses futurs clients visés (petites structures). Les informations de société (CNT_Société, CNT_Service, CNT_Fonction) sont donc stockées directement dans la fiche contact plutôt que dans une table société séparée. Modifiable plus tard si besoin, mais pas une priorité.
- **Création d'un contact → création automatique d'un enregistrement dans ASP_Realisations** (date+heure, code user, code action CRE_CNT) — même logique prévue pour les autres actions (modification, suppression, etc.). Détail du mécanisme à définir au moment venu.
**Table ASP_Annees** (gérée à la main)
```sql
CREATE TABLE `ASP_Annees` (
`ANN_Année` int DEFAULT NULL,
`ANN_Code` varchar(3) DEFAULT NULL,
UNIQUE KEY `ANN_Année` (`ANN_Année`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
```
Table de correspondance année → code à 3 lettres utilisé dans CNT_Code (ex. 2026 → VSX, 2027 → VST — voir la logique de construction ci-dessus). Alimentée à la main par Jean-Marc, une ligne par nouvelle année.
**Table ASP_Statuts** (gérée à la main)
```sql
CREATE TABLE `ASP_Statuts` (
`STA_Incr` int NOT NULL AUTO_INCREMENT,
`STA_Code` varchar(3) NOT NULL,
`STA_Libellé` varchar(20) NOT NULL,
PRIMARY KEY (`STA_Incr`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
```
Table de référence des statuts de contact possibles (référencée par CNT_Statut). *Suggestion Claude, non urgente vu le volume et la gestion à la main : ajouter une contrainte UNIQUE sur STA_Code pour éviter la création accidentelle d'un code en double.*
**Table ASP_Professions (nouvelle, créée par Jean-Marc le 23 août 2026, gérée à la main comme ASP_Statuts/ASP_Actions)**
```sql
CREATE TABLE `ASP_Professions` (
`PRF_Incr` int NOT NULL,
`PRF_Libelle` varchar(40) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
ALTER TABLE `ASP_Professions`
ADD PRIMARY KEY (`PRF_Incr`),
ADD KEY `PRF_Libelle` (`PRF_Libelle`);
ALTER TABLE `ASP_Professions`
MODIFY `PRF_Incr` int NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=23;
```
Table de référence des professions possibles pour un contact (déjà peuplée — AUTO_INCREMENT=23 indique 22 lignes existantes). Liée à ASP_Contacts via `CNT_CodeProfession = PRF_Incr` (lien applicatif, pas de contrainte FOREIGN KEY SQL — cohérent avec le principe déjà en place pour CNT_Statut/ASP_Statuts, aucune table de référence d'ASPIC n'a de FK SQL formelle à ce stade). *Suggestion Claude, non urgente, même logique que pour STA_Code/ACT_Code : une contrainte UNIQUE sur PRF_Libelle éviterait la création accidentelle d'un libellé en double.*
**Décision d'ergonomie associée (23 août 2026) :** dans l'écran de détail du contact, CNT_CodeProfession sera saisi via une **liste déroulante** alimentée par ASP_Professions (même principe que pour un futur champ Statut lié à ASP_Statuts) plutôt qu'un champ texte libre. **La Profession n'est pas ajoutée à l'écran liste des contacts** (ni colonne ni filtre) — Jean-Marc a explicitement tranché contre, pour ne pas alourdir cet écran déjà considéré abouti (voir section 9) ; réservée à l'écran de détail pour l'instant, réévaluable plus tard si le besoin se confirme.
**Exception à la règle "table gérée à la main" — création d'une profession depuis le formulaire contact (demandée le 23 août 2026).** Contrairement à ASP_Statuts et ASP_Actions (ensembles fermés, gérés uniquement via phpMyAdmin), ASP_Professions est une liste plus ouverte, amenée à grandir avec l'usage : Jean-Marc a demandé la possibilité de créer une nouvelle profession directement depuis le formulaire du contact (mode modification, et mode ajout d'un nouveau contact), sans repasser par phpMyAdmin. **Choix d'ergonomie retenu :** un dernier choix **« + Nouvelle profession… »** en bas de la liste déroulante des professions ; le sélectionner fait apparaître un petit champ texte juste en dessous, dans le même panneau (pas de panneau superposé supplémentaire — cohérent avec la discipline des deux niveaux d'empilement maximum, section 2, et avec l'abandon de la sous-liste des réalisations pour la même raison). Valider (Entrée ou petit bouton) crée la ligne dans ASP_Professions (INSERT PRF_Libelle, récupération de lastInsertId()) et sélectionne aussitôt la nouvelle profession créée pour le contact en cours d'édition. **Reste à préciser au moment de l'implémentation :** l'endpoint API à créer pour cette création (probablement un petit `professions.php` avec GET liste / POST création, sur le même modèle que contacts.php) et la gestion d'un doublon de libellé (ASP_Professions n'a pas de contrainte UNIQUE sur PRF_Libelle à ce stade — voir suggestion Claude ci-dessus).
**Table ASP_Realisations** (journal des actions réalisées sur un contact) — **structure corrigée, version V02 (20 août 2026)**
```sql
CREATE TABLE `ASP_Realisations` (
`REA_Incr` int NOT NULL AUTO_INCREMENT,
`REA_IncrContact` int NOT NULL,
`REA_CodeUser` varchar(3) NOT NULL,
`REA_Date` datetime NOT NULL,
`REA_Action` varchar(20) NOT NULL,
`REA_Comment` text,
PRIMARY KEY (`REA_Incr`),
KEY `REA_CodeUser` (`REA_CodeUser`) USING BTREE,
KEY `REA_Action` (`REA_Action`) USING BTREE,
KEY `REA_Date` (`REA_Date`) USING BTREE,
KEY `REA_IncrContact` (`REA_IncrContact`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
```
Une ligne par action réalisée sur un contact (REA_IncrContact référence CNT_Incr ; REA_Action référence ACT_Code de ASP_Actions ; REA_Date inclut l'heure pour préserver la chronologie à l'affichage, comme demandé par Jean-Marc).
**⚠️ Incohérence corrigée le 23 août 2026 (repérée par Jean-Marc) : REA_Action était en varchar(3) alors qu'il référence ACT_Code (ASP_Actions), lui-même en varchar(15)** — une valeur de code action de plus de 3 caractères (ex. "CRE_CNT", "MAJ_CNT", "REL_TEL", déjà plus longs que 3 caractères dans les données peuplées de ASP_Actions, voir plus bas) aurait donc été tronquée silencieusement à l'insertion. **Corrigé : REA_Action passe en varchar(15)**, aligné sur ACT_Code. **⚠️ Longueur à nouveau élargie le 26 août 2026 : REA_Action, ACT_Code et OPE_Action (voir section 18) passent tous à varchar(20), et ACT_Libelle à varchar(40)**, pour accueillir les codes plus longs introduits avec la prospection (ex. `WEB-FORM-001-CRE`). Point de vigilance pour la suite : toujours vérifier qu'un champ de liaison (FK applicative, sans contrainte SQL formelle — voir le principe déjà en place pour CNT_Statut/CNT_CodeProfession) a bien la même longueur que la colonne qu'il référence, ASPIC n'ayant pas de contrainte FOREIGN KEY SQL pour le détecter automatiquement.
**✅ Points de vigilance identifiés en revue par Claude sur la V01 (20 août 2026), tous corrigés par Jean-Marc et confirmés dans la V02 transmise le même jour :**
- **`UNIQUE KEY` sur REA_CodeUser** était très probablement une erreur de configuration : tel quel, un même code utilisateur n'aurait pu apparaître qu'une seule fois dans toute la table. **Corrigé en V02** : c'est désormais un index simple (`KEY ... USING BTREE`), comme REA_Action.
- **`UNIQUE KEY` sur REA_Date** aurait interdit à deux actions d'être journalisées à la même seconde. **Corrigé en V02** : index simple également.
- **Absence d'index sur REA_IncrContact**, alors que c'est la colonne la plus utile à indexer (retrouver l'historique d'un contact donné depuis l'écran de détail, `WHERE REA_IncrContact = ?`). **Corrigé en V02** : `KEY REA_IncrContact (REA_IncrContact)` ajouté.
- **Coquille détectée dans le fichier SQL transmis** ("Tables V01.txt") : juste avant le bloc `ALTER TABLE ASP_Realisations ... AUTO_INCREMENT`, la ligne de commentaire était `--n` au lieu de `-- ` (tiret-tiret-espace), ce qui aurait pu provoquer une erreur de syntaxe MySQL si ce fichier avait été rejoué tel quel. **Corrigé en V02.**
- **CNT_Actif sans valeur par défaut** — voir ci-dessus dans ASP_Contacts. **Corrigé en V02** (`DEFAULT '1'`).
Retour de Jean-Marc sur cette revue : "bien vu d'ailleurs !" — confirmation que cette étape de revue technique (au-delà de la structure fonctionnelle) apporte de la valeur et est à reproduire sur les prochaines évolutions de schéma.
**Table ASP_Actions** (référence des types d'action, gérée à la main, déjà peuplée)
```sql
CREATE TABLE `ASP_Actions` (
`ACT_Incr` int NOT NULL AUTO_INCREMENT,
`ACT_Code` varchar(20) NOT NULL,
`ACT_Libelle` varchar(40) NOT NULL,
`ACT_Comment` text NOT NULL,
PRIMARY KEY (`ACT_Incr`),
KEY `ACT_Code` (`ACT_Code`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
```
**⚠️ Contenu corrigé le 24 août 2026 — Jean-Marc a changé les codes d'action en base sans le journaliser sur le coup** ("je te confirme que j'ai changé les codes des actions, j'aurais dû te le dire"). **Contenu réel de ASP_Actions, communiqué par Jean-Marc le 24 août 2026, complété le 26 août 2026 avec les codes de prospection (voir section 18) :**
| ACT_Incr | ACT_Code | ACT_Libelle |
|---|---|---|
| 1 | `CNT_CRE` | Création du contact |
| 2 | `CNT_MAJ` | Mise à jour du contact |
| 3 | `CNT_SUP` | Suppression du contact |
| 4 | `CNT_TELS` | Appel Téléphonique Sortant |
| 5 | `CNT_TELE` | Appel Téléphonique Entrant |
| 9 | `WEB-FORM-001-CRE` | Web Form 001. Le prospect s'enregistre |
| 10 | `WEB-FORM-001-VER` | Web Form 001. Vérification du mail |
| 11 | `WEB-FORM-001-VAL` | Web Form 001. L'adresse mail est validée |
| 12 | `WEB-FORM-001-DOC` | Web Form 001. Viktor envoie la doc |
| 13 | `WEB-FORM-001-LEC` | Web Form 001. Le prospect lit la doc |
**Point de vigilance à valeur générale :** une table de référence gérée à la main (ASP_Actions, ASP_Statuts, ASP_Professions, ASP_Campagnes, ASP_Docs) peut être modifiée directement en base sans que Claude en soit informé, puisqu'ASPIC n'a pas de mécanisme de synchronisation — à signaler explicitement à Claude dès qu'un changement de ce type est fait, plutôt que de compter sur le journal pour rester à jour tout seul.
*Suggestion Claude, non urgente : ajouter une contrainte UNIQUE sur ACT_Code, même logique que pour STA_Code ci-dessus.*
**Table ASP_Users (état validé le 19 août 2026, à des fins d'authentification)**
```sql
CREATE TABLE `ASP_Users` (
`USR_Code` varchar(3) NOT NULL,
`USR_Nom` varchar(40) NOT NULL,
`USR_MDP` varchar(15) NOT NULL,
`USR_RatioPanneauContact` tinyint UNSIGNED NOT NULL DEFAULT '100',
`USR_RatioPanneauProspect` tinyint UNSIGNED NOT NULL DEFAULT '100',
PRIMARY KEY (`USR_Code`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
```
**⚠️ Structure mise à jour le 25 août 2026** : Jean-Marc a ajouté les deux colonnes `USR_RatioPanneauContact`/`USR_RatioPanneauProspect` (voir section 17) directement en base, `DEFAULT '100'` conformément à la décision prise le même jour. Deux comptes existent à ce stade dans la table (dont `JMP`) — contenu des lignes volontairement non reporté ici (mots de passe en clair, voir ci-dessous). **Troisième compte, `Vik` (Viktor, premier assistant IA de Jean-Marc), créé le 26 août 2026 — voir section 18** : sert d'identité pour la journalisation automatique des opérations sur les prospects et comme expéditeur des mails, sans usage réel de mot de passe à ce stade (aucune authentification effective des scripts publics de prospection).
**⚠️ Nouvelle colonne `USR_Societe` ajoutée le 29 août 2026** (`varchar(40) DEFAULT NULL`, gérée à la main comme le reste de `ASP_Users`) — voir section 22 pour le détail complet (badge de repérage d'environnement dans le bandeau applicatif de `liste_contacts.html`).
Correspondance avec l'écran d'identification (section 8) : le champ "Identifiant" du formulaire de connexion correspond à USR_Code (code utilisateur court, 3 caractères), pas à USR_Nom. Correspondance également avec REA_CodeUser (ASP_Realisations) et OPE_CodeUser (ASP_Operations, section 18) qui référencent ce même code. **⚠️ Ce paragraphe est désormais un historique : depuis le 30 août 2026 (section 24, MIRADOR), le champ d'identification correspond à MIR_Email, plus à USR_Code — voir cette section.**
Décisions prises (19 août 2026) sur les deux points remontés en relecture :
- **PRIMARY KEY ajoutée sur USR_Code** par Jean-Marc — la table a désormais une vraie clé primaire, cohérent avec ASP_Contacts.
- **Mot de passe stocké en clair (USR_MDP), assumé consciemment.** Choix délibéré de Jean-Marc pour rester agile sur une application strictement interne, avec une gestion des utilisateurs faite directement en phpMyAdmin (pas de module ajout/modification/suppression d'utilisateur dans ASPIC). Ce compromis serait à revoir si ASPIC est un jour proposée à un client externe (cf. section 1, portée). **Voir section 21 : ce compromis est effectivement revu le 28 août 2026, avec l'arrivée de MIRADOR et de futurs bêta testeurs externes. Et section 24 : USR_MDP n'est plus du tout consulté par le code depuis le 30 août 2026, devenu un vestige à retirer quand Jean-Marc le souhaite.**
**À faire :** table ASP_Contacts_SaisieWeb pour le formulaire web public — idée initiale du 19 août, finalement remplacée par le modèle ASP_Prospects/ASP_Campagnes/ASP_Operations (voir section 11, 17 et 18).
**Convention de nommage des fichiers en production (précisée le 21 août 2026) :** les écrans front, livrés par Claude sous des noms descriptifs/versionnés (ex. `ASPIC_ecran_identification.html`, `aspic_liste_contacts.html`), sont renommés par Jean-Marc au moment du transfert FTP vers le serveur, sans préfixe décoratif et en `.php` par uniformité avec le reste de l'application (même si ces fichiers ne contiennent aujourd'hui aucun code PHP — un `.php` sans balise `<?php ?>` est servi à l'identique par Apache+PHP, aucun changement de contenu requis) : `ASPIC_ecran_identification.html` → `identification.php`, `aspic_liste_contacts.html` → `liste_contacts.php`. `login.php` garde déjà son nom final. Jean-Marc conserve de son côté un numéro incrémental sur les fichiers reçus pour garder un historique de versions ; ce renommage/historique est géré entièrement par lui au moment du transfert, sans action particulière attendue de Claude sur les noms de fichiers livrés.
@@ -0,0 +1,63 @@
## 3. Architecture technique
Trois couches, chacune volontairement minimale :
- **Base de données** : MySQL (base ASPIC, utilisateur dédié crm_user — même logique que pour les autres applications du VPS : un compte limité à sa base plutôt que root).
- **API** : quelques fichiers PHP (PDO) exposant le CRUD contacts en JSON, packagés dans un conteneur léger (image officielle php:8.3-apache + extensions pdo_mysql / mysqli).
- **Frontend** : un seul fichier index.html en JavaScript vanilla — pas de build, pas de dépendance, tableau + formulaire d'ajout/modification/suppression appelant l'API en fetch().
### Recette de déploiement réutilisable
Pour éviter de redécouvrir les mêmes pièges, la recette suivante (validée sur une précédente application PHP du VPS) a été réutilisée telle quelle : image officielle + extensions installées à chaud au démarrage (plutôt qu'un Dockerfile/build:, non testé sur ce Docker Manager), et un service SFTP dédié pour déposer les fichiers, avec le chown fait par le conteneur applicatif lui-même à chaque démarrage (plus fiable qu'un conteneur de correction de permissions séparé, dont l'ordre de démarrage n'est pas garanti sur cette interface).
**docker-compose.yml** (situé dans `/docker/aspic/` sur le VPS — chemin confirmé le 30 août 2026, voir section 23)
```yaml
services:
crm-app:
image: 'php:8.3-apache'
restart: unless-stopped
command: >
bash -c "chown -R 1001:1001 /var/www/html && docker-php-ext-install pdo_mysql mysqli && apache2-foreground"
environment:
DB_HOST: "mysql"
DB_NAME: "ASPIC"
DB_USER: "crm_user"
DB_PASS: "***"
BREVO_API_KEY: "***"
LIEN_DOC_CLE_SECRETE: "***"
ROUTER_DB_HOST: "mysql2"
ROUTER_DB_USER: "crm_user"
ROUTER_DB_PASS: "***"
volumes:
- ./src:/var/www/html
networks:
- proxy
sftp:
image: atmoz/sftp:latest
restart: unless-stopped
volumes:
- ./src:/home/jeanmarc/www
ports:
- "2224:22"
command: jeanmarc:***:1001::www
networks:
proxy:
external: true
```
*`BREVO_API_KEY` et `LIEN_DOC_CLE_SECRETE` ajoutées le 26 août 2026 (voir section 18) — renseignées et déployées le jour même, fonctionnelles en production.* **`ROUTER_DB_HOST`/`ROUTER_DB_USER`/`ROUTER_DB_PASS` ajoutées le 30 août 2026 (voir section 24, MIRADOR) — connexion vers l'instance `mysql2` qui héberge désormais MIRADOR.**
### Convention de nommage et de livraison des fichiers (actée le 21 août 2026)
Déployer consiste à copier dans le répertoire de l'application (le volume `./src` monté dans le conteneur, voir `docker-compose.yml` ci-dessus) les fichiers finaux de celle-ci. **Convention de livraison actée :** Claude livre désormais les fichiers sous leur nom final "propre", sans préfixe (`identification.html`, `liste_contacts.html`, `aspic.php`, `contacts.php`, `db.php`) — les anciens préfixes (`ASPIC_ecran_`, `aspic_`) utilisés en début de projet sont abandonnés. Jean-Marc conserve de son côté l'habitude d'incrémenter un numéro de version derrière chaque fichier reçu pour garder un historique, et fait lui-même le renommage final (y compris le passage `.html` → `.php` le cas échéant) au moment du transfert FTP. **Depuis le 30 août 2026 (section 23), ce dossier `./src` est aussi directement la copie de travail du dépôt Git — voir cette section pour le nouveau circuit de livraison/versionnage.**
### Points d'attention retenus pour une prochaine application PHP
- UID 1001 (pas 0/root) pour l'utilisateur SFTP — un compte root est bloqué en authentification par mot de passe par la configuration SSH par défaut de l'image.
- Mot de passe SFTP : éviter les caractères spéciaux actifs en shell (`: , * $ \` & | ; < > ( )` guillemets, espace) — préférer un mot de passe purement alphanumérique.
- Faire le chown directement dans la commande de démarrage du conteneur applicatif (pas via un conteneur séparé) : plus robuste, ne dépend pas de l'ordre de démarrage entre conteneurs.
- Repartir sur un dossier de données neuf en cas de comportement erratique persistant (permissions, authentification) plutôt que de s'acharner sur un dossier qui a accumulé plusieurs changements de configuration.
- **Un conteneur avec un volume bind-mount (`./chemin:/dans/le/conteneur`) reste attaché à l'objet-dossier présent sur l'hôte au moment de son démarrage. Si ce dossier est renommé/recréé sur l'hôte (ex. `mv dossier dossier_old` puis recréation d'un `dossier` neuf) pendant que le conteneur tourne, celui-ci continue de voir l'ancien contenu jusqu'à son redémarrage — voir le piège rencontré en section 23 avec le conteneur SFTP.**
@@ -0,0 +1,9 @@
## 4. Fichiers de l'application
Les identifiants de connexion à la base (et, depuis le 26 août 2026, la clé API Brevo et la clé de signature des liens de doc) sont lus depuis les variables d'environnement du conteneur (jamais codés en dur dans les fichiers).
**src/db.php — connexions PDO** — réécrit le 30 août 2026 pour MIRADOR (voir section 24) : `getDbConnection()` route désormais dynamiquement vers la base de travail propre à la session en cours (posée par `aspic.php` après vérification contre MIRADOR), avec repli sur `DB_HOST`/`DB_NAME` en l'absence de session exploitable (scripts publics) ; nouvelle fonction `getRouterConnection()` pour la connexion fixe à MIRADOR. Voir section 24 pour le détail complet et le code.
**src/contacts.php — API CRUD (GET / POST / PUT / DELETE)** — reconstruit intégralement le 24 août 2026 (voir section 12), aligné sur la vraie structure ASP_Contacts, tri en liste blanche, jusqu'à 7 filtres avec joker "*" (paramètre lié, plus jamais concaténé), `WHERE CNT_Actif = 1`, suppression logique, garde de session (section 15), vérification d'existence séparée du `rowCount()` sur PUT (section 13). **Inchangé depuis le passage à MIRADOR (30 août 2026, section 24) : le routage de base se fait entièrement dans `getDbConnection()`, sans qu'aucun endpoint n'ait à être modifié.**
**src/index.html — prototype initial (sections 3-5)**, remplacé depuis par `identification.html`/`liste_contacts.html` (sections 8-9) et le panneau détail intégré à `liste_contacts.html` (sections 12-17).
@@ -0,0 +1,5 @@
## 5. Points de friction rencontrés et résolus
- Structure de table aux contraintes NULL inversées lors du premier jet : contacts.php et index.html ont dû être réécrits une fois la structure réelle de la table confirmée.
- Tentative d'accès aux fichiers via un compte SFTP "large" pensé comme donnant accès à la racine du VPS : en réalité limité à un sous-ensemble de dossiers système, ne donnant pas accès aux données des projets Docker Compose. Retour à un service SFTP dédié au projet, monté directement sur le même volume que l'application.
- Un premier essai de configuration Docker Compose s'appuyant sur un Dockerfile personnalisé (build:) a été écarté au profit de l'image officielle + extensions installées à chaud, cette dernière approche étant déjà validée sur une application précédente et donc sans risque d'incompatibilité avec le Docker Manager utilisé.
@@ -0,0 +1,46 @@
## 6. Pistes pour la suite
**Évolution du modèle de données**
- Structure réelle de ASP_Contacts, ASP_Annees, ASP_Statuts, ASP_Realisations, ASP_Actions, ASP_Professions, ASP_Users : voir section 2, à jour.
- ASP_Prospects, ASP_Campagnes, ASP_Operations, ASP_Mails, ASP_Docs : nouvelles tables du 26 août 2026, voir section 18.
- MIRADOR (MIR_Users) : base d'authentification/dispatch multi-instances, voir section 21 (conception) et section 24 (branchement effectif, 30 août 2026).
**Évolution graphique du frontend**
Deux directions possibles, à trancher : rester en JavaScript vanilla enrichi (cohérent avec le choix d'architecture initial), ou viser une charte visuelle plus travaillée si l'application est amenée à être livrée telle quelle à des clients. Le prototype d'écran d'identification (section 8) tranche pour l'instant en faveur d'un JS vanilla enrichi, avec une vraie charte graphique ASPIC (couleurs, typographie Metro DF).
**Cible d'affichage et outil de maquettage (précisé le 19 août 2026)**
- Écrans conçus pour un usage desktop uniquement ; pas de gestion spécifique du mobile pour l'instant.
- Résolution de référence : 1920×1080 (écrans de travail de Jean-Marc).
- Principe de conception : pas de scroll sur les écrans — le contenu doit tenir dans la hauteur visible, quitte à adapter les tailles/densités des éléments plutôt que de laisser déborder.
- Outil de maquettage retenu : Penpot, auto-hébergé sur le VPS, utilisé en solo. Prise en main en pause (19-20 août 2026) ; premiers jets d'écrans transmis directement en image ou en tableur, ça fonctionne tout aussi bien pour la revue.
**Écran "Liste des contacts" et écran "Détail du contact"**
Statut détaillé en sections 6 (historique complet des décisions d'ergonomie), 9 (implémentation) et 12-17 (branchement aux données réelles, ergonomie de visualisation/modification, création/suppression, panneau redimensionnable). Les deux écrans, plus l'écran d'identification, sont considérés comme aboutis par Jean-Marc au 25 août 2026. **Décision du 26 août 2026 : cette partie "gestion des contacts" est laissée en l'état pour l'instant, le développement bascule sur la prospection (section 18).**
**Décisions de modélisation discutées le 19 août 2026**
- *JOIN ASP_Realisations / ASP_Contacts (relation 1-N sur CNT_Incr) :* pas de vue SQL pour l'instant, deux requêtes distinctes plutôt qu'un JOIN. Une vue ne sera envisagée que si le même JOIN se retrouve dupliqué à l'identique dans plusieurs endpoints (règle empirique : à partir de 3 réutilisations) — **cette règle a été explicitement réinvoquée et appliquée le 26 août 2026 pour trancher la question d'une "table des scénarios" de prospection (voir section 18) : pas assez de répétitions concrètes pour généraliser un moteur, on reste sur du code dédié par parcours. Réinvoquée une troisième fois le 28 août 2026 pour l'architecture MIRADOR (voir section 21) : pas de table "instances" séparée pour factoriser serveur/port, dupliqué par ligne utilisateur tant que le nombre d'instances reste faible.**
- *Formulaire web public de saisie de contact :* idée initiale d'une table ASP_Contacts_SaisieWeb à contraintes assouplies, promue manuellement vers ASP_Contacts — **remplacée le 26 août 2026 par le modèle ASP_Prospects (section 18), plus riche (campagnes, vérification mail, suivi de lecture) et mieux aligné avec le pivot du 23-25 août sur la prospection multi-campagnes (sections 11 et 17).**
**Décisions sur la suppression et l'historisation des données (20 août 2026)**
- *Suppression logique, jamais physique, pour les données métier réelles.* Choix de principe de Jean-Marc, appliqué systématiquement sur ASPIC (CNT_Actif à 0, jamais de DELETE SQL réel sur les données importantes) — **repris à l'identique pour les prospects via PSP_Actif (section 18), et pour MIRADOR via MIR_Actif (section 21). Exception assumée pour des données de test technique (pas des prospects réels) : suppression physique explicitement demandée et effectuée par Jean-Marc le 26 août 2026 pour nettoyer un enregistrement de test avant de relancer un essai — voir section 18.**
- *Pas d'historisation des modifications de champs.* Un UPDATE écrase simplement l'ancienne valeur, sans table d'historique.
**Automatisations futures (évoqué le 19 août 2026)**
Objectif à terme : greffer des automatisations sur ASPIC, en particulier un déclenchement lorsque des enregistrements apparaissent depuis un formulaire public. **Premier pas concret réalisé et testé avec succès en production le 26 août 2026 (section 18)** : formulaire de prospection avec vérification mail et envoi automatique de documentation, sans intervention manuelle de Jean-Marc.
**Portabilité**
- Transport d'une version du CRM vers mvl-dcs.fr évoqué par Jean-Marc, non planifié dans l'immédiat.
- Objectif à terme : que cette même base PHP + MySQL soit directement réutilisable chez un client sur un hébergement mutualisé classique, sans Docker ni VPS.
- Note (19 août 2026) : ASPIC est pour l'instant à usage strictement interne — la portabilité reste un objectif d'architecture, pas un besoin client immédiat. **La fonctionnalité de prospection (section 18) est un cas à part : conçue pour l'usage propre de Jean-Marc (Viktor, Brevo, n8n potentiellement), pas nécessairement destinée à être reproduite telle quelle chez un client — contrairement au cœur CRM (contacts).**
**Protection du code et modèle de déploiement pour de futurs projets clients (22 août 2026)**
Réflexion engagée par Jean-Marc sur un sujet hypothétique, anticipant un futur déploiement d'applications de ce type chez des clients externes. Constat technique établi : aucune protection réelle n'existe côté JavaScript navigateur ; côté PHP, des encodeurs bytecode existent mais supposent un loader installé, incompatible avec l'hébergement mutualisé bas de gamme visé. Stratégie retenue selon la nature du projet (sur mesure pour un client vs. produit verticalisé mutualisé, avec point RGPD à vérifier avec un professionnel du droit dans le second cas) — ASPIC reste hors sujet dans l'immédiat.
@@ -0,0 +1,17 @@
## 7. Statut actuel
**Écrans "Identification" et "Liste des contacts" (avec panneau détail intégré) : considérés comme aboutis par Jean-Marc au 25 août 2026**, testés contre ses vraies données en production (aspic.mvl-tls.fr). Détail complet en sections 8, 9 et 12-17. Session utilisateur PHP native implémentée (section 15), panneau détail redimensionnable avec préférence par utilisateur (section 17). **Décision du 26 août 2026 : cette partie est laissée en l'état, le développement bascule sur la prospection.**
**26 août 2026 : nouvelle fonctionnalité — ajout d'un prospect depuis un formulaire externe, avec vérification d'adresse mail et envoi automatique de documentation.** Voir section 18 pour le détail complet. **Testée avec succès en conditions réelles le jour même** : formulaire (`mevelia-solutions.bzh/form`) → création du prospect → mail de vérification HTML (bouton + logo) → clic → adresse validée → envoi automatique de la doc en HTML (bouton) — chaîne complète validée en production sur `aspic.mvl-tls.fr`. Deux bugs rencontrés et corrigés en cours de test (dérive de schéma sur `ASP_Prospects`, variable `{(Lien)}` absente d'un modèle de mail), puis évolution demandée et livrée le jour même pour passer les mails en HTML (logo, bouton) — détail complet en section 18. Reste non urgent, mis de côté pour une prochaine session : peaufiner le visuel du formulaire public (`prospects_form_web.html`).
**27-28 août 2026 : mise en place d'un dépôt Git pour ASPIC.** Voir section 20 pour le détail complet (dépôt GitHub, mirroir Gitea, limite technique découverte sur l'accès en écriture depuis Claude, structure finale du dépôt).
**28 août 2026 (soir) : architecture MIRADOR (authentification centralisée multi-instances) et numéro de version applicatif.** Voir section 21 pour le détail complet — schéma `MIR_Users` proposé (non encore déployé au-delà d'une ligne de test), fonctionnalité de numéro de version (`V1.000`) livrée, testée et validée en production le jour même.
**29 août 2026 : badge société dans le bandeau (repère visuel d'environnement) et avatar utilisateur enfin dynamique.** Voir section 22 pour le détail complet — nouvelle colonne `USR_Societe` sur `ASP_Users`, badge et avatar unifiés dans la même couleur bleue, version applicative passée à `V1.001`.
**30 août 2026 : push GitHub directement depuis le VPS en SSH (le dossier de déploiement `./src` devient aussi le dépôt Git), réconciliation GitHub/production, correctif V1.002.** Voir section 23 pour le détail complet — le blocage d'écriture côté proxy Claude (section 20) reste entier et n'a pas été contourné, mais Jean-Marc peut désormais pousser lui-même en quelques commandes depuis un terminal sur son VPS, sans repasser par le glisser-déposer web.
**30 août 2026 (suite) : retrait du compte de démonstration, et MIRADOR effectivement branché sur l'authentification (identifiant = email, routage dynamique multi-bases).** Voir section 24 pour le détail complet — `db.php`/`aspic.php`/`identification.html` livrés, aucun des autres endpoints modifié, nouvelle infrastructure à trois instances MySQL (mysql/mysql2/mysql3) et bases MS-DEV/MS-TST mise en place par Jean-Marc en amont.
**30 août 2026 (soir) : MIRADOR validé avec un deuxième utilisateur, nettoyage des bases devenues obsolètes, `USR_MDP` retiré, déploiement sur `aspic.bzh` — version V1.003.** Voir section 25 pour le détail complet — tests réussis, bases `ASPIC`/`MIRADOR` de l'instance `mysql` supprimées, colonne `USR_MDP` retirée de `ASP_Users` sur MS-DEV et MS-TST, application déployée sur `aspic.bzh` en plus de `aspic.mvl-tls.fr` (deux environnements strictement identiques, chacun avec son propre environnement Docker), accès via deux tuiles Homarr. Push GitHub du commit `V1.003` vérifié réussi par Claude (lecture directe du dépôt).
@@ -0,0 +1,7 @@
## 8. Écran d'identification — prototype (19 août 2026, finalisé les 22-23 août 2026)
Premier écran de l'application (plein écran, sans scroll). Bandeau fixe (acrostiche ASPIC, photo d'un aspic), panneau "Utilisateur" centré (Identifiant = USR_Code, Mot de passe avec bouton œil, bouton Valider). Police Metro DF mutualisée en deux fichiers (`MetroDF.woff2`/`.ttf`, `@font-face` en URL relative). Compteur d'essais de connexion (3 essais, sortie de l'application au 3e échec). Panneau d'erreur rouge superposé, recentré sur le panneau de saisie. Échap ferme d'abord un panneau d'erreur ouvert, sinon fait sortir de l'application (avec écran de repli gris "Vous pouvez fermer cette fenêtre" en filet de sécurité). `login.php` écrit puis renommé `aspic.php`. **⚠️ Compte de démonstration (`UTILISATEURS_DEMO`) : signalé encore présent sans être retiré à de multiples reprises (28, 29, 30 août — sections 21, 22, 23), finalement retiré le 30 août 2026 (section 24).** Champ "Identifiant" du formulaire : `USR_Code` jusqu'au 30 août 2026, puis `MIR_Email` (section 24, MIRADOR). **Validé sans réserve par Jean-Marc le 23 août 2026 : "il me semble que tout va bien, je suis content !"**
Point de méthode retenu de cette période : un écran qui "ne se met pas à jour" après déploiement peut être dû au gestionnaire de mots de passe du navigateur (qui outrepasse `autocomplete="off"`), pas seulement au cache — déjà rencontré une fois, à garder en tête.
**Constat du 30 août 2026 (section 23) : la comparaison directe entre le dépôt GitHub et le vrai dossier de production a finalement été faite (`diff -rq`) — `identification.html` s'est révélé identique entre les deux. Le soupçon de décalage sur ce fichier précis (sections 21-22) était donc infondé ; le compte de démonstration, lui, était bien présent des deux côtés — retiré depuis, voir section 24.**
@@ -0,0 +1,12 @@
## 9. Écran "Liste des contacts" — prototype (20 août 2026), finalisation (22-23 août 2026)
Deuxième écran de l'application, considéré abouti au 23 août 2026. Tableau zébré (jaune/bleu pâle) des contacts actifs (Nom/Prénom/Dépt/Ville/Tél/Portable/Mail), sélection en `box-shadow: inset` vert sauge, navigation clavier (flèches, PgUp/PgDn), tri sur Nom/Ville avec flèche orange indiquant le sens actif, 23 lignes visibles à l'écran de Jean-Marc. Entrée sur une ligne ouvre le détail en visualisation, double-clic gauche ouvre directement en modification. Menu contextuel (clic droit) : Ajouter un nouveau contact / Afficher le détail de ce contact / Filtrer — ce dernier absent si le clic droit est fait en zone vide. Panneau de filtre en grille "libellé ⋯⋯⋯ champ", joker "*" en préfixe, case "Supprimer les filtres", Entrée = Appliquer. Menu hamburger vers les grandes sections de l'app (Gestion des contacts actif, Gestion des actions et Documentation grisées). Échap ferme les panneaux superposés dans l'ordre menu contextuel → hamburger → filtre, ou ramène à la liste depuis l'écran de détail.
**Bugs marquants résolus (22-23 août 2026), enseignements à valeur générale pour le projet :**
- **Alignement colonnes/en-tête** : la vraie cause était un padding horizontal asymétrique entre l'en-tête et les lignes de données (pas `flex-shrink` comme cru initialement) — **pattern impératif pour tous les futurs écrans de liste ASPIC : en-tête et lignes doivent avoir exactement le même padding horizontal total.**
- **Bug SQL critique du joker "\*"** : une clause `ESCAPE` mal construite cassait toute recherche avec joker en production (MySQL/MariaDB), alors qu'elle passait sans erreur en test contre SQLite — **point de méthode à valeur générale : pour toute requête SQL manipulant des antislashs/littéraux spéciaux, toujours tester contre une vraie instance MySQL/MariaDB, jamais SQLite seul.** Depuis `contacts.php` (24 août 2026), le caractère d'échappement est passé en paramètre lié, plus jamais concaténé dans le texte SQL — élimine cette classe de bug par construction.
- Mesure de largeurs de colonnes en Chromium headless : toujours prévoir une marge de sécurité généreuse (15-20 %), la police y rend systématiquement plus étroite que sous le vrai Chrome/Windows de Jean-Marc.
- Préférer les événements natifs du navigateur (`dblclick`, etc.) à une détection manuelle par minuterie quand c'est possible.
**Validé par Jean-Marc le 23 août 2026 : "Les champs s'affichent correctement, c'est très bien pour moi."**
@@ -0,0 +1,5 @@
## 10. Écran "Détail du contact" — conception et principes retenus (20-23 août 2026)
Panneau ouvert par-dessus la liste (empilement à un seul niveau), deux états (visualisation lecture seule / modification), bascule via Entrée (visualisation), double-clic gauche ou "Modifier le contact" (modification). Menu contextuel en visualisation : Modifier le contact / Supprimer le contact / Revenir à la liste des contacts. Échap : modification → visualisation (sans conserver la saisie) → liste. Deux colonnes de champs (grille libellé/pointillé/champ, taille alignée sur les lignes de la liste, 11px/16px), Code en lecture seule, Statut et Profession en liste déroulante (Profession avec création à la volée "+ Nouvelle profession…"). Sous-liste des réalisations en lecture seule (Date/Qui ?/Action/Commentaires = REA_Comment, pas ACT_Comment), pleine largeur sous la grille de champs, zébrée, avec ascenseur et pied "nn réalisations d'action". Largeur du panneau : 900px.
**Panneau redimensionnable (25 août 2026, section 17 pour le détail complet des échanges) :** barre de séparation déplaçable entre la grille des champs et la sous-liste des réalisations, en mode visualisation uniquement, bornes de la ligne Nom seule à la grille complète. Ratio mémorisé par utilisateur en pourcentage (0-100), colonne `USR_RatioPanneauContact` sur ASP_Users, endpoint `preferences.php`.
@@ -0,0 +1,11 @@
## 11. Traitements de données de l'écran détail (24-25 août 2026)
**Branchement aux vraies données (24 août 2026) :** trois nouveaux endpoints — `professions.php` (GET liste + POST création avec détection de doublon), `statuts.php` (GET seul), `realisations.php` (GET historique d'un contact + POST journalisation, `REA_Date` déterminée côté serveur avec `NOW()`).
**Session utilisateur (24 août 2026, section 15 pour le détail) :** mécanisme PHP natif (`session_start()` + `$_SESSION`), tous les endpoints protégés exigent `$_SESSION['USR_Code']` (y compris en lecture), `REA_CodeUser` lu uniquement depuis la session (non falsifiable côté client). Les 5 fichiers concernés (`aspic.php` + 4 endpoints) sont interdépendants et doivent être déployés ensemble.
**Traitement "Valider les modifications" :** PUT `contacts.php` puis POST `realisations.php` (`REA_Action = CNT_MAJ`, best-effort — un échec de journalisation seul n'annule pas la modification déjà enregistrée).
**Bug corrigé (24 août 2026) :** `rowCount()` après un `UPDATE` reflète les lignes réellement modifiées, pas les lignes trouvées par le `WHERE` — un `PUT` qui ne change rien (valeurs identiques) était donc traité à tort comme "contact introuvable" (404). Corrigé en vérifiant l'existence par un `SELECT` séparé.
**Création et suppression, enfin implémentées (25 août 2026, section 16 pour le détail) :** génération de `CNT_Code` en deux temps (recherche d'`ANN_Code` avant l'`INSERT`, échec net si l'année n'a pas de ligne dans ASP_Annees plutôt que de laisser un contact sans code). Nouveau mode `mode-suppression` sur le panneau détail, commentaire obligatoire, bouton désactivé tant qu'il est vide. Textes dynamiques du bouton et du placeholder selon le mode (modification/création/suppression).
@@ -0,0 +1,3 @@
## 12. Clarification — "notion de session utilisateur" (24 août 2026)
Une session serveur (comme phpMyAdmin) qui reste ouverte après identification jusqu'à expiration par inactivité. **Correction d'une imprécision de Claude, répétée plusieurs fois dans ce journal : la génération de `CNT_Code` n'a jamais été bloquée par la session (elle ne dépend que de `CNT_Incr`) — seule la journalisation `ASP_Realisations` (savoir QUI a fait l'action) en dépendait réellement.** Résolu depuis (section 11).
@@ -0,0 +1,3 @@
## 13. Prochaines étapes envisagées côté frontend (report)
Le détail complet des itérations graphiques (couleurs, alignement, largeurs de colonnes, restyling des panneaux, bug du pointillé du panneau de filtre, etc.) reste tracé au fil des sections 6, 8, 9 et 10 ci-dessus — pas dupliqué ici pour éviter la redondance.
@@ -0,0 +1,3 @@
## 14. Pivot annoncé sur le modèle de prospection (23 août 2026)
Message de Jean-Marc en fin de session, posé sans être traité sur le moment : ses futures campagnes de scraping (LinkedIn, Google Maps) vont générer des populations de prospects trop hétérogènes pour remonter directement dans ASP_Contacts. Nouvelle direction envisagée : un fichier prospects distinct par campagne, ASP_Contacts redéfinie comme réservée aux prospects qualifiés uniquement. Conséquence sur les réalisations : de nouvelles actions à ajouter à ASP_Actions (envoyer un mail, un sms, appeler, prendre RDV, devis, facture) — pas encore fait à ce stade.
@@ -0,0 +1,3 @@
## 15. Session utilisateur — implémentation (24 août 2026)
Voir section 11 pour la synthèse ; détail technique (code, fichiers modifiés) inchangé depuis, non redupliqué ici.
@@ -0,0 +1,3 @@
## 16. Panneau de détail du contact — ergonomie et traitements de création/suppression (25 août 2026)
Voir section 11 pour la synthèse des traitements ; ajustements graphiques (largeur des champs Téléphone/CP/SIRET, tri des statuts par ordre de création, majuscule forcée sur Nom/Ville, correction du bug "null" affiché pour les valeurs vides) tracés en détail dans l'historique de session, non redupliqués ici.
@@ -0,0 +1,5 @@
## 17. Panneau détail redimensionnable et prospection multi-campagnes (25 août 2026)
**Panneau redimensionnable :** voir section 10 pour la synthèse. Persistance du ratio par utilisateur sur `ASP_Users` (`USR_RatioPanneauContact`/`USR_RatioPanneauProspect`, ce dernier prêt pour le futur écran Prospects), mesure dynamique des bornes (`scrollHeight`), aucune contrainte hors du mode visualisation.
**Réflexion sur l'architecture de prospection (non implémentée à ce stade, reprise et concrétisée le 26 août 2026 — voir section 18) :** discussion sur le risque de conflit d'incrément lors de la bascule d'un prospect vers ASP_Contacts (résolu : laisser MySQL gérer l'AUTO_INCREMENT, jamais de `SELECT MAX()+1` fait à la main) et sur le choix entre une table par campagne vs. une table générique avec colonne segment/campagne — **cette seconde option a été retenue et mise en œuvre le 26 août 2026 (ASP_Prospects + ASP_Campagnes, section 18).**
@@ -0,0 +1,144 @@
## 18. Prospection — formulaire de prospect externe, vérification mail, suivi de lecture documentation (26 août 2026)
**Contexte.** Reprise du pivot amorcé le 23-25 août (sections 14 et 17) : premier chantier concret de prospection, avec un formulaire web public comme premier canal d'entrée. Décision actée en tout début de session : la partie "gestion des contacts" est laissée en l'état (voir section 7), le développement bascule sur cette nouvelle fonctionnalité. **Chantier mené jusqu'au bout dans la même session : conception, développement, déploiement et test réel réussi en production.**
**Modèle de données — nouvelles tables (structures finales, créées par Jean-Marc au fil de la session).**
```sql
CREATE TABLE `ASP_Campagnes` (
`CMP_Incr` int NOT NULL AUTO_INCREMENT,
`CMP_CodeCampagne` varchar(20) NOT NULL,
`CMP_Explications` text,
`CMP_DocDefaut` int DEFAULT NULL,
`CMP_Creation` date NOT NULL DEFAULT (CURRENT_DATE),
PRIMARY KEY (`CMP_Incr`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
CREATE TABLE `ASP_Prospects` (
`PSP_Incr` int NOT NULL AUTO_INCREMENT,
`PSP_Nom` varchar(40) DEFAULT NULL,
`PSP_Prenom` varchar(40) DEFAULT NULL,
`PSP_Statut` varchar(3) DEFAULT NULL,
`PSP_Societe` varchar(40) DEFAULT NULL,
`PSP_Service` varchar(40) DEFAULT NULL,
`PSP_Fonction` varchar(40) DEFAULT NULL,
`PSP_AdrLig1` varchar(40) DEFAULT NULL,
`PSP_AdrLig2` varchar(40) DEFAULT NULL,
`PSP_AdrLig3` varchar(40) DEFAULT NULL,
`PSP_AdrCP` varchar(6) DEFAULT NULL,
`PSP_AdrVille` varchar(40) DEFAULT NULL,
`PSP_SIRET` varchar(20) DEFAULT NULL,
`PSP_Tel` varchar(15) DEFAULT NULL,
`PSP_Portable` varchar(15) DEFAULT NULL,
`PSP_Mail` varchar(40) DEFAULT NULL,
`PSP_Notes` text,
`PSP_CodeProfession` int DEFAULT NULL,
`PSP_CodeCampagne` varchar(20) NOT NULL,
`PSP_IncrContact` int DEFAULT NULL,
`PSP_MailVerifie` tinyint(1) NOT NULL DEFAULT '0',
`PSP_TokenVerification` varchar(64) DEFAULT NULL,
`PSP_TokenExpiration` datetime DEFAULT NULL,
`PSP_Actif` tinyint(1) NOT NULL DEFAULT '1',
PRIMARY KEY (`PSP_Incr`),
KEY `PSP_Statut` (`PSP_Statut`),
KEY `PSP_CodeProfession` (`PSP_CodeProfession`),
KEY `PSP_CodeCampagne` (`PSP_CodeCampagne`),
KEY `PSP_IncrContact` (`PSP_IncrContact`),
UNIQUE KEY `PSP_TokenVerification` (`PSP_TokenVerification`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
CREATE TABLE `ASP_Operations` (
`OPE_Incr` int NOT NULL AUTO_INCREMENT,
`OPE_IncrProspect` int NOT NULL,
`OPE_CodeUser` varchar(3) NOT NULL,
`OPE_Date` datetime NOT NULL,
`OPE_Action` varchar(20) NOT NULL,
`OPE_Comment` text,
PRIMARY KEY (`OPE_Incr`),
KEY `OPE_IncrProspect` (`OPE_IncrProspect`),
KEY `OPE_CodeUser` (`OPE_CodeUser`) USING BTREE,
KEY `OPE_Action` (`OPE_Action`) USING BTREE,
KEY `OPE_Date` (`OPE_Date`) USING BTREE
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
CREATE TABLE `ASP_Mails` (
`MAI_Incr` int NOT NULL AUTO_INCREMENT,
`MAI_Code` varchar(30) NOT NULL,
`MAI_Objet` varchar(150) NOT NULL,
`MAI_Corps` text NOT NULL,
`MAI_CorpsHtml` text DEFAULT NULL,
PRIMARY KEY (`MAI_Incr`),
UNIQUE KEY `MAI_Code` (`MAI_Code`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
CREATE TABLE `ASP_Docs` (
`DOC_Incr` int NOT NULL AUTO_INCREMENT,
`DOC_Libelle` varchar(40) NOT NULL,
`DOC_URL` varchar(255) NOT NULL,
PRIMARY KEY (`DOC_Incr`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
```
Toutes gérées à la main (comme ASP_Statuts/ASP_Actions/ASP_Professions), sauf ASP_Prospects et ASP_Operations qui sont alimentées par le code. Voir section 2 pour les évolutions de longueur associées sur ASP_Actions/ASP_Realisations (`ACT_Code`/`REA_Action`/`OPE_Action` en varchar(20), `ACT_Libelle` en varchar(40)). **`MAI_CorpsHtml` ajoutée en fin de session (voir plus bas, passage des mails en HTML).**
**Conventions actées.**
- Préfixe `PSP_` pour tous les champs de ASP_Prospects, choisi par Jean-Marc pour bien distinguer ce domaine de celui des contacts (`CNT_`).
- **Réutilisation de ASP_Actions comme table de référence commune aux contacts et aux prospects** (plutôt qu'une table séparée) — argument de Jean-Marc, validé : garantit la cohérence en lecture de l'historique le jour où un prospect bascule en contact qualifié (voir `PSP_IncrContact`, prévu pour cette bascule mais pas encore implémentée).
- **Convention de dérivation des codes ASP_Actions à partir du code de campagne** : `<campagne>-CRE` (création du prospect), `<campagne>-VER` (mail de vérification envoyé), `<campagne>-VAL` (adresse validée), `<campagne>-DOC` (doc envoyée), `<campagne>-LEC` (doc lue). Le code PHP dérive ces codes automatiquement à partir du code de campagne — une nouvelle campagne qui suit ce même schéma ne demande aucune configuration supplémentaire côté code.
- `MAI_Code` suit le même principe pour le mail de vérification (`<campagne>-DEM`), mais **Jean-Marc a tranché pour des modèles de mail dédiés à chaque usage plutôt que mutualisés**, même en cas de contenu identique au départ — argument : traçabilité de ce qui a réellement été envoyé (un modèle modifié après coup ferait perdre la trace du texte exact envoyé à une vague de prospects), et possibilité de faire des tests A/B entre variantes. Le mail "doc" ne suit donc pas la même dérivation automatique que les codes ASP_Actions : son `MAI_Code` est `<campagne>-DOC`, choisi manuellement par Jean-Marc au moment de la création de la ligne.
- Syntaxe des variables dans `MAI_Corps`/`MAI_CorpsHtml`/`MAI_Objet` : `{(NomVariable)}`, casse PascalCase (`{(Prenom)}`, `{(Nom)}`, `{(Lien)}`, `{(Titre)}`, `{(LogoUrl)}`). **Point de vigilance retenu suite à une erreur de Claude en fin de session : n'écrire les caractères accentués qu'en clair dans le SQL (la base est nativement UTF-8), jamais en séquences d'échappement `\u00xx` (syntaxe JavaScript/JSON, non reconnue par MySQL — laisse un texte du type "demandu00e9" au lieu de "demandé").**
- `OPE_CodeUser` systématiquement `'Vik'` pour tous les événements automatiques journalisés dans ASP_Operations — ces événements ne sont jamais déclenchés par un utilisateur ASPIC connecté au sens de la session PHP (section 15).
- `ORIGINES_CORS_AUTORISEES` (dans `prospects_core.php`) : `https://mevelia-solutions.bzh` (confirmé sans `www`, formulaire déposé sur `mevelia-solutions.bzh/form`).
**Nouvel utilisateur système — Viktor.** Ligne créée dans ASP_Users, `USR_Code = 'Vik'`, pour "Viktor", le premier assistant IA de Jean-Marc. Sert d'identité pour la journalisation automatique (ASP_Operations) et comme expéditeur des mails (`viktor@ms.bzh`). Pas de mot de passe fonctionnel utilisé à ce stade : le formulaire public et les scripts associés ne passent pas par le mécanisme de connexion/session existant (`aspic.php` + `$_SESSION`, section 15) — ils écrivent le code `'Vik'` directement. Le mot de passe de Viktor aura un rôle réel le jour où des automatisations externes (agents IA de suivi, orchestration n8n — voir section 17) devront appeler les endpoints protégés existants (contacts.php, realisations.php…) en son nom — pas nécessaire pour ce chantier.
**Envoi de mail — choix de Brevo.** Reprise du point resté ouvert depuis le 19 août (section 6) : une tentative antérieure avec Brevo semblait avoir perturbé la réception Outlook sur la même boîte mail. Cause probable identifiée en discussion : configuration DNS non isolée entre domaine d'envoi et boîte de réception (Jean-Marc a confirmé avoir réussi à faire cohabiter Mailjet et Outlook dans une précédente société, moyennant un paramétrage DNS spécifique). **Solution retenue :** domaine d'envoi dédié `ms.bzh` (déjà chez OVH, partiellement paramétré lors d'essais Budibase antérieurs), adresse d'expédition `viktor@ms.bzh`, et un `Reply-To` séparé pointant vers `contact@mevelia-solutions.bzh`. Appel direct à l'API REST Brevo en PHP (cURL, sans bibliothèque) — pas de relais SMTP à configurer dans le conteneur. Clé API (format `xkeysib-...`, bien la clé API REST et non les identifiants SMTP distincts de Brevo — point de vigilance signalé par Claude avant déploiement, confirmé correct par Jean-Marc) stockée dans `BREVO_API_KEY` (même principe que `DB_PASS`), générée sans date d'expiration et déployée avec succès en fin de session.
**Envoi immédiat, pas de file d'attente.** Décision de Jean-Marc : le mail de vérification doit partir immédiatement après la soumission du formulaire, dans la même requête HTTP — pas de cron ni de déclenchement différé pour cette étape (contrairement à une première piste envisagée avec n8n, écartée pour ce besoin précis une fois la contrainte d'immédiateté clarifiée ; le sujet cron/n8n redeviendra pertinent pour de futures étapes différées d'un parcours de prospection).
**Vérification de l'adresse mail — mécanisme retenu.** Un token aléatoire imprévisible (`random_bytes(32)`, 64 caractères hexadécimaux), valable 48h, inséré dans un lien de vérification envoyé par mail. Une piste alternative (valider par la réponse du prospect au mail, avec traitement automatique du contenu entrant via IMAP) a été explorée en détail puis **abandonnée par Jean-Marc lui-même comme trop complexe pour ce premier jet** ("pas besoin de sortir l'artillerie lourde") — un simple clic suffit. `PSP_MailVerifie`/`PSP_TokenVerification`/`PSP_TokenExpiration` portent cet état directement sur ASP_Prospects (pas de champ de date de création/conversion séparé sur la table : ces événements sont journalisés dans ASP_Operations, cohérent avec le principe déjà en place pour ASP_Contacts/ASP_Realisations où la date de création n'est pas un champ mais un enregistrement).
**Anti-spam.** Champ honeypot (champ caché, invisible pour un humain via CSS, laissé accessible pour un robot) sur le formulaire public — soumission silencieusement ignorée (réponse 200 neutre) si ce champ est rempli. Choisi plutôt qu'un captcha, pour rester léger.
**Nommage des scripts par campagne — décision de Jean-Marc, avec une factorisation proposée par Claude.** Jean-Marc voulait des scripts publics nommés explicitement par campagne (`prospect_<campagne>_ajoute.php`), quitte à dupliquer du code, pour s'y retrouver facilement à mesure que le nombre de campagnes grandit — avec un vrai bénéfice de sécurité identifié en discussion : le code de campagne n'est jamais un paramètre manipulable par le visiteur, il est implicite dans le fichier appelé (contrairement à un `?campagne=...` en paramètre). Pour éviter la duplication de la logique elle-même (risque déjà rencontré sur ce projet, cf. le bug CNT_MAJ/MAJ_CNT, section 2), la logique commune vit dans `prospects_core.php`, et chaque campagne n'a qu'un tout petit fichier qui l'appelle avec son code en dur (`prospect_web-form-001_ajoute.php`, 3 lignes). Le script de vérification (`prospects_verifier.php`) et celui de suivi de lecture (`prospect_doc.php`) restent en revanche communs à toutes les campagnes : rien dans leur logique ne dépend de savoir à l'avance quelle campagne est concernée (l'information est déjà portée par la ligne trouvée en base).
**Suivi de la lecture de la documentation — lien signé.** Le mail "doc" contient un lien vers `prospect_doc.php?a=<PSP_Incr>&b=<DOC_Incr>&c=<CMP_Incr>&d=<signature>`, qui journalise la lecture puis redirige (302) vers l'URL réelle du document (table ASP_Docs, gérée à la main — `DOC_URL` modifiable indépendamment du mail déjà envoyé, si le document change d'adresse après coup ; un des arguments de Jean-Marc en faveur de cette indirection). Le paramètre `d` est une **signature HMAC-SHA256** (tronquée à 16 caractères hexadécimaux) des paramètres a/b/c, calculée avec une clé secrète côté serveur (`LIEN_DOC_CLE_SECRETE`, générée par Jean-Marc et déployée en fin de session) — empêche un visiteur de modifier les paramètres de l'URL sans que ce soit détecté (comparaison en temps constant via `hash_equals()`, même principe que la vérification du mot de passe dans `aspic.php`, section 15). **Motivé par un vrai précédent vécu par Jean-Marc** (manipulation de paramètres constatée sur ses annonces eBay il y a 20 ans par un tiers ayant réutilisé ses URLs d'images). En cas de signature invalide, redirection neutre vers la page provisoire de `mevelia-solutions.bzh` plutôt qu'une page d'erreur qui confirmerait la tentative au visiteur (et plutôt qu'une redirection délibérément désagréable, évoquée en plaisanterie par Jean-Marc puis écartée d'un commun accord : un domaine qui redirige vers un contenu problématique risquerait d'être mal classé par les filtres anti-spam/proxys d'entreprise qui scannent les liens des mails — l'inverse de l'objectif de délivrabilité recherché par toute la démarche).
*Point de vigilance signalé par Claude, accepté comme non bloquant pour cette v1 : la signature empêche bien la modification de `a`/`b`/`c` sans la clé secrète ; le point de vigilance résiduel est mineur (numérotation séquentielle visible dans un lien légitimement reçu, sans conséquence pratique au-delà de révéler approximativement le volume de prospects).*
**Table des scénarios d'entrée — idée jugée pertinente mais reportée.** Jean-Marc a proposé, en cours de session, l'idée d'une table générique de "scénarios" décrivant les étapes d'entrée d'un prospect (explicitement distincte du suivi commercial ultérieur, volontairement laissé sans script rigide — parallèle fait par Jean-Marc lui-même avec les anciens chatbots à arbre de décision, cassants au premier cas non prévu). Trois exemples concrets donnés par Jean-Marc : formulaire web (avec vérification et abandon — "on brûle le prospect" — si pas de réponse), scraping LinkedIn ciblé par profession et taille d'entreprise (sans vérification, adresses jugées fiables, envoi direct d'une doc spécifique au métier), traitement d'un message vocal transcrit par IA (contenu de mail entièrement dynamique, pas un simple remplissage de variables). **Recommandation de Claude, acceptée par Jean-Marc :** ne pas construire de moteur générique piloté par une table pour l'instant — la règle déjà en place sur ce projet pour ce genre d'arbitrage (généraliser à partir de 3 répétitions concrètes constatées, section 6) est satisfaite en nombre d'exemples mais pas en homogénéité : les trois formes diffèrent trop structurellement (branchement conditionnel avec délai pour l'un, envoi direct sans attente pour l'autre, contenu généré dynamiquement pour le troisième) pour être couvertes proprement par une même structure de données sans complexité disproportionnée pour ce que ça résout. Décision : factoriser seulement les briques réellement communes (envoi de mail depuis un modèle, journalisation ASP_Operations — déjà fait), et coder chaque parcours en PHP normal, au fur et à mesure. "Brûler" un prospect sans réponse réutilisera `PSP_Actif = 0` (même principe que `CNT_Actif`, jamais de suppression physique) ; "rejoindre le pool" (le prospect quitte l'onboarding et devient suivi par l'agent commercial) sera un événement ASP_Operations dédié plutôt qu'un nouveau champ, un indicateur rapide pouvant être ajouté plus tard si un futur écran de suivi en a besoin pour ses filtres. **Sujet à reprendre une fois un deuxième parcours (LinkedIn ou répondeur) vraiment engagé** — pas avant, pour ne pas concevoir dans le vide.
**Test en conditions réelles et évolutions du jour (26 août 2026, fin de session).** Formulaire déposé sur `mevelia-solutions.bzh/form`, clé Brevo API générée et déployée, DNS de `ms.bzh` déjà en ordre depuis les essais Budibase antérieurs, `ORIGINES_CORS_AUTORISEES` corrigée à `https://mevelia-solutions.bzh` (sans `www`). Chaîne complète testée et validée en production le jour même.
Deux bugs rencontrés et corrigés en tout premier test :
- **Dérive de schéma** : premier essai en échec ("erreur serveur" générique, volontairement sans détail exposé au client — voir la gestion `catch (PDOException)` dans `traiterAjoutProspect()`). Diagnostic via les logs du conteneur `crm-app` (`error_log()`) : `ASP_Prospects` avait été créée par Jean-Marc avant l'ajout, en cours de conception, des trois colonnes de vérification (`PSP_MailVerifie`, `PSP_TokenVerification`, `PSP_TokenExpiration`) — colonnes absentes en base bien que déjà actées dans ce journal. Corrigé par un `ALTER TABLE` ciblé. **Point de méthode à valeur générale, déjà noté en section 2 pour les tables de référence gérées à la main, mais qui s'applique ici à une table alimentée par le code également : toute évolution de structure décidée en cours de conversation doit être répercutée en base par Jean-Marc avant le prochain test, sinon la dérive n'apparaît qu'à l'usage.**
- **Mail de vérification sans lien cliquable** : le contenu de `MAI_Corps` pour `WEB-FORM-001-DEM`, saisi par Jean-Marc avant que la syntaxe `{(Lien)}` soit définitivement actée, contenait un texte d'attente ("lien(bouton/image) suivant ???") jamais remplacé par la vraie variable — corrigé par `UPDATE` direct en base, sans toucher au code.
**Passage des mails en HTML (fin de session, 26 août 2026).** Demande de Jean-Marc après avoir vu le premier mail de vérification en texte brut (URL du script PHP affichée en clair, pas de logo) : passer les mails de prospection en HTML, avec un bouton plutôt qu'un lien brut, et le logo de l'agence. **Choix technique retenu :** mail *multipart* plutôt qu'un remplacement pur HTML — `ASP_Mails` reçoit une nouvelle colonne `MAI_CorpsHtml` (`text`, nullable), `MAI_Corps` (texte brut) restant systématiquement renseignée comme repli pour les clients mail qui n'affichent pas le HTML, et pour la délivrabilité (un mail 100 % HTML avec peu de texte réel est un signal surveillé par les filtres anti-spam). `envoyerMail()` (dans `prospects_core.php`) envoie les deux (`textContent` + `htmlContent`) à l'API Brevo quand `MAI_CorpsHtml` est renseignée, sinon repasse en texte brut seul comme avant — rétrocompatible avec un `MAI_Code` qui n'aurait pas encore de version HTML. Nouvelle constante `LOGO_URL` (`https://aspic.mvl-tls.fr/logo.png`, fichier statique déposé à la racine du même dossier que les scripts PHP), injectée automatiquement dans les variables de substitution de tout mail sous `{(LogoUrl)}`, sans avoir à la passer explicitement à chaque appel. Bouton en HTML/CSS inline (couleur `#FF5A1F`, cohérente avec la charte du formulaire, avec attribut `width` en plus du style pour un rendu fiable y compris sous Outlook) à la place du lien brut — répond aussi au besoin de ne pas exposer le nom du script PHP en clair dans le texte du mail.
Deux bugs supplémentaires rencontrés et corrigés lors de ce passage au HTML :
- **Accents mal échappés en SQL** : premier jet des `UPDATE MAI_CorpsHtml` utilisant par erreur des séquences `é` (échappement Unicode JavaScript/JSON) — non reconnu par MySQL, qui les a laissées telles quelles dans le texte ("demandu00e9" au lieu de "demandé"). Corrigé en réécrivant les `UPDATE` avec les caractères accentués directement (la base est nativement en UTF-8, aucun échappement n'est nécessaire) — voir aussi la convention actée plus haut dans cette section.
- **Logo introuvable malgré une URL valide** : le logo ne s'affichait dans aucun client (y compris l'aperçu du tableau de bord Brevo lui-même), avec l'icône d'image cassée et le texte alternatif "Mevelia Solutions" à la place. Diagnostiqué via les logs d'accès du conteneur `crm-app` : les requêtes réelles portaient sur `/logo-mevelia.png` (ancien nom de fichier utilisé dans une version antérieure de la constante `LOGO_URL`), preuve que le fichier `prospects_core.php` déployé sur le serveur n'était pas la toute dernière version envoyée. Corrigé en redéployant le bon fichier. **Effet de bord instructif noté au passage : Brevo dispose de son propre service de pré-chargement/cache des images de mail (`Brevo/1.0 (redirection-images...)`, visible dans les logs d'accès), qui interroge le serveur avant l'envoi — utile à savoir pour de futurs diagnostics d'image dans un mail transactionnel.**
**Délivrabilité — premier constat.** Le tout premier mail de test est arrivé dans les spams (Gmail) plutôt qu'en boîte de réception — attendu pour un domaine d'envoi tout juste remis en service (`ms.bzh`), la réputation se construit avec le volume et le temps. Jean-Marc l'a marqué manuellement "Ce n'est pas un spam" dans Gmail. Point non bloquant, à surveiller plutôt qu'à corriger dans l'immédiat ; le SPF/DKIM déjà en place côté Brevo pour ce domaine est le levier principal si le problème persiste dans la durée.
**Fichiers livrés le 26 août 2026, vérifiés (`php -l`, `node --check`), testés et validés en conditions réelles :**
- `prospects_core.php` — logique commune : connexion DB (réutilise `db.php`), envoi de mail via l'API Brevo avec substitution de variables `{(...)}` (texte et HTML, `{(LogoUrl)}` injectée automatiquement), journalisation ASP_Operations, routage téléphone Tel/Portable (préfixe 06/07 après suppression des espaces), génération et vérification de la signature HMAC des liens de doc (`signerParametresDoc()`/`genererLienDoc()`), gestion CORS (`autoriserCorsSiNecessaire()`, liste blanche d'origines dans `ORIGINES_CORS_AUTORISEES`), validation des champs (longueurs, email, au moins mail ou téléphone requis).
- `prospect_web-form-001_ajoute.php` — point d'entrée public de la campagne WEB-FORM-001, simple appel à `traiterAjoutProspect('WEB-FORM-001')`.
- `prospects_verifier.php` — validation du token de vérification (avec expiration), puis envoi automatique de la doc par défaut de la campagne (lookup dynamique via `ASP_Campagnes.CMP_DocDefaut`, aucune configuration supplémentaire nécessaire pour une future campagne suivant le même principe).
- `prospect_doc.php` — vérifie la signature du lien (`a`/`b`/`c`/`d`), journalise la lecture (`<campagne>-LEC`), redirige vers l'URL réelle du document ; redirection neutre vers `mevelia-solutions.bzh` si signature invalide ou paramètres introuvables.
- `prospects_form_web.html` — formulaire public autonome (Nom/Prénom/Téléphone/Mail + honeypot), CSS/JS inline, déposé sur `mevelia-solutions.bzh/form`. **Visuel encore basique, à peaufiner — non urgent, mis de côté pour une prochaine session.**
**Données réelles en base (26 août 2026), transmises par Jean-Marc au fil de la session :**
- **ASP_Campagnes** : une ligne, `WEB-FORM-001` (`CMP_Incr = 1`, `CMP_DocDefaut = 1`, créée le 26/08/2026).
- **ASP_Actions** : 5 nouveaux codes de prospection (`WEB-FORM-001-CRE/-VER/-VAL/-DOC/-LEC`, incréments 9 à 13), voir le tableau complet en section 2.
- **ASP_Mails** : deux lignes — `WEB-FORM-001-DEM` (mail de vérification) et `WEB-FORM-001-DOC` (mail d'envoi de la doc, avec `{(Lien)}` et `{(Titre)}`) — chacune désormais avec une version `MAI_CorpsHtml` en plus de `MAI_Corps`.
- **ASP_Docs** : une ligne, "Documentation Mevelia Solutions" (`DOC_Incr = 1`), pointant vers `https://mvl-dcs.fr/docs/DocumentationGenerale.pdf` — URL probablement provisoire, le site définitif de Jean-Marc n'est pas encore en ligne.
**Reste à faire, plus tard :** peaufiner le visuel du formulaire public `prospects_form_web.html` (non urgent). Peupler ASP_Docs à mesure que d'autres documents (spécifiques à de futures campagnes) existeront. Concevoir un deuxième parcours d'entrée (LinkedIn ciblé ou répondeur transcrit) une fois un besoin concret réel — éclairera la question laissée ouverte sur une éventuelle généralisation en "table des scénarios". Mécanisme de bascule d'un prospect qualifié vers ASP_Contacts (`PSP_IncrContact`) pas encore conçu. Suivre dans la durée la délivrabilité du domaine `ms.bzh` (sortie des spams une fois la réputation établie).
@@ -0,0 +1,33 @@
## 19. Navigation entre contacts depuis le panneau détail, suppression des triangles d'ascenseur (27 août 2026)
**Contexte.** Reprise du chantier "gestion des contacts" (voir section 7, laissé en l'état le 26 août au profit de la prospection). Demande de Jean-Marc : pouvoir passer au contact suivant/précédent directement depuis le panneau de visualisation d'un contact, en respectant l'ordre de tri et le filtre actuellement appliqués sur l'écran liste — sans devoir refermer le panneau pour cliquer une autre ligne.
**Fonctionnalité livrée.**
- **Barre de position**, nouvelle bande grise tout en haut du panneau détail (au-dessus de la grille de champs), format repris à l'identique de la barre de statut de l'écran liste : `Contact X/N`, `Tri : ...`, `Filtre : ...`. Visible **en mode visualisation uniquement** — masquée en édition, création et suppression (la position n'a pas de sens dans ces modes), décision actée avec Jean-Marc avant implémentation.
- **Navigation clavier** : `PgDn` → contact suivant, `PgUp` → contact précédent, actifs uniquement quand le panneau détail est ouvert en mode visualisation (aucun effet en édition/création/suppression, ni quand c'est le panneau de filtre qui est ouvert).
- **Menu contextuel du panneau détail** : deux nouveaux items, "Contact précédent" et "Contact suivant", placés avec "Revenir à la liste des contacts" (groupe navigation, distinct du groupe édition en haut du menu). Grisés (non cliquables) en début (1/N) ou fin (N/N) de liste.
- Le contact ciblé par la navigation est déterminé à partir de `contactsAffiches`, le tableau déjà trié/filtré côté client par l'écran liste (aucun nouvel appel API de liste nécessaire) — garantit que la navigation respecte exactement le tri/filtre affiché, sans logique dupliquée. Si le contact actuellement affiché ne s'y retrouve plus (cas limite : filtre changé entre deux ouvertures du panneau sans repasser par la liste), la barre de position se masque plutôt que d'afficher un rang faux.
- Petit correctif d'enchaînement associé : après "Valider les modifications"/"Confirmer l'ajout", le rechargement de la liste (`rendreListe()`) est désormais **attendu** (`await`) avant de réafficher le contact, pour que la position/navigation reflète immédiatement le tri à jour (utile en particulier si le champ trié, Nom ou Ville, vient d'être modifié).
**Bug pré-existant repéré à cette occasion, non corrigé (hors périmètre de cette demande) :** l'ordre de priorité du traitement de la touche Échap place la fermeture du panneau détail *avant* la fermeture de son propre menu contextuel — un Échap alors que ce menu contextuel est ouvert ferme donc tout le panneau au lieu de juste refermer le menu, contrairement à l'ordre documenté par ailleurs (section 9 : "menu contextuel → hamburger → filtre"). Repéré en testant la nouvelle fonctionnalité (fermeture du menu par Échap), pas provoqué par elle. À corriger si Jean-Marc le souhaite — signalé mais pas traité, pour rester strictement dans le périmètre demandé.
**Suppression des triangles d'ascenseur.** Demande accessoire de Jean-Marc : retirer les petits triangles (▲/▼) précédemment ajoutés au-dessus/en dessous de l'ascenseur de la liste des contacts et de celui de la sous-liste des réalisations ("cela n'ajoute rien"). Les 4 glyphes retirés, mais les cases qui les contenaient (`case-ascenseur`) sont conservées : elles réservent un espace de la largeur exacte de l'ascenseur réel (mesurée dynamiquement en JS), nécessaire à l'alignement des colonnes d'en-tête avec les colonnes de données — cf. le bug d'alignement colonnes/en-tête déjà rencontré et documenté en section 9.
**Tests effectués.** Vérification syntaxique (`node --check` sur le JavaScript extrait). Vérification de la structure HTML (balises équilibrées, pas d'id dupliqué). Test fonctionnel via Playwright avec API simulées (3 contacts fictifs) : navigation PgDn/PgUp en avant et en arrière, blocage correct aux bornes (1/N et N/N), état grisé du menu contextuel aux bornes, barre de position masquée et navigation neutralisée en mode édition, triangles bien absents des 4 emplacements concernés. Pas de test contre les vraies données de production (pas d'accès direct au serveur depuis cette session) — à valider par Jean-Marc comme d'habitude après déploiement.
**Fichier livré :** `liste_contacts.html` (mis à jour, nom final `liste_contacts.php` après renommage habituel par Jean-Marc au transfert FTP).
**Corrections suite aux premiers tests de Jean-Marc (27 août 2026, même jour).**
- **Hauteur du panneau de visualisation.** Jean-Marc a précisé sa demande initiale après un premier essai : la barre de position ne devait pas faire grandir le panneau en mode visualisation par rapport au mode édition — au prix, assumé par lui, de perdre un peu de hauteur visible sur la grille de champs + sous-liste des réalisations ("on s'en moque un peu maintenant vu qu'on a la barre de répartition"). **Solution retenue :** plutôt que de retrancher un delta fixe (qui aurait supposé de recalculer à la main à chaque futur changement d'un des deux panneaux), `mesurerZoneRedimensionnable()` mesure désormais dynamiquement l'écart réel entre la hauteur naturelle du panneau en mode visualisation et sa hauteur en mode édition (bascule temporaire et invisible des classes CSS pour lire cette dernière, via une nouvelle fonction `mesurerHauteurPanneauPourMode()`), et retranche cet écart du budget partagé grille+réalisations. Le budget combiné grille+réalisations étant déjà, par construction du mécanisme de redimensionnement du 25 août, indépendant du ratio choisi par l'utilisateur, cette correction garantit une égalité de hauteur totale entre les deux modes quel que soit le ratio mémorisé. Correctif d'ordonnancement associé : `mettreAJourBarrePosition()` (qui fixe l'état d'affichage réel de la barre) s'exécute désormais avant `mesurerZoneRedimensionnable()`, pour que la mesure ne soit jamais faite sur un état d'affichage obsolète de la barre.
- **Bug Échap/menu contextuel corrigé.** Comme signalé dans le journal plus haut : un Échap alors que le menu contextuel du panneau détail est ouvert fermait tout le panneau au lieu du seul menu. Corrigé en vérifiant l'état de ce menu contextuel *avant* l'état général du panneau dans le gestionnaire de touche Échap — cohérent avec le principe "referme le panneau le plus prioritaire d'abord" déjà documenté (section 9).
**Tests effectués.** Vérification syntaxique (`node --check`) et de la structure HTML (inchangées, toujours correctes). Test fonctionnel Playwright : hauteur du panneau strictement identique entre mode visualisation et mode édition (écart mesuré à 0px sur le cas de test) ; Échap avec menu contextuel ouvert referme uniquement le menu (le panneau reste ouvert) ; un second Échap referme ensuite bien le panneau ; re-test de non-régression de la navigation PgUp/PgDn (toujours correcte après le changement d'ordonnancement dans `basculerModeDetail`).
**Fichier livré :** nouvelle version de `liste_contacts.html`, en remplacement de celle du même jour.
**Correctif visuel — ligne vide au-dessus de "Nom" (27 août 2026, retour de test de Jean-Marc).** Jean-Marc a signalé, à l'usage, qu'une bande blanche apparaissait entre la nouvelle barre de position et le premier champ ("Nom"), lisible comme une ligne de champ vide. Diagnostic (mesures Playwright à l'appui) : le padding-top de 16px du corps du panneau (hérité du mode édition, où il n'y a pas de barre au-dessus) s'ajoutait à la hauteur de la barre de position, créant un blanc d'environ 20px — la largeur d'une ligne de champ. **Corrigé** en ramenant ce padding-top à 0 spécifiquement en mode visualisation (nouvelle règle CSS `#panneau-detail.mode-vue .panneau-corps{padding-top:0;}`), sur le même principe que `#zone-liste` qui suit directement `#entete-colonnes` sans marge dans l'écran liste (section 9) : la barre elle-même joue déjà le rôle de séparateur visuel. "Nom" se retrouve donc directement sous la barre, espacé seulement par la marge habituelle de 4px déjà posée par `.ligne-detail`. **Effet de bord positif :** cette réduction de padding, mesurée dynamiquement comme le reste (voir `mesurerZoneRedimensionnable()`, plus haut), diminue d'autant l'écart à retrancher du budget grille+réalisations pour la parité de hauteur — un peu de la hauteur perdue lors du correctif précédent est donc récupérée automatiquement, sans intervention supplémentaire.
**Tests effectués.** Vérification syntaxique (`node --check`) et de la structure HTML (balises `<div>` équilibrées : 104/104). Test fonctionnel Playwright avec capture d'écran : bande blanche disparue, "Nom" directement sous la barre de position. Non-régression vérifiée : hauteur du panneau strictement identique entre mode visualisation et mode édition (435px des deux côtés sur le cas de test), navigation PgDn/PgUp toujours correcte, comportement Échap (menu puis panneau) toujours correct.
**Fichier livré :** nouvelle version de `liste_contacts.html`, en remplacement de celle du même jour.
@@ -0,0 +1,28 @@
## 20. Dépôt Git — GitHub + mirroir Gitea, limite technique découverte sur l'écriture depuis Claude (27-28 août 2026)
**Contexte et objectif.** Reprise d'un sujet posé plus tôt (section 2 : "installer Git, non urgent") dans le projet dédié à la gestion du serveur, avec Jean-Marc et Claude côté serveur. Mise en place actée : dépôt privé sur **GitHub** (`https://github.com/JMP-MS/ASPIC`), token d'accès dédié nommé "Claude-ASPIC" (lecture/écriture) généré pour permettre à Claude de travailler avec le code du projet sans dépendre d'envois de fichiers répétés. **Gitea installé sur le VPS en mirroir de ce dépôt GitHub**, synchronisation automatique toutes les heures — Gitea n'est qu'un miroir de lecture côté serveur, pas un canal de travail pour Claude ; son installation/paramétrage relève du projet de gestion du serveur, pas de celui-ci.
**Objectif principal recherché par Jean-Marc :** que Claude n'ait plus besoin de lui redemander les fichiers actuels du projet en début de session pour pouvoir travailler dessus.
**Test réalisé le 28 août 2026 (session ASPIC) :**
- **Lecture (clone) : fonctionnelle.** Le dépôt a été cloné avec succès dans l'environnement de travail de Claude en utilisant le token fourni. Structure du dépôt (au moment du test) organisée par type de fichier : `html/` (`identification.html`, `liste_contacts.html`), `php/` (`aspic.php`, `contacts.php`, `db.php`, `index.php`, `preferences.php`, `professions.php`, `prospect_doc.php`, `prospect_web-form-001_ajoute.php`, `prospects_core.php`, `prospects_verifier.php`, `realisations.php`, `statuts.php`), `png/` (`logo.png`), `polices/` (`MetroDF.ttf`, `MetroDF.woff2`). Contenu vérifié à jour au 28 août (`liste_contacts.html` contient bien les fonctionnalités du 27 août — barre de position, navigation PgUp/PgDn). Un seul commit dans l'historique à ce stade ("Add files via upload"), cohérent avec un dépôt tout juste initialisé par import du dossier existant plutôt que par un historique construit au fil de l'eau.
- **Écriture (push) : bloquée, pas un problème de token.** Toute tentative de `git push` échoue avec une erreur explicite d'un proxy git propre à l'environnement cloud où tourne Claude : *"access denied by the git proxy: JMP-MS/ASPIC is not in this session's authorized repository set, so the proxy will not inject a credential for it."* Ce proxy intercepte toutes les connexions sortantes de Claude vers GitHub ; il autorise la lecture avec un token fourni par l'utilisateur, mais bloque l'écriture tant que le dépôt visé n'a pas été explicitement ajouté à une liste de dépôts autorisés pour la session — mécanisme de sécurité côté Anthropic (protection probable contre le risque qu'une session manipulée pousse des commits non désirés), pas un souci de permissions GitHub ou de scope du token. Aucun outil disponible côté Claude, dans cette session, pour ajouter ce dépôt à la liste autorisée. **Ce blocage est indépendant du protocole (HTTPS vs SSH) — il intercepte toute connexion sortante de Claude vers github.com avant même l'authentification. Confirmé toujours d'actualité le 30 août 2026 (sections 23 et 24) : ce n'est pas ce blocage qu'on a levé, seulement contourné en poussant depuis le VPS de Jean-Marc plutôt que depuis l'environnement de Claude.**
- **Contournements envisagés et écartés :** pas de connecteur GitHub officiel disponible dans le catalogue de connecteurs de l'organisation (vérifié) ; pas d'accès terminal (shell) sur le PC de Jean-Marc via le pont vers son ordinateur (seuls des outils de lecture/écriture de fichiers sont disponibles pour cet appareil, pas d'exécution de commandes) — impossible donc de pousser depuis chez lui en s'appuyant sur son propre réseau. La commande `/install-github-app`, trouvée par Jean-Marc en ligne, a été vérifiée non pertinente : c'est une commande de l'application **Claude Code CLI locale** (à installer sur un poste, avec un vrai terminal), qui sert à configurer GitHub Actions sur un dépôt — sans rapport avec le mécanisme d'autorisation du proxy git rencontré ici, et de toute façon inaccessible depuis une session Cowork comme celle-ci (pas de terminal local équivalent). Recherche complémentaire : ce blocage correspond à une limitation connue et actuellement sans solution de contournement côté Cowork, sans mécanisme self-service pour autoriser un dépôt.
**Décision retenue (workflow hybride, 28 août 2026) :**
- Claude lit désormais l'état du code directement sur GitHub en début de session (plus besoin que Jean-Marc renvoie les fichiers pour que Claude retrouve le contexte).
- Claude continue de livrer les nouvelles versions de fichiers en téléchargement dans la conversation, comme avant la mise en place de Git.
- C'est Jean-Marc qui pousse ces fichiers sur GitHub (source de vérité du code), qui se synchronise ensuite automatiquement vers Gitea (mirroir serveur, toutes les heures) — Git n'automatise donc pas encore la livraison des fichiers de Claude vers le dépôt, seulement la lecture par Claude de l'état existant.
- Jean-Marc a noté que ce n'est pas aussi pratique qu'espéré (l'objectif initial incluait l'écriture directe par Claude), mais le gain principal (fin des demandes de renvoi de fichiers) reste acquis.
- Si un accès en écriture direct devient nécessaire plus tard, cela nécessiterait de vérifier côté paramètres de compte/organisation Claude l'existence d'un mécanisme d'autorisation de dépôt — non trouvé à ce stade, peut évoluer. **Mise à jour du 30 août 2026 : toujours non trouvé, mais un contournement pratique équivalent a été mis en place — voir section 23.**
**Structure finale retenue pour le dépôt (28 août 2026, même session) — retour sur l'organisation par type de fichier.** Après un premier échange où Claude avait confirmé que l'organisation par type (`html/`, `php/`, `png/`, `polices/`) lui convenait (elle n'a pas besoin de refléter la structure de déploiement, qui reste à plat dans le volume `./src` — section 3), Jean-Marc est revenu sur ce choix en tenant compte de la contrainte pratique posée par le nouveau workflow : c'est lui qui pousse manuellement chaque livraison de Claude vers GitHub, par un simple glisser/déposer. **Décision finale : tous les fichiers applicatifs (html/php/png/polices) reviennent à plat à la racine du dépôt**, à l'exception d'un nouveau dossier **`bdd/`**, dédié aux exports de structure des tables ASPIC (CREATE TABLE à jour, dans l'esprit des fichiers "Tables V01/V02.txt" transmis en tout début de projet — section 2) — pour garder une trace versionnée du schéma de données à côté du code, mais distincte des fichiers applicatifs eux-mêmes. Un dépôt à plat présente d'ailleurs un avantage supplémentaire par rapport à l'organisation par type : il colle exactement à la structure de déploiement réelle (fichiers copiés à plat dans `./src`). **Structure confirmée toujours à plat le 30 août 2026 lors du clone effectué depuis le VPS (section 23), pas de dossier `bdd/` visible à ce moment-là (à vérifier avec Jean-Marc, hors périmètre de la session du 30 août).**
**Migration prise en charge par Jean-Marc lui-même** (recopie des fichiers déjà présents dans les sous-dossiers vers la racine, suppression des anciens dossiers `html/`/`php/`/`png/`/`polices/`, création du dossier `bdd/`), directement sur l'interface GitHub — pas d'action de Claude possible ici (écriture bloquée, voir ci-dessus). **Point à observer, curiosité de Jean-Marc :** effet de cette réorganisation sur le mirroir Gitea au prochain rafraîchissement horaire. Un mirroir Git réplique en principe l'historique du dépôt d'origine tel quel (suppressions, renommages et recréations de fichiers inclus, comme n'importe quel autre commit) — un comportement sans surprise est donc attendu, mais cela reste à confirmer en pratique et n'a pas été vérifié à ce stade ; le paramétrage précis de ce mirroir Gitea relève du projet de gestion du serveur, pas de celui-ci.
**Deux points relevés par Claude en parcourant le dépôt lors du test du 28 août, à clarifier avec Jean-Marc (état constaté avant la réorganisation ci-dessus, à revérifier une fois la migration faite) :**
- `prospects_form_web.html` (formulaire public de prospection, livré et déployé le 26 août 2026 — voir section 18) était **absent du dépôt GitHub**. À vérifier si c'est un oubli lors de l'import initial ou un choix volontaire (peut-être parce que ce fichier vit sur un autre hébergement, `mevelia-solutions.bzh/form`, distinct du serveur ASPIC lui-même) — réponse de Jean-Marc encore en attente à ce stade.
- Un fichier `php/index.php` présent dans le dépôt (redirection de la racine du domaine vers l'écran d'identification — voir commentaire du fichier lui-même) n'était jusqu'ici pas documenté dans ce journal. Sa cible de redirection est écrite en dur comme `identification.html` (avec l'extension `.html`) ; à vérifier auprès de Jean-Marc si le fichier déployé en production a bien été conservé sous ce nom précis pour que cette redirection fonctionne, ou si ce point mérite une correction — réponse de Jean-Marc encore en attente à ce stade.
@@ -0,0 +1,75 @@
## 21. MIRADOR — base d'authentification centralisée multi-instances, numéro de version applicatif (28 août 2026, soir)
**Contexte.** Reprise de session le soir même, après l'annonce de deux évolutions d'infrastructure par Jean-Marc : installation d'une nouvelle instance MySQL sur le serveur, et achat prochain du nom de domaine `aspic.bzh` (toujours pas acquis à ce stade — voir réflexions stratégiques, section 7). Ces deux annonces ont ouvert une réflexion approfondie sur l'architecture d'accès multi-environnements d'ASPIC, capturée ici pour la partie strictement technique (schéma de données, code livré) — le raisonnement business sous-jacent (bêta test, modèle SaaS envisagé) reste dans les réflexions stratégiques.
**Topologie retenue.** Une fois `aspic.bzh` actif, deux publics distincts accèderont à la dernière version de l'application : Jean-Marc lui-même (essais avec des données propres) et de futurs bêta testeurs, chacun avec sa propre base. Décision actée : la première instance MySQL (celle utilisée aujourd'hui) hébergera deux bases distinctes, **`MS-DEV`** (nouveau nom de baptême de la base ASPIC actuelle, celle du bac à sable `aspic.mvl-tls.fr` — renommage restant à faire par Jean-Marc, y compris le `DB_NAME` du `docker-compose.yml` correspondant) et **`MS-EXP`** (nouvelle base, usage personnel de Jean-Marc sur `aspic.bzh`). La deuxième instance MySQL, toute nouvelle, hébergera autant de bases que nécessaire pour les bêta testeurs, une base par testeur — cohérent avec le principe déjà retenu dans les réflexions stratégiques (isolation par base plutôt que colonne `client_id` partagée). Convention de nommage des bases actée : lisible/thématique (`MS-DEV`, `MS-EXP`) plutôt que numérotée (`ASPIC_001`, etc.) — **vérifié que le tiret ne pose aucun problème technique** : l'application ne fait jamais de requête inter-bases (chaque connexion PDO cible une seule base via le DSN, `dbname=MS-DEV`), donc aucune requête SQL n'a jamais à qualifier ou échapper le nom du schéma. **⚠️ Topologie finalement mise en place le 30 août 2026 (section 24) : trois instances (mysql/mysql2/mysql3), MS-DEV et MS-TST (pas MS-EXP) créées par clonage sur `mysql`, MIRADOR dupliquée sur `mysql2` — voir cette section pour l'état réel.**
**Nouvelle base MIRADOR — authentification et dispatch centralisés.** Le routage vers la bonne base ne peut pas se faire par sous-domaine (principe déjà acté le 28 août au matin, réflexions stratégiques section 8 : un seul point d'entrée `aspic.bzh`, aucun `toto.aspic.bzh` visible). Décision : une base à part entière, nommée **MIRADOR**, porte l'identité et le routage de tous les utilisateurs, quelle que soit l'instance/base cible. Évolution assumée par rapport à l'idée du 28 août matin (vérifier le mot de passe *dans* la base cible après un premier aiguillage) : l'authentification est désormais **entièrement centralisée dans MIRADOR** — l'appli y vérifie l'identifiant et le mot de passe, obtient en retour les coordonnées de connexion de la base cible, puis ouvre une seconde connexion vers cette base pour le travail applicatif.
**Structure proposée pour `MIR_Users`** (schéma discuté et validé avec Jean-Marc, **non encore créé en base à ce stade** — une seule ligne de test existe, créée manuellement par Jean-Marc) :
```sql
CREATE TABLE `MIR_Users` (
`MIR_Incr` int NOT NULL AUTO_INCREMENT,
`MIR_Email` varchar(100) NOT NULL,
`MIR_Code` varchar(3) NOT NULL,
`MIR_MDPHash` varchar(255) NOT NULL,
`MIR_ServeurHost` varchar(100) NOT NULL,
`MIR_ServeurPort` smallint UNSIGNED NOT NULL DEFAULT 3306,
`MIR_BaseNom` varchar(64) NOT NULL,
`MIR_Actif` tinyint(1) NOT NULL DEFAULT '1',
`MIR_Creation` datetime NOT NULL DEFAULT CURRENT_TIMESTAMP,
PRIMARY KEY (`MIR_Incr`),
UNIQUE KEY `MIR_Email` (`MIR_Email`),
UNIQUE KEY `MIR_BaseNom_Code` (`MIR_BaseNom`, `MIR_Code`)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_0900_ai_ci;
```
Choix actés dans cette structure :
- **`MIR_Email` comme identifiant de connexion**, unique — remplace l'idée du 28 août matin d'un identifiant structuré type `code_client-code_user` (réflexions stratégiques, section 8). Évolution assumée : l'email est plus naturel pour des bêta testeurs externes, qui n'ont pas de convention de code à retenir.
- **`MIR_Code` (3 caractères) conservé** pour son rôle déjà existant de signature dans les tables tenant (`REA_CodeUser`/`OPE_CodeUser`), mais **unique seulement au sein d'une même base cible** (contrainte `UNIQUE (MIR_BaseNom, MIR_Code)`), pas globalement — argument de Jean-Marc : "au maximum une BDD est utilisée par 2 ou 3 utilisateurs au maximum, le jour où il y en aura plus par instance, c'est que les problèmes auront changé de forme."
- **`MIR_ServeurHost`/`MIR_ServeurPort`/`MIR_BaseNom` dupliqués par ligne utilisateur**, pas factorisés dans une table "instances" séparée — même principe "pas de sur-ingénierie" déjà appliqué ailleurs sur ce projet (règle des 3 répétitions, section 6), réévaluable si le nombre d'instances grossit vraiment.
- **Les identifiants MySQL applicatifs (le compte technique utilisé par le conteneur PHP pour se connecter, ex. `crm_user`) restent volontairement absents de cette table** — ce sont des identifiants d'infrastructure, pas une donnée par utilisateur. Ils continuent de vivre dans les variables d'environnement du conteneur, un compte par instance avec des droits sur toutes les bases qu'elle héberge. MIRADOR ne dit donc que QUELLE base rejoindre, jamais AVEC QUEL compte technique s'y connecter. **Confirmé/simplifié le 30 août 2026 (section 24) : Jean-Marc a choisi de dupliquer le même compte `crm_user` (mêmes identifiants) sur toutes les instances, ce qui évite toute logique de correspondance host→identifiants côté code.**
- **`MIR_MDPHash` plutôt que `MIR_MDP`**, choix de nommage délibéré pour ne jamais confondre avec l'ancienne convention en clair (`USR_MDP`, voir section 2).
- **`MIR_Actif`** : suppression logique, même principe universel déjà appliqué sur `CNT_Actif`/`PSP_Actif`.
**Abandon du mot de passe en clair.** Le compromis assumé depuis le 19 août sur `ASP_Users.USR_MDP` ("à revoir si ASPIC est un jour proposée à un client externe", section 2) est explicitement revu ici : MIRADOR stocke un hash bcrypt (`password_hash()`/`password_verify()` en PHP), pas un mot de passe en clair — déclenché précisément par l'arrivée de bêta testeurs externes.
**Hébergement de MIRADOR — décision et raisonnement.** MIRADOR est hébergée sur l'instance MySQL actuelle (celle qui portera aussi MS-DEV/MS-EXP), plutôt que sur une instance dédiée dès le départ, malgré un risque théorique réel identifié en discussion : une indisponibilité de cette instance couperait l'authentification de tout le monde, y compris de futurs bêta testeurs dont les données réelles seraient pourtant intactes sur l'instance 2. Ce risque n'est pas jugé réel tant qu'aucun bêta testeur ne dépend effectivement du service — même logique que la règle des répétitions déjà appliquée ailleurs sur ce projet (généraliser/isoler à partir du besoin réel constaté, pas par anticipation). **Déclencheur retenu pour la migration future vers une instance dédiée : avant l'onboarding du premier vrai bêta testeur**, pas la date d'activation technique de `aspic.bzh` elle-même (un délai peut s'écouler entre les deux). Migration anticipée comme peu coûteuse le moment venu (table probablement très petite, export/import + changement de paramètres de connexion côté appli). Bonne pratique retenue indépendamment de la topologie : le compte MySQL applicatif qui interroge MIRADOR devrait être un compte à part, avec des droits limités à cette seule base — pas encore mis en place, à faire à la création réelle de la base. **⚠️ Choix révisé le 30 août 2026 (section 24) : MIRADOR a en réalité été dupliquée sur une deuxième instance (`mysql2`), pas hébergée sur la première comme envisagé ici — l'app interroge désormais cette copie sur `mysql2`.**
**Génération des mots de passe hashés — pas de nouvel outil nécessaire.** Question posée par Jean-Marc : un hash en base nécessite-t-il un petit logiciel d'administration, contrairement au mot de passe en clair qu'il pouvait taper directement dans phpMyAdmin ? Réponse : non, `MIR_Users` reste une table "gérée à la main" comme les autres tables de référence, à un détail près — le hash doit être calculé par PHP (`password_hash()`), pas par phpMyAdmin. Geste ajouté : une commande `docker exec -it <nom_du_conteneur> php -r "echo password_hash('...', PASSWORD_DEFAULT) . PHP_EOL;"` (nom réel du conteneur à vérifier avec `docker ps`, le nom du service dans `docker-compose.yml` n'étant pas forcément le nom réel du conteneur — **confirmé le 30 août 2026 : le conteneur ASPIC se nomme `aspic-crm-app-1`, service `crm-app` défini dans `/docker/aspic/docker-compose.yml`, voir section 23**), dont le résultat est ensuite collé tel quel dans `MIR_MDPHash` via phpMyAdmin. **Un vrai outil d'administration (formulaire dédié) sera envisagé seulement si l'onboarding de bêta testeurs devient fréquent** — même règle des répétitions.
**Contournement pratique du jour.** La console Hostinger (accès terminal embarqué dans le panneau web) refusait de s'ouvrir au moment de générer le premier hash (souci déjà rencontré par Jean-Marc par le passé, non résolu ici — relève du projet de gestion du serveur ; piste donnée : essayer un accès SSH direct depuis un vrai terminal plutôt que la console web, `ssh -l "email@domaine" ip_vps` si l'identifiant est une adresse mail contenant un `@`). En attendant, le hash du tout premier utilisateur MIRADOR (Jean-Marc lui-même) a été généré directement dans l'environnement de travail de Claude (PHP 8.4 disponible, format bcrypt identique et compatible avec le PHP 8.3 de production), vérifié par un aller-retour `password_hash()`/`password_verify()` avant transmission. Jean-Marc a ensuite créé la ligne correspondante dans `MIR_Users` manuellement et testé avec succès ("Carton plein !"). **Ce souci de console Hostinger est résolu depuis (30 août 2026) : Jean-Marc dispose maintenant d'un accès terminal fonctionnel avec clé SSH paramétrée sur le VPS — voir section 23, où cet accès a servi à un tout autre usage (push GitHub) mais lève au passage cette limitation.**
**⚠️ Point de vigilance important pour la suite : MIRADOR n'est à ce stade qu'un schéma discuté avec une seule ligne de test — aucun code applicatif ne la consulte encore.** Le login réel d'ASPIC (`aspic.php`) continue de fonctionner exactement comme avant (vérification directe contre `ASP_Users` de la base cible). Le branchement effectif de l'écran d'identification sur MIRADOR (interroger MIRADOR en premier, puis router vers la base cible) reste entièrement à faire. **Fait le 30 août 2026 — voir section 24.**
**Numéro de version applicatif (`V1.000`, livré et validé en production le 28 août 2026).** Fonctionnalité demandée par Jean-Marc en cours de session : afficher discrètement, sur l'écran d'identification et sur l'écran liste des contacts, le numéro de version du code déployé sur l'environnement consulté.
*Décision de conception : source locale à chaque environnement, pas centralisée dans MIRADOR.* Le numéro reflète le code réellement déployé sur CET environnement précis (MS-DEV, MS-EXP, ou plus tard une base bêta testeur), justement pour repérer un déploiement resté en retard — exactement le genre de souci déjà rencontré avec le bug du logo du 26 août 2026 (section 18), où un fichier périmé était resté en place sans qu'on le voie tout de suite. Une valeur centralisée dans MIRADOR aurait affiché le même numéro partout, quel que soit l'état réel du code déployé à chaque endroit — inutile pour cet objectif.
*Fichier livré :* `version.php`, nouveau, à la racine — porte la constante `ASPIC_VERSION` et sert aussi de mini-endpoint public JSON (`{"version":"V1.000"}`), sans session requise (consulté aussi depuis l'écran d'identification, avant toute connexion).
*Format retenu :* `V<majeur>.<compteur sur 3 chiffres>` (ex. `V1.000`), incrémenté par Jean-Marc **à la main, une seule fois en fin de session, juste avant de pousser sur GitHub** — pas à chaque fichier reçu en cours de travail (une soirée peut compter jusqu'à 6 versions d'un même fichier avant d'être figée). **Convention retenue pour l'avenir : faire figurer ce numéro dans le message de commit GitHub au moment du push**, pour relier directement "ce qu'affiche l'appli" à "l'état exact du code sur GitHub à ce moment-là" — pas appliqué au tout premier push de ce soir (Jean-Marc a poussé sans message personnalisé, "je ferai ça la prochaine fois"). **Convention effectivement appliquée pour la première fois le 30 août 2026 — voir section 23, messages de commit `"Réconciliation avec la production réelle - V1.001 (...)"` et `"V1.002 - Bandeau identification : Ingenierie devient Intelligence"`.**
*Placement, deux itérations sur l'écran liste des contacts.* Premier essai en tout premier élément de la barre de statut du bas (`#statut`), suivant la demande initiale de Jean-Marc ("ligne du bas à gauche") — repéré après coup comme peu visible (couleur trop proche du fond de la barre, et Jean-Marc regardait en fait plutôt à droite). Deuxième essai avec `margin-left:auto` pour plaquer l'élément à droite du flux flex — jugé encore "en plein milieu" par Jean-Marc, l'espace du repère de largeur de scrollbar partagé avec la liste laissant une marge après l'élément. **Version finale :** élément sorti du flux flex, positionné en absolu contre le bord droit réel de la barre (juste avant `--sb-width`, la variable CSS déjà utilisée pour mesurer dynamiquement la largeur de l'ascenseur — section 19), couleur assombrie (`#4a4a4a`) et mise en gras pour un contraste suffisant sur le fond clair de la barre. Sur l'écran d'identification, la bande `.footer-line` — présente dans le code depuis le tout premier jet de l'écran (section 8), marquée "optionnelle, à confirmer" dans le CSS sans jamais avoir été utilisée — porte désormais ce numéro, en bas à droite, sans itération nécessaire côté positionnement.
*Tests effectués avant chaque livraison :* `php -l` sur `version.php`, `node --check` sur le JavaScript extrait des deux écrans, équilibre des balises `<div>` (23/23 et 104/104, inchangé), et un test fonctionnel réel via un serveur PHP local (`php -S`) et Playwright (capture d'écran + lecture du texte affiché) — rendu possible par un accès en lecture au dépôt GitHub (voir ci-dessous), qui a évité de redemander les fichiers à jour à Jean-Marc.
**Validé par Jean-Marc en production sur `aspic.mvl-tls.fr` le 28 août 2026 ("Carton plein !").** Point de méthode à noter au passage : un premier test de Jean-Marc n'avait pas vu le numéro sur l'écran liste des contacts alors qu'il était bien affiché — il regardait à droite, l'élément était encore à gauche à ce moment de l'itération. Pas un bug, une histoire de placement pas encore convergé avec son attente.
**Accès en lecture au dépôt GitHub, deuxième utilisation (28 août 2026, soir).** Le mécanisme déjà établi le 28 août matin (section 20) a été réutilisé avec succès : Jean-Marc a transmis un token d'accès personnel pour cloner le dépôt en lecture dans l'environnement de travail de Claude. **Point de vigilance signalé par Claude, pas encore acté par Jean-Marc :** ce token (`Claude-ASPIC`) a été généré sans date d'expiration — suggestion de le régénérer en lecture seule avec une expiration raisonnable, l'écriture n'apportant rien puisqu'elle reste de toute façon bloquée côté proxy git pour cette session (section 20). **Refus explicite de Claude de stocker ce token dans un document du projet**, à la demande de Jean-Marc qui voulait s'éviter d'avoir à le recopier à chaque session : un token sans expiration avec droits d'écriture, stocké en clair dans un document potentiellement relu par une session future, représente un risque disproportionné par rapport au gain de confort — un gestionnaire de mots de passe reste la solution recommandée côté Jean-Marc. **Ce token a été retransmis en session le 30 août 2026 pour un nouveau clone en lecture (section 23) — toujours pas stocké, même principe reconduit.**
**Procédure de mise à jour du dépôt GitHub, clarifiée (28 août 2026).** Jean-Marc dépose ses fichiers via glisser-déposer sur l'interface web GitHub ("Add file" → "Upload files"). Confirmé : déposer un fichier qui porte le même nom qu'un fichier déjà présent au même chemin est traité comme une modification (l'historique complet reste consultable), pas comme une suppression suivie d'une recréation — pas besoin de supprimer quoi que ce soit au préalable. Point de discipline retenu pour la suite (pas appliqué ce soir) : personnaliser le message de commit à chaque envoi, idéalement en y faisant figurer le numéro de version de l'application au moment du push (voir plus haut). **⚠️ Cette procédure de glisser-déposer web est abandonnée le 30 août 2026 au profit d'un `git push` en SSH directement depuis le VPS — voir section 23. Jean-Marc a lui-même signalé ne pas avoir été très à l'aise avec le glisser-déposer, ce qui a motivé le changement.**
**Reste à faire, plus tard :**
- ~~Créer réellement la base MIRADOR et sa table `MIR_Users` en production~~ **Fait — MIRADOR dupliquée sur `mysql2` (section 24).**
- ~~Réécrire `aspic.php` (écran d'identification) pour consulter MIRADOR en premier, avant de router vers la base cible~~ **Fait le 30 août 2026 — section 24.**
- Renommer la base ASPIC actuelle en `MS-DEV` côté MySQL et mettre à jour le `docker-compose.yml` du bac à sable en conséquence (`DB_NAME`). **Nuancé le 30 août 2026 : MS-DEV a finalement été créée par clonage à côté de l'ASPIC existante (conservée en filet de sécurité), pas par renommage — voir section 24.**
- Créer la base `MS-EXP` pour l'usage personnel de Jean-Marc sur `aspic.bzh`, une fois le nom de domaine actif. **Toujours à faire — la base créée le 30 août pour les tests est `MS-TST`, pas `MS-EXP` (section 24).**
- Monter la deuxième instance MySQL destinée aux bêta testeurs, et réfléchir à un processus de création de leurs bases (à la main via phpMyAdmin pour commencer, script de provisioning si le volume le justifie un jour). **Deux instances supplémentaires montées entre-temps (mysql2, mysql3) — section 24.**
- Régénérer le token GitHub `Claude-ASPIC` en lecture seule avec expiration (suggestion de Claude, en attente de décision de Jean-Marc).
- ~~Prendre l'habitude d'un message de commit descriptif (incluant le numéro de version) à chaque push GitHub.~~ **Fait pour la première fois le 30 août 2026 (section 23) — à confirmer que ça devient systématique.**
- Migrer MIRADOR vers une instance MySQL dédiée avant l'onboarding du premier vrai bêta testeur (pas avant).
- ~~Vérifier/corriger le compte de démonstration encore présent dans `identification.html`~~ **Retiré le 30 août 2026 (section 24).**
@@ -0,0 +1,35 @@
## 22. Badge société dans le bandeau et avatar utilisateur enfin dynamique, version V1.001 (29 août 2026)
**Contexte.** Jean-Marc prépare la cohabitation prochaine de deux façons d'utiliser ASPIC : son espace de développement habituel (avec Claude, comme aujourd'hui) et un futur espace de test séparé, où il mènera des essais avec des agents IA tiers (potentiellement Hostinger Agents — voir réflexions stratégiques, section 9) indépendamment du bac à sable de développement, pour limiter le risque de casse. Besoin exprimé : pouvoir voir d'un coup d'œil dans quel environnement il se trouve, sans attendre la mise en place complète de l'architecture MIRADOR/aspic.bzh (section 21, toujours non déployée à ce stade).
**Décision d'architecture discutée avant implémentation.** Deux options envisagées : une colonne `USR_Societe` dans `ASP_Users` (redondante par construction — même valeur sur toutes les lignes d'une même base), ou une constante par environnement façon `version.php` (une seule valeur à changer par déploiement, aucune redondance). Jean-Marc a tranché pour la colonne dans `ASP_Users`, en assumant consciemment la redondance : avec 2-3 utilisateurs par base, ce n'est pas un vrai risque pratique — même raisonnement déjà tenu pour `MIR_Code` non unique globalement (section 21) — et ça permettrait même de distinguer par exemple "Société X - Standard" et "Société X - Commercial" si plusieurs profils cohabitaient un jour dans une même base. Le jour où le nombre d'utilisateurs par base grossirait vraiment, ce sera le signe que les problèmes ont changé de forme et qu'une autre approche s'impose — pas avant.
**Structure.**
```sql
ALTER TABLE `ASP_Users`
ADD COLUMN `USR_Societe` varchar(40) DEFAULT NULL AFTER `USR_Nom`;
```
Colonne texte libre (pas un code court à 3 lettres comme évoqué au tout début de la discussion), longueur alignée sur `CNT_Société` (ASP_Contacts). `NULL` = badge masqué pour cet utilisateur (ex. `Vik`, le compte système, n'a pas vocation à en avoir un). Gérée à la main comme le reste de `ASP_Users`.
**Découverte en cours de chantier : l'avatar n'était pas vraiment branché sur la session.** En inspectant `liste_contacts.html` (clone GitHub en lecture, même mécanisme qu'aux sections 20-21) pour localiser l'élément à modifier, `#avatar` s'est révélé afficher "JMP" en dur dans le HTML depuis le tout premier jet de cet écran — jamais corrigé une fois la session utilisateur réelle mise en place (section 15), simplement parce qu'un seul compte utilisateur était réellement utilisé jusqu'ici. Corrigé à cette occasion, avec l'accord de Jean-Marc : l'avatar lit désormais le `USR_Code` retourné par `preferences.php`.
**Implémentation.**
- `preferences.php` (GET) renvoie désormais aussi `USR_Code` et `USR_Societe` de l'utilisateur de la session en cours, en plus des deux ratios déjà renvoyés — même restriction de sécurité qu'avant (toujours `$_SESSION['USR_Code']`, jamais un code fourni côté client). `USR_Societe` n'est pas modifiable via ce endpoint (PUT reste limité aux deux colonnes de ratio).
- `liste_contacts.html` : nouvel élément `#badge-societe`, forme pilule non cliquable, positionné juste avant l'avatar dans le bandeau. Masqué (attribut HTML `hidden`) si `USR_Societe` est vide pour l'utilisateur connecté.
- Couleur : Jean-Marc pensait initialement que l'avatar existant était déjà bleu ("j'ai la berlue") ; vérification faite, il est gris (`var(--gris-fonce)`) aussi bien dans le code que confirmé en production. Une nouvelle variable CSS `--bleu-badge` (`#2E5AAC`) a d'abord été introduite pour le seul badge société, puis Jean-Marc a demandé d'étendre cette même couleur à l'avatar : les deux éléments d'identité affichés côte à côte (société, utilisateur) partagent maintenant la même teinte bleue.
- Numéro de version incrémenté à `V1.001` dans `version.php` (convention actée section 21 : à la main, une fois en fin de session, avant le push GitHub).
**Tests effectués avant chaque livraison :** `php -l` (`preferences.php`, `version.php`), `node --check` sur le JavaScript extrait, équilibre des balises `<div>` (105/105, +1 par rapport à la version précédente pour le nouveau badge). Test fonctionnel Playwright avec `preferences.php` simulé : cas société renseignée (badge visible, texte affiché, avatar avec le vrai code) et cas société vide (badge proprement masqué) — captures d'écran à l'appui. Confirmation par mesure de la couleur calculée (`getComputedStyle`) que l'avatar et le badge partagent bien la même teinte après la demande d'unification.
**Validé par Jean-Marc en production le 29 août 2026** ("C'est exactement ce que je voulais ! ... ça rend super bien !"), avec un nom de société assez long affiché sans souci visuel constaté à ce stade.
**Constat au passage (déjà noté section 8) :** le clone GitHub du 29 août montre que `identification.html` contient toujours le compte de démonstration (`UTILISATEURS_DEMO`) — décalage GitHub/production toujours pas corrigé à cette date, simplement reconfirmé, hors périmètre de ce chantier. **Ce fichier, comme les deux autres livrés ce jour-là (`liste_contacts.html`, `preferences.php`), n'avait à ce stade jamais été poussé sur GitHub — resté "en retard" jusqu'à la réconciliation du 30 août 2026, voir section 23.**
**Reste à faire, plus tard :**
- Peupler `USR_Societe` pour les comptes existants qui n'y sont pas encore.
- Le badge n'a pas de gestion de troncature visuelle pour un nom nettement plus long que "Mevelia Solutions" — à surveiller à l'usage si un futur nom de société s'avère beaucoup plus long.
- Une fois l'architecture MIRADOR/aspic.bzh réellement déployée (section 21, effectivement branchée section 24), réévaluer si ce badge doit continuer à venir de `ASP_Users` (base cible) ou évoluer vers une autre source, selon la façon dont les futurs environnements de test seront effectivement structurés.
@@ -0,0 +1,56 @@
## 23. Push GitHub en SSH depuis le VPS, fusion de `./src` avec le dépôt Git, réconciliation, correctif V1.002 (30 août 2026)
**Contexte.** Session ouverte sur un petit correctif isolé (voir plus bas), puis élargie à un chantier plus structurant : Jean-Marc a désormais un accès terminal fonctionnel sur son VPS avec clé SSH déjà paramétrée pour sa propre connexion — ce qui résout au passage le souci de console Hostinger capricieuse rencontré le 28 août (section 21). Ceci a permis de reprendre la question, restée en suspens depuis le 27-28 août (section 20), du confort du circuit de mise à jour de GitHub : Jean-Marc trouvait le glisser-déposer manuel sur l'interface web "pas très pratique" et n'était pas très à l'aise avec cette manipulation.
**Point important, clarifié en début de chantier :** le blocage d'écriture rencontré par Claude en section 20 (le proxy git de l'environnement cloud de Claude, qui refuse tout `git push` vers un dépôt non explicitement autorisé) est **indépendant du protocole** — il intercepte toute connexion sortante de Claude vers GitHub avant même l'authentification, que ce soit en HTTPS ou en SSH. Ce chantier ne lève donc pas ce blocage-là, et ne cherche pas à le lever : il met en place un circuit où **c'est Jean-Marc, depuis son propre VPS, qui pousse en SSH** — un chemin entièrement différent, qui ne passe jamais par l'environnement de Claude.
**Correctif isolé livré en tout début de session — `NGÉNIERIE` → `NTELLIGENCE` (V1.002).** Demande récurrente de Jean-Marc ("cela fait 20 fois que j'y pense") : dans le bandeau acrostiche de l'écran d'identification, le mot associé à la lettre "I" affichait `NGÉNIERIE` (pour "Ingénierie Commerciale") ; changé en `NTELLIGENCE` (pour "Intelligence Commerciale"), plus cohérent avec la vocation IA du projet. Localisé dans `identification.html`, ligne `<div class="word">NGÉNIERIE</div>` — seule occurrence dans tout le dépôt. Changement d'un seul mot, vérifié par diff (une seule ligne modifiée), équilibre des balises `<div>` inchangé (23/23), JavaScript extrait toujours valide (`node --check`). `version.php` incrémenté à `V1.002` en parallèle, à la demande explicite de Jean-Marc (numéro donné directement, pas calculé). Fichiers livrés en téléchargement dans la conversation — leur déploiement effectif sur le VPS n'a eu lieu que plus tard dans la session, une fois le nouveau circuit Git en place (voir plus bas).
**Mise en place de l'accès SSH dédié pour GitHub.**
- Clé SSH générée directement sur le VPS, dédiée à ce seul usage (distincte de celle que Jean-Marc utilise pour se connecter au VPS depuis son poste) : `ssh-keygen -t ed25519 -f ~/.ssh/id_ed25519_aspic_github -N ""` (sans phrase de passe, pour un `git push` non interactif — risque jugé limité puisque la clé n'a accès qu'à un seul dépôt, voir plus bas).
- Ajoutée côté GitHub comme **Deploy key** sur le seul dépôt `JMP-MS/ASPIC` (`Settings → Deploy keys`), avec l'option **Allow write access** cochée — plutôt qu'une clé personnelle liée à l'ensemble du compte GitHub de Jean-Marc. Choix délibéré de Jean-Marc (répondant à une question posée explicitement) : cohérent avec le principe de moindre privilège déjà appliqué ailleurs sur ce projet (comptes techniques limités à leur périmètre plutôt que larges — `crm_user` limité à sa base, futur compte MySQL dédié pour MIRADOR en section 21).
- Alias d'hôte dédié dans `~/.ssh/config` sur le VPS, pour éviter tout conflit avec d'éventuelles autres clés GitHub sur ce même serveur :
```
Host github.com-aspic
HostName github.com
User git
IdentityFile ~/.ssh/id_ed25519_aspic_github
IdentitiesOnly yes
```
Le remote Git du dépôt utilise donc `git@github.com-aspic:JMP-MS/ASPIC.git`, pas l'adresse standard `git@github.com:...`.
- **Vérification de l'empreinte de l'hôte à la première connexion.** Au premier `ssh -T git@github.com-aspic`, SSH a demandé de confirmer l'empreinte du serveur GitHub (`SHA256:+DiY3wvvV6TuJJhbpZisF/zLDA0zPMSvHdkr4UvCOqU`) — étape normale et attendue (mémorisation de l'hôte pour les connexions suivantes), pas une anomalie. Empreinte vérifiée par Claude contre la documentation officielle GitHub avant validation par Jean-Marc (correspondance confirmée) plutôt que d'accepter à l'aveugle — cohérent avec la discipline "tester plutôt que croire sur parole" déjà appliquée sur ce projet. Connexion validée : *"Hi JMP-MS/ASPIC! You've successfully authenticated, but GitHub does not provide shell access."*
- **`git config --global --add safe.directory`** nécessaire au premier `git status`/`git clone` en tant que `root` sur ce nouveau dossier ("detected dubious ownership") — protection standard de Git depuis une version récente, sans lien avec un problème réel de permissions ici ; résolu en une commande, comme suggéré par Git lui-même.
**Localisation du vrai dossier de déploiement.** Le chemin exact du `docker-compose.yml` d'ASPIC sur le VPS n'était pas documenté dans ce journal. Retrouvé de façon fiable via `docker inspect aspic-crm-app-1 --format '...'` (ou `docker inspect aspic-crm-app-1 | grep -A2 '"Source"'` en repli, plus robuste aux soucis de guillemets lors d'un copier-coller de template Go) plutôt qu'en devinant l'emplacement du fichier YAML — donne directement le vrai chemin hôte du bind mount. **Chemin confirmé : `/docker/aspic`, avec le dossier `src` à l'intérieur** (le service applicatif se nomme `crm-app` dans `docker-compose.yml`, le conteneur réel `aspic-crm-app-1` ; le service SFTP existant se nomme `sftp`).
**Fusion du dossier de déploiement avec le dépôt Git — décision et méthode.** Plutôt que de garder deux emplacements distincts (le dossier `./src` servant au déploiement, et un clone Git séparé ailleurs sur le VPS qu'il aurait fallu recopier vers `./src` à chaque livraison — solution qui aurait reproduit exactement la corvée que Jean-Marc voulait éviter), décision de faire de `./src` lui-même la copie de travail du dépôt Git. Un seul dossier sert désormais à la fois de dossier de production et de dépôt Git.
**Point de vigilance essentiel, identifié avant toute manipulation : ne pas cloner GitHub par-dessus `./src` sans précaution.** Le dépôt GitHub était connu comme étant en retard sur la vraie production (`version.php` y affichait encore `V1.000` alors que la prod réelle était à `V1.001` depuis le 29 août, section 22 — les livraisons du 29 août n'avaient jamais été poussées). Un clone brutal aurait donc fait régresser la prod. Méthode de réconciliation suivie :
1. `mv src src_old` — le vrai dossier de production est mis de côté intact, comme filet de sécurité, avant toute manipulation.
2. `git clone git@github.com-aspic:JMP-MS/ASPIC.git src` — un nouveau `src` est recréé à partir du dépôt GitHub (donc, à ce stade, en retard).
3. `diff -rq src_old src --exclude=.git` — comparaison explicite avant d'agir. Résultat : trois fichiers divergents (`liste_contacts.html`, `preferences.php`, `version.php`) — exactement les trois fichiers du chantier badge société/avatar du 29 août jamais poussés (section 22). `identification.html`, en revanche, s'est révélé **identique** entre les deux — résultat intéressant qui clôt une question restée ouverte depuis plusieurs sessions (section 8) : le soupçon de décalage GitHub/prod sur ce fichier précis était infondé (le compte de démonstration `UTILISATEURS_DEMO`, lui, est bien présent des deux côtés, retiré depuis — section 24). Aucun fichier présent d'un seul côté (pas de ligne "Only in ..." dans le diff).
4. `rsync -a --exclude='.git' src_old/ src/` — le contenu réel de la production est ramené dans le nouveau dossier Git, pour ne perdre aucun travail non poussé.
5. `git add -A && git commit -m "Réconciliation avec la production réelle - V1.001 (badge société/avatar, USR_Code/USR_Societe dans preferences.php)" && git push` — premier vrai commit "de fond" du dépôt (commit `ff0e3a6`), qui succède au commit unique initial ("Add files via upload", section 20). **Identité Git (`user.name`/`user.email`) configurée en global sur le VPS à cette occasion, absente auparavant.**
**Piège rencontré — le conteneur SFTP restait attaché à l'ancien dossier.** Une fois la réconciliation poussée, Jean-Marc a déposé les deux fichiers V1.002 du tout début de session (`identification.html`, `version.php`) via son client SFTP habituel (FileZilla, port 2224) — mais `grep -c NTELLIGENCE identification.html` sur le nouveau `src` répondait `0` alors que FileZilla confirmait un dépôt réussi. **Cause identifiée :** un conteneur avec un volume bind-mount reste attaché à l'objet-dossier hôte tel qu'il existait à son démarrage. Le service `sftp` tournait déjà depuis un moment quand `mv src src_old` a eu lieu ; il est donc resté attaché à ce qui s'appelle maintenant `src_old`, invisible pour Git, alors même que FileZilla montrait un dépôt "réussi" au bon nom de dossier apparent. Le conteneur `crm-app`, lui, avait déjà été redémarré entre-temps (voir plus bas) et voyait donc correctement le nouveau `src`. **Diagnostic confirmé** par `grep -c NTELLIGENCE src_old/identification.html` → `1`. **Corrigé** par `cp src_old/identification.html src_old/version.php src/` (récupération directe, sans repasser par FileZilla) puis `docker compose restart sftp`, pour que les prochains dépôts FileZilla arrivent directement au bon endroit. **Point de méthode à valeur générale, ajouté en section 3 : un renommage/recréation de dossier sur l'hôte pendant qu'un conteneur tourne n'est pas vu par ce conteneur tant qu'il n'est pas redémarré — à vérifier systématiquement (et redémarrer tous les conteneurs concernés, pas seulement l'application principale) après toute manipulation de ce type sur un dossier monté en bind mount.**
**Deuxième commit — clôture du correctif V1.002.** Une fois les fichiers bien positionnés dans le dossier Git : `git add -A && git commit -m "V1.002 - Bandeau identification : Ingenierie devient Intelligence" && git push` (commit `64cc133`). `docker compose restart crm-app` refait ensuite pour réappliquer le `chown -R 1001:1001` sur les fichiers déposés par `cp` en tant que `root` (ce dernier ne préserve pas le propriétaire d'origine sans l'option `-p`/`-a`). **Confirmé en production par Jean-Marc le jour même :** bandeau affichant "INTELLIGENCE", version `V1.002` visible.
**Nouveau circuit de livraison, en vigueur à partir de maintenant :**
1. Claude livre les fichiers en téléchargement dans la conversation, comme avant.
2. Jean-Marc les dépose dans `/docker/aspic/src` via son client SFTP habituel (FileZilla, port 2224) — inchangé de son point de vue, c'est toujours le même dossier apparent.
3. Depuis un terminal SSH connecté au VPS, dans `/docker/aspic/src` : `git add -A`, `git commit -m "..."` (idéalement avec le numéro de version, convention de la section 21 enfin appliquée), `git push`.
4. Le glisser-déposer manuel sur l'interface web GitHub (section 20-21) est abandonné.
**`src_old` conservé pour l'instant en filet de sécurité** (pas supprimé à la fin de cette session) — à retirer une fois plusieurs cycles de livraison confirmés stables via le nouveau circuit.
**Reste à faire, plus tard :**
- Supprimer `src_old` une fois le nouveau circuit éprouvé sur quelques livraisons.
- ~~Vérifier/corriger le compte de démonstration (`UTILISATEURS_DEMO`) dans `identification.html`~~ **Retiré le 30 août 2026 — voir section 24.**
- Confirmer que la convention "numéro de version dans le message de commit" (section 21) devient systématique sur les prochains push, pas seulement ce soir.
- Documenter ce nouveau circuit Git (clé SSH dédiée, deploy key, alias d'hôte) également côté projet de gestion du serveur si Jean-Marc le juge pertinent pour la cohérence avec les autres applications du VPS — non fait ici, hors périmètre de ce journal.
@@ -0,0 +1,49 @@
## 24. MIRADOR branché — authentification par email, routage dynamique multi-bases, retrait du compte de démonstration (30 août 2026)
**Contexte.** Reprise de session le même jour que la section 23. Deux chantiers traités à la suite : le retrait du compte de démonstration resté en place depuis le 21 août (signalé sans être traité aux sections 8, 21, 22, 23), puis le branchement effectif de MIRADOR sur l'authentification — jusqu'ici un schéma discuté avec une seule ligne de test (section 21), aucun code applicatif ne le consultant encore.
**Retrait du compte de démonstration.** `identification.html` : suppression du tableau `UTILISATEURS_DEMO` (JMP/aspic2026 en dur), de `trouverUtilisateurDemo()`, et de la bascule automatique dans `verifierIdentifiants()` en cas d'échec de `aspic.php` — un `aspic.php` injoignable fait désormais échouer proprement l'identification plutôt que de se rabattre sur ce filet. Commentaire résiduel nettoyé dans `aspic.php`. Commit local dans le clone de travail (`git commit`), mais **push impossible depuis cette session** : le blocage du proxy git documenté en section 20 (*"access denied by the git proxy: JMP-MS/ASPIC is not in this session's authorized repository set"*) a été retesté explicitement à cette occasion et reste entier, quel que soit le token fourni — confirmation qu'il s'agit bien d'un blocage au niveau du proxy Anthropic, pas d'un problème de droits GitHub. Le circuit qui fonctionne reste celui de la section 23 (FileZilla → `/docker/aspic/src` → `git push` en SSH depuis le VPS).
**Nouvelle infrastructure MySQL, mise en place par Jean-Marc avant ce chantier.** Trois instances MySQL désormais actives (`mysql`, `mysql2`, `mysql3`), configurées de façon homogène et toutes rejoignables par leur nom de service Docker sur le réseau `proxy` partagé avec le conteneur `crm-app` — pas de configuration réseau supplémentaire nécessaire. Bases créées par clonage de l'ASPIC existante : **MS-DEV** et **MS-TST** sur l'instance `mysql` (l'ancienne base `ASPIC` est conservée telle quelle, en filet de sécurité, non utilisée par le nouveau circuit). **MIRADOR** dupliquée à l'identique sur `mysql2` — c'est cette copie-là que l'application interroge désormais pour l'authentification (voir `ROUTER_DB_HOST` ci-dessous) ; la copie restée sur `mysql` n'est plus utilisée par le code, à garder en tête si les deux venaient à diverger un jour. Le compte technique `crm_user` a été dupliqué à l'identique (mêmes identifiants) sur les trois instances, avec des droits complets sur toutes les bases qu'elles hébergent chacune — ce choix d'homogénéisation simplifie beaucoup le code : une seule paire d'identifiants suffit pour toute connexion, quelle que soit l'instance visée.
**`docker-compose.yml` du conteneur `crm-app`, mis à jour par Jean-Marc :**
```yaml
environment:
DB_HOST: "mysql"
DB_USER: "crm_user"
DB_PASS: "***"
ROUTER_DB_HOST: "mysql2"
ROUTER_DB_USER: "crm_user"
ROUTER_DB_PASS: "***"
```
(`DB_NAME`, `BREVO_API_KEY`, `LIEN_DOC_CLE_SECRETE` inchangées, non reproduites ici — voir section 3 pour le fichier complet.) Pas de `ROUTER_DB_NAME` ajoutée : `db.php` utilise `MIRADOR` par défaut si la variable est absente, ce qui est le cas ici.
**Nouvelle architecture de connexion (`db.php`, réécrit).** Deux fonctions désormais :
- `getRouterConnection()` : connexion fixe vers MIRADOR sur l'instance de routage (`ROUTER_DB_HOST`/`ROUTER_DB_USER`/`ROUTER_DB_PASS`), appelée uniquement par `aspic.php` à l'identification.
- `getDbConnection()` : si la session PHP en cours porte des coordonnées de routage (`$_SESSION['ASPIC_DB_HOST']`/`['ASPIC_DB_PORT']`/`['ASPIC_DB_NAME']`, posées par `aspic.php` après vérification contre MIRADOR), la connexion s'ouvre vers cette base précise — deux utilisateurs connectés en même temps peuvent donc travailler sur deux bases différentes (MS-DEV, MS-TST, ou une base de bêta testeur plus tard), chacun cloisonné dans sa propre session. Sans routage exploitable (pas de session, ex. les scripts publics de prospection — `prospects_core.php`), repli sur la base par défaut du conteneur (`DB_HOST`/`DB_NAME`), exactement comme avant MIRADOR.
**Conséquence importante, à valeur générale pour la suite : aucun des autres endpoints (`contacts.php`, `realisations.php`, `professions.php`, `statuts.php`, `preferences.php`) n'a eu besoin d'être modifié.** Ils appellent tous `getDbConnection()` sans argument, exactement comme avant — c'est cette fonction seule qui route désormais vers la bonne base selon la session en cours. Choix délibéré plutôt que d'introduire une fonction dédiée aux écrans authentifiés avec un nom différent : minimise le diff, cohérent avec la discipline "pas de sur-ingénierie" déjà en place sur ce projet.
**`aspic.php`, réécrit contre MIRADOR.** L'identifiant du formulaire de connexion n'est plus `USR_Code` (3 caractères) mais **`MIR_Email`** (`identification.html` : champ `type="email"`, placeholder "Adresse mail"). Déroulé :
1. Connexion à MIRADOR (`getRouterConnection()`), `SELECT ... FROM MIR_Users WHERE MIR_Email = ? AND MIR_Actif = 1`.
2. Vérification du mot de passe par `password_verify()` contre `MIR_MDPHash` — **`ASP_Users.USR_MDP` n'est plus consulté du tout**, cette colonne devient un vestige que Jean-Marc pourra retirer de chaque base (ASPIC, MS-DEV, MS-TST...) quand il le souhaite, sans urgence. Un hash bcrypt factice est comparé même quand l'email est inconnu, pour ne pas laisser une différence de temps de réponse trahir l'absence d'un compte — dans le même esprit que le `hash_equals()` déjà en place ailleurs sur ce projet, geste peu coûteux plutôt que garantie forte.
3. En cas de succès : `$_SESSION['USR_Code']` reçoit `MIR_Code` (qui reprend intégralement le rôle jusqu'ici tenu par `USR_Code` — `REA_CodeUser`/`OPE_CodeUser`, jointure `ASP_Users` pour les préférences/avatar/ratios) et `$_SESSION['ASPIC_DB_HOST']`/`['ASPIC_DB_PORT']`/`['ASPIC_DB_NAME']` reçoivent les coordonnées de routage lues dans la ligne MIRADOR.
4. Une connexion à la base cible est ouverte immédiatement (via `getDbConnection()`, qui voit déjà la session tout juste posée) pour lire `USR_Nom` — MIRADOR ne porte pas le nom complet de l'utilisateur, seulement email/code/mot de passe/routage. **Un échec de connexion à cette base cible à cet instant fait échouer explicitement le login (500)** plutôt que de laisser entrer un utilisateur dans une base injoignable, ce qui ferait échouer silencieusement tous les écrans suivants.
**Dépendance opérationnelle à garder en tête :** pour qu'un utilisateur MIRADOR se connecte pleinement (nom affiché, préférences, badge société), il faut qu'une ligne `ASP_Users` avec `USR_Code = MIR_Code` existe dans la base cible désignée par sa ligne `MIR_Users`. Comme MS-DEV et MS-TST sont des clones de l'ASPIC existante, les comptes déjà présents (JMP, Vik...) devraient déjà correspondre sans action supplémentaire — mais toute nouvelle ligne MIRADOR pointant vers un `MIR_Code` absent de la base cible laissera l'utilisateur entrer (authentification réussie) tout en cassant `preferences.php` (404) juste après.
**Tests effectués avant livraison** (pas d'accès direct aux instances MySQL réelles depuis cette session) : `php -l` sur les sept fichiers PHP de l'application (les cinq endpoints inchangés inclus, pour non-régression), `node --check` sur le JavaScript extrait d'`identification.html`, équilibre des balises `<div>` (23/23, inchangé). Logique de routage de `getDbConnection()` vérifiée par un script PHP autonome simulant les deux cas (pas de session → base par défaut du conteneur ; session avec routage MIRADOR → host/port/base exacts de la ligne résolue, testé avec deux jeux de coordonnées différents pour confirmer qu'un second utilisateur simultané serait bien routé ailleurs).
**Fichiers livrés le 30 août 2026 :** `db.php`, `aspic.php`, `identification.html`. **Aucun changement sur `contacts.php`, `realisations.php`, `professions.php`, `statuts.php`, `preferences.php`** (voir plus haut). Pas de mise à jour de `version.php` à ce stade — laissé à Jean-Marc, qui incrémente lui-même en fin de session avant le push (convention section 21).
**Reste à faire, côté Jean-Marc, avant test réel :**
- ~~Vérifier/mettre à jour la ligne `MIR_Users` de test existante (Jean-Marc lui-même) pour qu'elle pointe bien vers `MIR_ServeurHost = mysql`, `MIR_BaseNom = MS-DEV`~~ **Fait — voir section 25.**
- ~~Ajouter une ligne `MIR_Users` pour MS-TST~~ **Fait — deuxième utilisateur ajouté et testé avec succès, voir section 25.**
- ~~Déployer les trois fichiers via le circuit habituel, puis tester la connexion sur `aspic.mvl-tls.fr` avec les deux comptes~~ **Fait et validé — voir section 25.**
- ~~Une fois validé, envisager de retirer `USR_MDP` des tables `ASP_Users` (ASPIC, MS-DEV, MS-TST)~~ **Fait le 30 août 2026 (soir) — voir section 25.**
- ~~Redéploiement futur vers `aspic.bzh` une fois ce mécanisme validé sur `aspic.mvl-tls.fr`~~ **Fait le 30 août 2026 (soir) — `aspic.bzh` est en ligne avec le même code, voir section 25.**
@@ -0,0 +1,26 @@
## 25. MIRADOR validé à deux utilisateurs, nettoyage des bases obsolètes, retrait de USR_MDP, déploiement sur aspic.bzh — V1.003 (30 août 2026, soir)
**Contexte.** Suite directe de la section 24 : Jean-Marc reprend la main après le branchement de MIRADOR sur l'authentification pour effectuer les opérations qui restaient à sa charge (voir la liste "reste à faire" de la section 24), puis va nettement plus loin dans la même soirée — au point de considérer l'objectif initial de tout ce chantier (accès simultané multi-bases pour plusieurs utilisateurs sur `aspic.bzh`, section 21) comme atteint.
**Deuxième utilisateur MIRADOR ajouté et testé.** Jean-Marc a ajouté une ligne `MIR_Users` pour un second utilisateur (celui destiné à MS-TST) et généré son mot de passe hashé selon la commande déjà en place (`docker exec -it aspic-crm-app-1 php -r "echo password_hash('...', PASSWORD_DEFAULT) . PHP_EOL;"`, section 21). Test effectué en conservant l'environnement en l'état (deux instances MySQL, deux bases MS-DEV/MS-TST, comme laissé par la section 24) : **connexion réussie avec les deux comptes**, chacun routé vers sa propre base — première confirmation concrète, avec deux vrais utilisateurs distincts (au-delà des simulations par script PHP faites en section 24), que le routage dynamique de `getDbConnection()` fonctionne comme prévu.
**Nettoyage de l'infrastructure — suppression des bases devenues obsolètes.** Une fois ce test validé, Jean-Marc a supprimé les bases **ASPIC** et **MIRADOR** de l'instance `mysql` — les copies d'origine, conservées par précaution depuis la section 24 (`ASPIC` en filet de sécurité avant le clonage vers MS-DEV/MS-TST ; la copie de `MIRADOR` restée sur `mysql`, non consultée par le code depuis que l'application interroge celle de `mysql2`). Ce nettoyage n'a plus de risque à ce stade : la section 24 avait explicitement noté que `db.php` ne s'appuie plus que sur `mysql2` pour MIRADOR (`ROUTER_DB_HOST`) et que MS-DEV/MS-TST sont les seules bases de travail utilisées.
**Retrait de `USR_MDP`.** Colonne supprimée de la table `ASP_Users` dans **MS-DEV** et **MS-TST** (`ALTER TABLE ASP_Users DROP COLUMN USR_MDP` ou équivalent, fait directement par Jean-Marc en base). Cohérent avec ce qui était annoncé dès la section 24 : cette colonne n'était plus consultée par aucun code depuis le branchement de MIRADOR (le mot de passe est vérifié une seule fois, contre `MIR_MDPHash`) — elle était devenue un pur vestige, et son retrait ne change rien au comportement de l'application.
**Déploiement sur `aspic.bzh`.** Jean-Marc a téléchargé les fichiers de l'application sur `aspic.bzh` — premier déploiement réel de ce nom de domaine, dont l'acquisition avait été annoncée dès la section 21 (28 août 2026) comme le déclencheur prévu de toute cette architecture MIRADOR. `aspic.mvl-tls.fr` (environnement de développement habituel) et `aspic.bzh` (nouvel environnement de test) tournent désormais sur un code strictement identique. **Précisé par Jean-Marc le 31 août 2026 : les deux domaines ont chacun leur propre environnement sous Docker** (deux services/`docker-compose.yml` distincts, pas un conteneur partagé entre les deux noms de domaine) — un correctif futur devra donc être déployé séparément sur chacun des deux, via le même circuit FileZilla + SSH mais deux dépôts de fichiers distincts.
**Accès via Homarr.** Jean-Marc s'est installé deux tuiles sur son tableau de bord Homarr (outil de page d'accueil auto-hébergée, hors périmètre technique de ce journal) pour accéder directement aux deux environnements : une tuile vers `aspic.mvl-tls.fr` (développement), une tuile vers `aspic.bzh` (tests) — un raccourci de confort plutôt qu'un changement d'architecture, mais qui matérialise concrètement la coexistence des deux environnements évoquée depuis la section 22.
**Résultat.** Confirmé par Jean-Marc : **"ça fonctionne !"** — les deux environnements sont opérationnels, avec authentification centralisée par MIRADOR, chacun routé vers sa propre base MySQL. C'est la première fois que l'objectif initial du chantier MIRADOR (section 21 : permettre à plusieurs utilisateurs de travailler simultanément, chacun sur sa propre base, une fois `aspic.bzh` actif) est démontré de bout en bout sur les deux domaines réels, et pas seulement en test isolé sur `aspic.mvl-tls.fr`.
**Version applicative — `V1.003`.** Incrémentée par Claude dans `version.php` à la demande explicite de Jean-Marc, pour marquer cette étape ("je souhaite que cette version soit fièrement intitulée 1.003"). Fichier vérifié (`php -l`), livré à Jean-Marc, puis déployé par lui via le circuit habituel (FileZilla → `/docker/aspic/src` → SSH sur le VPS). **Push GitHub effectué le 31 août 2026 avec le commit `00c58fb "V 1.003 Accès par MIRADOR sur deux environnements"`, vérifié réussi par Claude** en récupérant l'état réel du dépôt (`git fetch`) : contenu strictement identique à ce qui avait été livré (diff vide), `version.php` affichant bien `V1.003`, compte de démonstration absent, code MIRADOR présent dans `aspic.php`/`db.php`, les huit fichiers PHP du dépôt passant `php -l` sans erreur.
**Le blocage d'écriture du proxy git côté Claude (section 20) reste entier et n'a jamais été levé** — mais Jean-Marc a explicitement confirmé le 31 août 2026 que ce n'est plus un sujet d'inquiétude pour lui : le circuit de contournement (FileZilla → SSH VPS, section 23) est maintenant adopté comme "une habitude de travail à prendre", pas une gêne. Point de méthode retenu pour ce circuit : les trois commandes du terminal (`git add`, `git commit`, `git push`) peuvent être collées d'un bloc dans le terminal SSH plutôt que tapées une à une — un terminal exécute les lignes collées dans l'ordre, comme si elles étaient tapées séquentiellement. Conseil donné par Claude : les enchaîner avec `&&` (`git add -A && git commit -m "..." && git push`) plutôt que de les laisser sur des lignes séparées, pour qu'un échec sur une étape (ex. rien à committer parce que le dépôt FileZilla n'a pas eu lieu avant) interrompe la suite au lieu de s'exécuter dans le vide.
**Point de vigilance résolu :** la question ouverte en fin de chantier (l'application `aspic.bzh` partage-t-elle son conteneur avec `aspic.mvl-tls.fr` ou dispose-t-elle de son propre déploiement Docker ?) a été tranchée par Jean-Marc le 31 août 2026 — voir le paragraphe "Déploiement sur `aspic.bzh`" ci-dessus : chaque environnement a son propre déploiement Docker.
**Reste à faire, plus tard :**
- Poursuivre l'onboarding de futurs bêta testeurs externes (objectif business sous-jacent à toute l'architecture MIRADOR — voir réflexions stratégiques) : rappel de la règle actée en section 21, migrer MIRADOR vers une instance MySQL dédiée avant l'arrivée du tout premier bêta testeur réel, pas avant.
- `src_old` (section 23) toujours à retirer une fois le nouveau circuit Git éprouvé sur quelques cycles supplémentaires de livraison — non traité dans cette session.