diff --git a/PROJECT_STATE.md b/PROJECT_STATE.md index 177ae23..87d60fc 100644 --- a/PROJECT_STATE.md +++ b/PROJECT_STATE.md @@ -211,6 +211,11 @@ Niente di assegnato: l'avviso certificati in scadenza (DD-035, 1.1.0) e le notif canali (DD-036–DD-039, 1.2.0) sono implementati (vedi `docs/CHANGELOG.md`). Le voci ancora aperte stanno in [docs/ROADMAP.md](docs/ROADMAP.md), sotto «Prossimo». +Specificato ma non ancora implementato: notifiche automatiche per turno palloni (12, 6 e 3 ore +prima, senza email, con revoca dell'incarico), solleciti presenze (24, 12 e 6 ore prima) e promemoria con +«oggi/domani» (DD-040, migration `m26` da scrivere). I pulsanti «Avvisa chi è di turno» e «Sollecita» +restano. + --- ## Note diff --git a/docs/DATABASE.md b/docs/DATABASE.md index 5f02c4d..fe295af 100644 --- a/docs/DATABASE.md +++ b/docs/DATABASE.md @@ -81,10 +81,10 @@ La tabella è verificata da `test/integration/permessi.test.ts` contro il databa | `push_subscriptions` | Dispositivi registrati per le notifiche Push. | | | `promemoria_push` | Non più usata. | Serviva da coda del testo quando la push partiva vuota; dal payload cifrato non la scrive né la legge nessuno. Tabella ancora presente, da eliminare con una migrazione. | | `notifiche_utente` | Centro notifiche in-app, con stato letto/non letto, sull'avatar del profilo. | Introdotta dalla migration `m17_notifiche_utente`. Cinque sorgenti (le prime quattro dalla `m17`, il sondaggio dalla `m24`), tutte in parallelo alla push esistente tranne i promemoria, la cui push la manda il worker (`notifiche_push_coda`): messaggio libero dell'admin (`notifica-personalizzata.ts`), un promemoria automatico prima di un evento — 24h e 3h prima, due `cron.schedule` (estensione `pg_cron`, abilitata il 31/07/2026 ma usata per la prima volta qui) — il turno palloni (`promemoria-palloni.ts`) e il sollecito presenze (`sollecita-presenze.ts`) e il sondaggio pre-partita (`apri-sondaggio.ts`, tipo `sondaggio_cacche`, `m24`), questi ultimi tre avviati a mano da un admin (il sollecito anche da un allenatore, DD-034). Volutamente non c'è una notifica sulla sola creazione di un evento: conta l'avvicinarsi della data, non il momento in cui è stato messo in calendario. I destinatari di un evento seguono la stessa convenzione di `convocatiEvento()`/`evento_permette_voto()` (M13): `convocati` vuoto = tutta la rosa attiva. I promemoria evento arrivano solo ai giocatori attivi: ai convocati, o a tutti se `convocati` è vuoto (`giocatori_destinatari_evento()`, riscritta da M21 per includere sempre gli allenatori e da M25 per escluderli, DD-039); il sollecito presenze (avviato da admin o allenatore) e il turno palloni non arrivano mai a loro. Letta/segnata come letta dal client con `useNotificheMie()`/`useSegnaLette()` (`src/lib/notifiche-utente.ts`), RLS-only, nessuna route API dedicata per queste due operazioni. | -| `notifiche_email_coda` | Coda delle email per le notifiche in-app: una riga per notifica, con stato d'invio. | Migration `m22_notifiche_email` (DD-036). Chiave `notifica_id` → `notifiche_utente(id)` con `ON DELETE CASCADE`. Stati: `in_coda`, `in_invio`, `inviata`, `fallita`, `saltata`; colonne `tentativi`, `prossimo_tentativo` (backoff), `errore` (solo codici SMTP), `inviata_il`, `aggiornata_il`. La riempie il trigger `notifiche_utente_accoda_email` (`AFTER INSERT OR UPDATE OF creato_il`): ogni notifica nuova e ogni rinvio via `upsert` finiscono in coda; le notifiche presenti prima della migration no. RLS attiva **senza policy** e permessi tolti a `anon`/`authenticated`: la usa solo il worker `mailer/` con la service role, tramite tre funzioni `SECURITY DEFINER` riservate a `service_role` — `prendi_notifiche_email(p_max)` (lotto con `FOR UPDATE SKIP LOCKED`, salta gli slot senza account o email o con le email spente, recupera le righe ferme `in_invio` da più di 10 minuti), `esito_notifica_email(...)` e `email_inviate_ultime_24h()`. Tabella separata da `notifiche_utente` perché la policy `UPDATE` del giocatore su quella riga gli permetterebbe di riportare a `in_coda` una mail già partita. Specifica in [notifiche-email.md](modules/notifiche-email.md). | +| `notifiche_email_coda` | Coda delle email per le notifiche in-app: una riga per notifica, con stato d'invio. | Migration `m22_notifiche_email` (DD-036). Chiave `notifica_id` → `notifiche_utente(id)` con `ON DELETE CASCADE`. Stati: `in_coda`, `in_invio`, `inviata`, `fallita`, `saltata`; colonne `tentativi`, `prossimo_tentativo` (backoff), `errore` (solo codici SMTP), `inviata_il`, `aggiornata_il`. La riempie il trigger `notifiche_utente_accoda_email` (`AFTER INSERT OR UPDATE OF creato_il`): ogni notifica nuova e ogni rinvio via `upsert` finiscono in coda; le notifiche presenti prima della migration no. RLS attiva **senza policy** e permessi tolti a `anon`/`authenticated`: la usa solo il worker `mailer/` con la service role, tramite tre funzioni `SECURITY DEFINER` riservate a `service_role` — `prendi_notifiche_email(p_max)` (lotto con `FOR UPDATE SKIP LOCKED`, salta gli slot senza account o email o con le email spente, recupera le righe ferme `in_invio` da più di 10 minuti), `esito_notifica_email(...)` e `email_inviate_ultime_24h()`. Tabella separata da `notifiche_utente` perché la policy `UPDATE` del giocatore su quella riga gli permetterebbe di riportare a `in_coda` una mail già partita. Specifica in [notifiche-email.md](modules/notifiche-email.md). Con DD-040 (migration `m26`, da scrivere) il trigger salta i tipi del turno palloni, che non mandano email. | | `preferenze_utente` | Preferenze del singolo account (non dello slot giocatore). | Migration `m22_notifiche_email`. Chiave `auth_user_id` → `auth.users(id)` con cascade; oggi una sola colonna, `email_notifiche` (default `true`), più `aggiornata_il`. **Nessuna riga = email attive**: la riga nasce solo quando il giocatore tocca l'interruttore «Email» in Profilo → Opzioni (upsert da `useEmailNotifiche()`, `src/lib/preferenze-utente.ts`). RLS: ognuno legge, inserisce e aggiorna solo la propria riga. | -| `promemoria_eventi_generati` | Registro dei promemoria evento (24 e 3 ore) già generati, per giocatore, evento e tipo. | Migration `m23_promemoria_gia_generati` (DD-037). Chiave `(giocatore_id, evento_id, tipo)` con `ON DELETE CASCADE` verso `giocatori_squadra` ed `eventi_app` (sparisce con l'evento, come le altre tabelle collegate, DD-029); `generato_il`. Scritta solo da `genera_promemoria_eventi()`, che crea la notifica in `notifiche_utente` soltanto per le righe appena registrate: eliminare la notifica dal centro notifiche non fa rigenerare il promemoria né accoda una nuova mail. RLS attiva **senza policy** e permessi tolti a `anon`/`authenticated`. La migration registra come già generati i promemoria presenti al momento dell'applicazione. | -| `notifiche_push_coda` | Coda delle push per i promemoria a 24 e 3 ore: una riga per notifica, con stato d'invio. | Migration `m24_push_promemoria_e_sondaggio` (DD-038). Stessa struttura di `notifiche_email_coda`: chiave `notifica_id` → `notifiche_utente(id)` con `ON DELETE CASCADE`, stati `in_coda`, `in_invio`, `inviata`, `fallita`, `saltata`, `tentativi`, `prossimo_tentativo`, `errore` (solo codici HTTP), `inviata_il`, `aggiornata_il`. La riempie il trigger `notifiche_utente_accoda_push_promemoria` (`AFTER INSERT`, solo tipi `evento_promemoria_24h` e `evento_promemoria_3h`: le altre notifiche mandano la push dall'app; nessun backfill). Le usa solo il worker `mailer/` con la service role, tramite `prendi_push_promemoria(p_max)` (lotto con `FOR UPDATE SKIP LOCKED` con testo e dispositivi iscritti del giocatore; salta i giocatori senza dispositivi e i promemoria creati da oltre 3 ore, recupera le righe ferme `in_invio` da più di 10 minuti) ed `esito_push_promemoria(...)`, entrambe `SECURITY DEFINER` riservate a `service_role`. RLS attiva **senza policy** e permessi tolti a `anon`/`authenticated`. | +| `promemoria_eventi_generati` | Registro dei promemoria evento (24 e 3 ore) già generati, per giocatore, evento e tipo. | Migration `m23_promemoria_gia_generati` (DD-037). Chiave `(giocatore_id, evento_id, tipo)` con `ON DELETE CASCADE` verso `giocatori_squadra` ed `eventi_app` (sparisce con l'evento, come le altre tabelle collegate, DD-029); `generato_il`. Scritta solo da `genera_promemoria_eventi()`, che crea la notifica in `notifiche_utente` soltanto per le righe appena registrate: eliminare la notifica dal centro notifiche non fa rigenerare il promemoria né accoda una nuova mail. RLS attiva **senza policy** e permessi tolti a `anon`/`authenticated`. La migration registra come già generati i promemoria presenti al momento dell'applicazione. Con DD-040 (migration `m26`, da scrivere) registra anche turno palloni e solleciti presenze a 24, 12 e 6 ore. | +| `notifiche_push_coda` | Coda delle push per i promemoria a 24 e 3 ore: una riga per notifica, con stato d'invio. | Migration `m24_push_promemoria_e_sondaggio` (DD-038). Stessa struttura di `notifiche_email_coda`: chiave `notifica_id` → `notifiche_utente(id)` con `ON DELETE CASCADE`, stati `in_coda`, `in_invio`, `inviata`, `fallita`, `saltata`, `tentativi`, `prossimo_tentativo`, `errore` (solo codici HTTP), `inviata_il`, `aggiornata_il`. La riempie il trigger `notifiche_utente_accoda_push_promemoria` (`AFTER INSERT`, solo tipi `evento_promemoria_24h` e `evento_promemoria_3h`: le altre notifiche mandano la push dall'app; nessun backfill). Le usa solo il worker `mailer/` con la service role, tramite `prendi_push_promemoria(p_max)` (lotto con `FOR UPDATE SKIP LOCKED` con testo e dispositivi iscritti del giocatore; salta i giocatori senza dispositivi e i promemoria creati da oltre 3 ore, recupera le righe ferme `in_invio` da più di 10 minuti) ed `esito_push_promemoria(...)`, entrambe `SECURITY DEFINER` riservate a `service_role`. RLS attiva **senza policy** e permessi tolti a `anon`/`authenticated`. Con DD-040 (migration `m26`, da scrivere) accoda anche turno palloni e solleciti presenze. | ## Funzioni speciali diff --git a/docs/DESIGN_DECISIONS.md b/docs/DESIGN_DECISIONS.md index 39d99e7..a2649a0 100644 --- a/docs/DESIGN_DECISIONS.md +++ b/docs/DESIGN_DECISIONS.md @@ -55,6 +55,7 @@ Serve a rispondere a domande del tipo: | [DD-037](#dd-037--i-promemoria-evento-ricordano-di-essere-già-stati-generati) | Promemoria evento senza ritorni | | [DD-038](#dd-038--ogni-notifica-ha-tre-canali-la-push-dei-promemoria-parte-dal-worker) | Tre canali per ogni notifica | | [DD-039](#dd-039--lallenatore-non-riceve-i-promemoria-degli-eventi) | Niente promemoria all'allenatore | +| [DD-040](#dd-040--promemoria-con-oggi-o-domani-turno-palloni-e-solleciti-automatici) | Palloni e solleciti automatici | **In valutazione** @@ -1710,6 +1711,9 @@ Vercel e avevano già i tre canali. allenatori, anche di giocatori non più attivi. - Le altre notifiche non cambiano canali e non ricevono push doppie. +**Aggiornamento (30 settembre 2026)** — DD-040 esclude dalle email le notifiche del turno palloni: per +quei tipi i canali sono due (push e in-app). + **Riesame** Se l'host di casa si rivela poco affidabile per una notifica sensibile al tempo come la push, o se il database dovesse comunque chiamare l'app per altro, valutare di nuovo la chiamata dal database @@ -1755,3 +1759,96 @@ restano invariati. **Riesame** Se l'allenatore dovesse poter partecipare a certi eventi (per esempio la cena), si riapre insieme alla sua possibilità di rispondere. + +### DD-040 — Promemoria con «oggi» o «domani», turno palloni e solleciti automatici + +**Data:** 30 settembre 2026 +**Stato:** Accettata (solo documentata: codice e migration `m26` non ancora scritti; fino al rilascio +l'app si comporta come in 1.2.0) + +**Contesto** +Quattro notifiche su sei partivano da un pulsante premuto da un admin (DD-025 per i palloni): se nessuno +lo premeva, nessuno veniva avvisato. I testi erano inoltre brevi e informali, e i promemoria non dicevano +quando e dove. Si vuole che turno palloni e sollecito presenze partano anche da soli, a orari fissi prima +dell'evento, e che i testi siano più formali e completi. I testi nuovi sono nel +[catalogo](modules/notifiche.md#testi-delle-notifiche). + +**Decisione** + +1. **Promemoria a 24 e 3 ore:** nuovo testo con data, ora e luogo. La parola nel titolo e nella riga + «Data» è **«domani»** se la data dell'evento è quella di domani (fuso `Europe/Rome`), **«oggi»** + altrimenti. Vale per entrambi i promemoria, quindi anche quello a 3 ore di un evento dopo la + mezzanotte dice «domani». La parola la calcola la funzione SQL quando genera la notifica. +2. **Turno palloni automatico.** Un job `pg_cron` ogni 15 minuti avvisa per gli eventi di tipo allenamento + o partita, in tre tempi con finestre che non si sovrappongono: **12 ore** (tra 12 e 6 ore prima + dell'inizio), **6 ore** (tra 6 e 3) e **3 ore** (meno di 3). Parte **solo se per l'evento c'è un + incaricato confermato** in `turni_palloni`: le proposte automatiche di `completaTurni()` non salvate non + contano. Gli avvisi sono due, con lo stesso testo nei tre tempi: «incarico assegnato» all'incaricato e + «riconsegna» a chi aveva i palloni all'evento precedente, se è un'altra persona. + - **Pulsante manuale.** «Avvisa chi è di turno» e la route `promemoria-palloni` **restano**: tipo + `turno_palloni`, testo nuovo, push mandata dalla route e non dalla coda. È una notifica in più; + riceverne due (automatica e manuale) va bene. + - **Incaricato cambiato dopo un avviso.** Il nuovo incaricato riceve il suo avviso al giro successivo, + con il tipo della finestra in cui cade. Chi aveva già ricevuto un avviso e non è più destinatario + riceve **una sola volta** «Turno palloni: incarico revocato» (`turno_palloni_revocato`), se + l'evento non è iniziato. Il job lo scopre confrontando i destinatari attuali con le notifiche + `turno_palloni*` già scritte per l'evento, senza trigger su `turni_palloni`. + - **Niente email.** Gli avvisi palloni (automatici, revoca e pulsante) arrivano come push e nel centro + notifiche, **non per email**: il trigger `notifiche_utente_accoda_email` salta i tipi + `turno_palloni`, `turno_palloni_12h`, `_6h`, `_3h` e `turno_palloni_revocato`. È un'eccezione a + DD-038 («ogni notifica ha tre canali») solo per questi tipi; l'interruttore «Email» non cambia. +3. **Sollecito presenze automatico in tre tempi.** Lo stesso job manda il sollecito **24 ore** (tra 24 e + 12 ore prima), **12 ore** (tra 12 e 6) e **6 ore** (meno di 6) prima, al massimo una notifica per + giocatore e per tipo. Lo riceve solo chi in quel momento non ha risposto o ha risposto «forse» ed è + destinatario dell'evento: i convocati, o tutta la rosa attiva se `convocati` è vuoto, mai l'allenatore. + Chi risponde presente, assente o in ritardo smette di riceverli; chi resta su «forse» li riceve tutti e + tre. Un evento creato a meno di 12 ore dall'inizio riceve solo il sollecito della sua finestra. + - **Pulsante manuale.** «Sollecita» e la route `sollecita-presenze` **restano**, anche per l'allenatore: + tipo `sollecita_presenze`, push mandata dalla route, testo nuovo con in più la riga «Richiesta di: + _Nome_». I destinatari sono **gli stessi dell'automatico** (convocati senza risposta o con «forse»); + oggi la route guarda invece tutti i giocatori attivi. +4. **Stessa infrastruttura dei promemoria** (DD-037, DD-038): le notifiche automatiche nascono nel + database, il registro `promemoria_eventi_generati` impedisce i doppioni anche se il giocatore le + elimina, la push la manda il worker dalla coda `notifiche_push_coda`, l'email (quando c'è) nasce dal + trigger su `notifiche_utente`. Tipi nuovi: `sollecita_presenze_24h`, `_12h`, `_6h`; + `turno_palloni_12h`, `_6h`, `_3h` e `turno_palloni_revocato`. Il `turno_palloni` del pulsante resta. +5. **Migration `m26`:** estende i `CHECK` sui tipi di `notifiche_utente` e del registro, il trigger della + coda push, il trigger della coda email (che salta i tipi dei palloni) e ridefinisce la funzione dei + promemoria. Le funzioni nuove riusano `giocatori_destinatari_evento()`. + +**Alternative scartate** + +- Togliere i pulsanti manuali (turno palloni e sollecito) una volta arrivati gli automatici → scartata su + indicazione esplicita: servono all'admin per rimandare un avviso o sollecitare fuori dagli orari fissi. +- Mandare tutti i solleciti (o gli avvisi palloni) a un evento creato tardi → scartata: più messaggi nel + giro di pochi minuti sono rumore. +- «Domani» e «oggi» fissi nei due testi → scartata: il job delle 24 ore scatta anche per eventi che + iniziano oggi, e quello delle 3 ore attraversa la mezzanotte. +- Calcolare il testo nell'app invece che nel database → scartata: le notifiche nascono nei job `pg_cron`, + che non chiamano l'app (DD-038). +- Spostare nel worker il calcolo di evento precedente e successivo dei palloni → scartata: un secondo + punto che scrive in `notifiche_utente`, per un vantaggio piccolo. +- Ritirare l'avviso già consegnato al vecchio incaricato → scartata: per la push non si può, e in-app + confonde. Si manda la revoca. + +**Conseguenze** + +- Cambia la regola «quattro notifiche su sei dipendono da un gesto manuale»: restano solo manuali il + messaggio dello staff e il sondaggio pre-partita; per turno palloni e sollecito gli automatici si + aggiungono ai pulsanti, e un giocatore può ricevere lo stesso avviso due volte. +- **Logica duplicata:** «evento precedente» e «evento successivo» per i palloni, oggi in `palloni-core.ts` + (usato dal pulsante e dalla schermata), vengono riscritti in SQL per i job. Se si cambia una regola in + una sola delle due versioni, pulsante e job danno risposte diverse: un test di integrazione le confronta + sugli stessi dati. +- `destinatariSollecito()` riceve anche i convocati e serve al sollecito manuale; un giocatore non + convocato non è più sollecitato. `avvisiPalloniEvento()` resta come riferimento per i test finché il SQL + non la sostituisce del tutto. +- Se un incaricato revocato viene poi riassegnato non riceve un secondo avviso, perché il registro lo ha + già generato per quella finestra: la revoca resta l'ultima notifica che ha letto. +- Le mail automatiche per evento e giocatore sono al massimo **cinque** (due promemoria, tre solleciti), + più quelle dei pulsanti (sollecito, sondaggio). Il turno palloni non ne manda. +- Il testo dei promemoria già generati non cambia (il registro impedisce di rigenerarli). + +**Riesame** +Dopo la prima stagione di uso: se i solleciti o gli avvisi palloni risultano troppi, ridurre le +finestre o escludere chi ha risposto «forse» più di una volta. diff --git a/docs/ROADMAP.md b/docs/ROADMAP.md index c1f1fd5..13d25f3 100644 --- a/docs/ROADMAP.md +++ b/docs/ROADMAP.md @@ -47,6 +47,9 @@ Tutto quello che è rilasciato, fino alla versione 1.0.0 (vedi `CHANGELOG.md`). ## Prossimo +- [ ] Notifiche automatiche — promemoria con «oggi/domani», turno palloni 12, 6 e 3 ore prima, sollecito + presenze a 24, 12 e 6 ore (i pulsanti «Avvisa chi è di turno» e «Sollecita» restano) ([specifica](modules/notifiche.md#testi-delle-notifiche), DD-040) + - [ ] Calendario ufficiale — i dati delle gare future arrivano già dal feed CSI, la pagina Campionato usa solo quelle giocate - [ ] Database esercizi diff --git a/docs/modules/allenatore.md b/docs/modules/allenatore.md index 1d70b40..a8180fb 100644 --- a/docs/modules/allenatore.md +++ b/docs/modules/allenatore.md @@ -114,7 +114,7 @@ a lui (`usePuoGestireEventi()`). Può anche **sollecitare le presenze** di un evento, come l'admin: la route `sollecita-presenze.ts` accetta admin o allenatore (`richiediGestoreEventi()`), e il -sollecito arriva solo ai giocatori che non hanno risposto, mai all'allenatore stesso. +sollecito arriva solo ai giocatori che non hanno risposto, mai all'allenatore stesso. Con DD-040 (non ancora implementato) si aggiungono solleciti automatici a 24, 12 e 6 ore prima dell'evento, ma il pulsante resta. **Restano solo admin:** dashboard `/admin`, dati e documenti dei giocatori, export CSI, notifica personalizzata, promemoria palloni, apertura sondaggio cacche, correzione delle @@ -129,7 +129,7 @@ Cosa riceve l'allenatore, notifica per notifica (il catalogo completo, con i can | ------------------------------------------------- | ----------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | Promemoria a 24 e 3 ore prima di un evento | **no** | Dal 29/09/2026 (m25, DD-039) `giocatori_destinatari_evento()` include solo i giocatori attivi: l'allenatore non può rispondere alle presenze né essere convocato, quindi il promemoria non gli chiederebbe nulla che possa fare (per esempio una cena di squadra). Prima gli arrivava sempre, convocato o no (m21). | | Messaggio dello staff | **sì** | In-app ed email vanno a tutta la rosa attiva, allenatori compresi; la push ai suoi dispositivi iscritti, o solo a lui se il messaggio è per lui. | -| Sollecito presenze | no | L'allenatore non risponde alle presenze (DD-034). Può invece **far partire** il sollecito. | +| Sollecito presenze | no | L'allenatore non risponde alle presenze (DD-034). Può invece **far partire** il sollecito . | | Turno palloni | no | I turni riguardano solo i giocatori. | | Sondaggio pre-partita | no | Né la push né l'avviso in-app e per email (DD-038): il sondaggio non lo riguarda. | | Notifiche smart (badge, serie, obiettivi, social) | **no** | Le mostra `CelebrazioneBadge` per il giocatore selezionato, letto da `useIo()`, che cerca l'id in `useRosa()`, da cui l'allenatore è escluso: per lui `useIo()` è `null` e `useNotificheSmart()` non fa nulla. Non ha statistiche da cui calcolarle. | diff --git a/docs/modules/notifiche-email.md b/docs/modules/notifiche-email.md index 5517e45..01d9e01 100644 --- a/docs/modules/notifiche-email.md +++ b/docs/modules/notifiche-email.md @@ -50,7 +50,9 @@ App su Vercel ──scrive──▶ notifiche_utente ──trigger──▶ noti ## Quali notifiche e a chi **Ogni riga nuova di `notifiche_utente` genera una mail**: messaggio admin, promemoria a 24 ore e a -3 ore, turno palloni, sollecito presenze, sondaggio pre-partita. Un tipo nuovo che in futuro finisse in quella tabella +3 ore, sollecito presenze, sondaggio pre-partita. **Fa eccezione il turno palloni** (DD-040, dalla +`m26`): per i tipi `turno_palloni`, `turno_palloni_12h`, `_6h`, `_3h` e `turno_palloni_revocato` il +trigger non accoda nulla, restano push e centro notifiche. Un tipo nuovo che in futuro finisse in quella tabella viaggerebbe per email senza altro lavoro. Le notifiche già presenti quando si applica la migration **non** vengono inviate: il trigger non ha backfill. diff --git a/docs/modules/notifiche.md b/docs/modules/notifiche.md index 1229f0c..ab6a965 100644 --- a/docs/modules/notifiche.md +++ b/docs/modules/notifiche.md @@ -31,27 +31,68 @@ particolare il certificato in scadenza **non** genera push né voci nel centro n Cosa fa partire una notifica, a chi arriva e su quali canali. Sono i canali indipendenti descritti più sotto: **push** (solo ai dispositivi che hanno attivato «Notifiche»), **centro notifiche in-app** (storico in `notifiche_utente`, per tutti i giocatori) ed **email** -([notifiche-email.md](notifiche-email.md): ogni riga di `notifiche_utente` genera una mail, quindi -la colonna Email coincide con la colonna In-app). +([notifiche-email.md](notifiche-email.md): ogni riga di `notifiche_utente` genera una mail, **tranne +quelle del turno palloni**, che restano solo push e in-app: DD-040). -| # | Notifica | Cosa la fa partire | Destinatari | Push | In-app | Email | -| --- | -------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------- | ------ | ----- | -| 1 | **Promemoria a 24 ore** — «Promemoria: _titolo_», corpo «data alle ora» | Job `pg_cron` `promemoria-eventi-24h`, ogni ora, per gli eventi che iniziano entro le prossime 24 ore. Tipo `evento_promemoria_24h`. La push la manda il worker (M24). | I convocati dell'evento, o tutta la rosa attiva se `convocati` è vuoto; mai gli allenatori (DD-039). | sì (worker) | sì | sì | -| 2 | **Promemoria a 3 ore** — stesso testo | Job `pg_cron` `promemoria-eventi-3h`, ogni 15 minuti, per gli eventi che iniziano entro le prossime 3 ore. Tipo `evento_promemoria_3h`. La push la manda il worker (M24). | Come il promemoria a 24 ore. | sì (worker) | sì | sì | -| 3 | **Messaggio dello staff** — titolo fisso «Messaggio dallo staff», corpo il testo scritto (max 300 caratteri) | Un admin, dalla tab «Notifiche» della dashboard: «Invia messaggio a tutti» o il pulsante della riga di un giocatore. Route `notifica-personalizzata`, tipo `admin`. | Push: tutti i dispositivi iscritti, o quelli del giocatore scelto. In-app ed email: tutta la rosa attiva (allenatori compresi, anche senza dispositivi iscritti), o il giocatore scelto. | sì | sì | sì | -| 4 | **Turno palloni** — «Tocca a te prendere i palloni» / «Porta i palloni» | Un admin, con il pulsante nella pagina dell'evento. Solo eventi di tipo allenamento o partita. Route `promemoria-palloni`, tipo `turno_palloni`. | L'incaricato del turno di quell'evento e, se è un'altra persona, chi aveva i palloni all'evento precedente. Se per l'evento non c'è un incaricato non parte nulla. | sì | sì | sì | -| 5 | **Sollecito presenze** — «Manca la tua risposta» | Un admin o un allenatore, con il pulsante nella pagina dell'evento. Route `sollecita-presenze`, tipo `sollecita_presenze`. | I giocatori attivi (mai gli allenatori) che non hanno ancora risposto o hanno risposto «forse». | sì | sì | sì | -| 6 | **Sondaggio pre-partita** — «💩 Sondaggio pre-partita aperto» | Un admin, dalla pagina della partita. Route `apri-sondaggio` (vedi [Scout Live](scout-live.md)). | Push: tutti i dispositivi iscritti, tranne quelli degli allenatori. In-app ed email (tipo `sondaggio_cacche`): i giocatori attivi, allenatori esclusi, anche senza dispositivi iscritti. | sì | sì | sì | -| 7 | **Notifiche smart** — badge appena sbloccato, serie a un traguardo, obiettivo di squadra tra il 90 e il 100%, badge social vinto | L'app, mentre è aperta: sono calcolate in locale da `calcolaNotifiche()`. | Chi ha l'app aperta con uno slot giocatore in rosa selezionato; **mai l'allenatore**. Nessun invio verso altri dispositivi. | no (solo locale) | no | no | +| # | Notifica | Cosa la fa partire | Destinatari | Push | In-app | Email | +| --- | -------------------------------------------------------------------------------------------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ---------------- | ------ | --------------- | +| 1 | **Promemoria a 24 ore** — «Promemoria evento di _domani_ (o _oggi_): _titolo_», con data, ora e luogo | Job `pg_cron` `promemoria-eventi-24h`, ogni ora, per gli eventi che iniziano entro le prossime 24 ore. Tipo `evento_promemoria_24h`. La push la manda il worker (M24). | I convocati dell'evento, o tutta la rosa attiva se `convocati` è vuoto; mai gli allenatori (DD-039). | sì (worker) | sì | sì | +| 2 | **Promemoria a 3 ore** — stesso testo | Job `pg_cron` `promemoria-eventi-3h`, ogni 15 minuti, per gli eventi che iniziano entro le prossime 3 ore. Tipo `evento_promemoria_3h`. La push la manda il worker (M24). | Come il promemoria a 24 ore. | sì (worker) | sì | sì | +| 3 | **Messaggio dello staff** — titolo fisso «Messaggio dallo staff», corpo il testo scritto (max 300 caratteri) | Un admin, dalla tab «Notifiche» della dashboard: «Invia messaggio a tutti» o il pulsante della riga di un giocatore. Route `notifica-personalizzata`, tipo `admin`. | Push: tutti i dispositivi iscritti, o quelli del giocatore scelto. In-app ed email: tutta la rosa attiva (allenatori compresi, anche senza dispositivi iscritti), o il giocatore scelto. | sì | sì | sì | +| 4 | **Turno palloni** — «Turno palloni: incarico assegnato» / «Turno palloni: riconsegna» | **Automatica**, **12, 6 e 3 ore prima** dell'evento (DD-040): job `pg_cron` ogni 15 minuti, solo per allenamenti e partite con un incaricato confermato; tipi `turno_palloni_12h`, `_6h` e `_3h`, push dal worker. Se l'incaricato cambia, chi aveva già ricevuto un avviso riceve una volta «Turno palloni: incarico revocato» (tipo `turno_palloni_revocato`). **Manuale**, in più: un admin, con il pulsante nella pagina dell'evento; route `promemoria-palloni`, tipo `turno_palloni`, push dalla route. | L'incaricato del turno di quell'evento e, se è un'altra persona, chi aveva i palloni all'evento precedente. Se per l'evento non c'è un incaricato non parte nulla. | sì | sì | **no** (DD-040) | +| 5 | **Sollecito presenze** — «Conferma di partecipazione richiesta: _titolo_» | **Automatica**, in tre tempi (DD-040): **24, 12 e 6 ore** prima dell'evento, dallo stesso job; tipi `sollecita_presenze_24h`, `_12h`, `_6h`, push dal worker. **Manuale**, in più: un admin o un allenatore con il pulsante nella pagina dell'evento; route `sollecita-presenze`, tipo `sollecita_presenze`, push dalla route. | Automatica e manuale: i destinatari dell'evento (convocati, o tutta la rosa attiva se `convocati` è vuoto; mai gli allenatori) che in quel momento non hanno risposto o hanno risposto «forse». | sì | sì | sì | +| 6 | **Sondaggio pre-partita** — «💩 Sondaggio pre-partita aperto» | Un admin, dalla pagina della partita. Route `apri-sondaggio` (vedi [Scout Live](scout-live.md)). | Push: tutti i dispositivi iscritti, tranne quelli degli allenatori. In-app ed email (tipo `sondaggio_cacche`): i giocatori attivi, allenatori esclusi, anche senza dispositivi iscritti. | sì | sì | sì | +| 7 | **Notifiche smart** — badge appena sbloccato, serie a un traguardo, obiettivo di squadra tra il 90 e il 100%, badge social vinto | L'app, mentre è aperta: sono calcolate in locale da `calcolaNotifiche()`. | Chi ha l'app aperta con uno slot giocatore in rosa selezionato; **mai l'allenatore**. Nessun invio verso altri dispositivi. | no (solo locale) | no | no | -Tutte le notifiche generate dal server (righe 1-6) hanno tutti e tre i canali (DD-038). La -differenza sta in come parte la push: quelle dei pulsanti (3-6) la mandano le route dell'app, quelle -dei promemoria (1 e 2) nascono nel database e la manda il worker `mailer/` (vedi «Push dei -promemoria»). Le righe 4 e 5 riscrivono la notifica esistente se l'admin preme di nuovo il pulsante -per lo stesso evento, e lo stesso fa il sondaggio (vedi «Centro notifiche in-app»). +Le notifiche generate dal server (righe 1-6) hanno tutti e tre i canali (DD-038), tranne il turno palloni (riga 4), che non manda email (DD-040). La +differenza sta in come parte la push: quelle dei pulsanti (3 e 6) la mandano le route dell'app, quelle +automatiche (1, 2, 4 e 5) nascono nel database e la manda il worker `mailer/` (vedi «Push dei +promemoria»). Il sondaggio riscrive la notifica esistente se l'admin preme di nuovo il pulsante per +lo stesso evento (vedi «Centro notifiche in-app»). + +> **Stato delle righe 1, 2, 4 e 5 (DD-040).** Testi, orari e destinatari sono quelli **decisi** il +> 30/09/2026, non ancora in codice: mancano la migration `m26` e le funzioni SQL dei job. I pulsanti +> «Avvisa chi è di turno» e «Sollecita» **restano** e si aggiungono agli avvisi automatici. Fino al rilascio l'app si comporta come in 1.2.0: promemoria +> con il testo breve, turno palloni e sollecito presenze solo da pulsante. Le sezioni più in basso descrivono lo stato attuale; i testi dei due pulsanti diventano quelli nuovi. Cosa riceve l'allenatore, notifica per notifica: [allenatore.md](allenatore.md#notifiche). +### Testi delle notifiche + +Il testo è lo stesso su push, centro notifiche in-app ed email: titolo e corpo si scrivono una +volta sola. Nell'email il titolo diventa l'oggetto e il corpo segue il formato di +[notifiche-email.md](notifiche-email.md#contenuto-delle-mail) (link «Apri CrAPP» e avviso di +disattivazione). Tra `<…>` i valori che cambiano a ogni invio; le date sono `GG/MM/AAAA`. + +| # | Notifica | Titolo | Corpo | +| --- | ----------------------------- | ---------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | +| 1-2 | Promemoria 24 e 3 ore | `Promemoria evento di domani: `, oppure `…di oggi: ` | `Data: domani, `
`Ora: `
`Luogo: ` — «domani» se la data dell'evento è quella di domani, «oggi» altrimenti (fuso `Europe/Rome`, vale per entrambi i promemoria) | +| 3 | Messaggio dello staff | `Messaggio dallo staff` | Il testo scritto dall'admin, max 300 caratteri. | +| 4a | Turno palloni, all'incaricato | `Turno palloni: incarico assegnato` | `Evento: `
`Data: , ore `
`Incarico: custodia dei palloni al termine dell'evento`
`Riconsegna: ` — senza evento successivo la riga «Riconsegna» manca | +| 4b | Turno palloni, a chi li aveva | `Turno palloni: riconsegna` | `Evento: `
`Data: , ore `
`Incarico: riconsegna dei palloni in custodia dal turno precedente` | +| 4c | Turno palloni, revoca | `Turno palloni: incarico revocato` | `Evento: `
`Data: , ore `
`Incarico: non più a tuo carico per questo evento` — a chi aveva ricevuto un avviso (4a o 4b) e non è più destinatario; una sola volta | +| 5 | Sollecito presenze | `Conferma di partecipazione richiesta: ` | `Data: `
`Ora: `
`Luogo: `
`Richiesta di: ` (solo nel sollecito manuale, e solo se il nome c'è)
`Risposta attuale: nessuna` (oppure `forse`)
`Azione: indicare presente, assente o in ritardo` — stesso testo a 24, 12 e 6 ore | +| 6 | Sondaggio pre-partita | `💩 Sondaggio pre-partita aperto` | ` · ore . Quante cacche hai fatto? Rispondi prima del fischio d'inizio.` | + +Le righe 1-2, 4a, 4b e 5 sono i testi nuovi (DD-040, ancora da implementare): nasceranno nelle +funzioni SQL della migration `m26`. Quelli in uso oggi sono: promemoria «Promemoria: _titolo_» con corpo +«_data_ alle _ora_» (migration `m23`, senza luogo e senza «oggi/domani»); turno palloni in +`palloni-core.ts` (`avvisiPalloniEvento`: «Tocca a te prendere i palloni» / «Porta i palloni»); sollecito in +`sollecita-presenze.ts` («Manca la tua risposta»). Le righe 3 e 6 non cambiano: +`notifica-personalizzata.ts` e `cacche.ts` (`avvisoSondaggio`). + +**Notifiche smart (riga 7).** Solo locali: la notifica di sistema ha il titolo preceduto +dall'emoji. Non entrano nel centro notifiche e non generano email. + +| Situazione | Titolo | Corpo | +| ---------------------------------- | ----------------------- | -------------------------------------------------------------------------------------------------- | +| Badge sbloccato (🏅) | ` ` | ` : badge sbloccato, complimenti!` | +| Badge segreto | `Badge segreto: ` | Testo del badge (`notificaPush`, poi `celebrazione`), altrimenti «Hai scoperto un badge nascosto!» | +| A un passo da un badge (🔥) | `Sei a un passo` | ` e sblocchi .` — scatta con al massimo 2 unità mancanti | +| Serie a un traguardo esatto (⚡) | `Serie di ` | `Continuità da veterano: non fermarti ora.` | +| Obiettivo di squadra tra 90% e 99% | `: %` | Microtesto di `microcopyObiettivo()`, dipende dall'obiettivo | +| Badge social vinto | ` x` | `I tuoi compagni hanno votato per te.` | + ### Cosa non genera nessuna notifica - Creare, spostare, modificare o cancellare un evento: contano solo i promemoria prima @@ -81,27 +122,39 @@ Sono l'effetto attuale della logica, non scelte documentate altrove: - **Il registro non segue le modifiche all'evento:** se un evento viene spostato dopo che il promemoria è partito, non ne parte uno nuovo per la data nuova. Chi viene aggiunto ai convocati dopo riceve invece il suo promemoria, perché il registro è per giocatore. -- **Le notifiche 3, 4 e 5, una volta eliminate, non tornano da sole:** riappaiono solo se un admin - rimanda il messaggio o ripreme il pulsante. +- **Le notifiche eliminate non tornano da sole:** la 3 e la 6 riappaiono solo se un admin + rimanda il messaggio o riapre il sondaggio; quelle automatiche (1, 2, 4 e 5) non tornano affatto, + perché il registro ricorda di averle già generate. - **Un evento già iniziato non genera più promemoria.** -- **Quattro notifiche su sei dipendono da un gesto manuale** (3, 4, 5 e 6): se nessuno preme il - pulsante, nessuno viene avvisato. Gli unici automatismi sono i due promemoria. -- **Per ogni evento i giocatori ricevono al massimo due mail automatiche** (24 e 3 ore prima), - più quelle dei pulsanti manuali. +- **Due notifiche su sei dipendono solo da un gesto manuale** (3 e 6): se nessuno preme il pulsante, + nessuno viene avvisato. Le altre quattro sono automatiche (DD-040); per turno palloni e sollecito il + pulsante resta come aggiunta e può arrivare in più rispetto a quelle automatiche. +- **Il turno palloni parte solo se l'incaricato è confermato** (DD-040): per una partita la + proposta automatica di `completaTurni()` non salvata non basta. Se l'incaricato cambia dopo + un avviso, il nuovo riceve il proprio al giro successivo e il precedente riceve una sola volta + «Turno palloni: incarico revocato»; se viene poi riassegnato, non riceve un secondo avviso. +- **I solleciti cadono in finestre che non si sovrappongono** (DD-040): 24 ore tra 24 e 12 ore + prima dell'inizio, 12 ore tra 12 e 6, 6 ore sotto le 6. Un evento creato a meno di 12 ore + dall'inizio riceve solo quello della sua finestra. Chi risponde presente, assente o in ritardo + smette di riceverli; chi resta su «forse» li riceve tutti e tre. +- **Per ogni evento un giocatore riceve al massimo cinque mail automatiche** (DD-040): due + promemoria e tre solleciti, più quelle dei pulsanti manuali (sollecito, sondaggio). Il turno palloni + (avvisi automatici, revoca e pulsante) non manda email: restano la push e l'alert in app. - **Non esistono preferenze per tipo di notifica:** un solo interruttore per la push («Notifiche») e uno per le email («Email»). Il centro notifiche in-app non si può spegnere. ### Test che verificano il catalogo -| Cosa | Test | -| -------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------- | -| Promemoria 24 e 3 ore: destinatari (convocati, tutti i giocatori attivi se `convocati` è vuoto, mai l'allenatore), deduplica, eliminazione, coda push ed email | `test/integration/destinatari-notifiche.test.ts`, `promemoria-eventi.test.ts`, `push-promemoria.test.ts`, `notifiche-email.test.ts` | -| Messaggio dello staff: push a tutti i dispositivi (allenatore compreso) o a uno solo, in-app a tutta la rosa attiva, riservato agli admin | `destinatari-notifiche.test.ts`, `notifiche-utente.test.ts` | -| Turno palloni: incaricato e chi li aveva prima, solo allenamenti e partite, riservato agli admin | `destinatari-notifiche.test.ts`, `unit/palloni-core.test.ts` | -| Sollecito presenze: chi non ha risposto o ha detto «forse», mai l'allenatore; permesso ad allenatore e admin, non ai giocatori | `destinatari-notifiche.test.ts`, `unit/presenze.test.ts` | -| Sondaggio pre-partita: push senza allenatori, avviso in-app ed email ai giocatori attivi, `upsert`, riservato agli admin | `destinatari-notifiche.test.ts`, `sondaggio-notifiche.test.ts`, `unit/cacche.test.ts` | -| Worker: esiti, backoff, tetto giornaliero, iscrizioni scadute | `unit/mailer-core.test.ts` | -| L'allenatore è fuori dalla rosa di gioco | `unit/giocatori-squadra.test.ts` (`inRosa`) | +| Cosa | Test | +| --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------------------------------------------------------------------------------------------------------------------------------- | +| Promemoria 24 e 3 ore: destinatari (convocati, tutti i giocatori attivi se `convocati` è vuoto, mai l'allenatore), deduplica, eliminazione, coda push ed email | `test/integration/destinatari-notifiche.test.ts`, `promemoria-eventi.test.ts`, `push-promemoria.test.ts`, `notifiche-email.test.ts` | +| Messaggio dello staff: push a tutti i dispositivi (allenatore compreso) o a uno solo, in-app a tutta la rosa attiva, riservato agli admin | `destinatari-notifiche.test.ts`, `notifiche-utente.test.ts` | +| Turno palloni: incaricato e chi li aveva prima, solo allenamenti e partite, riservato agli admin | `destinatari-notifiche.test.ts`, `unit/palloni-core.test.ts` | +| Sollecito presenze: chi non ha risposto o ha detto «forse», mai l'allenatore; permesso ad allenatore e admin, non ai giocatori | `destinatari-notifiche.test.ts`, `unit/presenze.test.ts` | +| Sondaggio pre-partita: push senza allenatori, avviso in-app ed email ai giocatori attivi, `upsert`, riservato agli admin | `destinatari-notifiche.test.ts`, `sondaggio-notifiche.test.ts`, `unit/cacche.test.ts` | +| Promemoria con «oggi/domani», turno palloni a 12·6·3 ore e revoca, solleciti a 24·12·6 ore (manuale compreso): finestre, destinatari, una sola volta, registro (DD-040, **previsti**, da scrivere con la `m26`) | `promemoria-eventi.test.ts` (aggiornato), nuovo `notifiche-automatiche.test.ts`, `unit/palloni-core.test.ts` | +| Worker: esiti, backoff, tetto giornaliero, iscrizioni scadute | `unit/mailer-core.test.ts` | +| L'allenatore è fuori dalla rosa di gioco | `unit/giocatori-squadra.test.ts` (`inRosa`) | Le push dei test vanno a un servizio push finto in locale: si verifica **a chi** arrivano, non la consegna su un telefono. Non ha un test l'esclusione dell'allenatore dalle **notifiche smart**: la @@ -155,7 +208,7 @@ copre in particolare le sessioni lunghe della webapp (vedi il L'invio effettivo (`src/lib/webpush.server.ts`, funzione `inviaPush`) firma un JWT VAPID (ECDSA P-256), cifra `{title, body}` per il dispositivo destinatario e fa una POST all'endpoint push del browser; è riusato identico da `sollecita-presenze.ts`, -`promemoria-palloni.ts` e `apri-sondaggio.ts`. +`promemoria-palloni.ts` e `apri-sondaggio.ts` . Il testo viaggia **dentro** la push, cifrato in `aes128gcm` (RFC 8188/8291) con le chiavi del dispositivo: il service worker fa `event.data.json()` e mostra la notifica senza toccare la @@ -179,6 +232,9 @@ variabile d'ambiente. | `apri-sondaggio`, `sollecita-presenze`, `promemoria-palloni`, `notifiche-attive`, `notifica-personalizzata` | `richiediAdmin` — token della sessione Supabase, poi ruolo `admin` in `user_roles` | l'app, da un pulsante o una vista riservati agli admin | | `csi`, `push-config`, `push-subscribe` | nessuno | il browser prima del login, che una sessione non ce l'ha ancora | +Con DD-040 `promemoria-palloni` e `sollecita-presenze` restano, accanto ai job automatici (la seconda +anche per l'allenatore, `richiediGestoreEventi()`). + `notifiche-attive` è a sola lettura: non manda push, restituisce gli id giocatore con almeno un dispositivo iscritto in `push_subscriptions` (deduplicati). Alimenta la tab "Notifiche" della dashboard admin (vedi [Profilo giocatore](profilo-giocatore.md)), non l'invio effettivo. @@ -224,7 +280,9 @@ non richiede che il dispositivo abbia attivato «Notifiche», ha uno storico per `notifiche_utente` (vedi [DATABASE.md](../DATABASE.md)) con stato letto/non letto, e non è la stessa cosa delle notifiche smart (quelle restano locali, non salvate a database). -Cinque sorgenti scrivono in `notifiche_utente`, mai il client: +Cinque sorgenti scrivono in `notifiche_utente`, mai il client (con DD-040 le sorgenti 3 e 4 hanno in più un +job `pg_cron` come la 2: turno palloni a 12, 6 e 3 ore, sollecito a 24, 12 e 6 ore; i pulsanti restano; +l'elenco descrive lo stato attuale): 1. **Messaggio admin** — `notifica-personalizzata.ts` inserisce una riga per destinatario in parallelo all'invio push esistente (a tutta la rosa attiva se `giocatoreId` è omesso). @@ -285,6 +343,11 @@ DD-038). La push dei promemoria dipende solo dai dispositivi iscritti, non dall'interruttore «Email». Se il worker non ha le chiavi VAPID parte lo stesso e manda solo le email, segnalandolo nel log. +Con DD-040 il trigger accoda in `notifiche_push_coda` anche `turno_palloni_12h`, `_6h`, `_3h`, `turno_palloni_revocato` e +`sollecita_presenze_24h`, `_12h` e `_6h`, perché nascono nel database e nessuna route manda la +push (quelle dei pulsanti, `turno_palloni` e `sollecita_presenze`, restano fuori dalla coda): le regole di invio, backoff e scarto sono quelle qui sopra. Un sollecito in coda da oltre 3 ore si scarta come un +promemoria. + ## Canale email (M22) Ogni riga di `notifiche_utente` viene inviata anche per email da un worker esterno, con diff --git a/docs/modules/palloni.md b/docs/modules/palloni.md index 2441908..ab8e3ef 100644 --- a/docs/modules/palloni.md +++ b/docs/modules/palloni.md @@ -55,6 +55,13 @@ oggi`, stesso criterio delle presenze): un turno assegnato in anticipo per un al ## Route API pubblica `/api/public/promemoria-palloni` +> **Affiancata da un invio automatico (DD-040, non ancora implementato).** Oltre al pulsante, un job +> `pg_cron` ogni 15 minuti manda l'avviso **12, 6 e 3 ore prima** dell'evento, solo se per l'evento c'è un +> 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 +> [catalogo](notifiche.md#testi-delle-notifiche). Il resto della sezione descrive lo stato attuale. + 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 @@ -69,7 +76,7 @@ nessuna chiamata di rete. Stesso meccanismo di `apri-sondaggio` (vedi ## Limiti noti -- **L'invio è manuale**: nessun cron manda il promemoria da solo, se l'admin non preme il +- **L'invio è manuale** (DD-040 aggiunge un invio automatico 12, 6 e 3 ore prima, senza toglierlo): nessun cron manda il promemoria da solo, se l'admin non preme il pulsante non parte niente (DD-025). `destinatariPromemoriaPalloni()` — la versione "chi è di turno oggi" — resta in `palloni-core.ts` ma non la chiama più nessuno. - La rotazione non considera le assenze dichiarate: può proporre il turno a chi ha risposto diff --git a/docs/modules/presenze.md b/docs/modules/presenze.md index d6c282b..0c35350 100644 --- a/docs/modules/presenze.md +++ b/docs/modules/presenze.md @@ -76,6 +76,13 @@ src/routes/api/public/sollecita-presenze.ts └─ elimina le iscrizioni push scadute (404/410) ``` +> **Sollecito automatico (DD-040, non ancora implementato).** Il pulsante «Sollecita» e la route +> **restano**, ma `destinatariSollecito()` passa a guardare anche i convocati (convocati che non hanno +> risposto o hanno risposto «forse», o tutta la rosa se `convocati` è vuoto); in più un job `pg_cron` manda il sollecito **24, 12 e 6 ore prima** dell'evento a chi in +> quel momento non ha risposto o ha risposto «forse», tra i destinatari dell'evento (convocati, o +> tutta la rosa attiva; mai l'allenatore). Testo e finestre nel [catalogo](notifiche.md#catalogo-delle-notifiche). +> Il diagramma sopra descrive lo stato attuale. + Un evento conta ai fini delle statistiche di presenza solo se è di tipo `partita` o `allenamento` e il giocatore è tra i convocati (o non ci sono convocati specificati, cioè vale per tutta la rosa) — `eventiContanoPresenze()` in `presenze.ts`. @@ -86,7 +93,7 @@ vale per tutta la rosa) — `eventiContanoPresenze()` in `presenze.ts`. - Aggiornamento ottimistico della cache locale dopo ogni salvataggio: nessuna rilettura dal server, la UI risponde subito. -- Il sollecito è **manuale**: nessun cron nel repository lo richiama automaticamente, parte +- Il sollecito è **manuale** (da DD-040 si aggiungono tre solleciti automatici, che non lo sostituiscono): nessun cron nel repository lo richiama automaticamente, parte solo dal bottone admin, e la route verifica il ruolo lato server con `richiediAdmin` (DD-024). - Ognuno risponde **solo per sé**, e non è più una regola della sola interfaccia: dalla