---
suivi: 1077
date: 2026-07-30
sujet: Module Foncier Lot 5 — socle d'alertes foncières
chantier: module-foncier
type: feature
statut: poussé
hash: e1c6043b
fichiers:
  - database/migrations/2026_07_30_170000_create_foncier_alertes_tables.php
  - app/Support/Foncier/Alertes/
  - app/Services/Foncier/FoncierAlerteEvaluationService.php
  - app/Http/Middleware/DeclencheFoncierAlertes.php
  - app/Http/Controllers/Foncier/FoncierAlerteAdminController.php
  - resources/js/Pages/Foncier/Alertes/
  - tests/Feature/Foncier/FoncierAlertesSocleTest.php
  - docs/suivi/SUIVI_1077_foncier_socle_alertes.md
---

## PROMPT ENVOYÉ

SUIVI #1077 — MODULE FONCIER LOT 5 : SOCLE D'ALERTES FONCIÈRES.
Prérequis #1075/#1076. Pattern web taches:digest (pas artisan), verrou +
dédup EN BASE, 2 types (veille revue, sans échange), admin foncier.gestion,
ACL à l'envoi, Teams bool exploité. Un commit, push.

## SYNTHÈSE

### Pattern taches:digest constaté dans le code
Middleware `DeclencheDigestQuotidien` (bootstrap web stack) :
1. `handle` : GET + Auth + après 9h Paris (+ jours ouvrables) →
   `insertOrIgnore` sur `taches_digest_runs.date_run` UNIQUE → si 1 ligne
   insérée, flag `declenche_digest` sur la requête.
2. Réponse utilisateur envoyée normalement (coût négligeable).
3. `terminate` : `Artisan::call('taches:digest')` — HTTPS Graph/Teams OK
   car contexte PHP-FPM (pas CLI SSH).

### Pattern repris (#1077) — DeclencheFoncierAlertes
Même verrou DB (`foncier_alerte_runs.date_run` UNIQUE, dès 8h).
`terminate()` appelle **directement** `FoncierAlerteEvaluationService::evaluer()`
— **aucune** commande artisan d'envoi (interdit). Dédup métier :
`foncier_alertes_emises` clé UNIQUE
(type, alertable_type, alertable_id, palier, destinataire_id, canal).
Survit à `optimize:clear` (R1).

### Architecture déclaration
Interface `FoncierAlerteType` + registry `FoncierAlerteTypeRegistry::TYPES`
= **seule** voie. Config DB (`foncier_alerte_configs`) : actif / paliers /
jours_silence. Admin lit le registry (pas de liste hardcodée hors TYPES).

### Ajouter un 3e type (zéro modif moteur)
1. Classe `app/Support/Foncier/Alertes/Types/MonType.php` (FoncierAlerteType)
2. Une ligne dans `FoncierAlerteTypeRegistry::TYPES`
3. Seed / insert `foncier_alerte_configs` (actif, paliers ou params)
→ Pas de changement EvaluationService / Middleware / Controller / Vue.

### Mapping types / canaux / paliers

| Type | Canaux | Paliers | Params |
|------|--------|---------|--------|
| dossier_veille_revue | cloche + teams | j-30, j-15, j-7, atteint | paliers JSON défaut [-30,-15,-7,0] |
| dossier_sans_echange | cloche + teams | silence_N | N=30 jours (exclu veille/terminé) |

### Destinataires

| Type | Destinataires | Accès vérifié à l'envoi | Test |
|------|---------------|-------------------------|------|
| veille_revue | responsables + créateur + DG/Admin | foncier.lecture + userCanViewRecord ; prix si foncier.prix | cloisonnement_et_prix |
| sans_echange | responsables + créateur | idem (sans prix dans message) | sans_echange_sur_en_cours |
| (retrait ACL) | encore dans destinataires potentiels | userCanViewRecord=false → skip | habilitation_retiree |

### Bundles Vite
- `Foncier/Alertes/Index` → Index-*.js (nouveau)
- `Foncier/Dossiers/Index` régénéré (lien Alertes)

### Contrôles
- php -l OK · ReflectionClass + DI OK
- Closures : registry `fn` codes() uniquement ; pas de closure métier dangereuse
- Tests Feature : 10 écrits (skipped local sans pdo_sqlite)

## DÉPLOIEMENT / TEST

1. `git pull origin main`
2. `php artisan migrate --force`
3. `php artisan view:clear && php artisan route:clear && php artisan journal:sync`
4. `/foncier/alertes` (foncier.gestion) : types, journal, « Évaluer maintenant »
5. Dossier veille `date_revue=aujourd'hui` → 1 alerte ; 50× → toujours 1
6. `Cache::flush` / optimize:clear → pas de double
7. B non habilité → 0 ; A sans prix → message sans montants
8. Teams false → Log::error + delivre=0 visible journal

## LEÇON

Sur OVH mutualisé cluster113, HTTPS sortant est mort en CLI
(`QUEUE_CONNECTION=sync` n'y change rien). Toute alerte Teams/Graph doit
partir d'un middleware web (`terminate` après verrou `insertOrIgnore` en base).
La déduplication et le verrou quotidien doivent être EN BASE : le cache
est vidé à chaque déploiement (`optimize:clear`) et provoquerait une salve
de doublons.
