Initialisation du dépôt : specs (socle plateforme, modèle de données, site vitrine) et steering

This commit is contained in:
2026-10-12 11:06:57 +02:00
commit 0a33e755ff
12 changed files with 1512 additions and 0 deletions
+305
View File
@@ -0,0 +1,305 @@
# Modèle Physique de Données — SchotzimmO
> **Source de vérité physique** des bases de données SchotzimmO. Document **transversal** à toute la plateforme. Ce document complète le modèle conceptuel (diagrammes ER des `design.md`) en décrivant le **détail physique** : tables, colonnes, types MariaDB, nullabilité, clés, index, relations et règles.
>
> **Architecture à deux niveaux de base** (voir `socle-plateforme/design.md`) :
> - **Base Socle** (centrale, unique) : authentification, référentiel des réseaux et leurs informations de connexion, abonnements, catalogue des modules/packs, administration, KPI d'usage remontés par réseau. **Aucune donnée métier produite par les mandataires.**
> - **Base Réseau** (une par réseau) : toutes les données métier cloisonnées d'un réseau — profil mandataire, parrainage/hiérarchie, et les données des Modules (Carnet d'adresses, KMS, CRM, Agenda, Facturation…).
>
> Le document grandit **module par module** : les sections Socle et Réseau sont posées en premier, puis une sous-section Base Réseau est ajoutée pour chaque Module.
---
## Conventions générales
- **SGBD** : MariaDB / MySQL, via le provider EF Core `Pomelo.EntityFrameworkCore.MySql`.
- **Moteur & encodage** : toutes les tables en **InnoDB**, charset **`utf8mb4`**, collation **`utf8mb4_unicode_ci`**.
- **Nommage** : tables en **`snake_case` pluriel** ; colonnes en **`snake_case`**. Mapping entité C# (PascalCase) vers table/colonne assuré par EF Core.
- **Clé primaire** : colonne **`id`**, type **`INT UNSIGNED`**, **`AUTO_INCREMENT`**, sauf exception documentée (ex. `mandataire_profiles.account_id` réutilise l'identifiant du socle).
- **Clés étrangères** : nommées **`<entité>_id`**. Toute FK **au sein d'une même base** est une vraie contrainte FK indexée. Les références **entre bases différentes** (ex. `account_id` du socle utilisé dans une base réseau) ne sont **pas** des contraintes FK MariaDB : ce sont des **références logiques** (mêmes valeurs d'identifiant), dont la cohérence est assurée **applicativement**.
- **Référence inter-bases** : `account_id` est généré par la Base Socle (`accounts.id`) et **réutilisé tel quel** comme identifiant du mandataire dans sa Base Réseau. Il n'y a jamais de jointure SQL traversant deux bases.
- **Dates & heures** : type **`DATETIME`**, valeurs stockées en **UTC** ; horodatage de création par défaut `CURRENT_TIMESTAMP`.
- **Chaînes** : **`VARCHAR(n)`** dimensionné (e-mail 255, nom/prénom 100…) ; **`TEXT`** pour le texte libre long.
- **Booléens** : **`TINYINT(1)`** (0/1), mappés sur `bool` C#.
- **Énumérations** : **`VARCHAR`** à valeurs contraintes applicativement (ex. `role`, `status`), plutôt que le type `ENUM` natif.
- **Montants** : **`DECIMAL(12,2)`**. Distances/quantités : `DECIMAL` dimensionné selon le Module.
- **Secrets** : mots de passe et jetons stockés **hachés**. **Aucun secret de connexion** (chaîne de connexion d'une base réseau) n'est stocké en clair en base : la Base Socle ne stocke qu'une **clé logique** de connexion, résolue vers un secret fourni par configuration.
- **Suppressions** : par défaut physiques. Les cas de conservation (rétrogradation d'abonnement) reposent sur la présence des données et l'état d'accès calculé.
### Légende des tableaux de colonnes
- **Clé** : `PK` = clé primaire, `FK` = clé étrangère (même base), `FK*` = référence logique inter-bases (non contrainte), `UK` = unicité.
- **Null** : `NON` = `NOT NULL`, `OUI` = nullable.
---
# PARTIE A — Base Socle (centrale, unique)
Référence : `socle-plateforme/design.md`. Cette base ne contient aucune donnée métier produite par les mandataires.
Vue d'ensemble :
| Table | Rôle |
|---|---|
| `networks` | Référentiel des réseaux + clé logique de connexion à leur base réseau + paramétrage |
| `accounts` | Comptes (authentification minimale) ; pivot d'identité, rattaché à un réseau |
| `packs` | Formules d'abonnement (Starter / Pro / Premium) |
| `module_catalog` | Catalogue des modules et pack requis pour chacun |
| `subscriptions` | Abonnement courant d'un compte à un pack |
| `password_resets` | Jetons de réinitialisation de mot de passe (hachés) |
| `network_usage_kpis` | Indicateurs d'usage remontés par chaque réseau (pas de donnée nominative) |
---
### Table `networks`
Référentiel des réseaux. Chaque réseau pointe vers sa propre base de données via une **clé logique de connexion** et porte son paramétrage de personnalisation.
| Colonne | Type | Null | Clé | Défaut | Description |
|---|---|---|---|---|---|
| `id` | `INT UNSIGNED` | NON | PK | auto | Identifiant du réseau |
| `code` | `VARCHAR(40)` | NON | UK | — | Code stable du réseau (ex. `iad`, `demo`) |
| `name` | `VARCHAR(100)` | NON | UK | — | Libellé affiché (ex. « IAD ») |
| `db_connection_key` | `VARCHAR(60)` | NON | UK | — | Clé logique résolvant la chaîne de connexion de la base réseau via la configuration (jamais la chaîne elle-même) |
| `theme_settings` | `TEXT` | OUI | — | `NULL` | Paramétrage de personnalisation (thème, libellés) au format JSON |
| `is_active` | `TINYINT(1)` | NON | — | `1` | Réseau ouvert à l'inscription si actif et initialisé |
| `is_provisioned` | `TINYINT(1)` | NON | — | `0` | Base réseau initialisée (schéma + données de référence) |
| `created_at` | `DATETIME` | NON | — | `CURRENT_TIMESTAMP` | Date de création (UTC) |
**Index** : `PRIMARY (id)`, `UNIQUE (code)`, `UNIQUE (name)`, `UNIQUE (db_connection_key)`.
**Relations** : `1 — N` vers `accounts.network_id` (même base). Référencé logiquement par toutes les tables des bases réseau.
**Données de référence (seed)** : `iad` (« IAD ») et `demo` (réseau de démonstration / tests).
**Règles** : un réseau n'est ouvert à l'inscription que si `is_active = 1` ET `is_provisioned = 1` (onboarding réseau mené par l'Administrateur au préalable).
---
### Table `accounts`
Comptes des mandataires — **authentification minimale uniquement**. Le profil métier et le parrainage vivent dans la Base Réseau (`mandataire_profiles`). L'`id` est réutilisé comme `account_id` côté réseau.
| Colonne | Type | Null | Clé | Défaut | Description |
|---|---|---|---|---|---|
| `id` | `INT UNSIGNED` | NON | PK | auto | Identifiant du compte ; réutilisé comme `account_id` dans la base réseau |
| `network_id` | `INT UNSIGNED` | NON | FK | — | Réseau de rattachement (non modifiable après création) |
| `email` | `VARCHAR(255)` | NON | UK | — | Adresse e-mail (login), normalisée en minuscules. Unique sur toute la plateforme |
| `password_hash` | `VARCHAR(255)` | NON | — | — | Empreinte du mot de passe (salée) ; jamais en clair |
| `role` | `VARCHAR(20)` | NON | — | `'mandataire'` | Rôle : `mandataire` ou `admin` |
| `is_active` | `TINYINT(1)` | NON | — | `1` | Compte actif (désactivation = `0`) |
| `created_at` | `DATETIME` | NON | — | `CURRENT_TIMESTAMP` | Date de création (UTC) |
**Index** : `PRIMARY (id)`, `UNIQUE (email)`, `INDEX (network_id)`.
**Relations** : `network_id` vers `networks.id` (même base). `id` référencé logiquement par `mandataire_profiles.account_id` dans la base réseau.
**Règles** :
- L'e-mail est unique sur l'ensemble de la plateforme (un e-mail = un seul compte, un seul réseau).
- Un rôle `admin` est un administrateur SchotzimmO (transverse) ; il n'est pas rattaché aux données métier d'un réseau.
- La désactivation d'un compte doit être propagée applicativement vers le profil réseau correspondant.
---
### Table `packs`
Formules d'abonnement. Trois packs ordonnés par niveau, **inclusifs**.
| Colonne | Type | Null | Clé | Défaut | Description |
|---|---|---|---|---|---|
| `id` | `INT UNSIGNED` | NON | PK | auto | Identifiant du pack |
| `pack_key` | `VARCHAR(20)` | NON | UK | — | Clé stable : `starter`, `pro`, `premium` |
| `label` | `VARCHAR(50)` | NON | — | — | Libellé affiché |
| `level` | `TINYINT UNSIGNED` | NON | UK | — | Niveau d'inclusion : `0` < `1` < `2` |
| `is_free` | `TINYINT(1)` | NON | — | `0` | Pack gratuit (vrai pour Starter) |
**Index** : `PRIMARY (id)`, `UNIQUE (pack_key)`, `UNIQUE (level)`.
**Données de référence (seed)** : `starter` (level 0, gratuit), `pro` (level 1), `premium` (level 2).
---
### Table `module_catalog`
Catalogue des modules et pack minimal requis.
| Colonne | Type | Null | Clé | Défaut | Description |
|---|---|---|---|---|---|
| `id` | `INT UNSIGNED` | NON | PK | auto | Identifiant de l'entrée catalogue |
| `module_key` | `VARCHAR(40)` | NON | UK | — | Clé stable du module (ex. `carnet-adresses`, `kms`) |
| `label` | `VARCHAR(80)` | NON | — | — | Libellé affiché |
| `required_pack_id` | `INT UNSIGNED` | NON | FK | — | Pack minimal débloquant le module |
| `is_active` | `TINYINT(1)` | NON | — | `1` | Module proposé/activé sur la plateforme |
**Index** : `PRIMARY (id)`, `UNIQUE (module_key)`, `INDEX (required_pack_id)`.
**Relations** : `required_pack_id` vers `packs.id`.
**Données de référence (seed)** : `carnet-adresses`, `kms`, `facturation` vers `starter` ; `crm`, `agenda` vers `pro`.
---
### Table `subscriptions`
Abonnement d'un compte à un pack. Un seul abonnement actif par compte.
| Colonne | Type | Null | Clé | Défaut | Description |
|---|---|---|---|---|---|
| `id` | `INT UNSIGNED` | NON | PK | auto | Identifiant de l'abonnement |
| `account_id` | `INT UNSIGNED` | NON | FK | — | Compte abonné (même base socle) |
| `pack_id` | `INT UNSIGNED` | NON | FK | — | Pack souscrit |
| `starts_at` | `DATETIME` | NON | — | `CURRENT_TIMESTAMP` | Début d'effet (UTC) |
| `ends_at` | `DATETIME` | OUI | — | `NULL` | Fin d'effet (UTC) ; `NULL` = sans échéance |
| `status` | `VARCHAR(10)` | NON | — | `'actif'` | `actif` ou `expire` |
**Index** : `PRIMARY (id)`, `INDEX (account_id)`, `INDEX (pack_id)`, `INDEX (account_id, status)`.
**Relations** : `account_id` vers `accounts.id` ; `pack_id` vers `packs.id` (même base).
**Règles** : au plus un abonnement `actif` par compte ; rétrogradation conserve les données réseau (consultation seule déduite de la présence de données).
---
### Table `password_resets`
Jetons de réinitialisation de mot de passe, stockés **hachés**, à usage unique et durée de vie limitée.
| Colonne | Type | Null | Clé | Défaut | Description |
|---|---|---|---|---|---|
| `id` | `INT UNSIGNED` | NON | PK | auto | Identifiant du jeton |
| `account_id` | `INT UNSIGNED` | NON | FK | — | Compte concerné |
| `token_hash` | `VARCHAR(255)` | NON | — | — | Empreinte du jeton |
| `expires_at` | `DATETIME` | NON | — | — | Échéance d'expiration (UTC) |
| `used` | `TINYINT(1)` | NON | — | `0` | Jeton déjà consommé |
| `created_at` | `DATETIME` | NON | — | `CURRENT_TIMESTAMP` | Date de création (UTC) |
**Index** : `PRIMARY (id)`, `INDEX (account_id)`, `INDEX (token_hash)`.
**Relations** : `account_id` vers `accounts.id`.
---
### Table `network_usage_kpis`
Indicateurs d'usage **remontés par chaque réseau** vers le socle pour les tableaux de bord d'administration. Contient des **agrégats non nominatifs** (jamais de donnée métier individuelle ; pas de jointure inter-bases).
| Colonne | Type | Null | Clé | Défaut | Description |
|---|---|---|---|---|---|
| `id` | `INT UNSIGNED` | NON | PK | auto | Identifiant de la mesure |
| `network_id` | `INT UNSIGNED` | NON | FK | — | Réseau concerné |
| `metric_key` | `VARCHAR(60)` | NON | — | — | Clé de l'indicateur (ex. `nb_mandataires_actifs`, `ca_total`) |
| `metric_value` | `DECIMAL(14,2)` | NON | — | — | Valeur agrégée |
| `period` | `VARCHAR(7)` | OUI | — | `NULL` | Période éventuelle au format `AAAA-MM` |
| `collected_at` | `DATETIME` | NON | — | `CURRENT_TIMESTAMP` | Date de remontée (UTC) |
**Index** : `PRIMARY (id)`, `INDEX (network_id)`, `INDEX (network_id, metric_key, period)`.
**Relations** : `network_id` vers `networks.id`.
**Règles** : alimentée par un processus de remontée par réseau ; ne contient aucune donnée individuelle de mandataire.
---
## Diagramme physique — Base Socle
```mermaid
erDiagram
networks ||--o{ accounts : "network_id"
networks ||--o{ network_usage_kpis : "network_id"
accounts ||--|| subscriptions : "account_id"
packs ||--o{ subscriptions : "pack_id"
packs ||--o{ module_catalog : "required_pack_id"
accounts ||--o{ password_resets : "account_id"
```
---
# PARTIE B — Base Réseau (une par réseau)
Référence : `socle-plateforme/design.md`. Chaque réseau dispose d'une instance de ce schéma. Toutes les tables sont **implicitement cloisonnées** : la base entière appartient à un seul réseau, il n'y a donc **pas** de colonne `network_id` dans les tables réseau (le cloisonnement est physique). Les identifiants `account_id` proviennent de la Base Socle (`FK*`, référence logique).
Vue d'ensemble (Socle réseau ; les Modules ajouteront leurs tables) :
| Table | Rôle |
|---|---|
| `mandataire_profiles` | Profil métier du mandataire ; pivot du parrainage (auto-référence `sponsor_account_id`) |
| `declared_members` | Membres déclarés (parrain/filleul) non encore inscrits |
| `affiliation_requests` | Demandes de confirmation d'affiliation entre mandataires inscrits |
---
### Table `mandataire_profiles`
Profil métier d'un mandataire au sein de son réseau. La clé primaire **réutilise l'`account_id`** de la Base Socle (pas d'auto-incrément). Porte l'**arbre de parrainage** via l'auto-référence `sponsor_account_id`.
| Colonne | Type | Null | Clé | Défaut | Description |
|---|---|---|---|---|---|
| `account_id` | `INT UNSIGNED` | NON | PK, FK* | — | Identifiant du compte (provient de `accounts.id` du socle) |
| `first_name` | `VARCHAR(100)` | NON | — | — | Prénom |
| `last_name` | `VARCHAR(100)` | NON | — | — | Nom |
| `network_member_id` | `VARCHAR(50)` | NON | — | — | Identifiant du mandataire propre à son réseau (obligatoire) |
| `legal_status` | `VARCHAR(30)` | OUI | — | `NULL` | Statut juridique (ex. `auto-entrepreneur`, `eirl`) |
| `siren` | `VARCHAR(14)` | OUI | — | `NULL` | Numéro SIREN/RSAC, optionnel |
| `sponsor_account_id` | `INT UNSIGNED` | OUI | FK | `NULL` | Parrain (auto-référence vers `mandataire_profiles.account_id`) ; `NULL` = racine |
| `created_at` | `DATETIME` | NON | — | `CURRENT_TIMESTAMP` | Date de création (UTC) |
**Index** : `PRIMARY (account_id)`, `UNIQUE (network_member_id)`, `INDEX (sponsor_account_id)`.
**Relations** :
- `account_id` : référence logique vers `accounts.id` (Base Socle), non contrainte en SQL.
- `sponsor_account_id` vers `mandataire_profiles.account_id` (auto-référence, même base réseau) : **vraie FK**, intégrité du parrainage garantie par la base.
**Règles** :
- Comme la base est propre au réseau, parrain et filleul sont **nécessairement** du même réseau (cloisonnement physique).
- Absence de cycle vérifiée applicativement à l'écriture (parcours ascendant).
- Au plus un `sponsor_account_id` validé par mandataire.
---
### Table `declared_members`
Membres déclarés dans l'arbre de parrainage mais **non encore inscrits**. Sans donnée métier. Convertis en `mandataire_profiles` lors du Rapprochement_Email à l'inscription (le compte étant alors créé côté socle).
| Colonne | Type | Null | Clé | Défaut | Description |
|---|---|---|---|---|---|
| `id` | `INT UNSIGNED` | NON | PK | auto | Identifiant du membre déclaré |
| `email` | `VARCHAR(255)` | NON | — | — | E-mail normalisé, clé de Rapprochement_Email |
| `first_name` | `VARCHAR(100)` | OUI | — | `NULL` | Prénom déclaré (si connu) |
| `last_name` | `VARCHAR(100)` | OUI | — | `NULL` | Nom déclaré (si connu) |
| `declared_by_account_id` | `INT UNSIGNED` | NON | FK | — | Mandataire ayant déclaré (référence `mandataire_profiles.account_id`) |
| `relation` | `VARCHAR(10)` | NON | — | — | Lien déclaré : `parrain` ou `filleul` |
| `status` | `VARCHAR(10)` | NON | — | `'declare'` | `declare` (supposé) ou `invite` (invitation envoyée) |
| `created_at` | `DATETIME` | NON | — | `CURRENT_TIMESTAMP` | Date de création (UTC) |
**Index** : `PRIMARY (id)`, `INDEX (declared_by_account_id)`, `INDEX (email)`.
**Relations** : `declared_by_account_id` vers `mandataire_profiles.account_id` (même base).
**Règles** : clôturé/supprimé lors de la conversion en `mandataire_profiles` par Rapprochement_Email.
---
### Table `affiliation_requests`
Demandes de **confirmation d'affiliation** entre deux mandataires déjà inscrits (cas de « perte de lien »).
| Colonne | Type | Null | Clé | Défaut | Description |
|---|---|---|---|---|---|
| `id` | `INT UNSIGNED` | NON | PK | auto | Identifiant de la demande |
| `requester_account_id` | `INT UNSIGNED` | NON | FK | — | Mandataire initiant la demande |
| `target_account_id` | `INT UNSIGNED` | NON | FK | — | Mandataire dont l'approbation est requise |
| `proposed_relation` | `VARCHAR(10)` | NON | — | — | Lien proposé : `parrain` ou `filleul` |
| `status` | `VARCHAR(12)` | NON | — | `'en_attente'` | `en_attente`, `approuvee` ou `refusee` |
| `created_at` | `DATETIME` | NON | — | `CURRENT_TIMESTAMP` | Date de création (UTC) |
| `resolved_at` | `DATETIME` | OUI | — | `NULL` | Date d'approbation/refus (UTC) |
**Index** : `PRIMARY (id)`, `INDEX (requester_account_id)`, `INDEX (target_account_id)`, `INDEX (status)`.
**Relations** : `requester_account_id` et `target_account_id` vers `mandataire_profiles.account_id` (même base).
**Règles** : l'approbation ne doit pas créer de cycle (vérifié à la résolution). Le cloisonnement réseau est garanti par construction (base propre au réseau).
---
## Diagramme physique — Base Réseau (socle réseau)
```mermaid
erDiagram
mandataire_profiles ||--o| mandataire_profiles : "sponsor_account_id"
mandataire_profiles ||--o{ declared_members : "declared_by_account_id"
mandataire_profiles ||--o{ affiliation_requests : "requester / target"
```
> **Note inter-bases** : `mandataire_profiles.account_id` correspond à `accounts.id` de la Base Socle (référence logique `FK*`, jamais une FK SQL ni une jointure traversant deux bases).
---
## Sections à venir (ajoutées au fil des Modules — tables de la Base Réseau)
- **Carnet d'adresses** *(à venir)*
- **KMS** *(à venir)*
- **CRM** *(à venir)*
- **Agenda** *(à venir)*
- **Facturation** *(à venir)*
+1
View File
@@ -0,0 +1 @@
{"specId": "8d383a0b-2d6f-42b2-80cc-975c7db17b09", "workflowType": "requirements-first", "specType": "feature"}
+174
View File
@@ -0,0 +1,174 @@
# Requirements Document
**Spec : Site Vitrine SchotzimmO**
## Introduction
Le **Site Vitrine** est le site public de **SchotzimmO**, la plateforme SaaS multi-réseaux destinée aux mandataires immobiliers. Accessible sans authentification sur le domaine **schotzimmo.fr**, il constitue la porte d'entrée commerciale de la Plateforme : il présente l'offre (modules, packs tarifaires, réseaux de mandataire immobilier pris en charge), permet d'essayer une démonstration et oriente le visiteur vers la connexion de l'App.
Le Site Vitrine se présente comme une **page vitrine unique** (one-page, de type tunnel de vente) à défilement et navigation par ancres, surmontée d'un **en-tête persistant** (logo + bouton Connexion) et complétée d'un **pied de page** donnant accès à des **pages séparées** : Contact, Mentions légales, CGU et Politique de confidentialité (RGPD).
Le Site Vitrine ne contient **aucun parcours d'inscription** ni aucun mécanisme d'authentification propre : l'inscription et l'authentification relèvent du socle et de l'App. Le Site Vitrine se contente de **rediriger** le visiteur vers la page d'authentification de l'App (cf. `socle-plateforme`, Requirement 2 — Authentification et session) et vers la **connexion démo** sur le réseau fictif SchotzimmO (cf. `socle-plateforme`, Requirements 2 et 3). Le Site Vitrine ne persiste aucune donnée métier ; le Formulaire_Contact envoie simplement un e-mail via le service d'envoi déjà prévu par la Plateforme.
> **Note** : Ce document exprime le besoin **fonctionnel métier** (attendu fonctionnel, structure du site, contenus et règles de gestion) fourni par le Product Owner business. Les exigences **techniques** dérivées (stack frontend/backend commune à l'App, sous-dossier `Site/`, intégration du service d'envoi d'e-mails, rendu responsive, SEO, redirections d'URL) seront rassemblées dans le document `design-requirements.md`, organisé **en miroir** (même numérotation et même titre, suivis de « — aspects techniques »), à préciser conjointement par le PO business et le PO IT. Chaque exigence renvoie, lorsque pertinent, à son pendant technique dans `design-requirements.md`.
---
## Glossary
- **Site_Vitrine** : Le site public de SchotzimmO, accessible sans authentification sur le domaine schotzimmo.fr, présentant l'offre et orientant le visiteur vers l'App. Distinct de l'App (la Plateforme authentifiée décrite par la spec `socle-plateforme` et les specs Modules).
- **App** : La Plateforme authentifiée SchotzimmO (frontend et backend applicatifs), accessible après connexion, décrite par les specs `socle-plateforme` et Modules.
- **Visiteur** : Toute personne consultant le Site_Vitrine, sans authentification ni Compte requis.
- **Page_Vitrine** : La page unique (one-page) du Site_Vitrine, à défilement vertical et navigation par ancres, regroupant dans un ordre défini l'ensemble des Sections commerciales.
- **Section** : Un bloc de contenu de la Page_Vitrine, repérable par une ancre et atteignable depuis la navigation. Les Sections de la Page_Vitrine sont, dans l'ordre : Hero, Fonctionnalités, Tarifs, Réseaux pris en charge, Démo.
- **Section_Hero** : La première Section de la Page_Vitrine : accroche et présentation générale de SchotzimmO.
- **Section_Fonctionnalités** : La Section présentant les Modules de la Plateforme (Carnet d'adresses, KMS, CRM, Agenda, Facturation).
- **Section_Tarifs** : La Section présentant les trois Packs d'abonnement (Starter gratuit, Pro, Premium) et les Modules inclus dans chacun.
- **Section_Réseaux** : La Section présentant les Réseaux_Pris_En_Charge (IAD au lancement).
- **Section_Démo** : La Section invitant à essayer la démonstration, comportant le Bouton_Démo.
- **En_Tête** : La barre persistante affichée en haut du Site_Vitrine, contenant le logo SchotzimmO et le Bouton_Connexion, visible sur toutes les pages et sections.
- **Pied_De_Page** : La zone de bas de page du Site_Vitrine donnant accès aux Pages_Légales et à la page Contact.
- **Logo** : La marque visuelle SchotzimmO affichée dans l'En_Tête ; un clic sur le Logo ramène en haut de la Page_Vitrine.
- **Bouton_Connexion** : Le contrôle de l'En_Tête redirigeant le Visiteur vers la page d'authentification de l'App (cf. `socle-plateforme`, Requirement 2).
- **Bouton_Démo** : Le contrôle de la Section_Démo redirigeant le Visiteur vers la connexion démo de l'App sur le Réseau_Fictif (cf. `socle-plateforme`, Requirements 2 et 3).
- **Démo** : La démonstration de l'App ouverte au Visiteur, s'appuyant sur le Réseau_Fictif ; disponible en environnement de production et utilisée pour les tests en environnement de développement.
- **Réseau_Fictif** : Le réseau de démonstration SchotzimmO (réseau fictif) servant de support à la Démo (cf. `socle-plateforme`, Requirement 3 — Réseaux).
- **Module** : Une unité fonctionnelle de la Plateforme présentée dans la Section_Fonctionnalités (Carnet_Adresses, KMS, CRM, Agenda, Facturation). Défini au socle (cf. `socle-plateforme`).
- **Pack** : Une formule d'abonnement présentée dans la Section_Tarifs. Trois Packs : Starter (gratuit), Pro, Premium (cf. `socle-plateforme`, Requirement 7).
- **Réseau_Pris_En_Charge** : Un réseau de mandataire immobilier dont les mandataires peuvent utiliser SchotzimmO (IAD au lancement). Le terme « réseau de mandataire immobilier pris en charge » est employé plutôt qu'« affilié », aucun lien contractuel ne liant SchotzimmO à ces réseaux.
- **Formulaire_Contact** : Le formulaire de la page Contact permettant au Visiteur d'adresser un message (notamment pour signaler un nouveau réseau à implémenter), envoyé par e-mail sans aucune persistance.
- **Email_Contact** : L'e-mail engendré par la soumission du Formulaire_Contact, acheminé vers l'adresse de contact de l'exploitant via le service d'envoi d'e-mails de la Plateforme.
- **Page_Contact** : La page séparée (hors Page_Vitrine) hébergeant le Formulaire_Contact.
- **Pages_Légales** : L'ensemble des pages séparées à vocation légale et réglementaire : Mentions légales, CGU (Conditions Générales d'Utilisation) et Politique de confidentialité (RGPD).
- **Navigation_Ancres** : Le mécanisme de navigation interne à la Page_Vitrine amenant, par défilement, de l'En_Tête ou d'un menu vers la Section visée.
---
## Requirements
### Requirement 1: En-tête et accès à la connexion
**User Story:** En tant que visiteur, je veux disposer d'un en-tête persistant avec le logo SchotzimmO et un bouton de connexion, afin d'identifier le service et d'accéder à tout moment à la connexion de l'App.
> Exigences techniques associées (rendu de l'En_Tête, URL d'authentification de l'App, redirection) : `design-requirements.md`, Requirement 1.
#### Acceptance Criteria
1. THE Site_Vitrine SHALL afficher un En_Tête contenant le Logo SchotzimmO et le Bouton_Connexion sur la Page_Vitrine, sur chaque Page_Légale et sur la Page_Contact.
2. WHEN le Visiteur sélectionne le Bouton_Connexion, THE Site_Vitrine SHALL rediriger le Visiteur vers la page d'authentification de l'App.
3. WHEN le Visiteur sélectionne le Logo depuis la Page_Vitrine, THE Site_Vitrine SHALL ramener le Visiteur en haut de la Page_Vitrine.
4. WHILE le Visiteur consulte la Page_Vitrine, une Page_Légale ou la Page_Contact, THE Site_Vitrine SHALL maintenir l'En_Tête visible pendant toute la durée du défilement vertical de la page.
5. WHEN le Visiteur sélectionne le Logo depuis une Page_Légale ou depuis la Page_Contact, THE Site_Vitrine SHALL afficher la Page_Vitrine et positionner le Visiteur en haut de celle-ci.
---
### Requirement 2: Page vitrine unique et navigation
**User Story:** En tant que visiteur, je veux parcourir une page unique claire et ordonnée, consultable sur mobile et en français, afin de découvrir l'offre SchotzimmO sans friction.
> Exigences techniques associées (one-page, ancres, responsive, langue, rendu du Pied_De_Page) : `design-requirements.md`, Requirement 2.
#### Acceptance Criteria
1. THE Site_Vitrine SHALL présenter une Page_Vitrine unique regroupant, dans cet ordre et sans qu'aucune autre Section ne s'intercale, la Section_Hero, la Section_Fonctionnalités, la Section_Tarifs, la Section_Réseaux puis la Section_Démo.
2. THE Site_Vitrine SHALL afficher dans l'En_Tête une Navigation_Ancres comportant un élément cliquable distinct pour chacune des cinq Sections (Section_Hero, Section_Fonctionnalités, Section_Tarifs, Section_Réseaux, Section_Démo).
3. WHEN le Visiteur sélectionne un élément de la Navigation_Ancres, THE Site_Vitrine SHALL faire défiler la Page_Vitrine jusqu'à ce que le haut de la Section correspondante soit visible dans la zone d'affichage, sans recharger la Page_Vitrine.
4. THE Site_Vitrine SHALL afficher un Pied_De_Page contenant quatre liens cliquables distincts donnant accès respectivement à la Page_Contact, aux Mentions légales, aux CGU et à la Politique de confidentialité.
5. THE Site_Vitrine SHALL présenter l'intégralité de ses contenus textuels visibles en langue française uniquement, sans aucun contenu rédigé dans une autre langue.
6. WHERE le Site_Vitrine est consulté depuis un terminal mobile dont la largeur d'écran est inférieure ou égale à 768 pixels, THE Site_Vitrine SHALL adapter sa présentation à la largeur d'écran sans défilement horizontal, tout en conservant l'ordre des cinq Sections ainsi que l'accès à l'En_Tête et au Pied_De_Page.
7. IF le Visiteur sélectionne un élément de la Navigation_Ancres dont la Section cible est absente ou inaccessible, THEN THE Site_Vitrine SHALL maintenir l'affichage courant de la Page_Vitrine sans la recharger.
---
### Requirement 3: Section Fonctionnalités
**User Story:** En tant que visiteur, je veux comprendre les modules proposés par SchotzimmO, afin d'évaluer si la plateforme répond à mes besoins de mandataire.
> Exigences techniques associées (contenu éditorial des Modules, rendu de la Section) : `design-requirements.md`, Requirement 3.
#### Acceptance Criteria
1. THE Section_Fonctionnalités SHALL présenter exactement les cinq Modules suivants, sans en omettre ni en ajouter : Carnet d'adresses, KMS, CRM, Agenda et Facturation.
2. THE Section_Fonctionnalités SHALL associer à chacun des cinq Modules présentés une description distincte de son rôle fonctionnel du point de vue du mandataire, de sorte qu'aucun Module ne reste sans description.
3. THE Section_Fonctionnalités SHALL désigner chaque Module par la dénomination exacte définie au socle (Carnet_Adresses, KMS, CRM, Agenda, Facturation) (cf. `socle-plateforme`).
---
### Requirement 4: Section Tarifs
**User Story:** En tant que visiteur, je veux consulter les formules tarifaires et les modules inclus, afin de choisir le pack adapté à mon activité.
> Exigences techniques associées (contenu éditorial des Packs, rendu de la Section) : `design-requirements.md`, Requirement 4.
#### Acceptance Criteria
1. THE Section_Tarifs SHALL présenter exactement trois Packs, dans cet ordre : Starter (gratuit), Pro puis Premium.
2. THE Section_Tarifs SHALL indiquer, pour chaque Pack, la liste complète des Modules inclus en employant la dénomination des Modules définie au socle (cf. `socle-plateforme`, Requirement 8).
3. THE Section_Tarifs SHALL distinguer visuellement, pour chaque Pack, les Modules inclus des Modules non inclus.
4. THE Section_Tarifs SHALL afficher explicitement, pour le Pack Starter, la mention de sa gratuité et l'absence de coût d'abonnement.
5. THE Section_Tarifs SHALL présenter chaque Pack comme incluant l'ensemble des Modules des Packs de niveau inférieur (le Pack Pro inclut les Modules du Pack Starter ; le Pack Premium inclut les Modules des Packs Pro et Starter), de manière cohérente avec les Packs définis au socle (cf. `socle-plateforme`, Requirements 7 et 8).
---
### Requirement 5: Section Réseaux de mandataire immobilier pris en charge
**User Story:** En tant que visiteur mandataire, je veux savoir quels réseaux de mandataire immobilier sont pris en charge, afin de vérifier que SchotzimmO est compatible avec mon réseau.
> Exigences techniques associées (contenu éditorial des réseaux, rendu de la Section) : `design-requirements.md`, Requirement 5.
#### Acceptance Criteria
1. THE Section_Réseaux SHALL présenter chaque Réseau_Pris_En_Charge de façon identifiable par son nom, incluant le réseau IAD nommé au lancement.
2. THE Section_Réseaux SHALL employer la dénomination « réseaux de mandataire immobilier pris en charge » pour désigner les Réseaux_Pris_En_Charge.
3. THE Section_Réseaux SHALL n'employer en aucun endroit le terme « affiliés » pour désigner les Réseaux_Pris_En_Charge.
4. WHERE un Visiteur souhaite signaler un réseau non encore pris en charge, THE Section_Réseaux SHALL afficher un renvoi explicite vers la Page_Contact.
5. WHEN le Visiteur sélectionne le renvoi vers la Page_Contact depuis la Section_Réseaux, THE Site_Vitrine SHALL afficher la Page_Contact.
---
### Requirement 6: Section Essayer la démo
**User Story:** En tant que visiteur, je veux essayer une démonstration de l'App, afin de me faire une idée concrète avant de m'engager.
> Exigences techniques associées (URL de connexion démo, Réseau_Fictif, environnements prod/dev) : `design-requirements.md`, Requirement 6.
#### Acceptance Criteria
1. WHILE la Section_Démo est affichée, THE Site_Vitrine SHALL afficher un Bouton_Démo portant un libellé invitant le Visiteur à essayer la Démo.
2. WHEN le Visiteur sélectionne le Bouton_Démo, THE Site_Vitrine SHALL rediriger le Visiteur vers la connexion démo de l'App sur le Réseau_Fictif SchotzimmO.
3. IF la redirection vers la connexion démo de l'App ne peut pas aboutir après la sélection du Bouton_Démo, THEN THE Site_Vitrine SHALL maintenir le Visiteur sur la Section_Démo et afficher un message d'erreur indiquant que la Démo est momentanément indisponible.
4. THE Section_Démo SHALL afficher un message visible indiquant que la Démo s'appuie sur le Réseau_Fictif SchotzimmO et ne mobilise aucune donnée réelle de Réseau_Pris_En_Charge.
---
### Requirement 7: Contact par e-mail
**User Story:** En tant que visiteur, je veux adresser un message à l'exploitant SchotzimmO (notamment pour signaler un nouveau réseau à implémenter), afin d'obtenir une réponse par e-mail, sans qu'aucune donnée ne soit conservée.
> Exigences techniques associées (service d'envoi d'e-mails, absence de persistance, validation des champs) : `design-requirements.md`, Requirement 7.
#### Acceptance Criteria
1. THE Page_Contact SHALL présenter un Formulaire_Contact comportant exactement trois champs obligatoires : le nom (1 à 100 caractères), l'adresse e-mail (1 à 254 caractères) et le message (1 à 2000 caractères) du Visiteur.
2. WHEN le Visiteur soumet le Formulaire_Contact avec les trois champs obligatoires renseignés et valides, THE Site_Vitrine SHALL engendrer un Email_Contact contenant le nom, l'adresse e-mail et le message saisis, à destination de l'adresse de contact de l'exploitant.
3. WHEN l'Email_Contact a été engendré avec succès, THE Site_Vitrine SHALL afficher au Visiteur un message de confirmation d'envoi distinct du Formulaire_Contact.
4. THE Site_Vitrine SHALL n'enregistrer aucune donnée du Formulaire_Contact de manière persistante, l'unique traitement des données saisies étant leur transmission dans l'Email_Contact.
5. IF un champ obligatoire du Formulaire_Contact est absent, vide ou dépasse sa longueur maximale autorisée, THEN THE Site_Vitrine SHALL afficher un message d'erreur identifiant chaque champ en défaut, conserver la saisie en cours et n'engendrer aucun Email_Contact.
6. IF l'adresse e-mail saisie ne contient pas un identifiant local suivi d'un caractère « @ » puis d'un nom de domaine comportant au moins un point, THEN THE Site_Vitrine SHALL refuser l'envoi, signaler que l'adresse e-mail est invalide et conserver la saisie en cours.
7. IF l'envoi de l'Email_Contact échoue, THEN THE Site_Vitrine SHALL informer le Visiteur de l'échec de l'envoi, l'inviter à réessayer et conserver l'intégralité de la saisie en cours.
---
### Requirement 8: Pages légales
**User Story:** En tant que visiteur, je veux accéder aux mentions légales, aux CGU et à la politique de confidentialité, afin de connaître mes droits et les conditions d'utilisation du service.
> Exigences techniques associées (pages séparées, routes dédiées, rendu) : `design-requirements.md`, Requirement 8.
#### Acceptance Criteria
1. THE Site_Vitrine SHALL fournir trois Pages_Légales distinctes et indépendantes de la Page_Vitrine : une page pour les Mentions légales, une page pour les CGU et une page pour la Politique de confidentialité (RGPD).
2. WHEN le Visiteur sélectionne un lien de Pied_De_Page vers une Page_Légale, THE Site_Vitrine SHALL afficher la Page_Légale correspondante sur une page séparée de la Page_Vitrine.
3. IF une Page_Légale sélectionnée est indisponible, THEN THE Site_Vitrine SHALL maintenir le Visiteur sur une page comportant le Pied_De_Page et afficher un message indiquant que la page demandée est momentanément indisponible.
4. THE Politique de confidentialité SHALL décrire au minimum la finalité du traitement des données du Formulaire_Contact, leur transmission par e-mail uniquement et l'absence de toute persistance de ces données.
5. THE Site_Vitrine SHALL présenter dans le Pied_De_Page de chaque page (Page_Vitrine, Page_Contact et chaque Page_Légale) un lien actif vers chacune des trois Pages_Légales.
@@ -0,0 +1,169 @@
# Design Requirements — Socle Plateforme SchotzimmO (aspects techniques)
## Introduction
Ce document rassemble les exigences **techniques** dérivées du besoin fonctionnel métier décrit dans `requirements.md`. Il est organisé **en miroir** : chaque section reprend le même numéro et le même titre que `requirements.md`, suivi de « — aspects techniques ». Seules les exigences comportant effectivement des critères techniques y figurent.
Ces exigences sont arbitrées conjointement par le PO business et le PO IT. Les choix d'architecture, modèles de données et contrats d'API détaillés sont, eux, décrits dans `design.md`.
> Les mots-clés EARS (WHEN / IF / WHILE / WHERE / THE ... SHALL) sont conservés. Les termes techniques, identifiants, codes HTTP, en-têtes et noms d'API restent en anglais ou sous leur forme littérale.
---
## Glossary (technique)
- **Base_Socle** : La base MariaDB centrale et unique portant l'authentification, le référentiel des Reseaux et leurs informations de connexion, les abonnements, le catalogue des Modules/Packs et les KPI d'usage par Reseau. Aucune donnée métier produite par les Mandataires.
- **Base_Reseau** : La base MariaDB propre à un Reseau, portant toutes ses données métier (profil mandataire, parrainage, données des Modules). Une Base_Reseau par Reseau.
- **Reference_Logique** : Un identifiant (`AccountId`) généré par la Base_Socle et réutilisé dans une Base_Reseau sans contrainte FK SQL ni jointure inter-bases ; la cohérence est assurée applicativement.
- **NetworkConnectionResolver** : Le composant qui, à partir du claim `network` du JWT, résout la clé logique (`db_connection_key`) du Reseau vers la chaîne de connexion de sa Base_Reseau fournie par configuration.
- **JWT** : *JSON Web Token* signé émis à la connexion, portant l'identité du Mandataire (`sub` = AccountId), son Reseau (`network`), son rôle (`role` ∈ { `mandataire`, `admin` }) et une échéance (`exp`).
- **Claim** : Une assertion portée par le JWT (identité, Reseau, rôle), utilisée côté Backend pour le contrôle d'accès.
- **Access_Token** : Le JWT d'accès transmis par le Frontend dans l'en-tête `Authorization: Bearer <token>` à chaque appel protégé.
- **Hash_Mot_De_Passe** : L'empreinte du Mot_De_Passe calculée avec un algorithme de hachage adapté aux mots de passe (ex. PBKDF2/BCrypt/Argon2), avec sel ; le Mot_De_Passe n'est jamais stocké en clair ni journalisé.
- **SMTP** : Le service d'envoi d'e-mails (invitations, réinitialisation). Configurable ; un `Smtp:Host` vide active un repli de journalisation du lien plutôt qu'un envoi.
- **Config_BDD** : Les chaînes de connexion MariaDB/MySQL (Base_Socle et chaque Base_Reseau), fournies par configuration (variables d'environnement), pointant soit vers des instances conteneurisées, soit vers un serveur externe. La Base_Socle ne stocke qu'une clé logique par Reseau, jamais la chaîne elle-même.
- **Migration** : Une migration de schéma gérée par l'ORM (EF Core), appliquée de façon idempotente au démarrage ou via commande.
- **DTO** : *Data Transfer Object*, objet de transport exposé par l'API, distinct des entités persistées.
- **Code_HTTP** : Le statut HTTP retourné par l'API (ex. 200, 201, 400, 401, 403, 404, 409, 422, 500).
- **Portee_Hierarchique** : Le périmètre de données qu'un appel peut retourner compte tenu de la Visibilite_Descendante (soi-même, Descendance agrégée, Filleuls_Directs en détail).
---
## Requirements (aspects techniques)
### Requirement 1: Inscription d'un Mandataire — aspects techniques
1. THE Backend SHALL exposer l'inscription via `POST /api/auth/register` acceptant un DTO { email, password, firstName, lastName, networkId, networkMemberId, legalStatus?, siren?, referralToken? }.
2. WHEN l'inscription réussit, THE Backend SHALL répondre `201 Created` et ne jamais retourner le Hash_Mot_De_Passe ni aucune donnée de sécurité.
3. IF l'Adresse_Email est déjà utilisée, THEN THE Backend SHALL répondre `409 Conflict` avec un message générique ne révélant aucune donnée du Compte existant.
4. IF le DTO est invalide (champ obligatoire manquant, email mal formé, password < 8 caractères, siren au format incorrect), THEN THE Backend SHALL répondre `422 Unprocessable Entity` avec le détail des champs en erreur.
5. THE Backend SHALL stocker le Mot_De_Passe uniquement sous forme de Hash_Mot_De_Passe salé ; en aucun cas en clair.
6. THE Backend SHALL valider l'Adresse_Email par une expression de forme standard et normaliser sa casse (minuscule) avant unicité et Rapprochement_Email.
7. WHEN un `referralToken` valide et non expiré est fourni, THE Backend SHALL en extraire `networkId` et l'identité du Parrain et ignorer tout `networkId` divergent fourni par le client.
8. IF le `referralToken` est fourni mais invalide ou expiré, THEN THE Backend SHALL répondre `400 Bad Request` et signaler que le lien d'invitation n'est plus valide.
---
### Requirement 2: Authentification et session — aspects techniques
1. THE Backend SHALL exposer `POST /api/auth/login` { email, password, rememberMe? } et retourner, en cas de succès, un Access_Token (JWT) et son échéance.
2. THE Backend SHALL signer le JWT avec une clé secrète fournie par configuration (`JWT:SecretKey`), échouer au démarrage si la clé est absente, et appliquer une durée de validité configurable (`JWT:ExpirationMinutes`, défaut 720).
3. THE JWT SHALL porter les claims `sub` (AccountId), `network` (NetworkId), `role` et `exp`.
4. WHEN les identifiants sont invalides, THE Backend SHALL répondre `401 Unauthorized` avec un message générique, sans distinguer email inconnu et mot de passe erroné, et SHALL appliquer une temporisation ou limitation de débit contre le forçage.
5. THE Backend SHALL protéger toutes les routes métier par une authentification Bearer et répondre `401 Unauthorized` en l'absence d'un Access_Token valide.
6. THE Frontend SHALL transmettre l'Access_Token dans l'en-tête `Authorization: Bearer <token>` et effacer le token local à la déconnexion.
7. THE Frontend SHALL stocker l'Identifiant_Mémorisé dans le stockage local du navigateur et ne jamais y stocker le Mot_De_Passe ni l'Access_Token au-delà de la politique de session définie.
8. THE Backend SHALL exposer `POST /api/auth/forgot-password` { email } répondant toujours `200 OK` quel que soit l'existence de l'Adresse_Email (anti-énumération), et générer le Jeton_Reinitialisation uniquement si le Compte existe.
9. THE Backend SHALL générer un Jeton_Reinitialisation cryptographiquement aléatoire, à usage unique, de durée de vie configurable (`Auth:ResetTokenLifetimeMinutes`, défaut 60), stocké sous forme hachée.
10. THE Backend SHALL exposer `POST /api/auth/reset-password` { token, newPassword } répondant `200 OK` en cas de succès, `400 Bad Request` si le token est invalide, expiré ou déjà consommé, et `422` si le nouveau mot de passe est trop court.
11. THE Backend SHALL construire les liens d'e-mail à partir d'une base d'URL configurable (`Auth:FrontendBaseUrl`).
---
### Requirement 3: Réseaux — aspects techniques
1. THE Backend SHALL persister dans la Base_Socle un référentiel `Networks` (code, libellé, `db_connection_key`, `theme_settings`, `is_active`, `is_provisioned`) et exposer `GET /api/networks` (Reseaux actifs et provisionnés) accessible sans authentification pour alimenter l'inscription.
2. THE Backend SHALL stocker toutes les données métier d'un Reseau dans sa Base_Reseau dédiée ; les tables de Base_Reseau ne portent pas de colonne `NetworkId` (le cloisonnement est physique).
3. THE NetworkConnectionResolver SHALL déterminer la Base_Reseau de chaque requête métier à partir du claim `network` du JWT, indépendamment de tout paramètre fourni par le client.
4. IF le claim `network` ne correspond à aucun Reseau actif et provisionné, THEN THE Backend SHALL répondre `403 Forbidden` et n'ouvrir aucune connexion.
5. WHEN l'Administrateur réalise l'Onboarding_Reseau, THE Backend SHALL créer/initialiser la Base_Reseau (schéma + données de référence) via `POST /api/admin/networks`, enregistrer `db_connection_key` et `theme_settings`, puis positionner `is_provisioned = 1`.
6. IF la Base_Reseau d'un Reseau est indisponible ou non provisionnée, THEN THE Backend SHALL répondre `503 Service Unavailable` sans exposer de détail d'infrastructure.
7. THE Backend SHALL fournir au Frontend la Personnalisation_Reseau (`theme_settings`) du Reseau courant pour l'application du thème et des libellés.
---
### Requirement 4: Parrainage et hiérarchie — aspects techniques
1. THE Backend SHALL modéliser l'Arbre_Parrainage dans la Base_Reseau par une relation d'auto-référence `mandataire_profiles.sponsor_account_id` (vraie FK intra-base), et un état d'Affiliation (`supposee`, `validee`).
2. THE Backend SHALL modéliser le Membre_Declare dans la Base_Reseau comme une entité porteuse d'Adresse_Email normalisée, lien hiérarchique et statut, sans credentials ni données métier (le cloisonnement Reseau étant physique, pas de `NetworkId`).
3. THE Backend SHALL exposer `POST /api/referrals/declare` (déclarer un Parrain ou des Filleuls) et `POST /api/referrals/{id}/invite` (envoyer l'Invitation par SMTP).
4. WHEN une Invitation est envoyée, THE Backend SHALL générer un Lien_Parrainage signé (token portant `networkId` et identité du Parrain), de durée de vie configurable, et l'inclure dans l'e-mail via `Auth:FrontendBaseUrl`.
5. WHEN `Smtp:Host` est vide, THE Backend SHALL journaliser le Lien_Parrainage et le lien de réinitialisation au lieu de les envoyer, pour les environnements sans SMTP.
6. THE Backend SHALL effectuer le Rapprochement_Email sur l'Adresse_Email normalisée (casse ignorée) lors de toute inscription, et convertir le Membre_Declare (Base_Reseau) en profil mandataire lié au Compte créé (Base_Socle), en coordonnant les deux bases avec compensation en cas d'échec partiel.
7. IF une déclaration introduit un cycle (le descendant deviendrait ancêtre), THEN THE Backend SHALL répondre `409 Conflict` sans persister.
8. THE Backend SHALL exposer `POST /api/referrals/confirmations` et `POST /api/referrals/confirmations/{id}/approve|reject` pour la Confirmation_Affiliation entre Comptes déjà inscrits, et répondre `403 Forbidden` si l'appelant n'est pas la partie concernée.
9. THE Backend SHALL garantir, par contrainte applicative, au plus un `sponsor_account_id` validé par mandataire à un instant donné.
---
### Requirement 5: Visibilité hiérarchique descendante — aspects techniques
1. THE Backend SHALL dériver la Portee_Hierarchique d'un appel à partir du claim `sub`, en calculant la Descendance via la relation `sponsor_account_id` au sein de la Base_Reseau de l'appelant.
2. WHEN une Vue_Agregee est demandée, THE Backend SHALL calculer les agrégats sur l'ensemble de la Descendance par requête serveur, sans retourner d'identifiants individuels dans la réponse agrégée.
3. WHEN une Vue_Detail_Direct est demandée, THE Backend SHALL restreindre le jeu de résultats aux mandataires dont le `sponsor_account_id` est directement l'appelant (N+1).
4. IF un appel vise un AccountId hors de la Portee_Hierarchique de l'appelant, THEN THE Backend SHALL répondre `403 Forbidden` sans révéler l'existence de la ressource (ne pas distinguer de `404` pour les ressources hors périmètre lorsque la divulgation serait sensible).
5. THE Backend SHALL exposer les capacités de partage hiérarchique via un contrat commun que chaque Module implémente (fourniture d'agrégats et, le cas échéant, de détail N+1), sans que le socle ne présume des données de chaque Module.
> **Note de calcul (indicateurs envisagés, non figés)** : les premiers agrégats pressentis sont le nombre de mandats en entrée, le nombre de mandats en sortie, le chiffre d'affaires et le nombre de Filleuls par niveau. Leur définition précise relève des specs des Modules fournisseurs.
---
### Requirement 6: Annuaire des abonnés — aspects techniques
1. THE Backend SHALL exposer `GET /api/directory` retournant les Comptes et Membres_Declares du `network` du claim, paginé, avec un indicateur d'état (inscrit / déclaré).
2. THE Backend SHALL exposer `GET /api/directory/tree` retournant l'Arbre_Parrainage de la Descendance de l'appelant.
3. THE Backend SHALL n'exposer dans ces endpoints que des champs d'annuaire (nom, prénom, Identifiant_Reseau, état, lien hiérarchique) et aucune donnée métier de Module.
4. THE Backend SHALL supporter une recherche serveur par nom, prénom ou Identifiant_Reseau et limiter la taille de page par défaut (ex. 50) avec pagination.
---
### Requirement 7: Abonnements et Packs — aspects techniques
1. THE Backend SHALL persister les Packs et les Abonnements, un Abonnement portant AccountId, PackId, dates d'effet et statut (`actif`, `expire`).
2. THE Backend SHALL exposer `GET /api/subscription` (abonnement courant et Modules résultants) et `POST /api/subscription` (changement de Pack).
3. WHEN un changement de Pack est appliqué, THE Backend SHALL recalculer côté serveur la liste des Modules_Accessibles par inclusion des Packs inférieurs.
4. THE Backend SHALL déterminer les Modules_Consultation_Seule en comparant les Modules non inclus dans le Pack courant avec la présence de données persistées du Mandataire pour ces Modules.
5. IF une action d'écriture est tentée sur un Module hors Abonnement, THEN THE Backend SHALL répondre `403 Forbidden` avec un code applicatif indiquant « abonnement requis ».
6. WHERE un paiement en ligne sera introduit ultérieurement, THE Backend SHALL isoler la logique de facturation d'abonnement derrière une abstraction (le MVP gère l'activation manuelle par l'Administrateur ou l'auto-attribution Starter), sans dépendance de paiement dans le socle initial.
---
### Requirement 8: Catalogue et activation des Modules — aspects techniques
1. THE Backend SHALL persister un Catalogue_Modules { ModuleKey, libellé, PackId requis, actif } et l'exposer via `GET /api/modules`.
2. THE Backend SHALL initialiser le Catalogue avec : `carnet-adresses`, `kms`, `facturation` → Pack Starter ; `crm`, `agenda` → Pack Pro.
3. THE Backend SHALL protéger les routes de chaque Module par un contrôle d'autorisation vérifiant, à partir des claims et de l'Abonnement, le droit d'accès (plein accès ou consultation seule), et répondre `403 Forbidden` à défaut.
4. THE Backend SHALL distinguer, dans la réponse `GET /api/modules`, l'état de chaque Module pour l'appelant (`accessible`, `consultation`, `verrouille`) afin que le Frontend adapte la navigation.
5. THE Frontend SHALL masquer ou désactiver les actions d'écriture d'un Module en état `consultation` et proposer un parcours de montée en abonnement pour un Module `verrouille`.
---
### Requirement 9: Administration de la Plateforme — aspects techniques
1. THE Backend SHALL matérialiser le rôle Administrateur par le claim `role=admin` et protéger les routes `/api/admin/**` par une autorisation dédiée répondant `403 Forbidden` à tout appelant non admin.
2. THE Backend SHALL exposer `GET /api/admin/accounts` (tous Reseaux, via la Base_Socle, avec Reseau, Abonnement, statut), `PUT /api/admin/accounts/{id}/subscription`, et `PUT /api/admin/modules` (gestion Module->Pack).
3. THE Backend SHALL exposer `POST /api/admin/networks` et `PUT /api/admin/networks/{id}` pour l'Onboarding_Reseau et le paramétrage (Personnalisation_Reseau).
4. THE Backend SHALL exposer `GET /api/admin/stats` retournant les KPI d'usage **remontés par chaque Reseau** (table `network_usage_kpis` de la Base_Socle), sans requête ni jointure traversant les Base_Reseau et sans donnée métier individuelle.
5. THE Backend SHALL exposer des endpoints d'administration de l'Arbre_Parrainage (Base_Reseau concernée) permettant de rattacher, détacher ou réaffecter un mandataire, en refusant (`409 Conflict`) toute opération créant un cycle.
---
### Requirement 10: Tableau de bord — aspects techniques
1. THE Backend SHALL exposer `GET /api/dashboard` agrégeant les contributions des seuls Modules_Accessibles de l'appelant via un contrat de fourniture commun.
2. THE Backend SHALL borner le périmètre des données du Tableau_De_Bord à l'appelant et à sa Portee_Hierarchique.
3. THE Frontend SHALL afficher un état vide explicite par section lorsque le Module correspondant ne fournit aucun élément.
4. THE Backend SHALL concevoir l'agrégation du Tableau_De_Bord de façon résiliente : la défaillance de la contribution d'un Module ne doit pas empêcher le rendu des autres sections.
---
### Requirement 11: Cadre applicatif et navigation — aspects techniques
1. THE Frontend SHALL être une application Angular (TypeScript) à routage protégé par un garde d'authentification redirigeant vers la connexion en l'absence de Session valide.
2. THE Frontend SHALL construire dynamiquement la navigation des Modules à partir de `GET /api/modules` (états `accessible` / `consultation` / `verrouille`).
3. THE Frontend SHALL présenter un composant d'identité (avatar par initiales) donnant accès au Profil, au paramétrage, à l'Abonnement et à la déconnexion.
4. THE Frontend SHALL centraliser la présentation des erreurs d'API par mappage des Codes_HTTP vers des messages utilisateur en français (401 → reconnexion, 403 → accès/abonnement requis, 422 → erreurs de champ, 5xx → indisponibilité).
5. THE Backend SHALL exposer `GET /api/profile` et `PUT /api/profile` permettant de modifier l'identité et les coordonnées, en interdisant la modification du `NetworkId`.
---
### Requirement 12: Persistance et isolation des données par Réseau — aspects techniques
1. THE Backend SHALL utiliser MariaDB/MySQL avec une Base_Socle et une Base_Reseau par Reseau ; chaque Config_BDD est fournie par variable d'environnement, pointant vers une instance conteneurisée **ou** un serveur externe, sans changement de code.
2. THE Backend SHALL gérer deux jeux de Migrations EF Core : le schéma socle appliqué à la Base_Socle, et le schéma réseau appliqué de façon idempotente à **chaque** Base_Reseau.
3. THE Backend SHALL utiliser deux DbContext (`SocleDbContext`, `ReseauDbContext`), le `ReseauDbContext` recevant sa chaîne de connexion de la requête via le NetworkConnectionResolver.
4. THE Backend SHALL garantir qu'aucune requête ni jointure ne traverse deux Base_Reseau, et qu'aucune donnée d'un Reseau n'est atteignable depuis le contexte d'un autre Reseau ; les requêtes métier sont en outre bornées à la Portee_Hierarchique de l'appelant.
5. WHEN une opération écrit plusieurs entités au sein d'une même base, THE Backend SHALL l'exécuter dans une transaction unique avec rollback complet en cas d'échec ; WHEN une opération touche à la fois la Base_Socle et une Base_Reseau (ex. création de Compte + profil), THE Backend SHALL la séquencer avec compensation applicative (pas de transaction distribuée).
6. THE Backend SHALL ne jamais journaliser de secret (Mot_De_Passe, token, chaîne de connexion) et masquer ces valeurs dans les logs et messages d'erreur ; la Base_Socle ne stocke qu'une clé logique de connexion par Reseau.
7. THE Backend SHALL exposer `GET /health` pour la sonde de disponibilité du déploiement (healthcheck conteneur).
8. THE deployment SHALL s'orchestrer via Docker Compose (service Backend ASP.NET Core, service Frontend statique), les bases MariaDB (Base_Socle et Base_Reseau) étant soit des services du Compose, soit un serveur externe référencé par la Config_BDD.
+426
View File
@@ -0,0 +1,426 @@
# Design Document
**Spec : Socle Plateforme SchotzimmO**
> Document de conception technique rédigé en français ; les titres de sections suivent le gabarit standard. Le besoin fonctionnel métier est décrit dans `requirements.md` ; les exigences techniques dérivées dans `design-requirements.md`. La présente conception met en œuvre ces exigences.
---
## Overview
**SchotzimmO** (« l'ami de l'immobilier » ; *Schotzi* signifie « ami » en alsacien, *immO* pour l'immobilier) est une plateforme web **SaaS multi-utilisateur, multi-réseaux et multi-module** destinée aux mandataires immobiliers. Ce document décrit la conception technique du **socle** : les capacités transverses communes à tous les Modules (authentification, comptes, Réseaux, parrainage/hiérarchie, visibilité descendante, annuaire, abonnements/Packs, activation des Modules, administration, tableau de bord, cadre applicatif, isolation des données).
Le socle fournit l'ossature sur laquelle se branchent les Modules métier (Carnet d'adresses, KMS, CRM, Agenda, Facturation…), chacun décrit dans sa propre spec. Il définit les contrats transverses que ces Modules implémentent (contribution au Tableau_De_Bord, partage hiérarchique, contrôle d'accès par Abonnement).
### Décision structurante : architecture à deux niveaux de base
L'isolation des réseaux est **physique**, par une architecture à deux niveaux de base de données :
- **Base Socle** (centrale, unique) : authentification, référentiel des Réseaux et leurs informations de connexion, abonnements, catalogue des Modules/Packs, administration, KPI d'usage remontés par réseau. Elle ne contient **aucune donnée métier** produite par les mandataires.
- **Base Réseau** (une par Réseau) : toutes les données métier cloisonnées — profil mandataire, parrainage/hiérarchie, et données des Modules.
À la connexion, le socle authentifie l'utilisateur sur la Base Socle, lit son Réseau, en déduit la **chaîne de connexion de sa Base Réseau**, et toute l'activité métier de la session s'exécute sur cette Base Réseau. L'identité (`account_id`) est générée côté socle et **réutilisée** comme clé du profil côté réseau (identité partagée).
### Périmètre fonctionnel (socle)
| # | Capacité | Description |
|---|----------|-------------|
| 1 | Inscription | Création de Compte avec Reseau, Identifiant_Reseau, Statut_Juridique, SIREN optionnel ; via Lien_Parrainage ou directe ; Starter par défaut |
| 2 | Authentification & session | Login e-mail/mot de passe (Base Socle), JWT, « se souvenir de moi », reset par e-mail |
| 3 | Réseaux | Référentiel multi-réseaux, base par réseau, onboarding réseau par l'Administrateur, personnalisation (thème/paramétrage) |
| 4 | Parrainage & hiérarchie | Déclaration parrain/filleuls, invitations e-mail, états supposé/validé, rapprochement par e-mail, confirmation bilatérale, anti-cycle (Base Réseau) |
| 5 | Visibilité descendante | Agrégats sur toute la Descendance, détail limité aux Filleuls_Directs (N+1) |
| 6 | Annuaire | Liste et arbre des membres d'un Reseau (inscrits + déclarés), recherche |
| 7 | Abonnements & Packs | Starter/Pro/Premium inclusifs, souscription, rétrogradation = conservation + consultation seule |
| 8 | Catalogue & activation Modules | Référentiel Module->Pack, états accessible/consultation/verrouillé, contrôle d'accès par appel |
| 9 | Administration | Rôle admin, gestion comptes/abonnements/catalogue, onboarding réseau, correction d'arbre, KPI d'usage par réseau |
| 10 | Tableau de bord | Agrégation résiliente des contributions des Modules accessibles |
| 11 | Cadre applicatif | Navigation dynamique, avatar/profil, mapping d'erreurs, garde d'auth, personnalisation par réseau |
| 12 | Isolation des données | Isolation **physique** par Base Réseau + authentification centrale ; références inter-bases logiques |
### Contraintes techniques
- **Multi-utilisateur authentifié** : chaque Mandataire possède un Compte (Base Socle) ; session par **JWT signé HS256** (durée configurable, défaut 720 min / 12 h) ; tous les endpoints de données sont protégés par authentification Bearer.
- **Isolation physique par réseau** : une **Base Réseau par Réseau** ; la Base Socle ne porte aucune donnée métier. Les références `account_id` entre Base Socle et Base Réseau sont **logiques** (mêmes valeurs d'identifiant), jamais des FK SQL ni des jointures inter-bases.
- **Résolution de connexion par réseau** : la Base Socle stocke, par Réseau, une **clé logique** (`db_connection_key`) qui résout la chaîne de connexion de la Base Réseau via la **configuration** (jamais la chaîne en base). Un service de résolution fournit le `DbContext` réseau de la requête selon le claim `network` du JWT.
- **Frontend** : **Angular (TypeScript)**, SPA, routage protégé par un garde d'authentification, navigation construite dynamiquement depuis le catalogue des Modules, thème/libellés personnalisables par Réseau.
- **Backend** : **ASP.NET Core Web API (C#)**, architecture en couches (Controllers -> Services -> Repositories/DbContext) avec **deux DbContext** : `SocleDbContext` et `ReseauDbContext`.
- **Base de données** : **MariaDB / MySQL**, via le provider EF Core **Pomelo.EntityFrameworkCore.MySql**. Chaînes de connexion fournies par variable d'environnement : la Base Socle et chaque Base Réseau peuvent être **conteneurisées** ou **externes** (cas de l'environnement de PROD de l'exploitant). Migrations EF Core appliquées de façon idempotente : schéma socle sur la Base Socle, schéma réseau sur **chaque** Base Réseau.
- **E-mails** : service **SMTP** configurable (invitations, réinitialisation). Si `Smtp:Host` est vide, repli de **journalisation** du lien.
- **Hébergement** : **Docker Compose** — un conteneur `frontend` (Angular servi par nginx) publié sur un port hôte, un conteneur `backend` (ASP.NET Core) non exposé directement, et des bases MariaDB **conteneurisées** (services avec volumes) ou **externes**. Terminaison **HTTPS par un reverse-proxy externe** (ex. Caddy).
- **Deux réseaux au lancement** : `iad` (production) et `demo` (démonstration en ligne + tests de développement).
- **Politique de visibilité** : `403 Forbidden` pour tout accès hors Portee_Hierarchique ; `404` pour les ressources réellement inexistantes dans le périmètre autorisé.
### Charte graphique
Reprise de la palette SchotzimmO : variables CSS globales — Orange `#F28C28`, Bleu `#0074B8` (primaire), Anthracite `#343A40`, Gris clair `#E7E9EA`. Le **bleu est conservé comme clin d'oeil au réseau IAD** (réseau partenaire au lancement). La charte de base est **personnalisable par Réseau** (thème, libellés) via le paramétrage porté par la Base Socle. Convention d'interaction : les boutons d'action passent à l'orange plein au survol ; les boutons « Annuler » restent neutres, les suppressions rouges, les états actifs en bleu plein. Les assets (logo, favicon) seront fournis ultérieurement.
---
## Architecture
### Architecture globale
```mermaid
graph TB
subgraph "Navigateur (SPA)"
FE[Angular App<br/>TypeScript]
end
subgraph "Backend (ASP.NET Core)"
API[API REST<br/>Controllers]
AUTHZ[Autorisation<br/>Auth + Abonnement + Hierarchie]
RES[NetworkConnectionResolver<br/>resout la base reseau]
SVC[Services socle + modules]
MOD[Contrats Modules<br/>IModuleAccess<br/>IDashboardContributor<br/>IHierarchySharing]
MAIL[EmailService<br/>SMTP / repli log]
SOCLE[(SocleDbContext)]
RESEAU[(ReseauDbContext)]
end
subgraph "Stockage"
DBS[(Base Socle<br/>MariaDB)]
DBR1[(Base Reseau IAD)]
DBR2[(Base Reseau Demo)]
end
SMTP[Serveur SMTP]
FE -- "HTTP/JSON + Bearer JWT" --> API
API --> AUTHZ
AUTHZ --> RES
AUTHZ --> SVC
SVC --> MOD
SVC --> MAIL
SVC --> SOCLE
SVC --> RESEAU
SOCLE --> DBS
RES -. "chaine de connexion selon claim network" .-> RESEAU
RESEAU --> DBR1
RESEAU --> DBR2
MAIL -- "SMTP" --> SMTP
```
### Flux de connexion et résolution de base réseau
```mermaid
sequenceDiagram
participant U as Utilisateur
participant FE as Angular Frontend
participant API as AuthController
participant SOCLE as SocleDbContext (Base Socle)
participant RES as NetworkConnectionResolver
participant RESEAU as ReseauDbContext (Base Reseau)
U->>FE: Saisit e-mail + mot de passe
FE->>API: POST /api/auth/login
API->>SOCLE: Verifie identifiants + lit network_id
SOCLE-->>API: Compte valide (account_id, network, role)
API-->>FE: JWT { sub, network, role }
U->>FE: Requete metier (Bearer JWT)
FE->>API: GET /api/... (module)
API->>RES: Resout la connexion pour claim network
RES-->>RESEAU: Chaine de connexion de la Base Reseau
API->>RESEAU: Execute la requete metier (account_id)
RESEAU-->>FE: Donnees du reseau
```
### Flux de contrôle d'accès à un Module
```mermaid
sequenceDiagram
participant FE as Frontend
participant API as Module Controller
participant AUTHZ as Autorisation
participant SUB as SubscriptionService (Base Socle)
participant SVC as Service du Module (Base Reseau)
FE->>API: Requete Module + Bearer JWT
API->>AUTHZ: Verifie JWT (sub, network, role)
AUTHZ->>SUB: Droit sur ce Module ?
SUB-->>AUTHZ: accessible | consultation | verrouille
alt verrouille
AUTHZ-->>FE: 403 (abonnement requis)
else consultation + ecriture
AUTHZ-->>FE: 403 (lecture seule)
else autorise
AUTHZ->>SVC: Execute sur la Base Reseau (account_id)
SVC-->>FE: 200 / 201
end
```
---
## Data Models
Le modèle est réparti sur **deux bases**. Le **détail physique** (types MariaDB, index, contraintes, seed) est tenu dans `.kiro/specs/_data-model/database.md`.
### Base Socle (centrale)
```mermaid
erDiagram
NETWORK ||--o{ ACCOUNT : "network_id"
NETWORK ||--o{ NETWORK_USAGE_KPI : "network_id"
ACCOUNT ||--|| SUBSCRIPTION : "account_id"
PACK ||--o{ SUBSCRIPTION : "pack_id"
PACK ||--o{ MODULE_CATALOG : "required_pack_id"
ACCOUNT ||--o{ PASSWORD_RESET : "account_id"
NETWORK {
int Id PK
string Code UK
string Name UK
string DbConnectionKey UK
text ThemeSettings "nullable"
bool IsActive
bool IsProvisioned
}
ACCOUNT {
int Id PK
int NetworkId FK
string Email UK
string PasswordHash
string Role "mandataire|admin"
bool IsActive
datetime CreatedAt
}
PACK {
int Id PK
string PackKey UK
int Level UK
bool IsFree
}
MODULE_CATALOG {
int Id PK
string ModuleKey UK
int RequiredPackId FK
bool IsActive
}
SUBSCRIPTION {
int Id PK
int AccountId FK
int PackId FK
datetime StartsAt
datetime EndsAt "nullable"
string Status "actif|expire"
}
PASSWORD_RESET {
int Id PK
int AccountId FK
string TokenHash
datetime ExpiresAt
bool Used
}
NETWORK_USAGE_KPI {
int Id PK
int NetworkId FK
string MetricKey
decimal MetricValue
string Period "nullable"
}
```
### Base Réseau (une par réseau)
```mermaid
erDiagram
MANDATAIRE_PROFILE ||--o| MANDATAIRE_PROFILE : "sponsor_account_id"
MANDATAIRE_PROFILE ||--o{ DECLARED_MEMBER : "declared_by_account_id"
MANDATAIRE_PROFILE ||--o{ AFFILIATION_REQUEST : "requester / target"
MANDATAIRE_PROFILE {
int AccountId PK "= accounts.id du socle (ref logique)"
string FirstName
string LastName
string NetworkMemberId UK
string LegalStatus "nullable"
string Siren "nullable"
int SponsorAccountId FK "nullable"
datetime CreatedAt
}
DECLARED_MEMBER {
int Id PK
string Email
string FirstName "nullable"
string LastName "nullable"
int DeclaredByAccountId FK
string Relation "parrain|filleul"
string Status "declare|invite"
}
AFFILIATION_REQUEST {
int Id PK
int RequesterAccountId FK
int TargetAccountId FK
string ProposedRelation "parrain|filleul"
string Status "en_attente|approuvee|refusee"
}
```
### Notes de modélisation
- **Identité partagée (Option B)** : `ACCOUNT` (Base Socle) porte le strict minimum d'authentification. `MANDATAIRE_PROFILE` (Base Réseau) porte le profil métier et le parrainage, sa clé primaire `AccountId` **réutilisant** `accounts.id`. La cohérence des deux morceaux (création, désactivation) est assurée applicativement.
- **Parrainage dans la Base Réseau** : l'auto-référence `sponsor_account_id` est une **vraie FK** au sein de la base réseau -> intégrité du parrainage garantie par la base. Le cloisonnement réseau est **physique** (base propre au réseau), d'où l'absence de colonne `network_id` dans les tables réseau.
- **Références inter-bases** : `account_id` circule entre les deux bases comme **référence logique** (`FK*`), jamais via une FK SQL ou une jointure traversant deux bases.
- **Abonnement** : un seul `SUBSCRIPTION` actif par Compte (Base Socle). Packs **inclusifs par `Level`**. **Consultation seule** déduite : Module hors Pack courant **mais** données présentes dans la Base Réseau.
- **Réseau** : `networks.db_connection_key` = clé logique de connexion (secret en configuration) ; `is_provisioned` indique que la Base Réseau est initialisée (schéma + seed) avant toute inscription.
- **KPI d'usage** : `network_usage_kpis` reçoit des agrégats **non nominatifs** remontés par chaque réseau ; aucune consolidation ne traverse les bases réseau.
---
## Components and Interfaces
### Endpoints d'API (socle)
| Domaine | Méthode & route | Rôle |
|---|---|---|
| Réseaux | `GET /api/networks` | Liste des Reseaux ouverts à l'inscription (public) |
| Inscription | `POST /api/auth/register` | Création de Compte (directe ou via referralToken) |
| Auth | `POST /api/auth/login` | Connexion (Base Socle), retourne le JWT |
| Auth | `POST /api/auth/forgot-password` | Déclenche la réinitialisation (réponse constante) |
| Auth | `POST /api/auth/reset-password` | Applique le nouveau mot de passe via jeton |
| Auth | `POST /api/auth/change-password` | Changement de mot de passe (authentifié) |
| Profil | `GET /api/profile`, `PUT /api/profile` | Consulter/modifier le Profil (Base Réseau ; Reseau non modifiable) |
| Parrainage | `GET /api/referrals/validate` | Valide un Lien_Parrainage et retourne le pré-remplissage |
| Parrainage | `POST /api/referrals/declare` | Déclarer parrain / filleuls |
| Parrainage | `POST /api/referrals/{id}/invite` | Envoyer une Invitation par e-mail |
| Parrainage | `POST /api/referrals/confirmations` | Proposer une Confirmation_Affiliation |
| Parrainage | `POST /api/referrals/confirmations/{id}/approve\|reject` | Approuver / refuser |
| Annuaire | `GET /api/directory` | Membres du Reseau (inscrits + déclarés), paginé, recherche |
| Annuaire | `GET /api/directory/tree` | Arbre de la Descendance de l'appelant |
| Abonnement | `GET /api/subscription`, `POST /api/subscription` | Abonnement courant / changement de Pack |
| Modules | `GET /api/modules` | Catalogue + état par Module (accessible/consultation/verrouille) |
| Tableau de bord | `GET /api/dashboard` | Agrégation des contributions des Modules accessibles |
| Admin | `GET /api/admin/accounts`, `PUT /api/admin/accounts/{id}/subscription` | Gestion des comptes/abonnements |
| Admin | `POST /api/admin/networks`, `PUT /api/admin/networks/{id}` | Onboarding et paramétrage d'un Reseau |
| Admin | `PUT /api/admin/modules` | Rattachement Module -> Pack |
| Admin | `GET /api/admin/stats` | KPI d'usage remontés par réseau (non nominatifs) |
| Admin | `POST /api/admin/referrals/reassign` | Correction de l'arbre de parrainage |
| Santé | `GET /health` | Sonde de disponibilité (healthcheck conteneur) |
### Composants backend structurants
- **SocleDbContext** — accès à la Base Socle (networks, accounts, packs, module_catalog, subscriptions, password_resets, network_usage_kpis).
- **ReseauDbContext** — accès à une Base Réseau (mandataire_profiles, declared_members, affiliation_requests, + tables des Modules). Sa chaîne de connexion est fournie à la requête.
- **NetworkConnectionResolver** — à partir du claim `network` du JWT, résout la `db_connection_key` du Réseau vers la chaîne de connexion (configuration) et fournit le `ReseauDbContext` de la requête.
- **AuthService / AccountService** — authentification sur la Base Socle, création de Compte (socle) + profil (réseau) dans une opération coordonnée.
- **ReferralService** — déclarations, invitations, rapprochement par e-mail, confirmations (Base Réseau).
- **SubscriptionService** — abonnement et calcul des Modules accessibles / consultation seule.
- **NetworkProvisioningService** — onboarding d'un Réseau (création/initialisation de la Base Réseau : schéma + seed), réservé à l'Administrateur.
### Contrats transverses pour les Modules
Approche « plugins applicatifs » par injection de dépendances (composition à la compilation) :
- **IModuleAccess** — déclare le `ModuleKey` et le `RequiredPack` d'un Module ; permet au socle de calculer l'état (`accessible` / `consultation` / `verrouille`) et de garder les routes.
- **IDashboardContributor** — fournit les éléments d'un Module pour le Tableau_De_Bord, appelé de façon **résiliente**.
- **IHierarchySharing** — fournit, pour un périmètre hiérarchique, les **agrégats** (toute la Descendance) et le **détail N+1** (Filleuls_Directs) que le Module accepte de partager.
Ajouter un Module = enregistrer son implémentation (injection de dépendances), son entrée de Catalogue_Modules, et ses tables dans le schéma de la Base Réseau.
---
## Error Handling
- **Mapping des Codes_HTTP** (centralisé côté Frontend, messages en français) : `401` (session expirée -> reconnexion), `403` (accès refusé / abonnement requis / lecture seule), `404` (ressource inexistante dans le périmètre autorisé), `409` (conflit : e-mail déjà utilisé, cycle de parrainage), `422` (erreurs de validation), `5xx` (indisponibilité).
- **Résolution de base réseau** : si la Base Réseau d'un Réseau est indisponible ou non provisionnée, le Backend répond `503` (service indisponible) sans exposer de détail d'infrastructure ; la connexion n'est jamais tentée pour un Réseau `is_provisioned = 0`.
- **Validation d'entrée** : DTO validés côté Backend ; erreurs de champ en `422`, sans divulgation sensible.
- **Anti-énumération** : `forgot-password` répond toujours `200` ; `register` renvoie un message générique si l'e-mail est déjà pris.
- **Isolation en cas d'erreur d'accès** : accès hors Portee_Hierarchique -> `403` ; `404` possible pour les ressources dont l'existence serait sensible.
- **Cohérence inter-bases** : la création d'un Compte (socle) et de son profil (réseau) est coordonnée ; en cas d'échec de l'une des étapes, l'opération est annulée pour ne pas laisser un Compte sans profil (ou l'inverse).
- **Transactions** : toute écriture multi-entités **au sein d'une base** est atomique (rollback complet en cas d'échec). Les opérations traversant les deux bases sont séquencées avec compensation applicative (pas de transaction distribuée).
- **Secrets** : aucun secret (mot de passe, token, chaîne de connexion) n'est journalisé ni renvoyé dans un message d'erreur.
- **Résilience du Tableau de bord** : l'échec de la contribution d'un Module est isolé et n'empêche pas le rendu des autres sections.
---
## Testing Strategy
- **Tests unitaires (Backend)** : services socle (authentification, création compte+profil coordonnée, calcul des Modules accessibles, portée hiérarchique), résolution de connexion réseau (mock de configuration), avec mocks (SMTP, horloge).
- **Tests d'intégration (Backend)** : endpoints d'API sur une Base Socle + une Base Réseau de test (conteneurs éphémères), vérifiant les Codes_HTTP, l'isolation par base réseau et la cohérence inter-bases.
- **Tests de propriétés / invariants** : non-existence de cycle dans l'Arbre_Parrainage, au plus un abonnement actif par compte, visibilité strictement descendante, aucune jointure/accès traversant deux bases réseau.
- **Tests Frontend** : composants Angular clés (formulaires d'inscription/connexion, navigation dynamique, personnalisation par réseau, mapping d'erreurs).
- **Tests de sécurité ciblés** : routes protégées refusant l'accès sans JWT valide, routes admin refusant un rôle `mandataire`, impossibilité d'accéder à une Base Réseau autre que celle du claim `network`.
- **Non-régression** : suite exécutée au build ; aucune livraison sans build vert.
---
## Correctness Properties
Propriétés que le système doit garantir en permanence (invariants vérifiables) :
### Property 1: Isolation physique par réseau
**Validates: Requirements 3.3, 3.4, 12.2, 12.5**
Toute donnée métier d'un Réseau réside dans sa Base Réseau ; aucune requête ne lit les données d'un autre Réseau, et aucune jointure ne traverse deux bases.
### Property 2: Authentification centrale cohérente
**Validates: Requirements 1.3, 2.2, 12.1**
Un Compte existe dans la Base Socle avec un e-mail unique sur toute la plateforme ; tout Compte actif possède un profil correspondant dans sa Base Réseau (`account_id` partagé), et réciproquement.
### Property 3: Acyclicité du parrainage
**Validates: Requirements 4.5, 4.6**
L'Arbre_Parrainage ne contient aucun cycle ; un mandataire a au plus un `sponsor_account_id` validé.
### Property 4: Visibilité descendante stricte
**Validates: Requirements 5.1, 5.2, 5.3, 5.4**
Un mandataire n'accède jamais aux données d'un ancêtre ou d'une autre branche ; le détail est limité au niveau N+1, l'agrégat couvre toute la Descendance.
### Property 5: Unicité de l'abonnement actif
**Validates: Requirements 7.7**
Au plus un abonnement au statut `actif` par Compte à un instant donné (Base Socle).
### Property 6: Inclusivité des Packs
**Validates: Requirements 7.2, 8.1**
Un Compte accède à tout Module dont `RequiredPack.Level <= Level` du Pack courant.
### Property 7: Conservation à la rétrogradation
**Validates: Requirements 7.5, 7.6, 7.8**
Aucune donnée n'est supprimée lors d'une Retrogradation ; les Modules hors Pack mais pourvus de données (Base Réseau) restent accessibles en consultation seule.
### Property 8: Confidentialité des secrets
**Validates: Requirements 2.7, 2.8**
Les mots de passe, jetons et chaînes de connexion ne sont jamais stockés en clair ni journalisés ; la Base Socle ne stocke qu'une clé logique de connexion.
### Property 9: Atomicité et cohérence inter-bases
**Validates: Requirements 12.4**
Toute écriture multi-entités au sein d'une base est atomique ; les opérations traversant les deux bases sont compensées pour ne jamais laisser un Compte sans profil (ou l'inverse).
### Property 10: Réseau provisionné avant inscription
**Validates: Requirements 3.1, 3.2**
Aucune inscription n'est possible sur un Réseau non actif ou non provisionné (`is_active = 0` ou `is_provisioned = 0`).
---
## Design Decisions
- **Deux niveaux de base (Base Socle + Base Réseau par réseau)** (retenu) vs **base unique mutualisée avec cloisonnement applicatif** (rejeté) : l'isolation physique par réseau garantit le cloisonnement par construction (pas de fuite inter-réseaux par bug), facilite la personnalisation par réseau et une éventuelle exigence contractuelle de séparation. Coût assumé : migrations à appliquer sur N bases réseau, provisioning d'une base à chaque nouveau réseau, et références inter-bases logiques (pas de FK SQL). Alternative mutualisée écartée car le projet valorise l'isolation forte et la personnalisation par réseau.
- **Identité partagée, Option B** (retenu) vs **compte entièrement au socle** (rejeté) : l'authentification minimale reste centrale (login unique), mais le profil et le parrainage vivent dans la Base Réseau pour que les FK métier (parrainage, propriété des données) restent **intra-base** et garanties par MariaDB. Le compte entièrement au socle aurait dispersé les FK métier entre deux bases, sans intégrité référentielle.
- **Clé logique de connexion + secret en configuration** (retenu) vs **chaîne de connexion stockée en base** (rejeté) : conforme à la règle « aucun secret en base/log » ; la Base Socle ne contient qu'une clé, le secret restant dans la configuration d'environnement.
- **Pas de consolidation inter-réseaux ; KPI remontés par réseau** (retenu) : l'administration consomme des agrégats non nominatifs poussés par chaque réseau dans `network_usage_kpis`, sans requête traversant les bases réseau. Évite toute jointure inter-bases et respecte le cloisonnement.
- **MariaDB/MySQL via Pomelo** (retenu) vs **SQLite** (rejeté) : multi-utilisateur concurrent + serveur MariaDB déjà présent en PROD. Connexions en configuration (conteneurisées ou externes) sans changement de code.
- **JWT HS256** (retenu) : simple à exploiter ; clé en configuration, démarrage refusé si absente. Passage à RS256 possible plus tard.
- **Contrats de Modules (plugins applicatifs)** (retenu) vs **socle monolithique** (rejeté) : permet d'empiler les Modules sans modifier le socle.
- **Packs inclusifs par niveau** (retenu) vs **composition module par module** (rejeté au MVP) : trois Packs ordonnés simples à comprendre et facturer ; rattachement Module->Pack paramétrable.
- **Rétrogradation = conservation + consultation seule** (retenu) vs **suppression/archivage** (rejeté) : aucune perte de donnée ; consultation seule déduite de la présence de données.
- **Onboarding réseau par l'Administrateur** (retenu) : un Réseau est créé, provisionné et paramétré (thème) avant toute inscription de mandataire ; détail à affiner après l'expérience IAD.
---
## Deployment
- **docker-compose** : services `backend` (ASP.NET Core, non exposé), `frontend` (nginx servant la SPA Angular, publié sur un port hôte), et **optionnellement** des services `db` MariaDB (Base Socle et bases réseau) avec volumes, pour les environnements sans serveur externe.
- **Bases externes** : en PROD, les services `db` sont omis ; les chaînes de connexion (socle + réseaux) pointent vers le serveur MariaDB existant de l'exploitant.
- **Provisioning d'un réseau** : création de la Base Réseau (schéma + seed), enregistrement du Réseau dans la Base Socle (`db_connection_key`, `theme_settings`), passage `is_provisioned = 1`. Au lancement : réseaux `iad` et `demo`.
- **Migrations** : schéma socle appliqué à la Base Socle ; schéma réseau appliqué à **chaque** Base Réseau, de façon idempotente.
- **Secrets par environnement** : clé JWT, chaînes de connexion (socle + réseaux) et paramètres SMTP via variables d'environnement (`.env`), jamais dans l'image. Démarrage refusé si la clé JWT est absente.
- **Reverse-proxy HTTPS externe** (Caddy) relaie vers le frontend ; le backend ne gère pas la terminaison TLS.
- **Healthcheck** : `GET /health` sur le backend.
@@ -0,0 +1,285 @@
# Requirements Document
**Spec : Socle Plateforme SchotzimmO**
## Introduction
**SchotzimmO** (« l'ami de l'immobilier » ; *Schotzi* signifie « ami » en alsacien, *immO* pour l'immobilier) est une **plateforme SaaS multi-utilisateur, multi-réseaux et multi-module** destinée aux **mandataires immobiliers**, en complément de la plateforme mère de leur réseau (ex. IAD). Elle les aide à gérer en propre leur activité quotidienne : relation prospects, suivi des opportunités et des actions, agenda, carnet d'adresses, suivi kilométrique, facturation vers le siège, notes de frais, tableau de bord et reporting.
La plateforme est **multi-réseaux** : chaque mandataire appartient à un **Réseau** choisi à l'inscription (IAD et un réseau de démonstration au lancement, d'autres réseaux ultérieurement). Les données de chaque Réseau sont **physiquement isolées** dans une base de données propre à ce Réseau ; les arbres de parrainage sont donc **cloisonnés par Réseau**. Un Réseau est préalablement initialisé par l'Administrateur (Onboarding_Reseau) avant toute inscription.
L'accès aux fonctionnalités est organisé en **Modules** regroupés dans des **Packs d'abonnement** (Starter gratuit, Pro, Premium). Chaque Mandataire souscrit un abonnement **individuel** qui détermine les Modules auxquels il accède.
Ce document, dit **Socle Plateforme**, décrit les exigences **transversales** communes à tous les Modules : comptes et inscription, authentification, Réseaux, parrainage et hiérarchie, visibilité hiérarchique, annuaire, abonnements et Packs, activation des Modules, administration, tableau de bord, cadre applicatif et isolation des données par Réseau. Les exigences propres à chaque Module (Carnet d'adresses, KMS, CRM, Agenda, Facturation…) sont décrites dans leurs specs dédiées.
> **Note** : Ce document exprime le besoin **fonctionnel métier** (attendu fonctionnel, mode opératoire, règles de gestion) fourni par le Product Owner business. Les exigences **techniques** dérivées (isolation par deux niveaux de base MariaDB conteneurisée ou externe, résolution de connexion par Réseau, JWT, codes HTTP, sécurité, e-mails, rendu) sont rassemblées dans le document `design-requirements.md`, à préciser conjointement par le PO business et le PO IT. Chaque exigence renvoie, lorsque pertinent, à son pendant technique dans `design-requirements.md`.
---
## Glossary
- **Plateforme** : L'ensemble du système SchotzimmO (socle + Modules).
- **Frontend** : L'interface utilisateur de la Plateforme.
- **Backend** : Le serveur d'API de la Plateforme.
- **API** : L'interface de programmation exposée par le Backend pour communiquer avec le Frontend.
- **Mandataire** : Un utilisateur de la Plateforme, mandataire immobilier d'un Réseau, disposant d'un Compte.
- **Compte** : Le compte individuel d'un Mandataire, identifié par une Adresse_Email (login) et protégé par un Mot_De_Passe. Un Compte appartient à exactement un Réseau. L'authentification est centralisée ; les données métier du Mandataire résident dans la base de son Réseau.
- **Adresse_Email** : L'adresse e-mail du Mandataire, utilisée comme identifiant de connexion et destinataire des e-mails (invitation, réinitialisation de mot de passe).
- **Mot_De_Passe** : Le secret d'au moins 8 caractères associé au Compte, jamais conservé en clair.
- **Session** : L'état authentifié d'un Mandataire, établi à la connexion et vérifié à chaque accès protégé.
- **Jeton_Reinitialisation** : Un jeton à usage unique et à durée de vie limitée, envoyé par e-mail, permettant de définir un nouveau Mot_De_Passe sans connaître l'ancien.
- **Identifiant_Mémorisé** : L'Adresse_Email de connexion conservée par le navigateur lorsque le Mandataire a demandé « Se souvenir de moi », afin de pré-remplir le champ Adresse_Email lors des visites ultérieures sur ce même navigateur. Le Mot_De_Passe n'en fait jamais partie.
- **Profil** : Les paramètres personnels du Mandataire : identité (nom, prénom), Identifiant_Reseau, Statut_Juridique, numéro SIREN/RSAC optionnel, coordonnées, et données utiles aux Modules (ex. Adresse_Domicile pour KMS).
- **Reseau** : L'organisation mère à laquelle appartient un Mandataire (ex. IAD). Choisi à l'inscription. Chaque Reseau dispose d'une **base de données propre** qui héberge toutes ses données métier : les relations de parrainage et la visibilité hiérarchique sont ainsi strictement cloisonnées par Reseau, aucun lien ni aucune donnée ne traversant deux Reseaux.
- **Onboarding_Reseau** : Le parcours d'initialisation d'un Reseau mené par l'Administrateur avant toute inscription de Mandataire : création et initialisation de la base du Reseau, puis paramétrage (Personnalisation_Reseau). Un Reseau n'est ouvert à l'inscription qu'une fois initialisé.
- **Personnalisation_Reseau** : Le paramétrage propre à un Reseau (thème, libellés et réglages d'interface), appliqué par-dessus le socle commun.
- **Identifiant_Reseau** : L'identifiant du Mandataire propre à son Reseau (fourni par le Reseau). Obligatoire à l'inscription.
- **Statut_Juridique** : La forme d'exercice du Mandataire (ex. auto-entrepreneur, EIRL). Détermine notamment la présence d'un numéro SIREN/RSAC.
- **Numero_Siren** : Le numéro SIREN/RSAC du Mandataire, **optionnel** (présent lorsque le Statut_Juridique l'implique, ex. EIRL ; absent en auto-entrepreneur).
- **Parrain** : Le Mandataire qui a recruté/affilié un autre Mandataire (son Filleul) au sein d'un même Reseau. Un Filleul a au plus un Parrain.
- **Filleul** : Le Mandataire affilié à un Parrain au sein d'un même Reseau. Un Parrain peut avoir plusieurs Filleuls.
- **Filleul_Direct** : Un Filleul rattaché directement à un Parrain (niveau immédiatement inférieur, N+1).
- **Descendance** : L'ensemble des Mandataires situés sous un Parrain dans l'arbre de parrainage, tous niveaux confondus (Filleuls_Directs, puis leurs propres Filleuls, etc.).
- **Arbre_Parrainage** : La structure hiérarchique des relations Parrain/Filleul au sein d'un Reseau donné.
- **Membre_Declare** : Un Mandataire déclaré dans l'Arbre_Parrainage (comme Parrain ou Filleul d'un Mandataire inscrit) mais **pas encore inscrit** : il figure dans l'annuaire comme lien « supposé », sans Compte actif et sans aucune donnée métier.
- **Invitation** : Une proposition d'inscription envoyée par e-mail par un Mandataire à son Parrain supposé ou à l'un de ses Filleuls supposés, porteuse d'un Lien_Parrainage pré-renseignant le Reseau et le lien hiérarchique.
- **Lien_Parrainage** : Un lien (ou code) d'inscription transmis par Invitation ou partagé par un Parrain, pré-renseignant à l'inscription le Reseau et l'identité du Parrain, afin d'établir l'affiliation.
- **Affiliation** : Le lien Parrain/Filleul. Elle est **validée** lorsque le Filleul s'inscrit via un Lien_Parrainage ou confirme son rattachement ; elle est **supposée** tant qu'elle n'est pas validée par le Filleul.
- **Confirmation_Affiliation** : L'acte par lequel deux Mandataires déjà inscrits établissent ou valident entre eux un lien Parrain/Filleul resté supposé (cas de « perte de lien » lorsque le rapprochement automatique par e-mail n'a pas opéré). Le lien devient validé lorsque la partie concernée (Parrain ou Filleul) l'approuve.
- **Rapprochement_Email** : Le mécanisme par lequel un Membre_Declare (identifié par une Adresse_Email lors de sa déclaration) est automatiquement relié au Compte s'inscrivant avec cette même Adresse_Email, afin d'éviter les doublons et d'établir l'Affiliation.
- **Annuaire** : Le répertoire des Mandataires d'un Reseau (inscrits et Membres_Declares), permettant de visualiser l'organisation et de retrouver un contact.
- **Visibilite_Descendante** : Le principe selon lequel un Parrain peut consulter des informations de sa Descendance, jamais l'inverse (aucune visibilité ascendante ni latérale par défaut).
- **Vue_Agregee** : Une consultation d'indicateurs **agrégés** (totaux, KPI consolidés) portant sur toute la Descendance d'un Parrain, sans détail nominatif.
- **Vue_Detail_Direct** : Une consultation de données **détaillées** limitée aux Filleuls_Directs (niveau N+1) d'un Parrain, dans les limites définies par chaque Module.
- **Module** : Une unité fonctionnelle de la Plateforme (ex. Carnet_Adresses, KMS, CRM, Agenda, Facturation). Chaque Module est rattaché à un Pack.
- **Catalogue_Modules** : La liste des Modules disponibles sur la Plateforme, chacun associé au Pack qui le débloque.
- **Pack** : Une formule d'abonnement regroupant un ensemble de Modules. Trois Packs existent : **Starter** (gratuit), **Pro**, **Premium**. Un Pack inclut tous les Modules des Packs de niveau inférieur.
- **Abonnement** : La souscription individuelle d'un Mandataire à un Pack, déterminant les Modules auxquels il a accès. Un Mandataire a exactement un Abonnement actif à un instant donné.
- **Retrogradation** : Le passage d'un Abonnement vers un Pack de niveau inférieur (ou l'expiration d'un Pack payant vers Starter). Les données des Modules devenus inaccessibles sont **conservées** et restent accessibles **en consultation a minima**.
- **Module_Accessible** : Un Module auquel le Mandataire a accès en pleine fonctionnalité au titre de son Abonnement courant.
- **Module_Consultation_Seule** : Un Module non inclus dans l'Abonnement courant mais pour lequel le Mandataire possède des données antérieures, accessible en lecture seule à la suite d'une Retrogradation.
- **Administrateur** : Le gestionnaire de la Plateforme (exploitant SchotzimmO), disposant de droits d'administration transverses (comptes, abonnements, Catalogue_Modules, Onboarding_Reseau, indicateurs d'usage par Réseau). Rôle distinct de celui de Mandataire.
- **Tableau_De_Bord** : L'écran d'accueil présenté après connexion, regroupant les actions du jour et des indicateurs clés (KPI), alimenté par le socle et enrichi par les Modules_Accessibles.
- **KPI** : Un indicateur clé de performance affiché sur le Tableau_De_Bord ou dans un reporting.
---
## Requirements
### Requirement 1: Inscription d'un Mandataire
**User Story:** En tant que mandataire immobilier, je veux créer un compte sur SchotzimmO en indiquant mon réseau et mon identité, afin d'accéder aux outils de gestion de mon activité.
> Exigences techniques associées (validation serveur, unicité, hachage du mot de passe, codes HTTP) : `design-requirements.md`, Requirement 1.
#### Acceptance Criteria
1. THE Frontend SHALL proposer un parcours d'inscription demandant au minimum : Adresse_Email, Mot_De_Passe, nom, prénom, Reseau et Identifiant_Reseau.
2. THE Frontend SHALL permettre de renseigner, à l'inscription, un Statut_Juridique et un Numero_Siren optionnel.
3. WHEN le Mandataire soumet le formulaire d'inscription avec tous les champs obligatoires valides, THE Backend SHALL créer un Compte rattaché au Reseau choisi et retourner une confirmation.
4. IF l'Adresse_Email soumise correspond déjà à un Compte existant, THEN THE Backend SHALL refuser la création et signaler que l'adresse est déjà utilisée, sans révéler d'autre information sur le Compte existant.
5. IF le Mot_De_Passe soumis comporte moins de 8 caractères, THEN THE Backend SHALL refuser la création et signaler que le mot de passe est trop court.
6. IF un champ obligatoire (Adresse_Email, Mot_De_Passe, nom, prénom, Reseau, Identifiant_Reseau) est absent ou vide, THEN THE Frontend SHALL afficher un message d'erreur identifiant chaque champ manquant sans soumettre la requête au Backend.
7. WHEN l'inscription est effectuée via un Lien_Parrainage valide, THE Frontend SHALL pré-renseigner le Reseau et l'identité du Parrain et empêcher la modification du Reseau pré-renseigné.
8. WHEN un Compte est créé via un Lien_Parrainage valide, THE Backend SHALL établir l'Affiliation entre le nouveau Mandataire et le Parrain désigné à l'état validé.
9. WHEN un Compte est créé, THE Backend SHALL lui attribuer par défaut un Abonnement au Pack Starter.
10. IF le Numero_Siren est fourni mais ne respecte pas un format de numéro valide, THEN THE Backend SHALL refuser la création et signaler que le numéro est invalide.
---
### Requirement 2: Authentification et session
**User Story:** En tant que mandataire, je veux me connecter de façon sécurisée et rester authentifié, afin d'accéder à mes données en toute confidentialité.
> Exigences techniques associées (JWT, durée de session, en-têtes, codes HTTP) : `design-requirements.md`, Requirement 2.
#### Acceptance Criteria
1. THE Frontend SHALL proposer un écran de connexion demandant l'Adresse_Email et le Mot_De_Passe.
2. WHEN le Mandataire soumet des identifiants valides, THE Backend SHALL établir une Session et retourner au Frontend de quoi accéder aux ressources protégées.
3. IF les identifiants soumis sont invalides, THEN THE Backend SHALL refuser la connexion et retourner un message générique n'indiquant pas lequel de l'Adresse_Email ou du Mot_De_Passe est erroné.
4. WHILE une Session est active, THE Backend SHALL n'autoriser l'accès aux ressources d'un Mandataire qu'à ce Mandataire authentifié.
5. WHEN le Mandataire demande à se déconnecter, THE Frontend SHALL mettre fin à la Session côté client et rediriger vers l'écran de connexion.
6. WHEN le Mandataire coche « Se souvenir de moi » lors d'une connexion réussie, THE Frontend SHALL conserver l'Identifiant_Mémorisé pour pré-remplir l'Adresse_Email lors des visites ultérieures sur ce navigateur, sans jamais conserver le Mot_De_Passe.
7. WHEN le Mandataire demande une réinitialisation de mot de passe pour une Adresse_Email, THE Backend SHALL générer un Jeton_Reinitialisation à usage unique et à durée de vie limitée et l'envoyer par e-mail, en répondant de façon identique que l'adresse existe ou non.
8. WHEN le Mandataire soumet un nouveau Mot_De_Passe avec un Jeton_Reinitialisation valide et non expiré, THE Backend SHALL mettre à jour le Mot_De_Passe et invalider le jeton.
9. IF un Jeton_Reinitialisation est expiré, déjà utilisé ou inconnu, THEN THE Backend SHALL refuser la réinitialisation et signaler que le lien n'est plus valide.
10. WHILE aucune Session valide n'est présente, THE Frontend SHALL rediriger toute tentative d'accès à une ressource protégée vers l'écran de connexion.
---
### Requirement 3: Réseaux
**User Story:** En tant qu'exploitant, je veux que chaque mandataire soit rattaché à un réseau, afin de cloisonner les hiérarchies et de préparer l'ouverture à d'autres réseaux que IAD.
> Exigences techniques associées (référentiel des réseaux, cloisonnement en base) : `design-requirements.md`, Requirement 3.
#### Acceptance Criteria
1. THE Backend SHALL maintenir un référentiel de Reseaux ; seuls les Reseaux actifs et initialisés (Onboarding_Reseau effectué) SHALL être proposés à l'inscription.
2. WHEN un Compte est créé, THE Backend SHALL le rattacher à exactement un Reseau et interdire la modification ultérieure de ce rattachement par le Mandataire.
3. THE Backend SHALL garantir qu'aucune relation d'Affiliation ne relie deux Mandataires appartenant à des Reseaux différents.
4. WHEN la Plateforme présente l'Annuaire, l'Arbre_Parrainage ou une Vue_Agregee à un Mandataire, THE Backend SHALL restreindre les données au seul Reseau du Mandataire.
5. IF aucun Reseau n'est sélectionné à l'inscription (hors Lien_Parrainage), THEN THE Frontend SHALL empêcher la soumission et signaler que le Reseau est obligatoire.
6. WHEN l'Administrateur réalise l'Onboarding_Reseau d'un nouveau Reseau, THE Backend SHALL initialiser l'espace de données propre à ce Reseau et enregistrer sa Personnalisation_Reseau avant toute inscription.
7. IF un Mandataire tente de s'inscrire sur un Reseau non actif ou non initialisé, THEN THE Backend SHALL refuser l'inscription et signaler que le Reseau n'est pas disponible.
8. WHERE un Reseau définit une Personnalisation_Reseau (thème, libellés), THE Frontend SHALL appliquer ce paramétrage par-dessus la présentation commune du socle.
---
### Requirement 4: Parrainage et hiérarchie
**User Story:** En tant que mandataire, je veux déclarer mon parrain et mes filleuls et les inviter à rejoindre la plateforme, afin de reconstituer mon équipe au sein de mon réseau.
> Exigences techniques associées (e-mails d'invitation, génération et validité des Liens_Parrainage, états en base) : `design-requirements.md`, Requirement 4.
#### Acceptance Criteria
1. THE Frontend SHALL permettre à un Mandataire de déclarer son Parrain (au plus un) et ses Filleuls (plusieurs) au sein de son Reseau, en fournissant pour chaque personne déclarée au minimum son Adresse_Email, servant de clé de Rapprochement_Email.
2. WHEN un Mandataire déclare un Parrain ou un Filleul non encore inscrit, THE Backend SHALL enregistrer un Membre_Declare correspondant dans l'Arbre_Parrainage du Reseau, sans Compte ni donnée métier associée.
3. WHEN un Mandataire déclenche une Invitation vers un Parrain ou un Filleul déclaré, THE Backend SHALL envoyer un e-mail contenant un Lien_Parrainage pré-renseignant le Reseau et le lien hiérarchique.
4. WHEN un destinataire s'inscrit via un Lien_Parrainage, THE Backend SHALL convertir le Membre_Declare correspondant en Compte et établir l'Affiliation à l'état validé.
5. THE Backend SHALL garantir qu'un Filleul a au plus un Parrain à un instant donné.
6. IF une déclaration de hiérarchie créerait un cycle dans l'Arbre_Parrainage (un Mandataire devenant ancêtre et descendant d'un autre), THEN THE Backend SHALL refuser la déclaration et signaler l'incohérence.
7. WHILE une Affiliation reste supposée (Filleul non inscrit ou non confirmée), THE Backend SHALL n'exposer aucune donnée métier du Membre_Declare concerné.
8. WHEN un Membre_Declare existe pour une Adresse_Email ensuite utilisée pour une inscription (via Rapprochement_Email), THE Backend SHALL rattacher l'inscription au Membre_Declare existant plutôt que d'en créer un doublon.
9. WHEN un Mandataire déjà inscrit déclare comme Parrain ou Filleul un autre Mandataire déjà inscrit (cas de perte de lien, sans Rapprochement_Email préalable), THE Backend SHALL créer une Affiliation à l'état supposé en attente de Confirmation_Affiliation par la partie concernée.
10. WHEN la partie concernée approuve une Confirmation_Affiliation, THE Backend SHALL faire passer l'Affiliation à l'état validé et recomposer l'Arbre_Parrainage en conséquence.
11. IF la partie concernée refuse une Confirmation_Affiliation, THEN THE Backend SHALL ne pas établir le lien et conserver l'état antérieur de l'Arbre_Parrainage.
---
### Requirement 5: Visibilité hiérarchique descendante
**User Story:** En tant que parrain, je veux consulter des informations sur ma descendance (agrégées sur tous les niveaux, détaillées pour mes filleuls directs), afin de suivre et d'accompagner mon équipe, sans que mes filleuls ne voient mes données.
> Exigences techniques associées (filtrage des requêtes par périmètre hiérarchique, portée par Module) : `design-requirements.md`, Requirement 5.
#### Acceptance Criteria
1. THE Backend SHALL appliquer le principe de Visibilite_Descendante : un Parrain peut consulter des informations de sa Descendance, et aucun Mandataire ne peut consulter les informations d'un de ses ancêtres ou d'un Mandataire d'une autre branche.
2. WHEN un Parrain consulte une Vue_Agregee, THE Backend SHALL calculer les indicateurs agrégés sur l'ensemble de sa Descendance (tous niveaux) au sein de son Reseau, sans exposer de détail nominatif.
3. WHEN un Parrain consulte une Vue_Detail_Direct, THE Backend SHALL limiter le détail aux seuls Filleuls_Directs (niveau N+1), dans les limites de portée définies par chaque Module concerné.
4. IF un Mandataire tente d'accéder à des données hors de son périmètre de Visibilite_Descendante, THEN THE Backend SHALL refuser l'accès.
5. WHERE un Module ne définit pas de données partageables en consultation hiérarchique, THE Backend SHALL n'exposer aucune donnée de ce Module dans les Vues_Agregees ou Vues_Detail_Direct.
> **Note d'évolution** : un paramètre « nombre de niveaux » permettant à un Parrain de limiter la portée de certaines Vues_Agregees est envisagé ultérieurement et n'entre pas dans le périmètre initial.
---
### Requirement 6: Annuaire des abonnés
**User Story:** En tant que mandataire, je veux consulter un annuaire des membres de mon réseau, afin de visualiser l'organisation et de retrouver des contacts.
> Exigences techniques associées (champs exposés, recherche, pagination) : `design-requirements.md`, Requirement 6.
#### Acceptance Criteria
1. WHEN un Mandataire consulte l'Annuaire, THE Backend SHALL retourner les Mandataires inscrits et les Membres_Declares de son Reseau uniquement.
2. THE Frontend SHALL distinguer visuellement, dans l'Annuaire, les Mandataires inscrits des Membres_Declares non encore inscrits.
3. THE Frontend SHALL permettre de rechercher un membre de l'Annuaire par nom, prénom ou Identifiant_Reseau.
4. THE Backend SHALL n'exposer dans l'Annuaire que des informations d'identification et de contact de niveau annuaire, à l'exclusion de toute donnée métier des Modules.
5. WHEN un Mandataire visualise son équipe, THE Frontend SHALL présenter l'Arbre_Parrainage de sa Descendance au sein de son Reseau.
---
### Requirement 7: Abonnements et Packs
**User Story:** En tant que mandataire, je veux souscrire une formule d'abonnement, afin de débloquer les modules dont j'ai besoin ; en tant qu'exploitant, je veux financer l'hébergement et la maintenance via des formules payantes.
> Exigences techniques associées (modèle de données abonnement, paiement éventuel, codes HTTP) : `design-requirements.md`, Requirement 7.
#### Acceptance Criteria
1. THE Backend SHALL proposer exactement trois Packs : Starter (gratuit), Pro et Premium.
2. THE Backend SHALL garantir qu'un Pack inclut l'ensemble des Modules des Packs de niveau inférieur (Pro inclut Starter ; Premium inclut Pro et Starter).
3. THE Backend SHALL attribuer par défaut le Pack Starter à tout Compte sans Abonnement payant actif.
4. WHEN un Mandataire souscrit ou change de Pack, THE Backend SHALL mettre à jour son Abonnement et recalculer la liste de ses Modules_Accessibles.
5. WHEN une Retrogradation survient (changement vers un Pack inférieur ou expiration d'un Pack payant), THE Backend SHALL conserver l'intégralité des données des Modules devenus inaccessibles.
6. WHILE un Module n'est plus inclus dans l'Abonnement courant mais contient des données du Mandataire, THE Backend SHALL exposer ce Module en Module_Consultation_Seule (lecture seule), sans permettre de nouvelle saisie ni modification.
7. THE Backend SHALL garantir qu'un Mandataire a au plus un Abonnement actif à un instant donné.
8. IF un Mandataire tente une action d'écriture sur un Module_Consultation_Seule, THEN THE Backend SHALL refuser l'action et signaler que le Module nécessite un abonnement de niveau supérieur.
---
### Requirement 8: Catalogue et activation des Modules
**User Story:** En tant que mandataire, je veux n'accéder qu'aux modules compris dans mon abonnement, et voir clairement ceux que je pourrais débloquer, afin de comprendre l'offre.
> Exigences techniques associées (contrôle d'accès par Module, référentiel Module→Pack) : `design-requirements.md`, Requirement 8.
#### Acceptance Criteria
1. THE Backend SHALL maintenir un Catalogue_Modules associant chaque Module au Pack qui le débloque.
2. THE Backend SHALL rattacher, au lancement, les Modules suivants aux Packs : Carnet_Adresses, KMS et Facturation au Pack Starter ; CRM et Agenda au Pack Pro.
3. WHEN le Frontend présente la navigation, THE Frontend SHALL afficher les Modules_Accessibles comme actifs, les Modules_Consultation_Seule comme consultables, et les Modules non souscrits comme proposables à l'abonnement.
4. WHEN un Mandataire tente d'accéder à un Module non inclus dans son Abonnement et sans données existantes, THE Backend SHALL refuser l'accès fonctionnel et le Frontend SHALL proposer la montée en abonnement correspondante.
5. THE Backend SHALL vérifier, à chaque appel d'API d'un Module, que le Mandataire dispose du droit d'accès correspondant à son Abonnement (plein accès ou consultation seule).
---
### Requirement 9: Administration de la Plateforme
**User Story:** En tant qu'exploitant SchotzimmO, je veux administrer les comptes, les abonnements et le catalogue des modules, afin de gérer la plateforme et d'en suivre l'activité globale.
> Exigences techniques associées (rôle Administrateur, périmètre des droits, statistiques) : `design-requirements.md`, Requirement 9.
#### Acceptance Criteria
1. THE Backend SHALL distinguer le rôle Administrateur du rôle Mandataire et réserver les fonctions d'administration au seul Administrateur.
2. WHEN l'Administrateur consulte la liste des Comptes, THE Backend SHALL retourner les Comptes de tous les Reseaux avec leur Reseau, leur Abonnement et leur statut.
3. THE Backend SHALL permettre à l'Administrateur de modifier l'Abonnement d'un Compte et de gérer le rattachement des Modules aux Packs dans le Catalogue_Modules.
4. WHEN l'Administrateur consulte les statistiques globales, THE Backend SHALL retourner des KPI consolidés (ex. nombre de Comptes par Reseau, répartition par Pack) sans exposer de donnée métier individuelle au-delà du nécessaire.
5. IF un Mandataire non Administrateur tente d'accéder à une fonction d'administration, THEN THE Backend SHALL refuser l'accès.
6. WHEN un différend ou une incohérence de hiérarchie lui est signalé, THE Backend SHALL permettre à l'Administrateur de corriger l'Arbre_Parrainage d'un Reseau (rattacher, détacher ou réaffecter un Mandataire à un Parrain), dans le respect du cloisonnement par Reseau et de l'interdiction des cycles.
---
### Requirement 10: Tableau de bord
**User Story:** En tant que mandataire, je veux retrouver à l'ouverture mes actions du jour et des indicateurs clés, afin de démarrer ma journée efficacement.
> Exigences techniques associées (agrégation inter-modules, rafraîchissement) : `design-requirements.md`, Requirement 10.
#### Acceptance Criteria
1. WHEN un Mandataire se connecte, THE Frontend SHALL présenter le Tableau_De_Bord comme écran d'accueil.
2. THE Tableau_De_Bord SHALL regrouper les actions du jour et des KPI issus des seuls Modules_Accessibles du Mandataire.
3. WHERE un Module_Accessible fournit des éléments pour le Tableau_De_Bord, THE Frontend SHALL afficher la section correspondante ; WHERE aucun Module ne fournit d'élément, THE Frontend SHALL afficher un état vide explicite.
4. WHEN le Mandataire sélectionne une action ou un indicateur du Tableau_De_Bord, THE Frontend SHALL le rediriger vers le Module concerné.
5. THE Backend SHALL ne fournir au Tableau_De_Bord que des données appartenant au Mandataire ou relevant de sa Visibilite_Descendante.
---
### Requirement 11: Cadre applicatif et navigation
**User Story:** En tant que mandataire, je veux une interface cohérente avec une navigation claire entre les modules et l'accès à mon profil, afin d'utiliser la plateforme facilement.
> Exigences techniques associées (structure de navigation, avatar, pages de paramétrage) : `design-requirements.md`, Requirement 11.
#### Acceptance Criteria
1. THE Frontend SHALL présenter une navigation donnant accès au Tableau_De_Bord, à l'Annuaire et aux Modules selon l'Abonnement du Mandataire.
2. THE Frontend SHALL afficher un contrôle d'identité (avatar) donnant accès au Profil, au paramétrage, à l'Abonnement et à la déconnexion.
3. WHEN le Mandataire accède à son Profil, THE Frontend SHALL permettre de consulter et modifier son identité, ses coordonnées et les données transverses, à l'exception du Reseau de rattachement.
4. THE Frontend SHALL présenter de façon cohérente, sur tous les Modules, l'en-tête, la navigation et les messages d'erreur ou de confirmation.
5. WHEN un Module n'est pas accessible, THE Frontend SHALL le signaler dans la navigation et proposer, le cas échéant, l'accès en consultation seule ou la montée en abonnement.
---
### Requirement 12: Persistance et isolation des données par Réseau
**User Story:** En tant que mandataire, je veux que mes données soient strictement isolées de celles des autres mandataires, afin de garantir la confidentialité de mon activité.
> Exigences techniques associées (cloisonnement par tenant en base MariaDB, index, intégrité) : `design-requirements.md`, Requirement 12.
#### Acceptance Criteria
1. THE Backend SHALL persister les données métier de chaque Mandataire de manière durable, dans la base de données propre à son Reseau, rattachées à son Compte.
2. THE Backend SHALL garantir qu'une requête de données d'un Mandataire ne retourne que des données de son propre Reseau lui appartenant ou relevant de sa Visibilite_Descendante, aucune donnée d'un autre Reseau ne pouvant être atteinte.
3. IF une requête vise des données n'appartenant pas au Mandataire et hors de sa Visibilite_Descendante, THEN THE Backend SHALL refuser l'accès sans divulguer l'existence de ces données.
4. WHEN une opération d'écriture échoue partiellement, THE Backend SHALL annuler l'intégralité de l'opération et conserver l'état précédent sans modification partielle.
5. THE Backend SHALL garantir l'isolation des Arbres_Parrainage et des données entre Reseaux distincts.
6. WHEN un Compte quitte le Reseau (désinscription ou départ), THE Backend SHALL rattacher par défaut sa branche (ses Filleuls_Directs) au Parrain du Compte partant (grand-parrain), en préservant la cohérence de l'Arbre_Parrainage et l'absence de cycle.
7. WHERE le rattachement par défaut d'une branche ne correspond pas à la réalité du Reseau, THE Backend SHALL permettre aux Mandataires concernés de réorganiser la branche via le mécanisme de déclaration et de Confirmation_Affiliation (auto-gestion), et à l'Administrateur de corriger l'Arbre_Parrainage.
8. IF le Compte partant n'a pas de Parrain (racine de branche), THEN THE Backend SHALL conserver ses Filleuls_Directs comme nouvelles racines de branche de leur Reseau, sans rattachement ascendant.