# Refactoring Log: manutenzione del sito in produzione

Sito **live su www.benza.it** dall'11/07/2026. Qui si registrano bug-fix e modifiche richieste da Benza,
voce più recente in cima.

La storia del rifacimento (aprile-luglio 2026, fino al go-live) è in
[`archive/REFACTORING_LOG_rifacimento.md`](archive/REFACTORING_LOG_rifacimento.md).

---

## 14/09/2026, gallery admin: si puo' sostituire una foto, anteprime in 4:3

Richiesta di Paolo. Dalla scheda "Modifica" di un'immagine in gallery si potevano cambiare nome,
descrizione e gallery, ma non il file. Il vecchio admin lo permetteva (`sito_old/admin/immagine.php`
aveva i campi di caricamento anche sull'immagine esistente): regressione nostra, non un extra.

Aggiunto `api/admin/immagini/sostituisci.php`: genera le tre versioni (originale, media 1400 px,
miniatura 500 px) con nomi nuovi, cosi' il browser non ripropone la copia in cache, aggiorna il
record e cancella i file vecchi se nessun altro record li usa. Nel modale c'e' il campo
"Sostituisci immagine" con anteprima dal vivo.

Le anteprime, nel modale e nelle card dell'elenco, sono ora in 4:3 con lo stesso taglio della
gallery pubblica: quello che resta fuori nell'admin resta fuori anche sul sito, e Benza lo vede
prima di pubblicare.

Provato sul NAS (immagine 865 sostituita con se stessa, file vecchi rimossi) e messo in produzione
via FTP con dimensioni verificate.

---

## 10/09/2026 — Giacenze: i titoli dicono dove stanno i teli

Richiesta di Benza. Nella pagina delle giacenze le due sezioni si chiamavano solo "Rotoli
interi" e "Spezzoni", ma in azienda la distinzione non e' solo di formato: i rotoli stanno
anche nel magazzino Ravera, gli spezzoni sono in via Pascoli. Chi legge il report deve
sapere dove andare a prendere il telo.

Titoli aggiornati in `admin/giacenze.php` e negli stessi due titoli del PDF di stampa
(`admin/giacenze_report_pdf.php`), che altrimenti sarebbe rimasto indietro: e' la copia
che gira per il magazzino, ed e' proprio quella a cui serve l'indicazione.

**Notato di passaggio, non sistemato.** Il login admin (`admin/login.php`) carica ancora
Bootstrap e Font Awesome dai CDN: il lavoro del 09/09 sugli asset locali ha toccato solo il
percorso pubblico. Non e' urgente, l'admin lo vedono in tre, ma prima o poi va allineato.

---

## 08/09/2026 — Lavoro fuori da questo sito: la landing gruppobenza.it

Nato in questi giorni un progetto separato, `gruppobenza_26`, con il suo repository e la
sua documentazione: la landing del Gruppo Benza, online su **www.gruppobenza.it**. Qui si
annota solo quello che tocca *questo* sito.

**Da fare su benza.it, non ancora fatto.** I due interventi che chiudono il triangolo di
menzioni fra i tre domini, che è il motivo per cui la landing ha valore GEO:
- un link a `https://www.gruppobenza.it/` nel footer di `footer_pub.php`;
- `"parentOrganization": { "@id": "https://www.gruppobenza.it/#gruppo" }` dentro il blocco
  JSON-LD `Organization` di `header_pub.php`.

Senza questi due, la landing resta un sito che parla del gruppo ma che nessuno degli altri
due conferma.

**Una cosa emersa e non ancora sistemata qui.** Lo snippet del Consent Mode di benza.it
dichiara solo `analytics_storage: 'denied'`: i tipi non elencati valgono `granted`, quindi
`ad_storage`, `ad_user_data` e `ad_personalization` restano concessi mentre `privacy.php`
promette che cookie di profilazione non ce ne sono. Oggi nessun cookie pubblicitario viene
scritto davvero, ma sulla landing i quattro tipi sono negati per nome e `gtag.js` si
scarica solo dopo l'accettazione. I due siti si comportano diversamente: allineare benza.it
è un lavoro piccolo e in sospeso.

---

## 07/09/2026 — Rimessi Google Analytics e il banner cookie

Benza ha cambiato di nuovo idea, stavolta al contrario: vuole le statistiche per capire chi visita il
sito e per poter ottimizzare le campagne. Ripristinato quello che era stato tolto l'11/08 (commit
`82a41ee`), con un `git revert` e i conflitti risolti tenendo **entrambi** i lati, perché nel frattempo
`header_pub.php` e `sito.css` erano cresciuti col lavoro GEO del 04/09.

Ripristinati: lo snippet GA4 `G-42EHH23ZFT` con Consent Mode v2 (`analytics_storage: denied` di
default), `assets/js/cookie-consent.js`, il blocco CSS del banner in `sito.css`, e le voci sui cookie
analitici in `privacy.php`.

Il consenso resta limitato all'analitica: il banner parla solo di Analytics e aggiorna solo
`analytics_storage`. Niente `ad_storage` né remarketing: quello è un passo successivo che richiede
anche un banner diverso e un'altra riga in privacy, quindi si decide quando serve davvero.

Verificato in produzione nei due stati. Prima del consenso: banner presente, GA manda solo il ping
senza cookie previsto dal Consent Mode (`gcs=G1-0`, `npa=1`) e non scrive nessun `_ga`. Dopo
l'accettazione: banner via, cookie `bz_cookie_consent=accepted` e `_ga_42EHH23ZFT` scritti, segnale a
`gcs=G1-1`, sessione pre consenso agganciata alla nuova via `ga_temp_client_id`, e dati visibili in
tempo reale su Analytics (`user_engagement`, `form_start`).

Le chiamate a `region1.google-analytics.com/g/collect` rispondono **503 ma i dati arrivano lo stesso**,
verificato sul rapporto in tempo reale. Non è un guasto da inseguire. La proprietà sta nell'account
Google `duebytes@gmail.com` (account Analytics `duebytes` 56997040, proprietà 549417907), non nel
personale.

Nota emersa durante la verifica: nel browser restano cookie `_ga`, `_ga_425DPXN7EB`, `_ga_B2HF27M9YG` e
`_fbp` che **non li scrive questo sito** (nessuna richiesta a Facebook o ad altre proprietà GA parte
dalle pagine pubbliche). Sono residui di vecchie visite, con scadenza lunga.

---

## 04/09/2026 — Admin: la tendina delle select usciva dallo schermo

Su finestre basse (viewport ~570 px) la tendina di una select nell'admin — riprodotto sul modal
"Modifica immagine" di `gallery_immagini` — era alta 735 px e partiva a 479 px dall'alto: le ultime voci
finivano sotto il bordo della finestra e non erano selezionabili. La pagina non si può scrollare mentre
select2 è aperto, perché la tendina è agganciata al `body` e lo scroll la chiude.

La causa era una regola nostra in `assets/css/style.css` che toglieva del tutto il limite di altezza
(`max-height: none !important` su `.select2-results__options`, presente dal commit iniziale). Senza
limite select2 misura una tendina più alta del viewport e la apre comunque verso il basso.

**Fix:** `max-height: min(400px, 45vh)` con `overflow-y: auto`. Col limite select2 misura l'altezza vera
e, se sotto non ci sta, ribalta il menu sopra il campo. Verificato sulla pagina live: da 735 px
(y 479→1214, fuori schermo) a 307 px ribaltata sopra (y 134→441), tutte le voci raggiungibili.

Stessa regola sbagliata, stesso fix, in `ordini_clienti_26/assets/css/style.css` — nel gestionale c'era
già una toppa locale in `riba.php` che faceva esattamente questo per una sola tendina.

---

## 04/09/2026 — GEO: rendere il sito leggibile dalle IA

Benza ha chiesto a un assistente dove trovare un telo per laghetto e si è visto elencare catene di
bricolage, senza comparire. Il motivo non era misterioso: il sito non diceva quasi nulla di sé a una
macchina.

**Quello che mancava.** La sitemap elencava 38 pagine informative e nessuna del catalogo, quindi 58
prodotti e 346 modelli non erano dichiarati da nessuna parte (Google li aveva trovati da solo seguendo i
link, ma erano arrivati per caso). I dati strutturati esistevano soltanto nella pagina FAQ: nessun blocco
diceva chi è l'azienda, e nessuna scheda diceva che quello è un articolo in vendita con un prezzo. Le
schede prodotto servivano tutte lo stesso `<title>` "Dettaglio Prodotto", e le categorie "Categoria",
perché il titolo vero lo scriveva il JavaScript, che i crawler delle IA in genere non eseguono.

**Sitemap dal database** (`sitemap.php`): 385 indirizzi al posto di 38, con le 9 categorie attive, le 338
schede dei modelli visibili e le 12 gallerie. Resta raggiungibile come `/sitemap.xml`, che è l'indirizzo
registrato in Search Console, passando dagli **alias del router**: la riscrittura nel `.htaccess` non
aggancia su Aruba, dove mod_rewrite riceve il percorso completo del filesystem. Il vecchio file statico è
stato rinominato `sitemap_statico_vecchio.bak` sul server.

**Dati strutturati.** Blocco `Organization`/`Store` in `header_pub.php`, quindi su tutte le pagine, con
indirizzo, telefoni, anno di fondazione, aree servite, competenze e profili ufficiali. Blocco `Product`
sulle schede, generato lato server con codice, prezzo, foto e disponibilità. La disponibilità considera
anche i sotto-teli: un master con scorta zero si vende lo stesso se ha misure disponibili, e dichiararlo
esaurito sarebbe stato un errore verso i motori e verso i clienti.

**Titoli e descrizioni veri** lato server su schede e categorie.

**`llms.txt`** con la descrizione dell'azienda, il catalogo per aree e le pagine utili, più qualche nota
per chi risponde alle domande degli utenti. **`robots.txt`** ora nomina i crawler dei modelli (GPTBot,
ClaudeBot, PerplexityBot, Google-Extended e gli altri) per accoglierli in modo esplicito.

**Otto domande d'acquisto nelle FAQ.** Le 51 esistenti erano tutte tecniche; mancava proprio la domanda
dello screenshot. Aggiunte dove si compra, quanto costa, EPDM o PVC, spedizioni, calcolo dei metri,
privati o aziende, su misura, posa in opera. I prezzi sono verificati sul listino (PVC da circa 4 euro al
metro quadro, EPDM da circa 8) e dati come ordine di grandezza, con rimando alla scheda per il prezzo
aggiornato, così il testo non invecchia male. Stanno sia nel blocco dati sia nel testo visibile, perché
un markup che dichiara contenuti assenti dalla pagina viene penalizzato.

**Cosa questo lavoro non fa.** Rende il sito comprensibile e candidabile, non garantisce la citazione: i
modelli pescano molto da dove un'azienda viene nominata da altri, quindi contano anche recensioni,
cataloghi di settore e articoli. È la condizione necessaria, non quella sufficiente.

Non è lavoro extra: SEO e GEO erano parte di quanto concordato per il rifacimento, e il GEO era rimasto
indietro.

## 13/08/2026 (sera) — Foto propria per ogni modello

Finora la foto era solo del prodotto e tutti i modelli sotto mostravano quella: va bene per le varianti
dello stesso articolo, meno quando sotto un prodotto stanno cose diverse fra loro, come pompe o skimmer di
marche differenti. Migration 023: colonna `foto_modello`, vuota sui 1.669 modelli esistenti, con ricaduta
sulla foto del prodotto dove manca, così finché Benza non carica niente non cambia nulla.

Endpoint `api/admin/modelli/upload.php` sul modello di quello dei prodotti, con in più il
ridimensionamento: una versione da 1000 px per la scheda e una miniatura da 200 px per la tabella, dove le
righe possono essere tredici. Le funzioni di ridimensionamento sono quelle già usate dalle gallery,
raccolte in `api/config/img_helper.php` con guardia `function_exists` per non toccare l'endpoint delle
immagini che già funziona. Nel pannello: campo con anteprima e pulsante per togliere la foto; il
caricamento avviene dopo il salvataggio, così funziona anche sui modelli nuovi usando l'id che torna dalla
create.

Sul fronte pubblico la miniatura sta in ogni riga della tabella e in ogni card su telefono, dove la card è
diventata una griglia con la foto in alto a sinistra. Su telefono la descrizione del prodotto è passata a
tutta larghezza sotto foto e titolo: prima restava strizzata nella colonna accanto, con righe da poche
parole. Per farlo il contenitore del testo sparisce come scatola (`display: contents`) e i suoi figli
entrano nella griglia, senza toccare il markup; le utility `d-flex` sono state sostituite da classi
proprie, perché con `!important` non si potevano scavalcare nella media query.

**Due trappole pagate sulle dimensioni delle miniature.** Il formato 4:3 chiesto da Benza non si vedeva:
lo stile inline chiedeva 64x48 ma la cella era larga 80 px con 32 di padding, e la regola generale
`img { max-width: 100% }` stringeva l'immagine ai 48 px di spazio interno, rendendola di nuovo quadrata.
Serve `max-width: none` sull'immagine, altrimenti la misura dipende dal padding della cella. Poi la
miniatura rientrava rispetto alla foto del prodotto sopra, perché la cella aveva il padding standard:
tolto a sinistra e ridotto a destra, ora entrambe partono a 66 px dal bordo. Verificato misurando nel
browser, non a occhio.

## 13/08/2026 — Allegati dei modelli salvati nella cartella sbagliata + campo video

Benza segnalava che l'admin non salvava i PDF dei modelli. In realtà li salvava, ma in
`api/uploads/pdf/`, mentre la scheda prodotto costruisce il link come `uploads/pdf/` + nome file, cioè
nella root del sito. Record aggiornato, messaggio di conferma, e link a 404: da fuori sembrava che non
salvasse niente. Il percorso era sbagliato di un livello in `update.php` e `create.php`. Riprodotto sul
NAS prima di toccare la produzione: 404 prima, 200 dopo.

In produzione sono stati recuperati **6 PDF** che Benza aveva caricato lo stesso giorno fra le 16:32 e le
16:40 e che erano rimasti nella cartella sbagliata: spostati in `uploads/pdf/`, i link tornano a
funzionare senza dover ricaricare niente.

**Campo `youtube` sui modelli** (migration 022). Nel vecchio sito `pdf1` e `pdf2` erano caselle di testo
e Benza ci aveva incollato anche link a video: 12 modelli li avevano ancora, in tre formati diversi
(`youtube.com/watch`, `youtu.be`, `it.youtube.com`), e uno di questi aveva il video in `pdf2` con un PDF
vero in `pdf1`, motivo per cui la migration sposta i due campi separatamente. Ora c'è un campo dedicato
nel modal e la scheda prodotto mostra il video sopra i documenti. L'ID si estrae con la stessa logica già
usata dalla sezione Video, quindi qualsiasi formato di link va bene, e il valore viene normalizzato in
`https://www.youtube.com/watch?v=ID` al salvataggio. Il campo viene scritto da una query separata,
eseguita solo se la colonna esiste, così l'admin continua a funzionare dove la migration non è ancora
girata.

**Etichette dei PDF invertite.** Il modal diceva "PDF 1 (scheda tecnica)" e "PDF 2 (manuale)", il sito
mostra `pdf1` come "Istruzioni" e `pdf2` come "Scheda tecnica". Hanno ragione i dati e quindi il sito: in
`pdf1` ci sono 269 file col nome istruzioni e zero datasheet, in `pdf2` il contrario. Corrette le
etichette dell'admin.

---

## 12/08/2026 — Pagina categoria: da 227 query a 4

Segnalata una pagina di categoria che ci metteva 39 secondi. La chiamata a
`api/pubblico/prodotti/read.php` interrogava il database una volta per prodotto e una volta per ogni
modello per sapere se avesse sotto-teli disponibili: sulla categoria 3 erano 16 + 209 query, più le due
iniziali. Ogni controllo dei sotto-teli faceva per giunta una scansione completa di `modelli`, perché
`telo_master` non era indicizzata.

Sul NAS non si notava, perché la tabella è piccola e sta in memoria. In produzione il conto torna con
quello che si è visto: 39243 ms diviso 225 query fa circa 174 ms l'una, cioè il database di Aruba
momentaneamente lento moltiplicato per il numero di andate e ritorni. Con 4 query lo stesso rallentamento
costa 0,7 secondi.

Due metodi nuovi in `Modello.php`: `readByProdotti()` prende i modelli di più prodotti in una query sola,
`mappaSottoTeliDisponibili()` calcola in un colpo quali master hanno sotto-teli disponibili. L'output è
stato confrontato byte per byte fra vecchio e nuovo su sei categorie, sullo stesso database. In
produzione la categoria 3 è passata da 0,84 a 0,15 secondi.

Migration 021: indice su `modelli.telo_master`. Guadagno piccolo oggi (1,5 ms a scansione su 1.669
righe), serve ai punti che la interrogano riga per riga, come la subquery nella lista admin dei modelli.
Verificato dopo l'esecuzione: la query passa da `type: ALL` con 1654 righe a `type: ref` con 1.

Verificato anche che le altre tabelle sopra le 50 righe hanno già indicizzate le chiavi esterne. Nessuna
chiave esterna è però dichiarata a livello di database: non è un problema di velocità, ma di integrità
(oggi nulla impedisce di cancellare una categoria lasciando i prodotti orfani). Da valutare a parte.

---

## 11/08/2026 (sera) — Rimossi Google Analytics e il banner cookie

Benza ha cambiato idea in giornata: niente statistiche. Tolto lo snippet GA4 da `header_pub.php`,
eliminati `assets/js/cookie-consent.js` e il blocco CSS del banner, cancellato il file anche da Aruba.
Il sito non carica più nulla da Google a fini statistici, verificato sulla pagina vera (`window.gtag` e
`window.dataLayer` non definiti, nessuno script `googletagmanager`).

`privacy.php` riallineata: niente cookie analitici, resta il solo reCAPTCHA sui moduli. Tolte anche le
finalità di "analisi statistiche anonime" e il legittimo interesse per "analisi", che ora non
corrispondono a nessun trattamento reale. Il cookie `bz_cookie_consent` già scritto nei browser dei
visitatori scade da solo, non serve intervenire.

Resta la proprietà GA4 `G-42EHH23ZFT` nell'account Analytics "duebytes": inerte senza il tag, si può
cancellare a mano se non serve più. Il blocco GTM fantasma tolto stamattina **resta tolto**, era una cosa
diversa (vedi sotto).

## 11/08/2026 — Google Analytics 4 con Consent Mode v2 + rimozione GTM fantasma

Controllo Search Console su richiesta: `/admin/`, `/api/`, `/app/`, `/caricamenti/` correttamente esclusi
da indicizzazione (robots.txt + verifica diretta "Controllo URL" → non indicizzata). 338 pagine non
indicizzate segnalate, quasi tutte indirizzi del vecchio sito ormai 404 (126) o duplicati senza canonico
sul sito nuovo (129): non urgente, da valutare con calma.

Nel verificare l'installazione di Google Analytics è saltato fuori un blocco Google Tag Manager
(`GTM-TJ7WHQF`) attivo in produzione su ogni pagina pubblica, inserito **per errore mio** il 06/07/2026
dentro un commit di tutt'altro scopo (audit admin) — non veniva dal vecchio sito, non era in nessun
account Google del cliente o dell'agenzia: un ID placeholder dimenticato lì durante lo sviluppo e mai
tolto prima del go-live. Rimosso da `header_pub.php`.

Al suo posto, **Google Analytics 4 vero**, proprietà creata nell'account "duebytes" (misurazione
`G-42EHH23ZFT`), con **Consent Mode v2**: `header_pub.php` imposta `analytics_storage: denied` di default
prima ancora di caricare `gtag.js`, poi `assets/js/cookie-consent.js` mostra un banner (cookie tecnici
sempre attivi, Analytics solo dopo consenso esplicito) e aggiorna il consenso con `gtag('consent',
'update', ...)` solo al click su "Accetta". La scelta si salva nel cookie `bz_cookie_consent` (180
giorni, `SameSite=Lax`) — se assente il banner ricompare. Verificato che senza consenso non parte nessuna
richiesta verso Google, e che con consenso la richiesta di misurazione porta `gcs=G1-1`. Il sito **non
aveva alcun banner cookie prima**, nonostante `privacy.php` menzionasse già Google Analytics tra i cookie
usati (testo scritto genericamente in fase di rifacimento, mai stato vero fino ad ora) — aggiornato per
essere accurato. GA4 carica solo sulle pagine pubbliche (`header_pub.php`), mai nell'admin.

## 10/08/2026 — Ricerca interna al sito (richiesta Benza) — in produzione dall'11/08/2026

Barra di ricerca in navbar con tendina dei suggerimenti e pagina risultati in stile Benza, al posto del
Google CSE come motore principale. Tenuta qualche giorno sul NAS per il collaudo, poi deployata su Aruba
l'11/08/2026 insieme a Google Analytics. **Nel deploy dimenticato `footer_pub.php`** (carica
`assets/js/ricerca.js`, ma il markup del pannello sta in `header_pub.php`): la lente si apriva ma restava
vuota, nessun errore in console perché lo script semplicemente non veniva caricato. Trovato verificando
la pagina vera dopo il caricamento, non dandola per buona solo perché "andava sul NAS". Indice rigenerato
puntando a `https://www.benza.it` invece del NAS prima del commit finale.

Due sorgenti unite da `api/pubblico/ricerca/read.php`:
- **indice statico** `assets/ricerca_indice.json` (325 voci, 184 KB) generato da
  `tools/genera_indice_ricerca.php`: 28 pagine informative, 285 documenti PDF, 12 gallerie, 44 video.
  Lo script **scarica le pagine via HTTP** invece di leggere i sorgenti PHP, così indicizza quello che
  vede il visitatore; per Documenti, Gallery e Video, che caricano i contenuti via JavaScript, legge
  direttamente le API, altrimenti resterebbero pagine vuote.
- **catalogo dal vivo** dal database (categorie, prodotti, modelli): niente rigenerazione dopo le
  modifiche da admin.

Scelte di ranking, dopo aver guardato i risultati veri: punteggio maggiore al titolo, poi descrizione,
poi testo; **fuori se manca anche una sola parola** cercata; bonus per tipo (modello 8, pagina 6,
prodotto 6, categoria 4, gallery e documento 2) perché la pagina "Istruzioni per il montaggio" deve
battere il PDF omonimo. Sui teli si mostrano **solo i master** (`telo_master = 0`): le varianti sono le
misure dello stesso telo e riempivano i risultati di righe identiche (51 risultati per "telo epdm",
scesi a 16).

Il **Google CSE resta in fondo alla pagina** come seconda opzione: cerca dentro il testo dei PDF, che il
nostro indice non legge (indicizziamo i titoli dei documenti, non il contenuto).

Sicurezza: valori sempre messi in sicurezza prima di finire nel markup, e href limitati a indirizzi
relativi o http(s), perché gli URL dei PDF arrivano dalla pagina Documenti che è editabile da admin.
Verifiche in `tools/test_ricerca.php` (11 controlli sulle regole di punteggio, slug ed estratto).

**Da ricordare al deploy:** caricare anche `assets/ricerca_indice.json` e rigenerarlo con
`php tools/genera_indice_ricerca.php https://www.benza.it` dopo modifiche alle pagine o ai documenti.

**Descrizioni dei prodotti pubblicate e ancore nei risultati (10/08).** Cercando "montaggio" usciva
"BORDO DEL LAGHETTO", ma nella pagina di destinazione quella parola non compariva da nessuna parte. Due
cause: la ricerca guardava dentro `prodotti.desc_prodotto`, che **il sito non mostrava da nessuna parte**
(era visibile solo in admin), e l'estratto mostrava i primi 165 caratteri, fermandosi prima della parola
trovata (che stava al carattere 233).

Su decisione di Paolo le descrizioni ora si vedono, sotto il nome del prodotto nella pagina categoria
(46 prodotti su 58 ne hanno una; `white-space: pre-line` perché diverse sono elenchi con a capo e
pallini, e foto e titolo passano ad `align-items-start` quando c'è testo). L'estratto nei risultati è
ritagliato attorno alla parola cercata, come già si faceva per le pagine.

In più, sempre su indicazione di Paolo, i risultati di tipo prodotto puntano ora a
**`categoria{N}-slug#prodotto{id}`**: la pagina si apre già sul prodotto giusto, che viene evidenziato
per un paio di secondi. Lo scorrimento è **istantaneo e non animato**: il sito ha `scroll-behavior:
smooth` sull'html, che su distanze di migliaia di pixel non arriva a destinazione (verificato: si fermava
a 50px su un obiettivo di 9629). Le ancore vanno gestite in JavaScript perché i blocchi prodotto non
esistono ancora quando il browser leggerebbe l'ancora da solo.

**Revisione del 10/08 dopo il primo giro di prove (richieste di Paolo).** Il campo sempre aperto in
navbar rubava spazio: mandava i menu su due righe e a certe larghezze faceva sparire il logo. Ora c'è
solo una **lente**, e al clic il campo entra da destra coprendo il menu, con il logo che resta al suo
posto. Su mobile la lente sta **fuori dal menu a scomparsa**, a sinistra dell'hamburger. "Shop Online"
in navbar è diventato "Shop" (unico punto accorciato: nel footer e nella pagina resta per esteso).

Tre trappole incontrate, tutte risolte:
- **sfondo del pannello**: qualsiasi colore scelto a mano stonava con quello dell'intestazione. Il
  pannello ora non ha sfondo proprio e si dissolve il menu sotto (`.bz-navbar.ricerca-aperta`), così il
  colore è per forza quello vero della navbar.
- **`clip-path` ritaglia i figli**: con l'animazione sul pannello, l'elenco dei suggerimenti veniva
  tagliato via e non si vedeva. Il ritaglio è passato al solo campo e la tendina è uscita dal form.
- **posizione fissa del pannello**: con `left` in pixel il campo finiva sopra il logo su certe larghezze.
  Ora la misura si prende dal logo vero all'apertura (e al ridimensionamento della finestra).

---

---

## 10/08/2026 — Hero della homepage: spazio verticale

Su finestre basse il badge "Dal 1932" toccava la navbar e il pulsante Shop Online usciva dalla foto,
tagliato da `overflow: hidden`. L'hero aveva `min-height: 85vh` e nessun padding proprio, quindi il
contenuto sforava invece di far crescere il blocco. Aggiunto spazio sopra e sotto, con il padding
inferiore che tiene conto dell'onda decorativa (120px, 80 su mobile). Commit `4dc9d18`.

---

## 06/08/2026 — Copie interne a Benza: Reply-To al cliente

Le notifiche che Benza riceve (nuovo ordine, ordine da offerta, pagamento PayPal e Nexi) avevano solo il
reply-to di default `preventivi@`: premendo Rispondi si scriveva a se stessi. Ora il reply-to è l'indirizzo
del cliente su tutte e quattro. Il From resta `preventivi@benza.it` perché Aruba rifiuta un mittente
diverso dall'utente SMTP. Stesso fix già applicato ai moduli il 28/07. Commit `136c18b`.

---

## 05/08/2026 — Email di conferma ordine: mancava la lunghezza dei teli

Segnalato da Benza sull'ordine #1660: l'articolo compariva come "Telo Carlisle OASEfol EPDM 1 mm Liscio
- Larghezza 15,25 mt", senza la lunghezza scelta. Il template gestiva già `lato1`/`lato2` ma `create.php`
non glieli passava, e il formato previsto (`15,25x15 m`) avrebbe ripetuto la larghezza già nel nome. Ora
stampa ` - Lunghezza N m` come il gestionale. Corretto anche `offerta.php`, che aveva lo stesso buco.
Vale dai nuovi ordini. Commit `c1117dd`.

---

## 03/08/2026 — Ordini pagati dall'e-commerce: ora passano da soli in "Da spedire"

Segnalato sull'ordine **#12921**: pagato, ma rimasto fuori dalla tab "Da spedire" del gestionale, che
Davide ha dovuto spuntare a mano. Il gestionale quel flag lo mette da solo quando il pagamento viene
registrato **lì** (`api/ordini/stati.php`, categoria 6), mentre `registraPagamento()` di
`api/config/gestionale_helper.php` inseriva lo stato e aggiornava `pagato`/`da_saldare` senza toccare
`da_spedire`: gli ordini pagati dal sito restavano quindi indietro.

Aggiunta la stessa regola dopo l'aggiornamento degli importi: `da_spedire = 1` se l'ordine risulta ormai
saldato (tolleranza di 2 euro) e non esiste già una spedizione inviata (categoria 7 con `inviato = 1`).
Una sola `UPDATE` condizionata invece di leggere e poi decidere. Verificata con un `prepare` reale sul
DB e provata in lettura sui dati veri: marca gli ordini saldati e non spediti, salta i non saldati e
salta quelli già spediti.

Controllato che non ci fossero altri ordini rimasti indietro nella stessa condizione: nessuno, #12921
era l'unico ed era già stato sistemato a mano.

---

---

## 31/07/2026 — Pagina di logout: lang=it

Mancava l'attributo della lingua sulla pagina di logout, unica del sito senza. Commit `fbbd09c`.

---

## 30/07/2026 — PHP 8.5 su Aruba: rimosse le curl_close deprecate

Aruba è passata a **PHP 8.5.6** (il NAS di sviluppo è fermo a 8.4.14), quindi il codice deve girare su
entrambe. Verifica fatta eseguendo davvero le funzioni sui due server: nessun errore bloccante, ma su 8.5
`curl_close()` emette `E_DEPRECATED` ("has no effect since PHP 8.0"). Rimosse le 3 chiamate in
`includes/api.php`: si cancellano e basta, perché dal PHP 8.0 l'handle cURL è un oggetto liberato dal
garbage collector, quindi il file resta valido anche su 8.4.

Contano perché in produzione `display_errors` è a 0 e restano silenziose, ma in locale è a 1: quando il
NAS passerà a 8.5, un avviso del genere finirebbe dentro le risposte JSON delle API. Commit `5e21ab1`.

---

---

## 30/07/2026 — Etichetta sugli stati scritti nel gestionale

Nel gestionale ogni tipo di stato ora propone la sua etichetta quando lo si crea a mano (Benza la
riscriveva ogni volta uguale). Gli stati che **il sito** crea nel gestionale sono stati allineati alle
stesse diciture, così nel riepilogo dell'ordine le voci sono uniformi a prescindere da chi ha creato lo
stato. In `api/config/gestionale_helper.php`:
- `creaOrdine()` → categoria 5: etichetta **`Ricevuto Ordine`**.
- `registraPagamento()` → categoria 6: etichetta **`Pagamento: € <importo>`**, con
  `number_format($importo, 2, ',', '.')`. Formato confrontato voce per voce con quello del gestionale
  (`1.500,50`, `250,00`, `12.000,00`).
- Lo stato categoria 10 (contatto da form) resta **senza** etichetta, come il Generico nel gestionale.

Le due `INSERT` hanno una colonna in più: `bind_param` ricontato (`isids` e `isidis`) e query validate
con un `prepare` reale sul DB, che ha confermato sintassi e numero di segnaposto. `or_stati.etichetta` è
`varchar(100)` sia sul NAS sia in produzione, quindi nessuna migration. Deploy su Aruba con reset
opcache (qui il ponte va chiamato **senza** `.php`, il sito riscrive gli URL). Commit `8098812`.

---

---

## 30/07/2026 — Listino prezzi: link diretto al PDF

Il bottone "Listino Prezzi" nell'hero di `/vasche` puntava a `/documenti`, pagina da cui i listini sono
stati rimossi il 24/07 (commit `e43ba4b`). Ora punta a **`listino.pdf`** (già in root su Aruba, 200 KB,
indicato da Benza) con `target="_blank" rel="noopener"`. Aggiornati anche gli altri 4 rimandi testuali al
listino che finivano sulla stessa pagina: `vasche.php` (elenco "Ulteriori informazioni"),
`vasche_accessori.php`, `faq.php` (domanda 31 sui prezzi IVA esclusa), `condizioni_vendita.php`.
Nessun rimando "listino → documenti" resta nel sito. Commit `0b20a21`.
Su conferma dell'utente allineato anche il CTA "Scarica il listino" della **homepage**, che usava un PDF
diverso (`uploads/pdf/pdf/listino_vasche.pdf`, 739 KB) → ora `listino.pdf` (commit `39b9032`). Unico
listino linkato in tutto il sito: **`/listino.pdf`**.

---

---

## 28/07/2026 — Form contatti/preventivo: Reply-To al cliente + allegati nel gestionale

Due segnalazioni di Benza sul modulo contatti.

**1. Reply-To.** La mail che arriva a Benza aveva come mittente `preventivi@benza.it`, quindi "Rispondi"
riscriveva a se stessi. Il From non può diventare l'indirizzo del cliente (l'envelope MAIL FROM deve
restare l'utente SMTP autenticato o Aruba risponde `550 Sender not allowed`, e un From falsificato
fallirebbe SPF/DMARC). Soluzione: `Mailer::send()`/`sendTemplate()` accettano un **reply-to per messaggio**
(6° parametro, stringa o `['email'=>,'name'=>]`); `reset()` fa `clearReplyTos()` e ripristina il default,
altrimenti il reply-to di un invio resterebbe agganciato ai successivi. Negli endpoint il nome mittente
diventa "Mario Rossi (via sito Benza)" e il Reply-To l'email del cliente. In `preventivo/send.php` il From
va ripristinato prima della conferma al cliente (due invii con la stessa istanza). Commit `9a8bfa2`.

**2. Allegati non salvati nel gestionale.** Il contatto arrivava in `ordini_clienti_26` (or_ordini +
or_stati cat.10) ma senza allegati. Tre bug distinti in `api/config/gestionale_helper.php`:
- `aggiungiAllegato()` faceva INSERT sulle colonne `allegato` e `nome_file`, **inesistenti**: lo schema
  reale di `or_allegati` è `(cod_ordine, cod_stato, cod_preventivo PK, file, data_ora)`. L'INSERT falliva
  sempre in silenzio.
- Il file non veniva **copiato** in `<root gestionale>/uploads/allegati/`, dove il gestionale lo cerca
  (link diretto `uploads/allegati/<file>`, nessun download.php). Nel campo `file` va solo il NOME.
- `cod_stato` era passato a `0`: il gestionale carica gli allegati con `WHERE cod_stato = ?`, quindi con 0
  il file non sarebbe comunque mai comparso nella scheda ordine.
Inoltre `contatti/send.php` non chiamava affatto `aggiungiAllegato()` (l'allegato viveva solo nella mail).
Ora `creaContatto()` memorizza il `cod_stato` (`getUltimoCodStato()`), il nome file segue la convenzione del
gestionale `{prefix}-{base40}-{rand8}.{ext}` con prefix `ord-{ordine}-stato-{stato}-1` e denylist estensioni
eseguibili, e la cartella si risolve per ambiente (Aruba `app/ordini_clienti/uploads/allegati/`, NAS
`../ordini_clienti_26/uploads/allegati/`). Testato in produzione: file `ord-12895-stato-70546-1-*.pdf`
correttamente presente sul server.

---

---

## 24/07/2026 — Documenti: rimosso gruppo "Listini prezzi"

Su richiesta di Benza, in `/documenti` è stato **rimosso l'intero gruppo "Listini prezzi"** (titolo `<h2>`
+ i due link "Listino Vasche in acciaio zincato" e "Prezzi Teli impermeabili PVC ed EPDM"), che sarebbe
altrimenti rimasto vuoto. Modifica nel fallback `includes/documenti_default.html` (la pagina è CMS su
`web_contenuti` chiave `documenti`, ma `contenuto_html` è vuoto → serve il file di default). Deploy FTP,
verificato live: endpoint `contenuti/read.php?chiave=documenti` non contiene più "Listini prezzi".

---

---

## 11/07/2026 — Gruppo B: libreria documenti completa + ripristino contenuti (audit vecchio↔nuovo)

Su richiesta del cliente ("quel che c'era prima, ci va anche adesso"), migrazione fedele integrale.
- **documenti.php**: da ~29 a **TUTTI i 322 documenti** del vecchio (doc_genap/2/3, doc_benza, doc_johnguest),
  classificati via workflow parallelo (agenti) in 9 sezioni: Listini, Cataloghi, **Certificazioni**(44),
  **Schede tecniche**(63), **Disegni e istruzioni**(60), **Normative**(cards + 17 PDF), Azienda,
  **Rassegna stampa**(79), **Volantini e materiali**(59). Teli EPDM (Firestone/Carlisle/Hidrostan/OASEFOL…)
  tutti inclusi. Escluso solo doc_oase/doc_pdm (Benza srl puro). Path %20-encoded, file verificati esistenti.
- **index.php**: aggiunta 3ª card categoria in homepage "Riparazioni Vasche in Cemento" (riga prodotti a 3).
- **Audit link pagina-per-pagina** (workflow, 17 pagine) → contenuti Benza verbatim ripristinati:
  - vasche_progettazione: intero "Capitolato serbatoio antincendio" (4 blocchi fornitura/esclusioni materiali+documenti); norma UNI-EN 12845:2015→2020.
  - vasche_riparazioni: blocco "10 domande per preventivo"; marche EPDM Carlisle+Hidrostan; garanzia "fino a 30 anni".
  - vasche_istruzioni: box "Suggerimento utile" (etichette anello/lamiera).
  - vasche_coperture: voce+video+PDF "JT Cover"; link diretto istruzioni copertura in tensione.
  - laghi: uniformato ai **6 video del vecchio** (rimossi 3 extra, rinumerati 1-6).
  - laghi_piscine: intro biolaghi + articoli (anche OASE/Benza srl, come da richiesta).
  - info_staff: link brevetto PDM + ISO 9001 piscine.
  - liquami: creati depliant_liquami.pdf / listino_liquami.pdf dai .bak; link volantino/listino ripristinati.
  - NON ripristinati per scelta cliente: timeline 2023/24 in info_storia, 2ª sede Via Feraldi in info_dovesiamo.
- Tutte le nuove righe di card usano `align-items-start` (no bug height:100%). Verificato a 1024px: nessuna card stirata.
- Deploy Aruba via FTP in chiaro (19 file + 2 PDF), dimensioni verificate. Tutto live su www.benza.it.

---

---

## 11/07/2026 — Fix da feedback Benza post-golive (Gruppo A) + deploy Aruba

Root-cause con workflow parallelo (12 bug) + agenti; fix testati su dev, deployati su Aruba.
- **Navbar mobile**: `.navbar-collapse` espansa era trasparente (navbar `height` fisso) → sfondo
  bianco pieno nel `@media (max-width:991.98px)`. Ultima riga `.bz-nav-actions` (Shop sx, carrello+utente
  dx via `margin-left:auto`). Sottolineatura voce attiva allineata a sx su mobile (`::after left:1rem`).
- **info_staff.php**: foto staff ora cliccabili → lightbox Bootstrap (usa `immagine_hq`).
- **preventivo.php**: rimossa pill "Consigliato" (badge, righe ~551-556).
- **documenti.php**: cataloghi "qualità"→"risoluzione"; "Genaflex acqua potabile"→"Genaflex PVC 0,5 mm
  — scheda tecnica"; `$normative` ora con 3° elemento (path PDF) → card cliccabili (loop aggiornato).
- **vasche_istruzioni.php**: link istruzioni/manuale/figure/spessori deviati su "documenti" → PDF diretti.
- **index.php**: orari 8-12/14-18 → 9:00-12:30 / 15:00-18:30.
- **header_pub.php**: FAQ nel dropdown Vasche (`faq#sec-serbatoi`/`#sec-laghi`); menu "Contatti"
  `info_contatti`→`contatti` (form); restructure Shop/carrello/utente in un unico `<li class="bz-nav-actions">`.
- **info_storia.php**: link "Contattaci" → `contatti`.
- **video.php**: rimosso il rendering delle descrizioni (erano appunti interni, citavano concorrenza).
- **vasche.php**: "Acquista Online" → "Richiedi preventivo".
- **contatti.php + api/pubblico/contatti/send.php**: form era rotto (reCAPTCHA v3 key morta) → sostituito
  con honeypot (`website`) + timing (`elapsed_ms>=3000`) come la registrazione; row `align-items-start`
  (card recapiti non più stirate da `height:100%`); rimossa card "Deposito Via Armea" (non era nel vecchio).
- **listino.pdf**: ripristinato in root (categorie.link_cat id=10 puntava a benza.it/listino.pdf, mancante).

**Deploy Aruba (lezione):** FTPS troncava a 0 byte i file >~16 KB (sito.css → sito senza CSS). Risolto con
FTP in chiaro (`set ftp:ssl-allow no`), staging su disco reale (no SMB), verifica `cls -l` dimensioni.
webroot = `www.benza.it/` (fare `cd` dopo login).

**Aperto (Gruppo B, da concordare con Benza):** libreria documenti (~29 vs ~185 del vecchio), schede
tecniche reali (~70), audit link vecchio↔nuovo, riparazioni cemento, sedi pagina orari.

## 08/09/2026 — Footer: eBay, e favicon verde per la landing del gruppo

**Link eBay sbagliato.** Il footer e i dati strutturati puntavano a
`ebay.it/str/benzawaterstorage`, che non esiste. Il negozio vero è
`ebay.it/str/benzairrigazione`. Corretto in `footer_pub.php`, nel `sameAs` di
`header_pub.php` e nel `sameAs` dei dati strutturati della landing
gruppobenza.it, che se lo portava dietro copiato da qui.

**Icona.** Il carrello (`fas fa-shopping-cart`) non diceva eBay a nessuno:
sostituito con `fab fa-ebay`, il logotipo del marchio. Font Awesome 6.5.1 lo
ha fra le brands, e in homepage era già in uso nel blocco recensioni. Misurato
sulla pagina vera: 20px di larghezza dentro il cerchio da 40px, in linea con
gli altri tre. Nessuna regola CSS aggiuntiva serve.

**Favicon di gruppobenza.it.** Presa la B bianca su fondo della favicon di
benza.it e ricolorata sul verde `#006000` del logo Benza srl: il segno dice
"Benza", il colore distingue il gruppo dalla società dell'acqua. Generate ico
(16/32/48), png 16 e 32, apple-touch 180; via il segnaposto di Astro e messi i
`link rel="icon"` nel layout, che non c'erano. `theme-color` allineato al fondo
della pagina (`#04222c`).

## 09/09/2026 — Prestazioni, e i tre siti che si nominano a vicenda

**Il sito dichiara di appartenere al gruppo.** Riga nel footer verso gruppobenza.it e
`parentOrganization` nel JSON-LD verso `https://www.gruppobenza.it/#gruppo`, che è l'`@id` con cui la
landing si identifica. Dall'altra parte benza.it era già dichiarato `subOrganization`: ora il rimando
esiste in tutte e due le direzioni. Da benzasrl.it manca, e quel sito non è nostro.

**Consenso allineato alla landing.** Nel comando `default` del Consent Mode i tipi non elencati
valgono `granted`: dichiarando solo `analytics_storage` restavano concessi `ad_storage`,
`ad_user_data` e `ad_personalization`, che il banner non ha mai chiesto. Ora sono negati tutti e
quattro, e `gtag.js` si scarica solo dopo l'accettazione invece che a ogni caricamento.

**Prestazioni: da 57 a 85** su profilo mobile, immagine principale da 18,1 a 3,8 secondi, peso della
home da 5,7 a 1,9 MB. In ordine di guadagno:

- I due loghi erano PNG da **10981 pixel** di lato (605 e 746 KB) su ogni pagina, mostrati alti 48.
  Ridotti a 600 px: 20 e 29 KB. Gli originali in `docs/originali-non-web/`.
- Le sette foto della home ricompresse alle stesse misure, PSNR di ognuna sopra i 35 dB: da 3,8 a 1,7 MB.
- Font Awesome ritagliato alle **173 icone** davvero usate, censite su tutti i sorgenti e su tutte e
  384 le pagine della sitemap: i tre woff2 da 324 a 24 KB. Elenco in `docs/icone-in-uso.txt`, font
  interi in `docs/originali-non-web/fontawesome-interi/`.
- Caratteri, Bootstrap, Font Awesome e jQuery spostati sul nostro dominio. I caratteri erano un
  `@import` dentro `sito.css`, cioè una catena di quattro richieste in fila prima di disegnare una
  lettera. Tenuti i soli alfabeti latino e latino esteso: 132 KB invece di quasi 400.
- Aruba non comprimeva i JavaScript, perché escono come `text/javascript`: aggiunto il blocco
  `AddOutputFilterByType DEFLATE` nel `.htaccess`. Centoventi KB su ogni pagina.
- Preload dell'hero, che è un background CSS e veniva scoperto solo dopo il foglio di stile.

**Corretto un difetto già in produzione:** `fa-hexagon-nodes` su `vasche_coperture` è un'icona a
pagamento, sul pacchetto gratuito non disegnava nulla. Sostituita con `fa-circle-nodes`.

**Valutato e scartato:** compilare Bootstrap su misura. Sul filo pesa 31 KB, non i 227 del file
grezzo, e una versione ritagliata ne farebbe risparmiare una ventina creando una dipendenza da
ricompilare a ogni aggiornamento.

**Regola del gitignore corretta:** `vendor/` prendeva anche `assets/vendor/`, ora è `/vendor/`.

## 15/09/2026 — Liquami: via il rimando ai prezzi dei teli

Richiesta di Benza. Nel box "Documenti e prezzi" di `liquami.php` c'era la riga "Per conoscere i prezzi
dei teli di fondo e di copertura, entra alla pagina Teli per lo stoccaggio dei liquami zootecnici", con
un link al vecchio `acquisti.benza.it`. Tolta la riga, il box si chiama ora solo "Documenti". Indice di
ricerca rigenerato da produzione (325 voci) e caricato insieme alla pagina, dimensioni verificate.

## 17/09/2026 — Due video in più: Birdie sulle coperture, Steel Mesh Silo sui liquami

Richiesta di Benza. In `vasche_coperture.php` la griglia dei filmati passa da tre a quattro card con
"Birdie: copertura a elementi galleggianti esagonali con palline" (`xpDhyYTa2Ew`, il numero 13 della
pagina Video). In `liquami.php` nuova sezione a sfondo chiaro prima della CTA, video a sinistra e a
destra la descrizione completa del filmato numero 35 (`mhafBpMymIU`, Steel Mesh Silo, i sette passaggi
del montaggio). Indice di ricerca rigenerato da produzione e caricato; `vasche_coperture.php` arrivava
a 700, rimesso a 644.

## 17/09/2026 — Liquami: manuale Genap Slurry Lagoon nel box Documenti

Richiesta di Benza. Nel box "Documenti" resta il volantino (vecchio ma innocuo), secondo punto nuovo:
"Manuale di istruzioni, condizioni di montaggio e disegni con sezioni, in inglese" che apre
`uploads/pdf/pdf/genap_slurry_lagoon_manual.pdf` (24 pagine, 1,5 MB, versione luglio 2018, caricato a
mano su Aruba perché `uploads/` non è su git). Tolto il rimando alla pagina certificazioni: lì sono
rimaste solo quelle per l'acqua. Indice di ricerca rigenerato e caricato.

## 21/09/2026 — Search Console: canonico dal server e redirect che non partivano

Da Search Console: 279 pagine "duplicata senza URL canonico", 214 "non trovata (404)".

**Canonico.** Il `<link rel="canonical">` lo scriveva il JavaScript, e per Google le schede con slug
diversi (nome cambiato, maiuscole nel codice, `?m=ID`) erano copie senza padrone. Ora lo mette
`header_pub.php` per ogni pagina (`$url_canonico`, altrimenti il nome della pagina, la home è `/`).
Modello, categoria e gallery lo calcolano con `ric_slug`, lo stesso della sitemap, tramite
`bz_canonico_scheda()` in `helpers.php`, che oltre a dichiararlo **rimanda con 301** all'indirizzo
giusto quando quello richiesto è diverso; il router conserva `$pathRichiesto` apposta, perché `$path`
lo riscrive in `modello`/`categoria`. In `modello.php` i tag nel nome diventano spazi e non spariscono,
altrimenti `Telo<br>EPDM` dava uno slug diverso da quello della sitemap. `gallery_view.php` legge il
nome dal database anche per il titolo. Il canonico via JavaScript è stato tolto.

**404.** I 32 `RewriteRule ^vecchio$ nuovo [R=301]` del `.htaccess` su Aruba non agganciano nulla
(percorso completo in mod_rewrite, trappola già nota): i vecchi indirizzi del sito erano tutti 404.
Spostati in `router.php` come `$redirect301`, insieme agli alias (`consegne`, `la-nostra-storia`,
`chi-siamo`, `dove-siamo`), che prima rispondevano 200 e facevano un doppione. In più: i PDF che stavano
in `/allegati/` e `/pdf/` rimandano a `/uploads/pdf/...` se il file c'è (157 + 19 dei 404), e le vecchie
`gallery-<nome>` vanno all'elenco delle gallery. Resta `sitemap.xml` come unico alias senza redirect.

Trappola trovata strada facendo: `(new Database())->getConnection()->prepare(...)` chiude la
connessione subito ("MySQL server has gone away"), perché l'oggetto muore a fine espressione. L'oggetto
va tenuto in una variabile.

Verificato in produzione: tutti i 388 indirizzi della sitemap rispondono 200 senza redirect, i vecchi
indirizzi e i PDF rimandano dove devono, `/app/` non è toccata. In Search Console va premuto
"Convalida correzione" sulle due voci grandi; i numeri scendono nelle settimane successive.

Aggiunta della stessa mattina: le schede di modelli, categorie e gallery **spariti o nascosti**
rispondevano 200 con "Prodotto non trovato" e canonico `/modello` (i due "errore di reindirizzamento"
in Search Console). Ora, se il database risponde e la riga non c'è, la pagina dice 404. Le altre voci
piccole sono giuste così: `/api/` bloccata da robots, `shop.php`/`index.php` che perdono il `.php`.
Convalida della correzione avviata in Search Console il 21/09 su "duplicata senza canonico" e "404".

## 23/09/2026 — La nostra storia: ripristinati il 2023 e il negozio di via Feraldi

Nella trasposizione di `info_storia.php` si erano persi gli ultimi due blocchi del vecchio sito: il 2023
(certificazione ISO 9001 della Benza SRL per le piscine, livello Rivenditore Gold OASE della BWS) e il
2024 con la foto e l'inaugurazione del negozio di via Feraldi 30 (28/05/2024), col PDF dell'articolo.
Ora sono il capitolo 9, foto a sinistra su sfondo chiaro; la CTA finale passa a sfondo bianco per
tenere l'alternanza. Confronto frase per frase col vecchio testo: non manca altro. Il link al PDF dei
90 anni punta direttamente a `uploads/pdf/pdf/` invece di passare dal redirect.

## 24/09/2026 — Search Console: il seguito delle convalide

Nuovo avviso "Google ha scelto una canonica diversa" su `model1489`: Google aveva scelto la vecchia
versione con lo spazio nel codice (`model1489-Skimmer%20standard-...`), che dal 21/09 fa già 301.
Chiesta l'indicizzazione della versione giusta.

Le convalide del 21/09 risultavano "non riuscite" il 22. Controllati tutti gli indirizzi uno per uno:

- **404 (193):** 187 fanno ora un solo 301 verso una pagina che risponde 200, `video` risponde 200.
  Restavano 5 404 veri, sistemati così: otto PDF di `uploads/pdf/` non erano **mai stati caricati
  su Aruba** (INSCENIO, FILTRO_BIOSMART, PONDOVAC4/5, AQUAOXY, LUNAQUA, Vitronic, Aquaset_Fountain),
  ora ci sono e hanno le stesse dimensioni; `laghi/x.pdf` (link relativi di vecchie pagine) cerca il
  file per nome in `uploads/pdf/pdf/`; `registrazione` e `registrazione-utente` vanno a `login`.
  Resta `m²`, indirizzo spazzatura: il 404 è la risposta giusta.
- **Gallery vecchie:** `gallery-<nome>` non va più all'elenco ma alla gallery giusta, perché lo slug
  storico è ancora in `web_gallery.slug`; il router la serve come `gallery_view` e questa fa 301 a
  `gallery{ID}-slug` (un salto solo). Se lo slug non c'è o la gallery è nascosta, elenco.
- **Duplicati (292):** 290 in attesa di riscansione, falliscono solo due PDF scansionati senza testo
  (newsletter 2015-05, ISO 9001 piscine), che Google raggruppa fra loro. Lasciati così.
- **Link interni vecchi:** in `faq.php` e `laghi.php` c'erano link a `gallery-<nome>`, `foto-gallery`
  e `teli-laghi-artificiali#...`: ora puntano agli indirizzi nuovi. In `laghi.php` aggiunte le ancore
  `tabVideo` e `tabTradux` del vecchio sito, che le FAQ usano.

## 24/09/2026 — Staff: grassetti e link nelle didascalie, e "Con noi da"

In `info_staff.php` le didascalie della gallery 17 perdevano tutto l'HTML (il codice toglieva i tag),
e "Con noi da Luglio 2026" di Lucito non compariva perché si cercava solo "Con noi dal".
Ora il ruolo passa da `sanitizeBenzaHtml(riga, {link: true})`: la funzione di `common.js` accetta
un secondo parametro che lascia passare anche gli `<a>`, solo se il browser li interpreta come
http o https (`new URL`, non una regex: `java&#9;script:` con una tabulazione in mezzo passava un
controllo testuale). Gli altri attributi spariscono, i link esterni aprono una nuova scheda con
`rel="noopener noreferrer"`. Senza il parametro la funzione si comporta come prima (nomi modello
in carrello, checkout, preventivo). Provato con dieci input malevoli, nessuno sopravvive.
I link nelle didascalie puntano ancora a indirizzi vecchi (`vasche-prefabbricate-acciaio`,
`teli-laghi-artificiali`, `la-nostra-storia`): funzionano col 301 del router, si possono
correggere dall'admin della gallery.

## 30/09/2026 — Migrazione su riserve-idriche.benza.it

Il sito di Benza Water Storage si è spostato su `riserve-idriche.benza.it`; su `www.benza.it` c'è la pagina
del Gruppo Benza (build di `gruppobenza_26`) che rimanda tutto il resto con 301. Tutto il dettaglio, il
perché delle scelte e cosa resta aperto è in `docs/MIGRAZIONE.md`. Nel codice: `includes/dominio.php`
(dominio in un punto solo), `GestionaleSync::inviaAlPonte()` + `deploy_benza_it/ponte/` per gli allegati,
link interni assoluti resi relativi, slug JS dei modelli senza trattino finale, modulo preventivo senza
reCAPTCHA (la chiave non esisteva più: il modulo era fermo anche su benza.it).
