Files
CRAPP/docs/modules/badge.md
T
davideandClaude Opus 5 f687322c3f Vieta l'autovoto, allinea la doc al codice e toglie tre riletture.
Rilettura completa della documentazione confrontata con il codice. Dove la doc
diceva il falso l'ho corretta; dove aveva ragione lei ho corretto il codice.

Autovoto (la doc aveva ragione)

- migration m12_niente_autovoto: vincoli mvp_no_autovoto e badge_social_no_autovoto,
  gli stessi che pagelle_voti ha dalla v1.0. Le righe che li violano vengono
  cancellate prima dell'ALTER, altrimenti fallisce; in locale non ce n'erano.
  M11 garantisce solo che il voto sia firmato con il proprio votante_id, non che il
  votato sia un altro: eleggersi MVP restava a un POST di distanza.
- VotazioneMvp non mostra più il votante nell'elenco, come già faceva VotoSocial.

Test che guardavano la colonna sbagliata

- scritture.test.ts verificava che aggiornato_il si muovesse, chiamandolo "quello che
  alimenta la serie di conferme". È l'opposto: la serie usa risposto_il, che il trigger
  di M9 deve tenere fermo. Ora il test prova a riscriverlo e controlla che il database
  abbia tenuto la prima risposta; prima passava anche senza trigger.
- destinatariSollecito() esce dalla route sollecita-presenze e diventa una funzione pura
  in presenze.ts, con i suoi test — stesso trattamento di avvisiPalloniEvento.

Tre riletture in meno

- giocatori-squadra, scout-store e avatar-store usavano invalidateQueries dove il dato
  scritto era già noto: ora setQueryData, come il resto dell'app. Resta scout-live, dove
  il lock può averlo vinto un altro dispositivo.

Documentazione riallineata

- presenze.md, badge.md, mvp.md: i limiti su RLS aperta e route non autenticata erano
  superati da M11 e DD-024;
- serie-presenze.md: il filtro è e.data < oggi, non <=, e l'evento di oggi non conta
  (conterebbe come assenza per tutti); aggiunta la tabella risposto_il/aggiornato_il;
- ARCHITECTURE.md ed EFFICIENZA_CLOUD.md: una sola eccezione a setQueryData;
- DATABASE.md: i vincoli delle tre tabelle di voto;
- PROJECT_STATE.md: fermo a M9, ora arriva a M12.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
2026-09-06 19:30:49 +02:00

80 lines
3.6 KiB
Markdown

# Modulo — Badge
**Stato:** implementato (v1.0), coerente con DD-007 e DD-008
**File principali:** `src/lib/badges.ts`, `src/lib/badge-social.ts`,
`src/components/crapp/CollezioneBadge.tsx`, `src/components/crapp/BadgeDrawer.tsx`,
`src/components/crapp/CelebrazioneBadge.tsx`, `src/components/crapp/VotoSocial.tsx`
---
## Obiettivo
Gamification: sbloccare badge (gradi bronzo/argento/oro, più badge "segreti") in base a
statistiche personali reali del giocatore, per motivare la partecipazione senza penalizzare i
ruoli con meno statistiche "spettacolari" (DD-008).
---
## Dati
Nessuna tabella dedicata ai badge sbloccati: **calcolati interamente a runtime**
dall'oggetto `Giocatore` (DD-007). L'unica tabella coinvolta è `badge_social_voti`, per i
badge assegnati per voto dai compagni.
---
## Implementazione
- `badgeDefs`/`badgeSegreti` (`badges.ts`) definiscono ogni badge con una funzione
`valore(g)` e soglie bronzo/argento/oro. Le fonti dato sono solo statistiche indipendenti
dal ruolo in campo: MVP, media pagelle, turni palloni, presenze, serie, infortuni,
ritardi, cacche — **mai** punti/ace/muri dello Scout Live, coerentemente con DD-008.
- I badge segreti restano nascosti (icona lucchetto) finché non sbloccati.
- **Badge social** (`badge-social.ts`, tabella `badge_social_voti`): 5 categorie fisse per
partita ("Compagno affidabile", "Miglior spirito di squadra", "Fair play", "Meme della
partita", "Cuore del gruppo"), votabili una volta a testa per categoria/partita
(modificabile), con **auto-voto escluso in interfaccia** (`VotoSocial.tsx`) e rifiutato dal
database (vincolo `badge_social_no_autovoto`, migration `m12_niente_autovoto`). A
differenza delle [Pagelle](pagelle.md), qui non c'è alcun tentativo di anonimato:
`votante_id`/`votato_id` sono entrambi visibili.
- `CollezioneBadge.tsx` mostra sbloccati, in progresso, badge social vinti e un contatore di
badge segreti ancora da scoprire; `BadgeDrawer.tsx` il dettaglio di un singolo badge;
`CelebrazioneBadge.tsx` l'overlay celebrativo alla prima visualizzazione di un badge nuovo.
- Il rilevamento "nuovo" (`notifiche-smart.ts`) confronta id deterministici con quelli già
visti, salvati in `localStorage` — quindi **locale al dispositivo**, non sincronizzato tra
dispositivi dello stesso giocatore.
---
## Regole rispettate
- **DD-007**: nessuna tabella `badge_sbloccati`, tutto calcolato a runtime dai dati
esistenti.
- **DD-008**: nessun `BadgeDef` usa dati di reparto (punti/ace/muri); solo statistiche
raggiungibili da qualunque ruolo.
---
## Limiti noti
- **Dipendenza dal modulo [Serie](serie-presenze.md)**: i badge "Sempre in palestra",
"Risposta lampo" e il segreto "Mai un forfait" si muovono solo se cambiano le serie. Le
serie sono calcolate sui dati reali dalla migration `m9` in avanti, ma "Risposta lampo" e
"Mai un forfait" dipendono da `serieConferme`, e `risposto_il` non è ricostruibile per le
risposte precedenti a `m9`: su quelle righe la serie è un'approssimazione.
- Nessuno storico dei badge sbloccati: se cambiano le soglie o i dati sorgente, un badge già
"ottenuto" può sparire o apparire retroattivamente.
- La policy di M11 garantisce che il voto sia firmato con il proprio `votante_id`, ma non
che il votato sia un giocatore convocato per quella partita: quello resta un filtro solo
applicativo.
- Notifiche "nuovo badge" solo locali al dispositivo (localStorage), si ripetono cambiando
browser o dispositivo.
---
## Evoluzioni possibili
- Sincronizzare lo stato "visto" su Supabase invece che solo in localStorage.
- Verificare sui dati di stagione che i tre badge legati alle serie si sblocchino davvero,
ora che le serie sono calcolate.