---
suivi: 1368
date: 2026-08-31
sujet: diag_leads_meta_campagne_absente
chantier: leads_marketing
type: diagnostic
statut: poussé
hash: c296b819
fichiers:
  - docs/suivi/SUIVI_1368_diag_leads_meta_campagne_absente.md
  - tools/diag/diag_leads_meta_campagne_absente_1368.php
---

## PROMPT ENVOYÉ

PHASE 1 — DIAGNOSTIC. `/admin/leads-marketing` : Programme et Campagne « — »
sur leads Meta récents (≥ 17/08). Affectation commerciale discriminante OK
(Stéphanie / Mathieu / Laurent). Lecture seule. Pas de rattrapage. Pas #1364/#1367.

## FICHES LUES

- `#1365` / `#1366` — pattern scripts `tools/diag/` : `PHP_SAPI !== 'cli'`,
  require relatif, SHOW COLUMNS, sections try/catch, CWD OVH racine.
- `#1367` — hors périmètre (verrou factures).
- Pas de fiche `#948` / `#950`–`#956` dans `docs/suivi/` (commits git seulement).

## SYNTHÈSE (6 objets)

### OBJET PRIORITAIRE — Critère d’affectation commerciale

| Élément | Valeur |
|---------|--------|
| Fichier:ligne | `MetaLeadIngestionService.php:49-67` |
| Résolution | `MetaCampaignCode::resolveForLead($campaignId, $campaignName)` (`MetaCampaignCode.php:29-54`) |
| Priorité | 1) `meta_campaign_id` exact actif ; 2) code `^(\d{3})\s*-` extrait du **nom** campagne |
| Commercial | `$mapping->commercial_user_id` ; si mapping null **ou** commercial null → `LEADS_FALLBACK_COMMERCIAL_USER_ID` |
| Programme | `$mapping->programme_id` (même mapping — **pas** un autre critère) |
| Page / form / géo | **Non** utilisés pour l’affectation commerciale à l’ingestion |

**Conséquence logique** : si le commercial varie de façon discriminante **sans** réaffectation manuelle
(`LeadMarketingController::updateCommercial` ~280), la table `meta_campaign_codes` **est** interrogée
avec succès pour ces leads. Le défaut n’est alors **pas** un masquage front type #1363 :
`LeadMarketingController::serializeListItem` (~445-452) envoie tel quel `programme` et
`meta_campaign_name` ; l’UI affiche `—` seulement si **null / vide en base**
(`Index.vue` ~269-278).

Hypothèses à trancher par la sortie OVH du script (ligne lead récente + peuplement mapping) :

1. **Mapping trouvé** + `programme_id` NULL sur la ligne de mapping + `getCampaign` en échec
   → commercial OK, Programme « — », Campagne « — » (nom jamais enrichi).
2. **Mapping trouvé** avec programme, mais colonnes lead non écrites → bug d’écriture (peu probable
   au vu du code create() l.73-95).
3. **Fallback seul** + réaffectations manuelles / changement de fallback → expliquerait le
   basculement Stéphanie (17-19/08) → Mathieu/Laurent (fin août) **si** confirmé en base
   (commercial = fallback_id pour la majorité).

### OBJET 1 — Dater la rupture

**DB locale Cursor : indisponible** (MySQL 127.0.0.1 refusé). Tableau 60 jours + dernier lead
rattaché = **sortie brute obligatoire** du script OVH section `OBJET 1`.

Côté git (30 j+) : **aucun** commit touchant l’ingestion webhook / verrou campagne autour du
17/08. Derniers lots Meta : `#950`–`#956` (24/07/2026, backfill multi-pages), `#952` filtre UI,
`#1146` (fix loadMissing, pas le chemin campagne). → rupture **plus probable** données /
token / mappings Meta qu’un déploiement ERP du 17/08.

### OBJET 2 — CENTRAL : ce que Meta envoie / ce que l’ERP lit

| Étape | Fichier:ligne | Champ |
|-------|---------------|-------|
| Webhook leadgen | `MetaWebhookController.php:71-78` | `leadgen_id`, `form_id`, `page_id`, `ad_id`, `adset_id`, `campaign_id` **optionnel**, `created_time` |
| Graph lead | `MetaGraphService.php:463-466` + `ProcessMetaLead.php:39` | fields : `id,created_time,ad_id,adset_id,campaign_id,form_id,field_data` — **pas** `campaign_name` |
| Nom campagne | `ProcessMetaLead.php:48-52` → `MetaGraphService::getCampaign` (~486) | `fields=name` sur `/{campaign_id}` |
| Persistance | `MetaLeadIngestionService.php:79-92` | colonnes `meta_campaign_id`, `meta_campaign_name` + `raw_payload` = `{webhook, lead_api}` |

**Payload brut conservé : OUI** (`leads_marketing.raw_payload`).

Point critique : le **libellé** campagne affiché en UI dépend **uniquement** de `getCampaign`
(enrichissement tokenisé). Si `getLead` réussit (contact OK) mais `getCampaign` échoue
(token / droits ads / campagne autre compte), `meta_campaign_name` reste null → colonne « — »
alors que le lead est créé. Comparer dans le script les slices `lead_api.campaign_id` /
clés webhook **AVANT** vs **APRÈS** le dernier lead nommé.

### OBJET 3 — Mécanisme de rattachement (prouvé peuplé ?)

1. `campaign_id` (API ou webhook) + éventuellement `campaign_name` (getCampaign).
2. `MetaCampaignCode::resolveForLead` → `programme_id`, `commercial_user_id`, code.
3. Code aussi dérivé du nom `^NNN -` si mapping fournit le code.

**Peuplement** : COUNTs `meta_campaign_codes` (actifs, avec programme, avec commercial, avec
`meta_campaign_id`) + buckets leads 15 j `sans_id` / `id_sans_mapping` / `id_avec_mapping`
→ script OBJET 3. Si table vide ou IDs Meta récents absents du mapping → tous les leads
tombent en fallback commercial + Programme « — » (précédents index vides du projet).

### OBJET 4 — Depuis quel changement + jeton

- Git (fichiers Meta / leads) depuis ~01/07 : `a030ea84` #950, `115d784a` #951, `229bc944` #956,
  `6099c8d7` #952, `e4ab4607` #1146 — **rien autour du 17/08** sur ce chemin.
- Webhook live : **un seul** token `META_PAGE_ACCESS_TOKEN` (`MetaGraphService::pageAccessToken`
  ~642). Tokens GEMME/ENVOL = **backfill #956 seulement**.
- **#948** (routage multi-page webhook) : **jamais construit** — signaler si les leads récents
  ont un `meta_page_id` ≠ page HECTARE : getCampaign / droits peuvent diverger.
- Santé jeton : script appelle `checkTokenHealth()` **sans** divulguer le secret ; en CLI OVH
  le réseau externe peut être HTTP 000 → indiquer relance web/FPM si besoin. Lire aussi
  `meta_health_checks` si peuplé.

### OBJET 5 — Complétude autres champs

Agrégats AVANT/APRÈS cutoff (dernier lead avec nom campagne) : prenom/nom/email/tel,
`meta_*`, programme, commercial, SharePoint, Teams — script OBJET 5. Attendu d’après le
symptôme UI : contact + commercial + SP/Teams verts ; campagnes/programme en chute.

### OBJET 6 — Récupération (faisabilité, pas d’exécution)

- Si `raw_payload` + `meta_campaign_id` présents : **oui**, relecture locale / re-appel
  `getCampaign` + `resolveForLead` (même logique que backfill).
- API Meta : `GET /{lead-id}` + `GET /{campaign-id}?fields=name` depuis **PHP-FPM**, pas CLI OVH.
- Volume : COUNTs script OBJET 6 (60 j sans nom). **Ne pas exécuter** de rattrapage ici.

## DÉPLOIEMENT-TEST

```bash
cd /home/hectare-envol/envol.hectare.fr
php tools/diag/diag_leads_meta_campagne_absente_1368.php
```

Coller la sortie complète. Trancher ensuite PHASE 2 (fix affichage vs enrichissement vs mapping).

## LEÇON

Commercial et programme partagent le **même** mapping ; un commercial discriminant n’implique
pas que le nom de campagne soit stocké. Le nom UI dépend d’un **second** appel Graph
(`getCampaign`) distinct de `getLead`. Ne pas conclure « bug d’affichage » sans lire les
colonnes nulles en base.
