# Backend — Niveaux, suivi d'impact & preuve sociale

Squelette pour `sfauquant/uluana_server` (branche `develop`), écrit dans tes conventions
(`_core.php`, `current_user()`, `respond`/`error`, `gen_id`, migrations auto).

| Fichier livré | Destination |
|---|---|
| `sql/2026_07_05_025_niveaux_impact_social.sql` | `sql/` |
| `api/levels.php` | `api/` |
| `api/impact.php` | `api/` |

La migration est jouée automatiquement par `deploy/migrate.php` au prochain déploiement
(elle est idempotente : `IF NOT EXISTS` + colonnes ignorées si déjà présentes).

---

## Ce que ça couvre

Trois briques qui manquaient (le reste — checklist, satisfaction, photos/docs — passe déjà
par l'existant, voir plus bas) :

1. **Niveaux par domaine** — `GET /api/levels`
2. **Suivi d'impact d'une action** (graphe d'évolution) — `GET/POST /api/impact/{card_id}`
3. **Preuve sociale avant/après** (le slideshow + « +141 % en moyenne ») — `GET /api/impact/{card_id}/aggregate`

---

## 1. Niveaux par domaine

**Prérequis données** : rattacher chaque carte à un domaine. Ajoute simplement
`"domain": "parcours_client"` dans le guide de la carte (`data/card_guides/*.json`).
Domaines reconnus : `image_de_marque`, `parcours_client`, `gamme_de_tarifs`
(modifiables dans la constante `DOMAINS` de `levels.php`). L'import
(`import-catalogue.php`) pousse la valeur dans la colonne `card_guide.domain` — **aucun code**.

**Règle XP** (dans `levels.php`) : chaque action `validee` = `XP_PER_ACTION` (15) dans le
domaine de sa carte. Paliers cumulés dans `LEVEL_THRESHOLDS`.

**Réponse** `GET /api/levels` :
```json
{
  "user_id": "u_…",
  "xp_total": 75,
  "domains": [
    { "domain":"image_de_marque", "label":"Image de marque",
      "xp":30, "level":2, "progress":0.55, "xp_to_next":45, "next_floor":120 },
    …
  ]
}
```
→ alimente la barre « Mes domaines » de la vue chemin (les 3 anneaux + niveau).

---

## 2. Suivi d'impact (graphe)

Une mesure = une valeur à une date, dans `action_impact` (une ligne par
`user/card/metric/date`). Le 1er relevé sert d'« avant », le dernier d'« après ».

- `GET /api/impact/{card_id}?metric=contacts` → `{ series:[{date,value}], before, after, next_at, followup_days }`
- `POST /api/impact/{card_id}` body `{ "value": 41, "date":"2026-07-05", "metric":"contacts", "note":"" }`
  → upsert + programme le **prochain relevé à J+7** (`actions.impact_next_at`, `rappel_sent=0`).

Le graphe de la vue « action terminée » se dessine à partir de `series`. La pastille
« prochain suivi dans J-7 » lit `next_at`. Le **cron existant** (`cron/send_reminders.php`)
peut envoyer le rappel en filtrant sur `actions.impact_next_at <= CURDATE() AND rappel_sent=0`.

---

## 3. Preuve sociale avant/après

- `GET /api/impact/{card_id}/aggregate?metric=contacts`
```json
{ "enough": true, "count": 12, "avg_pct": 141,
  "samples": [ { "name":"Camille", "before":8, "after":22, "delta":"+175%" }, … ] }
```
→ le « +141 % de contacts en moyenne » et le slideshow des autres créateurs sur l'écran
**Découverte**. Anonymisé (prénom seul) et masqué tant qu'il y a moins de
`AGG_MIN_USERS` (3) artisans → renvoie `{ "enough": false }`.

⚠️ Le calcul du « avant/après » de l'utilisateur lui-même n'est **jamais** exposé avant
qu'il ait terminé l'action : cet endpoint n'agrège que les relevés existants des autres.

---

## Ce qui NE demande aucun code (déjà en place)

- **Checklist « ce que je mets en place » + satisfaction + nb de contacts** : ce sont des
  champs `renseignements.fields` dans le guide de la carte → réponses stockées dans
  `actions.data` (JSON) via `ActionsService.saveData()` → `PUT /api/my-actions`. Ajoute juste
  les champs au JSON du guide (types `boolean`, `text`, `number`, `tags`, `show_if`).
- **Photos / documents** : upload via `/api/upload` (`type=action_image|action_document`,
  `card_id` requis), listés/supprimés via `api/action-documents.php`. Déjà générique.
- **Prendre / terminer une action** : `POST` / `PUT /api/my-actions` (statut `en_cours` → `validee`).

---

## Routage

Chaque endpoint est un fichier `api/<ressource>.php` (même schéma que `suivis.php`,
`parcours.php`). Vérifie que ta règle de réécriture `/api/<x>/...` → `api/<x>.php` est
générique (c'est le cas pour les endpoints actuels) — sinon ajoute `levels` et `impact`
à la liste des ressources connues.

## Appels côté front (rappel)

```js
const levels   = await fetch(DataManager.url('/api/levels'), { headers, credentials:'include' }).then(r=>r.json());
const impact   = await fetch(DataManager.url('/api/impact/' + cardId), { headers, credentials:'include' }).then(r=>r.json());
const proof    = await fetch(DataManager.url('/api/impact/' + cardId + '/aggregate'), { headers, credentials:'include' }).then(r=>r.json());
await fetch(DataManager.url('/api/impact/' + cardId), { method:'POST', headers, credentials:'include', body: JSON.stringify({ value: 41 }) });
```
(`headers` = `_authHeaders()` comme dans `parcours.js`.)
