# CLAUDE.md

Contexte pour Claude Code sur ce dépôt. À lire avant toute intervention.

## Projet
**app-sobook** (Sobook) — application de **réservation de chambres d'hôtel** : un site public
(vitrine + réservation en ligne, FR/DE extensible) et un **espace de gestion** ultra simple pour
le gestionnaire (planning, réservations, clients, chambres, prestations, tarifs, fermetures,
réglages, factures). Dérivée du template **baseapp** (auth, rôles, utilisateurs, réglages, audit).

## ⚠️ Piège PHP (IMPORTANT)
Le `php` du PATH Windows est en **8.1** (trop vieux pour Laravel 13). Utiliser
**`backend\artisan.bat <cmd>`** (wrapper PHP 8.4) ou `D:\wamp64\bin\php\php8.4.24\php.exe`.
Composer/tests/pint : `$env:Path = 'D:\wamp64\bin\php\php8.4.24;' + $env:Path`.

## Démarrer
- **`dev.bat`** lance l'API (`:8000`) + la SPA (`:5173`). **WAMP (MySQL) doit tourner.** Base : `app_sobook`.
- Comptes : `admin@baseapp.test` / `password` (admin) · `user@baseapp.test` / `password` (réception).
- Rôles : `admin` (tout), `gestionnaire` (chambres, prestations, tarifs, fermetures, réglages sauf `Setting::ADMIN_ONLY_KEYS` : SMTP, langues, nom de l'app ; pas les utilisateurs), `user` (réservations, clients, planning). Routes : middleware `admin` ou `role:admin|gestionnaire` ; front : `RoleRoute`, `lib/roles.ts`.
- URLs : site public `http://localhost:5173/` · admin `http://localhost:5173/admin`.
- Données de démo (hôtel, 6 chambres, prestations, réservations) : `DemoSeeder`, local uniquement.
  Repartir de zéro : `backend\artisan.bat migrate:fresh --seed`.
- Images envoyées depuis l'admin : `storage/app/public` → `backend\artisan.bat storage:link` (déjà fait en local).

## Stack
Laravel 13 / PHP 8.4 / Sanctum / spatie permission + activitylog / Pest · React 19 / Vite /
TypeScript / Tailwind v4 / TanStack Query / react-hook-form + zod / date-fns / lucide-react.

## Modèle métier (backend/app/Models)
- `Guest` (client), `Room` (chambre, textes traduisibles JSON `{fr, de…}` ; `type_key` = groupe de chambres identiques : le site public n'affiche que la première (`Room::units()`, `representative()`), `PublicController` attribue une unité libre via `AvailabilityService::findFreeUnit`), `Service` (prestation,
  4 modes de prix : séjour / nuit / personne / personne-nuit), `Closure` (fermeture, `room_id` null =
  tout l'hôtel), `RateRule` (tarif spécial : prix fixe ou %, période, chambre ou global, priorité,
  séjour min), `Booking` (+ `BookingService` lignes figées, `EmailLog`).
- `Setting` clé/valeur : identité + couleurs, coordonnées de l'hôtel, règles de réservation, langues,
  SMTP (mot de passe chiffré), modèles d'e-mails par langue (`mail_<type>_<subject|body>_<lang>`), facture.
- Champs traduisibles : trait `HasTranslations` (`translate('name', 'de')` avec repli langue par défaut).

## Services (backend/app/Services)
- `PricingService::quote()` : prix nuit par nuit (tarifs spéciaux) + prestations − remise.
- `AvailabilityService` : conflits réservations/fermetures, règles du site public (dates passées,
  séjour min, délai max), nuits indisponibles pour le calendrier.
- `BookingManager` : création/modification/changement de statut, client trouvé ou créé par e-mail, e-mails.
- `BookingMailer` + `MailConfigurator` + `TemplatedMail` (gabarit HTML : logo, récapitulatif du séjour, bouton, lien règlement, pièce jointe `mail_attachment_path` du disque public pour les e-mails clients) : e-mails depuis les modèles des réglages, via le SMTP des
  réglages (sinon mailer du `.env`, `log` en local → `storage/logs/laravel.log`). Chaque envoi est journalisé.

## API (backend/routes/api.php)
- Public sans auth : `/api/settings`, `/api/public/rooms`, `/api/public/rooms/{slug}`,
  `/api/public/rooms/{id}/unavailable-dates`, `/api/public/services`, `/api/public/quote`,
  `POST /api/public/bookings`, `/api/public/bookings/{ref}?email=`.
- Connecté (réception + admin) : dashboard, calendar, bookings (+ quote, status, payment, resend-email),
  guests, rooms/services (lecture), uploads.
- Admin seulement : users, settings/all + PUT settings + test-email, rooms/services/closures/rate-rules (écriture).

## Frontend (frontend/src)
- `pages/public/*` + `components/public/*` : site vitrine (i18n via `i18n/`, `useT()`). **Langue dans l'URL** : `/fr/…`, `/de/…` (`/:lang` dans `App.tsx`) ; `/` et les anciennes URLs sans préfixe redirigent vers la langue préférée. Liens internes via `p('/chemin')` de `useT()`.
  Ajouter une langue : `i18n/xx.ts` + `i18n/dictionaries.ts` + `Setting::AVAILABLE_LANGUAGES`, puis l'activer dans Réglages → Langues.
- `pages/admin/*` + `components/admin/*` : espace de gestion (français). `AdminLayout` = barre latérale.
- `BookingFormModal` : création/édition avec devis en direct ; `GuestCombobox` : recherche + création client à la volée.
- Page publique `/reglement` (`TermsPage`) : texte `terms_<lang>` des réglages (mise en forme `## Titre`, `- puce`), acceptation obligatoire (`terms_accepted`) à la réservation en ligne.
- SEO : hook `lib/seo.ts` (`useSeo` : titre, description, Open Graph, canonical, hreflang, JSON-LD) appelé par chaque page publique ; côté API `SeoController` sert `/sitemap.xml`, `/robots.txt`, `/llms.txt` (base = `FRONTEND_URL`). `VITE_SITE_URL` = origine publique du site.
- `WeekCalendar` : planning chambres × jours (dashboard + page Planning). `InvoicePage` : facture imprimable.
- Couleurs : `--accent` / `--violet` posées sur `:root` depuis les réglages (`App.tsx`), tokens Tailwind `accent`, `violet`, `canvas`, `ink`, `line`.
- Formulaires en modale : état initialisé au montage (composant interne monté à chaque ouverture), pas de `setState` dans un effet (règle ESLint react-hooks).

## Import depuis WordPress / VikBooking (client Les Deux-Clefs)
- Charger l'export SQL dans une base locale (`mysql -u root tmp_x < dump.sql`), puis :
  `backend\artisan.bat sobook:import-vikbooking tmp_x --fresh --mark-past-paid --images-base=http://localhost:8000/storage/uploads/rooms --closure="2026-11-16,2026-12-02,Congés annuels"`
- Commande : `app/Console/Commands/ImportVikBooking.php` (1 chambre Sobook par unité VikBooking,
  clients, réservations `VB-<id>` avec attribution d'unité, fermetures). Les photos doivent être
  copiées dans `backend/storage/app/public/uploads/rooms/` avant (URL `big_<fichier>` du plugin).
- Coordonnées, couleurs, logo, horaires, IBAN : saisis dans Réglages (ou `Setting::setMany`).

## Conventions
- Validation → Form Requests ; sorties → API Resources ; listes paginées ; erreurs JSON uniformes.
- Front : hooks TanStack Query dans `hooks/`, jamais de fetch dans les pages ; `toast()` pour les retours.
- Schéma DB **uniquement via migrations**.

## Qualité
- Tests : `backend\artisan.bat test` (Pest, SQLite mémoire). Format : `vendor\bin\pint`.
- Front : `npm run lint` et `npm run build` dans `frontend/`.

## Ne jamais committer
`.env`, `vendor/`, `node_modules/`, `frontend/dist/`, `storage/*`.

## Déploiement
Voir `DEPLOY.md` (OVH mutualisé, PHP 8.3 : `composer.json` fige `config.platform.php` à 8.3, ne pas retirer). Penser à `storage:link` en prod pour les images.
