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 04e80a3531
6 changed files with 240 additions and 127 deletions
+28 -6
View File
@@ -1,6 +1,6 @@
# Project State # Project State
Ultimo aggiornamento: 06/09/2026 Ultimo aggiornamento: 09/09/2026
## Stato generale ## 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`. 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 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 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 - Cursor e Claude Code come ambienti di sviluppo
- Vercel configurato; Environment Variables aggiornate al nuovo Supabase (Preview e Production) - Vercel configurato; Environment Variables aggiornate al nuovo Supabase (Preview e Production)
- Supabase proprietario attivo — Project Ref: `kfkcldwncxqaixetsjes` - 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 - Sviluppo locale verificato con il nuovo Supabase
--- ---
@@ -36,8 +39,9 @@ reali (M9).
## Database ## Database
- Schema v1.0 e migration da M1 a M12 applicate al nuovo Supabase (`m12_niente_autovoto` - Schema v1.0 e migration da M1 a M16 applicate al nuovo Supabase
in produzione dal 06/09/2026, verificata con `npx supabase migration list`) (`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`) - `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 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 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 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 schema ma mai usata) al blocco condiviso dello Scout Live — prima entrambi vivevano solo
in `localStorage`, quindi visibili a un solo dispositivo 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 - Squadra
- Presenze - Presenze
- Badge - Badge (incluso badge social)
- Scout Live (blocco e archivio partite sincronizzati tra dispositivi, migration `m7_scout_partite`) - Scout Live (blocco e archivio partite sincronizzati tra dispositivi, migration `m7_scout_partite`)
- Pagelle - Pagelle
- MVP - MVP
- Obiettivi di squadra
- Turno palloni (specifica in `docs/modules/palloni.md`)
- Infortuni, in forma minima (specifica in `docs/modules/infortuni.md`)
- Notifiche - Notifiche
- Profilo Giocatore (specifica in `docs/modules/profilo-giocatore.md`) - Profilo Giocatore (specifica in `docs/modules/profilo-giocatore.md`)
- Serie di presenze (specifica in `docs/modules/serie-presenze.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 # Changelog
Tutte le modifiche significative del progetto, dalla più recente. Il formato segue Le modifiche rilevanti di CrAPP sono documentate qui, in ordine di rilascio. Il formato
[Keep a Changelog](https://keepachangelog.com/it/1.1.0/) e la numerazione segue [Keep a Changelog](https://keepachangelog.com/it/1.1.0/): ogni versione ha una data
[Semantic Versioning](https://semver.org/lang/it/): il numero di versione è quello di e le voci sono divise per categoria (Aggiunto, Modificato, Sicurezza...). L'elenco
`package.json`. 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 Le versioni sono sempre a tre cifre (`x.y.z`, mai `x.y`). Il progetto è pre-1.0 (`0.y.z`):
[ROADMAP.md](ROADMAP.md), che elenca il _cosa_ senza numeri di versione — quelli stanno solo finché resta sotto `1.0.0` un aumento di `y` può includere anche cambi non compatibili
qui. 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 ### Aggiunto
- Login con Google tramite Supabase Auth e collegamento automatico dell'account al proprio - **Gestione squadra** — rosa dei giocatori con ruoli e dati anagrafici di base.
giocatore confrontando l'email (DD-011, DD-018; migration `m5_email_giocatori_squadra`): - **Profilo Giocatore** — dati personali e amministrativi, documento d'identità,
senza sessione non si entra in nessuna schermata. certificato medico (caricamento, scadenza, stato, download) e foto tessera in
- Dashboard amministratore `/admin`: stato dei profili, download di documento, certificato e un'unica schermata, sia lato giocatore sia lato amministratore; lo storico dei
foto tessera, export CSV per il tesseramento, aggiunta e disattivazione dei giocatori certificati resta un'estensione futura.
(la riga non viene mai eliminata, così presenze, voti e badge restano agganciati al suo id). - **Gestione tesseramenti CSI** — raccolta dei dati richiesti dal CSI, tracciamento di chi
- Profilo giocatore: dati anagrafici, documento, certificato medico e foto tessera con le è già tesserato (numero e data tessera) ed export CSV per il tesseramento.
relative scadenze (migration `m2_profili_giocatore`, bucket privato), divisi nelle tab - **Calendario** — eventi di allenamento e partita, con schermata di dettaglio dedicata.
Stagione, Documenti e Opzioni — [modules/profilo-giocatore.md](modules/profilo-giocatore.md). - **Presenze** — conferma o rifiuto della partecipazione a un evento, visibile a tutta la
- Tracciamento del tesseramento CSI: numero e data di tessera in `giocatori_squadra`, badge e squadra al posto di chat e fogli condivisi.
contatore in dashboard (migration `m8_tesseramento_csi`). - **Serie di presenze** — tre serie (presenze, conferme, allenamenti) calcolate sui dati
- Foto profilo condivise tra dispositivi tramite il bucket pubblico `avatar-giocatori` reali della rosa.
(migration `m6_avatar_giocatori`). - **Scout Live** — un solo referente alla volta registra in tempo reale le azioni di gioco
- Serie di presenze calcolate sui dati reali (`serieConsecutiva()`), con la colonna durante la partita.
`risposto_il` che congela l'istante della **prima** risposta tramite trigger (migration - **Badge** — gamification con gradi bronzo/argento/oro, badge segreti e badge social
`m9_risposte_presenze_risposto_il`): sblocca la serie "Conferme 24h" e i badge "Risposta votati tra compagni.
lampo" e "Mai un forfait" — [modules/serie-presenze.md](modules/serie-presenze.md). - **Pagelle** — voto tra compagni (1-10) a fine partita, con media personale e di squadra.
- Scout Live sincronizzato tra dispositivi: il blocco "chi sta scoutando" passa dalla tabella - **Votazione MVP** — elezione del migliore in campo della partita tramite voto tra
`scout_sessioni` e le partite concluse vengono archiviate in `scout_partite` (migration compagni, un voto a testa.
`m7_scout_partite`). Si apre dalla sezione «Scout live» di `/partita/$id`, solo il giorno - **Obiettivi di squadra** — traguardi collettivi che avanzano con presenze, risposte alle
della partita, e può usarlo chiunque sia autenticato: uno per volta, grazie al lock. convocazioni, pagelle e risultati di campionato.
- Votazione MVP legata all'evento CrAPP e non al referto CSI o allo Scout: si apre due ore - **Turno palloni** — rotazione condivisa e promemoria di chi porta e riporta i palloni ad
dopo `data`+`ora` della partita, anche senza risultato caricato — [modules/mvp.md](modules/mvp.md). allenamenti e partite.
- Badge Pagellone: la media pagelle conta per il badge solo con almeno `VOTI_MINIMI_PAGELLA` - **Notifiche push** — promemoria intelligenti su un unico opt-in per dispositivo.
(5) voti ricevuti — prima un singolo voto poteva sbloccarlo o farlo sparire senza nessuna - **Dashboard amministratore** — vista aggregata su tesseramenti, certificati, presenze e
significatività statistica — [modules/badge.md](modules/badge.md). dati della rosa, con download CSV.
- Sondaggio pre-partita aperto dalle 8:00 del giorno della partita fino al fischio d'inizio - **Collegamento CSI** — classifica di campionato e Coppa, storico partite e dettaglio di
(poi resta chiuso, anche nei giorni successivi) e pulsante «Avvisa tutti del sondaggio» per ogni gara (formazioni, storico scontri diretti, probabilità di vittoria calcolata dal
gli amministratori (`POST /api/public/apri-sondaggio`); nessun cron, l'invio è manuale. CSI) letti in tempo reale dal portale ufficiale Livescore CSI Bologna, senza inserimento
- Turni palloni con rotazione automatica sulle partite e assegnazione manuale per gli manuale da parte degli amministratori.
allenamenti, che restano «da assegnare» finché non si sceglie (migration M10). - **Infortuni** — conteggio degli eventi saltati per infortunio, riusando lo stato di
- Notifiche push con il testo cifrato **dentro** la push (`aes128gcm`, RFC 8291), così presenza già registrato per le convocazioni.
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`).
### Sicurezza ### Sicurezza
- Migration `m4_solo_autenticati`: tolto al ruolo `anon` l'accesso alle tabelle dell'app - Autenticazione tramite Google via Supabase Auth, unico metodo di accesso; permessi
(applicata in produzione il 03/09/2026). differenziati per ruolo (giocatore/amministratore) su tabelle e route.
- 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`).
+10 -4
View File
@@ -6,7 +6,7 @@ il _cosa_: `CHANGELOG.md` registra _quando_ una voce è stata rilasciata e con q
## Fatto ## 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] Gestione squadra
- [x] Calendario - [x] Calendario
@@ -16,17 +16,23 @@ Tutto quello che è in `main` e finirà nella prima release.
- [x] Badge - [x] Badge
- [x] Badge social - [x] Badge social
- [x] Pagelle - [x] Pagelle
- [x] Votazione MVP
- [x] Obiettivi di squadra - [x] Obiettivi di squadra
- [x] Turno palloni
- [x] Infortuni — conteggio eventi saltati, in forma minima
- [x] Notifiche Push (promemoria intelligenti) - [x] Notifiche Push (promemoria intelligenti)
- [x] Dashboard amministratore - [x] Dashboard amministratore
- [x] Download CSV dati - [x] Download CSV dati
- [x] Certificati medici — caricamento, scadenza, stato e download; lo storico dei - [x] Profilo Giocatore — dati personali, documento d'identità, certificato medico
certificati resta un'estensione futura (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à - [x] Gestione tesseramenti CSI — raccolta dati, export CSV e tracciamento di chi è già
tesserato (numero e data di tessera) tesserato (numero e data di tessera)
- [x] Collegamento CSI (stagione 2025/26) - [x] Collegamento CSI (stagione 2025/26)
- [x] Classifica automatica - [x] Classifica automatica (campionato e Coppa)
- [x] Risultati campionato - [x] Risultati campionato
- [x] Dettaglio partita — formazioni, storico scontri diretti e probabilità di vittoria
calcolata dal CSI
## Prossimo ## 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 # 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 ## Obiettivo
Il modulo "Profilo Giocatore" raccoglie tutte le informazioni personali, amministrative e documentali di ciascun membro della squadra. 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.