2026-09-03 15:10:18 +02:00
# Modulo — Palloni
2026-09-09 09:32:19 +02:00
**Stato:** implementato
2026-09-03 15:10:18 +02:00
**File principali:** `src/lib/palloni.ts` , `src/lib/palloni-core.ts` ,
`src/components/crapp/TurnoPalloni.tsx` , `src/components/crapp/PromemoriaPalloni.tsx` ,
`src/routes/api/public/promemoria-palloni.ts`
---
## Obiettivo
Gestire un turno a rotazione condiviso per chi porta e riporta i palloni ad allenamenti e
2026-09-30 14:09:41 +02:00
partite, con proposta automatica, possibilità di modifica manuale e avvisi push, nel centro
2026-10-01 14:33:38 +02:00
notifiche e senza email 3 ore prima dell'evento (DD-040, DD-042). Gli eventi extra-campo (`tipo === "evento"` , es. cena di squadra) e i
2026-09-28 11:01:46 +02:00
compleanni non c'entrano: non richiedono palloni.
2026-09-03 15:10:18 +02:00
---
## Dati
Tabella `turni_palloni` (`evento_id` , `giocatore_id` , `aggiornato_da` , `aggiornato_il` ) —
contiene solo i turni **confermati manualmente** ; le proposte automatiche non salvate non vi
compaiono.
---
## Implementazione
2026-09-30 14:03:50 +02:00
- `eventiPalloni()` (`palloni-core.ts` ) ordina gli eventi per data, poi ora, poi id (lo stesso ordine dei job SQL di `m26` : «evento precedente» e «successivo» dipendono da qui) ed è il filtro comune a tutta la pipeline (proposte,
2026-09-28 11:01:46 +02:00
conteggi, serie, promemoria): include solo `tipo === "partita"` e `tipo === "allenamento"` ,
scarta eventi extra-campo e compleanni.
- `completaTurni()` (`palloni-core.ts` ) propone, per ogni **partita** senza turno già salvato,
il candidato con meno turni fatti, poi quello che non lo fa da più tempo, poi per ordine
alfabetico — un algoritmo greedy, non un ordine fisso né solo per data. Gli **allenamenti**
non ricevono proposta automatica: restano «da assegnare» finché qualcuno non sceglie un
incaricato in `TurnoPalloni` (scelta della squadra).
2026-09-03 15:10:18 +02:00
- `useAssegnaTurno()` (`palloni.ts` ) conferma una proposta o riassegna manualmente, con
upsert su `evento_id` .
- Il conteggio "quante volte hai portato i palloni" mostrato nel profilo e nei badge è
2026-09-08 14:43:58 +02:00
ricalcolato a runtime da `conteggioTurni()` sui **soli turni confermati** (`turniSalvati`
in `rosa.ts` ) — non è uno storico in tabella dedicata, ma non include le proposte
automatiche di `completaTurni()` (quelle restano solo per la UI di rotazione,
`TurnoPalloni.tsx` /`PromemoriaPalloni.tsx` ). Conta solo gli eventi già passati (`e.data <
2026-09-08 15:36:33 +02:00
oggi` , stesso criterio delle presenze): un turno assegnato in anticipo per un allenamento
2026-09-08 14:43:58 +02:00
futuro non è ancora "portato", quindi non sale finché quel giorno non arriva.
2026-09-09 10:55:06 +02:00
- `serieConsecutivaPalloni()` (`palloni-core.ts` ) calcola le volte **consecutive** in cui il
giocatore ha portato i palloni (`Giocatore.seriePalloni` in `rosa.ts` ), mostrate nel
sottotitolo della classifica interna di Squadra quando si ordina per Palloni. Stesso
criterio "solo eventi già passati" di `conteggioTurni()` ; un evento passato senza turno
confermato non spezza la serie di nessuno (viene saltato, non conta come "non portati").
2026-09-03 15:10:18 +02:00
- `TurnoPalloni.tsx` mostra/assegna il turno sulla card di un evento; `PromemoriaPalloni.tsx`
è il banner in Home per il giocatore di turno.
---
## Route API pubblica `/api/public/promemoria-palloni`
2026-09-30 14:03:50 +02:00
> **Affiancata da un invio automatico (DD-040, migration `m26`).** Oltre al pulsante, un job
2026-10-01 14:33:38 +02:00
> `pg_cron` ogni 15 minuti manda l'avviso **una sola volta, 3 ore prima** dell'evento (DD-042, migration `m27`), solo se per l'evento c'è un
2026-09-30 10:06:01 +02:00
> incaricato confermato in `turni_palloni`. Il pulsante e la route **restano**, con tipo
> `turno_palloni` e push dalla route. I due destinatari e i testi nuovi («incarico assegnato» /
> «riconsegna», validi per il pulsante e per i job, più la revoca se l'incaricato cambia) sono nel
2026-09-30 14:03:50 +02:00
> [catalogo](notifiche.md#testi-delle-notifiche). Il resto della sezione descrive il pulsante.
2026-09-30 10:06:01 +02:00
2026-09-06 12:15:39 +02:00
La fa partire un **amministratore** dal pulsante «Avvisa chi è di turno» dentro il riquadro
palloni dell'evento (`TurnoPalloni.tsx` ), riservato agli admin (DD-025). Riceve l'`eventoId` ,
e `avvisiPalloniEvento()` calcola i due destinatari di _quell'evento_ : chi deve **prendere** i
palloni e chi deve **riportarli** (l'incaricato dell'evento precedente), con un testo diverso
per ciascuno.
2026-09-06 14:24:07 +02:00
Titolo e testo viaggiano cifrati dentro la push, quindi il service worker li mostra senza
nessuna chiamata di rete. Stesso meccanismo di `apri-sondaggio` (vedi
2026-09-06 12:15:39 +02:00
[Notifiche ](notifiche.md )).
2026-09-03 15:10:18 +02:00
---
## Limiti noti
2026-09-30 14:09:41 +02:00
- **L'avviso automatico parte solo con un incaricato confermato** (DD-040): per una partita la
proposta di `completaTurni()` non salvata non basta, e senza incaricato non parte nulla. Il pulsante
dell'admin (DD-025) resta per rimandare l'avviso o per una proposta non ancora confermata. `destinatariPromemoriaPalloni()` — la versione "chi è di
2026-09-06 14:24:07 +02:00
turno oggi" — resta in `palloni-core.ts` ma non la chiama più nessuno.
2026-09-03 15:10:18 +02:00
- La rotazione non considera le assenze dichiarate: può proporre il turno a chi ha risposto
"assente" o "infortunato" per quell'evento.
2026-09-08 15:36:33 +02:00
- **`conteggioTurni()` non filtra per tipo evento** (a differenza di `eventiPalloni()` , che
2026-09-28 11:01:46 +02:00
include solo partite e allenamenti): guarda solo `e.data < oggi` . Un turno registrato per
errore su un evento fuori dal dominio "richiede i palloni" conterebbe comunque per il badge
Sherpa dei palloni (`badge.md` § Problemi noti). Rischio basso — l'UI non offre questa
combinazione, dato che né `TurnoPalloni` né i promemoria automatici propongono più un turno
per eventi extra-campo o compleanni — ma il comportamento attuale è pinnato da un test
dedicato in `palloni-core.test.ts` .
2026-09-03 15:10:18 +02:00
---
## Evoluzioni possibili
- Versionare il cron (es. una migration con `cron.schedule` ) invece di configurarlo solo
lato dashboard.
- Escludere dalla rotazione chi ha già dichiarato assenza per l'evento.