# Structure d'un dossier client

Référence pour l'arborescence physique d'un client, à l'intérieur de
`public/tenants/{slug}/` et `storage/app/tenants/`. Complète
`client-json-explication.md` (contenu du JSON) et `architecture-application.md`
(structure du projet dans son ensemble).

## Principe : un client = deux zones distinctes

- **`storage/app/tenants/`** — données JSON du client, jamais
  accessible publiquement (hors de `public/`).
- **`public/tenants/{slug}/`** — assets physiques du client (images,
  et plus tard documents/vidéos), servis directement par le
  serveur web.

Le `slug` est l'identifiant stable du client dans les deux zones. Il
ne change jamais après création, y compris si le nom du dossier
source (`sandbox/jloze`, `sandbox/slibour`...) ne correspond pas
exactement.

## Zone JSON — `storage/app/tenants/`

```
storage/app/tenants/
├── exemple-template.json           # structure de référence, vide
├── client-json-exemple.json                # version PUBLIÉE — lue par le domaine réel
├── jerome-loze.brouillon.json      # version EN ÉDITION — lue par /sandbox/jerome-loze
├── sophie-libourel.json
└── sophie-libourel.brouillon.json
```

**Fonctionnement brouillon / publié (à sens unique) :**

- `{slug}.brouillon.json` existe **en permanence**, dès la création
  du client (import JSON initial : les deux fichiers sont créés
  identiques). C'est le **seul** fichier que l'admin (`/gestion`)
  modifie — jamais d'écriture directe dans `{slug}.json`.
- `{slug}.json` est la version publiée. Seule action qui l'affecte :
  "Publier les modifications" dans l'admin, qui copie le brouillon
  par-dessus. Le brouillon n'est jamais supprimé ni vidé après
  publication — il continue d'exister, identique au fichier publié,
  jusqu'à la prochaine modification.
- `/sandbox/{slug}` lit **toujours** `{slug}.brouillon.json`.
- Le domaine réel (`Route::domain()`) lit **toujours** `{slug}.json`.
- Le champ `publie` (booléen, à l'intérieur du JSON lui-même) reste
  distinct de ce mécanisme : il contrôle l'indexation Google du
  domaine réel, indépendamment de l'existence du brouillon. Voir
  `client-json-explication.md`.

**Aucun autre fichier ne doit apparaître dans ce dossier** — pas de
sauvegardes manuelles (`_back*.json`), pas de fichiers `_old`. Un
historique de versions, si besoin un jour, sera géré par Git sur
`storage/app/tenants/` (déjà versionné), pas par duplication manuelle
de fichiers — c'est justement ce que l'audit de l'ancien système
déconseille (section 6, point 11).

## Zone assets — `public/tenants/{slug}/`

```
public/tenants/{slug}/
└── assets/
    ├── img/
    │   ├── accueil/
    │   │   ├── {prenom}-{nom}-{metier.slug}-{ville_seo}-01.webp   ← hero
    │   │   ├── {prenom}-{nom}-{metier.slug}-{ville_seo}-02.webp   ← biographie
    │   │   └── {prenom}-{nom}-{metier.slug}-{ville_seo}-03.webp   ← approche
    │   └── portfolio/
    │       └── {slug-prestation}/
    │           ├── {prenom}-{nom}-{metier.slug}-{ville_seo}-01.webp
    │           ├── {prenom}-{nom}-{metier.slug}-{ville_seo}-02.webp
    │           └── ...
    ├── docs/     ← réservé, vide pour l'instant (PDF, plaquettes...)
    └── video/    ← réservé, vide pour l'instant
```

`assets/` en préfixe (plutôt que `accueil/`/`portfolio/` à la racine
du dossier client) : anticipe d'autres types de fichiers par client
(documents, vidéos) sans réorganisation future. `docs/` et `video/`
ne sont pas créés tant qu'aucune fonctionnalité ne les utilise —
seuls `img/accueil/` et `img/portfolio/` sont créés à la création du
client (voir plus bas).

### Règle de nommage des fichiers image — figé à l'upload

Tout fichier image (accueil ou portfolio) est nommé une seule fois,
au moment de l'upload via l'admin :

```
{prenom}-{nom}-{metier.slug}-{ville_seo}-{NN}.webp
```

- Capturé comme un **instantané** des champs `prenom`/`nom`/
  `metier.slug`/`ville_seo` du client au moment de l'upload — jamais
  recalculé si ces champs changent ensuite. Une modification
  ultérieure de la ville ciblée ou du métier ne renomme pas les
  fichiers déjà présents. Si `ville_seo` est absent, retombe sur
  `adresses[0].ville` (voir `client-json-explication.md` — ces deux champs sont
  volontairement distincts, `ville_seo` peut cibler une ville voisine
  plus recherchée que la commune réelle du client).
- `metier.slug` (pas `metier.libelle`) : pas d'accents, pas
  d'apostrophe, sûr pour un nom de fichier.
- `NN` : numéro séquentiel à deux chiffres, calculé par le
  contrôleur admin comme le premier numéro disponible dans le
  dossier concerné. Les trous laissés par des suppressions ne
  posent aucun problème.
- Seul le format `.webp` est accepté à l'upload (pas de conversion
  automatique pour l'instant — évolution possible plus tard, mais
  pas de dépendance de traitement d'image à ce stade, cohérent avec
  l'hébergement mutualisé sans SSH).
- Le champ `alt` de chaque image est indépendant du nom de fichier,
  librement modifiable via l'admin sans jamais renommer le fichier.

### `accueil/` — création à l'import JSON

Le dossier `assets/img/accueil/` est créé **au moment de l'import du
JSON** (création du client), qu'il contienne des images ou non au
départ — `photos_accueil` concerne tout client par définition, quel
que soit son métier. Ordre des trois images significatif :
1 = photo hero, 2 = illustration biographie, 3 = illustration
approche métier. Le tableau `photos_accueil` du JSON respecte le même
ordre (voir `client-json-explication.md`).

### `portfolio/{slug-prestation}/` — création à la création de la prestation

Un sous-dossier `assets/img/portfolio/{slug-prestation}/` est créé
**au moment de la création de la prestation** via l'admin, qu'elle
ait effectivement un portfolio ou non — coût négligeable (un dossier
vide), et évite un cas particulier ("créer le dossier seulement si
la première image est uploadée").

Le `slug` de la prestation est **figé dès sa création**, exactement
comme le slug du client : aucun mécanisme de renommage de dossier
n'existe. Renommer proprement une prestation implique de la
supprimer et d'en recréer une nouvelle — compromis assumé pour éviter
toute synchronisation fragile entre le nom de dossier physique et le
JSON.

Les catégories de filtre Isotope ne sont **pas** déduites de
sous-dossiers physiques : elles viennent du champ `categorie` de
chaque item du tableau `portfolio` dans le JSON (voir
`client-json-explication.md`). Le système de fichiers ne sert qu'au stockage
des images, jamais à la classification.

## Fichier de vérification Google Search Console

`google{hash}.html` — **pas** dans `public/tenants/{slug}/`. Placé
directement à la racine de `public/`, aux côtés des fichiers Laravel
standards. Il est unique par construction (le hash dans son nom),
donc plusieurs clients peuvent en avoir chacun un sans collision.
Le hash est référencé dans le champ `google_site_verification` du
JSON du client concerné, uniquement pour garder une trace de quel
fichier appartient à qui.

## Ce qui ne doit jamais apparaître dans un dossier client

- Aucun mapping domaine → client en dehors du tableau `domaines` du
  JSON lui-même (pas de fichier de configuration séparé).
- Aucun dossier de suivi de prestation (`K7mP2x`, `Jjf7JK`...) — hors
  scope de la migration, non touché, non reproduit.
- Aucune donnée d'un autre client, même par erreur de copier-coller
  de gabarit — c'est précisément le risque relevé dans l'audit
  section 7.4 (gabarit de suivi livré avec les données réelles de
  Caroline Poplawec).
