# Déploiement — Application STE MOBDIE PRIVÉ

Ce document décrit la mise en ligne de l'application sur le serveur cPanel réel.
Il reprend les paramètres qui ont déjà causé des pertes de temps sur le projet jumeau.

---

## 1. Hébergement

- **cPanel** : CapConnect — `saturn.capconnect.com:2083`
- **Compte** : `mobdiema`
- **Home** : `/home/mobdiema`
- **Racine web** : `/home/mobdiema/public_html/app.mobdie.ma/public`
- **Préfixe MySQL automatique** : `mobdiema_` (toute base/utilisateur créé est préfixé)
- **PHP** : passer de **8.2 à 8.3** via *MultiPHP Manager* pour le domaine `app.mobdie.ma`.

## 2. DNS (⚠️ point sensible)

- La zone DNS de **mobdie.ma est administrée chez Hostinger, pas dans cPanel.**
- **L'éditeur de zone de cPanel est inopérant sur ce domaine** : toute modification qui y est faite n'a **aucun effet**.
- L'enregistrement **A `app`** doit pointer vers **`46.4.24.98`** (à régler chez Hostinger).

## 3. Mise en ligne

1. Téléverser le contenu du zip `mobdie-app-v1.0.0.zip` dans `/home/mobdiema/public_html/app.mobdie.ma/`.
2. Vérifier que la racine web pointe bien sur le sous-dossier `public/`.
3. Copier `.env.example` en `.env` et compléter les valeurs (voir §6).
4. Générer la clé : `php artisan key:generate`.
5. Créer la base MySQL `mobdiema_app` et un utilisateur `mobdiema_app` (mot de passe fort) via cPanel, puis les renseigner dans `.env`.
6. Charger les données reprises :
   ```
   /opt/cpanel/ea-php83/root/usr/bin/php artisan mobdie:charger-dump
   ```
   Le dump `database/dump-initial.sql` contient la structure **et** toutes les données
   (97 clients, contrats, 197 échéances, 1818 opérations, 1795 lignes d'archive brute,
   comptes, catégories, prêteurs, rôles, réglages et les 3 utilisateurs).
   > La reprise depuis les fichiers Excel **ne peut pas** s'exécuter sur le serveur :
   > les sources restent sur le Mac. C'est le dump qui peuple la base.
7. Vider et reconstruire les caches :
   ```
   php artisan optimize:clear && php artisan config:cache && php artisan route:cache
   ```
8. (Optionnel) Après téléversement de l'archive documentaire, la normaliser :
   ```
   php artisan reprise:normaliser-archive
   ```
   en réglant `ARCHIVE_SOURCE_PATH` sur le dossier téléversé.

## 4. Cron (file d'attente + planificateur)

Ajouter ces **deux** tâches cron. Les trois réglages du worker sont indispensables,
faute de quoi les analyses IA échouent :

- délai d'exécution élevé (`--timeout=900`),
- limite mémoire explicite (`-d memory_limit=512M`) — le PHP CLI est plafonné à
  128 Mo indépendamment du réglage web,
- délai de reprise de la file (`DB_QUEUE_RETRY_AFTER=960`) **supérieur** au délai d'exécution.

```cron
* * * * * /opt/cpanel/ea-php83/root/usr/bin/php -d memory_limit=512M /home/mobdiema/public_html/app.mobdie.ma/artisan queue:work --stop-when-empty --max-time=55 --tries=3 --timeout=900 >/dev/null 2>&1
* * * * * /opt/cpanel/ea-php83/root/usr/bin/php /home/mobdiema/public_html/app.mobdie.ma/artisan schedule:run >/dev/null 2>&1
```

Le planificateur déclenche :
- `mobdie:planificateur-quotidien` à 07:00 (relances à valider, alertes de conformité, renouvellements, charges récurrentes),
- `mobdie:sauvegarde` à 02:30 (sauvegarde base + documents),
- `mobdie:balayer-analyses` toutes les heures (solde les analyses IA figées « en cours »).

> `--tries=3` et non `--tries=1` : un 429 ou un 529 de l'API sont des erreurs
> transitoires par nature, et un seul essai les traitait comme définitives. Les
> travaux distinguent désormais ce qui vaut d'être réessayé (429, 5xx, délai) de
> ce qui ne le vaut pas (clé refusée, modèle inexistant), et ne réessaient que
> le premier.

**Surveiller les travaux échoués.** La table `failed_jobs` se remplit désormais,
là où `--tries=1` faisait tout disparaître. Le bandeau « Arrière-plan » du
tableau de bord en donne le compte ; `php artisan queue:retry all` les rejoue.

## 5. Passerelle LiteSpeed

La passerelle coupe les requêtes web à ~60 secondes. **Toute analyse IA tourne en
file d'attente**, jamais en synchrone. `ANTHROPIC_TIMEOUT=300` et le `--timeout=900`
du worker couvrent les traitements longs.

## 6. Variables d'environnement notables

| Variable | Valeur / rôle |
|---|---|
| `CACHE_STORE` | `file` (pas de Redis sur mutualisé) |
| `SESSION_ENCRYPT` | `true` (sessions chiffrées) |
| `QUEUE_CONNECTION` | `database` |
| `DB_QUEUE_RETRY_AFTER` | `960` (> timeout worker 900) |
| `ANTHROPIC_TIMEOUT` | `300` |
| `MAIL_REPLY_TO_ADDRESS` | `facturation@mobdie.ma` |
| `MAIL_BCC_ADDRESS` | `facturation@mobdie.ma` |
| `ARCHIVE_SOURCE_PATH` | dossier de l'archive à normaliser |
| `SEED_SUPERADMIN_PASSWORD` / `SEED_ADMIN_PASSWORD` / `SEED_MANAGER_PASSWORD` | mots de passe initiaux, rejoués à l'identique |
| `GOOGLE_SERVICE_ACCOUNT_JSON` | JSON (ou chemin) du compte de service Drive |
| `GOOGLE_DRIVE_FOLDER_ID` | dossier Drive de destination des sauvegardes |

Chaque variable est documentée dans `.env.example`.

## 7. Adresses e-mail à créer sur le cPanel

| Adresse | Rôle |
|---|---|
| `noreply@mobdie.ma` | Expéditeur technique SMTP, **ne reçoit rien** |
| `facturation@mobdie.ma` | Réponse des devis/factures/relances, **reçoit chaque envoi en copie cachée** |
| `domiciliation@mobdie.ma` | Service, publiée sur les contrats et le formulaire public |
| `gerance@mobdie.ma` | Alertes internes et rapports de sauvegarde |
| `contact@mobdie.ma` | Adresse générale (conservée si elle existe déjà) |

> **DKIM** : la clé générée par cPanel doit être **recopiée manuellement dans la zone DNS
> chez Hostinger**, sans quoi les relances partiront en indésirable.

## 8. Fichier `public/.htaccess`

Il doit contenir un `DirectoryIndex index.php` explicite et les en-têtes de sécurité,
y compris une politique de contenu **compatible avec Filament**, car il hérite du
`.htaccess` du domaine parent.

> Ce fichier est **masqué par défaut** dans le gestionnaire de fichiers de cPanel :
> activer l'affichage des fichiers cachés pour le vérifier.

Un modèle est fourni dans `deploy/htaccess-public.txt` (à recopier dans `public/.htaccess`).

## 9. Sauvegarde Google Drive

- Créer un **compte de service** Google, activer l'API Drive, partager le dossier cible
  avec l'e-mail du compte de service.
- Renseigner `GOOGLE_SERVICE_ACCOUNT_JSON` et `GOOGLE_DRIVE_FOLDER_ID`.
- Si Drive échoue ou n'est pas configuré, l'application bascule automatiquement sur une
  **sauvegarde locale avec rotation** et envoie une confirmation quotidienne à `gerance@`.
  **L'application ne s'arrête jamais faute de Drive.**

### La preuve est en base, pas dans la boîte aux lettres

Chaque exécution écrit son résultat dans la table `backup_runs` — date, taille, durée,
succès ou échec, et la raison de l'échec. **Le courrier n'est plus la preuve** : une
messagerie muette et un disque plein produisaient jusqu'ici le même signal, c'est-à-dire
aucun.

Au-delà de **36 heures sans sauvegarde réussie**, un bandeau « Arrière-plan » paraît sur
le tableau de bord pour qui gère les réglages. Aucune sauvegarde du tout compte comme un
retard : c'est le cas le plus grave.

### Restaurer

```bash
# Contrôler l'archive sans rien écrire — à faire d'abord, toujours.
php artisan mobdie:restaurer storage/app/backups/mobdie-backup-2026-07-30_023000.zip --verifier-seulement

# Restaurer la base et les documents.
php artisan mobdie:restaurer storage/app/backups/mobdie-backup-2026-07-30_023000.zip

# La base seule, sans recopier les documents.
php artisan mobdie:restaurer <archive.zip> --sans-fichiers
```

En production, la commande **exige que l'on écrive le nom de la base** avant d'écraser
quoi que ce soit (`--force` la passe outre, pour un script). Après restauration, vérifier
le nombre de clients, de factures et d'écritures **avant** de rouvrir l'application.

> Une sauvegarde qui n'a jamais été relue n'est pas une sauvegarde. Relire une archive au
> moins une fois par trimestre, sur un serveur de recette, fait partie de l'exploitation.

## 10. Première connexion

Les mots de passe initiaux des trois comptes sont ceux définis dans le `.env`
(`SEED_*_PASSWORD`). Le **changement de mot de passe est imposé à la première connexion**.

---

## Nouveautés de la v1.3 — vérifications et outils

### Après la mise à jour

Les migrations de la v1.3 corrigent d'elles-mêmes le réglage du modèle d'IA et
accordent aux rôles existants les permissions de la nouvelle archive des
documents. Un simple `php artisan migrate --force` suffit ; aucun seeder n'est
à relancer.

Recompiler les feuilles de style est en revanche indispensable, sans quoi les
nouveaux écrans s'afficheront sans mise en forme :

```bash
npm ci && npm run build
php artisan optimize:clear && php artisan config:cache && php artisan route:cache
```

### Contrôler que tout est en état de marche

```bash
php artisan mobdie:diagnostic
```

La commande vérifie en une passe la clé API, la validité du modèle d'IA, la
réponse de l'API, le battement de la file d'attente, l'écriture dans storage,
l'espace disque, les gabarits de documents, la configuration mail et la base de
données. Elle affiche un tableau et **sort en code 1 dès qu'un contrôle
échoue**, ce qui permet de la brancher sur une supervision. L'option
`--sans-api` la fait tourner sans contacter Anthropic.

C'est la commande à lancer avant d'appeler à l'aide.

### Le témoin de vie de la file d'attente

Le worker écrit un battement à chaque boucle. Les écrans de domiciliation
affichent un bandeau d'avertissement si ce battement date de plus de cinq
minutes. Le cron `queue:work` ci-dessus, qui tourne chaque minute et s'arrête
quand la file est vide, écrit ce battement à chacun de ses passages : un
bandeau qui s'affiche signifie donc que le cron ne tourne plus.

### Les deux commandes d'entretien

```bash
# Ramène le modèle d'IA dans la liste des identifiants valables.
php artisan reprise:corriger-modele-ia

# Réconcilie le disque et la base : indexe les fichiers présents dans
# storage/app/private/client-documents et storage/app/private/archive-source
# qui n'ont pas d'enregistrement, et signale ceux dont le fichier a disparu.
php artisan mobdie:indexer-archive --simulation   # rapport seul, sans écriture
php artisan mobdie:indexer-archive                # écrit réellement
```

L'indexation est idempotente : une seconde passe ne recrée rien.

---

## Correctif v1.3.1 — téléversement de fichiers

### Ce qu'il faut faire au déploiement

Aucune migration. Après la mise à jour du code :

```bash
php artisan optimize:clear
php artisan config:cache && php artisan route:cache
npm ci && npm run build
```

Le vidage puis la reconstruction du cache de routes n'est pas facultatif : le
journal du 28/07 portait des « Route [filament.admin.pages.echeancier-global]
not defined » levées depuis la barre latérale, symptôme classique d'un cache de
routes plus ancien que le code déployé.

### Vérifier que le téléversement est réparé

Deux contrôles, dans cet ordre.

```bash
php artisan mobdie:diagnostic
```

La ligne « Téléversement » doit être à OK. Ce contrôle écrit un fichier par le
même chemin de code que l'application — `putFileAs()`, celui qui était cassé —
puis le relit et compare les octets. Il ne remplace pas le second contrôle : il
tourne en ligne de commande, or c'est précisément le contexte web qui était en
cause.

Puis, connecté en super-administrateur, ouvrir **Administration → Diagnostic du
téléversement** et y envoyer le PDF qui échouait. Toutes les étapes doivent être
au vert, sauf éventuellement « getRealPath() » et « Les deux chemins
coïncident-ils ? » : c'est justement l'anomalie du serveur, et l'application
sait désormais s'en passer. Ce qui compte est que « putFileAs() »,
« Storage::exists() juste après putFileAs() » et « Taille du fichier écrit »
soient au vert.

Le bouton « Copier le rapport » produit le texte à faire remonter en cas de
problème. Le bouton « Supprimer les fichiers d'essai » nettoie ensuite le
dossier `diagnostic-televersement`.

### Le témoin de vie de la file d'attente

Il est désormais écrit dans `storage/framework/queue-battement`, et non plus
dans le cache : un `php artisan optimize:clear` ne le fait plus mentir. Le
fichier est ignoré par git et recréé par le worker à sa première boucle. Après
un déploiement, laisser passer une minute avant de s'inquiéter d'un bandeau
« La file d'attente est arrêtée ».

---

## Mise à jour v1.4.7 — cachet téléversable, numéros NN/AAAA, factures antérieures

### Dans cet ordre

```bash
php artisan migrate --force
php artisan optimize:clear && php artisan config:cache && php artisan route:cache
```

Quatre migrations, toutes idempotentes ou sans donnée à reprendre :

* le réglage `documents.cachet` apparaît dans l'onglet **Documents**, livré vide
  — tant que rien n'est téléversé, c'est le fichier livré qui sort et les
  documents produits ne changent pas d'aspect ;
* l'unicité des devis et des factures passe du numéro seul au couple série +
  numéro, parce que la référence ne porte plus le préfixe et que `12/2026` peut
  désormais désigner à la fois un devis et une facture ;
* les textes d'aide de l'onglet **Numérotation** décrivaient l'ancien format et
  sont réécrits ;
* `invoices` gagne `date_origine` et `date_corrigee`, `client_documents` accepte
  une pièce sans client et gagne le lien vers la facture qu'elle justifie.

Le fichier `public/images/brand/cachet.png` a quitté `public/` pour
`resources/images/brand/cachet.png` : il n'est plus servi en libre accès. Rien à
faire à la main — il voyage avec le code.

### La reprise des trente-quatre factures antérieures

À passer **une seule fois**, après la migration. La commande est idempotente :
un second passage ne duplique rien.

```bash
php artisan reprise:importer-factures-historiques --simulation   # rapport seul
php artisan reprise:importer-factures-historiques                # écrit réellement
```

Le relevé de lecture et les trente-quatre PDF découpés sont livrés avec le code
dans `database/reprise/factures-2026`. Les chiffres de contrôle affichés en fin
d'exécution doivent tous être à OK : 34 factures, 39 083,31 DH HT, 7 816,69 DH
de TVA, 46 900,00 DH TTC.

La commande n'écrit **aucune** opération de trésorerie et **aucun**
encaissement : l'argent de ces factures est déjà au journal, et les créer
compterait le chiffre d'affaires deux fois. Elle ne touche pas non plus aux
échéances ni aux commissions.

Elle refuse d'écrire si la chronologie de la série est rompue — un numéro qui
précède une date antérieure — et ne laisse alors rien derrière elle.

### Vérifier

```bash
php artisan tinker --execute="echo \App\Services\NumberSequence::next(\App\Models\Invoice::class, setting('numerotation.facture_serie'), 2026);"
```

Doit répondre `35/2026`. Le report saisi dans les Réglages (34) devient sans
effet de lui-même : la base l'égale désormais, et il n'y a pas à le retirer.

---

## Mise à jour v1.4.8 — commissions, zone d'envoi, assistance, série des factures

### Dans cet ordre

```bash
php artisan migrate --force
php artisan db:seed --class=EmailTemplateSeeder --force
php artisan optimize:clear && php artisan config:cache && php artisan route:cache
```

Six migrations, toutes idempotentes :

* `invoices.numero` accepte le vide — un brouillon n'occupe plus aucun rang ;
* la permission `factures.numeroter`, donnée au seul super-administrateur ;
* `commissions.installment_id` et l'unicité (échéance, commercial) ;
* la table `email_templates` et les colonnes `modele` / `pieces_jointes` du
  registre du courrier ;
* le réglage `ia.taux_usd_mad`, qui convertit en dirhams le coût des appels.

Le semoir des modèles de message n'est pas rejoué par les autres semoirs : il
faut le lancer une fois. Il ne recrée que ce qui manque et ne réécrase aucun
texte modifié.

### Rattraper les commissions

À passer une fois, après la migration. La commande est rejouable : un second
passage ne crée aucun doublon.

```bash
php artisan mobdie:generer-commissions --simulation   # rapport seul
php artisan mobdie:generer-commissions                # écrit réellement
```

Elle ne crée **aucune** opération de trésorerie : une commission acquise n'est
pas une commission versée, et le versement se saisit au journal quand il a lieu.

Sur les données actuelles, elle ne créera rien et le dira : vingt-neuf échéances
encaissées n'ont aucun commercial de recouvrement, et les neuf autres relèvent
d'un événement — la signature — pour lequel aucun barème n'existe. Ce sont deux
décisions d'exploitant, et l'application ne les prend pas à sa place :

1. définir le barème « Signature d'un nouveau client » dans **Facturation →
   Barèmes de commission** (l'encart en tête de cet écran nomme les manques) ;
2. désigner le commercial de recouvrement des échéances concernées depuis
   l'échéancier — l'écran des commissions les liste ;
3. relancer la commande.

### Ce qui change à l'usage

**Les brouillons de facture n'ont plus de numéro.** Le numéro s'attribue à
l'émission, par le bouton « Émettre la facture ». Un brouillon se supprime sans
conséquence ; une facture émise ne se supprime plus — elle s'annule, ou se
compense par un avoir. Un contrôle en tête de la liste des factures signale un
trou ou un doublon dans la série ; il est muet quand tout va bien.

**Le numéro se modifie à la main**, dans le formulaire de la facture, mais
seulement par le super-administrateur, et chaque modification est inscrite au
journal d'audit.

**Une zone d'envoi** apparaît dans le groupe Domiciliation. Les modèles se
gèrent depuis les Réglages, bouton « Modèles de message ».

**Une assistance** apparaît dans le groupe Administration. Elle lit des vues
préparées, n'écrit jamais, cite ses sources, et son coût du mois s'affiche dans
l'onglet IA des Réglages. Ajustez-y le cours du dollar.

### Vérifier

```bash
php artisan tinker --execute="\$f = \App\Models\Invoice::create(['client_nom' => 'Essai', 'date' => now()]); echo \$f->reference; \$f->forceDelete();"
```

Doit afficher « brouillon du JJ/MM » — et non un numéro.

---

## Mise à jour v1.4.9 — la prime se saisit, et un widget muet ne casse plus rien

### Dans cet ordre

```bash
php artisan migrate --force
php artisan optimize:clear && php artisan config:cache && php artisan route:cache
```

Une seule migration. Elle ouvre le bénéficiaire d'une prime — un utilisateur, ou
une personne nommée qui n'en est pas une —, ajoute la date d'acquisition qui
ordonne le soldé, et le lien vers l'écriture qui a payé. Elle retire l'unicité
(échéance, commercial), qui encodait le modèle calculé.

Rien à rejouer : aucune prime n'existe en base.

### Ce qui change à l'usage

**La prime ne se calcule plus, elle se saisit.** Dans les trois fenêtres
d'encaissement — barre d'actions rapides, échéancier d'un client, encaissement
d'une facture — deux champs facultatifs sont apparus : *Prime pour* et *Montant
de la prime*. Un encaissement sans prime reste le cas ordinaire.

**Le barème n'est plus qu'une commodité.** Quand il en existe un pour ce
bénéficiaire et cet événement, il pré-remplit le montant, qui reste modifiable.
Quand il n'en existe pas, le champ est vide et la saisie se fait quand même. Le
bandeau qui annonçait les barèmes manquants a disparu : il signalait un défaut
qui n'en est plus un.

**Le bénéficiaire peut ne pas avoir de compte.** Choisissez « Une autre
personne… » et donnez son nom : c'est ainsi que Laaroussi et Rami entrent dans
les comptes.

**Les vingt-neuf encaissements sans prime** se rattrapent un par un, depuis
l'échéancier — action « Ajouter une prime » sur la ligne. Pas de traitement en
lot, aucun bénéficiaire deviné.

### Verser une prime

Écran **Facturation → Primes**. Une ligne par bénéficiaire : acquis, versé,
solde dû. Le bouton « Verser la prime » propose le solde entier, accepte un
montant partiel, une date et un compte.

⚠️ **Pour régulariser les deux versements déjà au journal** — 14 400 DH à
Laaroussi en août 2025, 700 DH à « salima / rami » en juin 2026 —, saisissez
d'abord les primes correspondantes, puis, dans la fenêtre de versement,
choisissez **« Rattacher une opération déjà au journal »** et non « Créer
l'opération ». Créer une écriture ferait sortir une seconde fois un argent déjà
sorti, et la trésorerie serait fausse de ces montants.

### Le widget qui cassait le tableau de bord

`IntegriteSerieWidget` est corrigé : sa vue est enveloppée hors de sa condition,
il se retire de la grille quand il n'a rien à dire, et il ne s'invite plus sur le
tableau de bord. Après `optimize:clear`, l'erreur « Livewire encountered a
missing root tag » ne doit plus apparaître au journal.

### Vérifier

```bash
php artisan mobdie:generer-commissions --simulation
```

Doit annoncer 0 opération de trésorerie créée. Puis ouvrir le tableau de bord et
la liste des factures : ni cadre blanc, ni erreur au journal.

---

## Mise à jour v1.5.0 — sécurité et robustesse

Neuf points, aucun nouveau service. Ce lot répare ce qui exposait ou ce qui
mentait.

### Après la mise à jour

```bash
php artisan migrate --force
php artisan optimize:clear && php artisan config:cache && php artisan route:cache
```

Cinq migrations. Celle qui donne une clé publique aux pièces parcourt la table
des documents ligne à ligne : compter quelques secondes sur l'archive complète.

### 1. Les pièces d'identité ne s'énumèrent plus

Les adresses de téléchargement portaient l'identifiant auto-incrémenté :
`/pieces/1/telecharger`, `/pieces/2/telecharger`. Elles portent désormais une
clé aléatoire (ULID). **Les anciennes adresses répondent 404 — elles ne
redirigent pas.** Tout lien vers une pièce collé dans un e-mail avant la mise à
jour cessera de fonctionner ; il faut le reprendre depuis l'application.

Le cloisonnement du commercial, qui n'existait que sur la liste des clients,
s'applique maintenant aux pièces, à l'archive documentaire et au relevé de
compte d'un domicilié.

> **À vérifier** : connectez-vous avec un compte commercial et tentez d'ouvrir la
> fiche, une pièce et le relevé d'un client qui ne lui est pas assigné. Les trois
> doivent répondre « accès refusé ».

### 2. Le journal comptable a une corbeille

`Transaction` et `ClientDocument` passent en suppression réversible. Supprimer
met à la corbeille ; **l'effacement définitif exige la permission
« Suppressions définitives »**, que l'administrateur n'a pas par défaut. Le
filtre « Corbeille » du journal et de l'archive montre ce qui y est, et l'action
« Restaurer » l'en sort.

Une écriture à la corbeille ne compte plus : ni dans les soldes de comptes, ni
au CPC, ni dans la TVA de l'exercice.

> **À vérifier** : mettez une écriture à la corbeille, contrôlez que le solde du
> compte a bougé du montant exact, restaurez-la, contrôlez qu'il revient.

### 3. Les autorisations sont écrites, non supposées

Le mode strict de Filament est actif et **trente-six policies** couvrent tous les
modèles. Sans elles, chaque bouton non gardé à la main était ouvert : un compte
qui n'avait que le droit de consulter la trésorerie pouvait modifier les
écritures, les comptes et les prêteurs.

Deux écrans deviennent plus stricts qu'avant, conformément à ce qu'annonçait
leur porte d'entrée : les **charges récurrentes** exigent « Saisir des opérations
de trésorerie », les **résiliations** exigent « Créer / modifier les contrats ».

> **À vérifier** : ouvrez l'application avec chacun des quatre rôles et parcourez
> les écrans principaux. Un refus doit être un refus visible, jamais une page
> blanche.

### 4. La sauvegarde prouve enfin quelque chose

Voir §9 : journal `backup_runs`, alerte au-delà de 36 heures, commande
`mobdie:restaurer`.

> **À vérifier** : le lendemain de la mise en ligne, la table `backup_runs` doit
> porter une ligne à succès. Puis, une fois, sur un serveur de recette :
> `php artisan mobdie:restaurer <archive> --verifier-seulement`.

### 5. L'IA se réessaie, et ne dépense pas sans limite

Trois essais avec attente croissante sur les seules erreurs transitoires (429,
5xx, délai) ; échec immédiat sur les erreurs définitives (clé refusée, modèle
inexistant). Les analyses figées plus d'une heure sont soldées chaque heure par
`mobdie:balayer-analyses`.

**Un plafond mensuel de dépense** est livré à **500 DH** — réglable dans
Réglages, onglet IA, `0` pour ne pas plafonner. Au-delà, l'application refuse
l'appel avec un message clair au lieu de le facturer. Le quota de jetons, qui
n'était qu'affiché, est lui aussi appliqué.

> **À vérifier** : Réglages → IA, ajuster le plafond au budget réel de la maison.
> C'est le seul réglage de ce lot qui demande une décision.

### 6. Un e-mail qui ne part pas ne perd plus rien

Le formulaire public enregistre le prospect même si le SMTP est muet — il
renvoyait une erreur 500 sur une demande pourtant enregistrée, et le visiteur
recommençait. Idem pour les alertes de conformité et le compte rendu de
sauvegarde.

L'envoi groupé des liens de coordonnées passe **en file d'attente** : sur cent
deux clients, la passerelle coupait vers la soixantième seconde. Le compte rendu
— ce qui est parti, ce qui a échoué, nommément — arrive en notification.

`MAIL_TIMEOUT=30` est à ajouter au `.env` (30 s par défaut si absent) : sans
délai, un SMTP qui pend bloquait jusqu'à la limite de PHP.

### 7. Les écrans ne calculent plus deux fois

Agrégats comptables mémorisés le temps de la requête, agrégations SQL à la place
des boucles PHP, relations préchargées, et quatre index posés :
`invoices.statut`, `invoices.date`, `transactions.direction`, et la **clé
étrangère `domiciliation_installments.invoice_id`**, qui manquait tout à fait.

> La migration met à `NULL` les `invoice_id` qui ne désignent aucune facture
> avant de poser la contrainte. Sur la base de production, ces lignes étaient
> nombreuses : c'est le défaut que corrigeait déjà l'écran de l'échéancier.

### 8. Le message de file arrêtée ne trompe plus

Il annonçait que « les envois d'e-mails ne partiront pas ». C'était faux : devis,
factures et relances partent en synchrone. Il nomme désormais ce qui s'arrête
réellement — les analyses de pièces et les envois groupés.

### 9. Les commandes de reprise sont à part

Douze scripts écrits pour la reprise de 2026 quittent `mobdie:` pour
**`reprise:`**, et vivent dans `app/Console/Commands/Reprise`. Chacune annonce à
l'exécution à quelle reprise elle a servi, et **refuse de s'exécuter en
production sans confirmation interactive** (`--force` pour un script).

| Avant | Après |
|---|---|
| `mobdie:apparier-forsa` | `reprise:apparier-forsa` |
| `mobdie:corriger-modele-ia` | `reprise:corriger-modele-ia` |
| `mobdie:import-comptabilite` | `reprise:import-comptabilite` |
| `mobdie:import-domiciliation` | `reprise:import-domiciliation` |
| `mobdie:importer-factures-historiques` | `reprise:importer-factures-historiques` |
| `mobdie:normaliser-archive` | `reprise:normaliser-archive` |
| `mobdie:rattacher-comptes-courants` | `reprise:rattacher-comptes-courants` |
| `mobdie:reconstruire-tiers` | `reprise:reconstruire-tiers` |
| `mobdie:renommer-archive` | `reprise:renommer-archive` |
| `mobdie:reperer-cca` | `reprise:reperer-cca` |
| `mobdie:reperer-pieces-etrangeres` | `reprise:reperer-pieces-etrangeres` |
| `mobdie:ventiler-tva` | `reprise:ventiler-tva` |

`reprise:ventiler-tva` **refuse désormais de tourner sans `--annee`** : sans
année, elle retraiterait tout l'historique, exercices clos compris.
`--tout-l-historique` le demande explicitement.

Les dix commandes d'exploitation gardent le préfixe `mobdie:` : `sauvegarde`,
`restaurer`, `planificateur-quotidien`, `balayer-analyses`, `diagnostic`,
`indexer-archive`, `generer-commissions`, `reparer-permissions`, `charger-dump`,
`generer-dump`.

> **À vérifier** : si un cron ou un script maison appelle l'une des douze
> anciennes commandes, corriger son nom — l'ancien n'existe plus.

---

## Mise à jour v1.5.1 — adresses d'expédition, mise en page des messages, rangement

### Après la mise à jour

```bash
php artisan migrate --force
composer dump-autoload
php artisan optimize:clear && php artisan config:cache && php artisan route:cache
npm run build
```

> `composer dump-autoload` n'est pas facultatif : trois domaines ont changé de
> place dans l'arborescence, et l'autochargeur mis en cache pointerait sur des
> fichiers qui n'existent plus.

Deux migrations : les adresses d'expédition, et la mise en page des modèles de
message. Les modèles existants gardent l'apparence qu'ils avaient.

### 1. Les adresses d'expédition

**Administration → Adresses d'expédition.** Sans adresse enregistrée, rien ne
change : les messages partent par l'adresse du fichier de configuration, comme
avant. En ajouter une permet de choisir, à l'envoi, depuis quelle adresse le
message part.

L'hôte, le port et le chiffrement se pré-remplissent avec le serveur déjà en
service — il ne reste que l'adresse et son mot de passe.

> **Testez chaque adresse à la création.** Le bouton « Tester cette adresse »
> envoie réellement un message et rapporte l'erreur du serveur en clair. Un mot
> de passe erroné doit se voir au moment où on le saisit, et non trois semaines
> plus tard sur une relance client qui ne part pas.

**Le mot de passe** est chiffré en base par la clé de l'application
(`APP_KEY`) : il n'est jamais réaffiché, jamais journalisé, jamais exporté. Le
formulaire présente un champ vide portant la mention « inchangé » ; laissez-le
vide pour le conserver.

> ⚠️ **`APP_KEY` devient une clé de déchiffrement.** La perdre rendrait les mots
> de passe des adresses illisibles — il faudrait les ressaisir. Elle figure déjà
> dans la sauvegarde du `.env` ; vérifiez que cette sauvegarde existe.

### 2. La mise en page des messages

**Administration → Modèles de message** porte désormais un choix de mise en page
— sobre, en-tête de marque, courrier formel — et ses réglages : logo, signature,
mentions légales, bouton d'action. La zone d'envoi permet de changer la forme au
coup par coup.

L'aperçu montre le rendu final et bascule entre écran large et téléphone.

> **À vérifier** : envoyez-vous un message dans chacune des trois mises en page
> et ouvrez-les dans Outlook si la maison en a un. Le gabarit est bâti en
> tableaux avec des styles en ligne pour cette raison précise.

### 3. La mise en forme par l'IA

Le bouton « Mettre en forme avec l'IA » paraît sous l'éditeur pour qui a le droit
« Lancer une analyse IA de pièces », et seulement si la clé API est renseignée.

Elle **remplit l'éditeur, elle n'envoie rien**. « Revenir à ma version » rétablit
le texte d'origine. Une proposition qui ajouterait un montant ou une date absents
du texte est écartée automatiquement, avec la liste de ce qui a été inventé.

L'appel est compté dans le suivi d'usage et soumis au **plafond mensuel** posé en
v1.5.0.

### 4. Ce qui a changé de place dans le code

Trois domaines — **Courrier**, **Facturation**, **Comptabilité** — portent
désormais leurs modèles, services, écrans et tests dans `app/Domaines/`. Voir
`ARCHITECTURE.md`.

**Rien ne change pour l'exploitant** : mêmes écrans, mêmes adresses, mêmes
données. Les noms de classe écrits en base — journal d'audit, source des
écritures — sont préservés par une table de correspondance : aucune ligne
existante ne devient illisible.

Les treize rapports de version sont rassemblés dans `CHANGELOG.md`, les
originaux dans `docs/rapports/`.

### Nouvelle variable d'environnement (facultative)

| Variable | Valeur / rôle |
|---|---|
| `BACKUP_DOSSIER` | Où écrire les archives de sauvegarde. Vide : `storage/app/backups` |

---

## Mise à jour v1.9.0 — les deux migrations, et le gabarit du contrat

Déploiement ordinaire (§3), avec **deux migrations additives et idempotentes**.
**Aucun fichier supprimé ni renommé** entre v1.8.2 et v1.9.0 : rien à effacer
sur le serveur. **Aucune commande de reprise.**

```bash
$PHP artisan migrate --force
$PHP artisan optimize:clear && $PHP artisan config:cache && $PHP artisan route:cache && $PHP artisan view:cache
```

### Ce que font les deux migrations

| Migration | Effet |
|---|---|
| `…_la_forme_juridique_du_domicilie` | Ajoute `domiciliation_clients.forme_juridique`, **nullable**. Aucune donnée touchée : tant qu'elle est vide, les documents impriment exactement ce qu'ils imprimaient. |
| `…_la_clause_de_renouvellement_devient_variable` | Remplace, **dans les gabarits stockés en base**, la phrase de tacite reconduction par `{{clause_renouvellement}}` — **uniquement si la phrase exacte s'y trouve encore**. Une retouche manuelle du gabarit n'est jamais écrasée, et un second passage ne fait rien. |

> Sauvegarde phpMyAdmin avant, comme pour toute migration qui écrit en base :
> la seconde touche la table `document_templates`.

### Après la mise en ligne

- **Réglages → Gérant** : vérifier « Fonction du gérant ». C'est ce champ, et
  non plus le mot « gérant » écrit en dur, qui s'imprime sous la signature du
  domiciliataire.
- **Générer un contrat de contrôle** : le nom et le titre des deux signataires
  doivent figurer **dans** les cadres de signature, et le document tenir sur
  deux pages.
- **Assets** : le paquet embarque `public/build` recompilé — la conversion des
  photos HEIC dans le navigateur en dépend.

---

## Mise à jour v1.9.1 — aucune migration, une commande de reprise, le worker PDF dans les assets

Déploiement ordinaire (§3), **sans migration**. **Aucun fichier supprimé ni
renommé** entre v1.9.0 et v1.9.1 : rien à effacer sur le serveur.

```bash
$PHP artisan optimize:clear && $PHP artisan config:cache && $PHP artisan route:cache && $PHP artisan view:cache
```

### Les assets : le paquet DOIT embarquer `public/build` recompilé

Le lot ajoute deux modules compilés (`sortie-document`, `lecteur-pdf`) et le
**worker PDF.js** (`public/build/assets/pdf.worker.min-*.mjs`, ~1,2 Mo),
référencé par `public/build/manifest.json`. Sans lui, le lecteur du téléphone
affiche « Impossible d'afficher le document » — le partage, lui, marche quand
même. Rien ne se charge hors de `app.mobdie.ma` : pas de CDN.

### Après la mise en ligne : la prime en double (décision du gérant, 26/09/2026)

La prime de Salima saisie deux fois (MM DRAGON LOG TRANS n° 1) se retire, et
le trop-perçu règle sa prime OL IMPERIAL MOROCCO. **Aucun argent ne bouge.**
Sauvegarde phpMyAdmin d'abord, comme toujours. Puis, depuis le terminal cPanel
(`$PHP` = `/opt/cpanel/ea-php83/root/usr/bin/php`, depuis le dossier de
l'app) :

```bash
# 1. La liste, et rien d'autre : un seul groupe attendu (Salima,
#    MM DRAGON LOG TRANS, échéance n° 1 du 12/08/2026, 2 × 100,00 DH,
#    versées le 08/09/2026 par l'opération n° 1899), et la proposition
#    de commande avec ses identifiants.
$PHP artisan reprise:prime-en-double

# 2. La commande proposée, telle quelle : simulation, rien n'est écrit.
#    Lire l'avant/après et les invariants (opération n° 1899 :
#    500,00 DH versés = 500,00 DH de primes, avant comme après ;
#    solde de Salima : 100,00 DH → 0,00 DH).
$PHP artisan reprise:prime-en-double --retirer=… --reporter-sur=…

# 3. La même, pour de bon. --force : le terminal cPanel ne transmet pas
#    les réponses interactives.
$PHP artisan reprise:prime-en-double --retirer=… --reporter-sur=… --appliquer --force
```

### Vérifier à l'écran

- **Primes** : solde de Salima **0,00 DH** ; la prime OL IMPERIAL MOROCCO est
  versée, rattachée à l'opération n° 1899, avec sa note « Réglée par le
  trop-perçu… ».
- **Assistance** : « combien encaissé en espèces en août 2026 » → 4 060,00 DH
  en Caisse (2 160,00 DH de paiements clients + 1 900,00 DH d'apports) ;
  « où en est le client Fakhar ? » → elle demande lequel des trois dossiers.
- **Sur l'iPhone** (app de l'écran d'accueil, puis Chrome) : Factures → ⋯ →
  « Partager ou enregistrer » → la feuille de partage s'ouvre → Fichiers,
  WhatsApp ; « Ouvrir le PDF » → toutes les pages défilent. Sur Mac :
  « Télécharger » télécharge comme avant.

## Mise à jour v1.10.0 — une colonne rendue facultative, deux commandes de contrôle

**⚠️ Sauvegarde phpMyAdmin OBLIGATOIRE avant la migration** (Exporter
`mobdiema_mobdie_app`, format SQL) : la migration
`2026_09_29_100000_payer_de_sa_poche` **modifie une colonne existante** —
`transactions.account_id` perd son « NOT NULL » (les dépenses payées de sa
poche n'ont pas de compte). Aucune donnée ne change ; la seconde migration
(`…_110000_les_transferts_inter_comptes`) est additive. Les deux sont
idempotentes. **Aucun fichier supprimé ni renommé** entre v1.9.1 et v1.10.0.
Le paquet embarque `public/build` recompilé (le thème porte la barre du haut).

```bash
$PHP artisan migrate --force
$PHP artisan optimize:clear && $PHP artisan config:cache && $PHP artisan route:cache && $PHP artisan view:cache
```

### Juste après la mise en ligne : le contrôle, en LECTURE

```bash
# 1. Le contrôle factures ↔ opérations : un compteur par contrôle, puis les
#    lignes. N'écrit rien. La 55/2026 (OL IMPERIAL MOROCCO) doit figurer au
#    contrôle 1 « Statut écrasé ». --json archive le rapport dans
#    storage/app/controles/.
$PHP artisan mobdie:controler-factures
$PHP artisan mobdie:controler-factures --json > /dev/null

# 2. La correction du contrôle 1, en SIMULATION : ce qui serait fait (cas
#    sûrs : paiement existant, écriture vivante, montant couvert) et ce qui
#    reste à arbitrer (jamais touché). Rien n'est écrit.
$PHP artisan reprise:statut-factures
```

**Seulement sur décision du gérant**, après lecture de la simulation (seconde
commande, donnée après la capture) :

```bash
$PHP artisan reprise:statut-factures --appliquer --force
```

### Vérifier à l'écran

Les neuf points du rapport `docs/rapports/RAPPORT-V1.10.0.md` (barre du haut,
saisie rapide, fonds d'associé, transfert, bandeau des factures et « Émettre et
envoyer » **fermé sans envoyer**, Encaissements par mois d'août 2026 :
Espèces 2 160,00 · Banque 700,00 · Total 2 860,00 · Autres entrées 2 550,00,
écran « Contrôles », Journal à 1 440 px).

## Mise à jour v1.10.1 — aucune migration, une commande de reprise

Déploiement ordinaire (§3), **sans migration**. **Aucun fichier supprimé ni
renommé** entre v1.10.0 et v1.10.1.

```bash
$PHP artisan optimize:clear && $PHP artisan config:cache && $PHP artisan route:cache && $PHP artisan view:cache
```

### Après la mise en ligne, en LECTURE

```bash
# 1. Le contrôle : contrôle 2 « Payée sans argent » → aucun (les 34 reprises
#    sont hors champ, dit en tête) ; contrôle 3 → WELLPHARM, paiement n° 2,
#    « opération n° 1864 à la corbeille — jumelle vivante : n° … ».
$PHP artisan mobdie:controler-factures

# 2. La liaison, en SIMULATION : 1 cas sûr attendu (paiement n° 2 → la jumelle).
$PHP artisan reprise:relier-paiements
```

Sur décision du gérant, après lecture de la simulation (seconde commande) :

```bash
$PHP artisan reprise:relier-paiements --appliquer --force
```

Les contrôles 4 (doublon HC IMPORT du 07/09) et 9 (opération n° 1891) se
traitent à l'écran par les gestes existants ; ils disparaissent ensuite.

## Mise à jour v1.10.2 — aucune migration, une commande de reprise

Déploiement ordinaire (§3), **sans migration**. **Aucun fichier supprimé ni
renommé** entre v1.10.1 et v1.10.2.

```bash
$PHP artisan optimize:clear && $PHP artisan config:cache && $PHP artisan route:cache && $PHP artisan view:cache
```

### Après la mise en ligne, en SIMULATION

```bash
# 1. HC IMPORT EXPORT : attendu « échéance n° 2 créée (12/02/2027, 720 DH) ;
#    opération du 07/09/2026 (720 DH, Caisse) restaurée et rattachée au
#    paiement de l'échéance n° 2 ». Rien n'est écrit.
$PHP artisan reprise:facture-plusieurs-echeances 50/2026

# 2. WELLPHARM : attendu « Paiement en double : paiements n° 2 et n° … »,
#    0 paiement relié. Rien n'est écrit.
$PHP artisan reprise:relier-paiements
```

Sur décision du gérant, après lecture (seconde commande) :

```bash
$PHP artisan reprise:facture-plusieurs-echeances 50/2026 --appliquer --force
```

Puis `mobdie:controler-factures` : contrôle 4 → aucun. Le paiement en double
WELLPHARM se supprime à l'écran (fiche du client), par le gérant.

## Mise à jour v1.10.3 — aucune migration, deux commandes de reprise

Déploiement ordinaire (§3), **sans migration**. **Aucun fichier supprimé ni
renommé** entre v1.10.2 et v1.10.3.

```bash
$PHP artisan optimize:clear && $PHP artisan config:cache && $PHP artisan route:cache && $PHP artisan view:cache
```

### Après la mise en ligne, en SIMULATION

```bash
# 1. WELLPHARM : attendu « facture 39/2026 : paiement n° 2 → n° 4 ; paiement
#    n° 2 retiré ; opération n° 1864 laissée à la corbeille ; aucun mouvement
#    d'argent ». Rien n'est écrit.
$PHP artisan reprise:paiement-en-double 4 2

# 2. HC IMPORT : attendu « fin 11/02/2027 → 11/08/2027 ; 2 échéances
#    couvertes, aucune créée ». Rien n'est écrit.
$PHP artisan reprise:prolonger-contrat 2026-109-C1
```

Sur décision du gérant, après lecture (secondes commandes) :

```bash
$PHP artisan reprise:paiement-en-double 4 2 --appliquer --force
$PHP artisan reprise:prolonger-contrat 2026-109-C1 --appliquer --force
```

Puis `mobdie:controler-factures` : contrôle 3 → aucun.

## Mise à jour v1.11.0 — six migrations additives, une commande de reprise

**Sauvegarde phpMyAdmin de `mobdiema_mobdie_app` d'abord** (Exporter, format
SQL) : six migrations, toutes **additives et idempotentes** — aucune colonne
existante n'est modifiée ni supprimée. **Aucun fichier supprimé ni renommé**
entre v1.10.3 et v1.11.0 ; aucune dépendance composer ni npm ; les assets
(`public/build`) voyagent compilés. Le cron ne change pas : le planificateur
quotidien porte les nouveaux rappels (dossiers clos, mises en demeure expirées,
administrations à informer), à la cloche, jamais par e-mail.

```bash
$PHP artisan migrate --force
$PHP artisan optimize:clear && $PHP artisan config:cache && $PHP artisan route:cache && $PHP artisan view:cache
```

Migrations attendues (`DONE`) : `2026_10_01_100000_le_cadre_legal_de_la_domiciliation`,
`…_110000_des_dossiers_clos_qui_disent_vrai`, `…_120000_les_modeles_versionnes`,
`…_130000_la_mise_en_demeure`, `…_140000_l_assistant_de_resiliation`,
`…_150000_les_plis_recommandes_du_fisc`.

### Après la mise en ligne, en SIMULATION

```bash
# Attendu : 12 dates recopiées ; 18 échéances / 14 100,00 DH (résiliés 9 /
# 7 000, annulés 9 / 7 100) ; 0 scission ; dû conservé 7 / 5 100,00 DH ;
# 0 relance ignorée ; à arbitrer : AL HENNA KITCHEN (12/03/2027), DEM DIKK
# CONNECT (sans acte), LUCALI (corbeille), 14 résiliations sans notification ;
# dernière ligne « 94 / 81 720,00 DH → 81 / 71 620,00 DH ». Rien n'est écrit.
$PHP artisan reprise:dossiers-clos
```

Sur décision du gérant, **après sa capture** (seconde commande) :

```bash
$PHP artisan reprise:dossiers-clos --appliquer --force
# Un second passage doit répondre « Rien à faire ».
$PHP artisan reprise:dossiers-clos --appliquer --force
```

### À régler à l'écran

- **Réglages → Cadre légal** : le n° et la date du récépissé de déclaration
  (art. 544-7 ; vide, la phrase disparaît du contrat) et les **adresses** des
  quatre administrations (semées sommaires).
- **Réglages → Gérant** : « Fonction du gérant » (Gérant par défaut).
- **Modèles de documents** : contrat et avenant 2026-10 en service (reconnus sur
  l'export du 01/10) ; si un modèle avait été retouché entre-temps, il est
  « Nouvelle version proposée » : Comparer, puis Adopter.
- Les **14 résiliations** reprises : ouvrir chacune (liste Résiliations ou fiche)
  — les quatre lettres se produisent à la première ouverture ; saisir le RC du
  client quand il manque ; marquer chaque dépôt envoyé, preuve jointe.

## Mise à jour v1.11.1 — une migration additive, une commande de lecture

Déploiement ordinaire (§3). Une migration **additive et idempotente** (l'article
15 du contrat). **Aucun fichier supprimé ni renommé** entre v1.11.0 et v1.11.1.

```bash
$PHP artisan migrate --force
$PHP artisan optimize:clear && $PHP artisan config:cache && $PHP artisan route:cache && $PHP artisan view:cache
```

Migration attendue (`DONE`) : `2026_10_02_100000_l_article_15_au_siege_social`.

### Après la mise en ligne, en LECTURE

```bash
# Attendu : « Article 15 : aucun document à régénérer. » Rien n'est écrit.
$PHP artisan mobdie:documents-article-15
```

S'il en liste, chaque pièce se régénère depuis la fiche du client (« Générer un
document »). Si la cloche dit « Article 15 du contrat : à corriger dans Modèles
de documents », le contrat avait été retouché à la main : corriger l'article 15
dans Modèles de documents.

## Mise à jour v1.11.2 — une migration additive, une commande de reprise

Déploiement ordinaire (§3). Une migration **additive et idempotente**
(`mises_en_demeure.adresse_envoi`). **Aucun fichier supprimé ni renommé** entre
v1.11.1 et v1.11.2.

```bash
$PHP artisan migrate --force
$PHP artisan optimize:clear && $PHP artisan config:cache && $PHP artisan route:cache && $PHP artisan view:cache
```

Migration attendue (`DONE`) : `2026_10_02_110000_l_adresse_d_envoi_de_la_mise_en_demeure`.

### Après la mise en ligne, en SIMULATION

```bash
# Attendu : « 1. Adresses nettoyées des restes de modèle : 9 » ;
# « 2. Adresses de notification inutilisables : 36 — 26 vide(s), 9 modèle(s)
# non rempli(s), 1 adresse(s) de MOBDIE » (Fanzium Maroc) ; actif 12 / 56.
# 2026-02 et 2026-108 absents. Rien n'est écrit.
$PHP artisan reprise:adresses-de-notification
```

Sur décision du gérant, après sa capture (seconde commande) :

```bash
$PHP artisan reprise:adresses-de-notification --appliquer --force
# Un second passage répond « Rien à nettoyer ».
```

Les adresses manquantes se saisissent sur les fiches (ou dans « Mettre en
demeure… », case « Enregistrer aussi cette adresse sur la fiche ») : la commande
n'en invente aucune.

---

## Mise à jour v1.12.0 — onze migrations additives, aucune commande d'écriture

Déploiement ordinaire (§3). Onze migrations **additives et idempotentes**.
**Aucun fichier supprimé ni renommé** entre v1.11.2 et v1.12.0. **Aucune
dépendance** composer ni npm (le dossier `public/build` voyage compilé).

```bash
$PHP artisan migrate --force
$PHP artisan optimize:clear && $PHP artisan config:cache && $PHP artisan route:cache && $PHP artisan view:cache
```

Migrations attendues (`DONE`), dans cet ordre :
`2026_10_03_100000_l_espace_client_les_fondations`,
`2026_10_03_110000_les_formules_en_ligne`,
`2026_10_03_115000_la_conservation_a_dix_ans`,
`2026_10_03_120000_le_formulaire_en_ligne`,
`2026_10_03_130000_les_nouvelles_demandes`,
`2026_10_03_140000_l_espace_client`,
`2026_10_03_150000_les_relances_automatiques`,
`2026_10_03_160000_le_lien_verifiez_votre_dossier`,
`2026_10_03_170000_les_cabinets_apporteurs`,
`2026_10_03_180000_la_page_a_valider`,
`2026_10_03_190000_les_permissions_de_l_espace_client`.

**Aucune commande d'écriture à lancer** : `relances:envoyer-automatiques` et
`portail:rappels-rc` ne font rien tant que l'interrupteur de « Relances
automatiques » est éteint, et il naît éteint. Le planificateur (§4) les prend en
charge : `portail:entretien` chaque jour à 6 h 30, `relances:envoyer-automatiques
--appliquer` du lundi au vendredi à 9 h, `portail:rappels-rc` à 9 h 10.

### Vérifier, en lecture seule

```bash
# Attendu : « SIMULATION — rien n'est envoyé. Interrupteur éteint. » et
# « 0 relance(s) partiraient aujourd'hui, sur 122 relance(s) « à envoyer » ».
$PHP artisan relances:envoyer-automatiques
```

### Le portail : `/espace` d'abord, le sous-domaine ensuite

Le portail répond tout de suite sous `https://app.mobdie.ma/espace` (formulaire,
connexion, espace client, espace cabinet). Rien à configurer pour cela.

Le sous-domaine retenu est **`domiciliation.mobdie.ma`** : sa mise en place est
décrite à la v1.12.1, ci-dessous. **Ne pas renseigner `PORTAIL_DOMAIN` sur une
v1.12.0** : la redirection de « / » vers l'administration y passait avant
l'accueil du portail, et les anciennes adresses `/espace/…` répondaient 404.

---

## Mise à jour v1.12.1 — aucune migration ; le portail sur `domiciliation.mobdie.ma`

Déploiement ordinaire (§3). **Aucune migration, aucun fichier supprimé ni
renommé, aucune dépendance.** Un dossier nouveau : `lang/fr/` (les messages de
validation en français).

```bash
$PHP artisan optimize:clear && $PHP artisan config:cache && $PHP artisan route:cache && $PHP artisan view:cache
```

Tant que `PORTAIL_DOMAIN` est vide, le portail reste sous `/espace`, exactement
comme en v1.12.0 : la v1.12.1 se déploie sans rien changer pour les clients.

### Passer le portail sur son sous-domaine

1. **DNS chez Hostinger** : un enregistrement **A** `domiciliation` vers
   `46.4.24.98` (l'adresse de `app`). La zone `mobdie.ma` a un enregistrement
   générique `*` qui envoie tout sous-domaine inconnu vers le site vitrine : sans
   enregistrement à son nom, `domiciliation.mobdie.ma` arrive chez Hostinger. On
   ne touche ni à `*` ni à `@`.
2. **cPanel → Domaines → Créer un domaine** : `domiciliation.mobdie.ma`, case
   « Partager la racine du document » **décochée**, racine
   `public_html/app.mobdie.ma/public` (le même dossier que l'app). Puis **État
   SSL/TLS → Exécuter AutoSSL**, une fois le DNS propagé.
3. **Contrôle, en lecture** : `SESSION_DOMAIN` doit être absent, vide ou `null`
   (un cookie de session épinglé sur `app.mobdie.ma` ou sur `.mobdie.ma` ferait
   échouer ou mêlerait les connexions), et
   `https://domiciliation.mobdie.ma/admin/login` doit répondre 200.
4. **Le gérant** pose lui-même `PORTAIL_DOMAIN=domiciliation.mobdie.ma` dans
   `.env`, puis `config:cache` et `route:cache`.
5. **Attendu** : `https://domiciliation.mobdie.ma/` → 200 (« Devenir domicilié ») ;
   `/connexion` → 200 ; `/mon-espace` sans connexion → 302 vers `/connexion` ;
   `https://app.mobdie.ma/espace/…` → **301** vers la même page du sous-domaine,
   paramètres compris ; `https://app.mobdie.ma/` → 302 vers `/admin` ; 35 routes
   `portail.*` (34 + la redirection des anciennes adresses).

À savoir :

- Les e-mails (code de connexion, relances, « Vérifiez votre dossier ») écrivent
  leurs liens sur le sous-domaine dès que la clé est posée ; les liens déjà
  envoyés sous `/espace` suivent la redirection 301. Un lien de fichier signé
  (15 minutes) ouvert pile au moment de la bascule est à redemander.
- **Mode essai** : il suppose le gérant connecté à l'administration *sur la même
  adresse*. Le cookie de session étant propre à chaque sous-domaine, il se
  connecte d'abord à `https://domiciliation.mobdie.ma/admin`, puis ouvre
  `https://domiciliation.mobdie.ma/demande`.
- L'administration reste servie sur les deux adresses (même application) ; on
  continue d'utiliser `app.mobdie.ma/admin`.

**Retour arrière** : retirer la ligne `PORTAIL_DOMAIN` de `.env`, puis
`config:cache` et `route:cache` — le portail revient sous `/espace`.

---

## Mise à jour v1.13.0 — une migration additive : les tarifs et le lien des cabinets

Déploiement ordinaire (§3). Une migration **additive et idempotente**
(`2026_10_04_100000_les_tarifs_et_le_lien_des_cabinets`) : la table
`tarifs_cabinet`, trois colonnes sur `apporteurs` (`lien_code`, `lien_actif`,
`lien_cree_le`), le modèle de message `portail_lien_cabinet`, et l'aide de
l'interrupteur « Formulaire public ouvert ». **Aucun fichier supprimé ni
renommé, aucune dépendance.**

```bash
$PHP artisan migrate --force
$PHP artisan optimize:clear && $PHP artisan config:cache && $PHP artisan route:cache && $PHP artisan view:cache
```

Attendu : 38 routes `portail.*` avec `PORTAIL_DOMAIN` (37 sans). Attendre
quelques secondes après `route:cache` avant de contrôler par `curl` : le cache
d'opcodes du serveur sert l'ancien fichier un court instant.

À régler à l'écran, par le gérant :

1. **Fiche du cabinet → onglet « Tarifs » → « Fixer les tarifs »** : un prix TTC
   par formule (vide : prix public). Le prix vaut pour tout le contrat,
   renouvellements compris ; il ne touche aucun devis ni contrat déjà né.
2. **Le lien client** se crée à la première visite du cabinet sur « Lien
   client », ou par « Lien client → Créer le lien » sur sa fiche. Il s'ouvre
   dès que le **n° CNDP** est saisi, même si le formulaire public est fermé ;
   il se désactive ou se remplace depuis la fiche.
3. La commission par nouveau client reste sur la fiche (défaut : Réglages →
   Espace client).

---

## Mise à jour v1.13.1 — deux migrations additives : la signature du domicilié, le prix d'une demande

Déploiement ordinaire (§3). Deux migrations **additives et idempotentes** :

- `2026_10_05_100000_la_signature_du_domicilie_a_l_attestation_de_fin` : le
  gabarit `attestation_fin` passe de « le domiciliataire seul » à « le
  domiciliataire, contreseing du domicilié possible » (seulement s'il était
  resté au réglage livré) ;
- `2026_10_05_110000_le_devis_modifie_d_une_demande` : le modèle de message
  `portail_devis_modifie` (20 modèles).

**Aucun fichier supprimé ni renommé, aucune dépendance.** Aucune pièce déjà
produite n'est refaite d'office.

```bash
$PHP artisan migrate --force
$PHP artisan optimize:clear && $PHP artisan config:cache && $PHP artisan route:cache && $PHP artisan view:cache
```

À l'écran :

1. **La signature du domicilié** — assistant de résiliation (« demande du
   client », « commun accord ») : « Faire signer le domicilié » ; après le
   prononcé, sur la fiche, ligne « Documents remis au client » →
   « Signature du domicilié… » : l'acte et l'attestation sont refaits avec
   (ou sans) la case du domicilié et son cadre de légalisation. Jamais pour un
   impayé.
2. **Le prix d'une demande** — Nouvelles demandes → « … » → « Modifier le
   prix… », tant que le contrat n'est pas établi : nouveau devis et nouveau
   projet de contrat, l'ancien devis « expiré » ; envoi au client facultatif.

---

## Mise à jour v1.13.2 — aucune migration : le tout lié

Déploiement ordinaire (§3), **sans migration**. **Aucun fichier supprimé ni
renommé, aucune dépendance.**

```bash
$PHP artisan optimize:clear && $PHP artisan config:cache && $PHP artisan route:cache && $PHP artisan view:cache
```

Puis la reprise, **en simulation d'abord** :

```bash
$PHP artisan reprise:relier-factures-aux-echeances
```

Attendu sur l'état du 01/10 : 27 devis reliés à leur contrat (plus ceux créés
depuis) ; les factures 54, 55, 56 et 57/2026 sur leur échéance n° 1 ; à
arbitrer : 39/2026 WELLPHARM ; 8 brouillons de renouvellement refaits, 1 retiré
(DIR B7ALI). Après lecture :

```bash
$PHP artisan reprise:relier-factures-aux-echeances --appliquer --force
```

À l'écran : Factures (colonne « Échéance », bandeau), Contrôles (ligne 11 :
WELLPHARM seule), Primes (n° 1, « Signature d'un nouveau client », base 600),
une fiche résiliée pour impayé (plus de « Signature du domicilié… »).

---

## Mise à jour v1.13.3 — aucune migration : les montants TTC, refaire une facture

Livrée **avec la v1.13.2**, en un seul paquet depuis la v1.13.1. Déploiement
ordinaire (§3), **sans migration**, aucun fichier supprimé ni renommé, aucune
dépendance. Puis, **dans cet ordre**, les deux reprises en simulation :

```bash
$PHP artisan reprise:tva-des-anciens-contrats && $PHP artisan reprise:relier-factures-aux-echeances
```

Attendu sur l'état du 01/10 : 64 contrats en hors taxe (5 sans échéance
laissés), 196 échéances TVA comprise (TTC 158 650,00 DH inchangé) ; puis la
reprise v1.13.2 (27 devis, factures 54 à 57/2026, 39/2026 à arbitrer, 8
brouillons refaits, 1 retiré). Après lecture, dans le même ordre :

```bash
$PHP artisan reprise:tva-des-anciens-contrats --appliquer --force && $PHP artisan reprise:relier-factures-aux-echeances --appliquer --force
```

Puis, à l'écran, **57/2026 → « Refaire la facture… »** (motif pré-rempli :
« Nom du client corrigé : Abderrahim El Hani »).

---

## Mise à jour v1.15.0 — cinq migrations additives, une commande de reprise

Déploiement ordinaire (§3), **avec migration**. Sauvegarde phpMyAdmin d'abord.
**Aucune dépendance composer ni npm, aucun fichier supprimé ni renommé.**

```bash
$PHP artisan migrate --force && $PHP artisan optimize:clear && $PHP artisan config:cache && $PHP artisan route:cache && $PHP artisan view:cache
```

Les cinq migrations : `le_catalogue_des_prestations` (tables `prestations`,
`formule_prestation` ; colonnes `prestations`, `clause_prestations` sur les
contrats), `les_versions_de_l_offre` (table `versions_offre`),
`l_offre_qui_s_ajuste` (modèles `offre_nouvelle_version`, `offre_acceptee` ;
gabarit `avenant_offre`), `les_demandes_du_client` (tables `demandes_client`,
`personnes_autorisees` ; quatre modèles `demande_*`), `le_courrier_recu_numerise`
(colonnes `scan_document_id`, `scan_envoye_le`, `important`, `retire_par` sur
`courriers` ; modèles `pli_recu`, `pli_recu_numerise`, `pli_retire`).

Puis l'article 2 du contrat, **en simulation d'abord** — le gérant lit
l'article 2 actuel et l'article 2 proposé :

```bash
$PHP artisan reprise:gabarit-prestations
```

Sur sa décision :

```bash
$PHP artisan reprise:gabarit-prestations --appliquer --force
```

Attendu : « Version 2026-10.1 créée et mise en service ». Le texte précédent
reste archivé (Gabarits → Comparer) ; aucun contrat existant n'est regénéré.
Si la commande refuse (« Article 2 introuvable » ou « ambiguë », gabarit
retouché à la main), faire la modification dans Gabarits : remplacer la liste
de l'article 2 par `{{prestations}}`, puis un paragraphe `{{phrase_redevance}}`.

Ensuite, à l'écran, **dans cet ordre** :

1. **Réglages → Formules en ligne → Prestations** : confirmer le prix de
   chaque option (360, 120, 300, 240, 150 DH / semestre sont les exemples de
   la maquette), relire les phrases du contrat, puis publier (« Proposée en
   option au formulaire ») celles à proposer en ligne.
2. **Formules en ligne** : créer la formule avec numérisation (« Options
   incluses » : numérisation, notification) à son prix.
3. **MILYASOL** : fiche → « Offre & contrat » → cocher numérisation et
   notification, prix 1 200 DH TTC / semestre, clause « sans réexpédition :
   les originaux sont retirés par le gérant ou son père » → « Envoyer la
   version 2 au client » → copier le texte WhatsApp.
4. **Domiciliation → Courrier reçu** : saisir le prochain pli ; le client
   reçoit l'e-mail d'arrivée (le scan joint si la numérisation est à son
   contrat).


## Mise à jour v1.15.1 — WELLPHARM, le contrat en double ; « ce qui change » en phrases

Déploiement ordinaire (§3), **sans migration**. Sauvegarde phpMyAdmin d'abord.
**Aucune dépendance composer ni npm, aucun fichier supprimé ni renommé.**

```bash
$PHP artisan optimize:clear && $PHP artisan config:cache && $PHP artisan route:cache && $PHP artisan view:cache
```

Puis WELLPHARM, **en simulation d'abord** :

```bash
$PHP artisan reprise:contrat-en-double 113 --garder=2026-106-C1 --retirer=2026-106-C2 --facture=39/2026 --paiement=2
```

Attendu : « facture 39/2026 annulée (garde son numéro…) ; paiement n° 2 retiré
(opération n° 1864 laissée à la corbeille) ; contrat 2026-106-C2 annulé,
1 échéance annulée (720,00 DH) ; … relance(s) en attente écartée(s) ;
contrat 2026-106-C1 gardé : échéance n° 1 payée, facture 41/2026 ; aucun
mouvement d'argent ». Si elle refuse, elle dit pourquoi : ne rien forcer, en
parler. Sans les options (`reprise:contrat-en-double 113`), elle liste les
contrats qui se chevauchent et propose la commande.

Sur décision du gérant :

```bash
$PHP artisan reprise:contrat-en-double 113 --garder=2026-106-C1 --retirer=2026-106-C2 --facture=39/2026 --paiement=2 --appliquer --force
```

Puis le contrôle :

```bash
$PHP artisan mobdie:controler-factures
```

Attendu : **contrôles 3 et 11 : aucun** pour WELLPHARM ; contrôle 12 « Deux
contrats actifs qui se chevauchent » : MOHAMED MEZIAN (2026-103-C1 / C2, aucune
échéance payée — la commande refuserait : à arbitrer sur la fiche), et tout
autre cas né depuis le 01/10. Fiche WELLPHARM : plus de bandeau d'impayé,
« Impayé 0 » dans la liste des clients, C2 « Annulé » dans l'onglet Contrats.

À l'écran : « Ajouter un contrat » sur un client qui a déjà un contrat actif
sur la période affiche « Ce client a déjà le contrat … actif jusqu'au … » et
demande de cocher « Créer quand même un second contrat ». Pour refaire le
**document** d'un contrat (changement de représentant), c'est « Générer un
document » en tête de la fiche du client, pas « Ajouter un contrat ».

## Mise à jour v1.15.2 — un seul déploiement v1.15.0 → v1.15.2 (v1.15.1 comprise)

La production est en v1.15.0 : **v1.15.1 et v1.15.2 partent ensemble**, en un
seul paquet (delta `v1.15.0` → `v1.15.2`, liste au rapport v1.15.2). **Une
migration additive** (`2026_10_15_100000_le_collaborateur_du_cabinet` :
`users.apporteur_id`). Aucune dépendance composer ni npm, **aucun fichier
supprimé ni renommé**. `public/build/` voyage (recompilé). Sauvegarde
phpMyAdmin de `mobdiema_mobdie_app` d'abord.

### 1. Mise en ligne

```bash
$PHP artisan migrate --force && $PHP artisan optimize:clear && $PHP artisan config:cache && $PHP artisan route:cache && $PHP artisan view:cache
```

Attendu : `2026_10_15_100000_le_collaborateur_du_cabinet ... DONE`.

### 2. Le diagnostic (lecture seule)

```bash
$PHP artisan mobdie:diagnostic --sans-api
```

D'abord la configuration (comme avant), puis **DIAGNOSTIC DES DONNÉES** :
« N anomalie(s), dont X à décider par le gérant, Y corrigée(s) par une
commande… », et les dix familles. Comparer aux chiffres attendus du rapport
v1.15.2 (≈ 59 clients en retard, 149 relances en file, 136 alertes, 53 dossiers
incomplets, soldes de trésorerie « concorde »). Seulement les données :
`--donnees` ; une famille : `--famille=1`.

### 3. Les reprises, dans l'ordre — chacune en simulation, puis `--appliquer --force` sur décision du gérant

1. **WELLPHARM** (v1.15.1) :

   ```bash
   $PHP artisan reprise:contrat-en-double 113 --garder=2026-106-C1 --retirer=2026-106-C2 --facture=39/2026 --paiement=2
   ```

   Attendu : voir « Mise à jour v1.15.1 » ci-dessus. Puis la même ligne avec
   `--appliquer --force`.

2. **NOVERA TECHNOLOGIES** (client 123) et **DEVANO TECHNOLOGIES** (128) :

   ```bash
   $PHP artisan reprise:contrat-en-double 123
   $PHP artisan reprise:contrat-en-double 128
   ```

   Chacune liste les deux contrats et propose la commande : garder le plus
   récent (la réédition du 08/10), retirer le premier, sans facture ni
   paiement (« aucun des deux contrats n'a rien encaissé »). Lancer la commande
   proposée (simulation), lire : « contrat … annulé, 1 échéance annulée
   (720,00 DH) ; … ; aucun mouvement d'argent », puis `--appliquer --force`.
   Si le gérant tient l'autre contrat pour le bon, inverser `--garder` et
   `--retirer`. Si une facture émise porte l'une des échéances, la commande
   refuse et le dit. *MOHAMED MEZIAN (110) se traite de même désormais (la
   commande ne refuse plus un doublon sans argent).*

3. **Paiements hors échéancier** :

   ```bash
   $PHP artisan reprise:paiement-hors-echeancier
   ```

   Attendu : « 0 paiement(s) hors échéancier à ranger » (ou la liste, si un cas
   est né depuis le 01/10) ; les factures déjà couvertes par une échéance
   renvoient à leur geste.

4. **Cabinet Rami Expertise** (cabinet n° 1, Salima = compte n° 3) :

   ```bash
   $PHP artisan reprise:rattacher-au-cabinet 1
   $PHP artisan reprise:rattacher-au-cabinet 1 --collaborateur=3 --ajouter=2026-99
   ```

   La première propose `--collaborateur=3` ; la seconde liste ≈ 20 clients à
   rattacher (LUCALI, FAKHAR SAMAH, CHLYAH NAJIB, …, MESKINI Asmae, MILYASOL),
   FAKHAR ZAKIA désignée par `--ajouter`, et les **candidats sans prime** —
   à ajouter un par un (`--ajouter=<dossier>`) seulement si le gérant les sait
   du cabinet. Puis `--appliquer --force`.

5. **La prime de FAKHAR ZAKIA**, après le rattachement :

   ```bash
   $PHP artisan reprise:paiement-hors-echeancier --client=2026-99
   ```

   Attendu : « déjà sous contrat … rien à ranger » et « Prime qui manque :
   100,00 DH pour Salima, acquise le 01/09/2026, non versée ». Puis
   `--appliquer --force`.

Puis, pour vérifier :

```bash
$PHP artisan mobdie:controler-factures
$PHP artisan mobdie:diagnostic --donnees --famille=1 --famille=3 --famille=4
```

### 4. À l'écran

- **Fiche d'un client** : les onglets Contrats, Échéancier, Facturation
  s'affichent avec la page ; vides, ils disent pourquoi. « Autres actions » :
  **« Classer sans suite… »** (METAIRIE BRUNO, PARAMO — sur décision du
  gérant) et « Rouvrir le dossier ».
- **Onglet Contrats** : **« Rééditer »** sur chaque contrat (nouveau
  document ; nouvelle date si rien n'est payé). C'est le geste pour « refaire le
  contrat », plus « Ajouter un contrat ».
- **EAGLE IT** : « Enregistrer un paiement » de 267 DH → « Acompte
  enregistré … reste dû 453,00 DH » ; la prime de Salima se saisit au solde.
- **Cabinet Rami → onglet « Commission »** : le relevé des primes de Salima
  et le texte « Primes à régler à Salima : … ; total … DH » (« Copier le
  texte »).
- **Fiche du prêt Forsa** : « Enregistrer un remboursement » ouvre sa fenêtre
  (il ne s'ouvrait pas). Les deux versements de 950 DH (04/08/2025,
  23/11/2025) restent **à arbitrer par le gérant** : rien n'est affecté.
- Factures et Devis s'ouvrent sur **15 lignes** (25, 50, 100 au choix) ;
  l'échéancier global, les opérations, les dossiers incomplets, le
  recouvrement et le registre sur **25**.

---

## Mise à jour v1.16.0 — un seul déploiement v1.15.2 → v1.16.0 (v1.15.3 comprise)

La production est en v1.15.2 : **v1.15.3 et v1.16.0 partent ensemble**, en un
seul paquet. **Une migration additive** (`2026_10_16_100000_les_cabinets_et_leurs_primes`).
Aucune dépendance composer ni npm. `public/build/` voyage (recompilé).
Sauvegarde phpMyAdmin de `mobdiema_mobdie_app` d'abord.

### 1. Le paquet

Ajoutés ou modifiés (`git diff --name-only --diff-filter=ACMR v1.15.2 v1.16.0`, hors `tests/`) :

```
app/Console/Commands/BancRecherche.php
app/Console/Commands/RecapCabinets.php
app/Console/Commands/Reprise/ContratEnDouble.php
app/Console/Commands/Reprise/RattacherAuCabinet.php
app/Domaines/Courrier/Filament/Pages/BoiteDeReception.php
app/Domaines/Courrier/Filament/Pages/CourrierRecuPage.php
app/Domaines/Courrier/Filament/Pages/EnvoyerUnMessage.php
app/Domaines/Courrier/Filament/Resources/Courriers/CourrierResource.php
app/Domaines/Courrier/Modeles/Courrier.php
app/Domaines/Facturation/Filament/Resources/Invoices/Schemas/InvoiceForm.php
app/Domaines/Facturation/Filament/Resources/Invoices/Tables/InvoicesTable.php
app/Domaines/Facturation/Filament/Resources/Quotes/Schemas/QuoteForm.php
app/Domaines/Facturation/Filament/Resources/Quotes/Tables/QuotesTable.php
app/Filament/Actions/Rapides/ContratClientExistantAction.php
app/Filament/Actions/Rapides/NouvelleFactureAction.php
app/Filament/Actions/RapportPrimesAction.php
app/Filament/Actions/ResilierAction.php
app/Filament/Pages/Demandes.php
app/Filament/Pages/EcheancierGlobal.php
app/Filament/Pages/Recouvrement.php
app/Filament/Pages/Reglages.php
app/Filament/Resources/AiAnalyses/AiAnalysisResource.php
app/Filament/Resources/Apporteurs/ApporteurResource.php
app/Filament/Resources/Apporteurs/RelationManagers/ClientsRelationManager.php
app/Filament/Resources/Apporteurs/RelationManagers/CommissionsRelationManager.php
app/Filament/Resources/Apporteurs/RelationManagers/CourriersRelationManager.php
app/Filament/Resources/ClientDocuments/Tables/ClientDocumentsTable.php
app/Filament/Resources/Commissions/Pages/ListCommissions.php
app/Filament/Resources/DomiciliationClients/DomiciliationClientResource.php
app/Filament/Resources/DomiciliationClients/RelationManagers/ContractsRelationManager.php
app/Filament/Resources/DomiciliationClients/Tables/DomiciliationClientsTable.php
app/Filament/Resources/RelanceQueues/RelanceQueueResource.php
app/Filament/Resources/Resiliations/ResiliationResource.php
app/Filament/Support/ChampClient.php
app/Filament/Support/RechercheDeClients.php
app/Http/Controllers/Portail/AccueilController.php
app/Http/Controllers/Portail/CabinetController.php
app/Http/Controllers/RapportPrimesController.php
app/Livewire/Portail/Inscription.php
app/Models/AccesPortail.php
app/Models/Apporteur.php
app/Providers/AppServiceProvider.php
app/Services/AI/PerimetreDonnees.php
app/Services/CommissionService.php
app/Services/CommissionsCabinet.php
app/Services/ContratEnDouble.php
app/Services/CreationDossier.php
app/Services/DiagnosticDonnees.php
app/Services/Portail/ConnexionPortail.php
app/Services/Portail/EspaceCabinet.php
app/Services/Portail/ExportCabinet.php
app/Services/Portail/FichiersPortail.php
app/Services/Portail/FilDuCabinet.php
app/Services/Portail/ParcoursDemande.php
app/Services/Portail/RecapCabinets.php
app/Services/Primes/EnvoiReleveCabinet.php
app/Services/Primes/PeriodePrimes.php
app/Services/Primes/RapportPrimes.php
app/Services/Primes/SituationDesPrimes.php
app/Services/RattachementCabinet.php
app/Services/Recherche/BancRecherche.php
app/Services/Recherche/IndexClients.php
app/Services/Recherche/RechercheClients.php
app/Services/Recherche/Resultat.php
app/Services/Recherche/Texte.php
app/Services/Recherche/Trouvaille.php
app/Services/ReleveCabinet.php
app/Services/SortieDeContrat.php
database/migrations/2026_10_16_100000_les_cabinets_et_leurs_primes.php
resources/css/filament/admin/theme.css
resources/css/portail.css
resources/views/components/portail/cabinet.blade.php
resources/views/filament/cabinet/releve-primes.blade.php
resources/views/filament/commissions/synthese.blade.php
resources/views/filament/primes/apercu-rapport.blade.php
resources/views/pdf/situation-primes.blade.php
resources/views/portail/cabinet/_recap-interrupteur.blade.php
resources/views/portail/cabinet/_suggestions.blade.php
resources/views/portail/cabinet/accueil.blade.php
resources/views/portail/cabinet/clients.blade.php
resources/views/portail/cabinet/historique.blade.php
resources/views/portail/cabinet/primes.blade.php
resources/views/portail/inscription.blade.php
resources/views/portail/partiels/champ.blade.php
resources/views/portail/partiels/nouveau-client-express.blade.php
resources/views/portail/pdf/recu-commission.blade.php
resources/views/portail/recap-desabonnement.blade.php
routes/console.php
routes/portail.php
routes/web.php
public/build/   (recompilé)
```

(Plus `CHANGELOG.md`, `DEPLOIEMENT.md`, `docs/rapports/RAPPORT-V1.15.3.md`,
`docs/rapports/RAPPORT-V1.16.0.md`, documentation seulement.)

**Supprimé** (`--diff-filter=DR`) — à retirer du serveur :

```bash
rm resources/views/portail/cabinet/commissions.blade.php
```

### 2. Mise en ligne

```bash
$PHP artisan migrate --force && $PHP artisan optimize:clear && $PHP artisan config:cache && $PHP artisan route:cache && $PHP artisan view:cache
```

Attendu : `2026_10_16_100000_les_cabinets_et_leurs_primes ... DONE`. Le réglage
« E-mail du lundi aux cabinets » arrive **éteint** : rien ne part.

### 3. Avant toute application — en lecture et en simulation, dans l'ordre

1. `$PHP artisan mobdie:diagnostic --donnees` — famille 1 : **NOVERA (2026-116)
   et DEVANO (2026-121)** dans la nouvelle cause « échéance due sur un contrat
   expiré/annulé », 1 440 DH chacun, avec la commande `reprise:contrat-en-double`
   (le compte des « jamais rien payé » baisse de 2) ; famille 4 : **FES HOSTING**
   avec `reprise:rattacher-au-cabinet <n°> --beneficiaire=Rachida`.
2. `$PHP artisan reprise:contrat-en-double 123` — attendu : « 2026-116-C1 — déjà
   marqué Expiré à la main », proposé `--garder=2026-116-C2 --retirer=2026-116-C1`.
   Puis la simulation : « contrat 2026-116-C1 annulé, 1 échéance annulée
   (720,00 DH) … aucun mouvement d'argent ».
3. `$PHP artisan reprise:contrat-en-double 128` — idem `2026-121-C2` / `2026-121-C1`.
4. `$PHP artisan reprise:rattacher-au-cabinet 3 --beneficiaire=Rachida` — ⚠️ sur la
   copie du 01/10, **Comptable Rachida porte le n° 2** et le n° 3 est AMG EXPERTISE
   COMPTABLE. La commande **refuse** si « Rachida » ne ressemble pas au contact du
   cabinet et affiche la liste des cabinets avec leur numéro : relancer alors avec
   le bon numéro. Attendu : « 1 prime reliée au cabinet Comptable Rachida : FES
   HOSTING, 300,00 DH, versée le 08/09/2026 (opération n° 1900) ; 1 client
   rattaché : FES HOSTING ; aucun mouvement d'argent ».
5. `$PHP artisan mobdie:banc-recherche` — lecture seule ; attendu ≥ 98 / 97 / 98 /
   98 / 96 % dans les 5 premiers, bruit 0 (sur la copie : 100 / 100 / 100 / 100 / 99,1 %).
6. `$PHP artisan portail:recap-cabinets` — simulation ; l'interrupteur reste éteint.

### 4. Puis, sur décision du gérant, une à une

Chaque fois la commande exacte affichée par la simulation, suivie de `--appliquer --force` :

```bash
$PHP artisan reprise:contrat-en-double 123 --garder=2026-116-C2 --retirer=2026-116-C1 --appliquer --force
$PHP artisan reprise:contrat-en-double 128 --garder=2026-121-C2 --retirer=2026-121-C1 --appliquer --force
$PHP artisan reprise:rattacher-au-cabinet <n° de Comptable Rachida> --beneficiaire=Rachida --appliquer --force
$PHP artisan mobdie:diagnostic --donnees
```

Après : NOVERA et DEVANO à **720 DH** chacun (l'échéance de leur C2) ; FES HOSTING
n'est plus cité en famille 4.

### 5. À l'écran

- Clients → recherche : « chabrak » → MILYASOL (Gérant · Nabil CHABRAK) ;
  « chabrac » → MILYASOL (Gérant ≈ Nabil CHABRAK) ; « badidi » → OMNIPET ;
  « ichen » → VOLGAS ; « lahjouji » → PARAMO. Courrier reçu → Nouveau pli :
  le nom d'une personne autorisée retrouve son client.
- Primes → Rapport des primes → Rami Expertise, Dates 01/09/2026 → 30/09/2026 :
  **100 / 700 / 500 / 300** ; « Télécharger le PDF » et « Excel » s'ouvrent.
- Espace cabinet de Rami → Primes → Depuis le début : **2 300 DH versés, 0 à recevoir**.
- Réglages → Espace client : « E-mail du lundi aux cabinets » éteint ;
  « M'envoyer un exemple » pour le voir avant de l'allumer.

---

## Mise à jour v1.16.1 — déploiement v1.16.0 → v1.16.1

La production est en v1.16.0 (depuis le 16/10/2026, 16 h 15). **Trois migrations
additives et idempotentes** ; aucune colonne supprimée ou convertie. Aucune
dépendance composer ni npm. `public/build/` voyage (recompilé). **Aucun fichier
supprimé ni déplacé** (`git diff --diff-filter=DR v1.16.0 v1.16.1` vide) : pas de
`rm` sur le serveur. Sauvegarde phpMyAdmin de `mobdiema_mobdie_app` d'abord.

### 1. Le paquet

Ajoutés ou modifiés (`git diff --name-only --diff-filter=ACMR v1.16.0 v1.16.1`, hors `tests/`) :

```
app/Console/Commands/ControlerFactures.php
app/Console/Commands/Diagnostic.php
app/Console/Commands/Reprise/PrimeEnDouble.php
app/Console/Commands/Reprise/RattacherAuCabinet.php
app/Console/Commands/Reprise/VersementDePrime.php
app/Domaines/Courrier/Filament/Resources/EmailTemplates/EmailTemplateResource.php
app/Domaines/Courrier/Modeles/EmailTemplate.php
app/Domaines/Courrier/Services/Messagerie.php
app/Domaines/Facturation/Services/ControleFacturesOperations.php
app/Domaines/Facturation/Services/InvoiceService.php
app/Domaines/Facturation/Services/PaiementEnDouble.php
app/Filament/Actions/EncaisserDAvanceAction.php
app/Filament/Actions/EnregistrerPaiementAction.php
app/Filament/Actions/Rapides/ContratClientExistantAction.php
app/Filament/Actions/RapportPrimesAction.php
app/Filament/Pages/AValider.php
app/Filament/Pages/ControlesFactures.php
app/Filament/Resources/Apporteurs/ApporteurResource.php
app/Filament/Resources/Apporteurs/Pages/ViewApporteur.php
app/Filament/Resources/Commissions/CommissionResource.php
app/Filament/Resources/Commissions/Tables/CommissionsTable.php
app/Filament/Resources/DomiciliationClients/Pages/ViewDomiciliationClient.php
app/Filament/Resources/DomiciliationClients/RelationManagers/ContractsRelationManager.php
app/Filament/Support/ChampsAvance.php
app/Filament/Support/ChampsPrime.php
app/Filament/Widgets/SyntheseCommissionsWidget.php
app/Http/Controllers/Portail/OuvrirFichierController.php
app/Http/Controllers/RapportPrimesController.php
app/Models/Commission.php
app/Models/CommissionVersement.php
app/Models/ConstatEcarte.php
app/Models/DomiciliationPayment.php
app/Policies/CommissionVersementPolicy.php
app/Policies/ConstatEcartePolicy.php
app/Services/CommissionService.php
app/Services/CommissionsCabinet.php
app/Services/ConstatsEcartes.php
app/Services/CorrectionDatesContrat.php
app/Services/DiagnosticDonnees.php
app/Services/InstallmentGenerator.php
app/Services/PaiementDAvance.php
app/Services/PaiementHorsEcheancier.php
app/Services/Portail/EspaceClient.php
app/Services/Portail/FilDuCabinet.php
app/Services/Primes/ChangementDeBeneficiaire.php
app/Services/Primes/SituationDesPrimes.php
app/Services/Primes/VersementsDePrime.php
app/Services/RattachementCabinet.php
app/Support/Trace.php
database/migrations/2026_10_16_200000_les_versements_de_prime.php
database/migrations/2026_10_16_210000_les_constats_ecartes.php
database/migrations/2026_10_16_220000_l_expediteur_des_modeles.php
resources/views/filament/commissions/synthese.blade.php
resources/views/filament/pages/controles-factures.blade.php
resources/views/filament/pages/partials/constats-ecartes.blade.php
routes/portail.php
routes/web.php
public/build/   (recompilé)
```

(Plus `CHANGELOG.md`, `DEPLOIEMENT.md`, `docs/rapports/RAPPORT-V1.16.1.md`, documentation seulement.)
Les routes changent (`portail.*.ouvrir`, `primes.rapport.ouvrir`) : le `route:cache` ci-dessous est indispensable.

### 2. Mise en ligne

```bash
$PHP artisan migrate --force && $PHP artisan optimize:clear && $PHP artisan config:cache && $PHP artisan route:cache && $PHP artisan view:cache
```

Attendu : `2026_10_16_200000_les_versements_de_prime`, `2026_10_16_210000_les_constats_ecartes`,
`2026_10_16_220000_l_expediteur_des_modeles` … DONE. La première remplit une ligne de
versement par prime versée (montant de la prime, sur son opération) : **aucune
opération créée**. La troisième pose facturation@ sur le relevé des primes et
domiciliation@ sur le récapitulatif du lundi, si ces adresses existent dans
Administration → Adresses d'expédition.

### 3. En lecture et en simulation, dans l'ordre

1. `$PHP artisan mobdie:diagnostic --donnees | head -40` — le tableau d'en-tête donne
   une ligne par famille (onze, dont **11. Contrats**). Famille 3 (détail :
   `--famille=3`) : **« Opération rattachée au-delà de son montant » — FES HOSTING,
   op. n° 1900 de 300 DH portant 400 DH** (attendu : la prime vaut 400 depuis le SQL
   du 16/10), et **« Opération des primes rattachée à aucune prime » — op. n° 1932**
   (100 DH « rachida »), plus le versement Laaroussi de 2025 (op. 1585) s'il est dans
   la catégorie des primes.
2. `$PHP artisan reprise:versement-de-prime <n° de la prime FES HOSTING> --operation=1932`
   (n° 23 sur la copie du 01/10 ; le vérifier dans Primes, colonne « Versée le »,
   « Op. n° 1900 ») — attendu :
   « Versement de l'opération n° 1900 ramené de 400,00 DH à 300,00 DH … Opération
   n° 1932 du 07/10/2026 rattachée : 100,00 DH … Total versé : 400,00 DH sur
   400,00 DH — soldée … Aucune opération créée ni modifiée ».
3. `$PHP artisan mobdie:controler-factures` — contrôle 9 : l'op. n° 1950 (900 DH
   « domiciliation comptable youssef ») ; contrôle 12 : plus d'Allam Ghita (dates
   corrigées le 16/10).

### 4. Puis, sur décision du gérant

```bash
$PHP artisan reprise:versement-de-prime <n° de la prime FES HOSTING> --operation=1932 --appliquer --force
$PHP artisan mobdie:diagnostic --famille=3
```

Après : FES HOSTING n'est plus citée ; Rachida Elfassi 400 DH versés, solde 0.
La commande, et non le geste « Rattacher un versement… », parce qu'elle ramène
d'abord la ligne de l'op. 1900 à ses 300 DH.

### 5. À l'écran

- **Primes** : synthèse — Salima ; **Rachida Elfassi — Comptable Rachida Elfassi :
  400 acquis, 400 versés, 0** ; **Amina — AMG EXPERTISE COMPTABLE : 300 / 300 / 0** ;
  deux lignes distinctes. Colonne « Versée le » de FES HOSTING : « Op. n° 1900
  (300,00 DH) + n° 1932 (100,00 DH) — à Rachida Elfassi ». Sur une ligne :
  « Changer le bénéficiaire… », « Corriger le montant… » s'ouvrent (fermer sans
  valider).
- **Fiche du cabinet n° 2** : « Contact : Rachida Elfassi · <téléphone> ».
- **Fiche d'un client sous contrat** → Autres actions : **« Encaisser d'avance… »**
  visible ; l'ouvrir montre l'aperçu (échéance suivante, nouvelle fin, nouvelle
  limite) — fermer sans valider.
- **Contrôles** : contrôle 9, op. n° 1950 → **« C'est voulu »** avec le motif ; elle
  passe dans « Écartés par le gérant (1) ».
- **Messagerie → Modèles de message** : « Relevé des primes » porte l'adresse
  facturation@ (si elle existe).
- Espace cabinet : un document laissé ouvert plus de 15 minutes se télécharge encore.

---

## Mise à jour v1.17.0 — déploiement v1.16.1 → v1.17.0

La production est en v1.16.1 (depuis le 16/10/2026, 19 h 39). **Une migration
additive et idempotente** ; aucune colonne supprimée ou convertie. Aucune dépendance
composer ni npm. `public/build/` voyage (recompilé). **Aucun fichier supprimé ni
déplacé** (`git diff --diff-filter=DR v1.16.1 v1.17.0` vide) : pas de `rm` sur le
serveur. Sauvegarde phpMyAdmin de `mobdiema_mobdie_app` d'abord.

### 1. Le paquet

`git diff --name-only --diff-filter=ACMR v1.16.1 v1.17.0 | grep -v '^tests/'`, plus
`public/build/`. **`phpunit.xml` n'a pas à voyager** (réglage des tests seulement) :

```
app/Console/Commands/BancLecture.php
app/Console/Commands/Diagnostic.php
app/Console/Commands/Reprise/GabaritPrestations.php
app/Exceptions/ModeleAssociationAbsentException.php
app/Filament/Actions/GenererDocumentAction.php
app/Filament/Actions/ResilierAction.php
app/Filament/Actions/VerifierAnalyseAction.php
app/Filament/Pages/Demandes.php
app/Filament/Pages/NouveauDossierDomiciliation.php
app/Filament/Pages/RegistreLegalPage.php
app/Filament/Pages/Reglages.php
app/Filament/Resources/ClientDocuments/Tables/ClientDocumentsTable.php
app/Filament/Resources/DocumentTemplates/Tables/DocumentTemplatesTable.php
app/Filament/Resources/DomiciliationClients/DomiciliationClientResource.php
app/Filament/Resources/DomiciliationClients/Pages/OffreDuDossierPage.php
app/Filament/Resources/DomiciliationClients/RelationManagers/DocumentsRelationManager.php
app/Filament/Resources/DomiciliationClients/Schemas/DomiciliationClientForm.php
app/Filament/Resources/DomiciliationClients/Schemas/DomiciliationClientInfolist.php
app/Filament/Resources/DomiciliationClients/Tables/DomiciliationClientsTable.php
app/Filament/Widgets/PiecesObligatoiresWidget.php
app/Filament/Widgets/SuiviLegalWidget.php
app/Http/Controllers/Portail/CabinetController.php
app/Http/Controllers/Portail/ClientController.php
app/Http/Controllers/Portail/VerificationController.php
app/Http/Controllers/ResiliationDocumentController.php
app/Jobs/AnalyserPiecesDeposeesJob.php
app/Jobs/AnalyzePiecesJob.php
app/Jobs/LirePieceDuPortail.php
app/Livewire/Portail/Inscription.php
app/Models/BeneficiaireEffectif.php
app/Models/ClientDocument.php
app/Models/DocumentTemplate.php
app/Models/DomiciliationClient.php
app/Models/Resiliation.php
app/Services/AI/BancLecture.php
app/Services/AI/ImagePourIa.php
app/Services/AI/PdfEnImages.php
app/Services/AI/PerimetreDonnees.php
app/Services/AI/PieceAnalyzer.php
app/Services/ConformityService.php
app/Services/CreationDossier.php
app/Services/DocumentGenerator.php
app/Services/DocumentsDeFin.php
app/Services/LegalCompleteness.php
app/Services/NommageArchive.php
app/Services/PhotoEnPdf.php
app/Services/Portail/ControlePieces.php
app/Services/Portail/DepotsPortail.php
app/Services/Portail/EspaceCabinet.php
app/Services/Portail/EspaceClient.php
app/Services/Portail/LienVerification.php
app/Services/Portail/ParcoursDemande.php
app/Services/Portail/RappelsRc.php
app/Services/Portail/ValidationPieces.php
app/Services/PrononcerResiliation.php
app/Services/Recherche/IndexClients.php
app/Services/Recherche/RechercheClients.php
app/Services/RegistreLegal.php
app/Services/VersionsDeGabarits.php
app/Support/CadreLegal.php
app/Support/Domiciliation.php
app/Support/FormeJuridique.php
app/Support/ModelesDeDocuments.php
app/Support/Mrz.php
app/Support/Prestations.php
app/Support/RecepisseAssociation.php
config/portail.php
database/migrations/2026_10_28_100000_les_associations.php
resources/views/filament/pages/partials/assistant-choix-type.blade.php
resources/views/filament/pages/registre-legal.blade.php
resources/views/filament/resiliations/lettres.blade.php
resources/views/portail/client/accueil.blade.php
resources/views/portail/client/documents.blade.php
resources/views/portail/client/profil.blade.php
resources/views/portail/inscription.blade.php
resources/views/portail/partiels/depot-piece.blade.php
resources/views/portail/partiels/nouveau-client-express.blade.php
resources/views/portail/racine.blade.php
resources/views/portail/verifier.blade.php
public/build/   (recompilé)
```

(Plus `CHANGELOG.md`, `DEPLOIEMENT.md`, `docs/rapports/RAPPORT-V1.17.0.md`.)
Les nouvelles classes sont chargées par le repli PSR-4 (`App\Support\Mrz`,
`App\Support\RecepisseAssociation`, `App\Services\AI\PdfEnImages`,
`App\Services\AI\BancLecture`, `App\Jobs\LirePieceDuPortail`,
`App\Exceptions\ModeleAssociationAbsentException`, la commande `mobdie:banc-lecture`) :
rien à faire de plus que d'habitude.

### 2. Mise en ligne

```bash
$PHP artisan migrate --force && $PHP artisan optimize:clear && $PHP artisan config:cache && $PHP artisan route:cache && $PHP artisan view:cache
```

Attendu : `2026_10_28_100000_les_associations` … DONE. Elle ajoute les quatre colonnes du
récépissé, la fonction des dirigeants (loi 43-05), deux réglages du cadre légal, et
**crée les neuf modèles « association »** bâtis sur le contrat en service (article 2 des
prestations compris). Aucun dossier n'est touché (aucune association en base).

**`.env`** : rien d'obligatoire. `PORTAIL_LECTURE` (facultatif, `auto` par défaut) : la
lecture d'une pièce du formulaire en ligne tourne après la réponse si LiteSpeed sait la
clore, sinon par la file (cron de chaque minute) — dans ce dernier cas les champs se
remplissent en moins d'une minute, le visiteur peut continuer à saisir.
La lecture renforcée décode les photos en mémoire (une photo de téléphone est
ramenée à 4 000 px) : garder **`memory_limit` à 256 Mo au moins** pour le PHP web
(MultiPHP INI Editor) ; le cron de la file tourne déjà à 512 Mo.

### 3. En lecture, dans l'ordre

1. `$PHP artisan mobdie:diagnostic --sans-donnees` — ligne **« Lecture des pièces »** :
   GD présent ; « PDF scanné rendu en images (…) » si pdftoppm, Imagick ou gs existent
   sur le serveur (sinon le PDF part tel quel, comme avant). Ligne **« Gabarits Word »** :
   OK (les neuf `_association` sont attendus).
2. `$PHP artisan mobdie:banc-lecture` — sans clé, une demi-minute : attendu
   « Recadrage … 100 % », « Bande MRZ … », **« Objectif tenu : aucune valeur fausse
   donnée pour sûre. »** Rien n'est écrit.
3. Facultatif, sur décision du gérant (dépense d'IA, plafond respecté) :
   `$PHP artisan mobdie:banc-lecture --pieces=20` — relit 20 pièces d'identité
   déposées, n'affiche que des n° de pièce et ✓ / ✗ / douteux, n'écrit rien.

### 4. À l'écran

- **Réglages → Cadre légal** : « Tribunal compétent pour une association » = Tribunal de
  première instance de Fès ; « Récépissé de déclaration d'une association — délai de
  remise (jours) » = 60, l'aide dit « pré-réglage de MOBDIE, pas un délai légal ».
- **Modèles de documents** : neuf lignes « … — association », statut « Nouveau »,
  rangées sous leur modèle. **Aperçu** du « Contrat de domiciliation — association » :
  « ASSOCIATION EXEMPLE », art. 1 sur le dahir n° 1-58-376, art. 3 seul à citer 544-4 et
  544-7, art. 14 « Tribunal de première instance de Fès ». À faire relire par un juriste
  avant la première association.
- **Nouveau dossier** : trois tuiles (Société, Association, Personne physique).
  Association → zone « Récépissé de déclaration et statuts » ; à « Vérifier », section
  « Déclaration de l'association », ni RC ni certificat négatif. Fermer sans créer.
- **Formulaire en ligne** (`domiciliation.mobdie.ma/demande`) : « Qui se domicilie ? »
  propose « Association » ; « En cours de déclaration / Déclarée » ; à l'étape 2, en
  déposant une CIN, « Lecture de votre CIN… » s'affiche puis les champs se remplissent.
  **Ne pas envoyer** de demande d'essai à un vrai client.
- **Liste des clients** : filtre « Forme ».
- Une **CIN photographiée en petit** dans l'espace client n'est plus refusée : elle
  arrive dans « À valider → Pièces » avec le point « Photo petite ».

## Mise à jour v1.18.0 — le connecteur Claude

Quatre migrations additives (tables `claude_tokens`, `claude_journal`, `claude_confirmations`, et
les réglages `claude.*` — accès **coupé** à la livraison). Une route `POST /mcp` (groupe `api`,
sans session) : rien à régler côté serveur, ni `.htaccess` ni `.env`. `public/build` recompilé.
Après `migrate --force` et les quatre `*:cache`, `curl -X POST https://app.mobdie.ma/mcp` doit
répondre **403** tant que l'accès n'est pas activé dans Paramétrage → Accès Claude. Une tâche
planifiée de plus : `mobdie:purger-journal-claude` (03:40), portée par `schedule:run`. Détail :
`5-deploiement/GUIDE-DEPLOIEMENT-V1.18.0.md`.

## Mise à jour v1.19.0 — dossier du comptable, échéances payées sans facture, connecteur

Deux migrations additives (`invoice_lines`/`quote_lines.prix_unitaire_ht` en DECIMAL(14,4) ;
`claude_confirmations.reponse`). Rien à régler côté serveur. Après `migrate --force` et les quatre
`*:cache` : `mobdie:deployer-v1190` (lecture seule), puis EN SIMULATION `reprise:factures-cabinet`
et `reprise:facturer-echeances-payees <codes>` ; `--appliquer --force` seulement sur décision du
gérant. Détail : `5-deploiement/GUIDE-DEPLOIEMENT-LOT-v4.34.0-v1.19.0.md` (siteweb Iradaventure 2).

## Mise à jour v1.20.0 — connecteur en mode administrateur, corbeille, appariements

Trois migrations additives et idempotentes (`claude_tokens.droits` et `go_session_jusqua` ; table
`claude_idempotence` ; `claude_journal.detail`). Rien à régler côté serveur (ni `.env`, ni cron, ni
`.htaccess`). `public/build` recompilé (classes des écrans Accès Claude et Journal Claude). Après
`migrate --force` et les quatre `*:cache` : `mobdie:deployer-v1200` (lecture seule) — Administrateur
**éteint sur tous les jetons**. Les droits se cochent ensuite, un par un, dans Paramétrage → Accès
Claude, sur un jeton créé au niveau Administrateur. Aucune reprise à lancer. Détail :
`5-deploiement/GUIDE-DEPLOIEMENT-LOT-v4.35.0-v1.20.0.md` (siteweb Iradaventure 2).

