# DD-029 — Cancellare un evento pulisce a cascata i dati collegati **Data:** 9 settembre 2026 **Stato:** Accettata **Contesto** Un audit del modulo Obiettivi ha verificato che gli obiettivi in sé non hanno bisogno di nessuna pulizia quando un evento viene cancellato: sono ricalcolati a runtime sull'elenco eventi corrente (`obiettivi.ts`), quindi un evento sparito da `eventi_app` semplicemente smette di contare. Il problema è un livello sotto: `useEliminaEvento()` (`src/lib/eventi.ts`) cancella solo la riga in `eventi_app`. Nessuna delle otto tabelle collegate (`risposte_presenze`, `cacche_partita`, `mvp_voti`, `pagelle_voti`, `badge_social_voti`, `turni_palloni`, `scout_sessioni`, `scout_live`, `scout_partite`) ha mai avuto una foreign key verso `eventi_app(id)`: le loro righe restavano orfane a database. Oggi è innocuo per le statistiche, perché nessun calcolo legge quelle tabelle se non partendo dall'elenco eventi corrente. Ma è un rischio latente: un id evento futuro identico a uno passato (generato come `"e" + timestamp`, collisione improbabile ma non impossibile) erediterebbe dati vecchi non suoi; e un calcolo futuro che iterasse direttamente una di quelle tabelle, invece di partire da `eventi_app`, conterebbe anche le righe orfane. **Decisione** Migration `m14_pulizia_dati_evento_cancellato`: un trigger `AFTER DELETE ON eventi_app` cancella a cascata le righe corrispondenti (`evento_id`/`match_id = id evento cancellato`) nelle otto tabelle collegate, tramite una funzione `SECURITY DEFINER`. Agisce a database, non in `useEliminaEvento()`: protegge anche chi cancella un evento scrivendo direttamente su PostgREST, non solo chi passa dal bottone dell'app. **Alternative scartate** - Foreign key con `ON DELETE CASCADE` verso `eventi_app(id)` → non applicabile subito: `mvp.md` documenta che storicamente `match_id` in `mvp_voti`/`pagelle_voti`/ `badge_social_voti` a volte conteneva l'id di una sessione Scout o di una partita CSI, non l'id evento CrAPP. Un vincolo FK avrebbe rifiutato la migration alla prima riga storica disallineata; il trigger non valida i dati esistenti, solo le cancellazioni da qui in avanti. - Cancellazione manuale nelle otto tabelle dentro `useEliminaEvento()` → fragile: va tenuta aggiornata a mano ogni volta che un nuovo modulo aggiunge una tabella con `evento_id`, e non protegge chi scrive/cancella direttamente su PostgREST. - Bonificare anche le righe orfane già esistenti nella stessa migration → rimandato: tocca dati reali già scritti, è un intervento più delicato che merita una migration a sé, non urgente perché quelle righe sono già invisibili a ogni calcolo attuale. **Conseguenze** - Da questa migration in poi, cancellare un evento (da qualunque punto, app o REST diretto) ripulisce automaticamente tutte le tabelle collegate. - Le righe orfane generate da cancellazioni **precedenti** a questa migration restano nel database: il trigger previene il problema da qui in avanti, non ripulisce lo storico. - `test/integration/pulizia-evento.test.ts` è la definizione eseguibile del comportamento: scrive una riga in ciascuna delle otto tabelle, cancella l'evento e verifica che spariscano tutte. **Aggiornamento (9 settembre 2026, stesso giorno)** — la bonifica dello storico prevista sopra come "riesame" è stata fatta subito dopo, migration `m15_bonifica_dati_evento_orfani`: righe orfane in `risposte_presenze`, `cacche_partita`, `turni_palloni`, `scout_sessioni`, `scout_live`, `scout_partite` identificate confrontando `evento_id` con `eventi_app`. Per `mvp_voti`/`pagelle_voti`/`badge_social_voti` il confronto si applica **solo** ai `match_id` nel formato id evento CrAPP (`^e[0-9a-z]+$`, quello di `nuovoIdEvento()`): i vecchi voti su id Scout (prefisso `s` + timestamp decimale) o CSI (numerico o `data-squadra-squadra`) non sono orfani, sono dati storici legittimi mai collegati a un evento CrAPP (vedi contesto sopra), e la migration non li tocca. Verificato manualmente sul database locale prima di applicarla: un voto di test su id Scout è sopravvissuto alla bonifica, un voto di test su id evento CrAPP orfano è stato rimosso. **Aggiornamento (9 settembre 2026)** — la verifica manuale di M15 non lasciava nessuna rete di sicurezza automatica per il futuro, a differenza del resto del progetto (DD-020). Migration `m16_funzione_bonifica_dati_evento_orfani` rende lo stesso corpo una funzione `bonifica_dati_evento_orfani()` (riservata al `service_role`, non richiamabile dall'app), coperta da `test/integration/bonifica-evento.test.ts`: inserisce una riga orfana e una storica su id Scout/CSI, richiama la funzione via RPC e verifica che tocchi solo la prima. Non serve richiamarla di nuovo ora (M15 ha già ripulito lo storico): resta pronta come intervento di manutenzione se in futuro il trigger di M14 smettesse di funzionare o emergesse un altro batch di orfani. **Riesame** Se una nuova tabella collegata a un evento non viene aggiunta al trigger quando creata (va aggiornata a mano, non c'è un meccanismo che lo forzi).