# ALDANA V1 — Spécifications de conception

> **Statut** : approuvé par l'utilisateur le 2026-05-18 (cinq sections validées en brainstorming)
> **Objet** : refonte complète du site d'Aurane Aldana (yoga, Versailles) avec ajout d'un système de réservation et paiement en ligne, espace membre et back-office propriétaire
> **Auteur** : Robin Galofaro, accompagné par Claude Code
> **Validation finale par Aurane Aldana** : à programmer (démos intermédiaires #1-#4 prévues, cf §9)

---

## 1. Contexte et objectifs

### 1.1 Le commanditaire

**Aurane Aldana**, professeure de yoga indépendante à Versailles. Activité solo, deux pratiques principales (Yoga, Power Yoga), enseignées dans un lieu partenaire (La Maison Amara, 16b Passages de la Geôle, 78000 Versailles). Propose aussi des cours particuliers à domicile ou en studio, et organise ponctuellement des Week-Ends Bien-Être.

**Situation actuelle (2026-05) :**
- Site WordPress vitrine : https://aldanayoga.com
- Comptes sociaux : Instagram `@aldanaaurane`, Facebook *Aurane Aldana Bien-Être Versailles*, LinkedIn *Aurane Aldana*
- Aucune réservation en ligne ; gestion par téléphone / email / présentiel
- Aucun paiement en ligne
- Aucun espace membre, aucune base clients structurée

### 1.2 Problème à résoudre

1. **Friction opérationnelle** : Aurane gère manuellement les réservations, ce qui consomme du temps et limite la croissance.
2. **Friction client** : les clients potentiels ne peuvent pas réserver ni payer en ligne, ce qui est attendu en 2026 pour un studio yoga urbain.
3. **Absence de récurrence financière** : pas de forfaits/abonnements vendables en ligne, pas de visibilité revenue.
4. **Site vitrine vieillissant** : WordPress non maintenu, identité visuelle à moderniser.

### 1.3 Objectifs V1

1. **Vitrine modernisée** reflétant l'identité d'Aurane (refondue dans une direction *muted dévotionnelle sobre*).
2. **Réservation en ligne** des cours collectifs (calendrier + booking auth).
3. **Paiement en ligne** via Stripe : séance unitaire, carnets, abonnement mensuel illimité, événements.
4. **Espace membre** avec historique, forfaits actifs, factures.
5. **Back-office Aurane** : CRUD cours/pratiques/formules/events, vue bookings, marquage présences, remboursements.

### 1.4 Hors scope V1 (reportés en V2+)

- Multi-profs (l'activité est solo aujourd'hui)
- Multi-lieux (un seul lieu : Maison Amara)
- Application mobile native
- Notifications push, SMS
- Système d'avis/notations
- Blog / Newsletter intégrée (un lien externe Mailchimp suffit V1)
- Programme de parrainage / fidélité
- Multilingue (français uniquement V1)

---

## 2. Positionnement et identité de marque

### 2.1 Nom et naming

- **Nom commercial** : ALDANA
- **Sous-titre / signature** : Le yoga d'Aurane à Versailles
- **Domaine** : `aldanayoga.com` (existant, à migrer)

### 2.2 Direction visuelle

Reprise du mockup Base44 (déjà validé esthétiquement) :
- **Palette** : muted (whitewash + nuances terre/sable), pas de doré (retrait conscient vs identité actuelle)
- **Typographie** : heading légère + italiques sélectives, tracking large (0.15-0.2em sur surtitres uppercase)
- **Animations** : sobres (apparitions à l'entrée du viewport via IntersectionObserver + transitions CSS, pas de framework JS)
- **Photos** : portraits d'Aurane (depuis site actuel et IG), photos du lieu Maison Amara
- **Style général** : minimaliste, presque sacré, contemplative

### 2.3 Ton du copy

**Registre dévotionnel sobre conservé du mockup**, calibré au contexte solo d'Aurane :

| Surtitre mockup | Conservé tel quel ou adapté |
|---|---|
| « L'autel » (= planning) | ✅ Conservé |
| « Notre lignée » (= profs) | ➜ « La transmission » (solo) |
| « Nos pratiques » | ✅ Conservé |
| « Texture de la pratique » | ✅ Conservé |
| « Namaste » (confirmation booking) | ✅ Conservé |
| « Votre première respiration commence ici » | ✅ Conservé en CTA |

### 2.4 Citation signature

> *« Il ne faut jamais sous-estimer l'influence du hasard sur l'existence de tout être »*

Reprise de l'identité actuelle d'Aurane. Posée en pied de page (footer) comme signature personnelle, et éventuellement en fin de hero home.

---

## 3. Arborescence du site

### 3.1 Pages publiques (sans authentification)

| URL | Titre h1 | Surtitre | Contenu principal |
|---|---|---|---|
| `/` | (hero rotatif) | — | Photo hero Aurane, intro 1-2 phrases, aperçu 3 pratiques, aperçu À propos, CTA réserver |
| `/yoga-et-moi` | Aurane | La transmission | Biographie, parcours, philosophie, citation signature |
| `/pratiques` | Modalités | Nos pratiques | Liste des pratiques (Yoga, Power Yoga, …) avec scales intensité/rythme/chaleur |
| `/le-lieu` | La Maison Amara | L'écrin | Photos du studio, ambiance, accès, parking, adresse complète |
| `/planning` | Planning & Réservation | L'autel | Calendrier hebdomadaire des cours collectifs, clic → BookingPanel |
| `/cours-particuliers` | Pratique intime | La rencontre | Présentation 1-1, formulaire de demande de contact |
| `/evenements` | Week-Ends Bien-Être | Le voyage | Liste events à venir, page détail par event, booking complet |
| `/tarifs` | Engagement | L'offrande | Formules : unitaire, carnets, abo mensuel, particulier, events |
| `/contact` | Contact | Le lien | Email, tél, adresse, formulaire générique, plan |

### 3.2 Pages d'authentification (publiques, sans Layout principal)

| URL | Fonction |
|---|---|
| `/login` | Connexion email + password |
| `/register` | Inscription (avec validation email par token) |
| `/mot-de-passe-oublie` | Demande reset (email avec token expirable 1h) |
| `/reinitialiser-mot-de-passe` | Saisie nouveau password (depuis lien email) |

### 3.3 Espace membre (authentification requise, role_ID ≥ 10)

| URL | Titre | Contenu |
|---|---|---|
| `/mon-compte` | Mon sanctuaire | Dashboard : prochaine résa, forfait actif (séances restantes + validité), 1 photo Maison Amara, lien vers les sous-sections |
| `/mon-compte/reservations` | Mes pratiques | Liste à venir (avec bouton annuler si J-24h OK) + historique (consultatif) |
| `/mon-compte/forfaits` | Mes engagements | Forfaits actifs et expirés, lien renouveler |
| `/mon-compte/factures` | Mes reçus | Liste avec download PDF |
| `/mon-compte/profil` | Mon profil | Modif email, password, infos contact |

### 3.4 Back-office Aurane (role_ID = 29, route `/admin/*`)

| URL | Fonction |
|---|---|
| `/admin` | Dashboard : cours du jour, bookings du jour, alertes (cours complets, annulations < 24h, nouveaux paiements, nouvelles demandes 1-1) |
| `/admin/cours` | CRUD cours collectifs, vue planning hebdo/mensuel, duplication semaine type |
| `/admin/pratiques` | CRUD pratiques (Yoga, Power Yoga…) |
| `/admin/evenements` | CRUD Week-Ends Bien-Être |
| `/admin/bookings` | Toutes les résas, filtrable par date/cours, marquage présences/no-show, déclenchement remboursement Stripe |
| `/admin/membres` | Liste membres, fiche détail, historique pratiques par membre, recherche |
| `/admin/forfaits` | CRUD formules tarifaires (carnets/abos), attribution manuelle exceptionnelle |
| `/admin/paiements` | Historique paiements Stripe, factures émises, statut remboursements |
| `/admin/demandes-particuliers` | Demandes 1-1 en attente, marquage statut, réponse (interne) |

### 3.5 Pages légales (publiques, footer)

`/mentions-legales`, `/politique-de-confidentialite`, `/cgv`

Contenu rédigé en P2 (vitrine), validé juridiquement avant prod.

---

## 4. Modèle de données

### 4.1 Vue d'ensemble — 12 tables, 7 domaines

```
AUTH                t_users
CATALOGUE           t_practices, t_subscription_plans, t_events
PLANNING            t_classes
ENGAGEMENT          t_subscriptions, t_payments
RÉSERVATIONS        t_bookings, t_event_bookings
SUPPORT             t_private_requests
INFRASTRUCTURE      t_processed_webhooks, t_site_settings
```

### 4.2 Conventions transverses

- Moteur InnoDB obligatoire (transactions, FK)
- Charset `utf8mb4_unicode_ci` partout
- PK `*_ID` en `INT UNSIGNED AUTO_INCREMENT` (passer à BIGINT seulement si volumétrie le justifie, peu probable V1)
- Tous les `created_at TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP`
- `updated_at` quand pertinent : `TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP`
- FK avec `ON DELETE RESTRICT` par défaut (jamais supprimer en cascade des données financières)
- Indexes systématiques sur FK et colonnes de filtrage temporel
- Migrations idempotentes (`CREATE TABLE IF NOT EXISTS`, helper procédure pour ajouts colonnes)

### 4.3 Schémas détaillés

#### `t_users`

```sql
CREATE TABLE IF NOT EXISTS t_users (
    user_ID         INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    email           VARCHAR(255) NOT NULL UNIQUE,
    password_hash   VARCHAR(255) NOT NULL,
    full_name       VARCHAR(150) NOT NULL,
    phone           VARCHAR(30) NULL,
    role_ID         TINYINT UNSIGNED NOT NULL DEFAULT 10,
    email_verified_at TIMESTAMP NULL DEFAULT NULL,
    email_verification_token VARCHAR(64) NULL,
    password_reset_token VARCHAR(64) NULL,
    password_reset_expires_at DATETIME NULL,
    failed_login_attempts TINYINT UNSIGNED NOT NULL DEFAULT 0,
    locked_until    DATETIME NULL,
    created_at      TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
    updated_at      TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    INDEX idx_email (email),
    INDEX idx_role  (role_ID)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
```

**Rôles** :
- `29` = Admin Studio (Aurane)
- `11` = Professeur (non utilisé V1, prévu V2 multi-profs)
- `10` = Client (membre standard)

#### `t_practices`

```sql
CREATE TABLE IF NOT EXISTS t_practices (
    practice_ID     INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    name            VARCHAR(100) NOT NULL,
    slug            VARCHAR(100) NOT NULL UNIQUE,
    short_description VARCHAR(200) NULL,
    description     TEXT NULL,
    intensity       TINYINT UNSIGNED NULL,
    pace            TINYINT UNSIGNED NULL,
    heat            TINYINT UNSIGNED NULL,
    duration_default_min SMALLINT UNSIGNED NULL,
    image_url       VARCHAR(500) NULL,
    display_order   SMALLINT UNSIGNED NOT NULL DEFAULT 100,
    active          TINYINT(1) NOT NULL DEFAULT 1,
    created_at      TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
    updated_at      TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    INDEX idx_active_order (active, display_order)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
```

#### `t_classes`

```sql
CREATE TABLE IF NOT EXISTS t_classes (
    class_ID        INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    practice_ID     INT UNSIGNED NOT NULL,
    title           VARCHAR(150) NOT NULL,
    class_date      DATE NOT NULL,
    class_time      TIME NOT NULL,
    duration_min    SMALLINT UNSIGNED NOT NULL,
    capacity        TINYINT UNSIGNED NOT NULL,
    spots_left      TINYINT UNSIGNED NOT NULL,
    status          ENUM('scheduled','cancelled','completed') NOT NULL DEFAULT 'scheduled',
    notes           TEXT NULL,
    created_at      TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
    updated_at      TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    FOREIGN KEY (practice_ID) REFERENCES t_practices(practice_ID) ON DELETE RESTRICT,
    INDEX idx_date (class_date),
    INDEX idx_practice_date (practice_ID, class_date)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
```

#### `t_subscription_plans`

```sql
CREATE TABLE IF NOT EXISTS t_subscription_plans (
    plan_ID         INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    name            VARCHAR(100) NOT NULL,
    slug            VARCHAR(100) NOT NULL UNIQUE,
    type            ENUM('unit','pack','monthly_unlimited','annual_unlimited') NOT NULL,
    sessions_count  SMALLINT UNSIGNED NULL,
    validity_days   SMALLINT UNSIGNED NULL,
    price_cents     INT UNSIGNED NOT NULL,
    currency        CHAR(3) NOT NULL DEFAULT 'EUR',
    stripe_price_id VARCHAR(100) NULL,
    description     TEXT NULL,
    display_order   SMALLINT UNSIGNED NOT NULL DEFAULT 100,
    active          TINYINT(1) NOT NULL DEFAULT 1,
    created_at      TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
    updated_at      TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    INDEX idx_active_order (active, display_order)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
```

`sessions_count NULL` = illimité. `validity_days NULL` = sans expiration (rare, plutôt 365 pour carnets, 31 pour abo mensuel, 366 pour annuel).

#### `t_subscriptions`

```sql
CREATE TABLE IF NOT EXISTS t_subscriptions (
    subscription_ID INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    user_ID         INT UNSIGNED NOT NULL,
    plan_ID         INT UNSIGNED NOT NULL,
    sessions_left   SMALLINT UNSIGNED NULL,
    valid_from      DATE NOT NULL,
    valid_until     DATE NOT NULL,
    status          ENUM('active','expired','cancelled','refunded') NOT NULL DEFAULT 'active',
    stripe_subscription_id  VARCHAR(100) NULL,
    stripe_payment_intent_id VARCHAR(100) NULL,
    created_at      TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
    updated_at      TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    FOREIGN KEY (user_ID) REFERENCES t_users(user_ID) ON DELETE RESTRICT,
    FOREIGN KEY (plan_ID) REFERENCES t_subscription_plans(plan_ID) ON DELETE RESTRICT,
    INDEX idx_user_status (user_ID, status),
    INDEX idx_valid_until (valid_until)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
```

#### `t_bookings`

```sql
CREATE TABLE IF NOT EXISTS t_bookings (
    booking_ID      INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    class_ID        INT UNSIGNED NOT NULL,
    user_ID         INT UNSIGNED NOT NULL,
    subscription_ID INT UNSIGNED NULL,
    status          ENUM('confirmed','cancelled','attended','no_show') NOT NULL DEFAULT 'confirmed',
    created_at      TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
    cancelled_at    DATETIME NULL,
    attended_marked_at DATETIME NULL,
    FOREIGN KEY (class_ID) REFERENCES t_classes(class_ID) ON DELETE RESTRICT,
    FOREIGN KEY (user_ID) REFERENCES t_users(user_ID) ON DELETE RESTRICT,
    FOREIGN KEY (subscription_ID) REFERENCES t_subscriptions(subscription_ID) ON DELETE RESTRICT,
    UNIQUE KEY uq_class_user_confirmed (class_ID, user_ID, status),
    INDEX idx_user_created (user_ID, created_at)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
```

`subscription_ID` est **NULLABLE** pour autoriser les cas exceptionnels (cours offert par Aurane, essai gratuit). En usage normal, il est toujours renseigné.

#### `t_payments`

```sql
CREATE TABLE IF NOT EXISTS t_payments (
    payment_ID      INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    user_ID         INT UNSIGNED NOT NULL,
    subscription_ID INT UNSIGNED NULL,
    event_booking_ID INT UNSIGNED NULL,
    amount_cents    INT UNSIGNED NOT NULL,
    currency        CHAR(3) NOT NULL DEFAULT 'EUR',
    type            ENUM('subscription_purchase','subscription_renewal','event_purchase','refund','adjustment') NOT NULL,
    stripe_payment_intent_id VARCHAR(100) NULL,
    stripe_invoice_id VARCHAR(100) NULL,
    stripe_charge_id VARCHAR(100) NULL,
    invoice_pdf_url VARCHAR(500) NULL,
    status          ENUM('pending','succeeded','failed','refunded') NOT NULL DEFAULT 'pending',
    refunded_payment_ID INT UNSIGNED NULL,
    created_at      TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
    updated_at      TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    FOREIGN KEY (user_ID) REFERENCES t_users(user_ID) ON DELETE RESTRICT,
    FOREIGN KEY (subscription_ID) REFERENCES t_subscriptions(subscription_ID) ON DELETE SET NULL,
    FOREIGN KEY (refunded_payment_ID) REFERENCES t_payments(payment_ID) ON DELETE SET NULL,
    INDEX idx_user_created (user_ID, created_at),
    INDEX idx_status (status)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
```

`refunded_payment_ID` = self-FK pour tracer qu'un payment de type `refund` rembourse quel paiement initial.

#### `t_events`

```sql
CREATE TABLE IF NOT EXISTS t_events (
    event_ID        INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    title           VARCHAR(200) NOT NULL,
    slug            VARCHAR(200) NOT NULL UNIQUE,
    subtitle        VARCHAR(200) NULL,
    date_start      DATETIME NOT NULL,
    date_end        DATETIME NOT NULL,
    location        VARCHAR(300) NULL,
    description     TEXT NULL,
    price_cents     INT UNSIGNED NOT NULL,
    currency        CHAR(3) NOT NULL DEFAULT 'EUR',
    stripe_price_id VARCHAR(100) NULL,
    capacity        SMALLINT UNSIGNED NOT NULL,
    spots_left      SMALLINT UNSIGNED NOT NULL,
    image_url       VARCHAR(500) NULL,
    status          ENUM('draft','published','sold_out','completed','cancelled') NOT NULL DEFAULT 'draft',
    created_at      TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
    updated_at      TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    INDEX idx_status_date (status, date_start)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
```

#### `t_event_bookings`

```sql
CREATE TABLE IF NOT EXISTS t_event_bookings (
    event_booking_ID INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    event_ID        INT UNSIGNED NOT NULL,
    user_ID         INT UNSIGNED NOT NULL,
    payment_ID      INT UNSIGNED NULL,
    status          ENUM('confirmed','cancelled','refunded') NOT NULL DEFAULT 'confirmed',
    notes           TEXT NULL,
    created_at      TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
    cancelled_at    DATETIME NULL,
    FOREIGN KEY (event_ID) REFERENCES t_events(event_ID) ON DELETE RESTRICT,
    FOREIGN KEY (user_ID) REFERENCES t_users(user_ID) ON DELETE RESTRICT,
    FOREIGN KEY (payment_ID) REFERENCES t_payments(payment_ID) ON DELETE SET NULL,
    UNIQUE KEY uq_event_user_confirmed (event_ID, user_ID, status),
    INDEX idx_user (user_ID)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
```

#### `t_private_requests`

```sql
CREATE TABLE IF NOT EXISTS t_private_requests (
    request_ID      INT UNSIGNED AUTO_INCREMENT PRIMARY KEY,
    user_ID         INT UNSIGNED NULL,
    full_name       VARCHAR(150) NOT NULL,
    email           VARCHAR(255) NOT NULL,
    phone           VARCHAR(30) NULL,
    preferred_dates TEXT NULL,
    message         TEXT NULL,
    status          ENUM('new','in_progress','scheduled','declined','closed') NOT NULL DEFAULT 'new',
    aurane_response TEXT NULL,
    created_at      TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
    updated_at      TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
    FOREIGN KEY (user_ID) REFERENCES t_users(user_ID) ON DELETE SET NULL,
    INDEX idx_status_created (status, created_at)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
```

`user_ID` nullable car le formulaire est accessible aux non-connectés (visiteurs anonymes).

#### `t_processed_webhooks`

```sql
CREATE TABLE IF NOT EXISTS t_processed_webhooks (
    event_id        VARCHAR(100) PRIMARY KEY,
    event_type      VARCHAR(100) NOT NULL,
    processed_at    TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP,
    INDEX idx_type_date (event_type, processed_at)
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
```

#### `t_site_settings`

```sql
CREATE TABLE IF NOT EXISTS t_site_settings (
    setting_key     VARCHAR(100) PRIMARY KEY,
    setting_value   TEXT NULL,
    setting_type    ENUM('text','html','image_url','json','int','bool') NOT NULL DEFAULT 'text',
    description     VARCHAR(300) NULL,
    updated_at      TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4 COLLATE=utf8mb4_unicode_ci;
```

Clés initiales : `home_hero_title`, `home_hero_subtitle`, `home_hero_image_url`, `about_aurane_bio_html`, `about_aurane_photo_url`, `lieu_address`, `lieu_description_html`, `contact_email`, `contact_phone`, `cancellation_window_hours` (default 24).

### 4.4 Données initiales (seeds)

Migration `012_seed_initial_data.sql` :
- 1 user admin : Aurane Aldana (`aldana.aurane@hotmail.fr`, role_ID=29, password à définir au lancement via /admin)
- 2 pratiques : Yoga, Power Yoga (intensité/rythme/chaleur à définir avec Aurane)
- 3 formules hypothèses (à valider avec Aurane) :
  - Unitaire : 25 € (sessions_count=1, validity_days=30)
  - Carnet 10 : 180 € (sessions_count=10, validity_days=365)
  - Abo mensuel illimité : 110 €/mois (sessions_count=NULL, validity_days=31, stripe_subscription)
- Settings de base (citations, infos lieu, fenêtre annulation = 24h)

---

## 5. Flux fonctionnels

### 5.1 Achat de forfait

```
1. User non-connecté : /tarifs → choix formule → CTA → redirect /login?return=/tarifs#plan-<slug>
2. User connecté : /tarifs → choix formule → POST /checkout/start avec plan_ID
3. Backend :
   a. Vérifie plan actif et user authentifié
   b. Crée Stripe Checkout Session (mode 'payment' ou 'subscription' selon plan.type)
   c. Stocke session_id côté serveur (table temp ou session PHP)
   d. Retourne URL Stripe Checkout
4. User redirigé vers checkout.stripe.com, paye
5. Stripe redirige vers /checkout/success?session_id=cs_xxx
6. EN PARALLÈLE : Stripe POST /api/webhook/stripe avec event 'checkout.session.completed'
7. Webhook handler :
   a. Vérifie signature Stripe
   b. Vérifie idempotence (t_processed_webhooks)
   c. Transaction :
      - INSERT t_payments (status='succeeded')
      - INSERT t_subscriptions (status='active', sessions_left, valid_from, valid_until)
      - INSERT t_processed_webhooks
   d. Email confirmation
8. User sur /checkout/success voit confirmation + CTA /mon-compte/forfaits
```

### 5.2 Réservation d'une séance

```
1. User /planning → clic sur cours → BookingPanel slide-in
2. Si non-connecté : "Se connecter pour réserver" → /login?return=/planning#class-<id>
3. Si connecté SANS forfait actif : "Voir les formules" → /tarifs
4. Si connecté AVEC forfait actif :
   a. Affiche "Au nom de <full_name>, avec <plan.name> (<sessions_left> séances restantes)"
   b. Bouton "Confirmer la réservation"
5. POST /api/booking/create {class_ID}
6. Backend transaction SQL atomique :
   START TRANSACTION;
   SELECT spots_left FROM t_classes WHERE class_ID=? FOR UPDATE;
   IF spots_left <= 0 → ROLLBACK + 409 'class_full';
   SELECT sessions_left, valid_until FROM t_subscriptions
     WHERE user_ID=? AND status='active' AND valid_until>=class_date
     ORDER BY valid_until ASC LIMIT 1 FOR UPDATE;
   IF aucune ligne → ROLLBACK + 409 'no_active_subscription';
   IF sessions_left=0 → ROLLBACK + 409 'subscription_exhausted';
   UPDATE t_classes SET spots_left=spots_left-1 WHERE class_ID=? AND spots_left>0;
   IF ROW_COUNT()!=1 → ROLLBACK + 409 'concurrent_booking';
   UPDATE t_subscriptions SET sessions_left=COALESCE(sessions_left,1)-1
     WHERE subscription_ID=? AND (sessions_left IS NULL OR sessions_left>0);
   INSERT INTO t_bookings (class_ID, user_ID, subscription_ID, status)
     VALUES (?, ?, ?, 'confirmed');
   COMMIT;
7. Email confirmation
8. Retour 200 → confirmation UI + redirect /mon-compte/reservations
```

### 5.3 Annulation par le membre

```
1. /mon-compte/reservations → clic "Annuler" sur résa
2. Check JS : si class_date - NOW() < 24h → bouton désactivé avec tooltip
3. POST /api/booking/cancel {booking_ID}
4. Backend (vérification serveur de la fenêtre J-24h, JS pas fiable) :
   START TRANSACTION;
   SELECT b.*, c.class_date, c.class_time, s.cancellation_window_hours
     FROM t_bookings b
     JOIN t_classes c ON c.class_ID=b.class_ID
     CROSS JOIN (SELECT setting_value AS cancellation_window_hours
                 FROM t_site_settings WHERE setting_key='cancellation_window_hours') s
     WHERE b.booking_ID=? AND b.user_ID=? AND b.status='confirmed' FOR UPDATE;
   IF aucune ligne → ROLLBACK + 404;
   IF TIMESTAMP(class_date,class_time) - NOW() < INTERVAL window HOUR
     → ROLLBACK + 403 'cancellation_window_passed';
   UPDATE t_bookings SET status='cancelled', cancelled_at=NOW() WHERE booking_ID=?;
   UPDATE t_classes SET spots_left=spots_left+1 WHERE class_ID=?;
   UPDATE t_subscriptions SET sessions_left=sessions_left+1
     WHERE subscription_ID=? AND sessions_left IS NOT NULL;
   COMMIT;
5. Email confirmation annulation
```

### 5.4 Annulation par Aurane (cours)

Action depuis `/admin/cours/<id>` → bouton "Annuler ce cours".
- Confirmation modale (irreversible)
- UPDATE t_classes status='cancelled'
- Pour chaque booking status='confirmed' :
  - UPDATE status='cancelled', cancelled_at=NOW()
  - UPDATE t_subscriptions sessions_left+=1
- Email mass aux concernés avec excuses et indication de re-booker

### 5.5 Marquage présence

Post-cours, depuis `/admin/bookings?class_id=X` :
- Liste les bookings status='confirmed' du cours
- Boutons : "Présent" / "Absent" par membre
- UPDATE status='attended' ou 'no_show', attended_marked_at=NOW()
- Pour 'no_show' : la séance reste consommée (pas de réincrément)

### 5.6 Remboursement par Aurane

Depuis `/admin/paiements/<id>` ou `/admin/bookings/<id>` :
- Bouton "Rembourser" (avec confirmation modale)
- POST /api/admin/refund {payment_ID, amount_cents, reason}
- Backend :
  1. Vérifie role_ID=29
  2. Appelle Stripe Refunds API
  3. Transaction :
     - INSERT t_payments (type='refund', refunded_payment_ID=X, amount_cents=-Y, status='pending')
     - UPDATE t_payments WHERE payment_ID=X SET status='refunded'
     - UPDATE t_subscriptions WHERE subscription_ID=Y SET status='refunded' (si applicable)
  4. Webhook ultérieur 'charge.refunded' confirmera et passera le status='succeeded' sur la ligne de refund

### 5.7 Demande cours particulier

```
1. /cours-particuliers → formulaire (nom, email, tél, dates souhaitées, message)
2. POST /api/private-request/create
3. Backend :
   - Validation (email valid, message non vide)
   - Anti-spam : honeypot field + rate limit IP (3 req / 15 min)
   - INSERT t_private_requests (user_ID si connecté, status='new')
   - Email à aldana.aurane@hotmail.fr avec lien direct /admin/demandes-particuliers/<id>
   - Confirmation user
4. Aurane gère depuis /admin/demandes-particuliers : status='in_progress' → 'scheduled' / 'declined' / 'closed'
```

### 5.8 Inscription à un événement

```
1. /evenements → /evenements/<slug>
2. Clic "M'inscrire" → si non-connecté → /login?return=...
3. POST /event/checkout/start {event_ID}
4. Stripe Checkout one-shot
5. Webhook 'checkout.session.completed' :
   - Vérifie event.spots_left > 0 (sinon refund auto)
   - INSERT t_payments
   - INSERT t_event_bookings (status='confirmed')
   - UPDATE t_events spots_left-=1
   - Email confirmation
```

### 5.9 Webhooks Stripe — sécurité critique

- **Endpoint** : `POST /api/webhook/stripe`
- **Vérification signature** obligatoire (header `Stripe-Signature` + secret depuis `.env`)
- **Idempotence** : `t_processed_webhooks` (event_id PK), si déjà traité → 200 immédiat
- **Events traités V1** :
  - `checkout.session.completed` → création subscription/event_booking
  - `invoice.paid` (abos mensuels) → extension valid_until
  - `invoice.payment_failed` → notif Aurane + email user "moyen de paiement échoué"
  - `charge.refunded` → confirmation des refunds
- **Log** systématique dans `storage/logs/stripe.log`
- **Réponse** : 200 sur succès traitement, 4xx sur signature invalide. Toutes les erreurs internes → 500 (Stripe retry).

---

## 6. Architecture technique

### 6.1 Stack

| Couche | Technologie | Version |
|---|---|---|
| Langage | PHP | 8.2+ (8.4 en local WAMP) |
| DBMS | MySQL / MariaDB | 8.0+ / 10.6+ |
| Serveur web | Apache | 2.4 avec mod_rewrite, mod_headers, mod_deflate, mod_expires |
| Frontend | HTML5 / CSS3 / JS vanilla | ES2020+ |
| Gestionnaire deps | Composer | 2.x |
| Tests | PHPUnit | 10+ |
| Style code | PSR-12 (squizlabs/php_codesniffer) |
| Analyse statique | PHPStan | niveau 6+ |

### 6.2 Dépendances Composer

```json
{
    "require": {
        "php": "^8.2",
        "vlucas/phpdotenv": "^5.6",
        "monolog/monolog": "^3.5",
        "stripe/stripe-php": "^15.0",
        "symfony/mailer": "^7.0",
        "phpoffice/phpword": "^1.3"
    },
    "require-dev": {
        "phpunit/phpunit": "^10.5",
        "phpstan/phpstan": "^1.10",
        "squizlabs/php_codesniffer": "^3.8"
    }
}
```

`phpoffice/phpword` réservé pour génération CNIL/RGPD docx (cf préférences user).

### 6.3 Structure dossiers (doc 01 starter kit)

```
_ALDANA/
├── public/                    DocumentRoot
│   ├── index.php              Front controller unique
│   ├── .htaccess
│   └── assets/
│       ├── css/
│       ├── js/
│       └── img/
├── src/                       Namespace App\
│   ├── Controllers/
│   │   ├── PublicController.php
│   │   ├── AuthController.php
│   │   ├── AccountController.php
│   │   ├── BookingController.php
│   │   ├── CheckoutController.php
│   │   ├── WebhookController.php
│   │   └── Admin/
│   ├── Models/
│   ├── Services/
│   │   ├── BookingService.php
│   │   ├── StripeService.php
│   │   ├── EmailService.php
│   │   ├── AuthService.php
│   │   └── SettingsService.php
│   ├── Repositories/
│   │   ├── UserRepository.php
│   │   ├── ClassRepository.php
│   │   ├── BookingRepository.php
│   │   └── ...
│   ├── Middleware/
│   │   ├── AuthMiddleware.php
│   │   ├── AdminMiddleware.php
│   │   └── CsrfMiddleware.php
│   ├── Helpers/
│   │   ├── functions.php
│   │   └── Database.php       Singleton PDO
│   └── Exceptions/
├── config/
│   ├── app.php
│   ├── database.php
│   ├── routes.php
│   └── stripe.php
├── views/
│   ├── layouts/
│   │   ├── public.php
│   │   ├── auth.php
│   │   ├── account.php
│   │   └── admin.php
│   ├── partials/
│   └── pages/
│       ├── public/            Vitrine
│       ├── auth/
│       ├── account/
│       └── admin/
├── storage/
│   ├── logs/
│   ├── cache/
│   ├── sessions/
│   └── uploads/
├── database/
│   ├── migrations/
│   │   ├── 001_create_users.sql
│   │   ├── ...
│   │   └── 012_seed_initial_data.sql
│   └── seeds/
├── tests/
│   ├── Unit/
│   └── Feature/
├── docs/superpowers/specs/    ← ce document
├── _mockup_base44/            mockup React conservé en référence visuelle
├── .claude/
│   ├── commands/
│   │   ├── situation.md       (déjà créé)
│   │   └── err.md             (déjà créé)
│   └── agents/                à créer P0
├── vendor/                    Composer (gitignored)
├── .env                       Secrets (gitignored)
├── .env.example
├── .gitignore
├── composer.json
├── composer.lock
├── phpunit.xml
├── CLAUDE.md                  (déjà créé)
└── README.md
```

### 6.4 Conventions de code

- `declare(strict_types=1);` en tête de chaque fichier PHP
- PSR-12 strict
- `htmlspecialchars($var ?? '', ENT_QUOTES, 'UTF-8')` partout en sortie HTML
- PDO prepared statements OBLIGATOIRES, jamais de concat SQL
- Tables `t_`, colonnes snake_case, PK `*_ID`
- Routes URL kebab-case français (`/cours-particuliers`, `/mes-reservations`)
- **Timezone** : PHP `date_default_timezone_set('Europe/Paris')` + MySQL session `SET time_zone = 'Europe/Paris'` à chaque connexion PDO. Toutes les comparaisons date/heure (J-24h, validité forfait, etc.) reposent dessus
- Vues PHP natif (pas de moteur de template V1, PHP suffit pour vitrine simple)
- JS : ES modules dans `public/assets/js/`, pas de framework, IntersectionObserver pour animations
- CSS : 1 fichier global `public/assets/css/aldana.css` + variables CSS pour la palette

---

## 7. Sécurité

### 7.1 Authentification

- Mots de passe hashés via `password_hash($pw, PASSWORD_ARGON2ID)`, vérifiés via `password_verify`
- Sessions PHP avec `cookie_httponly=1`, `cookie_secure=1` (HTTPS), `cookie_samesite=Lax`, `use_strict_mode=1`
- Régénération `session_id` à chaque login (`session_regenerate_id(true)`)
- Validation email obligatoire à l'inscription via token signé (expirable 24h)
- Reset password : token signé, expirable 1h, single-use
- Rate limit login : 5 tentatives échouées / 15 min → lockout 1h (champs `failed_login_attempts`, `locked_until` sur `t_users`)

### 7.2 CSRF

Token CSRF obligatoire sur tous les POST applicatifs (sauf `/api/webhook/stripe` qui a sa propre signature) :
- Génération à l'init de session, stockage `$_SESSION['csrf_token']`
- Champ `<input type="hidden" name="_csrf">` dans tous les formulaires
- Vérification middleware `CsrfMiddleware` côté backend
- Header `X-CSRF-Token` accepté pour les requêtes AJAX

### 7.3 XSS

- Toute sortie utilisateur via `htmlspecialchars($var ?? '', ENT_QUOTES, 'UTF-8')`
- Contenus admin (description pratique, description event) → `strip_tags($input, '<p><br><strong><em><ul><ol><li><a>')` en V1 (whitelist minimaliste). HTMLPurifier prévu V1.1 si Aurane demande plus de richesse
- Content-Security-Policy header strict côté `.htaccess`

### 7.4 SQL injection

- 100% des requêtes en prepared statements PDO
- Audit phpstan + revue manuelle de chaque controller à la fin de chaque phase

### 7.5 Webhooks Stripe

- Signature obligatoire (cf §5.9)
- Idempotence via t_processed_webhooks
- Validation event payload via SDK `\Stripe\Webhook::constructEvent()`

### 7.6 Apache / .htaccess

Reprise du template doc 01 :
- Headers : X-Content-Type-Options, X-Frame-Options, Referrer-Policy, Permissions-Policy, HSTS
- Désactivation listing dossiers, `Options -Indexes`
- Masquer `X-Powered-By`
- Blocage `.env`, `composer.*`, fichiers commençant par `.`

### 7.7 RGPD

- Politique de confidentialité conforme rédigée en P2
- Consentement cookies (cookie technique de session seul → pas de bannière obligatoire en V1 si pas de tracking ; si Google Analytics ajouté → bannière)
- Droit d'accès, rectification, suppression : routes admin pour exécuter ces demandes
- Suppression compte par l'utilisateur lui-même : page `/mon-compte/profil` → "Supprimer mon compte" → soft delete (`status='deleted'`, données anonymisées)
- Conservation : factures 10 ans (obligation comptable), reste 3 ans après dernière activité

### 7.8 HTTPS

- Obligatoire en prod, Let's Encrypt
- Redirection 301 HTTP → HTTPS via .htaccess
- HSTS max-age=31536000

---

## 8. Phasage du développement

### 8.1 Vue d'ensemble

| Phase | Charge (jours) | Cumul | Livrable |
|---|---|---|---|
| P0 Setup | 1-2 | 1-2 | Squelette PHP, deps installées, git init, "Hello World" répond |
| P1 BDD | 2-3 | 3-5 | 12 migrations + seeds, base aldana alimentée localement |
| P2 Vitrine | 5-7 | 8-12 | 9 pages publiques en ligne (preview), démo Aurane #1 |
| P3 Auth | 4-5 | 12-17 | Inscription, login, espace membre vide |
| P4 Booking | 3-4 | 15-21 | Résa fonctionnelle avec forfait BDD-seedé, démo #2 |
| P5 Stripe | 6-8 | 21-29 | Paiement complet en mode TEST, démo #3 |
| P6 Admin | 6-8 | 27-37 | Admin Aurane CRUD complet, démo #4 |
| P7 Events + 1-1 | 2-3 | 29-40 | Périmètre fonctionnel V1 complet |
| P8 Prod | 3-5 | **32-45 j** | aldanayoga.com en production |

→ **~6 à 9 semaines de dev effectif** selon disponibilité Robin.

### 8.2 Détail des phases

#### P0 — Setup & nettoyage (1-2 j)

1. Déplacer le mockup React actuel (`src/`, `package.json`, `vite.config.js`, etc.) dans `_mockup_base44/` pour garder en référence visuelle
2. Créer arborescence PHP PSR-4 (cf §6.3)
3. `composer init` + installation des deps (cf §6.2)
4. `.htaccess`, `public/index.php` front controller (template doc 01)
5. `.env.example` documenté, `.gitignore`, `.env` local rempli
6. Database singleton (`src/Helpers/Database.php`)
7. Logger Monolog initial
8. Router simple basé sur tableau de routes (`config/routes.php`)
9. `git init`, premier commit (incluant ce spec)
10. BDD locale `aldana` créée vide
11. Vérification : `php -S localhost:8000 -t public/` répond avec un "Hello ALDANA" sur `/`

#### P1 — Migrations & seeds (2-3 j)

1. Helper Migration runner (`php database/migrate.php`)
2. Écriture des 11 fichiers `001_*.sql` à `011_*.sql` (cf §4.3), pattern idempotent
3. `012_seed_initial_data.sql` avec données hypothèses Aurane
4. Tests : drop + migrate complet, vérification dumps
5. Documentation : README.md section "BDD locale"

#### P2 — Vitrine publique (5-7 j)

1. Layout public (Navbar + Footer) en HTML/CSS, animations CSS pures
2. Variables CSS pour palette (à finaliser visuel)
3. 9 pages publiques (cf §3.1) en PHP natif rendu server-side
4. Responsive mobile-first (breakpoints : 640, 768, 1024, 1280)
5. SEO basique (title, meta description, OG, canonical)
6. Pages légales (mentions, RGPD, CGV) avec contenu boilerplate à compléter avant prod
7. Sitemap.xml, robots.txt
8. Reprise du contenu textuel du site WordPress actuel (Yoga/Power Yoga descriptions, bio Aurane)
9. **Démo Aurane #1** : URL preview pour validation visuelle

#### P3 — Auth & espace membre (4-5 j)

1. AuthController : routes login/register/forgot/reset
2. AuthService : password hashing, token generation, mail
3. EmailService basé sur Symfony Mailer (config SMTP via .env)
4. Validation email à l'inscription (mail + token + clic confirmation)
5. CSRF middleware
6. AccountController : 5 pages /mon-compte/*
7. Rate limit login

#### P4 — Booking core (3-4 j)

1. BookingService avec la transaction SQL atomique
2. /planning page (server-side render du calendrier semaine en cours, navigation prev/next)
3. BookingPanel (sidebar slide-in via CSS + JS minimal)
4. /api/booking/create, /api/booking/cancel
5. Politique J-24h côté JS + serveur
6. Tests PHPUnit Feature sur le flux critique (3 tests minimum : succès, complet, forfait expiré, race condition concurrente)
7. Email confirmation/annulation
8. **Démo Aurane #2** : test résa avec forfait inséré manuellement

#### P5 — Stripe & paiements (6-8 j)

1. StripeService (init client, création checkout session, refunds)
2. CheckoutController + routes /checkout/start, /checkout/success
3. WebhookController + route /api/webhook/stripe avec signature
4. t_processed_webhooks pour idempotence
5. Dispatch des 4 events (checkout.session.completed, invoice.paid, invoice.payment_failed, charge.refunded)
6. Génération factures (Stripe Invoices natives, URL stockée dans t_payments)
7. /tarifs page interactive (lien checkout par formule)
8. /mon-compte/forfaits page (état + bouton renouveler)
9. /mon-compte/factures page (liste + download)
10. Bouton refund dans /admin/bookings (préfigure P6)
11. Tests E2E Stripe en mode TEST
12. **Démo Aurane #3** : flux paiement complet

#### P6 — Admin Aurane (6-8 j)

1. AdminMiddleware (vérif role_ID=29)
2. AdminLayout
3. /admin dashboard avec widgets (cours du jour, nouveaux paiements, alertes)
4. /admin/cours : liste, CRUD, duplication semaine type
5. /admin/pratiques : CRUD
6. /admin/evenements : CRUD (avant P7)
7. /admin/forfaits : CRUD formules (sync Stripe Prices via création/désactivation)
8. /admin/bookings : liste filtrable, marquage présences, déclenchement refund
9. /admin/membres : liste + fiche détail
10. /admin/paiements : historique consultatif
11. /admin/demandes-particuliers : kanban (new/in_progress/scheduled/declined/closed)
12. **Démo Aurane #4** : tour complet de l'admin

#### P7 — Events + cours particuliers (2-3 j)

1. /evenements liste + /evenements/<slug> détail
2. Flux booking event + paiement Stripe (réutilise infrastructure P5)
3. /cours-particuliers avec formulaire + anti-spam
4. EmailService notification Aurane sur nouvelle demande 1-1
5. Tests fonctionnels

#### P8 — Production (3-5 j)

1. Choix hébergement définitif (Hostinger / OVH mutualisé ou VPS)
2. Création BDD prod, import schéma + seed Aurane
3. Migration DNS aldanayoga.com (CNAME ou A record selon hébergeur)
4. SSL Let's Encrypt
5. Bascule Stripe en mode LIVE, création des Stripe Prices live
6. Configuration webhook Stripe LIVE pointant vers prod
7. Backups quotidiens automatisés (cron dump + rotation 30j)
8. Monitoring : tail logs cron toutes les heures, alerte mail si pattern critique
9. Tests E2E manuels (parcours visiteur → membre → réservation → paiement → annulation → renouvellement)
10. Formation Aurane (1h Zoom) sur l'admin
11. Documentation utilisateur (README admin, FAQ)
12. Bascule production

---

## 9. Risques et mitigations

| # | Risque | Probabilité | Impact | Mitigation |
|---|---|---|---|---|
| R1 | Aurane invalide la direction visuelle dévotionnelle après dev | Moyen | Élevé | Démo #1 dès P2 pour cadrer tôt, screenshots du mockup envoyés AVANT P0 |
| R2 | Hébergement mutualisé bloque les webhooks Stripe entrants (rare, mais OVH/Hostinger ont des limites) | Faible | Élevé | Test webhook entrant dès P0 sur hébergement cible, plan B = VPS Hetzner si bloqué |
| R3 | Tarifs et formules hypothèses différentes de la réalité Aurane | Élevé | Faible | Confirmer dès P1 avec Aurane, données seed faciles à modifier |
| R4 | Volumétrie : si Aurane prend 100+ clients d'un coup | Faible | Faible | Schéma scalable jusqu'à 10k bookings/mois sans souci |
| R5 | Bug critique en prod sur le décrément `spots_left` (race condition) | Faible | Élevé | Transaction SQL rigoureusement testée (PHPUnit Feature P4) + monitoring logs |
| R6 | Aurane ne sait pas / ne veut pas utiliser l'admin | Moyen | Élevé | Démo intermédiaires + formation finale P8 + UX admin pensée pour utilisateur non-tech |
| R7 | Migration WordPress actuel : perte de référencement SEO | Moyen | Moyen | Redirections 301 systématiques de l'ancien site vers le nouveau (anciennes URLs → nouvelles), sitemap soumis à Search Console |
| R8 | Délai dev sous-estimé (6-9 semaines deviennent 12) | Moyen | Faible | Découpage en phases démontrables = pas de cliff edge, ajustement scope possible en P5 ou P6 |

---

## 10. Hypothèses à valider avec Aurane

Liste des points où le spec a fait des choix par défaut, à valider en démo #1 ou avant si possible :

1. **Direction visuelle** : muted + dévotionnel sobre validé (pas le doré). À confirmer.
2. **Pratiques proposées** : Yoga + Power Yoga uniquement, ou ajout d'autres styles (Yin, Restorative, Vinyasa) ?
3. **Tarifs hypothèse** (à valider) :
   - Unitaire : 25 €
   - Carnet 10 séances : 180 € (validité 1 an)
   - Abo mensuel illimité : 110 €/mois
   - Cours particulier : tarif sur devis (V1)
   - Week-Ends Bien-Être : tarif variable par event
4. **Fenêtre d'annulation** : 24h par défaut. Autre valeur ?
5. **Capacité par cours** : à définir par cours dans admin (pas de défaut global). Estimation Maison Amara : 8-12 places ?
6. **Email expéditeur** : `aldana.aurane@hotmail.fr` (actuel) ou nouvel email `contact@aldanayoga.com` ?
7. **Mailing list / newsletter** : intégré V1 (Mailchimp embed) ou V2 ?
8. **Multilingue** : français seul confirmé V1 ?

Ces points sont à valider en priorité car ils impactent les seeds P1 et le contenu P2.

---

## 11. Critères de succès V1

La V1 est considérée livrée quand :

- [ ] aldanayoga.com pointe sur le nouveau site en production
- [ ] Aurane peut créer/éditer/annuler un cours en moins de 2 minutes depuis admin
- [ ] Un client peut s'inscrire, acheter un forfait, et réserver un cours en moins de 5 minutes
- [ ] Le décrément de `spots_left` n'a jamais produit de surbooking (auditable via tests + logs)
- [ ] Les webhooks Stripe sont traités avec une latence < 5 secondes en moyenne
- [ ] Les factures sont téléchargeables depuis l'espace membre
- [ ] Aurane peut rembourser un paiement en moins de 1 minute
- [ ] Le site obtient un score Lighthouse mobile : Performance ≥ 80, Accessibilité ≥ 90, SEO ≥ 90, Best Practices ≥ 90 (desktop ≥ 90 partout)
- [ ] HTTPS strict, HSTS activé, headers de sécurité conformes
- [ ] Aucune dépendance npm/Node en production (full PHP cohérent avec écosystème user)
- [ ] CLAUDE.md, `/situation`, `/err` opérationnels (déjà ✓)

---

## 12. Suite — Plan d'implémentation

Une fois ce spec validé par l'utilisateur, l'étape suivante est la rédaction d'un **plan d'implémentation détaillé** (via le skill `writing-plans`) qui découpera chaque phase en tâches concrètes avec ordre d'exécution et dépendances.

Le plan d'implémentation servira ensuite de base au développement effectif, possiblement avec dispatching d'agents parallèles via `subagent-driven-development` pour les phases découpables en tâches indépendantes (ex: les pages publiques de P2 peuvent être développées en parallèle).
