# Spec — Conditions générales et leur acceptation

> Statut : `done`

## Intention

Dire à qui utilise l'application ce qu'elle fait de ses données, ce qu'elle ne fait pas, et ce qu'on attend de lui — et **garder la trace** de l'acceptation, compte par compte.

L'application enregistre les nom, prénom et adresse e-mail d'élèves mineurs. Le faire sans que personne n'ait rien lu ni rien accepté n'était pas tenable.

## Contraintes

- Les CGU ne peuvent pas être complètes : l'**éditeur**, l'**hébergeur** et la **base légale du traitement** ne sont pas connus. Elles le disent en tête, comme les mentions légales. Des conditions qui les inventeraient seraient pires que des conditions absentes.
- Tout le reste décrit le **comportement réel** de l'outil, vérifiable dans le code : pas de paiement, pas de mode hors ligne, trois e-mails et jamais d'identifiants ([mail.md](mail.md)), cloisonnement par association, journal non modifiable, conservation à trois ans ([ADR-0011](../decisions/0011-conservation-trois-ans.md)).
- Aucune notification d'acceptation n'est envoyée : la trace est en base, pas dans une boîte mail.
- **Mise à jour du 2026-08-25** : le texte nomme désormais le prestataire d'envoi (Brevo) comme sous-traitant, parce que l'adresse du destinataire et le contenu du message passent par lui. Écrire « rien n'est transmis à un tiers » serait devenu faux le jour du branchement.

## Où l'acceptation a lieu, et pourquoi pas à l'inscription

Le chantier disait « validation des CGU **à l'inscription** ». Il n'y a plus d'inscription : `/inscription` a été remplacée par une demande d'accès, et les comptes sont créés par les administrateurs ou par un import de fichier. **Personne ne s'inscrit.** L'acceptation se joue donc à deux moments :

1. **La demande d'accès.** Celui qui demande est l'adulte responsable de l'association. Case obligatoire, horodatée sur `DemandeAcces` — c'est la seule acceptation possible avant qu'un compte existe.
2. **La première connexion de chaque compte.** Un vendeur créé par son admin, ou importé depuis un CSV, n'a jamais rien accepté. C'est là qu'il le fait, pour lui-même.

## La barrière

Tant que `User.cguAccepteesLe` est nul, **l'application se résume à cet écran**. La redirection est posée dans un écouteur sur toutes les requêtes, pas dans chaque contrôleur : un contrôleur ajouté plus tard oublierait le contrôle, et personne ne s'en apercevrait.

La liste des routes libres est **explicite** — accueil, connexion, **déconnexion**, mentions, CGU, demande d'accès, écran d'acceptation. Une liste de routes interdites laisserait passer, par construction, toute route nouvelle.

La déconnexion reste ouverte volontairement : enfermer quelqu'un qui refuse, c'est enfermer exactement la personne à qui il faut laisser la porte ouverte. Le menu de navigation disparaît aussi tant que rien n'est accepté — tous ses liens rebondiraient sur la barrière.

## La modale d'acceptation

À la première ouverture du compte, une **modale** se pose sur l'application : le voile et la boîte de dialogue du design system, sans menu ni contenu derrière.

Elle ne recopie pas les conditions. Elle en tire **trois points**, ceux qui engagent vraiment un élève : le compte est personnel et le journal est nominatif ; ce qui est enregistré et pendant combien de temps ; l'application n'encaisse pas et ne marche pas sans réseau. Les conditions complètes sont à un lien, qui s'ouvre dans un onglet à côté.

Un mur de texte accepté sans être lu n'est pas une acceptation. Trois paragraphes lus valent mieux.

La case doit être **cochée** : sans elle, rien ne se passe et la modale le dit. La date n'est écrite qu'une fois — repasser dessus ne la réécrit pas, sinon elle finirait par dater du dernier passage plutôt que de l'accord.

### Une modale servie comme une page, et pas l'inverse

C'est le point qui fait qu'elle bloque vraiment. Une modale **injectée dans l'application** — le vrai écran dessous, une couche par-dessus — se referme en supprimant un nœud du DOM, et ce qu'elle recouvrait est déjà chargé dans la page.

Ici, tant que rien n'est accepté, la modale est la **seule chose que le serveur rend**. Ce qui bloque n'est pas le voile, c'est la route : il n'y a rien derrière à découvrir. Aucun JavaScript n'est nécessaire, et couper JavaScript ne l'ouvre pas.

Sur un téléphone, le texte défile dans la boîte mais **la case et le bouton restent collés en bas** : sans ça, il faut deviner qu'il y a quelque chose sous le pli pour pouvoir continuer.

## Critères de « fait »

- [x] Page publique `/conditions-generales`, liée depuis l'accueil et les mentions légales.
- [x] Ce qui n'est pas connu y est signalé comme tel, en tête.
- [x] Case obligatoire et horodatée sur la demande d'accès.
- [x] **Modale** d'acceptation à la première ouverture du compte, horodatée sur le compte.
- [x] Modale servie comme une page : rien derrière, rien à refermer, aucun JavaScript.
- [x] Sur téléphone : le texte défile, la case et le bouton restent atteignables.
- [x] Barrière sur toutes les routes, avec une liste blanche explicite, déconnexion comprise.
- [x] Menu masqué tant que rien n'est accepté.
- [x] Couvert par des tests : le voile et la boîte sont bien là, les seuls liens sortants sont les conditions et la déconnexion, la barrière tient, les pages légales et la déconnexion restent ouvertes, la case non cochée n'accepte rien, une date déjà posée n'est pas réécrite.

## Hors périmètre

- **Le versionnage des CGU.** Une modification substantielle se gère en vidant `cgu_acceptees_le`, ce qui redemande l'acceptation à tout le monde. Stocker un numéro de version et un historique par compte serait la bonne réponse à un problème que ce projet n'a pas.
- **Le recueil de l'accord des représentants légaux.** Il ne se joue pas dans un formulaire web, et l'application ne peut pas en être la preuve. C'est à l'établissement de l'organiser — voir `docs/chantiers.md`.
- **L'export ou la suppression à la demande d'une personne**, avant le terme des trois ans. La brique technique existe (l'anonymisation), le chemin dans le back-office n'existe pas encore.
