# RGPD — formulaires publics (leads)

Ce document liste les donnees personnelles collectees par les formulaires du
site, leur finalite, ou elles sont stockees, qui peut y acceder et combien de
temps elles sont conservees. Il complete `docs/architecture-donnees.md`
(mapping formulaire -> table -> ressource admin) et rappelle les regles fixees
par `CLAUDE.md` (section 3, "Conventions de code").

Rappel des regles CLAUDE.md a respecter en permanence :
- **Minimisation** : ne collecter que les champs necessaires a la finalite du
  formulaire (pas de champ "au cas ou").
- **Finalite claire** : chaque formulaire sert un seul objectif metier (etre
  recontacte, candidater, etc.), affiche a l'utilisateur au moment de la saisie.
- **Logs sans donnee sensible** : ne jamais logger le contenu d'un formulaire
  (nom, email, telephone, message, fichiers). Les logs applicatifs (Laravel)
  ne doivent contenir que des informations techniques (erreurs, traces).

## 1. Inventaire des donnees collectees

### Message de contact (formulaire "Contact", branches maison / question)

- **Table** : `messages_contact`
- **Ressource admin** : `MessageContactResource` (groupe "Formulaires recus")
- **Champs personnels** : `nom`, `email`, `telephone`, `message` (libre)
- **Champs non personnels** : `type_besoin`, `budget`, `localisation`, `sujet`,
  `traite`
- **Finalite** : permettre a un conseiller Biome de recontacter une personne
  interessee par l'achat d'une maison ou ayant une question generale.

### Lead terrain ("vendre mon terrain", tous points d'entree)

- **Table** : `leads_terrain`
- **Ressource admin** : `LeadTerrainResource` (groupe "Formulaires recus")
- **Champs personnels** : `nom`, `telephone`, `email`, `message` (libre)
- **Champs non personnels** : `origine` (point d'entree du formulaire),
  `commune`, `adresse`, `reference_cadastrale`, `surface`, `largeur_facade`,
  `traite` — ces champs decrivent le terrain, pas la personne, mais restent
  a traiter avec la meme prudence (l'adresse d'un terrain peut aussi
  correspondre au domicile du vendeur).
- **Fichiers** : `photos` (0 a 10 photos du terrain, JPG/PNG, 5 Mo max chacune)
  stockees sur le **disque prive** (`storage/app/private/leads-terrain/photos`),
  chemins references en JSON dans le champ `photos` de la table.
- **Finalite** : permettre a l'equipe Biome d'evaluer un terrain propose a la
  vente et de recontacter son proprietaire.
- **Point de conception** : cette table est **unique** pour tous les points
  d'entree du formulaire "vendre mon terrain" (page dediee en section, page
  dediee en modale, branche terrain du formulaire Contact en section ou en
  modale). Le champ `origine` (`contact_section`, `contact_modale`,
  `terrain_section`, `terrain_modale`) trace le point d'entree exact sans
  dupliquer le schema.

### Demande de visite (page Bien)

- **Table** : `demandes_visite`
- **Ressource admin** : `DemandeVisiteResource` (groupe "Formulaires recus")
- **Champs personnels** : `nom`, `telephone`, `email`, `message` (libre)
- **Champs non personnels** : `bien_id` (reference du bien cote CRM,
  lecture seule, voir `docs/architecture-donnees.md`), `date_souhaitee`,
  `traite`
- **Finalite** : planifier une visite d'un bien a vendre avec la personne
  interessee.

### Demande d'infos / brochure / prix / rappel (page Bien + rappel generique)

- **Table** : `demandes_info_bien`
- **Ressource admin** : `DemandeInfoBienResource` (groupe "Formulaires recus")
- **Champs personnels** : `nom`, `prenom`, `email`, `telephone`, `message`
  (libre)
- **Champs non personnels** : `type` (`infos`, `brochure`, `prix`, `rappel`),
  `bien_id` (nullable), `souhaits` (JSON, cases cochees dans la modale
  "infos" : rappel / brochure / visite), `traite`
- **Finalite** : repondre a une demande d'information sur un bien precis, ou
  a une demande de rappel generique (formulaires Contact, Acheter,
  Partenaires) sans bien associe.
- **Point de conception** : cette table est **unique** pour les 4 types de
  demande (`type`). Le rappel generique sans bien utilise `bien_id = null` :
  c'est le meme mecanisme que la modale "Interesse par ce bien ?" (sidebar
  de la page Bien), qui remplit en plus `souhaits` selon les cases cochees.

### Candidature (offre d'emploi ou spontanee)

- **Table** : `candidatures`
- **Ressource admin** : `CandidatureResource` (groupe "Formulaires recus")
- **Champs personnels** : `nom_prenom`, `telephone`, `email`, `message`
  (libre), `disponibilite`, `annees_experience`
- **Champs non personnels** : `offre_emploi_id` (nullable ; voir "Point
  d'attention" ci-dessous), `poste_souhaite`, `consentement_rgpd`, `traite`
- **Fichiers (donnees sensibles au sens large)** : `cv_chemin` (obligatoire),
  `lettre_motivation_chemin` (optionnel). Stockes sur le **disque prive**
  (`storage/app/private/candidatures/cv` et `.../lettre_motivation`), noms de
  fichiers regeneres en UUID (le nom original n'est pas conserve tel quel).
- **Consentement explicite** : case a cocher obligatoire (`consentement_rgpd`,
  regle de validation `accepted`) avant tout envoi — sans elle, la
  candidature est refusee des la validation du formulaire.
- **Finalite** : permettre a l'equipe RH d'etudier une candidature a une
  offre d'emploi ou une candidature spontanee.
- **Point d'attention** : `offre_emploi_id` est un simple `varchar`, sans
  cle etrangere, car le CRUD `OffreEmploi` n'existe pas encore dans ce lot.
  A faire evoluer en cle etrangere reelle des que ce CRUD sera livre (lot
  suivant) — voir aussi la note dans "Ecarts CLAUDE.md" plus bas si
  pertinent.

### Candidature partenaire / sous-traitant (page Partenaires)

- **Tables** : `candidatures_partenaires` et
  `candidature_partenaire_photos`
- **Ressource admin** : `CandidaturePartenaireResource` (groupe "Formulaires
  recus")
- **Champs personnels** : `prenom`, `nom`, `telephone`, `email`, `adresse`
  (siege), `message` (libre), `preferences_contact`
- **Champs non personnels** : `societe`, `numero_tva`, `metiers`, `zones`,
  `effectif`, `rayon_km`, `disponibilite`, `date_disponibilite`,
  `travaille_promoteur`, `promoteurs_actuels`, `langue`, `statut`
- **Finalite** : permettre a l'equipe Biome d'etudier une proposition de
  collaboration avec un partenaire ou sous-traitant, et de retrouver un
  metier disponible sur une zone donnee au moment d'un chantier.

> **Photos de realisations** (etape facultative, depuis le 2026-08-28) :
> stockees sur le disque **prive** `local`, sous
> `candidatures-partenaires/{id}/`, et servies uniquement a un administrateur
> authentifie (`GET admin/candidatures-partenaires/photos/{photo}`). Elles
> peuvent montrer des chantiers de **clients tiers** : ce sont des donnees
> confiees, jamais du contenu de site, et elles ne doivent a aucun moment etre
> exposees par une URL publique.
>
> La purge des candidatures supprime **aussi les fichiers** : la cascade de la
> cle etrangere n'emporte que les lignes en base (verifie par
> `PurgeCandidaturesPartenairesTest`).

> **Reprise de l'ancien site WordPress** : 309 candidatures, 758 notes de
> suivi, 555 photos et 181 fiches BCE ont ete importees
> (`app:importer-partenaires-wordpress`). Ces donnees conservent leur **date
> d'origine**, ce qui les place directement sous la purge des 24 mois : les
> plus anciennes seront purgees a leur date reelle, et non 24 mois apres
> l'import. C'est voulu — l'import ne doit pas prolonger artificiellement une
> duree de conservation.
>
> Les notes de suivi peuvent contenir du texte libre saisi par l'equipe, donc
> potentiellement des appreciations sur des personnes. Elles suivent le meme
> sort que la candidature : suppression en cascade a la purge.

> `numero_tva` est stocke sous forme canonique `BE` + 10 chiffres. Un doublon
> n'est pas refuse a la soumission (le lead est toujours enregistre) mais
> signale dans la fiche admin : c'est un indice de profil envoye deux fois, pas
> une donnee supplementaire collectee.

> Note sur la modale "etre rappele" de la page Partenaires : elle n'est PAS
> stockee dans `candidatures_partenaires`, mais dans `demandes_info_bien`
> (`type = rappel`, `bien_id = null`), au meme titre que les autres rappels
> generiques. Voir la section correspondante ci-dessus.

## 2. Stockage

- **Donnees structurees** : MySQL, dans les 6 tables listees ci-dessus, plus
  `telechargements_guide` (voir section 5). Aucune duplication ailleurs.
- **Fichiers uploades** (photos de terrain, CV, lettres de motivation) :
  disque Laravel `local`, configure sur `storage/app/private` (voir
  `config/filesystems.php`) — **jamais dans `public/`**, donc jamais
  accessibles par une URL directe.
- **E-mails de notification** : chaque soumission declenche l'envoi d'un
  e-mail interne (`App\Mail\NouveauLeadMail`) a l'adresse configuree dans
  `config('site.email_reception_leads')` (voir README, variable d'env
  `MAIL_RECEPTION_LEADS`). Cet e-mail reprend les champs du formulaire : il
  doit etre traite avec la meme confidentialite qu'une donnee en base
  (boite mail interne, pas de transfert externe).

## 3. Qui peut acceder aux donnees

- **Consultation** : uniquement les comptes administrateurs connectes au
  panneau Filament (`/admin`), via les ressources du groupe "Formulaires
  recus". Aucune de ces ressources n'autorise la creation (`canCreate() =
  false`) : ce sont des soumissions entrantes, jamais du contenu editorial
  cree depuis l'admin.
- **Telechargement des fichiers de candidature** (CV, lettre de motivation) :
  routes dediees `admin.candidatures.cv` et `admin.candidatures.lettre-
  motivation` (voir `routes/web.php`), protegees par le middleware `auth`
  (la meme session que l'admin Filament). Impossible de deviner/atteindre un
  fichier sans etre connecte.
- **Suppression manuelle** : chaque table dispose d'une action "Supprimer"
  dans la liste de l'admin (`DeleteAction` Filament). Un administrateur peut
  donc supprimer une soumission (et devrait penser a supprimer les fichiers
  associes le cas echeant — voir limite ci-dessous).

## 4. Duree de conservation

- **Valeur : 24 mois**, portee par `config('site.retention_leads_
  mois')` (variable d'env `RETENTION_LEADS_MOIS`, voir README).
- **Statut : VALIDEE par Laurent (client) le 2026-07-03** — voir
  `docs/architecture-donnees.md` section "Decisions client du 2026-07-03".
  Cette duree est desormais definitive, a moins d'un nouvel arbitrage.
- **Purge automatique : IMPLEMENTEE.** La commande Artisan
  `app:purger-leads-anciens` (`App\Console\Commands\PurgerLeadsAnciensCommand`)
  supprime, pour chacune des 7 tables de leads (les 6 tables de la section 1
  ci-dessus, plus `telechargements_guide`, voir section 5) **ainsi que pour les
  reponses aux formulaires personnalises** (`form_submissions`, voir section 6),
  les enregistrements dont `created_at` depasse la duree de conservation
  configuree. Les fichiers prives associes (photos de terrain, CV, lettres de
  motivation, pieces jointes des formulaires personnalises) sont supprimes du
  disque `local` **avant** la ligne en base correspondante, pour ne jamais
  laisser de fichier orphelin en cas d'echec partiel.
- **Option `--dry-run`** : `php artisan app:purger-leads-anciens --dry-run`
  liste le nombre d'enregistrements concernes par table, sans rien supprimer
  (fichiers compris). Utile pour verifier l'impact avant une purge reelle.
- **Planification** : `routes/console.php` declare
  `Schedule::command('app:purger-leads-anciens')->daily()`, execute une fois
  par jour via l'unique tache cron `schedule:run` du mutualise OVH (voir
  `docs/deploiement-ovh.md`) — aucun demon supplementaire requis.
- **Journalisation sans donnee personnelle** : la sortie console affiche
  uniquement un compte d'enregistrements par table (ex. "Candidatures : 3
  enregistrement(s) supprime(s)."), jamais le contenu des soumissions.
- **En complement** : la suppression manuelle depuis l'admin (voir section 3)
  reste disponible pour retirer une donnee avant l'echeance des 24 mois.

## 5. Telechargement du guide (page Actualites)

- **Table** : `telechargements_guide`
- **Ressource admin** : `TelechargementGuideResource` (groupe "Formulaires
  recus")
- **Champs personnels** : `prenom`, `email`
- **Finalite** : envoyer le guide Biome (PDF gere depuis l'admin, voir
  `docs/architecture-donnees.md`) a une personne interessee par un projet
  immobilier en Wallonie.
- **Minimisation** : seuls le prenom et l'email sont demandes par la
  maquette, aucun champ supplementaire n'est collecte.
- Cette table est incluse dans la purge automatique decrite en section 4, au
  meme titre que les 6 autres tables de leads.

## 6. Formulaires personnalises (constructeur de formulaires)

Depuis l'ajout du **constructeur de formulaires** (onglet admin "Formulaires
personnalises"), un administrateur ou utilisateur autorise peut creer des
formulaires multi-etapes generiques, publies a une URL publique dediee
(`/formulaires/{uuid}`). Ces formulaires ne sont plus "en dur" : leur structure
est definie par un schema JSON. Les points RGPD specifiques :

- **Tables** : `forms` / `form_versions` (definition et versions du formulaire,
  aucune donnee personnelle), `form_submissions` (les reponses), et
  `form_submission_files` (fichiers joints).
- **Ressource admin** : `FormSubmissionResource` (groupe "Formulaires
  personnalises"), en **lecture seule** (`canCreate() = false`). Consultation
  reservee a la permission `reponses-formulaires.view` ; changement de statut a
  `reponses-formulaires.update` ; export CSV a `reponses-formulaires.export`.
- **Champs personnels : variables**, definis au cas par cas par la personne qui
  construit le formulaire (nom, email, telephone, etc. selon le schema). La
  **regle de minimisation reste imperative** : celui qui cree un formulaire ne
  doit y mettre que les champs necessaires a une **finalite claire**, affichee
  au visiteur. Un champ de **consentement RGPD** dedie est prevu (type
  `consentement-rgpd`, Phase 2) pour les formulaires collectant des donnees
  personnelles.
- **Donnees techniques stockees systematiquement** : `ip_address` et
  `user_agent` du visiteur au moment de la soumission, ainsi que la provenance
  (`source`, `utm_source/medium/campaign/content/term`). **Base legale :
  interet legitime** (lutte anti-spam/fraude, preuve et horodatage de la
  soumission, mesure d'origine des campagnes) — **decision client validee**.
  Ces informations n'etaient PAS collectees par les 7 formulaires "en dur"
  (sections 1 et 5) et constituent donc un ajout : la **politique de
  confidentialite publique doit le mentionner** (donnees collectees, finalite,
  base legale, duree).
- **Fichiers joints** : disque **prive** `local`
  (`storage/app/private/form-submissions/...`), noms regeneres en UUID, jamais
  exposes par URL publique. Telechargement depuis l'admin uniquement, sous
  `middleware('auth')` **et** permission `reponses-formulaires.view`
  (`App\Http\Controllers\Admin\FormSubmissionFichierController`).
- **Versioning** : chaque reponse est liee a la **version du formulaire** au
  moment de la soumission (`form_version_id`), ce qui garantit une lecture
  fidele meme si le formulaire est modifie ensuite.
- **E-mail de notification** : meme regle que les leads (envoi interne a
  `config('site.email_reception_leads')`, a traiter avec la meme
  confidentialite).
- **Conservation / purge** : **24 mois**, incluse dans la purge automatique de
  la section 4 (reponses + fichiers joints supprimes ensemble).
