# ALDANA Phase 0 — Setup & Scaffolding Implementation Plan

> **For agentic workers:** REQUIRED SUB-SKILL: Use superpowers:subagent-driven-development (recommended) or superpowers:executing-plans to implement this plan task-by-task. Steps use checkbox (`- [ ]`) syntax for tracking.

**Goal:** Transformer le mockup Base44 React en squelette PHP PSR-4 fonctionnel, avec Composer installé, MySQL locale `aldana` opérationnelle, et premier commit Git incluant tous les artefacts existants (spec, CLAUDE.md, commands).

**Architecture:** Reprise du starter kit PHP du playbook (`Dropbox\01_starter_kit_arborescence.md`) : `public/` comme seul DocumentRoot, namespace `App\` mappé sur `src/`, configuration par `config/*.php`, secrets dans `.env` (gitignored), front controller unique `public/index.php` routant via tableau de routes simple.

**Tech Stack:** PHP 8.4 (WAMP local), Composer 2.x, MySQL 9.1 (WAMP), Apache 2.4 (intégration en P8), vlucas/phpdotenv 5.6+, monolog/monolog 3.5+, stripe/stripe-php 15+, symfony/mailer 7+, phpoffice/phpword 1.3+

**Pré-requis environnement :**

- Windows + WAMP64 actif (PHP 8.4.0 + MySQL 9.1.0)
- Composer 2.x dans le PATH (vérification : `composer --version`)
- Git dans le PATH (vérification : `git --version`)
- `php` et `mysql` dans le PATH OU adapter les commandes par les chemins WAMP (`C:\wamp64\bin\php\php8.4.0\php.exe`, `C:\wamp64\bin\mysql\mysql9.1.0\bin\mysql.exe`)
- Working directory pour toutes les commandes : `C:\Users\rgalo\Dropbox\_ALDANA\`

---

## File Structure

Fichiers et dossiers créés ou touchés durant P0 :

```
_ALDANA/
├── _mockup_base44/              ← NOUVEAU (déplacement, Task 1)
│   └── [contenu React déplacé ici depuis src/, etc.]
├── public/                      ← NOUVEAU
│   ├── index.php                ← Task 14
│   ├── .htaccess                ← Task 8
│   └── assets/{css,js,img}/     ← Task 3 (vides avec .gitkeep)
├── src/                         ← NOUVEAU (nettoyé du React, Task 2)
│   ├── Controllers/.gitkeep
│   ├── Models/.gitkeep
│   ├── Services/.gitkeep
│   ├── Repositories/.gitkeep
│   ├── Middleware/.gitkeep
│   ├── Exceptions/.gitkeep
│   └── Helpers/
│       ├── Database.php         ← Task 11
│       └── functions.php        ← Task 12
├── config/                      ← NOUVEAU
│   ├── app.php                  ← Task 9
│   ├── database.php             ← Task 10
│   ├── routes.php               ← Task 13
│   └── stripe.php               ← Task 9 (avec app.php)
├── views/                       ← NOUVEAU
│   ├── layouts/.gitkeep
│   ├── partials/.gitkeep
│   └── pages/.gitkeep
├── storage/                     ← NOUVEAU
│   ├── logs/.gitkeep
│   ├── cache/.gitkeep
│   ├── sessions/.gitkeep
│   └── uploads/.gitkeep
├── database/                    ← NOUVEAU
│   ├── migrations/.gitkeep      (rempli en P1)
│   └── seeds/.gitkeep
├── tests/                       ← NOUVEAU
│   ├── Unit/.gitkeep
│   └── Feature/.gitkeep
├── docs/superpowers/            ← existe déjà
│   ├── specs/2026-05-18-aldana-v1-design.md   ← existe
│   └── plans/2026-05-18-aldana-p0-setup.md    ← ce fichier
├── .claude/                     ← existe déjà
│   ├── commands/{situation,err}.md           ← existent
│   └── agents/                  ← NOUVEAU (Task 18, 5 fichiers)
├── .env                         ← NOUVEAU (Task 7, GITIGNORED)
├── .env.example                 ← NOUVEAU (Task 7)
├── .gitignore                   ← NOUVEAU (Task 6)
├── composer.json                ← NOUVEAU (Task 4)
├── composer.lock                ← NOUVEAU (Task 5, auto-généré)
├── vendor/                      ← NOUVEAU (Task 5, GITIGNORED)
├── CLAUDE.md                    ← existe déjà
└── README.md                    ← NOUVEAU (Task 19, court)
```

Suppressions (Task 2) :

```
package.json, package-lock.json, node_modules/, vite.config.js,
jsconfig.json, postcss.config.js, tailwind.config.js, eslint.config.js,
components.json, index.html, src/ (le React déplacé en _mockup_base44/),
entities/ (déplacé en _mockup_base44/), desktop.ini (artefact Windows)
```

---

## Task 1: Archiver le mockup Base44 React

**Files:**

- Create dir: `_mockup_base44/`
- Move: `src/` → `_mockup_base44/src/`
- Move: `entities/` → `_mockup_base44/entities/`
- Move: `index.html` → `_mockup_base44/index.html`
- Move: `vite.config.js` → `_mockup_base44/vite.config.js`
- Move: `jsconfig.json` → `_mockup_base44/jsconfig.json`
- Move: `postcss.config.js` → `_mockup_base44/postcss.config.js`
- Move: `tailwind.config.js` → `_mockup_base44/tailwind.config.js`
- Move: `eslint.config.js` → `_mockup_base44/eslint.config.js`
- Move: `components.json` → `_mockup_base44/components.json`
- Move: `package.json` → `_mockup_base44/package.json`
- Move: `package-lock.json` → `_mockup_base44/package-lock.json` (si présent)

- [ ] **Step 1: Créer le dossier d'archivage**

```powershell
New-Item -ItemType Directory -Path "_mockup_base44"
```

- [ ] **Step 2: Déplacer tous les artefacts React/Vite dans le dossier d'archivage**

```powershell
Move-Item -Path src,entities,index.html,vite.config.js,jsconfig.json,postcss.config.js,tailwind.config.js,eslint.config.js,components.json,package.json -Destination _mockup_base44\
if (Test-Path package-lock.json) { Move-Item package-lock.json _mockup_base44\ }
```

- [ ] **Step 3: Vérifier le déplacement (la racine ne contient plus d'artefact React)**

```powershell
Get-ChildItem | Select-Object Name
```

Attendu (entre autres) : `_mockup_base44`, `CLAUDE.md`, `docs`, `.claude`, `cacert.pem`. **Absent** : `src`, `entities`, `index.html`, `package.json`, `vite.config.js`.

- [ ] **Step 4: Vérifier que le mockup est intact dans son nouveau dossier**

```powershell
Get-ChildItem _mockup_base44 | Select-Object Name
```

Attendu : `src`, `entities`, `index.html`, `package.json`, `vite.config.js`, `jsconfig.json`, `postcss.config.js`, `tailwind.config.js`, `eslint.config.js`, `components.json`.

---

## Task 2: Supprimer les artefacts npm/Node de la racine

**Files:**

- Delete: `node_modules/` (à la racine si présent — était précédent à la session)
- Delete: `desktop.ini` (artefact Windows)

- [ ] **Step 1: Supprimer node_modules de la racine s'il existe**

```powershell
if (Test-Path node_modules) { Remove-Item node_modules -Recurse -Force }
```

- [ ] **Step 2: Supprimer le fichier Windows desktop.ini**

```powershell
if (Test-Path desktop.ini) { Remove-Item desktop.ini -Force }
```

- [ ] **Step 3: Vérifier qu'il ne reste plus rien de Node à la racine**

```powershell
Get-ChildItem | Where-Object { $_.Name -in @('node_modules','package.json','package-lock.json','vite.config.js') }
```

Attendu : sortie vide (aucun item retourné).

---

## Task 3: Créer l'arborescence PHP PSR-4

**Files:**

- Create dirs (avec `.gitkeep` dans chacun pour les garder versionnés) :
  - `public/assets/css/`, `public/assets/js/`, `public/assets/img/`
  - `src/Controllers/`, `src/Models/`, `src/Services/`, `src/Repositories/`, `src/Middleware/`, `src/Exceptions/`, `src/Helpers/`
  - `config/`
  - `views/layouts/`, `views/partials/`, `views/pages/`
  - `storage/logs/`, `storage/cache/`, `storage/sessions/`, `storage/uploads/`
  - `database/migrations/`, `database/seeds/`
  - `tests/Unit/`, `tests/Feature/`

- [ ] **Step 1: Créer toute l'arborescence**

```powershell
$dirs = @(
  'public\assets\css','public\assets\js','public\assets\img',
  'src\Controllers','src\Models','src\Services','src\Repositories',
  'src\Middleware','src\Exceptions','src\Helpers',
  'config',
  'views\layouts','views\partials','views\pages',
  'storage\logs','storage\cache','storage\sessions','storage\uploads',
  'database\migrations','database\seeds',
  'tests\Unit','tests\Feature'
)
foreach ($d in $dirs) { New-Item -ItemType Directory -Path $d -Force | Out-Null }
```

- [ ] **Step 2: Ajouter un `.gitkeep` dans chaque dossier vide pour qu'ils soient versionnés**

```powershell
foreach ($d in $dirs) {
  $keep = Join-Path $d '.gitkeep'
  if (-not (Test-Path $keep)) { New-Item -ItemType File -Path $keep | Out-Null }
}
```

- [ ] **Step 3: Vérifier la structure**

```powershell
Get-ChildItem -Recurse -Directory -Depth 2 | Select-Object FullName
```

Attendu : tous les dossiers de la liste ci-dessus présents.

---

## Task 4: Initialiser composer.json

**Files:**

- Create: `composer.json`

- [ ] **Step 1: Créer le fichier composer.json**

```json
{
    "name": "robin/aldana",
    "description": "ALDANA — Site Aurane Aldana yoga Versailles : vitrine + réservation + paiement Stripe",
    "type": "project",
    "license": "proprietary",
    "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"
    },
    "autoload": {
        "psr-4": {
            "App\\": "src/"
        },
        "files": [
            "src/Helpers/functions.php"
        ]
    },
    "autoload-dev": {
        "psr-4": {
            "Tests\\": "tests/"
        }
    },
    "scripts": {
        "test": "phpunit",
        "analyse": "phpstan analyse src --level=6",
        "lint": "phpcs --standard=PSR12 src",
        "serve": "php -S localhost:8000 -t public/"
    },
    "config": {
        "optimize-autoloader": true,
        "sort-packages": true,
        "allow-plugins": {}
    },
    "minimum-stability": "stable",
    "prefer-stable": true
}
```

Écrire ce contenu exact dans `composer.json` à la racine.

- [ ] **Step 2: Vérifier que le JSON est valide**

```powershell
composer validate --no-check-publish
```

Attendu : `./composer.json is valid` ou similaire (warning sur `name` autorisé puisqu'il ne sera pas publié).

---

## Task 5: Installer les dépendances Composer

**Files:**

- Create (auto): `composer.lock`
- Create (auto): `vendor/`

- [ ] **Step 1: Lancer l'installation**

```powershell
composer install
```

Cette commande télécharge toutes les dépendances dans `vendor/` et génère `composer.lock`. Durée typique : 30s-2min selon la bande passante. Le fichier `src/Helpers/functions.php` n'existe pas encore (Task 12), Composer affichera un warning sur l'autoload `files` — ignorer, on créera le fichier en Task 12.

- [ ] **Step 2: Vérifier que vendor est créé**

```powershell
Get-ChildItem vendor | Select-Object Name -First 5
```

Attendu : au moins `autoload.php`, `composer/`, et plusieurs dossiers de vendor (stripe, monolog, symfony, vlucas, phpoffice, etc.).

- [ ] **Step 3: Vérifier qu'aucune vulnérabilité critique n'est signalée**

```powershell
composer audit
```

Attendu : `No security vulnerability advisories found.` Si des vulnérabilités apparaissent, lister précisément avant de continuer.

---

## Task 6: Créer le .gitignore

**Files:**

- Create: `.gitignore`

- [ ] **Step 1: Créer le fichier .gitignore**

```gitignore
# Dépendances
/vendor/
/node_modules/

# Secrets
.env
.env.*
!.env.example

# Storage (mais on garde les .gitkeep)
/storage/logs/*
/storage/cache/*
/storage/sessions/*
/storage/uploads/*
!/storage/logs/.gitkeep
!/storage/cache/.gitkeep
!/storage/sessions/.gitkeep
!/storage/uploads/.gitkeep

# IDE
.idea/
.vscode/
*.sublime-*

# OS
.DS_Store
Thumbs.db
desktop.ini

# Build
/public/build/
/public/hot/

# Tests
.phpunit.result.cache
/coverage/

# Claude local
.claude/settings.local.json
.claude/settings.local.json.tmp.*

# Backups
*.bak.*
*.backup_*
*.tmp.*
```

Écrire ce contenu exact dans `.gitignore` à la racine.

- [ ] **Step 2: Vérifier que le fichier est créé et lisible**

```powershell
Get-Content .gitignore -TotalCount 5
```

Attendu : les 5 premières lignes affichées (commentaire « Dépendances », vide, `/vendor/`, `/node_modules/`, vide).

---

## Task 7: Créer .env.example et .env

**Files:**

- Create: `.env.example`
- Create: `.env` (copie de l'exemple avec valeurs locales)

- [ ] **Step 1: Créer .env.example avec toutes les clés documentées**

```ini
# Application
APP_NAME="ALDANA"
APP_ENV=local
APP_DEBUG=true
APP_URL=http://localhost:8000
APP_KEY=

# Database (MySQL local WAMP)
DB_HOST=127.0.0.1
DB_PORT=3306
DB_NAME=aldana
DB_USER=root
DB_PASS=
DB_CHARSET=utf8mb4

# Mail (à configurer avant P3)
MAIL_DSN=null://null
MAIL_FROM_ADDRESS=contact@aldanayoga.com
MAIL_FROM_NAME="ALDANA"

# Stripe (à configurer en P5, mode TEST d'abord)
STRIPE_PUBLISHABLE_KEY=
STRIPE_SECRET_KEY=
STRIPE_WEBHOOK_SECRET=

# Logging
LOG_LEVEL=debug
LOG_CHANNEL=file

# Timezone
APP_TIMEZONE=Europe/Paris
```

Écrire ce contenu exact dans `.env.example` à la racine.

- [ ] **Step 2: Copier .env.example vers .env pour la config locale**

```powershell
Copy-Item .env.example .env
```

- [ ] **Step 3: Vérifier que .env existe**

```powershell
Test-Path .env
```

Attendu : `True`.

---

## Task 8: Créer public/.htaccess

**Files:**

- Create: `public/.htaccess`

- [ ] **Step 1: Créer le fichier .htaccess**

```apache
# Rewrite vers index.php (toutes les requêtes non-fichier/dossier)
<IfModule mod_rewrite.c>
    RewriteEngine On
    RewriteCond %{REQUEST_FILENAME} !-f
    RewriteCond %{REQUEST_FILENAME} !-d
    RewriteRule ^ index.php [QSA,L]
</IfModule>

# Sécurité — masquer les fichiers sensibles
<FilesMatch "^\.">
    Require all denied
</FilesMatch>

<FilesMatch "(composer\.(json|lock)|package\.json|\.env.*)">
    Require all denied
</FilesMatch>

# Headers de sécurité
<IfModule mod_headers.c>
    Header set X-Content-Type-Options "nosniff"
    Header set X-Frame-Options "SAMEORIGIN"
    Header set Referrer-Policy "strict-origin-when-cross-origin"
    Header set Permissions-Policy "geolocation=(), microphone=(), camera=()"
    Header always set Strict-Transport-Security "max-age=31536000; includeSubDomains" env=HTTPS
    Header unset X-Powered-By
</IfModule>

# Compression
<IfModule mod_deflate.c>
    AddOutputFilterByType DEFLATE text/html text/css text/javascript application/javascript application/json image/svg+xml
</IfModule>

# Cache statique
<IfModule mod_expires.c>
    ExpiresActive On
    ExpiresByType image/jpeg "access plus 1 month"
    ExpiresByType image/png "access plus 1 month"
    ExpiresByType image/webp "access plus 1 month"
    ExpiresByType image/svg+xml "access plus 1 month"
    ExpiresByType text/css "access plus 1 week"
    ExpiresByType application/javascript "access plus 1 week"
    ExpiresByType font/woff2 "access plus 1 year"
</IfModule>

# UTF-8 par défaut
AddDefaultCharset UTF-8

# Désactive listing répertoires
Options -Indexes -MultiViews
```

Écrire ce contenu exact dans `public/.htaccess`.

- [ ] **Step 2: Vérifier la création**

```powershell
Test-Path public\.htaccess
```

Attendu : `True`.

---

## Task 9: Créer config/app.php et config/stripe.php

**Files:**

- Create: `config/app.php`
- Create: `config/stripe.php`

- [ ] **Step 1: Créer config/app.php**

```php
<?php
declare(strict_types=1);

return [
    'name'     => $_ENV['APP_NAME'] ?? 'ALDANA',
    'env'      => $_ENV['APP_ENV'] ?? 'production',
    'debug'    => filter_var($_ENV['APP_DEBUG'] ?? false, FILTER_VALIDATE_BOOLEAN),
    'url'      => $_ENV['APP_URL'] ?? 'http://localhost:8000',
    'timezone' => $_ENV['APP_TIMEZONE'] ?? 'Europe/Paris',
    'key'      => $_ENV['APP_KEY'] ?? '',
];
```

Écrire ce contenu exact dans `config/app.php`.

- [ ] **Step 2: Créer config/stripe.php**

```php
<?php
declare(strict_types=1);

return [
    'publishable_key' => $_ENV['STRIPE_PUBLISHABLE_KEY'] ?? '',
    'secret_key'      => $_ENV['STRIPE_SECRET_KEY'] ?? '',
    'webhook_secret'  => $_ENV['STRIPE_WEBHOOK_SECRET'] ?? '',
];
```

Écrire ce contenu exact dans `config/stripe.php`.

- [ ] **Step 3: Vérifier la syntaxe PHP des deux fichiers**

```powershell
php -l config\app.php
php -l config\stripe.php
```

Attendu pour chacun : `No syntax errors detected in <file>`.

---

## Task 10: Créer config/database.php

**Files:**

- Create: `config/database.php`

- [ ] **Step 1: Créer le fichier**

```php
<?php
declare(strict_types=1);

return [
    'host'    => $_ENV['DB_HOST'] ?? '127.0.0.1',
    'port'    => (int) ($_ENV['DB_PORT'] ?? 3306),
    'name'    => $_ENV['DB_NAME'] ?? 'aldana',
    'user'    => $_ENV['DB_USER'] ?? 'root',
    'pass'    => $_ENV['DB_PASS'] ?? '',
    'charset' => $_ENV['DB_CHARSET'] ?? 'utf8mb4',
    'options' => [
        PDO::ATTR_ERRMODE            => PDO::ERRMODE_EXCEPTION,
        PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC,
        PDO::ATTR_EMULATE_PREPARES   => false,
        PDO::ATTR_PERSISTENT         => false,
    ],
];
```

Écrire ce contenu exact dans `config/database.php`.

- [ ] **Step 2: Vérifier la syntaxe PHP**

```powershell
php -l config\database.php
```

Attendu : `No syntax errors detected in config\database.php`.

---

## Task 11: Créer src/Helpers/Database.php (singleton PDO)

**Files:**

- Create: `src/Helpers/Database.php`

- [ ] **Step 1: Créer la classe singleton**

```php
<?php
declare(strict_types=1);

namespace App\Helpers;

use PDO;
use PDOException;
use RuntimeException;

/**
 * Singleton PDO partagé pour toute l'application.
 * Conforme au pattern BookConnect Database::getInstance()->getConnection().
 */
final class Database
{
    private static ?Database $instance = null;
    private PDO $connection;

    private function __construct()
    {
        $config = require dirname(__DIR__, 2) . '/config/database.php';

        $dsn = sprintf(
            'mysql:host=%s;port=%d;dbname=%s;charset=%s',
            $config['host'],
            $config['port'],
            $config['name'],
            $config['charset']
        );

        try {
            $this->connection = new PDO(
                $dsn,
                $config['user'],
                $config['pass'],
                $config['options']
            );

            // Timezone MySQL alignée avec PHP (cf spec §6.4)
            $this->connection->exec("SET time_zone = '+01:00'");
        } catch (PDOException $e) {
            throw new RuntimeException(
                'Database connection failed: ' . $e->getMessage(),
                (int) $e->getCode(),
                $e
            );
        }
    }

    public static function getInstance(): self
    {
        if (self::$instance === null) {
            self::$instance = new self();
        }
        return self::$instance;
    }

    public function getConnection(): PDO
    {
        return $this->connection;
    }

    private function __clone()
    {
    }

    public function __wakeup()
    {
        throw new RuntimeException('Cannot unserialize singleton');
    }
}
```

Note : `SET time_zone = '+01:00'` est l'offset France hiver (CET). En été (CEST) c'est `+02:00`. Pour gérer ça proprement, l'idéal est `SET time_zone = 'Europe/Paris'` MAIS MySQL doit avoir les tables timezone chargées. En P0 on accepte l'offset fixe ; on rectifie en P1 quand la BDD sera créée et qu'on pourra exécuter `mysql_tzinfo_to_sql`.

- [ ] **Step 2: Vérifier la syntaxe**

```powershell
php -l src\Helpers\Database.php
```

Attendu : `No syntax errors detected in src\Helpers\Database.php`.

---

## Task 12: Créer src/Helpers/functions.php

**Files:**

- Create: `src/Helpers/functions.php`

- [ ] **Step 1: Créer les helpers globaux**

```php
<?php
declare(strict_types=1);

/**
 * Helpers globaux ALDANA. Chargés via composer autoload "files".
 */

if (!function_exists('base_path')) {
    function base_path(string $relative = ''): string
    {
        return dirname(__DIR__, 2) . ($relative !== '' ? DIRECTORY_SEPARATOR . ltrim($relative, '/\\') : '');
    }
}

if (!function_exists('config')) {
    /**
     * Charge un fichier de config et retourne sa valeur (ou une clé).
     * Exemple : config('app.url') ou config('database')
     */
    function config(string $key): mixed
    {
        static $cache = [];
        $parts = explode('.', $key);
        $file = array_shift($parts);

        if (!isset($cache[$file])) {
            $path = base_path('config/' . $file . '.php');
            if (!is_file($path)) {
                throw new RuntimeException("Config file not found: {$file}");
            }
            $cache[$file] = require $path;
        }

        $value = $cache[$file];
        foreach ($parts as $part) {
            if (!is_array($value) || !array_key_exists($part, $value)) {
                return null;
            }
            $value = $value[$part];
        }
        return $value;
    }
}

if (!function_exists('e')) {
    /**
     * Échappement HTML sécurisé. À utiliser sur TOUTE sortie utilisateur.
     */
    function e(?string $value): string
    {
        return htmlspecialchars($value ?? '', ENT_QUOTES, 'UTF-8');
    }
}

if (!function_exists('view')) {
    /**
     * Rendu d'une vue PHP avec données injectées.
     * Exemple : view('pages.home', ['title' => 'Accueil'])
     */
    function view(string $name, array $data = []): string
    {
        $path = base_path('views/' . str_replace('.', '/', $name) . '.php');
        if (!is_file($path)) {
            throw new RuntimeException("View not found: {$name}");
        }
        extract($data, EXTR_SKIP);
        ob_start();
        require $path;
        return (string) ob_get_clean();
    }
}

if (!function_exists('redirect')) {
    function redirect(string $to, int $status = 302): never
    {
        header('Location: ' . $to, true, $status);
        exit;
    }
}

if (!function_exists('csrf_token')) {
    function csrf_token(): string
    {
        if (!isset($_SESSION['csrf_token'])) {
            $_SESSION['csrf_token'] = bin2hex(random_bytes(32));
        }
        return $_SESSION['csrf_token'];
    }
}
```

- [ ] **Step 2: Vérifier la syntaxe**

```powershell
php -l src\Helpers\functions.php
```

Attendu : `No syntax errors detected in src\Helpers\functions.php`.

- [ ] **Step 3: Régénérer l'autoload Composer (maintenant que le fichier existe)**

```powershell
composer dump-autoload -o
```

Attendu : `Generating optimized autoload files` puis `Generated optimized autoload files containing N classes`.

---

## Task 13: Créer config/routes.php

**Files:**

- Create: `config/routes.php`

- [ ] **Step 1: Créer le tableau de routes initial (avec route racine de test)**

```php
<?php
declare(strict_types=1);

/**
 * Table de routage simple : URL → [Controller, action].
 * Sera enrichie au fur et à mesure des phases.
 */

return [
    'GET /' => ['App\Controllers\HelloController', 'index'],
];
```

- [ ] **Step 2: Vérifier la syntaxe**

```powershell
php -l config\routes.php
```

Attendu : `No syntax errors detected in config\routes.php`.

- [ ] **Step 3: Créer le HelloController temporaire pour le smoke test**

```php
<?php
declare(strict_types=1);

namespace App\Controllers;

/**
 * Controller temporaire de smoke test P0. À supprimer en P2 quand
 * PublicController prendra le relais sur la route /.
 */
final class HelloController
{
    public function index(): void
    {
        $appName = config('app.name');
        $appEnv  = config('app.env');
        echo <<<HTML
<!DOCTYPE html>
<html lang="fr">
<head>
    <meta charset="UTF-8">
    <title>{$appName} — Setup OK</title>
    <style>
        body { font-family: system-ui, sans-serif; max-width: 600px; margin: 4rem auto; padding: 0 1rem; line-height: 1.6; }
        h1 { font-weight: 300; letter-spacing: 0.1em; }
        code { background: #f4f4f4; padding: 0.2em 0.4em; border-radius: 3px; }
    </style>
</head>
<body>
    <h1>{$appName}</h1>
    <p>Phase 0 — Squelette PHP opérationnel.</p>
    <ul>
        <li>Environnement : <code>{$appEnv}</code></li>
        <li>PHP : <code>PHP_VERSION</code> à remplacer dynamiquement</li>
    </ul>
    <p><em>« Il ne faut jamais sous-estimer l'influence du hasard sur l'existence de tout être »</em></p>
</body>
</html>
HTML;
    }
}
```

Écrire ce contenu exact dans `src/Controllers/HelloController.php`. Remplacer `PHP_VERSION` par `{PHP_VERSION}` ne fonctionnera pas en heredoc — on laisse la version PHP en clair dans le HTML pour le smoke test minimal.

- [ ] **Step 4: Vérifier la syntaxe**

```powershell
php -l src\Controllers\HelloController.php
```

Attendu : `No syntax errors detected`.

- [ ] **Step 5: Régénérer l'autoload pour intégrer le nouveau controller**

```powershell
composer dump-autoload -o
```

---

## Task 14: Créer le front controller public/index.php

**Files:**

- Create: `public/index.php`

- [ ] **Step 1: Créer le front controller complet**

```php
<?php
declare(strict_types=1);

/**
 * Front controller unique ALDANA.
 * Toutes les requêtes HTTP arrivent ici via le rewrite .htaccess.
 */

define('BASE_PATH', dirname(__DIR__));

require BASE_PATH . '/vendor/autoload.php';

// Charge .env
$dotenv = Dotenv\Dotenv::createImmutable(BASE_PATH);
$dotenv->safeLoad();

// Timezone (cf spec §6.4)
date_default_timezone_set(config('app.timezone'));

// Gestion erreurs selon environnement
if (config('app.debug')) {
    error_reporting(E_ALL);
    ini_set('display_errors', '1');
} else {
    error_reporting(0);
    ini_set('display_errors', '0');
}

// Logger Monolog
$logger = new Monolog\Logger('app');
$logger->pushHandler(new Monolog\Handler\StreamHandler(
    BASE_PATH . '/storage/logs/app.log',
    config('app.debug') ? Monolog\Level::Debug : Monolog\Level::Warning
));

// Session sécurisée (cf doc 03 sécurité du playbook)
session_start([
    'cookie_httponly' => true,
    'cookie_secure'   => !empty($_SERVER['HTTPS']),
    'cookie_samesite' => 'Lax',
    'use_strict_mode' => true,
    'name'            => 'aldana_session',
]);

// Routing minimaliste
$method = $_SERVER['REQUEST_METHOD'];
$uri    = parse_url($_SERVER['REQUEST_URI'] ?? '/', PHP_URL_PATH) ?: '/';
$key    = $method . ' ' . $uri;
$routes = require BASE_PATH . '/config/routes.php';

try {
    if (isset($routes[$key])) {
        [$controllerClass, $action] = $routes[$key];
        if (!class_exists($controllerClass) || !method_exists($controllerClass, $action)) {
            throw new RuntimeException("Route handler not found: {$controllerClass}::{$action}");
        }
        $controller = new $controllerClass();
        $controller->$action();
    } else {
        http_response_code(404);
        echo '<!DOCTYPE html><html lang="fr"><head><meta charset="UTF-8"><title>404</title></head><body><h1>404 — Page introuvable</h1></body></html>';
    }
} catch (Throwable $e) {
    $logger->error($e->getMessage(), ['exception' => (string) $e]);
    http_response_code(500);
    if (config('app.debug')) {
        echo '<pre>' . e($e->getMessage()) . "\n\n" . e((string) $e) . '</pre>';
    } else {
        echo '<!DOCTYPE html><html lang="fr"><head><meta charset="UTF-8"><title>500</title></head><body><h1>500 — Erreur serveur</h1></body></html>';
    }
}
```

- [ ] **Step 2: Vérifier la syntaxe**

```powershell
php -l public\index.php
```

Attendu : `No syntax errors detected in public\index.php`.

---

## Task 15: Smoke test — le serveur PHP intégré répond

**Files:** aucun nouveau, on teste l'existant.

- [ ] **Step 1: Lancer le serveur PHP intégré**

```powershell
php -S localhost:8000 -t public/
```

Le serveur reste actif en foreground. **Ne pas fermer le terminal** pendant le test suivant.

- [ ] **Step 2: Dans un AUTRE terminal PowerShell, tester l'accès**

```powershell
Invoke-WebRequest -Uri http://localhost:8000/ -UseBasicParsing | Select-Object StatusCode, RawContentLength
```

Attendu : `StatusCode : 200`, `RawContentLength` > 0.

- [ ] **Step 3: Vérifier le contenu (recherche du nom de l'app)**

```powershell
(Invoke-WebRequest -Uri http://localhost:8000/ -UseBasicParsing).Content -match 'ALDANA'
```

Attendu : `True`.

- [ ] **Step 4: Tester un 404**

```powershell
try { Invoke-WebRequest -Uri http://localhost:8000/inexistant -UseBasicParsing } catch { $_.Exception.Response.StatusCode.Value__ }
```

Attendu : `404`.

- [ ] **Step 5: Arrêter le serveur**

Retour au terminal du serveur, presser `Ctrl+C`.

---

## Task 16: Créer la base de données locale `aldana`

**Files:** aucun fichier, action MySQL directe.

- [ ] **Step 1: Créer la base avec encodage utf8mb4**

```powershell
mysql -u root -e "CREATE DATABASE IF NOT EXISTS aldana CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;"
```

(Adapter le chemin mysql si pas dans PATH : `C:\wamp64\bin\mysql\mysql9.1.0\bin\mysql.exe`)

Attendu : pas de sortie (succès silencieux), ou warning bénin.

- [ ] **Step 2: Vérifier que la base est créée**

```powershell
mysql -u root -e "SHOW DATABASES LIKE 'aldana';"
```

Attendu : tableau avec une ligne contenant `aldana`.

- [ ] **Step 3: Vérifier l'encodage**

```powershell
mysql -u root -e "SELECT default_character_set_name, default_collation_name FROM information_schema.SCHEMATA WHERE schema_name='aldana';"
```

Attendu : `utf8mb4`, `utf8mb4_unicode_ci`.

---

## Task 17: Smoke test — connexion MySQL depuis PHP

**Files:** aucun nouveau, on teste l'existant via une commande inline.

- [ ] **Step 1: Tester la connexion via un one-liner PHP utilisant Database::getInstance()**

```powershell
php -r "require 'vendor/autoload.php'; \Dotenv\Dotenv::createImmutable(__DIR__)->safeLoad(); $pdo = \App\Helpers\Database::getInstance()->getConnection(); echo $pdo->query('SELECT VERSION()')->fetchColumn();"
```

Attendu : une chaîne du type `9.1.0` (version MySQL).

Si erreur `Database connection failed` : vérifier que WAMP est démarré et que `.env` a bien `DB_USER=root` et `DB_PASS=` (pas de mot de passe par défaut sur WAMP).

---

## Task 18: Créer 5 agents experts dans .claude/agents/

**Files:**

- Create: `.claude/agents/architecte-php.md`
- Create: `.claude/agents/securite-php.md`
- Create: `.claude/agents/bdd-mysql.md`
- Create: `.claude/agents/frontend-vanilla.md`
- Create: `.claude/agents/deploiement-mutualise.md`

- [ ] **Step 1: Créer le dossier**

```powershell
New-Item -ItemType Directory -Path .claude\agents -Force | Out-Null
```

- [ ] **Step 2: Créer `.claude/agents/architecte-php.md`**

```markdown
---
name: architecte-php
description: Architecte PHP senior, conçoit la structure des features avant implémentation (PSR-4, séparation Controller/Service/Repository, conventions ALDANA). À invoquer pour toute nouvelle feature ou refactor structurel.
tools: Read, Grep, Glob
---

Tu es un architecte PHP avec 15 ans d'expérience en applications web métier (yoga studio, plateforme social, ERP).

Tu maîtrises :
- PSR-4, PSR-12, design patterns appliqués (Singleton, Repository, Service, Strategy)
- Conventions BookConnect/ALDANA : tables `t_*`, snake_case colonnes, `*_ID` PK
- Transactions SQL pour la concurrence, FOR UPDATE
- Découpage Controller léger / Service métier / Repository SQL

Quand on te consulte sur une feature :
1. Liste les fichiers à créer/modifier avec chemins exacts
2. Décris le découpage Controller/Service/Repository
3. Identifie les invariants métier à protéger (transactions, validations serveur)
4. Note les risques (concurrence, sécurité, performance)
5. Propose des tests unitaires/feature à écrire en TDD

Tu ne codes pas — tu produis un plan d'architecture qu'un dev implémente ensuite.

Référentiels : `Dropbox/02_guide_architecture_psr4.md`, mémoire BookConnect `project_architecture.md`, spec ALDANA V1 `docs/superpowers/specs/2026-05-18-aldana-v1-design.md`.
```

- [ ] **Step 3: Créer `.claude/agents/securite-php.md`**

```markdown
---
name: securite-php
description: Expert sécurité PHP, audite tout code touchant auth, sessions, formulaires, uploads, paiements. À invoquer avant chaque mise en prod et sur tout PR sensible (login, register, booking, webhook Stripe).
tools: Read, Grep, Glob, Bash
---

Tu es expert en sécurité applicative PHP avec spécialisation pentest et OWASP Top 10.

Pour chaque audit :
1. Liste les vulnérabilités potentielles avec criticité (Critique / Élevée / Moyenne / Faible)
2. Pour chaque vuln : code fautif EXACT (fichier:ligne) + code corrigé
3. Vérifie en priorité, dans l'ordre :
   - Injections SQL (PDO prepared obligatoire)
   - XSS (htmlspecialchars systématique)
   - CSRF (token sur tout POST sauf webhooks)
   - Sessions (httponly, secure, samesite, regenerate_id au login)
   - Authentication (Argon2id, rate limit, lockout)
   - Webhooks Stripe (signature vérifiée, idempotence)
   - Uploads (validation MIME, taille, stockage hors public/)
   - Secrets (.env jamais commité)
   - Headers de sécurité (CSP, HSTS, X-Frame-Options)

Tu ne modifies jamais le code sans validation. Tu produis un rapport structuré markdown.

Référentiels : `Dropbox/03_guide_securite_php.md`, OWASP Top 10 2021, PHP Security Manual, CWE.
```

- [ ] **Step 4: Créer `.claude/agents/bdd-mysql.md`**

```markdown
---
name: bdd-mysql
description: Expert MySQL/MariaDB, conçoit le schéma, optimise les requêtes, écrit les migrations idempotentes, audite la concurrence. À invoquer pour toute évolution BDD et pour tout problème de performance.
tools: Read, Grep, Glob, Bash
---

Tu es expert MySQL avec spécialisation modélisation OLTP et concurrence.

Tu connais par cœur :
- Conventions ALDANA : tables `t_*`, colonnes snake_case, `*_ID` PK INT UNSIGNED, utf8mb4_unicode_ci
- InnoDB transactions, isolation levels, SELECT ... FOR UPDATE
- Index design (couvrants, composites, prefix index)
- Pattern migration idempotente avec helper procédure (cf BookConnect)
- EXPLAIN, slow query log, optimizer hints

Quand on te consulte :
1. Si schéma : propose le DDL complet avec FK, indexes, contraintes
2. Si requête : EXPLAIN + propose optimisation (index, réécriture)
3. Si migration : pattern idempotent obligatoire, jamais de DROP sans backup
4. Si concurrence : identifier les zones critiques + proposer transaction
5. Vérifier les types compatibles entre FK et PK (incident classique BookConnect)

Règle d'or : JAMAIS inventer un nom de table/colonne. Sources de vérité : dump SQL local, migrations, INFORMATION_SCHEMA.

Référentiels : spec ALDANA §4, BookConnect mémoire `db_columns.md` et `db_schema_types.md`.
```

- [ ] **Step 5: Créer `.claude/agents/frontend-vanilla.md`**

```markdown
---
name: frontend-vanilla
description: Expert HTML/CSS/JS vanilla moderne (pas de framework), responsive mobile-first, animations CSS+IntersectionObserver, accessibilité, SEO technique. À invoquer pour toute page de la vitrine et de l'espace membre.
tools: Read, Grep, Glob
---

Tu es développeur front senior avec spécialisation vanilla moderne (ES2020+, CSS Grid/Flexbox, Web Components quand utile).

Tu connais :
- HTML5 sémantique, ARIA, accessibilité WCAG 2.1 AA
- CSS variables, calc(), clamp(), animations performantes (transform/opacity)
- IntersectionObserver pour les apparitions au scroll
- Mobile-first, breakpoints : 640, 768, 1024, 1280
- Fetch API, FormData, Web Storage
- Optimisations : preload, prefetch, lazy loading images, font-display: swap
- Pas de framework JS — tout en vanilla maintenable

Pour ALDANA, tu vas implémenter la palette muted du mockup Base44 (cf `_mockup_base44/src/index.css` pour la base) en respectant la direction dévotionnelle sobre validée dans le spec §2.

Référentiels : spec ALDANA §3 (arborescence), §6.4 (conventions front), `_mockup_base44/` pour inspiration visuelle uniquement.
```

- [ ] **Step 6: Créer `.claude/agents/deploiement-mutualise.md`**

```markdown
---
name: deploiement-mutualise
description: Expert déploiement hébergement mutualisé (Hostinger, OVH), .htaccess, FTP/SFTP, gestion DNS, SSL Let's Encrypt, backup BDD. À invoquer pour la phase P8 et toute mise en production.
tools: Read, Grep, Glob, Bash
---

Tu es expert mise en production d'applications PHP sur hébergement mutualisé.

Tu connais :
- Hostinger et OVH mutualisé (limites PHP, cron, MySQL, webhooks entrants)
- Configuration .htaccess avancée (rewrite, security headers, redirections 301)
- DNS : A record vs CNAME, propagation, sous-domaines
- SSL Let's Encrypt (gestion automatique via panel + renouvellement)
- Workflow FTP/SFTP propre (déploiement incrémental, exclusions, sauvegardes)
- check_deploy.php pattern BookConnect pour synchroniser local et prod
- mysqldump quotidien + rotation, restauration de backup

Tu produis :
1. Une checklist de pré-déploiement vérifiable
2. Une séquence d'actions ordonnée (DNS → SSL → FTP → BDD → Stripe LIVE → tests)
3. Un plan de rollback en cas de problème

Référentiels : `Dropbox/04_guide_deploiement_hebergement.md`, mémoire BookConnect `project_prod_paths.md` et `feedback_check_deploy.md`, spec ALDANA §8 (P8).
```

- [ ] **Step 7: Vérifier les 5 agents créés**

```powershell
Get-ChildItem .claude\agents\*.md | Select-Object Name
```

Attendu : 5 fichiers (`architecte-php.md`, `bdd-mysql.md`, `deploiement-mutualise.md`, `frontend-vanilla.md`, `securite-php.md`).

---

## Task 19: Initialiser Git et faire le premier commit

**Files:**

- Create: `README.md`
- Create: `.git/` (auto via git init)

- [ ] **Step 1: Créer un README.md minimal**

```markdown
# ALDANA — Site Aurane Aldana Yoga Versailles

Refonte du site `aldanayoga.com` : vitrine modernisée + réservation en ligne + paiement Stripe + espace membre + back-office.

## Stack

PHP 8.2+ · MySQL 8 utf8mb4 · Apache 2.4 · HTML/CSS/JS vanilla · Stripe · WAMP local · Hostinger/OVH prod (TBD).

## Démarrage local

```powershell
composer install
Copy-Item .env.example .env  # puis configurer DB_*
# Créer la base : mysql -u root -e "CREATE DATABASE aldana CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;"
composer serve  # → http://localhost:8000
```

## Documentation

- **Spec V1** : `docs/superpowers/specs/2026-05-18-aldana-v1-design.md`
- **Plans** : `docs/superpowers/plans/`
- **Briefing Claude** : `CLAUDE.md`
- **Mockup visuel d'origine** : `_mockup_base44/` (référence, non déployé)

## Phases

P0 Setup ✓ · P1 BDD · P2 Vitrine · P3 Auth · P4 Booking · P5 Stripe · P6 Admin · P7 Events · P8 Prod
```

Écrire ce contenu dans `README.md` à la racine.

- [ ] **Step 2: Initialiser le dépôt Git**

```powershell
git init
git branch -M main
```

Attendu : `Initialized empty Git repository in C:\Users\rgalo\Dropbox\_ALDANA\.git/`.

- [ ] **Step 3: Vérifier que .gitignore filtre bien vendor/ et .env**

```powershell
git status --short
```

Attendu : pas de fichier `vendor/...` ni `.env` dans la liste. Si l'un d'eux apparaît, vérifier `.gitignore`.

- [ ] **Step 4: Ajouter tous les fichiers et faire le commit initial**

```powershell
git add .
git commit -m "chore: P0 setup PHP scaffolding + spec V1 + Claude config"
```

Attendu : `[main (root-commit) <hash>] chore: P0 setup PHP scaffolding + spec V1 + Claude config` avec ~50-80 fichiers créés.

- [ ] **Step 5: Vérifier le log**

```powershell
git log --oneline
```

Attendu : une ligne avec le commit qu'on vient de faire.

---

## Self-Review (à faire mentalement avant de commencer)

**Spec coverage check** :

- §6.3 arborescence : couverte par Tasks 3, 4, 5, 8-14, 18
- §6.4 conventions (timezone, encoding) : couverte par Tasks 7, 9, 10, 11, 14
- §6.2 dépendances Composer : couverte par Tasks 4-5
- CLAUDE.md déclare `.claude/agents/` : couverte par Task 18
- Premier commit Git incluant spec + CLAUDE.md : couvert par Task 19

**Pas de spec coverage en P0** :

- §1-2 (contexte, positionnement) : conceptuel, pas implémentable
- §3 (arborescence pages) : P2
- §4 (modèle de données) : P1
- §5 (flux fonctionnels) : P3-P5
- §7 (sécurité) : P3 (auth)
- §8-11 : roadmap, pas implémentable

**Placeholder scan** : pas de "TODO", "TBD", "implement later" dans le plan. ✓

**Vérification cohérence** :

- `composer.json` autoload PSR-4 `App\\` → `src/` ↔ namespace dans Database.php, HelloController.php ↔ ✓
- `config/database.php` keys ↔ utilisées dans Database.php ✓
- `config/routes.php` mentionne `HelloController` ↔ créé en Task 13 ✓
- `.env.example` clés ↔ référencées dans `config/*.php` ✓

---

## Sortie attendue à la fin de P0

```text
C:\Users\rgalo\Dropbox\_ALDANA\
├── _mockup_base44/         (archivé)
├── .claude/                (commands + 5 agents)
├── .git/                   (initialisé)
├── config/                 (app.php, database.php, routes.php, stripe.php)
├── database/migrations/    (vide, P1)
├── docs/superpowers/       (specs + plans)
├── public/                 (index.php + .htaccess + assets/)
├── src/                    (Helpers/, Controllers/HelloController.php)
├── storage/                (logs/, cache/, sessions/, uploads/)
├── tests/                  (Unit/, Feature/ vides)
├── vendor/                 (Composer deps installées)
├── views/                  (vides, P2)
├── .env                    (config locale)
├── .env.example            (template versionné)
├── .gitignore
├── CLAUDE.md
├── composer.json
├── composer.lock
└── README.md
```

`composer serve` répond sur `http://localhost:8000/` avec la page « ALDANA — Phase 0 — Squelette PHP opérationnel ».
La base de données `aldana` existe localement, vide.
Un commit Git initial contient toute la fondation.

**Durée totale estimée P0** : 1 à 2 jours selon connaissance Composer/WAMP.

**Prochain plan à écrire après P0 livrée** : `2026-05-18-aldana-p1-bdd.md` (migrations SQL 001-012 + helper migration runner + seeds initiaux).
