# CLAUDE.md

Sito e catalogo di **Benza S.r.l.** (Sanremo, giardinaggio, irrigazione, piscine, arredo esterno,
nautica, vasi, prato sintetico, antinfortunistica). Preventivo accettato il 14/09/2026, 40 giorni
lavorativi dalla consegna del materiale. **Niente vendita online**: catalogo consultabile, con
"chiedi informazioni" al posto del carrello.

Lo stato operativo e le cose aperte stanno in `docs/STATO.md`: leggerlo per primo.
La memoria di lavoro condivisa con gli altri progetti Benza sta nella cartella memoria di
`sito_26` (`project_fusione_gruppo.md` e le trappole tecniche): se questa sessione parte da
questa cartella e non la vede, chiedere a Paolo di aprire da `sito_26` o leggere `docs/STATO.md`.

## Da dove viene

Copia adattata di `../sito_26` (Benza Water Storage, live su www.benza.it): stesso stack,
stessa struttura, stesso impianto grafico con il **verde `#006000`** al posto dell'azzurro.
Le convenzioni di `../sito_26/CLAUDE.md` valgono anche qui (API, JWT, log, bind_param,
niente N+1, query compatibili con MySQL strict). Le differenze:

- **Due livelli, non tre**: `categorie` (albero, `id_padre`) e `prodotti`. Il livello "modelli"
  non esiste. Un prodotto sta in piu' categorie (`prodotti_categorie`).
- **Varianti e caratteristiche = attributi a piu' valori** (`attributi`, `attributi_valori`,
  `prodotti_attributi`): filtri di categoria e "disponibile in: S, M, L" con lo stesso meccanismo.
- **Slug di benzashop.it invariati**: il router cerca lo slug in categorie, prodotti, marche;
  `redirect_legacy` copre gli indirizzi vecchi. Niente `categoria{id}-slug`.
- **Pagine pubbliche generate dal server** (`includes/catalogo.php`), non dal JavaScript: i
  filtri sono parametri GET. Le API pubbliche servono solo alla ricerca nella navbar.
- I dati aziendali (indirizzi, telefoni, orari, social, `@id` dei dati strutturati) stanno in
  **`includes/azienda.php`**, un posto solo. I testi fissi (otto reparti, tre servizi, cataloghi
  PDF) in **`includes/contenuti_statici.php`**, come funzioni. I reparti si vedono in testa alla categoria
  di primo livello (`categoria.php`); i servizi in `servizio-<slug>`; i PDF in `cataloghi`.
- Foto in `files/prodotti|categorie|marche/` (fuori da git), PDF in `files/pdf/`.
- Nessun utente pubblico, carrello, ordini, pagamenti, gestionale: il codice relativo non e'
  stato copiato. Non reintrodurlo.

## Ambienti

- Sviluppo: `https://nas.duebytes.synology.me/Lavoro/Benza/benzasrl_26/` (ogni pagina risponde
  404 pur funzionando: e' il NAS, in produzione no). Admin: `/admin/login`, account di sviluppo
  in `docs/sql/002_account_sviluppo.sql`.
- DB di sviluppo: `Benza_srl_catalogo` sul NAS, **l'unico dove `devuser` scrive** (eccezione alla
  skill `db-query`). Schema in `docs/sql/001_schema_catalogo.sql`.
- Produzione: sottodominio di benza.it su Aruba (nome e accessi ancora da ricevere da Paolo).
  `BZ_HOST_PROD` in `includes/config.php` e `api/config/funzioni_api.php` e' la riserva
  (`www.benzasrl.it`) finche' non c'e' il sottodominio. `BZ_GA4_ID` vuoto = niente Analytics.
- Segreti: `api/config/secrets.php` e `includes/mail_config.php` non versionati.

## Import da nopCommerce

Il catalogo viene dal dump di benzashop.it (SQL Server) aperto in Docker: procedura, comandi e
rapporto in `docs/STATO.md` e `docs/import/`. I dati estratti stanno su disco locale in
`~/Downloads/benzasrl_import/`, **mai sulla share SMB** (che con le scritture in massa cade).

## Trappole gia' pagate

- `(new Database())->getConnection()->prepare()` chiude la connessione prima dell'execute
  ("MySQL server has gone away"): tenere la connessione in una variabile, o usare `bz_db()`.
- Nel router la pagina va servita **fuori** dal `try` della ricerca slug: un errore dentro la
  pagina deve emergere come 500, non diventare un 404 silenzioso.
- In una query con `pc.id_prodotto` e `p.id_prodotto` nel JOIN, `COUNT(DISTINCT id_prodotto)`
  e' ambiguo: qualificare sempre.
- Il router include la pagina **a livello globale**, mai dentro una funzione: le pagine
  definiscono `$apiUrl`, `$keyEnc`, `$BZ_AZIENDA` che le funzioni leggono con `global`. Con
  l'include dentro `bz_servi()` l'admin accettava il login e subito rimandava alla pagina di
  accesso (`adminLogged` vedeva `$apiUrlInternal` vuoto). Lo stesso motivo per cui i contenuti
  fissi sono funzioni (`bz_reparti()`) e non variabili.
- Per verificare un glifo del font ritagliato, `measureText` non torna 0 se manca: torna la
  larghezza del glifo di riserva. Confrontare con un punto di codice sicuramente assente
  (`U+F000`) e ricaricare senza cache: il browser tiene il woff2 vecchio con lo stesso nome.

## Preferenze

Come in sito_26: italiano ovunque, risposte corte, verificare sul NAS prima di dire che
funziona, documentare in `docs/STATO.md` (non c'e' un REFACTORING_LOG separato finche' il sito
non e' online), aggiornare la memoria dopo ogni task senza aspettare che Paolo lo chieda.
