Allinea la documentazione al codice: migration, roadmap e moduli mancanti

Emerso da un audit doc↔codice: PROJECT_STATE.md era fermo a M12 (23 migration)
mentre supabase/migrations/ ne ha 27, fino a M16; ROADMAP.md non citava MVP,
Turno palloni, Infortuni e Profilo Giocatore come voci a sé pur essendo tutte
implementate; profilo-giocatore.md era l'unico modulo senza l'intestazione
Stato/File principali degli altri.

Aggiunge anche le due spec mancanti in docs/modules/: Squadra (anagrafica,
useRosa/useAnagraficaRosa, gestione admin, classifica interna) e Calendario ed
Eventi (vista mensile vs gestione admin, pulizia a cascata alla cancellazione).

Corregge inoltre la nota sul versionamento in CHANGELOG.md: sempre a tre cifre
(x.y.z), mai x.y.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This commit is contained in:
2026-09-09 13:31:39 +02:00
co-authored by Claude Sonnet 5
parent 5569d08b59
commit 555d4723a9
6 changed files with 240 additions and 127 deletions
+28 -6
View File
@@ -1,6 +1,6 @@
# Project State
Ultimo aggiornamento: 06/09/2026
Ultimo aggiornamento: 09/09/2026
## Stato generale
@@ -10,7 +10,9 @@ Backend migrato al nuovo Supabase proprietario. Autenticazione Google, dashboard
amministratore e Profilo Giocatore (lato giocatore e lato admin) sono in produzione su `main`.
Foto profilo (M6) e Scout Live (M7) non dipendono più da `localStorage`: entrambi ora
sincronizzano tra dispositivi tramite Supabase. Le serie di presenze sono calcolate sui dati
reali (M9).
reali (M9). Prima versione pre-release rilasciata (0.9.0, vedi `docs/CHANGELOG.md`). Cancellare
un evento pulisce ora a cascata tutte le tabelle collegate (M14) e le righe orfane da
cancellazioni precedenti a M14 sono state bonificate una tantum (M15/M16).
---
@@ -21,7 +23,8 @@ reali (M9).
- Cursor e Claude Code come ambienti di sviluppo
- Vercel configurato; Environment Variables aggiornate al nuovo Supabase (Preview e Production)
- Supabase proprietario attivo — Project Ref: `kfkcldwncxqaixetsjes`
- 23 migration in `supabase/migrations/`, fino a `m12_niente_autovoto`
- 27 migration in `supabase/migrations/`, fino a `m16_funzione_bonifica_dati_evento_orfani`
(09/09/2026)
- Sviluppo locale verificato con il nuovo Supabase
---
@@ -36,8 +39,9 @@ reali (M9).
## Database
- Schema v1.0 e migration da M1 a M12 applicate al nuovo Supabase (`m12_niente_autovoto`
in produzione dal 06/09/2026, verificata con `npx supabase migration list`)
- Schema v1.0 e migration da M1 a M16 applicate al nuovo Supabase
(`m16_funzione_bonifica_dati_evento_orfani` in produzione dal 09/09/2026, verificata con
`npx supabase migration list`)
- `public.giocatori_squadra`: rosa iniziale di 17 giocatori (migration `m5_email_giocatori_squadra`)
più quelli aggiunti da `/admin` a stagione in corso; da settembre 2026 tutti i giocatori
attivi hanno l'email registrata (colonna `email`, DD-018), impostabile da `/admin` senza
@@ -61,6 +65,19 @@ reali (M9).
scoutate concluse, e collegamento della tabella `scout_sessioni` (già presente nello
schema ma mai usata) al blocco condiviso dello Scout Live — prima entrambi vivevano solo
in `localStorage`, quindi visibili a un solo dispositivo
- Migration `m13_convocati_e_pagelle_chiuse`: le RLS di `mvp_voti`/`pagelle_voti`/
`badge_social_voti` richiedono che votante e votato siano convocati all'evento (e per le
pagelle anche `pagelle_chiuse = false`)
- Migration `m14_pulizia_dati_evento_cancellato` (DD-029): cancellare un evento pulisce a
cascata, tramite trigger, tutte le tabelle collegate (`risposte_presenze`,
`cacche_partita`, `mvp_voti`, `pagelle_voti`, `badge_social_voti`, `turni_palloni`,
`scout_sessioni`, `scout_live`, `scout_partite`)
- Migration `m15_bonifica_dati_evento_orfani`: bonifica una tantum delle righe orfane da
cancellazioni di eventi precedenti a M14, senza toccare i vecchi voti MVP/pagelle/badge
social legati a id Scout o CSI
- Migration `m16_funzione_bonifica_dati_evento_orfani`: la stessa logica di bonifica di M15
resta richiamabile come funzione `bonifica_dati_evento_orfani()` (riservata al service
role), se mai servisse di nuovo
---
@@ -68,13 +85,18 @@ reali (M9).
- Squadra
- Presenze
- Badge
- Badge (incluso badge social)
- Scout Live (blocco e archivio partite sincronizzati tra dispositivi, migration `m7_scout_partite`)
- Pagelle
- MVP
- Obiettivi di squadra
- Turno palloni (specifica in `docs/modules/palloni.md`)
- Infortuni, in forma minima (specifica in `docs/modules/infortuni.md`)
- Notifiche
- Profilo Giocatore (specifica in `docs/modules/profilo-giocatore.md`)
- Serie di presenze (specifica in `docs/modules/serie-presenze.md`)
- Collegamento CSI: classifica campionato e Coppa, storico e dettaglio partita — formazioni,
scontri diretti (specifica in `docs/modules/collegamento-csi.md`)
---
+47 -117
View File
@@ -1,128 +1,58 @@
# Changelog
Tutte le modifiche significative del progetto, dalla più recente. Il formato segue
[Keep a Changelog](https://keepachangelog.com/it/1.1.0/) e la numerazione
[Semantic Versioning](https://semver.org/lang/it/): il numero di versione è quello di
`package.json`.
Le modifiche rilevanti di CrAPP sono documentate qui, in ordine di rilascio. Il formato
segue [Keep a Changelog](https://keepachangelog.com/it/1.1.0/): ogni versione ha una data
e le voci sono divise per categoria (Aggiunto, Modificato, Sicurezza...). L'elenco
completo delle funzionalità, fatte e previste, sta in [ROADMAP.md](ROADMAP.md); qui si
registra solo _quando_ una voce è stata rilasciata e con quale versione.
L'elenco delle funzionalità disponibili e previste non si ripete qui: sta in
[ROADMAP.md](ROADMAP.md), che elenca il _cosa_ senza numeri di versione — quelli stanno solo
qui.
Le versioni sono sempre a tre cifre (`x.y.z`, mai `x.y`). Il progetto è pre-1.0 (`0.y.z`):
finché resta sotto `1.0.0` un aumento di `y` può includere anche cambi non compatibili
all'indietro.
## [Non rilasciato] — 0.9.0
## [Non rilasciato]
Prima versione, pre-release.
## [0.9.0] - 2026-09-09
Prima versione pre-release: lo sviluppo precedente non era versionato a parte, quindi
questa release riunisce tutto ciò che l'app fa oggi in produzione.
### Aggiunto
- Login con Google tramite Supabase Auth e collegamento automatico dell'account al proprio
giocatore confrontando l'email (DD-011, DD-018; migration `m5_email_giocatori_squadra`):
senza sessione non si entra in nessuna schermata.
- Dashboard amministratore `/admin`: stato dei profili, download di documento, certificato e
foto tessera, export CSV per il tesseramento, aggiunta e disattivazione dei giocatori
(la riga non viene mai eliminata, così presenze, voti e badge restano agganciati al suo id).
- Profilo giocatore: dati anagrafici, documento, certificato medico e foto tessera con le
relative scadenze (migration `m2_profili_giocatore`, bucket privato), divisi nelle tab
Stagione, Documenti e Opzioni — [modules/profilo-giocatore.md](modules/profilo-giocatore.md).
- Tracciamento del tesseramento CSI: numero e data di tessera in `giocatori_squadra`, badge e
contatore in dashboard (migration `m8_tesseramento_csi`).
- Foto profilo condivise tra dispositivi tramite il bucket pubblico `avatar-giocatori`
(migration `m6_avatar_giocatori`).
- Serie di presenze calcolate sui dati reali (`serieConsecutiva()`), con la colonna
`risposto_il` che congela l'istante della **prima** risposta tramite trigger (migration
`m9_risposte_presenze_risposto_il`): sblocca la serie "Conferme 24h" e i badge "Risposta
lampo" e "Mai un forfait" — [modules/serie-presenze.md](modules/serie-presenze.md).
- Scout Live sincronizzato tra dispositivi: il blocco "chi sta scoutando" passa dalla tabella
`scout_sessioni` e le partite concluse vengono archiviate in `scout_partite` (migration
`m7_scout_partite`). Si apre dalla sezione «Scout live» di `/partita/$id`, solo il giorno
della partita, e può usarlo chiunque sia autenticato: uno per volta, grazie al lock.
- Votazione MVP legata all'evento CrAPP e non al referto CSI o allo Scout: si apre due ore
dopo `data`+`ora` della partita, anche senza risultato caricato — [modules/mvp.md](modules/mvp.md).
- Badge Pagellone: la media pagelle conta per il badge solo con almeno `VOTI_MINIMI_PAGELLA`
(5) voti ricevuti — prima un singolo voto poteva sbloccarlo o farlo sparire senza nessuna
significatività statistica — [modules/badge.md](modules/badge.md).
- Sondaggio pre-partita aperto dalle 8:00 del giorno della partita fino al fischio d'inizio
(poi resta chiuso, anche nei giorni successivi) e pulsante «Avvisa tutti del sondaggio» per
gli amministratori (`POST /api/public/apri-sondaggio`); nessun cron, l'invio è manuale.
- Turni palloni con rotazione automatica sulle partite e assegnazione manuale per gli
allenamenti, che restano «da assegnare» finché non si sceglie (migration M10).
- Notifiche push con il testo cifrato **dentro** la push (`aes128gcm`, RFC 8291), così
arrivano anche ad app chiusa e a schermo bloccato (DD-026).
- Collegamento CSI: classifica e risultati ufficiali letti dal portale Livescore CSI Bologna
(stagione 2025/26, Open Misto Eccellenza, Girone B) —
[modules/collegamento-csi.md](modules/collegamento-csi.md).
- Note dell'evento visibili in `/allenamento/$id` e `/partita/$id`, con gli a capo mantenuti.
- «Segnala un bug» e «Suggerisci una nuova funzionalità» in `/profilo`: due link che aprono
una issue GitHub sul template giusto, senza tabelle né schermate di gestione.
- Interfaccia accessibile: contrasto dei token colore sopra 4.5:1, `viewport-fit=cover` e
`theme-color` coerenti con un'app chiara, `lang="it"`, `:focus-visible` globale, tocchi da
44px, `aria-current`/`aria-pressed`/`aria-controls`/`aria-busy`, niente testo sotto i 12px.
- Movimento con molle interrompibili di `motion` al posto delle `@keyframes` a durata fissa,
swipe fra i mesi del calendario e barre di progresso che misurano l'avanzamento tra un
traguardo e il successivo (DD-021).
- Suite di test in `test/` (unit, integration, end-to-end) eseguita con bun e senza nuove
dipendenze: `npm run test` e `npm run test:all`.
- Convenzioni interne: primitive condivise (`Card`, `Campo`, `classiInput` in `ui-bits`),
cache aggiornata con `setQueryData` invece di rileggere il database dopo ogni scrittura, e
logica pura estratta in `src/lib/` con i suoi test.
- Infrastruttura di sviluppo: migrazione da Lovable a sviluppo locale, repository GitHub
indipendente, deploy automatico su Vercel.
### Modificato
- In Squadra la classifica interna (filtro «Classifica per» + elenco) è sotto i 6 riquadri
di Statistiche; la tab «Classifica» è stata rimossa dalla barra delle sottosezioni.
- Tolto l'hint statico "+2 questo mese" dalla StatTile Presenze in home (sezione «Colpo
d'occhio»): mostrava un testo fisso, non un dato calcolato.
- La StatTile Media voto in home applica ora la stessa soglia minima di voti del badge
Pagellone (`VOTI_MINIMI_PAGELLA`): sotto soglia mostra `—` invece di una media poco
significativa (DD-028).
- L'MVP di una partita richiede ora un quorum minimo di 2 voti totali (`VOTI_MINIMI_MVP`)
oltre al margine netto già richiesto: un solo voto non assegna più la vittoria (DD-028).
Alcuni conteggi `mvp` già mostrati possono scendere per effetto della nuova regola.
- Cancellare un evento pulisce ora a cascata, tramite trigger database, tutte le tabelle
collegate (presenze, pagelle, MVP, badge social, turni palloni, scout) invece di lasciarle
come righe orfane (migration `m14_pulizia_dati_evento_cancellato`, DD-029).
- Bonificate una tantum le righe orfane lasciate da cancellazioni precedenti a M14 (migration
`m15_bonifica_dati_evento_orfani`), senza toccare i vecchi voti MVP/pagelle/badge social
legati a id Scout o CSI, che restano dati storici legittimi (DD-029).
- La bonifica sopra è ora anche una funzione richiamabile, `bonifica_dati_evento_orfani()`
(migration `m16_funzione_bonifica_dati_evento_orfani`, riservata al service role), coperta
da test di integrazione invece che verificata solo a mano (DD-029).
### Corretto
- Classifica interna di Squadra: a parità di valore (es. stesse presenze) i giocatori
condividono ora la stessa posizione invece di essere numerati in sequenza (`classificaRank`
in `src/lib/rosa.ts`); la corona di primo posto va a tutti i pari merito in testa, non solo
al primo dell'elenco.
- Il sottotitolo di ogni riga della classifica interna di Squadra mostrava sempre le
"presenze consecutive" anche ordinando per Media voto, MVP, Palloni o Cacche, un dato
scollegato dal criterio scelto: ora segue il criterio selezionato (`dettaglioClassifica` in
`src/lib/rosa.ts`). Per Palloni mostra le volte consecutive in cui il giocatore li ha
portati (nuovo campo `Giocatore.seriePalloni`, calcolato da `serieConsecutivaPalloni` in
`src/lib/palloni-core.ts`), non più le presenze. Per MVP mostra le partite giocate — solo
partite, non più allenamenti compresi (nuovo campo `Giocatore.partiteGiocate`, da
`contaPartiteGiocate()` in `src/lib/presenze.ts`).
- **Gestione squadra** — rosa dei giocatori con ruoli e dati anagrafici di base.
- **Profilo Giocatore** — dati personali e amministrativi, documento d'identità,
certificato medico (caricamento, scadenza, stato, download) e foto tessera in
un'unica schermata, sia lato giocatore sia lato amministratore; lo storico dei
certificati resta un'estensione futura.
- **Gestione tesseramenti CSI** — raccolta dei dati richiesti dal CSI, tracciamento di chi
è già tesserato (numero e data tessera) ed export CSV per il tesseramento.
- **Calendario** — eventi di allenamento e partita, con schermata di dettaglio dedicata.
- **Presenze** — conferma o rifiuto della partecipazione a un evento, visibile a tutta la
squadra al posto di chat e fogli condivisi.
- **Serie di presenze** — tre serie (presenze, conferme, allenamenti) calcolate sui dati
reali della rosa.
- **Scout Live** — un solo referente alla volta registra in tempo reale le azioni di gioco
durante la partita.
- **Badge** — gamification con gradi bronzo/argento/oro, badge segreti e badge social
votati tra compagni.
- **Pagelle** — voto tra compagni (1-10) a fine partita, con media personale e di squadra.
- **Votazione MVP** — elezione del migliore in campo della partita tramite voto tra
compagni, un voto a testa.
- **Obiettivi di squadra** — traguardi collettivi che avanzano con presenze, risposte alle
convocazioni, pagelle e risultati di campionato.
- **Turno palloni** — rotazione condivisa e promemoria di chi porta e riporta i palloni ad
allenamenti e partite.
- **Notifiche push** — promemoria intelligenti su un unico opt-in per dispositivo.
- **Dashboard amministratore** — vista aggregata su tesseramenti, certificati, presenze e
dati della rosa, con download CSV.
- **Collegamento CSI** — classifica di campionato e Coppa, storico partite e dettaglio di
ogni gara (formazioni, storico scontri diretti, probabilità di vittoria calcolata dal
CSI) letti in tempo reale dal portale ufficiale Livescore CSI Bologna, senza inserimento
manuale da parte degli amministratori.
- **Infortuni** — conteggio degli eventi saltati per infortunio, riusando lo stato di
presenza già registrato per le convocazioni.
### Sicurezza
- Migration `m4_solo_autenticati`: tolto al ruolo `anon` l'accesso alle tabelle dell'app
(applicata in produzione il 03/09/2026).
- I permessi di amministrazione arrivano solo da `user_roles` (`src/lib/ruoli.ts`): senza,
basterebbe scegliere il nome giusto per amministrare.
- Migration `m11_scritture_per_ruolo`: ogni voto è firmato con lo slot collegato all'account
(DD-023); il collegamento account → giocatore non è modificabile dal giocatore stesso
(DD-016).
- Migration `m12_niente_autovoto`: i vincoli `mvp_no_autovoto` e `badge_social_no_autovoto`
rifiutano l'auto-voto anche a chi scrive direttamente su PostgREST, come già faceva
`pagelle_voti`.
- Al voto MVP partecipano solo i presenti (o in ritardo) di quell'evento; il filtro è
applicativo, non RLS ([modules/mvp.md](modules/mvp.md)).
- Migration `m13_convocati_e_pagelle_chiuse` (DD-027): la policy di M11 su `pagelle_voti`,
`mvp_voti` e `badge_social_voti` verifica ora anche che votante e votato siano tra i
convocati dell'evento, e per le sole pagelle che `pagelle_chiuse` sia falso — prima erano
filtri solo applicativi, aggirabili scrivendo direttamente su PostgREST.
- La suite copre i rifiuti `401` di `richiediAdmin` (DD-024), i permessi di
`badge_social_voti` e le deroghe admin di M11, e verifica che un ripensamento non riscriva
`risposto_il` (trigger di `m9`).
- Autenticazione tramite Google via Supabase Auth, unico metodo di accesso; permessi
differenziati per ruolo (giocatore/amministratore) su tabelle e route.
+10 -4
View File
@@ -6,7 +6,7 @@ il _cosa_: `CHANGELOG.md` registra _quando_ una voce è stata rilasciata e con q
## Fatto
Tutto quello che è in `main` e finirà nella prima release.
Tutto quello che è in `main`, rilasciato in versione 0.9.0 (vedi `CHANGELOG.md`).
- [x] Gestione squadra
- [x] Calendario
@@ -16,17 +16,23 @@ Tutto quello che è in `main` e finirà nella prima release.
- [x] Badge
- [x] Badge social
- [x] Pagelle
- [x] Votazione MVP
- [x] Obiettivi di squadra
- [x] Turno palloni
- [x] Infortuni — conteggio eventi saltati, in forma minima
- [x] Notifiche Push (promemoria intelligenti)
- [x] Dashboard amministratore
- [x] Download CSV dati
- [x] Certificati medici — caricamento, scadenza, stato e download; lo storico dei
certificati resta un'estensione futura
- [x] Profilo Giocatore — dati personali, documento d'identità, certificato medico
(caricamento, scadenza, stato, download) e foto tessera; lo storico dei certificati
resta un'estensione futura
- [x] Gestione tesseramenti CSI — raccolta dati, export CSV e tracciamento di chi è già
tesserato (numero e data di tessera)
- [x] Collegamento CSI (stagione 2025/26)
- [x] Classifica automatica
- [x] Classifica automatica (campionato e Coppa)
- [x] Risultati campionato
- [x] Dettaglio partita — formazioni, storico scontri diretti e probabilità di vittoria
calcolata dal CSI
## Prossimo
+60
View File
@@ -0,0 +1,60 @@
# Modulo — Calendario ed Eventi
**Stato:** implementato
**File principali:** `src/lib/eventi.ts`, `src/lib/eventi.server.ts`, `src/routes/calendario.tsx`
(vista mensile, tutti), `src/routes/eventi.tsx` (creazione/modifica, solo admin),
`src/components/crapp/EventoCard.tsx` (card condivisa)
**Test:** `test/unit/eventi.test.ts`
---
## Obiettivo
Un unico calendario condiviso per allenamenti, partite, amichevoli ed eventi extra
(riunioni, cene di squadra...), al posto di messaggi sparsi in chat. Ogni evento in
`eventi_app` diventa il punto a cui si agganciano presenze, convocazioni, MVP, pagelle,
scout e turno palloni — la maggior parte degli altri moduli dipende da un `evento.id`.
## Due schermate, due pubblici
- **`/calendario`** — vista mensile per tutta la squadra, sola lettura. Mostra allenamenti,
partite, eventi ed **eventi virtuali** per i compleanni della rosa (`compleanniEventi()`
in `eventi.ts`, generati a runtime dall'anagrafica di `useAnagraficaRosa()`, non righe
vere di `eventi_app`): la spunta della vista `giorniIT`/`mesiIT` colora la cella per tipo
di evento, i giorni con più eventi si dividono lo spazio.
- **`/eventi`** — "Gestione eventi", riservata agli amministratori (`useIsAdmin()`): crea,
modifica ed elimina un evento, sceglie i convocati (`convocatiEvento()`, vuoto = tutta la
rosa). Da qui si distingue "partita" da "amichevole" tramite il flag `campionato`
(`categoriaEvento()`/`daCategoria()` in `eventi.ts` convertono tra la categoria mostrata
in interfaccia e la coppia `{ tipo, campionato }` salvata nel database).
Entrambe leggono la stessa cache (`useEventi()`, `EVENTI_KEY`, `staleTime` 10 minuti: il
calendario cambia raramente). `EventoCard.tsx` è la card riusata da entrambe le schermate;
`linkPerEvento()` decide dove porta il click — `/partita/$id` per una partita (con
`/partita-csi/$id` come alternativa "solo CSI" quando non c'è un evento collegato, vedi
`collegamento-csi.md`), `/allenamento/$id` per un allenamento, nessun link per eventi ed
eventi virtuali (compleanni).
## Lettura lato server
`src/lib/eventi.server.ts` (`leggiEventi()`) è la stessa conversione riga→modello di
`eventi.ts`, ma con `supabaseAdmin` per le route API che girano senza sessione utente (es.
`sollecita-presenze.ts`, `promemoria-palloni.ts` — vedi `presenze.md` e `palloni.md`) e per
`notifiche-smart.ts`, che decide i promemoria da mandare in base agli eventi del giorno.
---
## Limiti noti
1. **Cancellare un evento è distruttivo per tutto ciò che vi era agganciato.** Un trigger
(`m14_pulizia_dati_evento_cancellato`,
[DD-029](../DESIGN_DECISIONS.md#dd-029--cancellare-un-evento-pulisce-a-cascata-i-dati-collegati))
pulisce a cascata presenze, cacche, voti MVP/pagelle/badge social, turni palloni e scout
di quell'evento: non è recuperabile con un annulla, e prima di M14 quelle righe restavano
orfane nel database (bonificate una tantum da M15/M16, vedi `PROJECT_STATE.md`).
2. **Nessuna creazione automatica degli eventi partita dal calendario CSI.** Le gare
ufficiali arrivano già come dati (`getEventsByTeamId.php`, vedi `collegamento-csi.md`),
ma un amministratore deve comunque creare a mano l'evento corrispondente in `/eventi`
perché esistano convocazioni, presenze, MVP e pagelle per quella partita — altrimenti la
gara resta visibile solo nello storico CSI, con un dettaglio "solo CSI" più povero
(`/partita-csi/$id` invece di `/partita/$id`). In `docs/ROADMAP.md` sotto "Prossimo".
+6
View File
@@ -1,5 +1,11 @@
# Modulo — Profilo Giocatore
**Stato:** implementato
**File principali:** `src/lib/profili.ts`, `src/lib/profili-core.ts`, `src/routes/profilo.tsx`,
`src/routes/admin.tsx`
---
## Obiettivo
Il modulo "Profilo Giocatore" raccoglie tutte le informazioni personali, amministrative e documentali di ciascun membro della squadra.
+89
View File
@@ -0,0 +1,89 @@
# Modulo — Squadra
**Stato:** implementato
**File principali:** `src/lib/giocatori-squadra.ts`, `src/lib/giocatori-squadra.server.ts`,
`src/lib/rosa.ts`, `src/routes/squadra.tsx`, `src/routes/admin.tsx` (sezione rosa)
**Test:** `test/unit/giocatori-squadra.test.ts`, `test/unit/rosa.test.ts`
---
## Obiettivo
Tenere l'anagrafica della rosa (nome, numero di maglia, ruolo, chi è collegato a quale
account) in un unico posto — `giocatori_squadra` — e farla usare a tutte le schermate che
hanno bisogno di sapere "chi c'è in squadra", invece di ciascuna avere la propria copia.
Prima di [DD-015](../DESIGN_DECISIONS.md#dd-015--rosa-anagrafica-da-codice-hardcoded-a-database)
la lista viveva hardcoded in `src/lib/crapp-data.ts`: aggiungere o disattivare un
giocatore dalla dashboard admin non aveva alcun effetto sul resto dell'app.
---
## Due letture diverse, per non pagare due volte lo stesso costo
- **`useAnagraficaRosa()`** (`rosa.ts`) — solo id, nome, ruolo, numero, data di nascita dei
giocatori `attivo`. Serve dove basta sapere chi c'è, es. i compleanni nel Calendario o le
liste presenze: non monta gli hook di MVP/pagelle/palloni/infortuni.
- **`useRosa()`** (`rosa.ts`) — la stessa anagrafica arricchita con tutte le statistiche
personali calcolate a runtime: presenze, partite giocate, serie (presenze, allenamenti,
partite, conferme, palloni), MVP vinti, media voto pagelle, palloni, cacche, infortuni,
ritardi. Non fa query aggiuntive: combina in un `useMemo` le cache già in memoria di
`mvp-voti.ts`, `pagelle.ts`, `cacche.ts`, `palloni.ts`, `infortuni.ts`, `presenze.ts`,
`eventi.ts` — la spec di ciascuna di queste statistiche sta nel modulo relativo
(`mvp.md`, `pagelle.md`, `palloni.md`, `infortuni.md`, `presenze.md`). `useRosa()` è anche
la base di `useIo()` (il giocatore sul dispositivo corrente) e `useObiettivi()`
(`obiettivi-squadra.md`).
Entrambe filtrano solo i giocatori `attivo`: chi ha lasciato la squadra resta nel database
(presenze, voti, pagelle e badge della stagione restano agganciati al suo id) ma sparisce
dagli elenchi correnti.
## Gestione dati squadra (solo amministratore)
Da `/admin` un amministratore può ([DD-017](../DESIGN_DECISIONS.md#dd-017--lamministratore-può-compilare-i-dati-al-posto-del-giocatore)):
| Azione | Hook | Effetto |
| ---------------------- | ------------------------ | ------------------------------------------------------------- |
| Modificare dati squadra | `useSalvaDatiSquadra()` | Nome, cognome, numero, ruolo, email (usata per il collegamento automatico, non il dato personale del profilo) |
| Aggiungere un giocatore | `useAggiungiGiocatore()` | Nuova riga con id progressivo `g<N>` (`prossimoIdGiocatore()`), non generato dal database |
| Attivare/disattivare | `useImpostaAttivo()` | Non elimina la riga: la storia della stagione resta intatta |
| Scollegare un account | `useScollegaAccount()` | Libera uno slot collegato per errore ([DD-016](../DESIGN_DECISIONS.md#dd-016--schema-dati-profilo-giocatore-f0) regola 2); il giocatore si ricollega al primo accesso successivo |
| Registrare il tesseramento CSI | `useSalvaTesseramento()` | Numero e data tessera, note solo dopo il tesseramento effettivo (vedi `profilo-giocatore.md`) |
Il collegamento giocatore↔account, invece, non è manuale: avviene in automatico al primo
accesso con Google, per corrispondenza email
([DD-018](../DESIGN_DECISIONS.md#dd-018--collegamento-automatico-giocatoreaccount-per-email)).
`useCollegaGiocatore()` esiste per completare quel flusso, non per una scelta libera
dell'admin.
Le regole di validazione (`validaDatiSquadra()`, `numeroGiaUsato()`) rispecchiano i vincoli
della tabella (numero maglia univoco tra gli attivi, campi obbligatori): l'obiettivo è
mostrare un messaggio leggibile invece di far arrivare un errore Postgres grezzo
all'amministratore.
## Classifica interna di Squadra
La tab "Stats" di `/squadra` mostra una classifica interna ordinabile per 5 criteri
(`CriterioClassifica` in `rosa.ts`): presenze, media voto, MVP, palloni, cacche/partita.
`classificaRank()` calcola un "dense rank" (a parità di valore stessa posizione, il
successivo non salta — 1, 1, 2, non 1, 1, 3); `dettaglioClassifica()` sceglie quale
sottostatistica mostrare sotto il nome, coerente col criterio selezionato (es. "voti
pagella" per il criterio media voto, non sempre "presenze consecutive").
Le altre tab di `/squadra` (Rosa, Obiettivi, Badge) sono viste diverse sugli stessi dati di
`useRosa()`/`useObiettivi()`/`badges.ts`: non introducono altra logica di dominio, solo
presentazione — le rispettive specifiche stanno in `badge.md` e `obiettivi-squadra.md`.
---
## Limiti noti
1. **`giocatori_squadra` non ha ancora una colonna per la data di nascita.** Per i
giocatori storici (seed iniziale) la nascita viene letta da `crapp-data.ts`
(`nascitaPerId`, lookup per id); un giocatore aggiunto dopo la migrazione non ha nascita
nota finché la colonna non esiste (DD-015). Effetto visibile: niente compleanno nel
Calendario per quei giocatori.
2. **`src/lib/crapp-data.ts` resta come fallback**, non più come fonte viva: se il database
non risponde o non è ancora popolato, `rosaFallback()` genera una rosa di riserva dai
dati statici storici. Un ambiente nuovo senza dati in `giocatori_squadra` mostra quindi
comunque una squadra, non una schermata vuota — ma è la rosa 2025/26 hardcoded, non
quella reale.