# Rapport v1.17.0 — domicilier une association, et lire toutes les pièces, même abîmées

Base : `v1.16.1` (`476f223`, en production depuis le 16/10/2026). Tag : `v1.17.0`.
Cinq commits, un par partie (A à E), puis les suites de la relecture indépendante (un commit à part).
**Suite complète : 1 643 tests, 1 642 au vert, 10 678 assertions** (1 604 à v1.16.1).
Un seul échec, préexistant et propre au poste de développement :
`PortailSousDomaineTest › écrit les liens du portail…` (l'`APP_URL=http://localhost:8000`
du `.env` local fuit dans `route()` ; il échouait déjà au tag v1.16.1, il passe sur une
configuration sans port). Aucune dépendance composer ni npm. `public/build/` recompilé.

## Le droit retenu, tel qu'il est écrit dans les documents

- Le **siège** d'une association : dahir n° 1-58-376 du 3 joumada I 1378 (15 novembre
  1958), art. 5 — c'est l'association qui déclare (autorité locale, procureur du Roi,
  changements dans le mois). MOBDIE ne déclare rien à sa place.
- Le **contrat** : Dahir des obligations et contrats (déjà cité aux art. 8 et mise en
  demeure).
- Les **devoirs de MOBDIE** (544-4, 544-7) restent : ils ne sont cités qu'à l'**article 3**
  du contrat et dans les **lettres aux administrations**.
- **Tombent** : RC dans les 3 mois, certificat négatif, modèle J, lettre au greffe.
- **Tribunal** : réglage `cadre_legal.tribunal_civil`, « Tribunal de première instance de
  Fès ».

Recommandation maintenue : faire relire le contrat « association » par un juriste avant
la première association.

## B-1 — L'inventaire des gabarits

| Gabarit (`ModelesDeDocuments::gabarits()`) | Ce qu'il imprimerait pour une association | Décision |
|---|---|---|
| `contrat` | art. 1 « conformément aux articles 544-1 et suivants » ; art. 3 RC sous 3 mois, greffe ; art. 4 (544-6) greffe ; art. 5 (544-5) ; « siège social » ; indemnité d'occupation « au registre du commerce » | **`contrat_association`** (texte du lot ; art. 2, 6–9, 11–13, 15 repris du contrat EN SERVICE) |
| `avenant` | art. 1 544-1 ; annexe = articles du contrat société | **`avenant_association`** (annexe = articles 2–15 du contrat association) |
| `attestation` | « conformément aux articles 544-1 et suivants » | **`attestation_association`** |
| `attestation_fin` | 544-1 ; rien sur la déclaration du nouveau siège | **`attestation_fin_association`** (rappelle la déclaration dans le mois, art. 5) |
| `resiliation_non_paiement`, `resiliation_demande`, `resiliation_accord` | 544-1 ; « informer … le greffier du tribunal compétent (544-6) » | **trois variantes `_association`** |
| `lettre_administration` | devoir de MOBDIE (544-4, 9°) — juste ; mais « cette entreprise », et le tableau d'identité « Raison sociale / RC : non communiqué » | **`lettre_administration_association`** + tableau d'identité par le récépissé (`DocumentsDeFin::tableauIdentite`) |
| `avenant_offre` | « le domicilié », « siège social » (révélé par l'inventaire) | **`avenant_offre_association`** |
| `mise_en_demeure` | art. 255 DOC ; « siège {{social_ou_entreprise}} » → « siège de l'association » ; aucune référence au Code de commerce | **pas de variante** |
| `lettre_pli_non_remis` | devoir de MOBDIE (544-4, 8°) ; texte neutre ; tableau d'identité adapté | **pas de variante** |
| `resiliation` (ancien acte, désactivé) | — | inchangé |

Ailleurs :
- **Mise en page des actes** (`documents/layout`) : rôles « Le domiciliataire / Le
  domicilié », cadres de légalisation — identiques (B-7) ; le titre du signataire vient de
  `FormeJuridique::titreDe()` (Président / Présidente).
- **Variables calculées** : `phrase_reste_du`, `phrase_accord`, la liste `{{prestations}}`
  (le catalogue dit « siège du domicilié ») passent par
  `ModelesDeDocuments::accorderPourLAssociation()` ; `{{mention_recepisse}}` (le récépissé de
  MOBDIE) tait sa référence à 544-7 dans un acte d'association ; `{{domicilie_rc}}` /
  `{{domicilie_rc_ville}}` n'impriment rien.
- **Modèles de message** (en base, éditables) : `reponse_demande` demande « le certificat
  négatif de la dénomination », `notification_resiliation` parle du « siège social de la
  société » — **laissés tels quels** (voir plus bas).

## A — Le modèle

- `DomiciliationClient::estAssociation()` (seule source) ; `associationDeclaree()` ;
  `mentionRecepisse()` (« déclarée auprès du Pachalik de Fès (récépissé définitif n° 45/2026
  du 03/02/2026) » / « en cours de déclaration » ; l'article de l'autorité n'est jamais doublé).
- `FormeJuridique::ASSOCIATIVES`, `estAssociative()` ; `titreParDefaut('association')` =
  Président / Présidente ; le titre saisi prime. (Un test v1.9.0 qui prenait l'association
  comme « forme sans règle » prend désormais la coopérative.)
- Pièces : `recepisse_association`, `pv_constitutif`, `liste_bureau` (devinées d'après le nom
  du fichier, avant le récépissé de séjour) ; `exigencesDocumentaires($type, $forme)` :
  pièce d'identité du président, statuts, récépissé, contrat — **pas de certificat négatif** ;
  PV et liste du bureau souhaitables.
- Réglages (Cadre légal) : `cadre_legal.tribunal_civil`, `cadre_legal.delai_recepisse_association`
  (60, écrit comme pré-réglage dans l'aide de l'écran).

## B — Les documents

- `DocumentGenerator::gabaritPour()` : la variante `_association` quand `estAssociation()` ;
  absente ou désactivée → `ModeleAssociationAbsentException` (« Le modèle association du
  contrat est absent ou désactivé… ») — **jamais de repli sur la société**. Les appels
  existants la rattrapent (assistant, résiliation, demandes, réédition) ; « Générer un
  document » l'affiche en notification ; un lien de PDF répond 409 avec le message.
- Les variantes ne se choisissent pas à la main (liste de « Générer un document ») ; elles
  sont visibles et éditables dans « Modèles de documents », rangées sous leur gabarit de base
  (« Contrat de domiciliation — association », description qui le dit).
- Référence d'un document : la nature de l'acte (`CONTRAT/…`), quelle que soit la variante ;
  `meta.template` garde la clé réelle (`contrat_association`) pour « l'autre version ».
- `reprise:gabarit-prestations` traite aussi `contrat_association` (version `2026-11-A.1`).
- **Non-régression** : `tests/Feature/InstantaneDesActesTest.php` — 26 actes (11 gabarits ×
  société et personne physique, plus contrat et avenant sur un contrat « 2026-10.1 » à
  `{{prestations}}`), HTML rendu figé **au commit de base** dans
  `tests/Fixtures/instantanes-actes-v1.16.1/` (images réduites à leur empreinte), identique
  octet pour octet.

## C — Le dossier, la conformité, les alertes

- `LegalCompleteness` : dénomination, objet, président, pièce, adresse, téléphone, e-mail ;
  les quatre champs du récépissé dès qu'il est entamé ; ICE souhaitable ;
  `exigibiliteRc()` / `rcExigible()` → null / faux.
- `App\Support\RecepisseAssociation` (seule source) : attendu à J+`delai` du début du
  contrat ; **reçu** = validé, ou classé par l'exploitant ; jamais au seul dépôt en ligne.
  `ConformityService` lève « Récépissé de déclaration du siège à recevoir » et le ferme
  quand il est reçu ; `ValidationPieces::valider()` le ferme. Plus d'alerte RC ni de
  `RappelsRc` pour une association. Fiche (infolist), contrôles, espace client (« À faire »),
  espace cabinet (tâches).
- Fin : `CadreLegal::destinatairesPour()` — impôts, TGR, douane si cochée ; **aucune lettre
  au greffe**, ni à l'autorité locale (l'attestation de fin le rappelle). Le compteur dit « x/3 ».
- Registre : « toute personne domiciliée » ; colonnes **Forme** et **RC ou récépissé**
  (écran et export).
- Loi 43-05 : colonne additive `beneficiaires_effectifs.fonction` ; le président pré-inscrit
  (« Présidente »), les membres du bureau par fonction, sans part ni contrôle « ≤ 100 % ».

## D — Les écrans

Fiche (Dénomination, Objet, Président(e), onglet « Déclaration de l'association » ; RC,
tribunal, certificat négatif cachés), infolist, liste (badge Association, filtre Forme,
« Sans certificat négatif » sans les associations), recherche globale par récépissé ;
assistant « Nouveau dossier » (tuile Association, zone « Récépissé de déclaration et
statuts ») ; formulaire en ligne et espace cabinet (type Association, « En cours de
déclaration / Déclarée », tuile récépissé ou statuts obligatoire, lecture qui ne remplit que
le vide, président et bureau, projet de contrat sur le gabarit association) ; espace client
(profil sans capital, « Pour l'autorité locale et la banque ») ; lien « Vérifiez votre
dossier » ; recherche (« Récépissé », « Président(e) ») ; assistance IA (forme, récépissé).
**Factures, devis, reçus** : déjà conformes (nom, adresse, ICE seulement s'il existe ;
aucun RC du client) — rien à changer.

## E — Lire toutes les pièces, même abîmées

1. **Lire d'abord, juger ensuite** : `PhotoEnPdf` mesure, ne refuse plus ; `ControlePieces`
   signale « Photo petite » / « Photo peu nette » ; bloquants : vide, ni image ni PDF,
   indécodable, **et une lecture qui n'a rien rendu** (« Le numéro ne se lit pas : reprenez
   la photo à plat, sans reflet, la carte remplissant l'écran. »).
2. **Vues** (`ImagePourIa::vues`) : entière (niveaux 1–99 % par canal, ombres éclaircies,
   netteté douce), carte recadrée (marge 3 %, ≤ 2 000 px), zone MRZ ou numéro agrandie en
   gris. **Passe de cadrage** (modèle courant, image 1 000 px) : cadre, orientation, MRZ,
   numéro. Au plus 3 vues par face.
3. **PDF scanné** (`PdfEnImages`) : pdftoppm, Imagick, gs (détectés à l'exécution ;
   `mobdie:diagnostic` le dit). La photo d'origine gardée au dépôt est lue plutôt que son
   PDF (`PieceAnalyzer::aLire`).
4. **Escalade** sur champ clé manquant ou douteux, ou MRZ non prouvée — y compris au
   portail, où le délai de 45 s l'interdisait ; la seconde lecture reçoit la première et ne
   relit que ce qui manque. Au plus 2 lectures + le cadrage ; plafond = arrêt sur la
   première lecture ; en mode direct de l'admin, budget de 40 s (cadrage sauté).
5. **`App\Support\Mrz`** : TD1/TD3, clés 7-3-1, une confusion par champ ; **le format du
   n° de CIN** et **la clé composite** comme preuves supplémentaires (le banc a montré
   qu'une double confusion peut tomber sur la bonne clé : une chance sur dix) ; prouvé →
   l'emporte sur la face, l'écart est dit ; clé en échec → « à vérifier ».
6. **Sûr / douteux** ; « ? » pour l'illisible ; un champ douteux pré-remplit avec « À
   vérifier » (formulaire, assistant, « Vérifier & appliquer », points de « À valider →
   Pièces »).
7. **Hors de la requête** : `LirePieceDuPortail` (après la réponse si LiteSpeed sait la
   clore, sinon par la file ; « immediate » dans les tests), résultat en cache sous une clé
   verrouillée, `wire:poll` « Lecture de votre CIN… ». Les lectures du portail comptent
   désormais dans la dépense du mois.

### Le banc synthétique (`mobdie:banc-lecture --nombre=20`, sans clé d'API)

20 CIN fictives × 13 dégradations (intacte, flou 1 et 3, JPEG 30, de travers 3° et 12°,
quart et demi-tour, reflet, sous-exposée, petite dans le cadre, 480 px, pliure) ; 120
bandes MRZ abîmées (aucune erreur, confusion dans le n°, dans la naissance, double
confusion, erreur non confusable, caractère illisible).

| Mesure | v1.16.1 | v1.17.0 |
|---|---|---|
| Recadrage sur le cadre de la carte | aucun (photo entière réduite à 1 600 px) | 260 / 260 |
| Écart des niveaux (1ᵉʳ–99ᵉ centile, moyenne) | 181 (154 brut) | 241 |
| N° (MRZ) : juste / faux donné pour sûr | 60 / **60** | 60 prouvés, 60 à vérifier, **0 faux** |
| Naissance (MRZ) : juste / faux donné pour sûr | 80 / **40** | 64 prouvés, 56 à vérifier, **0 faux** |
| Nom (MRZ, sans clé) | pris tel quel | toujours « à vérifier » |

**Objectif tenu sur le banc synthétique : aucune valeur fausse donnée pour sûre.** La
lecture complète par le modèle (`--lecture`) et `--pieces=N` demandent une clé d'API : à
lancer en production par le gérant (lecture seule, sans donnée personnelle affichée).

## Migrations — une seule, additive et idempotente

`2026_10_28_100000_les_associations` : `domiciliation_clients.recepisse_numero`,
`recepisse_date`, `recepisse_nature`, `recepisse_autorite` ; `beneficiaires_effectifs.fonction` ;
réglages `cadre_legal.tribunal_civil` et `cadre_legal.delai_recepisse_association` ; les neuf
gabarits `_association` (version `2026-11-A`) **bâtis sur le contrat en service** — créés
s'ils manquent, jamais réécrits. Rejouée : rien à faire (testé). Base vide : OK (testé).
Aucune reprise de données (0 association au 01/10).

## Fichiers supprimés ou renommés

**Aucun** (`git diff --name-status --diff-filter=DR v1.16.1 v1.17.0` vide).

## Laissé tel quel, et pourquoi

- **Modèles de message en base** (`reponse_demande`, `notification_resiliation`) : ils
  parlent de certificat négatif et de « société » ; ce sont des textes éditables par le
  gérant (Messagerie → Modèles de message), et aucune association n'est en base. À adapter
  par lui s'il veut un message propre aux associations.
- **Alerte RC d'une personne physique** : `ConformityService` la lève aussi pour une
  personne physique sans RC (constat de l'inventaire, antérieur) ; hors du lot, « rien ne
  change pour les personnes physiques ».
- **La forme « Association » est une personne morale** dans `Domiciliation::types()` : pas
  de troisième type (A-1) ; seuls les écrans l'affichent « Association ».
- **Le libellé « Le domicilié »** dans la mise en page des actes (rôles) : identique aux
  sociétés (B-7).
- **L'aide « 45 secondes »** de « Analyser par IA » (mode direct réel : 25 s par appel,
  40 s au total) : texte antérieur, non touché.
- **Le nom lu sur la bande MRZ** n'a pas de clé : il ne remplit qu'un champ vide, et reste
  « à vérifier ».

## Relecture indépendante

Faite par un agent qui n'avait pas écrit le code, sur `git diff v1.16.1..HEAD`. Il a
d'abord **recontrôlé la non-régression lui-même** : les 26 actes rendus depuis le code du
tag v1.16.1 sont identiques octet pour octet aux instantanés. Ses constats, et leur suite
(commit « suites de la relecture indépendante ») :

| Constat | Suite |
|---|---|
| **Bug** — la carte était recadrée sur l'image déjà tournée avec un cadre mesuré avant rotation : une CIN à l'envers dans un coin donnait une vue de la table | corrigé (recadrer, puis tourner) ; test d'une carte tournée et décentrée |
| Une lecture vide de bout en bout (exception) devenait « échec » (non bloquant) au lieu de « rien » (bloquant) | corrigé ; le test simule l'exception réelle |
| Une lecture arrivée sans que l'écran l'ait cherchée était perdue à l'envoi | « Continuer » et « Envoyer » la prennent ; test |
| La dépense du banc `--pieces` échappait au plafond | comptée dans le mois |
| Lecture après la réponse : durée et mémoire de PHP | durée desserrée à 240 s ; photo > 4 000 px ramenée ; `memory_limit` noté au guide |
| Correction d'une clé non reportée dans la composite ; date prouvée mais impossible ; « 31/02 » reportée au 3 mars | corrigés |
| « du Arrondissement » ; récépissé entamé par sa seule autorité ; fichier muet pris pour le récépissé ; aperçu « SOCIÉTÉ EXEMPLE » | corrigés |
| Le contrat association n'était testé que sur le texte livré | test sur un contrat en service à `{{prestations}}` |
| `douteux` qui survivait à une relecture | effacé à chaque nouvelle lecture, gardé dans le brouillon |

Vérifié correct par la relecture : choix du gabarit, refus rattrapé partout, aucune
lettre au greffe, `RappelsRc`, propriétés `#[Locked]`, clés de cache non devinables,
injection Filament, migration idempotente, commandes de rastérisation sans shell.
