Richieste centralizzate
Raccolta, presa in carico, assegnazione e storico in un'unica vista condivisa dal team.
Gestisci. Organizza. Fai vivere eventi.
Smart event ticketing
Richieste, posti, accrediti e biglietti smettono di rincorrersi. Eventix dà al team una base dati condivisa e al richiedente uno stato sempre leggibile, dal primo nome all'ultimo pass consegnato.
Una sola regia
Eventix non aggiunge un altro foglio da aggiornare: mette nella stessa vista eventi, disponibilità, richieste e persone da accreditare.
Funzioni
Ogni funzione nasce da un bisogno operativo concreto: sapere chi ha chiesto cosa, quanti posti restano e a che punto è la consegna.
Raccolta, presa in carico, assegnazione e storico in un'unica vista condivisa dal team.
Posti totali, richiesti e liberi per evento e per zona, con semafori immediati sulla capienza.
Emissione, stato di consegna, codici e nominativi sempre allineati alla richiesta.
Admin, editor e utenti vedono solo ciò che serve, con permessi espliciti per le attività sensibili.
Privacy, termini, cookie policy e download tracciati: i documenti vivono nello stesso portale.
Interfaccia responsive, ricerca rapida e azioni essenziali raggiungibili anche da smartphone, senza perdere il contesto.
Come funziona
Il flusso resta esplicito: ogni passaggio lascia una traccia e chi richiede sa sempre a che punto è la sua richiesta.
Il richiedente indica evento, partecipanti e contatti in un modulo guidato.
Il team prende in carico, verifica la disponibilità e aggiorna lo stato senza duplicazioni.
Biglietti e pass restano collegati ai nominativi, con tracciamento della consegna.
Fatto per lavorare
Eventix nasce per chi organizza eventi e gestisce accrediti ogni giorno: interfaccia essenziale, dati accessibili e passaggi espliciti.
Area riservata
Accedi con le credenziali del tuo account o scrivici per richiedere l'abilitazione.

# Changelog
Tutte le modifiche rilevanti a questo progetto saranno documentate in questo file.
## [Unreleased]
## [0.23.0] - 2026-11-08
### Added
* **anteprima email al referente aggiornata in tempo reale**: nell'anteprima di *Email info ritiro* / *Email biglietti* (*Gestisci richiesta*) il riquadro «Come lo vedrà il referente» si aggiorna mentre si modificano testo, punto di ritiro, finestra, scaletta, mittente e destinatari in Cc, senza ricaricare la pagina. Il nuovo endpoint `POST /requests/<id>/mail/preview` (permesso `requests.manage`, nessun invio e nessuna scrittura sul database) rigenera l'HTML dell'iframe riutilizzando la stessa composizione dell'invio reale, così l'anteprima non può divergere dal messaggio spedito; il client (`static/js/mail-preview.js`) usa debounce a 350 ms, come già per l'anteprima di *Invia lista*. **Fix scaletta**: la scaletta compilata nel form viene ora inserita nel corpo dell'anteprima e dell'invio (se vuota non è citata); se si modifica manualmente la textarea, il testo scritto prevale. Test: `tests/check_mail_templates.py` (`check_mail_preview_live`).
## [0.22.0] - 2026-11-06
### Added
* **avvio sempre nel venv del progetto**: `python app.py` ora parte **sempre** con l'interprete del venv (`venv/Scripts/python.exe`): se lo script viene lanciato con un Python di sistema, il nuovo modulo `venv_bootstrap.py` (solo libreria standard) rilancia il processo con l'interprete giusto. Il bootstrap è richiamato in cima a `app.py` dentro `if __name__ == "__main__":`, **prima** degli import pesanti (Flask/psycopg2/PIL); in produzione (`gunicorn app:app`) non si attiva. Il rilancio si disattiva con `EVENTIX_SKIP_VENV=1` e un marker anti-ciclo (`EVENTIX_VENV_ACTIVE=1`) impedisce i loop. Test: `tests/check_venv_bootstrap.py`.
* **tipologia e note nella lista per l'organizzatore**: in *Gestisci richiesta* (admin/editor, **non** visibile al richiedente) si assegna la **zona/tipologia di ogni biglietto** (colonna «Biglietto», scegliendo fra le zone della scheda evento) e si aggiunge una **nota** a livello richiesta. Nella lista inviata all'organizzatore (e nei download **Excel/PDF**) compaiono le colonne **TIPOLOGIA** e **NOTE**, e la richiesta viene **scomposta in una riga per zona**: se i biglietti sono di tipi diversi la richiesta genera più righe (es. 3+3), ciascuna con la propria tipologia. Nuova colonna `requests.management_notes` (`initialize_database()`; le richieste restano per ambiente e non vengono promosse in produzione). Test: `tests/check_event_export.py`, `tests/check_request_edit_manage.py`, `tests/check_event_import.py`.
* **tipologie di biglietto per data (eventi a date multiple)**: le **zone/tipologie possono essere diverse per ogni data**, come già il luogo: nuova colonna `event_dates.ticket_zones` (`initialize_database()`, testo `nome: posti`; promossa con l'evento da `promotion._sync_event_dates()`). Nel form evento ogni riga-data ha il proprio repeater di **zone/tipologie** (vuoto = la data eredita le zone dell'evento). In *Gestisci richiesta* le tipologie disponibili e i «posti liberi per zona» sono quelli **della data della richiesta**; la scheda evento mostra le zone del giorno selezionato. Sorgente unica `_event_date_zones()` (data → evento → «Posto unico»). Test: `tests/check_event_import.py`, `tests/check_promotion.py`, `tests/check_request_edit_manage.py`, `tests/check_management_ui.py`.
* **eliminazione di un evento a date multiple (data singola o tutte)**: eliminando un evento a **date multiple** dal calendario l'app ora **chiede** se rimuovere **solo la data** della riga selezionata oppure **tutte le date** (l'intero evento). La *singola data* si elimina solo se quel giorno non ha richieste collegate; rimuovendo l'**ultima data** viene eliminato anche l'evento. Con *tutte le date* restano le regole esistenti (bloccata se l'evento ha richieste). Nuova pagina di conferma `event_delete_scope.html`. Test: `tests/check_request_edit_manage.py`.
## [0.21.0] - 2026-11-04
### Added
* **release automatica al deploy (versione e changelog)**: `/admin/deploy/run` ora esegue anche la release vera e propria — ripiega le voci `[Unreleased]` di `CHANGELOG.md` in una nuova sezione `## [<versione>] - <data>` e allinea `VERSION` e `.release-please-manifest.json` con un unico commit atomico su `develop` (Git Data API), poi crea la Release GitHub col **numero nuovo**. Niente più bump manuali: la versione avanza e il changelog si allinea da soli, e il badge «Da pubblicare» non resta bloccato su un `+N` identico tra Staging e Produzione. Rimossa la workflow `release-please.yml` (di fatto inerte). Test: `tests/check_promotion.py`.
* **footer generale delle email (tenant RTL)**: sotto la firma e prima di «Generato con Eventix» in tutte le email compare ora un **footer generale dell'organizzazione**, configurabile in *Impostazioni* → *Template email* (`mail_footer`, HTML semplice). Default: i loghi **RTL 102.5 / Radio Zeta / Radio Freccia** e i dati societari **RTL 102,500 HIT RADIO S.r.l. — Via Piemonte 61/63 — 20093 Cologno Monzese (MI), Italy**. I percorsi `/static/...` diventano **URL assoluti** all'invio, così i loghi si vedono anche nei client di posta. Sotto il testo e **sopra** il footer generale compare sempre la **firma del mittente** (nome e, se presenti, telefono ed email), aggiunta automaticamente anche alle email *info ritiro* / *biglietti*. È legato all'organizzazione (tenant, oggi `rtl`). Test: `tests/check_event_export.py`, `tests/check_mail_templates.py`.
* **email al referente: mittente alternativo «Invia da» e destinatari in Cc**: le email **info ritiro** e **biglietti** al referente hanno ora il menu **Invia da** — l'utente attivo più gli **indirizzi alternativi** configurati in *Impostazioni* (`outbound_sender_emails`, come *Invia lista*) — e il campo a pastiglie **Destinatari in copia (Cc)**, con l'elenco utenti e la spunta **CC** di *Gestione utenti* come default. L'identità scelta diventa il **mittente** (`From`) e i segnaposto `{mittente}`/`{mittente_email}`/`{mittente_telefono}`; in **modalità debug** il Cc è azzerato e gli indirizzi scelti compaiono nell'avviso di prova. Test: `tests/check_mail_templates.py`.
* **email al referente (info ritiro e biglietti)**: in *Gestisci richiesta* i pulsanti **Email info ritiro** e **Email biglietti** aprono l'anteprima dell'email al **referente** (chi riceve mail/info), con oggetto e testo generati da due **template** configurabili in *Impostazioni* → *Template email* (`pickup_email_template`, `tickets_email_template`) e modificabili prima dell'invio. I tre campi liberi **punto di ritiro**, **finestra di ritiro** e **scaletta della serata** sono a livello **evento** (nuove colonne `events.pickup_point`/`pickup_window`/`event_schedule`, `initialize_database()` e promosse in produzione): precompilati dall'evento, si possono compilare anche nell'anteprima e all'invio vengono **risalvati sull'evento**, così le richieste successive (altri referenti/partecipanti) li ereditano. La **finestra di ritiro** vuota si calcola dall'inizio dell'evento (apertura `pickup_window_default_hours` ore prima, default **2**; durata `pickup_window_minutes`, default **60**) e la **scaletta** è facoltativa (blocco `{?scaletta_orari}…{/scaletta_orari}`). Segnaposto gestiti dal nuovo modulo `mail_templates.py` (sostituzione tollerante, data estesa in italiano). In **modalità debug** l'invio va a chi invia e al destinatario di debug, altrimenti al referente. Test: `tests/check_mail_templates.py`, `tests/check_event_import.py`, `tests/check_promotion.py`.
* **referente e ritira distinti nella richiesta**: la richiesta separa il **referente** (chi riceve mail/info e i biglietti) dal **ritira** (chi ritira fisicamente i biglietti, es. in cassa accrediti). Nel form di *Nuova richiesta* e in *Modifica richiesta*, sotto il blocco *Referente* c'è il blocco *Ritira* con tre scelte — **Come il referente** (default), **Il richiedente**, **Un partecipante** (di norma il primo) — e in *Gestisci richiesta* compare la riga *Ritira*. La colonna **RITIRA** dell'export (Excel/PDF/email all'organizzatore) mostra ora **chi ritira** e non più il referente; le richieste esistenti (senza il valore) continuano a mostrare il referente. Nuove colonne `requests.pickup_kind`/`pickup_name`/`pickup_participant_index` (`initialize_database()`, nessun backfill). Test: `tests/check_event_export.py`, `tests/check_optional_names.py`.
* **mittente alternativo nell'invio lista**: l'anteprima di *Invia lista* ha un campo **Invia da** con l'indirizzo dell'utente attivo (default) più gli **indirizzi alternativi** configurati dall'admin in *Impostazioni* → *Disponibilità e lista email* (`outbound_sender_emails`, una riga per indirizzo nel formato `Nome <email>`, es. `Biglietti RTL <biglietti@rtl.it>`). L'indirizzo scelto diventa il **mittente** (`From`, con `Reply-To` sullo stesso indirizzo) e compare nella **firma** (nome + email); un indirizzo non configurato viene ignorato e l'anteprima live segue la scelta. Test: `tests/check_event_export.py`.
* **avvisi riducibili: restano ridotti dopo la prima comparsa e durano giorni configurabili**: l'avviso delle **Regole generali** (utenti) non si riapre più **espanso** a ogni cambio pagina — resta **espanso** solo alla prima comparsa (~8 s) e poi **ridotto a badge** (espandibile) per **N giorni** configurabili dall'admin in *Regolamento* (`disclaimer_config.badge_days`, default 3, `0` = nessun avviso riducibile; `user_disclaimer_state.toast_shown_at` registra la prima comparsa). Allo stesso modo il badge **Novità di Eventix** (admin) resta visibile per **N giorni** (massimo **3**) scelti dal superadmin in *Impostazioni* (`app_settings.news_badge_days`, default 2), invece di sparire dopo due minuti. Test: `tests/check_disclaimer.py`.
* **periodo degli eventi nell'elenco richieste** (dashboard): nuovo menu **Eventi futuri** (default) / **Eventi passati** / **Tutti gli eventi** accanto ai filtri di stato. Di default la pagina mostra **solo le richieste con evento ancora da svolgere**, così gli eventi passati non riempiono più l'elenco; *Eventi passati* apre lo storico e *Tutti gli eventi* non filtra per data. Il periodo si combina con i filtri di stato e resta selezionato cambiando filtro. Le richieste sono inoltre ordinate per **data evento** (dalla più vicina) e non più per ordine di arrivo; nella scheda evento la lista usa la data effettiva della richiesta (per gli eventi a intervallo, il giorno scelto dal richiedente). Test: `tests/check_requests_views.py`.
* **disponibilità residua basata sui posti confermati**: nella scheda evento i numeri escludono le richieste **annullate** e la **disponibilità residua** conta solo i posti **confermati** (`Confermata`/`Archiviata`), non quelli ancora in attesa (`Ricevuta`/`In carico`); lo stesso criterio vale per il semaforo e per i badge di *Gestisci richiesta*. Le colonne mostrate restano «richiesti» (richieste effettive) e «disponibili» (totali − confermati). Test: `tests/check_request_edit_manage.py`.
* **semaforo giallo a posti rimanenti (configurabile)**: la soglia non è più percentuale (80%): il semaforo diventa **giallo** quando restano al massimo `limited_remaining_seats` posti (default **6**) e **rosso** a esaurimento. La soglia si imposta in *Impostazioni* (sezione *Disponibilità e lista email*) e vale per scheda evento, elenco eventi e form richieste. Test: `tests/check_request_edit_manage.py`, `tests/check_event_import.py`.
* **annullamento «test - no statistiche» = eliminazione reale**: scegliendo quel motivo la richiesta viene **eliminata del tutto** (cascade su partecipanti, diario e biglietti), non più conservata; gli altri motivi restano tracciati nello storico. Il **cestino** non è più attivo sulle richieste già **annullate** e nell'elenco dell'evento le annullate scendono in fondo. Test: `tests/check_request_edit_manage.py`.
* **nomi con iniziale maiuscola e «RITIRA» = Cognome Nome**: in liste, email ed export ogni nome/cognome è normalizzato con l'iniziale maiuscola a prescindere da come è scritto (`mARIO` → `Mario`, `d'amico` → `D'Amico`); la colonna **RITIRA** mostra il referente come **Cognome Nome** (i nominativi restano Nome Cognome) e l'elenco è ordinato alfabeticamente per referente. Il PDF non spezza più le righe tra due pagine e ripete l'intestazione. Test: `tests/check_event_export.py`.
* **frase standard sotto la tabella nell'email di invio lista**: nuova impostazione `list_email_note` (default «Vi liberiamo i biglietti inutilizzati»), mostrata sotto la tabella della lista; nell'anteprima di invio un flag la include/esclude (inclusa di default). Test: `tests/check_event_export.py`.
* **Impostazioni: sezione *Disponibilità e lista email***: nuova sezione in *Impostazioni* con la soglia del semaforo (`limited_remaining_seats`) e la frase sotto la tabella (`list_email_note`); le chiavi sono seminate da `initialize_database()` e restano operative (non promosse in produzione). Test: `tests/check_promotion.py`.
* **pulsante «io» nel primo partecipante** (*Nuova richiesta*): quando la combo **Radio / Cliente** è **«Interno …»** (`Interno Dip./Coll.`) sotto il **cerchietto** del numero `1` della prima scheda (il numero resta nel suo cerchio, invariato) compare un piccolo pulsante **io** che copia nel partecipante nome, cognome, telefono ed email dell'utente che sta compilando la richiesta (`current_user_contacts`, che ora espone anche `first_name`/`last_name`); il pulsante sparisce scegliendo le altre voci e i campi restano modificabili a mano.
* **Home: elenco richieste compatto e righe ben separate**: celle più dense (font `.9rem`, `line-height 1.25`), titolo/artista/data su poche righe, badge della richiesta tutti su una riga, recapiti del referente su una riga sola e separatore fra una richiesta e l'altra finalmente visibile (`var(--line)`); sotto i 576px resta la vista a schede. Test: `tests/check_requests_views.py`, `tests/check_management_ui.py`.
* **Excel: impaginazione su tutti gli export**: il writer condiviso `event_export.build_xlsx()` ora esce con **colonne alla larghezza giusta** (`excel_column_widths()`, minimo 12 e tetto 60 caratteri), **intestazione in grassetto su fondo grigio con riga bloccata e filtro automatico**, testo a capo e allineato in alto, riga del totale in grassetto e biglietti come **numeri**. Test: `tests/check_event_export.py` (`check_excel_layout`).
* **import: duplicati proposti come unione**: nella revisione dell'importazione una riga con possibile duplicato preseleziona «Aggiorna / unisci nel record #…» (la scelta «Mantenere entrambi» resta disponibile riga per riga) e la stessa regola vale per un invio senza il campo. Test: `tests/check_event_import.py`.
* **date multiple: semaforo e tipologie biglietto**: il semaforo guarda **solo** la capienza della data — riga per data nell'elenco eventi/dashboard (numeri e semaforo del giorno) e *Gestisci richiesta* (capienza e biglietti richiesti della data della richiesta, 0 = nessun limite → verde) — e le **tipologie biglietto possono restare a 0** senza alcun avviso di scostamento (form, elenco, scheda evento). Test: `tests/check_event_import.py`, `tests/check_request_edit_manage.py`.
* **correzione versione in esecuzione**: `_record_running_version()` salvava i parametri sbagliati (`tuple index out of range`) e la versione non arrivava mai in `app_settings`: ora registra `running_app_version`, il valore che la pagina Deploy legge dal DB di produzione come fallback. Test: `tests/check_promotion.py`.
* **correzione scheda evento a date multiple**: il selettore *Tutte le date* / data specifica non compariva perché `date_mode` era letto dalla colonna sbagliata (`list_deadline_days`): ora la scheda mostra il riquadro «Stai gestendo …», il titolo delle richieste con la data e le azioni che mantengono la selezione; se le date non sono configurate in questo ambiente la scheda lo dichiara con il rimando a *Modifica*. Il badge «In gestione · X/Y emessi» è passato accanto ai pulsanti (intestazione più compatta). Nel modal *Nuovo evento* il `<form>` avvolgeva intestazione, corpo e pulsanti: così `.modal-content` non era la colonna flex attesa da Bootstrap e **il corpo non scorreva** (pulsanti fuori dallo schermo). Ora il form sta **dentro** il corpo e i campi del footer usano `form="new-event-form"`; il contenuto è limitato all'altezza della finestra e il corpo scorre.
* **promozione delle date multiple**: `promotion._sync_event_dates()` copia in produzione le date configurate di un evento `multi` (delete + insert idempotente) insieme all'evento. Test: `tests/check_promotion.py`.
* **form evento riordinato e capienza per data**: il modal *Nuovo evento* (e la modifica) è diviso in sezioni — Evento, Dove, Organizzatore, Quando, Biglietti, Consegna e vendita — con azioni nel footer; il **luogo è facoltativo** (eventi senza sede fissa, es. proiezioni) e per le **date multiple** «Posti totali» è **calcolato** dalla somma dei posti delle date (le tipologie biglietto possono restare a 0, nessun avviso di scostamento). Test: `tests/check_request_edit_manage.py`, `tests/check_event_import.py`.
* **scheda evento a date multiple**: riquadro «Stai gestendo …» sempre visibile, titolo delle richieste con la data, e pulsante **Torna alla scheda evento** (con la data) da anteprima invio e pagina di controllo export.
* **export e invio lista per evento a date multiple**: la lista **totale** ha la **colonna DATA** (email, Excel e PDF); con una data selezionata nella scheda l'export Excel/PDF e l'invio lista riguardano **solo quella data** e il **luogo** usato è quello **della data** (`event_dates.location_id`, fallback luogo dell'evento). Nella riga delle date multiple del form evento (nuovo/modifica) le colonne **Data · Posti · Luogo** sono ora etichettate e spiegate. Test: `tests/check_event_export.py`, `tests/check_list_cc.py`.
* **evento a date multiple mostrato come più voci**: nella dashboard (*Prossimi eventi*) e nell'elenco eventi un evento `multi` compare come **una voce per data**, con il **luogo della data** e il badge *Data multipla*; il link apre la scheda già su quella data (`?date=`). Test: `tests/check_requests_views.py`, `tests/check_management_ui.py`.
* **scheda evento a date multiple (macro evento)**: per un evento `multi` la scheda ha il selettore **Tutte le date** / data specifica (con semaforo per data); con una data si vedono solo le richieste di quel giorno e numeri/capienza per data, con *Tutte le date* l'aggregato. All'apertura parte dalla **prima data utile** (prima non passata). Il **luogo** mostrato è quello della data selezionata (`event_dates.location_id`). Test: `tests/check_requests_views.py`, `tests/check_management_ui.py`.
* **richieste su evento a date multiple**: in *Nuova richiesta*, per un evento `multi` compare un **elenco delle date configurate** (obbligatorio) con il **semaforo per data**; la data scelta è salvata in `requests.event_date` con il suo orario. Il **periodo aperto** non chiede alcuna data. La richiesta **non viene bloccata** a data esaurita: resta l'avviso «posti esauriti» (come oggi). `_resolve_request_event_date(event_start, date_mode, allowed_dates, selected)` valida la scelta. Test: `tests/check_event_import.py`, `tests/check_request_edit_manage.py`.
* **tipologie di data nei form evento**: il modal *Nuovo evento* e *Modifica evento* hanno la scelta **Data singola** / **Periodo aperto (dal/al)** / **Date multiple**; il periodo aperto non prevede la scelta della data in richiesta, le date multiple hanno un blocco ripetibile **data + capienza + luogo** (il luogo può cambiare da data a data, `event_dates.location_id`; vuoto = luogo dell'evento). La duplicazione copia anche le date multiple. Retrocompatibile: senza `date_mode` il vecchio *Intervallo* è un periodo aperto. Test: `tests/check_event_import.py`, `tests/check_request_edit_manage.py`.
* **regole generali (disclaimer) con accettazione e ruolo User+**: nuovo sistema delle *Regole generali* — testo e versione in `disclaimer_config` (riga unica, seed con i 4 punti: priorità di attribuzione, tempistiche di conferma a 48h, destinatario obbligatorio, monitoraggio abusi) e accettazione per utente/versione in `user_disclaimer_state` con il contatore degli accessi (DDL idempotente). All'accesso gli **utenti** (`user`) vedono una **modale bloccante** con il testo e il pulsante *Accetto le Regole Generali* disabilitato per **10 s** ai **primi 5 accessi** (2 dopo un aggiornamento di versione pubblicato dall'admin); staff, superadmin e il nuovo ruolo **`poweruser` (User+)** sono esenti. Nuove rotte: `GET /rules` (pagina utente con testo, versione e data di presa visione), `POST /disclaimer/accept` (JSON) e `GET/POST /admin/disclaimer` (solo admin: testo + flag *Incrementa versione*). Voce di menu *Regole generali* nella barra utente, nel menu mobile e in *Amministrazione*. File: `static/js/disclaimer.js`, `templates/rules.html`, `templates/admin_disclaimer.html`. Test: `tests/check_disclaimer.py`.
* **modale «Novità» per gli admin**: al primo accesso dopo un deploy (chiave = versione con build) gli admin vedono una modale informativa con l'elenco compatto delle novità (`latest_changelog_entries()` dal changelog), **chiusura automatica con countdown** (10 s), chiusura manuale e pulsante *Vedi tutte le novità* che apre il changelog completo nella modale dei crediti. Nessun dato sensibile nel browser (solo la versione già vista in `localStorage`). File: `static/js/whats-new.js`, markup in `base.html`.
* **tipologie di data dell'evento (schema e migrazione)**: nuova colonna `events.date_mode` (`single`, `open`, `multi`) e nuova tabella `event_dates (event_id, event_date, max_tickets)` con indice `idx_event_dates_event_id` (DDL idempotente in `initialize_database()`). Gli eventi **storici a intervallo** (`end_date` valorizzata) e quelli **senza data** diventano `open` (migrazione derivata, nessun `DROP`): l'intervallo continuo non è più offerto dai form e i dati restano intatti. `REQUEST_EVENT_DATE_SQL` ora usa `date_mode` (comportamento invariato per i dati esistenti), nuovo helper `event_date_mode()` e `promotion.EVENT_COLS` include `date_mode`. Test: `tests/check_event_import.py`, `tests/check_promotion.py`.
* **filtro di ricerca in *Statistiche*** (solo admin/superadmin): l'elenco **Per utente** ha il campo di ricerca dinamica già usato nelle altre viste (nome, cognome, username, ruolo) con contatore e riga «nessun utente per questa ricerca»; è client-side (`setupLiveFilter`, nessuna query aggiuntiva). Prepara il terreno per i grafici e l'esportazione delle statistiche previsti in seguito. Test: `tests/check_management_ui.py`.
* **anteprima di invio lista aggiornata in tempo reale**: nell'anteprima di *Invia lista* il riquadro **«Come lo vedrà l'organizzatore»** si aggiorna mentre si modifica l'introduzione (e quando cambiano i destinatari in Cc), senza ricaricare la pagina. Un nuovo endpoint `POST /events/<id>/send-list/preview` (permesso `requests.manage`, nessun invio) riattacca alla parte bloccata (tabella, firma, footer) rigenerata dai dati dell'evento l'introduzione digitata e restituisce l'HTML; il client lo richiama con debounce (350 ms, `static/js/list-preview.js`) annullando la richiesta precedente. La composizione del corpo è ora in un'unica funzione (`_event_send_data`) condivisa da anteprima e invio, così l'anteprima non può divergere dal messaggio spedito. Test: `tests/check_event_export.py`.
* **login con email o username e registrazione con approvazione**: l'accesso accetta **Email o Username** (identificatore con `@` -> email, altrimenti username) e, dopo un accesso con lo username, un messaggio invita a usare l'e-mail. Nuova **auto-registrazione** `/register` (nome, cognome, email, password) protetta da **Turnstile**, riservata ai **domini autorizzati** (`organizations.allowed_email_domains`: `rtl.it`, `radiofreccia.it`, `radiozeta.it` -> tenant `rtl`): l'account nasce non approvato (`is_approved=FALSE`, `registration_type='self_registered'`) e un admin lo approva o lo elimina da **Gestione utenti -> in attesa** (`/users/pending`); l'email e' **univoca**. Schema idempotente: tabella `organizations`, colonne `users.is_approved`/`registration_type`/`organization_id`, indice unico su `LOWER(email)`. Test: `tests/check_auth_email.py`.
### Changed
* **versioni di sviluppo e decisione «da pubblicare?»**: `VERSION` resta l'ultima release (Release Please la aggiorna solo su `main`), ma ora ogni modifica non rilasciata è tracciata: `APP_VERSION_LABEL` aggiunge il numero di voci sotto `[Unreleased]` (`0.20.0+38`, da `unreleased_changelog_entries()`) e `GET /version` espone anche `dev`/`pending`. In `/admin/deploy` il riquadro *Versioni* mostra in chiaro **Da pubblicare: SÌ/NO** con la prossima versione proposta (patch per le correzioni, minor per le nuove funzioni): quando i numeri coincidono ma ci sono voci `[Unreleased]` lo stato non è più «Allineate» ma «Staging avanti: da pubblicare», così non si confondono due versioni che sembrano uguali ma non lo sono. `VERSION` e `.release-please-manifest.json` restano SemVer puliti (il suffisso vive solo a runtime). Test: `tests/check_promotion.py` (`check_pending_release_decision`).
* **etichetta del login «Email o Username»**: il campo prima chiamato *Username* accetta anche l'e-mail.
* **novità e clausole come avvisi riducibili (toast -> badge)**: la modale *Novità* degli admin non è più bloccante - compare come **avviso in basso a destra** che resta espanso ~8 s, poi si riduce a un **badge espandibile** (durata configurabile in giorni, vedi *avvisi riducibili* sopra). Le **Regole generali** per gli utenti restano una modale bloccante con countdown **solo al primo accesso** per la versione corrente; dagli accessi successivi la richiesta di accettazione diventa lo stesso avviso riducibile (con il pulsante *Accetto*). Il comportamento è condiviso in `static/js/ux-toast.js` (`window.setupUxToast`), classi `.ux-toast`/`.ux-toast-badge` in `app.css`; soglie disclaimer `DISCLAIMER_FIRST_THRESHOLD`/`DISCLAIMER_UPDATE_THRESHOLD` a **1**. Test: `tests/check_disclaimer.py`.
* **voce di menu «Regolamento» e barra allineata al contenuto**: nel menu la voce delle regole generali si chiama **Regolamento** (pagina e modale restano *Regole generali*). Le voci della barra sono **più compatte** (padding orizzontale fisso 8px) e la pillola ha un padding minimo, così il testo della prima voce parte dallo stesso x del titolo della pagina e il menu **non cambia più a 1200px** (prima cambiava il padding e il menu "saltava" ridimensionando la finestra). Nota: il menu **non** usa `overflow`/`nowrap` — ritaglierebbero i sottomenu (`position: absolute`) di *Eventi*/*Anagrafiche*/*Amministrazione* e con molte voci (admin) il menu sforerebbe la pillola. La pagina *Regole generali* è stata reimpaginata: testata con versione e stato, testo a larghezza controllata (`68ch`) e piede con la presa visione. Test: `tests/check_management_ui.py`, `tests/check_disclaimer.py`.
* **regole generali: testo semplificato (niente HTML)**: la pagina di gestione non richiede più HTML — si usano convenzioni leggere convertite in HTML **sicuro** da `rules_text_to_html()` (escape *prima* delle sostituzioni): `*grassetto*` → `<strong>`, le righe `1. 2. 3.` → **elenco numerato**, `>` → simbolo «maggiore», l'**Invio** → interruzione di riga. Il testo di default è stato riscritto con le convenzioni (una normalizzazione una-tantum sostituisce il vecchio default HTML, senza toccare i testi personalizzati) e la pagina admin mostra anche un'**anteprima** del risultato. Test: `tests/check_disclaimer.py`.
* **permessi per gruppo: differenze dei ruoli spiegate**: la pagina mostra le **etichette leggibili** (User, **User+**, Editor, Admin) e una **legenda** che spiega cosa distingue i gruppi — in particolare che **User+** ha gli **stessi permessi di User** ma è **esente** da vincoli, approvazioni e *Regole generali*. La matrice elenca solo i permessi (menu e azioni): le differenze che non sono permessi sono descritte nella legenda, così User+ non sembra uguale a User. Test: `tests/check_permissions.py`, `tests/check_disclaimer.py`.
* **statistiche: utenti demo esclusi in produzione**: gli account `demouser` e `demoadv` compaiono nell'elenco *Per utente* **solo in locale/staging**; in produzione la query li esclude (`app_environment()`). Test: `tests/check_statistics.py`.
* **menu più corto e senza spezzature (admin)**: la voce *Amministrazione* diventa **Admin** e *Nuova richiesta* diventa **New** per lo staff; le etichette del menu non vanno più a capo (`white-space: nowrap`), così il menu non si spezza su due righe. Test: `tests/check_management_ui.py`.
* **oggetto della mail di invio lista**: l'oggetto predefinito è ora `Lista [radio/canale] per [nome evento] - [data] ore [ora]`, con il canale **dentro l'oggetto** (per esempio `Lista RTL 102.5 per PAUSINI - 04/11/2026 ore 21:00`), al posto del solo `Lista biglietti [evento] - [data ora]`. Senza radio/canale resta `Lista per [evento] - …`; senza data l'oggetto si ferma al nome dell'evento. L'introduzione del corpo è `Ecco la lista di RTL 102.5 per PAUSINI presso …` (senza articolo davanti al nome evento) e, quando la radio/canale non è indicata, diventa `Ecco la lista per PAUSINI presso …` (non più `Ecco la lista di - per …`). Test: `tests/check_event_export.py`.
* **intestazione della scheda evento più compatta**: la **radio/canale** non occupa più una riga propria ma sta nella riga dei metadati subito dopo **Piattaforma** (es. «Piattaforma: TicketOne, TicketMaster · Radio / canale: RTL 102.5»), e i **recapiti del luogo** (nome, telefono, email) sono una pillola accanto a **Sito del luogo** nella riga dei link (restano visibili solo agli admin). Test: `tests/check_management_ui.py`.
* **username fuori dalle viste**: richiedente, referente e nominativi sono mostrati come **solo nome e cognome** in *elenco richieste*, *scheda evento*, *Biglietti*, *Gestisci richiesta* e nell'etichetta dell'area utente: la coda `(username)` salvata in archivio (es. `Demo Basic (demouser)`) viene tolta (nuovo filtro Jinja `no_username`). Lo username resta visibile in *Gestione utenti* e nella scheda di modifica utente.
* **menù e moduli allineati (refactoring UI)**: la barra di navigazione riserva sempre lo spazio dei controlli compatti (`visibility: hidden`) e le voci hanno dimensioni fisse, quindi agganciandosi allo scroll non si spostano più e restano allineate con il contenuto; nell'elenco richieste la barra dei filtri è una griglia a due colonne con controlli della stessa altezza e nell'intestazione della scheda evento i metadati sono su griglia con colonna icona fissa (il testo parte sempre dallo stesso punto). I form di gestione (*Modifica evento*, *Nuova richiesta*, *Impostazioni*) sono divisi in **sezioni** con titolo, spaziatura uniforme e barra di salvataggio finale: *Modifica evento* in cinque sezioni (Evento, Luogo e organizzatore, Programmazione, Biglietti e zone, Consegna/vendita/canali), *Impostazioni* in sei (messaggi, liste, zone, mappature, notifiche, template email). Le pastiglie di *Radio / canale* e del campo Cc usano i colori dell'app (verde bosco, bordo tenue, focus coerente) invece di quelli predefiniti di Bootstrap. Test: `tests/check_management_ui.py`.
* **liste più ordinate**: intestazioni di tabella uniformi, colonne a larghezza stabile, **stato a pillola** colorata, referente con nome in evidenza e recapiti uno per riga nelle tabelle delle richieste; riepilogo allineato a destra nell'elenco eventi.
* **radio/canale a pastiglie (tag) nella scheda evento**: il campo «Radio / canale» non è più un testo libero ma un **campo a pastiglie**: si scelgono i canali di sempre dai suggerimenti (la lista unisce i canali configurati in *Impostazioni* → **Radio / canali proposti**, le etichette dei codici radio e i canali già usati negli eventi) e se ne possono aggiungere di nuovi scrivendoli, così l'elenco cresce con l'uso. Le sigle restano convertite con la mappatura dei codici (`RTL` → `RTL 102.5`) e i duplicati vengono tolti. Nuova impostazione `radio_channels_list`, promossa in produzione come le altre liste. Test: `tests/check_radio_channels.py`.
### Fixed
* **referente esterno → richiedente non veniva salvato**: in *Modifica richiesta*, cambiando il referente da **Referente esterno** a **Il richiedente** il nome restava quello esterno. Il campo `recipient_name` esiste solo nel blocco «Referente esterno» del form (nascosto ma comunque inviato): ora, con referente = richiedente, il nome si ricostruisce dal **richiedente selezionato** o da `requester_name`, come nel form di nuova richiesta. Test: `tests/check_request_edit_manage.py`.
* **elenco eventi ordinato per la data reale e ricerca evento completa nella nuova richiesta**: nell'elenco eventi un evento a **date multiple** compare come una voce per data, ma le date restavano raggruppate sotto la prima (es. 4, 8, 15 e poi il 6): ora ogni riga è ordinata per la **data reale** e le date multiple si intercalano agli altri eventi (4, 6, 8, 15), con i conclusi sempre in fondo (nuovo `_sort_event_rows_by_date()`, usato anche dalla dashboard). In *Nuova richiesta* il pulsante a discesa dell'evento mostrava solo i primi 8 eventi (`maxResults`): ora la freccia («Mostra tutte le opzioni») le mostra tutte, mentre la ricerca per testo resta limitata. Test: `tests/check_event_import.py`, `tests/check_management_ui.py`.
* **modali *Regole generali* e *Novità* che non si aprivano**: i due script (`disclaimer.js`, `whats-new.js`) erano caricati **prima** del bundle Bootstrap, quindi `window.bootstrap` non era ancora definito e la modale non veniva mai aperta. Ora sono caricati con **`defer`** (come `names-reminder.js`), così girano dopo Bootstrap. Test: `tests/check_disclaimer.py`.
* **voci del menu di navigazione allineate e aggancio allo scroll stabile**: i link semplici del menu erano `display:block` (testo in alto) mentre i trigger dei gruppi (Eventi, Anagrafiche, Amministrazione) sono `display:flex` (testo centrato): con la stessa `min-height` le etichette non stavano sulla stessa riga orizzontale. Ora anche i link centrano il contenuto, quindi icone e testi combaciano. L'aggancio della barra sticky usa inoltre un'**isteresi** (si aggancia a `top <= 1` e si rilascia solo oltre 16 px di scroll) così il passaggio non sfarfalla sul confine, che sembrava un salto. Test: `tests/check_management_ui.py`.
* **Cloudflare Turnstile che respingeva tutti gli invii da `/contatti`**: il widget era passato al **rendering esplicito** (`api.js?render=explicit` + `turnstile.render()`) mantenendo la classe `cf-turnstile` sulla casella: le due modalità non vanno mescolate, il widget non veniva creato e il modulo restava senza token, quindi ogni invio era respinto. Ora il modulo usa il **rendering implicito**, identico all'altro progetto che condivide la stessa sitekey (`api.js` senza parametri, `refresh-expired`/`refresh-timeout` in `auto` per default: il token scaduto viene rigenerato da Turnstile), con la sola guardia lato browser che blocca l'invio senza token. Il controllo dell'hostname non dipende più solo da `TURNSTILE_HOSTNAME`: il token è accettato se arriva da quell'hostname **oppure** dall'host che sta servendo la pagina (`X-Forwarded-Host`/`Host`, varianti `www.` incluse), quindi una variabile rimasta da un altro ambiente non blocca più il modulo mentre un token risolto su un altro sito resta rifiutato.
## [0.20.0] - 2026-10-30
### Added
* **destinatari in copia (Cc) della lista biglietti**: in *Gestione utenti* la nuova colonna **CC** ha un interruttore per ogni utente (colonna runtime `users.list_email_cc`, migrazione idempotente; modificabile **solo da admin/superadmin**, disattivata per chi non ha email) e il badge del superadmin nell'elenco è ora il **solo scudetto**, con i dettagli nel tooltip. Nell'anteprima di *Invia lista (demo)* il nuovo campo **Destinatari in copia (Cc)** è **sempre presente** — anche senza utenti spuntati, per aggiungere gli indirizzi a mano — e parte con gli utenti spuntati già inseriti (una pastiglia per ciascuno, si toglie con la X); permette di aggiungere altri destinatari con la **ricerca dinamica** fra gli utenti (`static/js/cc-recipients.js`: nome o email, pastiglie come in Gmail, `Invio`/virgola per confermare, Backspace per togliere l'ultima, indirizzo libero accettato). Il server valida e deduplica gli indirizzi (`_cc_recipients`) e passa il Cc a `_send_email`/`_smtp_send` (header `Cc`); nell'invio di prova il Cc **non** viene applicato — nessuna copia raggiunge utenti reali — e gli indirizzi scelti compaiono nell'avviso di debug: saranno messi in copia quando l'invio all'organizzatore diventerà reale. Test: `tests/check_list_cc.py`.
* **controllo dei dati mancanti prima di esportare o inviare la lista**: Excel, PDF ed email passano prima da una pagina di conferma quando la lista non è completa — nominativi senza nome/cognome, partecipanti indicati in numero inferiore ai biglietti richiesti, richieste senza referente, evento senza data o senza contatti organizzatore, lista vuota. Da lì si sceglie **Completa i dati** oppure **Scarica/Invia comunque**: in quest'ultimo caso il PDF riporta l'avviso nei metadati, l'email lo mette subito dopo il saluto e il file scaricato ha il suffisso `-incompleta`.
* **invio demo della lista biglietti** (`POST /events/<id>/send-list`, pulsante *Invia lista (demo)* nella scheda evento): compone l'email come l'export e la recapita **solo in prova** — a chi invia e al destinatario di debug — con l'etichetta `[DEBUG]`. L'invio reale all'organizzatore arriverà quando il testo sarà differenziato per tipologia di consegna dei biglietti.
* **permessi per gruppo** (`/admin/permissions`): matrice che assegna i permessi ai gruppi **user**, **editor** e **admin**. Permessi gestiti: *Gestione eventi*, *Anagrafiche*, *Importa eventi*, *Gestione utenti*, **Impersonificazione utenti**, *Impostazioni*, *Gestione richieste*, *Export lista*, *Statistiche complete*. La pagina è visibile a tutti gli **admin** ma **modificabile solo dal superadmin** (gli altri la vedono in sola lettura, con le caselle disattivate). Nuova tabella `role_permissions` (override per ruolo, con i default nel codice) e nuovo decoratore `permission_required()`; il superadmin ha sempre tutti i permessi. L'impersonificazione è un permesso: di serie **solo admin** (non editor né user). Il menu è ora guidato dai permessi: l'**editor** vede anche **Anagrafiche** (Luoghi, Organizzatori, Biglietti). Default: editor = *Gestione eventi*, *Anagrafiche*, *Gestione utenti*, *Gestione richieste*, *Export lista*. Attenzione: la tabella vive per ambiente, quindi i permessi vanno impostati sia in Staging sia in Produzione.
* **export della lista biglietti** (`GET /events/<id>/export/xlsx` e `.pdf`, pulsanti *Excel*/*PDF* nella scheda evento): file generati **in memoria**, mai salvati sul server. L'**Excel** (un solo foglio *Lista*) contiene **solo la tabella** della lista: intestazioni con i marcatori del template (`N° [numero biglietti]`, `RITIRA [referente]`, `NOMINATIVI [altri partecipanti separati da virgola]`), una riga per richiesta (numero, referente con `+ [pass]` quando la richiesta ha un pass senza partecipanti, nominativi separati da virgola) e la riga finale con il totale e `TOT`. Il **PDF** impagina la stessa lista in forma strutturata (intestazione, luogo e data, radio/canale, richieste, totale biglietti, consegna, tabella con intestazione ripetuta su ogni pagina, totale e blocco firma con referente invio, contatti e data di generazione). Nessuna dipendenza nuova: XLSX costruito con `zipfile`, PDF con `legal_pdf.build_pdf_from_pages()`.
* **statistiche per utente** (`/statistiche`, voce *Statistiche* nel menu): per ogni utente le richieste totali, quelle annullate e i biglietti richiesti/emessi/negati. Admin e superadmin vedono il dettaglio di tutti; gli altri utenti vedono solo il proprio numero di richieste (incluse quelle inserite a loro nome dallo staff). Le richieste marcate «test - no statistiche» sono escluse.
* **avviso liste eventi in scadenza**: banner su **tutte** le pagine per gli admin (espandibile con *Dettagli*) e finestra di riepilogo al primo accesso della giornata; gli eventi con lista non ancora inviata entro `list_deadline_days` giorni restano visibili finché non vengono chiusi.
* **Cloudflare Turnstile sul modulo pubblico Contatti**: widget e verifica server-side Siteverify, hostname opzionale e rifiuto fail-closed; CSRF e honeypot restano attivi. Configurare `TURNSTILE_SITE_KEY` e `TURNSTILE_SECRET_KEY`. `/add` è autenticato e non richiede challenge.
* **proposta di commit obbligatoria**: la checklist di fine processo richiede sempre la proposta di messaggio di commit, anche a task incompleto.
* **resilienza del pool DB**: `_checkout_live_connection()` valida con `SELECT 1` le connessioni ripescate dal pool e scarta quelle chiuse dal server dopo un periodo di inattività.
* **anteprima modificabile dell'email**: il pulsante *Invia lista (demo)* non spedisce subito: apre una pagina di **anteprima** con oggetto e corpo generati a runtime, **modificabili** prima dell'invio (più i destinatari di prova e l'eventuale avviso di lista incompleta). Il secondo invio (`action=send`) recapita esattamente il testo mostrato.
* **email della lista in HTML con tabella e footer Eventix**: il messaggio inviato all'organizzatore diventa `multipart/alternative`: oltre al testo (che resta modificabile in anteprima) contiene una **versione HTML** che riprende la lista come **tabella vera** — intestazione scura, righe alternate, riga del totale evidenziata — e chiude con il footer **«Generato con Eventix - www.eventix.work»**. La pagina di anteprima mostra il messaggio renderizzato in un riquadro. Ogni valore della lista è sottoposto a escape HTML: un nominativo con `<` o `&` non può diventare markup. Il footer Eventix compare anche in fondo al **PDF** della lista.
### Changed
* **badge superadmin ridotto allo scudetto**: nell'elenco *Gestione utenti* il badge accanto al ruolo mostra solo l'icona dello scudo (i dettagli restano nel tooltip), così la riga non si allarga con l'etichetta testuale.
* **anteprima dell'invio lista: solo l'introduzione è modificabile**: nell'anteprima di *Invia lista (demo)* la tabella della lista, la firma del mittente e il footer non si possono più riscrivere a mano — sono mostrati in un campo in sola lettura e vengono rigenerati dai dati dell'evento a ogni invio (`event_export.split_email_body()`; le righe con tabulazioni eventualmente inviate dal modulo vengono scartate). Prima si poteva modificare l'intero corpo dell'email, tabella compresa, con il rischio di inviare all'organizzatore una lista diversa da quella dell'evento.
* **mittente dell'email = utente che invia**: l'email della lista parte a nome di chi la invia (`From: Nome Cognome <email utente>`) con `Reply-To` sul suo indirizzo, così l'organizzatore risponde direttamente al referente invece che al mittente tecnico; nelle notifiche di sistema, dove non c'è un utente, resta `MAIL_DEFAULT_SENDER`/`MAIL_NOREPLY_SENDER`.
* **versione tracciabile a ogni fix**: `VERSION` viene aggiornato da Release Please **solo su `main`**, quindi in Staging restava fermo all'ultimo rilascio e non si distingueva un fix dall'altro. Ora footer e asset (`?v=`) mostrano `0.19.0+fix.<commit>`: `_build_identifier()` legge `APP_BUILD` (impostabile dal deploy), altrimenti `BUILD.txt`, altrimenti il commit git locale (con suffisso `-dirty` se ci sono modifiche non committate). `GET /version` espone anche `build` e `label`, la pagina Deploy mostra il build di Staging e il modale crediti rilascio + build. `VERSION` e il confronto Staging/Produzione restano invariati.
* **intestazioni Excel senza parentesi quadre**: il foglio scaricato riporta `N° numero biglietti`, `RITIRA referente`, `NOMINATIVI altri partecipanti separati da virgola` (prima `N° [numero biglietti]`, …): la tabella arriva all'organizzatore così com'è, non è un modulo da compilare.
* **nominativi senza username**: in lista (Excel, PDF ed email) referente e partecipanti compaiono come **solo nome e cognome**; lo username fra parentesi salvato in archivio (es. `Demo Basic (demouser)`) viene tolto. Una qualifica con spazi fra parentesi non viene toccata.
* **pass in lista con la tipologia**: in Excel, PDF ed email il pass è indicato come **`+ Pass (tipologia)`** (es. `FRANCESCA CAVALLI + Pass (Parcheggio)`) usando la tipologia richiesta sul partecipante; senza tipologia resta `+ Pass`. Il referente viene marcato quando è lui il titolare del pass (nome coincidente con un partecipante con pass, oppure richiesta con un solo nominativo).
* **invio di prova consentito con notifiche disattivate**: se la **modalità debug** è attiva l'invio demo della lista procede anche con le notifiche email spente — non raggiunge comunque destinatari reali — e il messaggio di conferma lo segnala. Senza debug l'invio resta bloccato.
* **scadenza di default della lista portata a 2 giorni**: `events.list_deadline_days` passa da 1 a 2 giorni prima dell'evento (default della colonna in `initialize_database()`, valori di ripiego `COALESCE(..., 2)` in tutte le query, default del form in *Modifica evento*). Gli **eventi già esistenti mantengono il valore salvato** (1): per allinearli serve un aggiornamento una tantum dei dati, non una migrazione.
* **notifiche in modalità debug**: le email non raggiungono più gli utenti reali ma **solo gli admin**, con oggetto `[DEBUG] …` e banner `*** EMAIL DI DEBUG - NON INVIATA AL DESTINATARIO REALE ***` più l'elenco dei destinatari previsti.
* **pagina Deploy coerente**: quando la versione di Produzione arriva dal DB (sonda `/version` non disponibile) non viene più dichiarato un falso *"Produzione in ritardo"* — lo stato è **Produzione non verificata (valore dal DB)** — e il riquadro *Versioni* mostra le **release in attesa** (voci sotto `[Unreleased]`, prossima versione proposta da Release Please), così lo scostamento fra `VERSION` e il codice in Staging è esplicito.
* **card "In arrivo" della dashboard più compatte**: l'artista non viene più ripetuto quando è già riconoscibile nel nome evento (`artist_redundant`, es. *Laura Pausini* su *Pausini - Io Canto World Tour — Bologna*) e la città non compare tre volte; il badge di gestione non va più a capo (`1 da gestire · 1 g`).
* **dashboard e dettaglio evento**: rimosso il nome evento ripetuto (l'artista compare solo se diverso dal nome e la città non è più duplicata); il richiedente mostra nome e cognome senza username; il referente espone i recapiti cliccabili (chiama/WhatsApp/mail); la colonna *Biglietti* mostra richiesti, emessi e negati; un badge indica se l'evento è **Completato** o **In gestione** con il conteggio degli emessi.
* **annullamento delle richieste**: la richiesta non viene più eliminata ma **archiviata** con `status = 'Annullata'`, data, autore e motivo (partecipante, richiedente, amministrazione, organizzatore, esaurimento posti) più l'opzione «test - no statistiche» riservata ad admin/superadmin; i pulsanti in dashboard e dettaglio evento aprono il modulo di annullamento invece di cancellare.
* **luoghi senza indirizzo**: creazione e modifica di un luogo non richiedono più indirizzo e città; il record viene marcato `is_incomplete` quando uno dei due manca (utile per luoghi generici come «cinema»).
* **regole Cline di ottimizzazione token**: nuovo `.clinerules/02-token-optimization.md` (compattazione del contesto, diff minimi, prevenzione dei loop, mappa del progetto, chat telegrafica), replicato con adattamenti negli altri workspace.
* **refresh di sessione tollerante**: un errore del database nel refresh periodico dei dati utente non produce più un 500 (viene loggato e ritentato alla richiesta successiva); le eccezioni non gestite vengono registrate con `@app.teardown_request` e `psycopg2.pool.PoolError` risponde 503 come per il DB non disponibile.
### Fixed
* **avviso «Nominativi da completare» che non tornava al nuovo accesso**: la chiusura (*Più tardi*) era memorizzata nel `sessionStorage` del browser, quindi un nuovo accesso nella stessa scheda non riproponeva la finestra pur avendo i nominativi ancora da completare. Ora la chiusura viene registrata nella **sessione del server** (`POST /api/names-reminder/dismiss`, flag `NAMES_REMINDER_DISMISSED_KEY` azzerato da `login` e dal primo inserimento password): nessun dato resta nel browser e la finestra ricompare a **ogni accesso** finché i nominativi mancano, senza ripresentarsi mentre si naviga nello stesso accesso (i badge giallo/rosso restano visibili in pagina).
* **verifica anti-spam del modulo Contatti che rifiutava invii validi**: il token Turnstile scade dopo **5 minuti** e può essere usato una sola volta, quindi un modulo compilato con calma veniva respinto con «Verifica anti-spam non valida. Ricarica la pagina e riprova.». Il widget ora usa il **rendering esplicito** e si **azzera da solo** alla scadenza del token (`expired-callback`) e al timeout della challenge (`timeout-callback`), rigenerando un token valido; senza token l'invio viene bloccato nel browser con un avviso, invece di fare il giro sul server. Il rifiuto del server **riapre il modulo pubblico con i dati inseriti** (prima un 400 generico rimandava il visitatore anonimo al login, perdendo il testo scritto) e ogni motivo di rifiuto (honeypot, token assente/scaduto, hostname, Siteverify irraggiungibile) viene ora **registrato nei log** e mostrato in pagina fuori produzione; i codici di errore di Siteverify sono tradotti in italiano (per esempio `timeout-or-duplicate` → «il controllo di sicurezza è scaduto o è già stato usato»). Il campo esca del modulo è stato rinominato da `company_website` a `contact_extra_info` perché i gestori di password tendono a compilare i campi con nomi che sembrano «azienda/sito», generando falsi rifiuti.
* **avviso liste in scadenza con tutti gli eventi futuri**: l'avviso segnalava come «in scadenza» ogni evento con la lista ancora da inviare, quindi comparivano decine o centinaia di eventi lontani nel tempo. `_imminent_events_for_admin()` filtra di nuovo nella query (`(COALESCE(e.end_date, e.event_date)::date - %s::date) <= COALESCE(e.list_deadline_days, 2)`, default **2 giorni**, modificabile per evento): banner e popup del primo accesso giornaliero elencano solo gli eventi entro la scadenza (compresi quelli che l'hanno già superata) e non compaiono se non ce ne sono.
* **avviso «Liste eventi da inviare» permanente**: l'avviso non filtra più per scadenza, quindi resta in pagina finché c'è una lista da inviare (prima spariva appena nessun evento cadeva entro i giorni di scadenza — per esempio dopo la chiusura degli eventi passati). Il titolo riporta il totale e quanti eventi sono **in scadenza** (badge); ogni voce mostra i giorni mancanti; il popup del primo accesso giornaliero elenca **solo** gli eventi che hanno raggiunto la scadenza. La dashboard non ricalcola più l'elenco con una query propria: unica fonte è `_imminent_events_for_admin()`.
* **le notifiche email non venivano inviate**: `_send_email()` si limitava a stampare il messaggio nei log, quindi l'invio demo della lista non arrivava a nessuno. Ora la consegna passa da `_smtp_send()` (SMTP reale con `MAIL_SERVER`, `MAIL_PORT`, `MAIL_USERNAME`, `MAIL_PASSWORD`, `MAIL_DEFAULT_SENDER`); senza `MAIL_SERVER` resta la sola stampa nei log e il messaggio in pagina lo dichiara. `_send_contact_notification()` riusa la stessa funzione (niente più codice SMTP duplicato).
* **diagnostica Turnstile**: la verifica registra il motivo del rifiuto (chiave mancante, token assente, `error-codes` di Siteverify, hostname atteso/ricevuto, Siteverify irraggiungibile) e lo mostra nella pagina di errore **fuori produzione**, così si capisce subito se è un problema di chiavi o di `TURNSTILE_HOSTNAME`.
* **barra di navigazione agganciata**: in stato *stuck* compariva il logo compatto a sinistra, che spostava le voci di menu, e cambiavano la spaziatura esterna e l'altezza delle voci: la navigazione perdeva l'allineamento con il contenuto e la barra "saltava" durante lo scroll. Ora i controlli compatti (ricerca + area utente) restano **a destra**, il logo compatto non compare e spaziatura/altezze non cambiano: le voci di menu restano allineate al contenitore della pagina.
* **popup "Liste eventi in scadenza"**: le voci erano testo con un link poco evidente e il modal restava aperto sopra la pagina di destinazione, così il collegamento diretto all'evento sembrava non funzionare. Ora ogni riga ha il pulsante **Apri evento**, il modal si chiude prima di navigare e la giornata viene segnata già alla comparsa (navigando dal popup non ricompare sulla pagina di destinazione).
* **versione di Produzione letta dal codice in esecuzione, non dal DB**: dopo il deploy della 0.19.0 la pagina *Deploy* mostrava ancora `Produzione v0.18.0` con il badge *Disallineamento: Produzione in ritardo*, mentre il sito pubblico serviva già la 0.19.0. `_version_status()` leggeva `app_settings.running_app_version`, scritto da `_record_running_version()` solo al **primo accesso autenticato** dopo l'avvio dell'istanza: se dopo il riavvio del container nessuno accede alla Produzione, il valore resta quello dell'istanza precedente e il confronto è falso. Nuova rotta pubblica `GET /version` (`{"version": APP_VERSION, "environment": app_environment()}`, senza DB né sessione) e sonda `promotion.prod_app_url()` + `promotion.fetch_running_version()` (`PROD_APP_URL`, fallback `https://<PROD_DOMAIN>`, timeout 4 s, risposta validata, errori senza URL/token); il valore del DB resta solo come fallback dichiarato e viene mostrato a parte quando diverge. Il riquadro *Versioni* indica la provenienza del dato e la sezione *Configurazione* segnala se `PROD_APP_URL` manca. Nessuna migrazione.
## [0.19.0] - 2026-10-28
### Added
* **badge e finestra dei nominativi mancanti**: il richiamo «N nominativi senza nome» diventa **giallo** (`warning`) finché c'è tempo e **rosso** (`danger`) quando all'evento mancano **meno di 5 giorni** oppure lo staff ha portato la richiesta in stato **Confermata** (`NAMES_CONFIRMED_STATUS`, `MISSING_NAMES_ALERT_DAYS`); gli helper `missing_names_badge()` e `missing_names_badge_label()` in `app.py` sostituiscono le stringhe sparse nei template, che ora non decidono più il colore. L'utente (non lo staff) con badge rosso trova al primo accesso la finestra **Nominativi da completare**: `GET /api/names-reminder` restituisce le proprie richieste da completare (`user_missing_names_alerts()`, 401 se non autenticato, vuoto per admin/editor) e `static/js/names-reminder.js` la disegna sopra ogni pagina, esclusi inserimento e modifica richiesta. La chiusura con *Più tardi* vale solo per la sessione del browser (`sessionStorage`, chiave `eventix-names-reminder-dismissed`) e la finestra ricompare al login successivo finché i nominativi non sono completati; nessuna migrazione.
* **recapiti del referente richiedente precompilati e persistenti**: nel form di nuova richiesta, scegliendo come destinatario *Il richiedente*, telefono ed email dell'utente registrato arrivano dal profilo (`current_user_contacts` in `users`) e sono mostrati in **sola lettura**, così non si inseriscono recapiti diversi da quelli usati per le notifiche. Il POST `/add` salva nel profilo soltanto i recapiti **mancanti** (`sync_profile_contacts()`: `UPDATE users ... WHERE COALESCE(TRIM(phone),'') = ''`), quindi il primo inserimento non va più ripetuto e i dati già presenti non vengono mai sovrascritti. Per i richiedenti esterni il comportamento resta invariato.
* **filigrana d'ambiente a più righe**: le pagine in **Staging Mode** e sulle istanze **locali** mostrano una filigrana leggerissima nello sfondo con il nome dell'ambiente (*STAGING MODE · DATI NON DI PRODUZIONE*, *LOCAL · ISTANZA DI SVILUPPO*), così non si confonde una copia di prova con il sito ufficiale. Durante l'**impersonificazione** la filigrana cambia variante, riporta l'utente impersonato (*IMPERSONIFICAZIONE · nome.utente*, con l'etichetta dell'ambiente quando non è Produzione) e la pagina prende una tinta leggermente diversa. Variante, righe e immagine SVG sono decise da `watermark_state()` e `watermark_image()` in `app.py`; `WATERMARK_BANDS` fissa le coordinate verticali delle varianti a due e tre righe e `watermark_image()` disegna una banda per riga (`<text>` dentro un `<g transform="translate(...)">`), così le righe non si sovrappongono. Lo strato (fisso, dietro i pannelli, `pointer-events: none`) e le tinte vivono in `static/css/app.css`. In Produzione senza impersonificazione non compare nulla.
* **combo «Radio / Cliente» con riferimento**: nel form di richiesta la voce si sceglie da un elenco unico (`Interno Dip./Coll.`, `Cliente`, `Fornitore`, `Parente/Amico`, `Ascoltatore`, `Altro`) e, per **Cliente**, **Fornitore** e **Altro**, compare un campo di dettaglio obbligatorio (*Nome cliente*, *Nome fornitore*, *Dettaglio*) salvato nella nuova colonna `requests.client_reference` (migrazione idempotente in `initialize_database()`); per le altre voci il riferimento viene azzerato. Elenchi e dettaglio evento mostrano «Voce: riferimento» con il filtro `client_label`. La combo è nel form di inserimento **per tutti i ruoli**, utenti compresi, e quindi anche quando un admin impersona un utente (l'impersonificazione assume ruolo e permessi dell'utente impersonato); nella modifica la gestisce chi ha la modifica completa (admin, oppure il richiedente finché la richiesta non è presa in carico), mentre gli altri la vedono in sola lettura. Il campo condiviso è `templates/_client_reference.html`.
* **biglietti e partecipanti nel form di richiesta**: accanto alle schede partecipante il pulsante **+ Aggiungi partecipante** aggiunge un biglietto e una scheda, mettendo il cursore sul nome nuovo; il campo *Biglietti richiesti* resta liberamente digitabile (si può svuotare e riscrivere) e le schede si ridisegnano solo sui valori validi, senza perdere i nominativi già inseriti.
* **verifiche automatiche**: nuova `tests/check_client_reference.py` (elenco chiuso, normalizzazione con `client_selection()`, filtro `client_label`, colonna `requests.client_reference` in `initialize_database()`, POST `/add` e `action=full` per admin e utente, combo modificabile solo dall'admin) e `check_event_location_extras` in `tests/check_request_edit_manage.py` esteso ai ruoli **user** ed **editor** per provare che i recapiti del luogo e dei contatti organizzatore restano nascosti.
* **verifiche automatiche sui nominativi**: `tests/check_optional_names.py` copre ora il colore del badge (`check_missing_names_badge`), il presidio dei template (`check_badge_templates`: helper al posto delle condizioni in linea, modale collegata a `GET /api/names-reminder`, `static/js/names-reminder.js` e `.names-reminder-item` in `static/css/app.css`), il salvataggio dei recapiti mancanti nel profilo senza sovrascrittura (`check_profile_contacts_sync`) e l'elenco della modale con 401 senza login (`check_names_reminder_alerts`).
* **temi stagionali**: oltre al ragno di Halloween, il sito mostra decorazioni ricorrenti per **Natale** (fiocchi di neve, 15/12 – 2/01), **Befana** (3 – 7/01) e **Pasqua** (dalla Domenica delle Palme al Lunedì dell'Angelo, calcolata ogni anno). Un solo tema per volta, scelto in ora italiana da `seasonal_theme_for_day()` in `app.py`, con i partial `templates/_theme_*.html` inclusi da `_seasonal_decorations.html`: `base.html` non contiene più CSS stagionale. Solo per utenti autenticati, `pointer-events: none` e `prefers-reduced-motion`.
* **tema compleanno**: chi indica la **data di nascita** nel proprio profilo (nuova colonna facoltativa `users.birthday`, migrazione idempotente in `initialize_database()`) riceve nel giorno del compleanno la decorazione con **palloncini e coriandoli** (`templates/_theme_birthday.html`, solo CSS). Contano solo mese e giorno: l'anno non viene confrontato e chi è nato il 29 febbraio festeggia il 28 negli anni non bisestili. Il compleanno è personale e ha la precedenza sulle finestre stagionali (Halloween, Natale, Befana, Pasqua), mentre l'anteprima forzata dal superadmin resta al primo posto. La decorazione dura solo i primi 30 secondi dopo l'accesso. La data viaggia in sessione come nome e avatar (nessuna query aggiuntiva a ogni pagina) e la pagina *Temi stagionali* mostra lo stato del proprio compleanno.
* **anteprima temi per il superadmin**: nuova pagina `/admin/seasonal-themes` (voce *Temi stagionali* in Amministrazione, riservata a superadmin o istanza in debug) per forzare un tema, tornare all'automatico o disattivare ogni decorazione; la scelta vive solo nella sessione (nessun DB, nessuna migrazione) e un badge «Tema di prova» la rende visibile e reversibile da ogni pagina.
### Changed
* **scheda evento impaginata**: l'intestazione del dettaglio evento non è più un unico paragrafo con i separatori «·», i contatti in linea e i link subito dopo. Categoria, titolo, stato, metadati (artista, luogo, organizzatore, indirizzo, data, posti, piattaforma), radio/canale, contatti a pillola e *Apri mappa* / *Planimetria* / *Sito del luogo* sono ora blocchi distinti che vanno a capo, con *Modifica* e *Nuova richiesta* raggruppati a lato. Indirizzi e nomi lunghi non allargano più la pagina oltre lo schermo.
* **contatti di luogo e organizzatore riservati all'admin**: nella scheda evento i recapiti dei contatti organizzatore e del luogo sono visibili solo agli admin; utenti ed editor continuano a vedere nome del luogo, indirizzo, *Apri mappa*, *Planimetria* e *Sito del luogo*, ma non telefoni ed email.
* **compleanno a tempo (30 secondi)**: la decorazione di compleanno è simpatica ma invasiva, quindi non resta accesa per tutta la sessione: compare solo nei **primi 30 secondi dopo l'accesso** (`BIRTHDAY_THEME_SECONDS`) e poi si spegne da sola con una dissolvenza CSS, senza ricaricare la pagina. Il timer (`BIRTHDAY_THEME_SESSION_KEY`) parte al login, in `/set-password` e all'avvio di un'impersonificazione (con ripristino all'uscita); il refresh periodico della sessione non lo tocca. Scaduti i secondi torna il tema della data e i palloncini ricompaiono soltanto con un nuovo login, sempre nel giorno del compleanno. L'anteprima forzata dal superadmin resta senza scadenza e la pagina *Temi stagionali* mostra i secondi residui.
* **Halloween solo con il ragno**: rimossi sfondo scuro, ragnatela a tutto schermo e ri-colorazione di link e pulsanti; layout, colori del menu e testi restano quelli originali. Resta il ragno animato appeso al filo in alto a sinistra, che non intercetta i clic e rispetta `prefers-reduced-motion`. La finestra è ricorrente e vale per gli utenti autenticati **dal 15 ottobre al 4 novembre di ogni anno** (`halloween_spider_active()` in `app.py`), senza date cablate.
### Fixed
* **azioni della scheda evento (*Nuova richiesta*)**: il gruppo con *Modifica* e *Nuova richiesta* viene reso solo se l'utente ha almeno un'azione disponibile (`role == 'admin' or can_request`), quindi non resta più un riquadro vuoto a destra dell'intestazione; su schermi sotto 768 px i due pulsanti si dividono la larghezza (`.event-heading-actions .btn { flex: 1 1 8rem }`) e restano raggiungibili anche con indirizzi o titoli lunghi che ora vanno a capo invece di allargare la pagina.
## [0.18.0] - 2026-10-25
### Added
* **regole per gli agenti AI**: `AI_INSTRUCTIONS.md` è dichiarato documento **normativo e fonte unica**, con l'ordine di precedenza rispetto a `.clinerules/` e agli appunti interni (`NOTES.md`, `ARCHITETTURA.md`), la nuova tabella *Revisioni di questo documento* e il passo *Allineamento delle regole* nella checklist di fine processo. Aggiunti i file di puntamento per Cline, Cursor, Copilot, Continue, Windsurf e agenti generici (`AGENTS.md`); le copie divergenti in `.cursorrules` e `.github/copilot-instructions.md` sono state sostituite da puntatori e il test `tests/check_ai_instructions.py` impedisce che le regole vengano duplicate o divergano.
* **Release GitHub e rollback del codice**: ogni *Deploy codice in produzione* crea (in modo idempotente) la GitHub Release `eventix-v<versione>` sul commit di `main` appena deployato; la pagina **Deploy** elenca le Release con data e stato (*In produzione* / *Staging*) e permette il **Rollback** del solo codice al tag scelto tramite l'API deploy-by-tag di Coolify (`POST /admin/deploy/rollback`, riservata ai superadmin in Staging). Le Release sono lette dall'API GitHub (nessuna migrazione DB); un errore della Release non blocca il deploy. Il rollback non annulla le migrazioni (solo additive) né i dati già promossi e `main` resta avanti: la pagina segnala il *Rollback attivo* per non confonderlo con un deploy in corso.
* **eventi a intervallo di date**: nuovo campo facoltativo `events.end_date` (migrazione idempotente + indice `idx_events_end_date`) e opzione **Intervallo** nei form nuovo/modifica evento. La data fine è inclusiva (salvata alle 23:59), l'evento resta *In corso* e richiedibile fino all'ultimo giorno e compare come `gg/mm/aaaa – gg/mm/aaaa` in elenco e dettaglio. Duplicazione e promozione in produzione coprono il nuovo campo (`promotion.EVENT_COLS`).
* **richieste su eventi a intervallo**: in *Nuova richiesta*, quando l'evento ha una data fine, compare il campo obbligatorio **Data dell'evento** con i giorni validi dell'intervallo. Il giorno scelto viene salvato in `requests.event_date` (mantenendo l'orario di inizio) e diventa il riferimento per biglietti, scadenze nominativi, promemoria, email e avvisi «Mancano N giorni»; gli eventi a data singola restano invariati.
* **import eventi — intervalli e DATA FINE**: `DATA` accetta intervalli italiani (`1-7 ottobre 2026`, `dal 24 al 26 luglio 2026`, `10/01/2027 – 12/01/2027`) e la nuova colonna facoltativa `DATA FINE` ha la precedenza. In revisione ogni riga ha il toggle **Intervallo** con il campo data fine; senza orario l'intervallo va da 00:00 a 23:59, mentre la data singola mantiene il default 21:00.
* **import eventi — nuove colonne**: il parser riconosce `STATO`, `ORARIO` e `CATEGORIA` (e i relativi alias) oltre alle colonne storiche; lo stato è normalizzato tra *Estrazione fatta*, *Lista inviata*, *Biglietti ricevuti* e *Chiusa* e un valore sconosciuto non blocca l'import.
* **import eventi — revisione e categorie**: in revisione ogni riga ha il selettore **Categoria** (propagato a tutte le righe con lo stesso nome evento via `data-event-name`), badge dello stato con legenda colorata, badge *Senza data* / *Senza luogo* e *Codice · nessuna posizione* per le righe con `Posizione = CODICI`; le categorie nuove scelte in revisione sono aggiunte a `app_settings.event_categories`.
* **elenco e scheda evento**: lo stato colora la riga dell'elenco (arancione *Estrazione fatta*, giallo *Lista inviata*, azzurro *Biglietti ricevuti*, verde *Chiusa*) e compare come badge nell'elenco e nel dettaglio; data e luogo mancanti sono evidenziati con badge e sfondo azzurro. Il form di modifica evento espone il selettore **Stato lavorazione** e permette di lasciare vuota la data.
### Changed
* **tag delle Release allineati al brand**: le Release create dal *Deploy codice in produzione* usano ora `eventix-v<versione>` (componente `eventix` in `release-please-config.json`) al posto di `taskman-v<versione>`; la Produzione è `https://eventix.work`. I tag storici `taskman-v*` restano accettati in sola lettura, quindi le Release già pubblicate restano elencate e selezionabili per il rollback.
* **barra di navigazione sticky**: la barra chiara resta agganciata in cima allo schermo durante lo scroll e, da 1200 px in su, mostra una versione compatta di logo, ricerca globale e area utente (campanella + avatar con menu profilo/Esci), così i controlli dell'header scuro restano raggiungibili quando questo esce dallo schermo. A pagina non scrollata il layout è identico a prima; su smartphone la barra resta nascosta e vale il menu offcanvas. L'area utente è ora un include condiviso (`templates/_app_account.html`) e le notifiche usano attributi `data-*` per aggiornare entrambe le campanelle.
* **filtri e disponibilità con intervallo**: dashboard, elenco eventi, dettaglio, nuova richiesta ed elenco richieste usano `COALESCE(end_date, event_date)` per futuro/attivo/passato; per le richieste su eventi a intervallo la data effettiva è il giorno scelto (`REQUEST_EVENT_DATE_SQL`), usato anche da scadenze nominativi, promemoria e calcolo «Mancano N giorni».
* **import eventi — data e luogo facoltativi**: `DATA` può essere vuota (evento «Data da definire», sempre visibile e richiedibile) e `LUOGO` mancante non blocca più la riga: l'evento entra con l'etichetta «Senza luogo», evidenziato come dato da completare, senza creare un luogo. `ORARIO` accetta `21`, `21:30`, `21.30` e la frazione oraria Excel; in assenza di orario resta il default 21:00.
* **import eventi — invio come Codice**: `Posizione = CODICI/CODICE` non genera zone né tipologie posto e imposta `delivery_type = 'Codice'`. Gli eventi con stato **Chiusa** vengono importati non attivi e non vengono riattivati dal flag *Attiva gli eventi importati*; `STATO = INCOMPLETO` dei file storici continua a mantenere l'evento non attivo.
* **template CSV di importazione**: aggiornato con le intestazioni `ARTISTA;CATEGORIA;STATO;DATA;ORARIO;LOCATION;CITTÀ;n°;Posizione;RADIO;ORGANIZZ.` e due righe di esempio (una con `CODICI` e data vuota).
### Fixed
* **geocodifica — solo risultati italiani**: `_geocode_import_location_lookup()` scarta i risultati con `address.country_code` diverso da `it` o con coordinate fuori dal riquadro italiano (35–48.5 N, 6–19 E) e il fallback Mapbox chiede `country=it`. Quando il nome del locale non ha un riscontro in Italia il luogo nasce **senza coordinate** e marcato incompleto, quindi compare in *Completa coordinate mancanti* ed è correggibile a mano, invece di ricevere città e coordinate di un omonimo estero e risultare per sempre completo. Nessuna migrazione DB (`NOTES.md`, 2026-10-25).
* **anagrafica luoghi — città e coordinate corrette**: riallineate in staging le schede luogo finite su un omonimo sbagliato dal geocoder (`Nelson Mandela Forum` a *Piennes*, `Palazzo del Turismo Jesolo` a *Corigliano-Rossano*, `Teatro Grande` in Nuovo Messico, `Arena di Verona` a 17 km, `Stadio Dall'Ara` e `Unipol Arena` sullo stesso punto) e in produzione i quattro luoghi con coordinate fuori dall'Italia (`HALL - Padova`, `PALA RESCIFINA`, `PALA TEKHNOSHIP`, `Stadio Centrale del Tennis`), più il refuso `Gradone Riviera` → `Gardone Riviera`. La correzione è di soli dati, senza modifiche di codice: l'irrigidimento del geocoder è la voce precedente.
* **import eventi — luoghi già presenti in produzione**: la revisione riconosce anche i luoghi che esistono solo in produzione (lettura in sola lettura del DB prod, etichetta «produzione» nel selettore) e la conferma ne copia la scheda in staging senza geocodificarla, così l'import riusa il luogo esistente invece di ricrearlo e la promozione non sovrascrive indirizzo e coordinate dell'anagrafica di produzione.
* **revisione import — apertura e scorrimento**: la pagina non salta più al fondo per il `focus()` automatico sul campo *data fine* degli intervalli; si apre sull'intestazione della tabella. L'intestazione resta fissa, i record scorrono dentro un'area con altezza massima: a fine elenco lo scroll passa alla pagina (anche sui browser che non concatenano il contenitore interno) e l'elenco viene sempre riaperto dall'inizio. Una barra orizzontale superiore, sincronizzata con quella inferiore, mostra tutte le colonne senza dover arrivare in fondo.
* **import eventi — rilevazione duplicati**: una riga è proposta come duplicato quando ha la stessa data, lo stesso luogo/città e un nome o artista simile, anche parziale (`Pausini` viene riconosciuto in `Pausini - Io canto tour`). Città, date e parole generiche (`tour`, `concerto`, `live`) non contano nel confronto; un luogo diverso evita il falso positivo. In revisione il badge mostra il record esistente e il motivo, con scelta tra *Mantenere entrambi* e *Aggiorna / unisci nel record*.
* **data fine nello stesso giorno**: form evento, revisione import e commit confrontano la data fine per giorno, quindi una fine nella stessa giornata dell'inizio è accettata e normalizzata alle 23:59; una `DATA FINE` senza `DATA` o precedente all'inizio viene marcata **Data fine da correggere** e il commit la rifiuta finché l'intervallo non viene corretto o la riga esclusa. Con il toggle **Intervallo** attivo nel form evento la data fine è obbligatoria; dashboard (`dal … al …`) e selettore della nuova richiesta mostrano entrambe le date.
* **viste con data nulla**: dashboard, elenco eventi, dettaglio e form di modifica non falliscono più sugli eventi senza data (`strftime` protetto, ordinamento con i senza data in cima, «Data da definire» al posto della data). La scheda evento non propone *Nuova richiesta* per eventi **Chiusa** o scaduti.
* **nome evento mancante**: l'import segnala solo «nome mancante» (la data non è più obbligatoria) e la riga viene scartata senza interrompere le altre.
* **promozione in produzione — stato evento**: `promotion.EVENT_COLS` include ora `status`, quindi *Estrazione fatta* / *Lista inviata* / *Biglietti ricevuti* / *Chiusa* viaggiano con l'evento pubblicato o aggiornato in produzione (prima si perdevano); `tests/check_promotion.py` verifica la colonna.
* **istruzioni AI allineate al codice**: `AI_INSTRUCTIONS.md` (con `.cursorrules` e `.github/copilot-instructions.md` come copie reali dello stesso testo e `.clinerules/` ripristinata come directory) descrive ambienti reali (`app_environment()`, modulo Deploy solo in Staging Mode), DDL idempotente in `initialize_database()`, passi e variabili di `/admin/deploy/run`, whitelist di `SYNC_TABLES`/`EVENT_COLS`/`IMPORT_SETTING_KEYS`, test `tests/check_*.py` (non `unittest`) e `/healthz` che risponde `OK` in testo.
### Bug Fixes
* **deploy**: la ricarica automatica di «In aggiornamento» puntava all'URL del POST (`/admin/deploy/run`) e dava «Method Not Allowed»; ora ricarica `/admin/deploy`.
* **orari**: tutta l'app usa Europe/Rome (sessione PostgreSQL con `timezone=Europe/Rome`, `promotion.local_now()` al posto di `datetime.now()`): giorni mancanti, «Inviato il», impegno nominativi, scadenze. Le righe create prima di questa versione con `CURRENT_TIMESTAMP` restano in UTC.
* **mobile**: la lente «Cerca» della barra in basso apre una barra di ricerca in alto invece del menu laterale.
* **home**: il nome evento nell'elenco richieste apre la pagina dell'evento.
* **mobile**: freccia discreta nel dettaglio evento per tornare all'elenco.
### Features
* **deploy**: dopo l'avvio di un deploy, se Produzione è ancora indietro il badge mostra «In aggiornamento…» (con ricarica automatica ogni 20 s, per 15 minuti) invece di «Disallineamento».
* **loghi radio**: i loghi di RTL 102.5, Radio Zeta e Radio Freccia (`static/img/logo_*.png`) sono collegati tramite il filtro `radio_parts` e la macro `_radio.html`, ma al momento non sono mostrati (su smartphone resta il nome, per scelta); servono per usi futuri.
* **layout smartphone**: schede compatte (etichetta e valore sulla stessa riga, azioni in orizzontale) per Home, dettaglio evento, luoghi, biglietti, utenti e organizzatori; filtri dell'elenco eventi su griglia a due colonne; icone di gestione organizzatore non più impilate.
* **elenco eventi**: accanto a «Mancano N giorni» compare la radio/canale dell'evento.
* **deploy**: la pagina mostra la versione di Staging e di Produzione con un badge di allineamento/disallineamento. Ogni ambiente registra la versione in esecuzione in `app_settings.running_app_version` (al primo accesso dopo l'avvio) e il deploy la legge dal DB di produzione.
* **import eventi**: al termine, se ci sono luoghi incompleti compare un messaggio con il pulsante *Avvia aggiornamento coordinate* (geocodifica a lotti, script condiviso `static/js/geocode-incomplete.js`).
* **radio / canale**: la radio mappata dall'import (RTL, RZ, RF → nome esteso) è mostrata nel dettaglio evento (es. «RTL 102.5 - Radio Freccia») ed è modificabile dalla pagina di modifica evento.
* **contatti**: telefono e email dei contatti (organizzatori, dettaglio evento) hanno un layout compatto: il telefono apre la scelta *Chiama / WhatsApp*, l'email è una busta con l'indirizzo nel tooltip; su smartphone restano solo le icone.
* **utenti**: aggiunto il pulsante *Tester* (disabilitato, funzione futura).
* **elenco eventi**: le azioni a icona sono su una griglia da 3 colonne, così le righe restano compatte anche in Staging (5-6 icone).
* **accesso**: il campo Password della vetrina include ora l'icona a forma di occhio per mostrare o nascondere temporaneamente il valore inserito, con etichette accessibili per lettori di schermo.
* **revisione import eventi**: aggiunto un flag compatto per escludere singole righe dall'importazione; la tabella di revisione usa spaziature leggermente più dense mantenendo la gerarchia visiva. La navbar del backoffice rispetta la safe area superiore nelle PWA iOS a schermo intero.
* **identità Eventix**: la navigazione pubblica torna a mostrare il lockup ufficiale (`static/img/eventix-lockup.png`, ricavato da `Logo.png` con trasparenza ripulita e dimensioni dichiarate `800×244`) affiancato dal payoff *Smart event ticketing*. Logo, wordmark e palette blu/viola sono ora coerenti tra vetrina, pagine legali, footer e PWA.
* **splash iniziale**: all'avvio degli utenti anonimi compare una breve schermata di brand con il lockup, una barra di avanzamento e il payoff, visibile una sola volta per sessione e non bloccante. Lo splash rispetta `prefers-reduced-motion`, ha un fallback senza JavaScript e viene chiuso al massimo dopo 2,4 s. Il manifest PWA usa `background_color`/`theme_color` `#090e22` per allineare anche lo splash di sistema.
* **vetrina desktop-first**: nuova hero navy a due colonne con login, anteprima prodotto costruita in HTML/CSS, funzioni in griglia bento, workflow editoriale, trust panel e CTA finale. `static/css/public.css` isola il linguaggio visivo pubblico dal backoffice e mantiene il layout completo da 320 a 1440 px; navbar chiara, footer, banner cookie e pagine legali sono riallineati al brand.
* **audit GDPR download**: `legal_document_downloads` registra anche `document_title`, `ip_address` e `user_agent`, con migrazione idempotente per i database esistenti e indice `(document_slug, downloaded_at DESC)`.
### Bug Fixes
* **import eventi (nessuna conferma in produzione)**: riepilogo, elenco dei record incompleti e messaggi d'errore dell'import erano salvati nel cookie di sessione (~4 KB): con molte righe il browser scartava il cookie e l'esito spariva. Ora l'esito è salvato in `event_import_batches` e in sessione resta solo l'ID; i singoli errori non sono più flashati uno per uno (sono nel riepilogo).
* **orari in UTC**: "Ultima esecuzione" e orario dell'hook Coolify in *Deploy* usano ora Europe/Rome (`promotion.local_now()`, dipendenza `tzdata`).
* **menu backoffice**: ripristinato il ritardo di chiusura (600 ms) dei sottomenu su desktop; la regola CSS `:hover` li chiudeva subito, ora l'apertura al passaggio è gestita da JavaScript.
* **import eventi (timeout in produzione)**: la geocodifica dei luoghi nuovi non può più superare il timeout di Gunicorn (30 s, `WORKER TIMEOUT` → 500 su `/events/import/commit`). Ogni richiesta ha un budget (`GEOCODE_REQUEST_BUDGET_SECONDS`, default 12 s) e una cache per query; esaurito il budget i luoghi vengono creati senza coordinate e marcati incompleti.
* **completamento luoghi**: nuovo pulsante *Completa coordinate mancanti* in *Luoghi* (`POST /locations/geocode-incomplete`, solo admin) che geocodifica a lotti di ~10 s i luoghi senza coordinate, senza sovrascrivere indirizzo e città già presenti; l'esito dell'import rimanda alla pagina.
* **deploy / migrazioni**: verificato che il deploy migra il DB di produzione (`initialize_database`) prima del merge; aggiunte regole per Cline e Copilot (`.clinerules/`, `.github/copilot-instructions.md`) e test sull'ordine migrazione → merge.
* **promozione staging → produzione**: gli eventi promossi includono ora `radio_channel` e `is_incomplete` e i luoghi `latitude`, `longitude` e `is_incomplete`; con la promozione Eventi (bulk o singolo record) vengono allineate in Official anche le sole impostazioni di configurazione dell'import (`import_radio_mappings`, `import_ticket_mappings`, `ticket_zones_list`, `ticket_zone_synonyms`, alias e liste categorie/canali/tipologie luogo), così l'import eventi si comporta come in Staging. Notifiche, template email e segreti restano esclusi.
* **revisione import eventi**: corretto il doppio inserimento della colonna di esclusione. Il template genera una sola checkbox `skip_<riga>` per record e l'inizializzazione è idempotente, così il POST di conferma non riceve più valori di esclusione duplicati.
### Removed
* **blueprint Render `render.yaml`**: la Produzione gira su VPS con Coolify (build Nixpacks) e nessuno script, workflow o test leggeva il blueprint, che era l'ultimo residuo del vecchio hosting Render con il nome di servizio `taskman`. Il file è stato eliminato anziché rinominato, perché in un blueprint Render `name` è l'identità del servizio: un rename avrebbe fatto creare un secondo servizio al posto di ribattezzare quello esistente. La sezione *Deploy* del `README.md` descrive ora lo stack reale (Coolify, comando Gunicorn, variabili per servizio di produzione e di staging).
### Tests
* `tests/check_event_import.py`: schema import in `initialize_database`, budget di geocodifica ed endpoint di completamento a lotti. `tests/check_promotion.py`: ordine migrazione → merge nel deploy.
* `tests/check_public_site.py`: verifica il controllo accessibile per mostrare/nascondere la password nel form di accesso.
* `tests/check_promotion.py`: verifica che le colonne usate dall'import (radio, incompletezza, coordinate) e le sole chiavi `app_settings` di configurazione import siano trasferite con gli eventi (bulk e singolo record), senza toccare impostazioni operative.
* `tests/check_public_site.py`: verifica lockup dimensionato e splash di brand, anteprima prodotto desktop, CSS pubblico responsive e colori PWA coerenti.
* `tests/check_legal_gdpr.py`: verifica le nuove colonne di audit GDPR e i relativi indici nella migrazione.
### Documentation
* `ARCHITETTURA.md`, `MANUALE.md`, `docs/MANUALE_PUBBLICO.md` e `README.md` aggiornati su identità, splash, vetrina desktop-first e audit download GDPR.
## [0.17.1] - 2026-10-25
### Added
* **completamento luoghi — link mappa, sito e contatti**: il pulsante *Completa coordinate mancanti* (e la geocodifica dell'import) ora recupera da OpenStreetMap anche il **link mappa** (costruito dalle coordinate trovate), il **sito web** e i **contatti** (telefono ed email) del luogo, salvandoli nelle colonne già esistenti `locations.map_url`, `website_url`, `contact_phone`, `contact_email`. I campi già compilati non vengono mai sovrascritti e il messaggio finale indica quanti luoghi hanno ricevuto link mappa e recapiti. Nella scheda evento compaiono il link **Sito del luogo** e i recapiti del luogo.
### Fixed
* **header scuro — ricerca e area utente sovrapposte**: la ricerca globale non si accavalla più al logo né ai controlli dell'area utente. L'area utente può restringersi (il nome utente va in ellissi, ancora più corto tra 768 e 991 px) mentre la ricerca mantiene una larghezza minima leggibile: la riga resta su una sola linea e non sfonda mai la larghezza della pagina, anche con nomi utente lunghi e badge *Superadmin*. Il logo resta sempre integro.
* **ricerca globale — i risultati non finiscono più sotto il menu**: il pannello dei suggerimenti dell'header scuro si apriva *dietro* la barra bianca di navigazione (si vedeva a sprazzi, tagliato a metà). Ora l'header è un livello sopra le voci di menu, l'elenco è interamente visibile, si chiude scorrendo la pagina e non lascia residui sopra la barra sticky; anche i menu a tendina di notifiche e profilo non vengono più coperti.
## [0.12.0](https://github.com/simograndi/taskman/compare/taskman-v0.11.1...taskman-v0.12.0) (2026-10-20)
### Features
* **vetrina pubblica**: `/` per i visitatori anonimi non reindirizza più al login ma mostra una pagina di presentazione del software (funzioni, flusso di lavoro e punti di fiducia) con il **riquadro di accesso sticky a destra** su desktop. Gli utenti autenticati continuano a vedere la dashboard; `/login` usa la stessa vetrina.
* **pagine legali integrate**: `templates/legal/base_legal.html` estende ora `base.html`: privacy, termini, cookie policy e contatti usano la navigazione pubblica, il footer e il banner cookie del portale, senza topbar o footer duplicati. Hero, tab, indice e callout di supporto sono riallineati alla palette Eventix.
* **favicon locale**: nuova variante `local` (`static/icons/favicon-local.png|ico`) servita quando l'host è `127.0.0.1`, `localhost` o `::1`; a seguire Staging (`favicon-staging.*`) e Produzione (`favicon-prod.*`). La stessa priorità vale per `GET /favicon.ico` e per i tag `<link rel="icon">`.
* **SEO della home**: la vetrina resta `index, follow` in Produzione e `noindex, nofollow` in Staging, con canonical su `PROD_DOMAIN` e sitemap invariata.
* **layout pubblico responsive**: nuova navbar sticky con menu collassabile sotto 992 px, hero editoriale senza immagini non dimensionate, funzioni in griglia 3/2/1 colonne, workflow in card e pannello accesso sticky senza overflow. Verificato senza overflow orizzontale da 320 a 1440 px.
* **PWA e mobile**: viewport con `viewport-fit=cover`, altezza `100dvh`, target touch da 44 px, safe area iOS su navbar/footer/banner e manifest arricchito (`id`, `lang`, `display_override`, shortcut, icone maskable).
* **banner cookie compatto**: scheda flottante da 430 px su desktop e tre azioni in riga su mobile (circa 150 px di altezza a 390 px), con pulsante di chiusura che registra i soli cookie necessari.
* **crediti**: rimosso lo splash gigante `credits-splash` che mostrava `Designer - White.png` (1254×1254) sopra la modale; resta il logo 64 px nel corpo.
### Bug Fixes
* la vetrina mobile non viene più coperta dal banner cookie a tutta larghezza e il logo pubblico non usa più l'immagine sorgente 1536×1024: il marchio è un blocco CSS compatto e tutte le immagini residue restano vincolate.
* l'indice delle pagine legali torna sticky su desktop (`align-self: stretch` sulla colonna) e diventa una riga di chip scorrevole sotto 992 px.
### Tests
* nuovo `tests/check_public_site.py`: vetrina anonima, login, dashboard autenticata, shell legale condivisa, indicizzazione per ambiente, priorità delle tre favicon, validità dell'ICO locale, layout responsive, navbar collassabile, banner cookie compatto e campi del manifest PWA.
* `tests/check_promotion.py` e `tests/check_legal_gdpr.py` aggiornati per l'host locale, l'asset `favicon-local.ico` e il layout legale che estende `base.html`.
### Documentation
* `ARCHITETTURA.md`, `MANUALE.md`, `docs/MANUALE_PUBBLICO.md` e `README.md` aggiornati su vetrina pubblica, login sticky, pagine legali integrate e favicon locale.
## [0.11.1](https://github.com/simograndi/taskman/compare/taskman-v0.11.0...taskman-v0.11.1) (2026-10-20)
### Features
* calendario eventi: flag rapido **attivo/non attivo** per gli admin nell'elenco (`POST /events/<id>/toggle-active`). L'attivazione è immediata e senza conferma; la disattivazione chiede conferma in una modale con il riepilogo di richieste collegate, biglietti richiesti ed emessi (biglietti/pass), ricordando che i dati già registrati restano invariati. I nuovi eventi nascono **attivi** (con conferma al salvataggio) e la duplicazione resta **non attiva** finché non viene verificata.
* importazione eventi da file (prevista): al termine dell'importazione verrà chiesto se attivare gli eventi importati, che verranno creati **non attivi** per consentire la verifica di date e posti.
### Bug Fixes
* disponibilità eventi: gli utenti vedono solo il **semaforo sul totale** (verde = ok, giallo = posti limitati/in esaurimento dall'80% richiesto, rosso = posti esauriti) e non più i numeri. Il dettaglio evento mostra il semaforo al posto dei posti per zona e del totale; admin/editor continuano a vedere numeri e zone. Le soglie sono centralizzate in `availability_status()` (usata da `/events`, form nuove richieste e badge di `manage_request`) e il messaggio "La richiesta non è garantita a causa dell'esaurimento dei posti disponibili." resta nel form richieste con semaforo rosso.
### Documentation
* `MANUALE_ADMIN.md` (§1), `MANUALE.md` (*Eventi*), `ARCHITETTURA.md` (§6-octies) e `docs/MANUALE_OPERATORI_ADMIN.md` (§7) documentano il flag rapido attivo/non attivo, il default attivo dei nuovi eventi, la conferma di disattivazione con riepilogo e il comportamento previsto dell'importazione da file.
### Tests
* `tests/check_request_edit_manage.py`: nuove verifiche sul semaforo (soglie 80%/100%, evento senza posti), rendering del dettaglio evento per utente (solo colore ed etichetta, nessun numero o zona) e per admin (numeri e zone visibili), semaforo rosso a esaurimento e messaggio `sold_out_message` ancora collegato al form richieste.
* `tests/check_request_edit_manage.py` e `tests/check_management_ui.py`: verifica che `POST /events/add` crei l'evento attivo di default, che `POST /events/<id>/toggle-active` attivi/disattivi (400 azione non valida, 404 evento assente, 403 ruolo user), che il template dell'elenco esponga il flag, la modale di riepilogo e la conferma di creazione.
## [0.11.0](https://github.com/simograndi/taskman/compare/taskman-v0.10.2...taskman-v0.11.0) (2026-10-20)
### Features
* layout compatto delle pagine di management: le azioni di riga (eventi, luoghi, organizzatori, utenti, biglietti, richieste) sono pulsanti a icona `btn btn-sm icon-btn` con tooltip nativo `title` e `aria-label`, raggruppati in `.action-group`; in Staging anche i pulsanti di promozione di `_promotion.html` diventano icone (pubblica/aggiorna, rimuovi dalla produzione, flag demo) e le righe restano leggibili.
* tabelle di management responsive: sotto 576 px le righe diventano schede impilate con l'etichetta di colonna (`table-stack` + `data-label`, cella azioni `.table-cell-actions`), mentre sotto 992 px i pannelli riducono il padding, i titoli di sezione vanno a capo e l'elenco eventi porta i pulsanti su una seconda riga. La gestione richiesta resta a scorrimento orizzontale per non tagliare i dropdown dei combobox.
* badge d'ambiente più compatto su smartphone per non coprire i contenuti in fondo alla pagina.
### Bug Fixes
* badge e favicon coerenti anche con `FLASK_ENV=staging`: `app_environment()` considera Staging Mode sia `development` sia `staging` ed è l'unica fonte per badge, favicon, meta robots/`X-Robots-Tag`, `robots.txt`/`sitemap.xml` e modulo Deploy. Prima un'istanza con `FLASK_ENV=staging` mostrava l'icona STG ma il badge verde **Official**, consentiva l'indicizzazione e teneva bloccato il modulo Deploy; ora mostra **Staging Mode** (giallo) con l'icona STG e si comporta come staging.
### Documentation
* `ARCHITETTURA.md` (§6-septies), `MANUALE.md` (*Azioni rapide e layout su mobile*), `docs/MANUALE_OPERATORI_ADMIN.md` (§7 con le convenzioni per le nuove viste e il nuovo test) e `docs/MANUALE_PUBBLICO.md` (FAQ mobile) aggiornati.
### Tests
* nuovo `tests/check_management_ui.py`: verifica ganci CSS e breakpoint, `data-label` su tutte le celle delle tabelle impilate, `title`/`aria-label` su ogni `.icon-btn`, assenza di azioni testuali nelle celle Azioni, esclusione di `manage_request.html` dalla modalità impilata e compilazione di tutti i template.
## [0.10.2](https://github.com/simograndi/taskman/compare/taskman-v0.10.1...taskman-v0.10.2) (2026-10-20)
### Bug Fixes
* favicon dinamica non visualizzata: i file `static/img/favicon_*.ico` dichiaravano 256×256 ma contenevano un bitmap 1254×1254 (ICO non valido, scartato dai browser). Le icone ora stanno in `static/icons/` (`favicon-staging.png|ico` arancione con badge STG, `favicon-prod.png|ico` ufficiale, `logo.png`, `apple-touch-icon.png`, `icon-192.png`, `icon-512.png`) e si rigenerano con `python tools/generate_favicons.py`.
* `GET /favicon.ico` serve l'ICO dell'ambiente corrente e i tag `<link rel="icon">` usano `?v={{ app_version }}` come cache buster: una favicon già in cache non resta più bloccata dopo il deploy.
* `static/manifest.webmanifest` allineato a `/static/icons/`: percorsi assoluti, icona 512 `maskable` e file presenti su disco.
### Documentation
* manuali pubblico e operatori, `ARCHITETTURA.md`, `README.md` e `MANUALE.md` aggiornati con le icone di `static/icons/`, la favicon dinamica per ambiente e il troubleshooting della cache delle favicon.
## [0.10.1](https://github.com/simograndi/taskman/compare/taskman-v0.10.0...taskman-v0.10.1) (2026-10-20)
### Bug Fixes
* doppio deployment a ogni promozione: con l'integrazione GitHub di Coolify il push su `main` avvia già un deploy (`is_webhook: true`) e l'hook manuale ne avviava un secondo (`is_api: true`). Prima dell'hook l'app legge `GET /api/v1/deployments/applications/<uuid>` (uuid ricavato da `PROD_DEPLOY_HOOK_URL`) e attende fino a `DEPLOY_AUTODEPLOY_GRACE_SECONDS` (default 8) il deployment webhook del merge; se c'è, l'hook non parte. Se l'API non è raggiungibile il deploy manuale prosegue con un avviso nell'esito.
* hook deduplicato in modo atomico: la prenotazione avviene prima della `POST`, quindi due richieste sovrapposte (doppio click) non inviano entrambe; gli errori rilasciano subito la prenotazione per permettere il retry.
* l'hook manuale viene saltato anche se Coolify ha già un deployment `queued`/`in_progress`; i form di `/admin/deploy` disabilitano il pulsante al primo invio e la sezione *Configurazione* indica se il riconoscimento dell'auto-deploy è pronto (`uuid` nell'hook + `COOLIFY_API_TOKEN`).
### Documentation
* manuale operatori: riconoscimento dell'auto-deploy GitHub, nuova variabile `DEPLOY_AUTODEPLOY_GRACE_SECONDS` e troubleshooting del doppio deployment; README, `ARCHITETTURA.md` e `.env.example` aggiornati.
## [0.10.0](https://github.com/simograndi/taskman/compare/taskman-v0.9.0...taskman-v0.10.0) (2026-10-20)
### Features
* deduplicazione dell'hook di deploy Coolify: entro `DEPLOY_HOOK_DEDUPE_SECONDS` (default 60) un invio riuscito non viene ripetuto, così doppio click o retry non avviano due deploy.
* riepilogo sintetico *Stato ultimo deploy* in `/admin/deploy`: esito complessivo, conteggio OK/errori/saltati, timestamp dell'ultima esecuzione e ultima risposta HTTP dell'hook.
### Security
* isolamento del modulo Deploy in Produzione (`FLASK_ENV` diverso da `development`): voce di menu, badge e pulsanti di promozione non vengono renderizzati; le `GET /admin/deploy*` redirigono alla dashboard con un avviso e le `POST` rispondono `403`.
### Documentation
* manuale operatori aggiornato: pannello Deploy esclusivo dello Staging, deduplicazione del webhook e riepilogo dell'ultimo deploy; `.env.example` con `DEPLOY_HOOK_DEDUPE_SECONDS`.
## [0.9.0](https://github.com/simograndi/taskman/compare/taskman-v0.8.0...taskman-v0.9.0) (2026-10-20)
### Features
* pool DB resiliente Neon con Flask-SQLAlchemy e `.env` consolidato ([d690ed7](https://github.com/simograndi/taskman/commit/d690ed71f2dd75e612d0b66428281ac75a26254f))
### Bug Fixes
* promozione e deploy supportano `PROD_DATABASE_URL` oltre alle variabili `PROD_PG*` ([13096ec](https://github.com/simograndi/taskman/commit/13096ec33dafbb314a09d07fb1bf6f101a08dab6))
* favicon dinamica in `templates/base.html`: la scelta tra `img/favicon_staging.ico` e `img/favicon_prod.ico` avviene con una condizione Jinja su `config.get('FLASK_ENV')`, con `FLASK_ENV` sincronizzato nella configurazione Flask a ogni render; i due file `.ico` restano in `static/img/` e non producono 404.
### Documentation
- Nuovi manuali `docs/MANUALE_PUBBLICO.md` (utenti, ambienti e SEO) e `docs/MANUALE_OPERATORI_ADMIN.md` (promozione Staging → Produzione, webhook Coolify, isolamento SEO).
- CHANGELOG riordinato in una cronologia sequenziale da `0.4.4` in poi (`0.5.0`, `0.6.0`, `0.7.0`) e `VERSION` portato a `0.9.0`.
## [0.8.0](https://github.com/simograndi/taskman/compare/taskman-v0.7.0...taskman-v0.8.0) (2026-10-20)
### Features
* aggiungi protezione CSRF per le richieste POST ([d69b9ce](https://github.com/simograndi/taskman/commit/d69b9ceafcd0f359bba69333d27fc8f0f3141cf6))
* autenticazione Bearer del token API Coolify nell'hook di deploy ([a9b355a](https://github.com/simograndi/taskman/commit/a9b355a82e1149aad4a2d5d72337f38098af6c1e))
* blocco modifica su richieste prese in carico, completamento nominativi e avviso Nomi Modificati ([9c0aab6](https://github.com/simograndi/taskman/commit/9c0aab62f03ef6e00c184d36a9dd132dfbea75e2))
* configura modal debug e asset Eventix ([ab96429](https://github.com/simograndi/taskman/commit/ab96429fea1fad158f366e23089a027bbaa44064))
* elenco e gestione biglietti con allegati PDF e codici (solo admin) ([35e7d24](https://github.com/simograndi/taskman/commit/35e7d24db52672069466bdb193832aa581ac25b8))
* gestione SEO dinamica (robots.txt, sitemap.xml, X-Robots-Tag, canonical) ([51b3406](https://github.com/simograndi/taskman/commit/51b3406b89140d8814057fb9310ca211c88826c9))
* introduzione connection pool, compressione web, indici di performance e manuali funzionali aggiornati ([fbe4b47](https://github.com/simograndi/taskman/commit/fbe4b4776710022cc365885999748602f4679177))
* operazioni massive, e refactoring responsive PWA ([b358950](https://github.com/simograndi/taskman/commit/b35895077ef1783108ba1d1c1c500be7489e41a6))
* ricerca globale header e filtri live nelle viste di gestione ([1699a98](https://github.com/simograndi/taskman/commit/1699a985e70c80be62d411474477a03af9ec34ae))
* tracciamento nomi biglietti/pass con contatore e avviso duplicati per admin ([22eb410](https://github.com/simograndi/taskman/commit/22eb410977491ef55cde5c0ec6a8eb4ff1e08cef))
### Bug Fixes
* admin e simone mantengono ruolo admin + permesso superuser ([3e95b55](https://github.com/simograndi/taskman/commit/3e95b55133d8ffff2fc7c18c56c316f141c9fa29))
* appunti architettura e manuale per contatti multipli e nome-citta ([dab9dae](https://github.com/simograndi/taskman/commit/dab9dae8d41c93a5730ff3d8ed8c7becc4555a7c))
* cursor already closed in gestione eventi ([fc01b8b](https://github.com/simograndi/taskman/commit/fc01b8bf989bf0104f4649d0e4365140235e79cb))
* data evento nei risultati della ricerca globale ([a5b5848](https://github.com/simograndi/taskman/commit/a5b58487457683572432b977d1b5c0146d396343))
* delivery e new event, overview ([17a85af](https://github.com/simograndi/taskman/commit/17a85afa5e8fbc3ef95fd360d6b9a799edc2f204))
* deploy limitato al ruolo superuser (ex superadmin hardcoded) ([088f2d6](https://github.com/simograndi/taskman/commit/088f2d620d4e311c77e571d2a5d6812bd60f4196))
* deploy superuser mantiene permessi admin (role invariato) ([5683bc5](https://github.com/simograndi/taskman/commit/5683bc57f56cbafc4daf99b594ea1106b513c98c))
* disattiva debug Flask di default e proteggi l'avvio locale ([83baac2](https://github.com/simograndi/taskman/commit/83baac2a00f2efaa112752490c0f1a5aa60edbfb))
* elenchi ([e0168b4](https://github.com/simograndi/taskman/commit/e0168b43115b947f2aefe6ade4359ed5f56a0313))
* gestione e modifica richieste ([a08472c](https://github.com/simograndi/taskman/commit/a08472c9d3e2b4fe2cf2472c4f4f7e28ca35f94f))
* Gestione Utenti ([6352c6a](https://github.com/simograndi/taskman/commit/6352c6a28e81ad078f1ff2a22daa0597932a88c1))
* Heathcheck ([c0a2e23](https://github.com/simograndi/taskman/commit/c0a2e238568462b74b03b35a9713210e70e7d010))
* le modali di creazione rapida non chiudono piu il form evento ([72782c8](https://github.com/simograndi/taskman/commit/72782c895e13df13d8f40f3043e3bebe3d8db506))
* login, user, richieste ([9b4b6e9](https://github.com/simograndi/taskman/commit/9b4b6e9ba97fcd99f2b32695b0b19915d8415baf))
* Notifiche ([04e5b48](https://github.com/simograndi/taskman/commit/04e5b48da0434369ee4c4cc9f204a8d0723761b2))
* nuova richiesta ed altri bug ([074f062](https://github.com/simograndi/taskman/commit/074f062dcd3931911a0430e1841e1b560f8030db))
* organizzatori/eventi ([2963d91](https://github.com/simograndi/taskman/commit/2963d91275d950c3246d4df2340bdb1af91a5d06))
* piattaforma di vendita ([3a6dd72](https://github.com/simograndi/taskman/commit/3a6dd7291c44ae5956cae1f7bf0f3bcc216847bb))
* postimultipli e gestione ([316fa0a](https://github.com/simograndi/taskman/commit/316fa0a5306a4a81009438f333d28561d0860fc5))
* ricerca dinamica ([4c8e5cb](https://github.com/simograndi/taskman/commit/4c8e5cba8d88f4eed490ce3fb550ec4be9f7867d))
* ricerca e campi obbligatori ([8bf7932](https://github.com/simograndi/taskman/commit/8bf7932579d7642715dcaef2297509a865facb35))
* ricerca luoghi e artisti ([5da7e58](https://github.com/simograndi/taskman/commit/5da7e584ecb081f6783b1dacd42b32fc665920c6))
* rotte admin e ruolo effettivo per i superadmin, deploy separato dai dati ([1c50850](https://github.com/simograndi/taskman/commit/1c50850def429b80475186d4bbc3390e82480be5))
* senza nome ([d3607ca](https://github.com/simograndi/taskman/commit/d3607ca613898eba44454cfe7d7860d8a3c15d98))
* usa la strategia release-please simple per aggiornare VERSION e CHANGELOG ([f471436](https://github.com/simograndi/taskman/commit/f4714360891fba08632e0cdd6dd19646a0853f9a))
* **Hook di deploy Coolify: risolto l'errore HTTP 401 Unauthorized**. La chiamata a `PROD_DEPLOY_HOOK_URL` usa `requests.post` con l'header `Authorization: Bearer <COOLIFY_API_TOKEN>` e `Content-Type: application/json`; la risposta viene verificata (HTTP 200/201 = deploy avviato, 401/403 = token non valido o non autorizzato, altri status = errore) e ogni esito, compresi gli errori di rete, è riportato con un messaggio descrittivo nel backoffice senza eccezioni non gestite e senza esporre token o URL.
## [0.7.0] - 2026-10-20
### Added
- **Deploy codice separato dai dati**: `POST /admin/deploy/run` esegue solo migrazioni schema prod, merge GitHub `develop` → `main` e hook (mai dati). Promozione dati sempre selettiva: per singolo record (`publish`/`remove`/`demo`) o bulk per entità (`POST /admin/deploy/data` → `promotion.sync_entities`), mai utenti/richieste/biglietti/note/notifiche/impostazioni.
- **Gestione per singolo record** (eventi, luoghi, organizzatori): pulsanti *Pubblica / Aggiorna in Produzione* e *Rimuovi dalla Produzione*, flag **demo** (esclusi dalla produzione) e stato `PENDING_DELETION` (rimozione applicata subito se la produzione è raggiungibile, altrimenti marcata in attesa), con badge *Demo*, *In produzione* e *Rimozione in attesa*. Nuove colonne `is_demo` e `sync_status` (e `sync_uid` sugli eventi).
### Fixed
- **Rotte admin e ruolo per i superadmin**: `/events`, `/locations` e `/organizers` (e le altre rotte admin) tornano a funzionare per `@admin` e `@simone`: i superadmin hanno ruolo `admin` + flag `is_superuser`, la sessione ne normalizza il ruolo effettivo a `admin` e i template usano il ruolo effettivo invece del solo `session['role']` (menu admin completo, rotte e badge corretti). Il menu mostra il badge scudo **Superadmin** e la lista utenti la colonna di stato.
- **Fix 500 su `/events`, `/locations`, `/organizers`**: `record_sync_info` non solleva mai 500 (catch-all + rollback protetto), `get_db_connection()` ricrea connessioni chiuse/rotte del pool e le viste non chiudono più la connessione prima del render (rilascio al teardown).
## [0.6.0] - 2026-10-19
### Added
- **Gestione SEO dinamica**: rotta `/robots.txt` che in produzione consente l'indicizzazione pubblica (`Allow: /`, blocca `/admin/`, `/operator/`, `/api/`, dichiara la sitemap) e in sviluppo blocca tutto (`Disallow: /`), con mimetype `text/plain`.
- Header HTTP `X-Robots-Tag: noindex, nofollow, noarchive` automatico in ambiente di sviluppo (`FLASK_ENV=development`) tramite `@app.after_request`.
- Meta tag `<meta name="robots">` dinamico e tag `<link rel="canonical">` sempre puntante al dominio ufficiale di produzione (`PROD_DOMAIN`) in `templates/base.html`.
- Rotta `/sitemap.xml` dinamica (mimetype `application/xml`): in produzione espone le pagine pubbliche, in sviluppo restituisce HTTP 404.
- Variabile d'ambiente `PROD_DOMAIN` (default `eventix.work`) per URL canonici e sitemap; documentata in `.env.example`.
- Test `tests/check_seo.py` per `/robots.txt`, `/sitemap.xml`, `X-Robots-Tag`, meta robots e canonical in entrambi gli ambienti.
- Nuova richiesta senza nominativi: i **nomi dei partecipanti sono facoltativi** all'inserimento (e nella modifica completa admin). La richiesta viene salvata anche senza nominativi con un avviso giallo; admin ed editor ricevono una notifica interna "Richiesta senza nominativi" (messaggio **URGENTE** se all'evento mancano meno di 5 giorni). Le richieste senza nominativi e con evento a **meno di 5 giorni** sono evidenziate con un alert rosso in dashboard, *Gestisci richiesta* e *Modifica richiesta*, con badge di richiamo negli elenchi.
- **Prompt di impegno sui nominativi (ruolo utente)**: inviando una nuova richiesta senza nominativi l'utente vede la finestra *Nominativi da comunicare* con la scadenza (5 giorni prima dell'evento) e deve spuntare *Accetto l'impegno* per procedere (l'invio è bloccato anche lato server); l'impegno è registrato sulla richiesta. Il richiedente riceve l'email di conferma con la scadenza e lo staff viene avvisato dell'impegno preso. Admin ed editor inviano senza prompt.
- **Promemoria automatici per i nominativi mancanti**: avvicinandosi la scadenza, richiedente e staff ricevono una email di promemoria e una notifica in campanella per le richieste ancora prive di nominativi; l'invio è idempotente (una sola volta per richiesta).
- **Richiesta presa in carico e completamento nominativi**: una richiesta è "presa in carico" quando almeno un biglietto/pass è *Emesso* oppure lo staff ha portato lo stato oltre *Ricevuta*. Da allora il richiedente non può più modificarla né eliminarla (403/404) e la pagina diventa **Richiedi una modifica**; può solo completare le schede senza nome o cognome (sezione *Completa i nominativi mancanti*, con telefono/email) oppure inviare una richiesta di modifica dal diario note.
- **Avviso "Nomi Modificati"**: il completamento dei nominativi attiva il nuovo flag `requests.names_modified`, mostra badge negli elenchi e alert in *Gestisci richiesta*/*Modifica richiesta* e riallinea i biglietti in `tickets` dei nominativi toccati, conservando gli allegati dei nomi invariati. L'avviso si azzera salvando la richiesta dalla gestione.
- **Eliminazione richiesta**: nuovo pulsante *Elimina* (con conferma) nella pagina di modifica e negli elenchi dashboard/evento. Gli admin eliminano qualsiasi richiesta, gli utenti solo le proprie non ancora prese in carico; la cancellazione rimuove a cascata partecipanti, diario note e biglietti. Il redirect accetta solo un `return_to` interno (anti-open-redirect).
### Changed
- **Debug Flask sicuro per default**: `FLASK_DEBUG` ha default `false` e l'avvio diretto con debug attivo e `HOST` non locale (es. `0.0.0.0`) viene rifiutato con un errore esplicito, per non esporre in rete il debugger Werkzeug; `python app.py` usa `HOST` con default `127.0.0.1` e `PORT` invariato. Variabili documentate in `README.md` e `.env.example`.
### Fixed
- Richieste senza nominativi: `add_request` e `update_request_full_admin` non bloccano più il salvataggio quando nome o cognome dei partecipanti non sono compilati; il referente scelto fra i partecipanti continua a richiedere nome, cognome e un recapito valido.
### Security
- **Protezione CSRF su tutte le azioni di modifica**: ogni richiesta `POST`/`PUT`/`PATCH`/`DELETE` deve inviare un token legato alla sessione — campo nascosto in tutti i form, header `X-CSRF-Token` per le richieste in background, campo `csrf_token` nel body JSON. Senza token valido la richiesta viene respinta prima di toccare il database o inviare email: `400 {"ok": false}` per gli endpoint `/api/*`, avviso e ritorno a una pagina sicura per le pagine normali. Il token è stabile per la durata della sessione, quindi i form aperti a lungo restano validi; accesso (`/login`) e primo inserimento password restano esenti in questa fase.
- **Cookie di sessione più protetti**: `SESSION_COOKIE_HTTPONLY=True` e `SESSION_COOKIE_SAMESITE="Lax"`; `SESSION_COOKIE_SECURE` non viene attivato automaticamente perché richiede HTTPS.
## [0.5.0] - 2026-10-11
### Added
- **Modulo di promozione dinamica staging → produzione** (solo superadmin): nuova pagina `/admin/deploy` (voce **Deploy** nel menu) che applica le migrazioni pendenti al DB di produzione, esegue il merge GitHub `develop` → `main` (se `GITHUB_TOKEN` e `GITHUB_REPOSITORY` sono configurati) e invoca `PROD_DEPLOY_HOOK_URL` per aggiornare il container. Senza variabili di produzione i passi vengono saltati con un avviso, senza errori HTTP 500; un errore su un passo interrompe quelli successivi. La sincronizzazione dei dati è separata e selettiva (vedi *Deploy codice separato dai dati*).
- Doppia connessione DB: variabili `PROD_PGHOST`, `PROD_PGDATABASE`, `PROD_PGUSER`, `PROD_PGPASSWORD`, `PROD_PGPORT`, `PROD_PGSSLMODE` (e opzionale `PROD_PGCHANNELBINDING`) per la produzione; supportato anche `PGPORT` per lo staging. Il deploy è rifiutato se produzione e staging puntano allo stesso database.
- **Sistema ruoli superadmin**: flag `users.is_superuser` che eredita tutti i permessi `admin` più il deploy; `initialize_database()` imposta ruolo `admin` + `is_superuser = TRUE` per `@admin` e `@simone` (incluso il legacy `role = 'superuser'`). La sessione normalizza i superadmin a ruolo effettivo `admin` (login, set-password, refresh con init schema anticipato), così rotte e menu admin funzionano; badge scudo **Superadmin** in header e lista utenti; deploy e promozione riservati ai superadmin (`@superuser_required`, 403 per gli altri).
- **Badge ambiente e favicon dinamica**: in base a `FLASK_ENV` l'interfaccia mostra *Staging Mode* (`development`) oppure *Official* e carica `favicon_staging.ico` o `favicon_prod.ico`.
- Test `tests/check_promotion.py` (accessi, fallback sicuro, merge/hook simulati, badge/favicon, perimetro delle tabelle sincronizzate).
- Elenco e gestione biglietti (solo admin): nuova voce **Biglietti** nel menu con tutti i nominativi tracciati — evento e data, richiedente, stato richiesta, stati biglietto/pass e zona, badge allegato, registrazione e il contatore "N richieste" sotto al nominativo — con filtro live e scheda **Dettaglio** con link a *Gestisci richiesta*; dalla scheda si aggiornano **link PDF** e **codice biglietto** (il caricamento file PDF è predisposto ma al momento disabilitato) e la tipologia di invio dell'evento è mostrata come riferimento. Il riallineamento con la richiesta ora conserva gli allegati: risalvare non azzera più PDF e codici inseriti.
- Creazione rapida "in overlay" di Luogo e Organizzatore dal form evento (nuovo e modifica): i pulsanti **+** aprono una modale che salva **senza ricaricare la pagina** tramite i nuovi endpoint JSON `POST /api/locations` e `POST /api/organizers`; il record creato viene aggiunto e selezionato automaticamente nella tendina e i dati già inseriti nell'evento restano compilati.
### Changed
- Recapiti del referente esterno riusati senza doppio inserimento: se il referente/richiedente esterno coincide (confronto normalizzato di nome e cognome) con un partecipante, i recapiti già indicati vengono copiati automaticamente sui campi vuoti della scheda partecipante, sia lato server sia in tempo reale nei form.
## [0.4.4] - 2026-10-05
### Changed
- Eventi con più contatti organizzatore: selettori ripetibili e ricercabili, senza duplicati, filtrati per organizzatore. Il cambio organizzatore azzera le selezioni; modifica e duplicazione conservano l'ordine. Elenco e dettaglio mostrano tutti i contatti (con recapiti nel dettaglio).
- Etichette evento uniformate a **Nome — Città** in elenchi, dashboard, richieste e suggerimenti di ricerca; senza città rimane il solo nome.
- Inizializzazione database: tabella `event_organizer_contacts` e recupero idempotente dei contatti legacy validi, mantenendo il primo contatto in `events.organizer_contact_id` per compatibilità.
### Fixed
- Notifiche (alert) rivisti: risolto il bug `psycopg2.InterfaceError: cursor already closed` in gestione richiesta (il controllo "evento passato" riusava il cursore dopo `cur.close()`, quindi la notifica di emissione non partiva mai e scattavano retry/500); confronto date unificato su `datetime.now()`; anti-duplicato interno (stessa notifica allo stesso utente entro 10 minuti, un solo inserimento per utente anche se i ruoli si sovrappongono) e anti-toast-doppio nel frontend (stessa pagina + altre tab via `localStorage`): niente più "4 notifiche uguali".
- Alert visivi coerenti: giorni rimanenti calcolati a giorno (`event.date - oggi`), non a ore — "Domani" resta domani fino a mezzanotte, "Oggi" solo il giorno stesso, evento passato marchiato **Scaduto** (mai "Oggi"/giorni negativi); richieste da gestire con badge graduato (grigio da 1 g, giallo da 3 g) e riga `table-warning` da 1 g; elenco eventi con badge **Scaduto · data** anche nel filtro "Tutti".
### Added
- Tracciamento nomi richiesti (visibile **solo agli admin**): nuova tabella `tickets` che registra ogni nominativo per cui vengono chiesti biglietti/pass (una riga per richiesta e nome normalizzato, con i campi riservati `pdf_url`/`ticket_code` predisposti ma non ancora usati per allegare PDF o codice). Se un admin immette un nome già presente riceve un **avviso giallo** ("Nome già richiesto in precedenza…") con il numero di richieste precedenti e il contatore viene incrementato; in *Gestisci richiesta* e nella *Modifica richiesta completa* sotto al nome compare in piccolo **quante richieste di biglietti/pass sono state fatte** per quella persona. Mentre digiti nome e cognome nei form compaiono avvisi a comparsa con i nomi già richiesti o simili (`GET /api/tickets/check`, solo admin); i flash ora distinguono il colore per categoria (giallo per gli avvisi, rosso per gli errori).
- Ricerca globale nell'header (`GET /api/search?q=`): campo centrale con risultati live raggruppati (Eventi, Richieste, Luoghi, Organizzatori, Utenti) e link diretti; lo scope rispetta il ruolo (gli user vedono solo eventi attivi futuri e proprie richieste). I risultati di eventi e richieste mostrano anche la data dell'evento (gg/mm/aaaa hh:mm) per distinguere gli omonimi.
- Filtro dinamico in tempo reale in ogni vista di gestione (richieste in dashboard e dettaglio evento, eventi, luoghi, organizzatori, utenti): campo al centro della barra titolo, conteggio “N totali” aggiornato al volo e messaggio di nessun risultato; i link globali a luoghi/organizzatori/utenti precompilano il filtro via `?q=`.
- Gestione richiesta: il blocco di aggiornamento in massa è ora **"Applica a selezione"**: ogni partecipante ha un flag di selezione con casella **"Tutti"** nell'intestazione della tabella; gli stati biglietti/pass (e la nuova **zona biglietto**) si applicano solo ai selezionati, con avviso se nessuno è spuntato.
- Gestione richiesta: quando un biglietto viene messo in stato **Emesso** compare la scelta della **tipologia di posto** (zona, es. Platea, Tribuna numerata) presa dalla scheda evento, con posti disponibili indicati; la zona è salvata per partecipante (`request_participants.ticket_zone`, conservata anche nelle modifiche complete admin e azzerata se il biglietto non è più emesso). Applicabile anche in massa ai selezionati.
- Dettaglio evento (admin/editor): **overview sempre visibile** con biglietti totali, disponibilità residua (colorata a semaforo), richieste da evadere e numero di zone ripartite.
- Eventi: **Zone e biglietti multipli** (`events.ticket_zones`, es. Posto unico, Platea, Tribuna numerata): nel form nuovo/modifica evento ogni riga è *zona + posti*; scegli una zona e compare automaticamente una nuova riga, senza duplicati. Il default è **"Posto unico"**. La **somma dei posti delle zone deve coincidere con i posti totali** (che fanno fede per semaforo e disponibilità): in caso di scostamento compare un avviso con le azioni **"Aggiorna totale"** (allinea il totale alla somma) e **"Rivedi posti"** (resta da correggere, l'invio è bloccato finché non coincidono); il server valida di nuovo e avvisa con un flash. La **capienza del luogo è solo un riferimento visualizzato, mai compilata in automatico**. Zone e posti sono mostrati nel dettaglio evento; la duplicazione evento li copia. Elenco zone e **sinonimi** (righe `Canonico = alias1, alias2`, usati per normalizzare i nomi in fase di importazione) configurabili in *Impostazioni* (`ticket_zones_list`, `ticket_zone_synonyms`).
- Gestione utenti: il form "Nuovo utente" (solo admin) include i campi **Telefono** e il flag **Richiesta pass** (gli admin sono sempre abilitati e l'elenco mostra telefono e permesso pass di ogni utente); **solo gli admin gestiscono i pass**: nella gestione richiesta gli altri ruoli vedono categoria e stato pass in sola lettura (niente flag, selettori o applicazione in massa) e il server ignora comunque i campi pass eventualmente inviati dal form.
- Gestione utenti rifatta: l'elenco `/users` è ora una tabella di sola lettura (anagrafica, contatti, ruolo, permesso pass) e ogni persona ha una pagina di modifica dedicata `/users/<id>/edit`; gli admin gestiscono username, ruolo, permesso pass e possono **reimpostare la password**, gli editor modificano solo anagrafica e contatti.
- Profilo personale `/profilo` per tutti i ruoli (raggiungibile dal menu del proprio nome): dati personali e avatar, cambio password con verifica della password attuale e **preferenze di notifica individuali** (nuove colonne `users.notify_email` e `users.notify_browser`): disattivando le email non si ricevono più le notifiche di sistema, disattivando gli avvisi a comparsa non compaiono più i toast (campanella e contatore restano attivi).
- Eventi: campo **Piattaforme di vendita** (`events.sales_platform`, es. Botteghino, Sito Web Ufficiale, TicketOne, Vivaticket), **una o più per evento**: alla creazione e in modifica evento una nuova riga di selezione compare automaticamente dopo la scelta, senza duplicati. Elenco configurabile `sales_channels_list` nelle Impostazioni; mostrato nel dettaglio evento e nella pagina di modifica richiesta.
- Modifica richiesta (admin): il richiedente registrato si seleziona con un combobox ricercabile con voce "Esterno"; se il richiedente e indicato come referente, i recapiti (telefono/email) vengono compilati automaticamente dai dati utente, con fallback lato server.
- Impostazioni: nuovo elenco "Piattaforme di vendita" (`sales_channels_list`) modificabile dagli admin.
- Eventi: **Artista**, **Città** e **Luogo** (testo libero di fallback) funzionano come gli altri campi di ricerca: mentre digiti propongono gli artisti già usati negli altri eventi, le città già usate e i nomi dei luoghi esistenti, per evitare doppioni e varianti dello stesso nome; resta possibile inserire un valore nuovo.
- Eventi: gli admin possono **duplicare** un evento dal pulsante "Duplica" in `/events` (`POST /events/<id>/duplicate`): copia senza richieste (nome + " (copia)", luogo, organizzatore, consegna, piattaforme e responsabili); la copia nasce non attiva da verificare/attivare.
## [0.4.3] - 2026-09-15
### Fixed
- Gestione richiesta: sistemato il layout della tabella partecipanti — niente più campi sovrapposti o tagliati: la pagina usa tutta la larghezza disponibile, ogni colonna ha una larghezza minima (Partecipante, Contatti, Biglietto, Pass), i contatti sono impilati senza schiacciare la riga, i select ricercabili compatti non vengono più coperti dalle icone di freccia e di reset e la tabella scorre in orizzontale sui display stretti. Il blocco "Applica a selezione" non comprime più select e pulsante (vanno a capo) e passa a tre colonne solo da schermi `lg`.
### Added
- Gestione richiesta (admin/editor): **disponibilità residua visibile** durante la selezione e l'assegnazione del posto. In cima alla pagina i badge posti totali / richiesti / disponibili (a semaforo come nel dettaglio evento) e, per gli eventi con più zone, i **posti liberi per zona** con la zona esaurita evidenziata. In ogni riga la tendina della tipologia di posto mostra i posti liberi della zona e, sotto la scelta, compare l'avviso "Posti liberi in …: N su M" (oppure "Zona esaurita"), aggiornato al volo quando si cambia stato biglietto o zona; anche il selettore in massa "Zona biglietto dei selezionati" riporta i posti liberi di ogni zona.
## [0.4.2] - 2026-09-14
### Fixed
- Login: il nome utente non è più sensibile alle maiuscole/minuscole — accetta ad esempio `Admin`, `ADMIN` o `aDmin` per l'utente registrato come `admin` (la password resta sensibile alle maiuscole).
- Homepage e filtro richieste: corretto il filtro "Archiviate", che passava `status=Archiviate` invece del valore realmente salvato nel database (`Archiviata`), quindi la selezione non mostrava alcuna richiesta anche quando una richiesta archiviata esisteva.
- Homepage (admin/editor): aggiunto il pulsante **Archivia** accanto alle richieste concluse (evento già passato o già archiviate) per portarle direttamente a stato `Archiviata`.
- Gestione richiesta: l'archiviazione (o l'aggiornamento) di una richiesta per un evento **già passato** non invia più nessuna notifica (né email né notifica interna di emissione biglietti/pass).
- Nuova richiesta (admin): selezionando un richiedente dall'elenco utenti, il nominativo e i recapiti (telefono/email) del referente (se il destinatario è il richiedente) vengono compilati automaticamente; inoltre il form non richiede più il reinserimento dei recapiti del richiedente esterno quando questi sono già indicati nella sezione Richiedente.
- Nuova richiesta (admin): corretto il salvataggio del richiedente selezionato dall'elenco utenti: prima il nome veniva copiato nel campo del richiedente esterno e il server lo trattava come esterno perdendo il collegamento all'utente; ora `requested_by` ha priorità sul nome, quindi il richiedente registrato viene associato correttamente all'utente e i recapiti del referente sono presi dal suo profilo.
- Nuova richiesta (admin): corretta la compilazione/ripristino dei dati del primo partecipante quando la validazione del form riporta di nuovo i campi partecipanti dopo un errore.
- Homepage e elenco eventi: precisato che la home mostra gli eventi futuri; un evento futuro prossimo (es. "Ligabue - Jesolo" del 12/09/2026) appare normale e non grigio perché non ancora scaduto; lo sfondo grigio/evidenziazione per scaduti vale sugli elenchi completi o quando filtrati per eventi passati.
- Gestione eventi e modifica evento: risolto l'errore `psycopg2.InterfaceError: cursor already closed` (le impostazioni venivano lette dopo la chiusura del cursore).
- Modifica richiesta: rimosso l'`id` duplicato del campo Note nel form completo.
- Notifiche: corrette le regole di invio — l'emissione di biglietti/pass notifica l'utente richiedente (o il referente invio per richiedenti esterni), le nuove note notificano anche l'utente richiedente oltre ad admin/editor, l'autore non riceve la notifica della propria azione e le richieste di modifica su biglietti emessi avvisano admin/editor.
- Gestione luoghi: corretto il disallineamento tra query e template — la tabella mostrava città/capienza/contatti nei campi sbagliati e i pulsanti "Sito web", "Mappa" e "Planimetria" puntavano a campi diversi; nei form di creazione e modifica manca la **Città** (che veniva azzerata a ogni salvataggio) e la tipologia selezionata non risultava mai corretta: ora campi e indici sono allineati e l'ordine è Nome, Indirizzo, Città, Tipologia, Capienza, Sito web, Mappa, Planimetria, Contatto, Email, Telefono.
- Dashboard: le richieste concluse (evento già passato o archiviate) sono spostate in fondo all'elenco ed evidenziate con sfondo grigio, come per gli eventi passati.
- Elenco eventi: con il filtro "Tutti" gli eventi già terminati (data passata) sono spostati in fondo all'elenco ed evidenziati con sfondo grigio, in linea con le richieste concluse in dashboard.
## [0.4.1] - 2026-09-09
### Added
- Eventi: tipologia di invio biglietti (`events.delivery_type`: invio digitale, invio cartaceo, ritiro in cassa accrediti, codice, altro) con note aggiuntive libere (`events.delivery_notes`), selezionabile alla creazione e modificabile dagli admin; mostrata nel dettaglio evento e nella creazione richiesta quando si seleziona l'evento.
- Gestione eventi: il form di creazione evento non è più sempre visibile in alto ma si apre da un pulsante "Nuovo evento" in una finestra modale.
- Gestione richiesta: aggiornamento in massa degli stati dei biglietti (e dei pass) di tutti i partecipanti con un solo clic.
- Ottimizzazione layout mobile: page-heading impilabile, tabelle e form più compatti su smartphone.
## [0.4.0] - 2026-09-09
### Added
- Richieste: scelta del destinatario di biglietti e pass (richiedente o uno dei partecipanti referente). Il referente deve avere nome, cognome e almeno un recapito validato (telefono o email). Possibile scegliere come richiedente anche utenti admin/editor, non solo user.
- Note delle richieste salvate come diario/log (`request_notes`): ogni inserimento viene aggiunto con autore, ruolo e data. Gli utenti modificano le richieste non emesse; dopo l'emissione inviano una richiesta di modifica agli amministratori.
- Modifica completa della richiesta riservata agli admin: richiedente (anche esterno), radio/cliente, quantita, partecipanti, destinatario invio.
- Eliminazione eventi e luoghi (solo admin, con conferma): la cancellazione e bloccata se esistono richieste/collegamenti.
- Luoghi: campi edit/delete ripristinati con piano di modifica completo (anche link mappa dedicato).
- PWA: `manifest.webmanifest`, icone dedicate e meta tag; il titolo delle pagine ora e `Eventix | pagina`.
### Fixed
- Pulsanti modifica/elimina uniformati (dimensioni identiche) su organizzatori, eventi e luoghi.
- "Apri mappa" dell'evento usa il link mappa del luogo oppure Google Maps dall'indirizzo (prima inviava al sito web).
- Impaginazione lista eventi e form richiesta su smartphone (layout non piu sovrapposto).
## [0.3.2] - 2026-09-09
### Fixed
- Sistemata la ricerca dinamica: i campi ricerca/combo non visualizzano più tutti i record sovrapposti, ma si comportano con una ricerca dinamica con autocomplete (ricerca per sottostringa, anche parte di una parola all'interno della frase). Aggiunto pulsante freccia per visualizzare tutte le opzioni disponibili. Applicato a tutti i campi di questo tipo nell'app.
## [0.3.1] - 2026-09-02
### Fixed
- Risolto il conflitto nell'uso contemporaneo dello stesso file di versione/changelog e verificata la correttezza complessiva delle funzionalità e dei test.
## [0.3.0] - 2026-09-02
### Fixed
- Corretta la gestione dei contatti dell'organizzatore nella creazione e modifica degli eventi, garantendo che vengano mostrati e associati correttamente solo se l'organizzatore è selezionato.
### Added
- Gestione anagrafiche organizzatori e contatti associabili agli eventi.
- Aggiornamento dei luoghi registrati con il link al sito web in sostituzione del link mappa.
## [0.2.0] - 2026-09-01
### Added
- Menu operativo permanente per ogni ruolo.
- Gestione eventi, partecipanti, disponibilita e impostazioni.
- Impersonificazione utenti riservata agli amministratori.
- Credits con dati sviluppatore e cronologia delle versioni.
- Anagrafiche organizzatori, relativi contatti e aggiornamento gestione luoghi con sito web.
## [0.1.0] - 2026-09-01
### Added
- Gestione ruoli, eventi e richieste.
- Area amministrativa per utenti ed eventi.
Usiamo solo cookie tecnici necessari al funzionamento e alla sicurezza del portale. Nessuna profilazione o marketing. Cookie Policy · Privacy Policy