# Welcom — Application de gestion des adhérents & contacts

Documentation complète pour reprendre le projet (notamment dans **Claude Code**).
Application web **PHP 8.3** qui remplace le Google Sheet « Suivi Entreprises et Networking Welcom » : base d'organisations e-commerce de la Loire / Haute-Loire, annuaire de contacts synchronisé avec **Brevo** et **HelloAsso**, outil d'emailing, et carte e-commerce publique.

> Association Welcom (asso-welcom.fr) — fédération régionale du e-commerce (42/43). Tout est en français.

---

## 1. Démarrage rapide (local)

```bash
# Pré-requis : PHP 8.3 CLI avec extensions sqlite3, curl, mbstring (imap optionnel)
cd adherent-welcom

# 1) Créer la base SQLite locale + un utilisateur admin
php tools/init_local.php          # crée data/welcom.sqlite à partir de sql/schema.sqlite.sql
php tools/create_user.php admin welcom42   # identifiants de connexion

# 2) Importer les données de référence (le « suivi » figé dans data/seed_data.json)
php tools/import_seed.php

# 3) Importer/typer les contacts depuis Brevo + engagement (nécessite la clé Brevo dans config)
php -r 'require "src/brevo.php"; brevo_import_all(); brevo_sync();'

# 4) (optionnel) Pré-rattacher automatiquement les domaines certains
php tools/autolink_domains.php

# 5) Lancer le serveur
php -S localhost:8000 -t public
# puis http://localhost:8000  (login: admin / welcom42)
```

> ⚠️ La base SQLite (`data/*.sqlite`) est **jetable** : tout se reconstruit avec `init_local` → `import_seed` → `brevo_import_all`. Ne pas y mettre de données qui n'existent pas dans le seed ou dans Brevo/HelloAsso.

---

## 2. Stack & dépendances

| Élément | Détail |
|---|---|
| Langage | PHP 8.3 (CLI en local, mod_php/php-fpm sur OVH) |
| Base locale | SQLite (`data/welcom.sqlite`) |
| Base production | MySQL (OVH) — schéma `sql/schema.mysql.sql` |
| Extensions PHP | `sqlite3`/`pdo_sqlite` (local), `pdo_mysql` (prod), `curl`, `mbstring`, `imap` (optionnel) |
| Front | PHP + HTML + CSS maison (`public/assets/style.css`) + JS vanilla (aucun framework, aucun build) |
| APIs externes | Brevo v3, HelloAsso v5 (OAuth2), api-adresse.data.gouv.fr (géocodage), recherche-entreprises.api.gouv.fr (enrichissement) |

Aucune dépendance Composer/npm : c'est volontairement « zéro build », déployable par simple copie sur OVH.

---

## 3. Arborescence

```
adherent-welcom/
├── config/
│   ├── config.php            # config réelle (clés API, DB) — gitignored
│   ├── config.example.php    # modèle à copier en config.php
│   └── .htaccess             # Deny from all
├── sql/
│   ├── schema.sqlite.sql     # schéma SQLite (local)
│   └── schema.mysql.sql      # schéma MySQL (OVH)
├── src/                      # logique métier (pas de HTML)
│   ├── db.php                # connexion PDO (SQLite/MySQL), helpers e(), config(), now_sql()
│   ├── model.php             # cœur métier : organisations, contacts, types, listes, recompute…
│   ├── auth.php              # sessions, login, CSRF (csrf_token / csrf_check)
│   ├── ui.php                # render_head(), render_nav() (barre de navigation)
│   ├── brevo.php             # API Brevo : import contacts, listes, campagnes, templates, envoi
│   ├── helloasso.php         # API HelloAsso : OAuth2, adhésions, présences événements, log_sync
│   ├── enrichment.php        # géocodage + enrichissement SIREN/CA via API publiques
│   └── mail.php              # IMAP (OVH Roundcube) — lecture e-mails (non finalisé)
├── public/                   # racine web (DocumentRoot = public/)
│   ├── index.php             # redirige vers dashboard/login
│   ├── login.php / logout.php
│   ├── dashboard.php         # Synthèse (KPIs + bannière « à rattacher »)
│   ├── organisations.php     # liste des organisations (filtres catégorie/adhésion/secteur)
│   ├── organisation.php      # fiche d'une organisation (infos, contacts, RDV, carte)
│   ├── contacts.php          # annuaire des individus (filtre par LISTE Brevo, drapeaux)
│   ├── contact.php           # fiche d'un individu (actions, activité, listes Brevo)
│   ├── rattacher.php         # rattacher les contacts aux entreprises (par domaine / un par un)
│   ├── engagement.php        # Emailing : modèles Brevo, campagnes, envoi liste/contacts choisis
│   ├── emails.php            # accès webmail Roundcube (OVH)
│   ├── evenements.php        # événements HelloAsso
│   ├── relances.php          # relances adhésions
│   ├── sync.php              # déclenche les synchros (helloasso|brevo|all)
│   ├── export.php            # export CSV/Excel (contacts, par type, par organisation)
│   ├── api/map.php           # FEED JSON PUBLIC pour la carte e-commerce (champs publics only)
│   ├── api/submit.php        # réception du formulaire « Soumettre mon entreprise » -> base (prospect)
│   ├── carte/index.html      # CARTE PUBLIQUE Leaflet (branchée sur api/map.php, plus sur le Sheet)
│   └── assets/               # style.css + welcom-logo.png
└── tools/                    # scripts CLI
    ├── init_local.php        # crée la base SQLite
    ├── create_user.php       # crée un utilisateur (login/mdp)
    ├── import_seed.php       # importe data/seed_data.json (organisations, contacts, presse, partenaires)
    └── autolink_domains.php  # pré-rattachement auto des domaines certains
```

Chaque dossier sensible (`config`, `src`, `sql`, `tools`) contient un `.htaccess` `Deny from all` ; seul `public/` est servi.

---

## 4. Configuration (`config/config.php`)

Copier `config.example.php` → `config.php` et renseigner :

```php
return [
    'app'  => ['name' => 'Welcom', 'session_ttl' => 86400],
    'db'   => [
        // local
        'driver' => 'sqlite', 'sqlite_path' => __DIR__.'/../data/welcom.sqlite',
        // OVH (décommenter) :
        // 'driver' => 'mysql', 'host' => '...', 'name' => '...', 'user' => '...', 'pass' => '...', 'charset' => 'utf8mb4',
    ],
    'security' => ['password' => '...'],   // sel/contexte interne
    'brevo'    => ['api_key' => '...'],     // clé encodée base64 (voir brevo_key())
    'helloasso'=> [
        'client_id' => '...', 'client_secret' => '...',
        'organization_slug' => 'welcom-web-loire-commerce',
        'membership_days' => 365,
    ],
    'imap'  => ['host' => 'ssl0.ovh.net', 'user' => 'contact@asso-welcom.fr', 'pass' => '...'],
    'map_url' => '',   // URL publique de la carte e-commerce (lien direct, optionnel)
    'list_adherents' => 4,  // ⚠️ liste Brevo « Adhérents ET e-commerçants » — NE PAS utiliser pour l'adhésion
];
```

**Identifiants en place** (déjà dans `config.php` du dépôt local) :
- Brevo : expéditeur actif `contact@news.asso-welcom.fr` (id 10). `contact@asso-welcom.fr` est inactif côté Brevo.
- HelloAsso : slug `welcom-web-loire-commerce`, OAuth2 client_credentials.
- E-mail : OVH Roundcube (`contact@asso-welcom.fr`).

---

## 5. Modèle de données

### Table `organisations` (l'entreprise = brique « entreprise »)
Champs clés : `nom`, `site_ecommerce`, `email` (e-mail générique entreprise), **`type`** (catégorie : `E-commerce` / `Presse` / `Partenaire` / `Institutionnel` / `Prestataire`), `activite`, `univers_metier` (secteur), `taille`, `adresse`, `ville`, `latitude`, `longitude`, `ca_communique`, `resume_public`, `fiche_cr` (compte-rendu RDV), `commentaires`, `date_rdv`, `is_adherent`, `statut_adhesion` (`a_jour`/`expire`/`inconnu`), `date_renouvellement`, `montant_cotisation`, `is_ancien`, `visible_carte`, `siren`, `effectif`, `events_cumul`, `last_event_date`.

### Table `contacts` (l'individu = brique de base)
Champs clés : `organisation_id` (NULLABLE, `ON DELETE SET NULL`), `prenom`, `nom`, `email`, `brevo_email`, `telephone`, `role`, `is_principal`, `in_brevo`, **`brevo_lists`** (ids de listes séparés par virgule — sert au filtre), engagement (`emails_recus`, `emails_ouverts`, `emails_clics`, `derniere_ouverture`), HelloAsso (`ha_is_member`, `ha_status`, `ha_last_date`, `ha_amount`, `ha_events_total`, `ha_events_12m`, `ha_last_event`, `ha_last_event_name`), `type` (champ legacy, **non utilisé dans l'UI** — l'adhésion est portée par l'organisation), `source` (`manuel`/`suivi`/`helloasso`/`brevo`).

### Autres tables
`notes`, `brevo_lists`, `brevo_campaigns`, `helloasso_token`, `sync_log`, `emails`. (`partenaires`, `presse`, `frais` existent encore dans le schéma mais **ne sont plus utilisées** : migrées en organisations+contacts.)

---

## 6. Règles métier importantes (à respecter absolument)

1. **HelloAsso = seule autorité pour l'adhésion** (qui a payé) et pour les **présences aux événements**. La liste Brevo **#4 « Adhérents ET e-commerçants » mélange** payeurs et non-payeurs : **ne jamais s'en servir** pour marquer un adhérent (faux positifs type BV Sport, Au Pays des Ânes).
2. **Le « type » est porté par l'ORGANISATION** (catégorie : E-commerce / Presse / Partenaire / Institutionnel / Prestataire). Les contacts ne sont **pas** typés dans l'UI : ils se filtrent par **liste Brevo**.
3. **Adhésion d'une organisation** : `recompute_org_status()` la calcule depuis HelloAsso (`ha_status` des contacts) ; **si HelloAsso n'a pas d'info, on conserve** la valeur héritée du suivi (ne pas écraser à 0).
4. **Contacts « entreprise » génériques** (`contact@`, `info@`…) → rangés dans `organisations.email`, **pas** comme individus. Les e-mails personnels sans nom → nom **déduit** de l'e-mail (`name_from_email()`).
5. **Drapeaux 🚩** : contact sans organisation, ou sans e-mail **ni** téléphone ; organisation sans contact ou sans contact joignable. Objectif produit : **relier un maximum de contacts aux entreprises**.
6. **Rattachement** : prioriser le **groupage par domaine** ; suggestions via `DOMAIN_SUGGEST` (entreprises identifiées par recherche) + correspondance de nom + domaine du site. Création d'organisation **uniquement sur action validée**.
7. **Jamais de données factices / placeholder.** Données issues d'API publiques affichées en gris italique (#666) pour les distinguer du premier main.
8. **Retrait du préfixe `www.`** : toujours `preg_replace('/^www\./','',$host)` — **ne jamais** faire `ltrim($host,'w.')` (rognait `wanadoo.fr` → `anadoo.fr`).

### Constantes clés (`src/model.php`)
- `ORG_TYPES` = `['E-commerce','Presse','Partenaire','Institutionnel','Prestataire']`
- `BREVO_LISTS` (id → libellé) : `2 Prospects`, `4 Adhérents & e-commerçants`, `11 Presse régionale`, `35 Prestataires`, `36 Institutionnels`, `37 Partenaires`, `39 Presse nationale`
- `DOMAIN_SUGGEST` : domaine e-mail → `[nom, catégorie]` (entreprises identifiées par recherche internet : Université Jean Monnet, Centre France, Editialis, SFI, Axome, CCI, Région AURA, etc.)
- `GENERIC_LOCALPARTS` : parties locales considérées « génériques » (→ e-mail d'organisation)

### Fonctions clés (`src/model.php`)
`org_create/org_update/org_get`, `clean_org_input` (whitelist `ORG_FIELDS`), `contact_create/contact_update/contact_attach/contact_detach/contact_delete`, `contacts_unassigned()`, `contacts_to_link()`, `recompute_org_status()/recompute_all_org_status()`, `type_from_brevo_lists()`, `email_is_generic()`, `name_from_email()`, **`autolink_certain_domains()`** (utilisée par le bouton « Pré-rattacher » et le tool CLI).

---

## 7. Intégrations externes

### Brevo (`src/brevo.php`)
- `brevo_key()` décode la clé base64 → vraie clé `xkeysib-…`. ⚠️ La clé peut être restreinte par IP (https://app.brevo.com/security/authorised_ips).
- `brevo_import_all()` : importe **tous** les contacts Brevo (≈1395) ; nommés → individus typés par liste, e-mails perso sans nom → nom déduit, génériques → e-mail d'organisation ; rattachement auto par domaine ; stocke `brevo_lists`.
- `brevo_sync()` : engagement (ouvertures/clics).
- `brevo_templates()` / `brevo_template($id)` : modèles d'e-mail (4 modèles existants : Qualification Prospects, Invitation individuelle, Communiqué de presse, Événement).
- `brevo_create_campaign()`, `brevo_send_campaign($id)` (envoi réel d'une campagne), `brevo_send_transactional($emails,$subject,$html)` (envoi individualisé à des contacts choisis), `brevo_list_add/remove`.

### HelloAsso (`src/helloasso.php`)
- OAuth2 client_credentials sur `https://api.helloasso.com/oauth2/token`.
- `ha_fetch_all()` : pagine les commandes (`/items?pageSize=100&withDetails=true`), back-off sur 403/429. Renvoie adhésions + présences événements par e-mail de payeur.
- `ha_sync()` : met à jour `ha_*` des contacts, fixe le `type` Adhérent/Ancien **sans écraser** les rôles, crée/rattache les payeurs inconnus, recalcule les organisations.
- ⚠️ HelloAsso **rate-limite** fortement : la 1ʳᵉ vraie synchro se fait côté serveur OVH via « Tout synchroniser » (bouton/`sync.php?source=helloasso`).

### Géocodage / enrichissement (`src/enrichment.php`)
- `geocode()` via `api-adresse.data.gouv.fr` (remplit lat/lng).
- Enrichissement SIREN/CA/effectif via `recherche-entreprises.api.gouv.fr` (filtrer par `siege.departement` 42/43).

---

## 8. Pages (public/)

| Page | Rôle |
|---|---|
| `dashboard.php` | KPIs + bannière « contacts à rattacher » |
| `organisations.php` | Liste + **filtre catégorie** + adhésion + secteur + recherche/tri ; drapeau si sans contact joignable |
| `organisation.php` | Fiche : infos éditables, **adresse → Google Maps**, site/e-mail cliquables, **compte-rendu RDV**, liste des contacts, enrichissement, géocodage |
| `contacts.php` | Annuaire (1277 individus) : **chips = listes Brevo**, recherche live, **drapeaux 🚩**, noms cliquables |
| `contact.php` | Fiche individu : actions (mail/appel/WhatsApp/organisation/itinéraire), activité (adhésion perso, événements, engagement), **cases listes Brevo synchronisées en temps réel** |
| `rattacher.php` | 2 vues : **Par entreprise (domaine)** avec picker en cascade *catégorie → entreprise* (recherche/autocomplétion, création si absente) et **Un par un** ; bouton **⚡ Pré-rattacher les domaines certains** |
| `engagement.php` | **Emailing** : modèle Brevo → objet/HTML → destinataires (liste Brevo **ou** contacts choisis) → brouillon ou **envoi réel** |
| `evenements.php` / `relances.php` | Événements HelloAsso / relances d'adhésion |
| `export.php` | Export CSV/Excel (`?type=contacts[&t=TYPE]` ou `?org=ID`) |
| `api/map.php` | **Feed JSON public** de la carte : e-commerçants `type='E-commerce'` **ET** `visible_carte=1`, géolocalisés. Zone (1-7) déduite de la commune. N'expose que des champs publics. `?type=adherent` filtre les adhérents |
| `api/submit.php` | Réception du formulaire « Soumettre mon entreprise » : crée une organisation **prospect non publiée** (`visible_carte=0`, `pipeline_stage='a_contacter'`) + un contact `Prospect` (source `carte`). reCAPTCHA vérifié si `recaptcha.secret` configuré |
| `carte/index.html` | **Carte publique** Leaflet (e-commerce ligérien). Autoportante (Leaflet/OSM/Fonts via CDN, logos via logo.dev) mais **données live** via `api/map.php`. Servie sur `gestion.asso-welcom.fr/carte/`, embarquable depuis le site vitrine |

> Navigation (`render_nav`) : Synthèse, Organisations, Contacts, À rattacher (badge), Emailing, Boîte mail, Événements, Relances + un lien **🗺️ Carte** (vers `map_url` ou `/carte/`).

---

## 9. État actuel des données (après seed + Brevo + autolink)

- **131 organisations** (90 E-commerce + Presse/Partenaire migrés + 27 créées au pré-rattachement).
- **1277 contacts** nommés. Répartition listes : Prospects ≈582, Institutionnels ≈191, Prestataires ≈167, Adhérents&e-commerçants ≈198, Presse nationale ≈42, Presse régionale ≈32, Partenaires ≈13, Sans liste ≈60.
- **≈25 organisations adhérentes** (du suivi ; HelloAsso confirmera/complétera).
- **≈965 contacts encore à rattacher** (≈258 domaines à 1-2 contacts + ≈670 e-mails perso).

---

## 10. Déploiement OVH (production)

1. Copier le dossier sur l'hébergement, **DocumentRoot → `public/`**.
2. Dans `config.php` : passer `db.driver` à `mysql` + renseigner host/name/user/pass.
3. Importer `sql/schema.mysql.sql` dans la base MySQL.
4. Créer l'utilisateur admin : `php tools/create_user.php admin <mdp>`.
5. Importer le seed : `php tools/import_seed.php`.
6. Première synchro : `sync.php?source=all` (Brevo + HelloAsso) — c'est là que HelloAsso remplit enfin adhésions & présences.
7. (optionnel) `php tools/autolink_domains.php` ou bouton « Pré-rattacher ».
8. Vérifier que `config/`, `src/`, `sql/`, `tools/` ne sont **pas** servis (htaccess en place).

---

## 11. Reste à faire / pistes

- ✅ **Carte publique branchée sur la base** (`public/carte/` ⇆ `api/map.php`), formulaire de soumission routé vers la base (`api/submit.php`). Sur OVH : renseigner `recaptcha.secret` dans `config.php` pour activer la vérification anti-robot, et `map_url` si la carte est servie ailleurs que `/carte/`.
- Intégrer la carte dans le site vitrine (iframe vers `gestion.asso-welcom.fr/carte/`) en remplacement de l'ancienne carte Google Sheet.
- Zone géographique calculée à la volée (commune + repli coordonnées) : si besoin de corriger une zone au cas par cas, prévoir une colonne `carte_zone` (migration) plutôt que de toucher au classifieur.
- Finaliser la **boîte mail** (IMAP OVH) : résumé des échanges par contact (la connexion IMAP `ssl0.ovh.net:993` est bloquée depuis l'environnement de dev mais fonctionnera sur OVH).
- Continuer le **rattachement manuel** des ≈258 domaines restants + e-mails perso.
- Affiner les **modèles d'emailing** et tester un envoi réel à petite échelle.

---

## 12. Conseils pour reprendre dans Claude Code

- **Lire d'abord** `src/model.php` (constantes + règles), puis `src/brevo.php` et `src/helloasso.php`.
- Boucle de test locale fiable :
  ```bash
  rm -f data/welcom.sqlite*; php tools/init_local.php
  php tools/import_seed.php
  php -r 'require "src/brevo.php"; brevo_import_all(); brevo_sync();'
  php -S localhost:8000 -t public   # login admin/welcom42
  ```
- Toujours `php -l fichier.php` après édition. Le shell est **dash** (pas de bashismes).
- Piège PHP récurrent : en syntaxe alternative `if(...): … else:`, ne pas finir par une instruction `if(...) echo …;` sans `:` juste avant `else:` (construire une chaîne puis `echo`).
- **Ne jamais** réintroduire de fausses données ; respecter les 8 règles métier (section 6).
