# PYRAMIDCOM — Dossier maître du projet

> Documentation fonctionnelle, métier, technique et d’exploitation.
> État consolidé le 31 juillet 2026 à partir du code, de la migration et de la production.
>
> Ce fichier est la mémoire principale du projet. Il doit être mis à jour lorsqu’une règle métier,
> une route, une table, un document, un tarif ou le déploiement change.

---

## 1. Résumé exécutif

PyramidCom est désormais une application web complète pour **SAS PYRAMID COM**, fabricant
français d’enseignes lumineuses et de communication visuelle. Elle remplace l’ancien site vitrine
WordPress/PHP et réunit dans une seule application :

1. un site commercial et SEO ;
2. trois configurateurs avec rendu visuel et estimation HT ;
3. un espace professionnel pour les revendeurs ;
4. un tunnel devis → BAT → signature ou correction ;
5. des dossiers de fabrication français et turcs ;
6. des fichiers SVG de production à l’échelle réelle ;
7. un CMS pour les contenus, réalisations et références ;
8. une administration commerciale avec filtres, archivage et projets manuels ;
9. l’envoi d’e-mails transactionnels et leur journalisation ;
10. un déploiement continu GitHub → o2switch.

Le site est en production sur **https://pyramidcom.fr**.

Les quatre familles de dossiers sont :

- enseignes et lettres boîtiers, profils P01 à P20 ;
- néons LED flexibles ;
- cadres textiles aluminium ;
- projets libres saisis manuellement dans l’administration.

Deux sujets liés aux franchises sont volontairement distincts :

- PyramidCom accompagne les réseaux et franchises françaises pour reproduire leur communication
  sur plusieurs établissements ;
- PyramidCom est également ouvert à des candidats souhaitant rejoindre son propre réseau et créer
  une agence de communication.

---

## 2. Identité, société et règles éditoriales

### Société

- **Raison sociale :** SAS PYRAMID COM
- **Adresse :** 35T Rue du Dr Eugène Jacquot, 90400 Danjoutin
- **Téléphone public :** 06 04 41 04 49
- **SIREN :** 880 588 421
- **SIRET siège :** 880 588 421 00011
- **TVA intracommunautaire :** FR54 880 588 421
- **RCS :** 880 588 421 R.C.S. Belfort
- **E-mail projets et réponses :** projets@pyramidcom.fr
- **E-mail d’envoi :** noreply@pyramidcom.fr
- **Domaine canonique :** https://pyramidcom.fr

### Vocabulaire obligatoire

- Afficher **« Réalisations »**, jamais « Portfolio », dans l’interface publique.
- Afficher les prix en **€ HT**.
- Le bouton final des configurateurs est **« Obtenir mon devis »**.
- Une estimation du configurateur n’est pas un prix contractuel.
- La fabrication ne doit pas commencer avant acceptation du BAT.
- Un document non accepté doit porter la mention **« NE PAS PRODUIRE »** sans dépasser du cadre.
- Le document accepté devient **« BON POUR FABRICATION »**.
- Les footers des documents ne doivent pas afficher le numéro de téléphone.

### Marque et ressources principales

| Ressource | Fichier |
|---|---|
| Logo principal horizontal | `public/logo-main.png` |
| Symbole carré | `public/logo-mark.png` |
| Logo blanc vectoriel du site | `public/logo-white.svg` |
| Logo blanc raster pour les e-mails | `public/logo-white.png` |
| Logo documents | `public/brand-logo.png` |
| Favicon | `public/favicon.svg` |
| Logo TechnoGo | `public/technogo-logo.svg` |
| Vidéo du hero | `public/videos/pyramidcom-hero.mp4` |

Le site public affiche « Propulsé par TechnoGo.fr » dans son footer.

### Typographies

- Montserrat est la police principale du site et de l’administration.
- Les titres utilisent des graisses fortes et une hiérarchie très visible.
- Les polices de configuration sont embarquées localement.
- Ne pas remettre de texte fonctionnel minuscule : viser 14–16 px pour le corps et 12–13 px
  minimum pour les informations secondaires.

---

## 3. Architecture actuelle

### Production

- Node.js 24.18 sur CloudLinux/o2switch ;
- Next.js 16.2 et React 19.2 ;
- TypeScript 5.9 ;
- MariaDB 11.4 via `mysql2` ;
- fichiers persistants sur le disque privé o2switch ;
- Passenger/cPanel pour servir l’application Node ;
- SMTP IONOS avec Nodemailer ;
- GitHub Actions pour produire le build déployable.

### Bibliothèques importantes

- Three.js : aperçu volumique 3D des lettres ;
- OpenType.js : conversion des textes en contours ;
- `svg2pdf.js` : export PDF à partir des SVG ;
- `html-to-image` et jsPDF : documents visuels A4 ;
- Sharp, utilisé par Next.js et pour les ressources raster ;
- Drizzle, conservé pour le schéma historique et les outils de migration.

### Couche de compatibilité héritée

Le projet a commencé sur Cloudflare D1/R2/Vinext. Certains noms techniques ont été conservés pour
ne pas réécrire toutes les routes :

- `env.DB` ressemble à l’API D1 mais traduit les requêtes vers MariaDB ;
- `env.MEDIA` ressemble à un bucket R2 mais écrit dans `UPLOAD_DIR` ;
- `db/schema.ts` utilise encore les types SQLite de Drizzle ;
- les scripts `demo:seed`, Wrangler et certains fichiers Vinext servent uniquement au développement
  historique, pas à la production o2switch.

La production n’utilise plus Cloudflare D1 ni R2.

### Adaptateur de base et stockage

`lib/runtime-env.ts` fournit :

- un pool MariaDB de 8 connexions par défaut ;
- `prepare().bind().first()/all()/run()` ;
- les transactions par `batch()` ;
- la conversion `INSERT OR IGNORE` → `INSERT IGNORE` ;
- un stockage local protégé contre les chemins `..` ;
- écriture atomique par fichier temporaire puis renommage ;
- un fichier `.metadata.json` par objet pour le type MIME et les métadonnées.

---

## 4. Structure de l’application

### Pages publiques

| Route | Fonction |
|---|---|
| `/` | Accueil, présentation, franchises, références, réalisations et trois configurateurs |
| `/enseignes-lumineuses` | Page SEO générale enseignes |
| `/lettres-boitiers` | Page SEO lettres boîtiers et profils |
| `/neon-led` | Page SEO néon LED |
| `/cadres-textiles` | Page SEO cadres textiles |
| `/portfolio` | Page publique « Réalisations » |
| `/pro?token=…` | Espace revendeur privé |
| `/bat?token=…` | Devis/BAT privé client |
| `/devis?token=…` | Entrée historique compatible du BAT privé |
| `/atelier?token=…&lang=tr` | Fiche atelier privée |
| `/atelier/vector?token=…` | Fichier vectoriel atelier par token |
| `/connexion` | Connexion administrateur |
| `/mentions-legales` | Mentions légales |
| `/confidentialite` | Politique de confidentialité |
| `/cookies` | Politique cookies |

`app/PublicHeader.tsx` est le header partagé des pages publiques. Le menu contient : Enseignes,
Lettres boîtiers, Néon LED, Cadres textiles, Réalisations et Espace pro.

### Administration

| Route | Rubrique |
|---|---|
| `/admin` | Devis enseignes |
| `/admin/neons` | Néons LED |
| `/admin/cadres-textiles` | Cadres textiles |
| `/admin/projets` | Projets manuels |
| `/admin/revendeurs` | Candidatures et comptes professionnels |
| `/admin/profils` | Catalogue P01 à P20 |
| `/admin/polices` | Bibliothèque de polices des configurateurs |
| `/admin/tarification` | Prix enseignes, textiles et néons |
| `/admin/cms` | Pages, réalisations, références, traductions et signature e-mail |
| `/admin/devis?type=…&id=…` | Document commercial imprimable |
| `/admin/fabrication?type=…&id=…` | Fiche atelier française |
| `/admin/fabrication?type=…&id=…&lang=tr` | Fiche atelier turque |
| `/atelier/vector?type=…&id=…` | SVG/PDF de production depuis l’admin |

L’administration utilise une barre latérale rabattable, disponible sur ordinateur et mobile.
Chaque rubrique métier possède sa propre route, ce qui évite une page monolithique trop longue.
Le titre principal et son surtitre ne sont affichés qu’une fois par page. Les cartes internes
commencent par une action ou un libellé compact : elles ne répètent jamais le titre de la rubrique
avec un second bandeau imposant.

---

## 5. Site public et contenu commercial

### Accueil

L’accueil met en avant :

- PyramidCom comme fabricant d’enseignes lumineuses ;
- la vidéo hero « On fabrique ce qui vous rend visible » ;
- les lettres boîtiers, néons, caissons et enseignes drapeau ;
- la maîtrise d’une charte de réseau ;
- la production en série et le suivi multi-sites ;
- les trois configurateurs ;
- les références clients ;
- les dernières réalisations triées par publication ;
- l’espace professionnel ;
- la candidature pour rejoindre le réseau PyramidCom.

### Réseaux et franchises

Le premier chapitre explique que PyramidCom sait accompagner des franchises françaises :

- respect de la charte ;
- production en série ;
- coordination multi-sites ;
- livraison et pose en France.

Le second chapitre, distinct, invite à devenir franchisé PyramidCom : tarifs compétitifs,
formation, suivi et accompagnement. Le droit d’entrée est discuté après dépôt de candidature.

### Références

Le CMS permet d’afficher les logos des clients et leurs liens autorisés. Les références historiques
incluent notamment Optic 2000, O’Tacos, France Code, Malo, Propag, Maison Demeusy, SNCF,
Century 21 et MyoTec.

### Réalisations

- gestion depuis l’administration ;
- photo, titre, ville, catégorie, description et texte alternatif ;
- états brouillon, publié ou masqué ;
- mise en avant ;
- tri par date de publication décroissante ;
- « Publier & remonter » met à jour la date ;
- cartes cliquables et lightbox publique.

---

## 6. Configurateur d’enseignes et lettres boîtiers

### Configuration par défaut actuelle

- profil : **P01** ;
- texte : **VOTRE ENSEIGNE** ;
- police : **Montserrat** ;
- hauteur : **30 cm** ;
- largeur calculée : environ **306 cm** avec l’espacement actuel ;
- éclairage : **LED indirecte** ;
- profondeur : **60 mm** ;
- fixation : **individuelle** ;
- façade : **Boutique en brique** ;
- largeur théorique de la façade : **13,5 m** ;
- position verticale du preset : 31 % ;
- vue initiale : nuit, rendu 2D.

La configuration locale comporte `defaultsVersion: 2`. Les anciennes valeurs automatiques ont été
invalidées afin que ce nouveau défaut apparaisse également chez les visiteurs déjà venus. Les choix
effectués après cette version restent mémorisés dans `localStorage`.

### Options

- texte jusqu’à 18 caractères ;
- import de logo PNG ou SVG ;
- profils P01 à P20 ;
- filtre par type d’éclairage ;
- bouton `i` indépendant pour la fiche détaillée ;
- polices actives de la bibliothèque administrable, préchargée avec Grotesk, Élégante,
  Arrondie, Condensée, Montserrat, Bebas Neue, Oswald, Playfair Display et Pacifico ;
- couleurs de face selon les capacités réelles du profil ;
- couleurs LED selon le profil ;
- hauteur et largeur proportionnelles ;
- espacement indépendant entre lettres ;
- profondeur limitée par la fiche du profil ;
- fixation individuelle, rails, entretoises ou Dibond ;
- façades types ou image du client ;
- position horizontale, verticale et échelle ;
- jour/nuit ;
- rendu 2D réaliste et rendu 3D Three.js ;
- image de prévisualisation et devis estimatif.

### Façades types

| Preset | Largeur visible |
|---|---:|
| Façade contemporaine | 15,5 m |
| Boutique en brique | 13,5 m |
| Façade claire | 13,5 m |
| Local commercial | 16 m |

Le popup Street View explique au client comment rechercher son adresse, ouvrir Street View et
capturer une vue bien frontale. Il utilise `public/images/street-view-guide.webp`.

### Proportions

`estimateTextAspectRatio()` estime la largeur à partir :

- des caractères ;
- de la police ;
- de la hauteur ;
- de l’espacement entre caractères.

Modifier la largeur recalcule la hauteur et inversement. Une porte ou un élément connu de la façade
sert de cohérence visuelle indirecte via la largeur théorique du preset.

L’espacement augmente la largeur et le gabarit de pose, mais **ne change pas le prix des lettres**.

### Prix des enseignes

Le serveur calcule :

```text
nombre facturé = caractères Unicode alphanumériques, espaces exclus
production = hauteur_cm × nombre × tarif_cm_lettre
surcharge profondeur = max(profondeur_mm - 60, 0) × nombre × 0,35 €
sous-total = max(prix minimum, production + surcharge)
montant = sous-total × facteur profil × facteur fixation
+ 250 € HT si plaque Dibond
- remise professionnelle validée
arrondi final au multiple de 10 €
```

Facteurs de fixation :

- rails : 1,08 ;
- entretoises : 1,05 ;
- individuelle : 1,00 ;
- Dibond : facteur 1,00 + supplément fixe 250 € HT.

Au-delà de 400 cm de largeur, l’API peut retourner un mode « sur devis ».

Le client ne peut jamais imposer un prix depuis le navigateur : l’API recalcule tout.

### P01 à P20

| Profil | Construction principale |
|---|---|
| P01 | Métal rétroéclairé, dos opale, halo arrière |
| P02 | Face acrylique et listel, éclairage frontal |
| P03 | Acrylique 10 mm, face et halo arrière |
| P04 | Double éclairage opale |
| P05 | Acrylique massif 10 mm |
| P06 | Face et chants acryliques lumineux |
| P07 | Face opale 6 mm |
| P08 | Face opale massive 18 mm |
| P09 | Métal avec éclairage latéral |
| P10 | Lettre sandwich |
| P11 | Métal avec ampoules apparentes |
| P12 | Bloc LED 30 mm |
| P13 | Mousse végétale, non lumineuse |
| P14 | Aluminium rétroéclairé |
| P15 | Acrylique peint rétroéclairé |
| P16 | Ligne LED intégrée |
| P17 | Caisson avec lettres traversantes |
| P18 | Caisson avec acrylique incrusté |
| P19 | Angel Deluxe / lumière latérale premium |
| P20 | Acrylique 18 mm avec bordure aluminium |

La source de vérité est `app/sign-profile-specs.ts`, complétée par la table
`sign_profile_catalog`. Chaque modification doit être contrôlée dans : 2D, 3D, popup, admin,
devis, BAT, SVG, fiche atelier française et fiche turque.

---

## 7. Configurateur néon LED

### Principe

Le texte doit former un ruban lumineux continu autant que possible. La bibliothèque est préchargée avec :

- Pacifico / Signature ;
- Dancing Script ;
- Allura ;
- Sacramento ;
- Satisfy.

Lobster a été retirée car elle n’est pas adaptée à la fabrication continue.

### Bibliothèque de polices des configurateurs

`/admin/polices` permet d’ajouter des fichiers TTF, OTF ou WOFF, de les affecter au
configurateur enseigne et/ou néon, de régler leur facteur de largeur et leur ordre, puis de les
désactiver sans casser les anciens dossiers. Une police affectée au néon doit être marquée
manuscrite afin de préserver la continuité du tracé. Les polices WOFF2 ne sont pas acceptées :
le moteur OpenType doit pouvoir relire le fichier pour fabriquer le SVG aux dimensions réelles.

Une police désactivée disparaît immédiatement des nouveaux configurateurs, mais son fichier reste
disponible pour restituer les devis, BAT et fichiers de production historiques. Le rendu 2D, le
volume Three.js et le SVG atelier utilisent tous la même source de police.

### Options

- texte manuscrit ;
- largeur et hauteur proportionnelles ;
- tube 6, 8 ou 10 mm ;
- blanc chaud, blanc froid, rose, rouge, bleu ou violet ;
- plexiglas contour, rectangle ou pose sans plaque apparente ;
- entretoises, suspension, vitrine ou pose directe ;
- quantité ;
- import SVG, PDF, PNG ou JPG, 8 Mo maximum ;
- tarif revendeur validé côté serveur ;
- devis, BAT, SVG/PDF vectoriel et fiches atelier FR/TR.

### Prix

La longueur de tube est estimée ainsi :

```text
max(1,2 m, caractères × hauteur_m × 2,25 × facteur_police)
```

Le prix additionne le forfait, la longueur de tube, le diamètre, le support, la surface,
la fixation, l’alimentation et les transformateurs supplémentaires. Des remises quantité sont
appliquées à partir de 2, 5 et 10 exemplaires, puis la remise professionnelle.

Les 24 règles sont administrables dans `neon_pricing_rules`.

Le SVG de production utilise le groupe `NEON_CONTOURS`.

---

## 8. Configurateur cadres textiles

### Profils disponibles

| Code | Profondeur | Faces | Éclairage |
|---|---:|---:|---|
| TF15S | 15 mm | 1 | non |
| TF30S | 30 mm | 1 | non |
| TF45S | 45 mm | 1 | non |
| TF60D | 60 mm | 2 | non |
| TF75S | 75 mm | 1 | LED |
| TF80D | 80 mm | 2 | LED |
| TF100S | 100 mm | 1 | LED |
| TF120D | 120 mm | 2 | LED |
| TFBOX | 100 mm | 4 | LED |

Chaque profil possède un bouton `i` et un popup indépendant.

### Visuel client

- JPG, PNG ou WEBP ;
- 8 Mo maximum dans le configurateur ;
- image affichée en direct dans le cadre ;
- effet d’éclairage projeté vers l’avant pour les profils LED ;
- original stocké dans le dossier client ;
- même image réutilisée dans le devis, le BAT et la fiche atelier.

### Prix

```text
surface = largeur_m × hauteur_m
périmètre = 2 × (largeur_m + hauteur_m)
prix unitaire = max(
  prix minimum,
  surface × (tarif profil_m² + tarif impression_m²)
  + périmètre × profondeur_mm × 0,55 €
)
```

Impression : 38 €/m² simple face et 68 €/m² pour plusieurs faces. Remise quantité de 5 % à
partir de 5 exemplaires et 10 % à partir de 10, puis remise professionnelle.

### Fabrication

`textileManufacturingSizes()` calcule :

- cadre fini extérieur ;
- ouverture ;
- débits horizontaux et verticaux à 45° ;
- toile finie avec jonc ;
- fichier avec fond perdu ;
- nombre de faces ;
- longueur de jonc silicone par face.

---

## 9. Parcours commercial commun

### États du dossier

- `new` : demande reçue ;
- `review` : étude ou correction ;
- `quoted` : devis/BAT publié ;
- `won` : BAT accepté ;
- `lost` : affaire perdue ou clôturée.

L’archivage est indépendant : `archived = 1` masque le dossier des vues actives sans modifier son
statut métier. Les listings proposent : recherche, statut, actifs/archivés/tous, date de début,
date de fin et pagination de 25 dossiers.

### États du BAT privé

- `pending` : attente client ;
- `accepted` : signé ;
- `changes_requested` : correction demandée.

### Cycle complet

```text
configuration publique ou saisie manuelle
→ demande new
→ étude review
→ chiffrage commercial
→ génération du token et du BAT
→ quoted / pending
→ consultation client
→ signature accepted / won
ou
→ correction changes_requested / review
→ correction interne
→ republication du BAT / pending
```

Une republication :

- conserve le même lien privé ;
- efface l’ancienne signature et l’ancienne réponse ;
- remet le BAT en attente ;
- republie le SVG épinglé correspondant à cette version.

Une réponse client n’est acceptée qu’une fois grâce à une mise à jour conditionnelle de l’état
`pending`.

### Validité

- validité réglable entre 1 et 90 jours ;
- calcul depuis la dernière mise à jour du devis ;
- un devis expiré ne peut pas être signé.

### Acceptation et correction

Pour accepter : nom du signataire et signature manuscrite PNG obligatoires. Pour demander une
correction : nom et commentaire obligatoires. Le commentaire apparaît immédiatement dans
l’administration et repasse le dossier en étude.

---

## 10. Devis, BAT, PDF et fichiers vectoriels

### Documents commerciaux

- devis estimatif public ;
- devis commercial administrateur ;
- BAT client privé ;
- vue façade lorsqu’elle existe ;
- vue à plat pour comprendre les proportions ;
- montant HT, validité, référence et options ;
- logo PyramidCom et filigrane discret ;
- export PDF A4 sans pages vides.

Les PDF commerciaux sont produits par capture maîtrisée du document HTML. Ils servent à la lecture,
pas à la découpe industrielle.

### SVG de production

`lib/production-svg.ts` fournit l’aperçu navigateur et `lib/production-svg-server.ts` convertit les
polices en vrais contours côté serveur avec OpenType.js.

Le SVG :

- travaille en millimètres ;
- possède un `viewBox` aux dimensions finales ;
- respecte largeur, hauteur, police et espacement ;
- n’est jamais inversé ;
- utilise `CUT_CONTOURS` pour les lettres ;
- utilise `NEON_CONTOURS` pour le néon.

Un logo SVG client est nettoyé avant intégration : scripts, `foreignObject`, animations, événements
et URL externes sont retirés ou refusés.

### Versionnement du SVG

Lors de « Enregistrer le devis et générer le BAT » :

1. le serveur génère le SVG ;
2. il compare son contenu à la dernière version ;
3. il crée `REFERENCE-production-v01.svg`, puis `v02`, etc. uniquement si le tracé change ;
4. il enregistre le fichier dans `project_files`, catégorie `production` ;
5. il épingle son identifiant dans `commercial_quotes.production_file_id`.

Le devis, le BAT et la fiche atelier lisent ce fichier épinglé afin qu’une correction non publiée
ne change jamais un BAT déjà transmis.

Un PNG/JPG n’est pas présenté comme vectoriel. La fiche avertit que la source raster doit être
contrôlée ou remplacée par un SVG/AI/EPS.

### Formats par famille

| Famille | Documents et sorties |
|---|---|
| Enseigne | devis façade/à plat, BAT, SVG/PDF vectoriel, atelier FR/TR |
| Néon | devis à plat, BAT, SVG/PDF vectoriel, atelier FR/TR |
| Textile | devis avec visuel, BAT, atelier FR/TR, débits et toile |
| Projet libre | devis/BAT joint, fichiers admin, atelier FR uniquement |

---

## 11. Fiches atelier

### Règles communes

- vue à plat, sans photo de façade pour la fabrication ;
- cotes cohérentes avec la hauteur réelle des lettres ;
- profil choisi affiché avec ses composants ;
- fichiers internes listés avec leur catégorie sous forme de badge ;
- contrôles atelier à cocher ;
- statut d’acceptation visible ;
- « NE PAS PRODUIRE » contenu dans la zone imprimable ;
- footer sans téléphone ;
- le nom du client est masqué sur la fiche turque et les vues atelier privées.

### Français et turc

Les enseignes, néons et textiles possèdent une fiche française et une fiche turque. Le projet libre
n’a qu’une fiche française.

Les termes techniques sont modifiables dans :

```text
Administration → Site & CMS → Traductions atelier
```

La fiche turque peut être envoyée par e-mail avec :

- le lien privé de la fiche ;
- le lien du SVG 1:1 pour enseigne ou néon ;
- les fichiers marqués « partager avec l’atelier ».

---

## 12. Projets manuels et fichiers

L’administration peut créer manuellement une enseigne, un néon, un cadre textile ou un projet
libre. Les préfixes sont :

- `PYR-` : enseigne ;
- `TXT-` : textile ;
- `NEO-` : néon ;
- `PRJ-` : projet libre.

La référence est préremplie avec un timestamp en base 36, mais peut être modifiée si elle respecte
le format attendu.

Le formulaire permet de saisir le client et d’importer un BAT initial SVG, PDF, AI, EPS, JPG, PNG
ou WEBP, jusqu’à 30 Mo. La description et les consignes sont modifiables dans le dossier. Leur
modification remet le projet en étude.

Chaque dossier accepte des fichiers internes avec :

- nom du document ;
- catégorie ;
- badge de catégorie dans la liste ;
- téléchargement ;
- suppression ;
- option « partager avec l’atelier » ;
- token individuel lorsque le fichier est partagé.

La suppression d’un dossier demande une confirmation explicite et efface les fichiers associés,
le devis, le lien atelier et les données commerciales.

---

## 13. Revendeurs et compte professionnel

### Candidature

Le formulaire demande : société, contact, e-mail, téléphone, SIRET, activité et volume estimé.

États :

- `pending` ;
- `approved` ;
- `rejected` ;
- `suspended`.

À l’approbation, un token hexadécimal aléatoire de 48 caractères donne accès à `/pro?token=…`.

### Remise

La case « tarif revendeur » dans un configurateur ne suffit jamais. Le serveur contrôle que
l’adresse appartient à un compte approuvé, puis applique le pourcentage configuré dans la fiche
tarifaire du produit.

L’espace professionnel affiche les projets correspondant à l’e-mail du revendeur.

Une invitation HTML peut être envoyée depuis l’administration.

---

## 14. CMS

### Rubriques

`/admin/cms` possède cinq onglets :

1. Pages ;
2. Réalisations ;
3. Références ;
4. Traductions atelier ;
5. Signature e-mail.

### Contenus

Chaque contenu peut avoir : valeur brouillon, valeur publiée, date de modification, date de
publication et historique dans `cms_revisions`. Le site public ne lit que la version publiée.

### Médias

- réalisation : JPG, PNG ou WEBP, 10 Mo maximum ;
- logo de référence : JPG, PNG, WEBP ou SVG, 5 Mo maximum ;
- contrôle strict des SVG ;
- suppression du fichier physique lorsqu’un média CMS est supprimé.

### Signature e-mail

La signature PyramidCom est personnalisable : nom, fonction, e-mail et téléphone. Elle propose :

- aperçu dans une iframe ;
- copie riche `text/html` + `text/plain` pour Apple Mail, Outlook et Gmail ;
- secours par sélection DOM pour Safari ;
- second bouton pour copier le code HTML brut ;
- logo blanc PNG, plus compatible avec les clients de messagerie que le SVG.

Installation dans Mail sur Mac : Mail → Réglages → Signatures → créer une signature vide → cliquer
dans la zone de droite → coller avec `⌘V`.

---

## 15. E-mails transactionnels

### Transport

- Nodemailer ;
- SMTP IONOS `smtp.ionos.fr` ;
- port 465, connexion sécurisée ;
- envoi depuis noreply@pyramidcom.fr ;
- réponses vers projets@pyramidcom.fr ;
- pool limité à deux connexions ;
- HTML et texte brut systématiques.

### Modèles

`lib/email-templates.ts` contient :

- accusé de réception client ;
- notification interne de nouvelle demande ;
- devis et BAT prêts ;
- confirmation de signature ;
- confirmation de demande de correction ;
- invitation revendeur ;
- dossier atelier turc ;
- signature PyramidCom commune.

Les styles sont en ligne pour maximiser la compatibilité avec Apple Mail, Gmail et Outlook.

### Déclencheurs

- nouvelle demande enseigne, néon ou textile : accusé client + notification interne ;
- demande manuelle : notification selon le parcours ;
- décision BAT : confirmation client + notification interne ;
- bouton admin : envoi du devis/BAT ;
- bouton admin : invitation revendeur ;
- bouton admin : dossier atelier turc.

### Journal

Chaque tentative est inscrite dans `email_logs` avec : famille, dossier, modèle, destinataire,
objet, statut `sent`/`failed`, identifiant SMTP, message d’erreur et date.

Une panne SMTP ne supprime jamais la demande déjà enregistrée.

---

## 16. Base de données

### Tables de production

| Table | Rôle |
|---|---|
| `quote_requests` | demandes enseignes |
| `textile_quote_requests` | demandes cadres textiles |
| `neon_quote_requests` | demandes néons |
| `manual_projects` | projets libres |
| `professional_applications` | candidatures et comptes revendeurs |
| `pricing_profiles` | prix P01–P20 |
| `sign_profile_catalog` | descriptions et spécifications P01–P20 |
| `textile_pricing_profiles` | prix des neuf profils textiles |
| `neon_pricing_rules` | 24 règles tarifaires néon |
| `commercial_quotes` | chiffrage, token BAT, réponse, signature et SVG publié |
| `project_files` | fichiers internes, production et partages |
| `factory_access_links` | tokens atelier |
| `cms_content` | contenus brouillons et publiés |
| `cms_revisions` | historique CMS |
| `cms_portfolio` | réalisations |
| `cms_references` | références clients |
| `admin_users` | comptes administrateurs |
| `email_logs` | journal des e-mails |
| `generator_fonts` | bibliothèque, affectations et état des polices des configurateurs |

### Conventions

- prix en centimes ;
- pourcentages en points de base lorsque nécessaire, 10 000 = 100 % ;
- dates en timestamps millisecondes ;
- clés de fichiers relatives à `UPLOAD_DIR` ;
- MariaDB en `utf8mb4`.

### Initialisation

`db/ensure-schema.ts` vérifie les 19 tables et rattrape les colonnes/index nécessaires au runtime.
Il doit rester synchronisé avec `db/schema.ts` et les scripts de migration.

### Migration historique du 30 juillet 2026

L’export D1/R2 a été converti et importé dans MariaDB/stockage local. Comptes observés juste après
migration, à considérer comme une photographie historique et non comme les volumes actuels :

| Donnée | Nombre |
|---|---:|
| Demandes enseignes | 6 |
| Demandes textiles | 5 |
| Demandes néons | 4 |
| Projets manuels | 1 |
| Candidatures pro | 4 |
| Devis commerciaux | 8 |
| Fichiers projet | 3 |
| Contenus CMS | 165 |
| Réalisations | 19 |
| Références | 9 |
| Profils tarifaires enseignes | 20 |
| Profils textiles | 9 |
| Règles néon | 24 |
| Profils de construction | 20 |
| Liens atelier | 4 |

Les données de démonstration, tarifs, CMS, références, réalisations et comptes revendeurs ont été
conservés volontairement.

---

## 17. API

### Publiques

| Route | Méthode | Fonction |
|---|---|---|
| `/api/site-content` | GET | contenus, réalisations et références publiés |
| `/api/sign-profiles` | GET | profils actifs |
| `/api/fonts` | GET | polices actives, filtrables par configurateur |
| `/api/font-assets` | GET | fichier de police utilisé par les rendus et le vectoriel |
| `/api/pricing` | POST | prix enseigne |
| `/api/textile-pricing` | POST | prix textile |
| `/api/neon-pricing` | POST | prix néon |
| `/api/quote-requests` | POST | nouvelle enseigne |
| `/api/textile-quote-requests` | POST | nouveau cadre textile |
| `/api/neon-quote-requests` | POST | nouveau néon |
| `/api/professional-applications` | POST | candidature revendeur |
| `/api/public-quotes` | GET/POST | lecture et décision BAT |
| `/api/public-quote-assets` | GET | fichiers autorisés du BAT |
| `/api/pro-account` | GET | espace revendeur par token |
| `/api/factory-project` | GET | dossier atelier par token |
| `/api/factory-assets` | GET | fichier produit atelier |
| `/api/project-files` | GET | fichier partagé par token |
| `/api/cms-media` | GET | média CMS |

### Administratives

Toutes vérifient la session :

- `/api/admin` ;
- `/api/admin/email` ;
- `/api/admin/email-signature` ;
- `/api/admin/manual-request` ;
- `/api/admin/factory-access` ;
- `/api/admin/production-svg` ;
- `/api/admin/fonts` ;
- `/api/admin/portfolio` ;
- `/api/admin/references` ;
- écritures/suppressions de `/api/project-files` ;
- accès directs aux fichiers enseigne, textile et néon.

---

## 18. Authentification et sécurité

### Administrateurs

- connexion par e-mail et mot de passe ;
- mot de passe dérivé avec `scrypt` ;
- comptes stockés dans `admin_users` ;
- session HMAC signée par `SESSION_SECRET` ;
- cookie `HttpOnly`, `Secure`, `SameSite=Lax` ;
- durée de session : 12 heures ;
- en développement, accès automatique sauf `ALLOW_DEV_ADMIN=false`.

Création/réinitialisation :

```bash
npm run admin:create -- "email@pyramidcom.fr" "Nom affiché"
```

Les premiers administrateurs de production sont `jh@pyramidcom.fr` et
`contact@pyramidcom.fr`.

### Tokens privés

- revendeur : 48 caractères hexadécimaux ;
- atelier : 48 caractères hexadécimaux ;
- fichier partagé : 48 caractères hexadécimaux ;
- BAT : valeur aléatoire enregistrée dans `commercial_quotes`.

Ne jamais journaliser ou afficher publiquement ces tokens.

### Fichiers

- validation type MIME, extension et taille côté serveur ;
- clés normalisées et enfermées dans `UPLOAD_DIR` ;
- `X-Content-Type-Options: nosniff` sur les téléchargements ;
- SVG nettoyés ;
- fichier atelier accessible seulement si `factory_visible = 1` ;
- suppression physique avec les enregistrements métier.

### Limites

- uploads configurateurs : 8 Mo ;
- réalisation CMS : 10 Mo ;
- logo référence : 5 Mo ;
- BAT/fichier projet : 30 Mo ;
- corps des requêtes Next : 32 Mo.

### RGPD restant à formaliser

Le système stocke des coordonnées, signatures et fichiers clients. Il reste à documenter
juridiquement : durée de conservation, politique d’effacement, registre des traitements,
consentement, responsables d’accès et procédure d’export/suppression.

---

## 19. Responsive et compatibilité navigateurs

### Cibles officielles Next.js 16

- Edge 111+ ;
- Safari 16.4+ ;
- Chrome 111+ ;
- Firefox 111+.

### Contrôles réalisés

- desktop 1440 × 900 ;
- mobile 390 × 844 ;
- absence de débordement horizontal ;
- header mobile ;
- accueil ;
- configurateurs enseigne, néon et textile ;
- popup de profil plein écran ;
- administration et signature e-mail ;
- absence d’erreurs console lors des tests ciblés.

Edge desktop/mobile utilise Chromium. Safari est couvert par la cible Next.js, les contrôles de
fonctionnalités et les mécanismes de secours ; un test physique Mac/iPhone reste conseillé lors de
chaque refonte importante.

Fonctions sensibles : `ClipboardItem`, `color-mix()`, `100dvh`, `backdrop-filter`, FileReader,
Object URL, Canvas, SVG et WebGL. La copie de signature utilise une solution de secours compatible
Safari.

---

## 20. SEO

- métadonnées globales dans `app/layout.tsx` ;
- données structurées `LocalBusiness` ;
- sitemap XML ;
- robots.txt ;
- canonical `https://pyramidcom.fr` ;
- redirections permanentes de `www` vers le domaine nu et de HTTP vers HTTPS ;
- pages service avec titres et descriptions uniques ;
- admin, API, BAT, devis et espace pro non indexables ;
- favicon SVG et aperçu social de marque.

Toute nouvelle page publique doit recevoir un titre, une description, un canonical, un lien interne
et, si nécessaire, une entrée dans le sitemap.

---

## 21. Production o2switch

### Configuration

```text
Domaine                 https://pyramidcom.fr
Node.js                 24.18.0, mode Production
Application             /home2/haja6102/pyramidcom.fr/app
Données persistantes    /home2/haja6102/pyramidcom.fr/data/uploads
Base                    haja6102_pyramidcom
Utilisateur SQL         haja6102_pyramidcom
Socket MariaDB          /var/lib/mysql/mysql.sock
Dépôt serveur           /home2/haja6102/repositories/pyramidcom
Dépôt GitHub             Jamel90/pyramidcom
Branche source          main
Branche compilée        deploy
```

Le mot de passe MariaDB et les autres secrets ne doivent jamais apparaître dans ce fichier.

### Variables obligatoires

Voir `.env.production.example` :

- DB_HOST, DB_PORT, DB_SOCKET, DB_NAME, DB_USER, DB_PASSWORD ;
- DB_POOL_SIZE ;
- UPLOAD_DIR ;
- SESSION_SECRET ;
- NEXT_PUBLIC_SITE_URL ;
- SMTP_HOST, SMTP_PORT, SMTP_SECURE, SMTP_USER, SMTP_PASSWORD ;
- MAIL_FROM, MAIL_REPLY_TO.

`app.js` refuse de démarrer en production si DB_NAME, DB_USER, DB_PASSWORD, UPLOAD_DIR ou
SESSION_SECRET manquent.

### Passenger

Le fichier de démarrage est `app.js`. Il :

- force `NODE_ENV=production` lorsque `PASSENGER_APP_ENV=pyramidcom-production-v2` ;
- écrit dans `app/tmp/app-error.log` ;
- capture exceptions, rejets et `console.error` ;
- vérifie les variables obligatoires avant Next.js ;
- prépare Next puis écoute le port fourni par Passenger.

Le groupe Passenger initial est resté bloqué pendant la première mise en service. Le groupe
versionné `pyramidcom-production-v2` a permis de repartir sur un processus sain.

### Build GitHub

`.github/workflows/build-production.yml` :

1. se déclenche sur `main` ;
2. utilise Node 24 ;
3. exécute `npm ci` ;
4. exécute `npm run build` ;
5. crée une branche orpheline `deploy` avec le build `.next` ;
6. pousse `deploy` en force.

Le workflow peut afficher un avertissement indiquant que `actions/checkout@v4` et
`actions/setup-node@v4` ciblent encore le runtime interne Node 20. Le job est actuellement forcé
sur Node 24 et réussit, mais les actions devront être mises à jour lorsqu’une version officielle
plus récente sera stabilisée.

### Déploiement serveur

Le Cron cPanel, toutes les cinq minutes, récupère le script depuis `origin/deploy` puis l’exécute.
Commande actuelle recommandée :

```bash
cd /home2/haja6102/repositories/pyramidcom && (git fetch origin deploy --quiet && git show origin/deploy:scripts/deploy-o2switch.sh > /home2/haja6102/.deploy-pyramidcom.sh && /bin/bash /home2/haja6102/.deploy-pyramidcom.sh) >> /home2/haja6102/pyramidcom.fr/app/tmp/deploy-cron.log 2>&1
```

Planification :

```text
*/5 * * * *
```

Le regroupement entre parenthèses est important : il redirige aussi les messages de `git fetch`
et évite les e-mails automatiques Cron pour une simple clé SSH ajoutée à `known_hosts`.

### Script `deploy-o2switch.sh`

- verrou `flock` contre deux déploiements simultanés ;
- compare la branche `deploy` au marqueur `.deployed_sha` ;
- extrait le build dans un dossier temporaire validé ;
- `rsync --delete` sans toucher à `tmp`, `data`, `.env` ni au marqueur ;
- active l’environnement Node 24 ;
- exécute `npm ci --omit=dev` ;
- garantit `PassengerAppEnv pyramidcom-production-v2` ;
- touche `tmp/restart.txt` ;
- teste https://pyramidcom.fr ;
- n’écrit le nouveau marqueur que si le contrôle HTTP réussit.

### Journaux

| Fichier | Contenu |
|---|---|
| `app/tmp/app-error.log` | démarrage et erreurs applicatives |
| `app/tmp/deploy.log` | détails du script de déploiement |
| `app/tmp/deploy-cron.log` | sortie complète du Cron |
| `data/deploy.lock` | verrou de déploiement |

### Sauvegardes

- JetBackup sauvegarde le répertoire personnel ;
- les uploads sont hors du dossier remplacé à chaque déploiement ;
- l’archive initiale de migration est conservée hors de la racine publique ;
- réaliser aussi des exports MariaDB périodiques.

---

## 22. Incidents rencontrés et solutions retenues

### Build `EAGAIN` / trop de processus

Sur o2switch, Next essayait de lancer trop de workers. Solution dans `next.config.ts` :

```text
cpus: 1
workerThreads: false
```

Le build est désormais réalisé sur GitHub, ce qui évite aussi de consommer les ressources cPanel.

### Avertissement racine Next.js

Plusieurs `package-lock.json` existaient sous le compte. `turbopack.root = process.cwd()` évite que
Next choisisse la mauvaise racine.

### Mot de passe MariaDB avec `$`

Une saisie shell incorrecte avait capturé 119 puis 28 caractères au lieu du vrai mot de passe.
Toujours utiliser une lecture silencieuse sans expansion, puis exporter explicitement :

```bash
read -r -s -p 'Mot de passe MariaDB : ' DB_PASSWORD
echo
export DB_PASSWORD
```

Ne jamais écrire le mot de passe en clair dans la commande.

### Migration SQL avec antislash

Le premier export contenait des antislashs littéraux dans `CREATE TABLE`. Le script de conversion
a été corrigé avant l’import MariaDB final.

### Passenger 500 sans journal

Le processus manuel `node app.js` fonctionnait mais Passenger ne lançait pas l’application. Le
launcher instrumenté, le journal `app-error.log`, les variables cPanel complètes et le groupe
Passenger versionné ont permis le démarrage.

### `/dev/fd/62` introuvable

CloudLinux ne supportait pas la substitution de processus utilisée par `tee`. Le script se relance
maintenant une seule fois dans un pipeline classique et récupère `PIPESTATUS[0]`.

### Index de répertoire exposé

Pendant la bascule, le domaine a affiché le contenu du répertoire car l’application n’était plus
liée correctement. L’application Node a été recréée avec : racine `pyramidcom.fr/app`, URL racine
du domaine et fichier `app.js`.

### Cron envoyant des e-mails

La redirection ne couvrait que la dernière commande. Toute la chaîne est maintenant placée dans
une sous-commande groupée redirigée vers `deploy-cron.log`.

---

## 23. Commandes utiles

### Local

```bash
npm ci
npm run dev
npm run build
node --test tests/rendered-html.test.mjs
npm run lint
```

### Production, depuis l’environnement virtuel

```bash
source /home2/haja6102/nodevenv/pyramidcom.fr/app/24/bin/activate
cd /home2/haja6102/pyramidcom.fr/app
node -v
npm -v
npm run smtp:test
```

### Créer un administrateur

```bash
npm run admin:create -- "email@pyramidcom.fr" "Administrateur"
```

### Tester MariaDB sans révéler le mot de passe

```bash
/usr/bin/mariadb --protocol=SOCKET --socket=/var/lib/mysql/mysql.sock \
  -u haja6102_pyramidcom -p haja6102_pyramidcom -Nse "SELECT 1;"
```

---

## 24. Validation avant livraison

### Automatique

- `npm run build` ;
- TypeScript terminé sans erreur ;
- `node --test tests/rendered-html.test.mjs` ;
- actuellement **21 tests** ;
- `git diff --check`.

### Fonctionnelle

- accueil et vidéo hero ;
- header desktop/mobile ;
- pages SEO et Réalisations ;
- P01 et P20 en 2D/3D ;
- couleurs face et LED ;
- espacement et proportions ;
- supplément Dibond ;
- néon et son SVG ;
- textile, image et fiche de débit ;
- demande client ;
- e-mails de réception ;
- connexion admin ;
- création manuelle ;
- devis commercial ;
- BAT privé ;
- signature et correction ;
- PDF sans page vide ;
- fiche atelier FR/TR ;
- SVG 1:1 ;
- fichiers partagés ;
- candidature et invitation pro ;
- CMS, réalisation, référence et signature e-mail.

---

## 25. Fonctions non présentes ou limites assumées

Ne pas annoncer comme disponibles :

- paiement Stripe ou acompte en ligne ;
- génération native Illustrator `.ai` ;
- conversion fiable d’un PNG/JPG en vrai fichier vectoriel industriel ;
- validation automatique de faisabilité atelier ;
- CAO industrielle ;
- planning complet de production/pose de type ERP ;
- gestion des stocks et achats ;
- rôles administrateurs granulaires ou MFA ;
- expiration automatique complète de tous les tokens ;
- politique RGPD et rétention entièrement automatisée ;
- garantie visuelle sur les anciennes versions de Safari/Edge ;
- prévisualisation 3D considérée comme plan de fabrication.

---

## 26. Prochaines priorités recommandées

### Priorité haute

1. Formaliser la conservation et la suppression des données/signatures.
2. Mettre en place des sauvegardes MariaDB exportables et un test de restauration.
3. Ajouter une révocation/expiration visible aux liens atelier et fichiers.
4. Ajouter des tests d’intégration réels MariaDB + stockage local.
5. Ajouter une surveillance d’erreurs sans enregistrer de tokens ou données personnelles.
6. Tester physiquement Safari sur Mac et iPhone après les grosses évolutions visuelles.

### Priorité moyenne

1. Ajouter des rôles admin et éventuellement une MFA.
2. Ajouter des tests visuels PDF et responsive automatisés.
3. Clarifier définitivement le rôle de `/devis` face à `/bat`.
4. Mettre à jour les actions GitHub lorsque leurs versions natives Node 24 seront disponibles.
5. Nettoyer les dépendances et scripts Cloudflare/Vinext devenus historiques.
6. Corriger ou retirer les anciens scripts de démonstration qui utilisent des codes obsolètes.

### Évolutions possibles

- acompte Stripe après acceptation ;
- suivi de fabrication et planning de pose ;
- notifications de relance des devis ;
- tableaux de bord de chiffre d’affaires ;
- comptes clients classiques en plus des tokens ;
- bibliothèque de plans et gabarits atelier ;
- historique de versions visuel du BAT.

---

## 27. Règles de continuité du développement

- Ne jamais recalculer un prix commercial uniquement côté navigateur.
- Ne jamais fabriquer depuis un PDF raster ou une simple estimation.
- Ne jamais inverser le SVG pour simuler une vue arrière.
- Ne jamais exposer une clé de stockage ou un token privé.
- Ne jamais afficher le nom client dans une fiche atelier publique/turque.
- Ne jamais modifier un profil P01–P20 dans un seul consommateur.
- Ne jamais ajouter un statut sans mettre à jour API, admin, BAT et espace pro.
- Ne jamais ajouter une colonne sans rattrapage `ensureSchema()` et stratégie de migration.
- Ne jamais remettre « Portfolio » dans l’interface publique.
- Ne jamais remettre le téléphone dans les footers des documents.
- Toujours conserver la vue à plat et les dimensions réelles dans les documents atelier.
- Toujours vérifier desktop, 390 px, PDF, BAT et fichiers lors d’une modification transversale.
- Toujours préserver `data/uploads`, `tmp` et les variables cPanel pendant le déploiement.
- Toujours mettre à jour ce document après une évolution structurelle.

---

## 28. Checklist d’une nouvelle fonctionnalité

- [ ] besoin métier et terminologie ;
- [ ] interface publique ou admin ;
- [ ] responsive 390 px ;
- [ ] accessibilité clavier, labels et contraste ;
- [ ] validation serveur ;
- [ ] authentification et confidentialité ;
- [ ] schéma et `ensureSchema()` ;
- [ ] migration des données existantes ;
- [ ] stockage et suppression physique ;
- [ ] états vide, chargement, erreur et toast visible ;
- [ ] prix HT et remise pro serveur ;
- [ ] devis ;
- [ ] BAT, signature et correction ;
- [ ] atelier FR ;
- [ ] atelier TR si applicable ;
- [ ] SVG/PDF de production si applicable ;
- [ ] e-mails et journal ;
- [ ] CMS si le texte doit être modifiable ;
- [ ] SEO si la page est publique ;
- [ ] tests automatisés ;
- [ ] build GitHub ;
- [ ] déploiement o2switch ;
- [ ] contrôle HTTP en production ;
- [ ] documentation mise à jour.
