From cdf8b144e2dd04a1717c39ba41564c5eea771f0a Mon Sep 17 00:00:00 2001 From: Davide Grilli Date: Thu, 1 Oct 2026 14:33:38 +0200 Subject: [PATCH] Turno palloni: un solo avviso, 3 ore prima dell'evento (DD-042) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit La migration m27 riduce il job automatico alla sola fascia delle 3 ore: l'avviso arriva una volta a chi deve portare i palloni e a chi li deve prendere. I tipi turno_palloni_12h e _6h restano ammessi per le notifiche già generate e per la revoca. Migration applicata in produzione. Test di integrazione aggiornati (nessun avviso a 4,5 e 9 ore); documentati DD-042, moduli, DATABASE, ROADMAP, glossario e changelog. Co-Authored-By: Claude Sonnet 5.5 --- docs/CHANGELOG.md | 6 + docs/DATABASE.md | 18 +-- docs/GLOSSARIO.md | 2 +- docs/ROADMAP.md | 2 +- docs/WORKER_EMAIL.md | 2 +- docs/decisions/DD-025.md | 4 +- docs/decisions/DD-040.md | 3 +- docs/decisions/DD-042.md | 36 ++++++ docs/decisions/README.md | 1 + docs/modules/notifiche.md | 44 +++---- docs/modules/palloni.md | 4 +- .../20261001120000_m27_palloni_solo_3h.sql | 22 ++++ test/README.md | 12 +- .../integration/notifiche-automatiche.test.ts | 109 +++++++++++------- 14 files changed, 175 insertions(+), 90 deletions(-) create mode 100644 docs/decisions/DD-042.md create mode 100644 supabase/migrations/20261001120000_m27_palloni_solo_3h.sql diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md index d7b7053..73746e9 100644 --- a/docs/CHANGELOG.md +++ b/docs/CHANGELOG.md @@ -15,6 +15,12 @@ versioni `0.y.z` erano pre-release. ### Modificato +- **Turno palloni: un solo avviso, 3 ore prima** — a chi deve portare i palloni e a chi li deve + prendere; prima ne partivano tre (12, 6 e 3 ore). Migration `m27_palloni_solo_3h`, applicata in + produzione il 01/10/2026 (DD-042). + +### Modificato + - **Avviso certificati: giocatore a 30 giorni** — il giocatore vede l'avviso giallo sul proprio certificato medico da un mese prima della scadenza (prima 7 giorni). Per gli admin non cambia nulla: 7 giorni prima in giallo, nero dopo la scadenza (DD-041). diff --git a/docs/DATABASE.md b/docs/DATABASE.md index 416ba6b..ccace6b 100644 --- a/docs/DATABASE.md +++ b/docs/DATABASE.md @@ -151,15 +151,15 @@ Le notifiche automatiche nascono nel database, da job `pg_cron` che chiamano fun riservate alla service role (anon e utenti autenticati non possono chiamarle). Testi e regole sono in [modules/notifiche.md](modules/notifiche.md); le decisioni in DD-037, DD-038 e DD-040. -| Funzione (migration) | Cosa fa | Job `pg_cron` | -| --------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------- | -| `genera_promemoria_eventi(tipo, finestra)` (M17, M23, M26) | Promemoria a 24 e 3 ore, con «oggi/domani», data, ora e luogo. | `promemoria-eventi-24h` (ogni ora), `-3h` (15 min) | -| `genera_avvisi_palloni()` (M26), con `genera_avvisi_palloni_fascia()` | Avvisi del turno palloni a 12, 6 e 3 ore per gli eventi con un incaricato confermato; poi chiama `genera_revoche_palloni()`. | `avvisi-palloni-automatici` (15 min) | -| `genera_revoche_palloni()` (M26) | Una revoca per chi aveva un avviso automatico e non è più destinatario, per eventi non ancora iniziati; guarda il registro. | (dentro `genera_avvisi_palloni()`) | -| `genera_solleciti_presenze()` (M26), con `..._fascia()` | Solleciti a 24, 12 e 6 ore ai destinatari dell'evento senza risposta o con «forse». | `solleciti-presenze-automatici` (15 min) | -| `inizio_evento(data, ora)` (M26) | Istante d'inizio di un evento in fuso `Europe/Rome`; `NULL` se l'ora non è valida, e l'evento viene solo escluso. | — | -| `giocatori_destinatari_evento(evento)` (M17, M21, M25) | Giocatori attivi destinatari: i convocati, o tutta la rosa se `convocati` è vuoto; mai gli allenatori. | — | -| `accoda_notifica_email()`, `accoda_push_promemoria()` (M22, M24, M26) | Trigger su `notifiche_utente`: la mail salta i tipi `turno_palloni*`; la push accoda i tipi automatici (non quelli dei pulsanti). | — | +| Funzione (migration) | Cosa fa | Job `pg_cron` | +| -------------------------------------------------------------------------- | -------------------------------------------------------------------------------------------------------------------------------------------------- | -------------------------------------------------- | +| `genera_promemoria_eventi(tipo, finestra)` (M17, M23, M26) | Promemoria a 24 e 3 ore, con «oggi/domani», data, ora e luogo. | `promemoria-eventi-24h` (ogni ora), `-3h` (15 min) | +| `genera_avvisi_palloni()` (M26, M27), con `genera_avvisi_palloni_fascia()` | Avviso del turno palloni a 3 ore (M27; in M26 erano 12, 6 e 3) per gli eventi con un incaricato confermato; poi chiama `genera_revoche_palloni()`. | `avvisi-palloni-automatici` (15 min) | +| `genera_revoche_palloni()` (M26) | Una revoca per chi aveva un avviso automatico e non è più destinatario, per eventi non ancora iniziati; guarda il registro. | (dentro `genera_avvisi_palloni()`) | +| `genera_solleciti_presenze()` (M26), con `..._fascia()` | Solleciti a 24, 12 e 6 ore ai destinatari dell'evento senza risposta o con «forse». | `solleciti-presenze-automatici` (15 min) | +| `inizio_evento(data, ora)` (M26) | Istante d'inizio di un evento in fuso `Europe/Rome`; `NULL` se l'ora non è valida, e l'evento viene solo escluso. | — | +| `giocatori_destinatari_evento(evento)` (M17, M21, M25) | Giocatori attivi destinatari: i convocati, o tutta la rosa se `convocati` è vuoto; mai gli allenatori. | — | +| `accoda_notifica_email()`, `accoda_push_promemoria()` (M22, M24, M26) | Trigger su `notifiche_utente`: la mail salta i tipi `turno_palloni*`; la push accoda i tipi automatici (non quelli dei pulsanti). | — | ## Badge diff --git a/docs/GLOSSARIO.md b/docs/GLOSSARIO.md index 1812865..28565bd 100644 --- a/docs/GLOSSARIO.md +++ b/docs/GLOSSARIO.md @@ -26,4 +26,4 @@ il campionato. Dove serve il dettaglio c'è il rimando al modulo. | **Worker** | Il container Docker in `mailer/` che invia email e push dei promemoria con la service role ([WORKER_EMAIL.md](WORKER_EMAIL.md)). | | **Service role** | Chiave Supabase che bypassa la RLS. Solo server e worker, mai il client ([OPERATIONS.md](OPERATIONS.md)). | | **DD-XXX** | Decisione registrata in [decisions/](decisions/README.md). | -| **M1…M26** | Numero d'ordine delle migration (`m26_notifiche_automatiche`…) in `supabase/migrations/`. | +| **M1…M27** | Numero d'ordine delle migration (`m27_palloni_solo_3h`…) in `supabase/migrations/`. | diff --git a/docs/ROADMAP.md b/docs/ROADMAP.md index 6db1304..1f89772 100644 --- a/docs/ROADMAP.md +++ b/docs/ROADMAP.md @@ -44,7 +44,7 @@ Tutto quello che è rilasciato, fino alla versione 1.0.0 (vedi `CHANGELOG.md`). - [x] Notifiche email — ogni notifica in-app inviata anche per email agli account registrati, da un worker Docker sull'host di casa via Gmail, con interruttore «Email» in Profilo ([specifica](modules/notifiche-email.md), DD-036) -- [x] Notifiche automatiche — promemoria con «oggi/domani», turno palloni 12, 6 e 3 ore prima, sollecito +- [x] Notifiche automatiche — promemoria con «oggi/domani», turno palloni 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) ## Prossimo diff --git a/docs/WORKER_EMAIL.md b/docs/WORKER_EMAIL.md index 57e03b2..f24fc90 100644 --- a/docs/WORKER_EMAIL.md +++ b/docs/WORKER_EMAIL.md @@ -1,7 +1,7 @@ # Worker email: installazione e gestione Come si mette in funzione, si controlla e si diagnostica il worker (`mailer/`) che invia le notifiche -per email e le push delle notifiche che nascono nel database: promemoria a 24 e 3 ore, turno palloni a 12, 6 e 3 ore con revoca, solleciti presenze a 24, 12 e 6 ore (DD-040). Cosa fa il canale email sta in +per email e le push delle notifiche che nascono nel database: promemoria a 24 e 3 ore, turno palloni a 3 ore con revoca, solleciti presenze a 24, 12 e 6 ore (DD-040). Cosa fa il canale email sta in [modules/notifiche-email.md](modules/notifiche-email.md), la push dei promemoria in [modules/notifiche.md](modules/notifiche.md#push-dei-promemoria-m24); perché è fatto così in [DD-036](decisions/DD-036.md) diff --git a/docs/decisions/DD-025.md b/docs/decisions/DD-025.md index 061a06b..67ac8f4 100644 --- a/docs/decisions/DD-025.md +++ b/docs/decisions/DD-025.md @@ -50,5 +50,5 @@ torna utile, e con lui il segreto: DD-024 descrive già come farlo. --- -**Aggiornamento (30 settembre 2026)** — DD-040 affianca al pulsante un invio automatico a 12, 6 e 3 ore -prima dell'evento, per gli eventi con un incaricato confermato; il pulsante e la route restano. +**Aggiornamento (30 settembre 2026)** — DD-040 affianca al pulsante un invio automatico (poi ridotto a uno solo, 3 ore +prima, da DD-042), per gli eventi con un incaricato confermato; il pulsante e la route restano. diff --git a/docs/decisions/DD-040.md b/docs/decisions/DD-040.md index 7feb8f3..748f87f 100644 --- a/docs/decisions/DD-040.md +++ b/docs/decisions/DD-040.md @@ -1,8 +1,7 @@ # DD-040 — Promemoria con «oggi» o «domani», turno palloni e solleciti automatici **Data:** 30 settembre 2026 -**Stato:** Accettata (implementata e verificata in locale: migration `m26`, job, route e test; la -migration non è ancora applicata in produzione) +**Stato:** Accettata (implementata: migration `m26`, applicata in produzione il 30 settembre 2026, job, route e test). Il turno palloni è poi passato a un solo avviso a 3 ore da [DD-042](DD-042.md). **Contesto** Quattro notifiche su sei partivano da un pulsante premuto da un admin (DD-025 per i palloni): se nessuno diff --git a/docs/decisions/DD-042.md b/docs/decisions/DD-042.md new file mode 100644 index 0000000..3da08cc --- /dev/null +++ b/docs/decisions/DD-042.md @@ -0,0 +1,36 @@ +# DD-042 — Turno palloni: un solo avviso, 3 ore prima + +**Data:** 1 ottobre 2026 +**Stato:** Accettata (migration `m27`, applicata in produzione il 1 ottobre 2026; modifica la parte «turno palloni» di [DD-040](DD-040.md)) + +**Contesto** +DD-040 manda l'avviso del turno palloni a 12, 6 e 3 ore prima dell'evento: tre messaggi identici per +chi deve portare i palloni e per chi deve prenderli. Sono troppi per un'informazione che non cambia. + +**Decisione** + +1. L'avviso automatico parte **una sola volta, 3 ore prima** dell'inizio (finestra «meno di 3 ore»), a + chi deve portare i palloni («incarico assegnato») e a chi li deve prendere («riconsegna», se è + un'altra persona). Testi, destinatari, push senza email e condizione dell'incaricato confermato + restano quelli di DD-040. +2. La revoca resta: chi è stato avvisato e poi non è più destinatario riceve una sola volta «incarico + revocato». Un evento creato o assegnato a meno di 3 ore dall'inizio riceve l'avviso al giro + successivo del job. +3. **Migration `m27`:** `genera_avvisi_palloni()` chiama solo la fascia `turno_palloni_3h`. I tipi + `turno_palloni_12h` e `_6h` **restano nei `CHECK`** e nella revoca, perché notifiche e righe di + registro già generate li usano; non ne nascono di nuovi. +4. Il pulsante «Avvisa chi è di turno» non cambia. I solleciti presenze (24, 12 e 6 ore) non cambiano. + +**Alternative scartate** + +- Togliere i tipi `12h` e `6h` dai `CHECK` → le righe già generate in produzione non sarebbero più + valide e la revoca non vedrebbe più chi ha ricevuto quegli avvisi. +- Tenere anche un avviso a 12 ore → scartata su indicazione esplicita: uno solo. + +**Conseguenze** + +- Chi non apre l'app nelle 3 ore prima può non vedere l'avviso; la push è il canale principale. +- Per le notifiche palloni già generate con DD-040 non cambia nulla. + +**Riesame** +Se 3 ore risultano poche per procurarsi i palloni, alzare la finestra (una sola, mai più avvisi). diff --git a/docs/decisions/README.md b/docs/decisions/README.md index de708db..dcf0aae 100644 --- a/docs/decisions/README.md +++ b/docs/decisions/README.md @@ -86,6 +86,7 @@ Una decisione sostituita non si cancella: si cambia il suo **Stato** e si rimand | [DD-038](DD-038.md) | Tre canali per ogni notifica | | [DD-039](DD-039.md) | Niente promemoria all'allenatore | | [DD-040](DD-040.md) | Palloni e solleciti automatici | +| [DD-042](DD-042.md) | Turno palloni: un solo avviso a 3 ore | | [DD-041](DD-041.md) | Avviso certificati: giocatore a 30 giorni | **In valutazione** diff --git a/docs/modules/notifiche.md b/docs/modules/notifiche.md index 93bde69..1b24638 100644 --- a/docs/modules/notifiche.md +++ b/docs/modules/notifiche.md @@ -34,15 +34,15 @@ 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, **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 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 | +| # | 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**, **una sola volta, 3 ore prima** dell'evento (DD-040, DD-042): job `pg_cron` ogni 15 minuti, solo per allenamenti e partite con un incaricato confermato; tipo `turno_palloni_3h` (`_12h` e `_6h` esistono solo nelle notifiche generate prima di `m27`), 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 | 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 @@ -51,8 +51,8 @@ promemoria»). Il sondaggio riscrive la notifica esistente se l'admin preme di n lo stesso evento (vedi «Centro notifiche in-app»). > **Righe 1, 2, 4 e 5 (DD-040).** Implementate con la migration `m26` (job `pg_cron`, testi, revoca) e -> verificate in locale; la migration va ancora applicata in produzione. Finché non lo è, l'app si comporta -> come in 1.2.0: promemoria con il testo breve, turno palloni e sollecito presenze solo da pulsante. +> applicate in produzione (`m26` il 30/09/2026; `m27` il 01/10/2026, che porta il turno palloni a un solo +> avviso 3 ore prima, DD-042). Cosa riceve l'allenatore, notifica per notifica: [allenatore.md](allenatore.md#notifiche). @@ -146,16 +146,16 @@ Sono l'effetto attuale della logica, non scelte documentate altrove: ### 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` | -| 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, `m26`) | `integration/notifiche-automatiche.test.ts` (nuovo), `promemoria-eventi.test.ts` (testo oggi/domani), `destinatari-notifiche.test.ts` (pulsanti), `unit/palloni-core.test.ts`, `unit/presenze.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 3 ore e revoca, solleciti a 24·12·6 ore (manuale compreso): finestre, destinatari, una sola volta, registro (DD-040, `m26`; palloni a 3 ore: DD-042, `m27`) | `integration/notifiche-automatiche.test.ts`, `promemoria-eventi.test.ts` (testo oggi/domani), `destinatari-notifiche.test.ts` (pulsanti), `unit/palloni-core.test.ts`, `unit/presenze.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 @@ -282,7 +282,7 @@ non richiede che il dispositivo abbia attivato «Notifiche», ha uno storico per stessa cosa delle notifiche smart (quelle restano locali, non salvate a database). 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, che scrive `turno_palloni_12h`/`_6h`/`_3h`/`_revocato` e `sollecita_presenze_24h`/ +job `pg_cron` come la 2, che scrive `turno_palloni_3h`/`_revocato` (`_12h`/`_6h` solo fino a `m26`) e `sollecita_presenze_24h`/ `_12h`/`_6h`; i pulsanti restano e scrivono `turno_palloni` e `sollecita_presenze`): 1. **Messaggio admin** — `notifica-personalizzata.ts` inserisce una riga per destinatario diff --git a/docs/modules/palloni.md b/docs/modules/palloni.md index c7358a0..0aa6f3e 100644 --- a/docs/modules/palloni.md +++ b/docs/modules/palloni.md @@ -11,7 +11,7 @@ Gestire un turno a rotazione condiviso per chi porta e riporta i palloni ad allenamenti e partite, con proposta automatica, possibilità di modifica manuale e avvisi push, nel centro -notifiche e senza email a 12, 6 e 3 ore dall'evento (DD-040). Gli eventi extra-campo (`tipo === "evento"`, es. cena di squadra) e i +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 compleanni non c'entrano: non richiedono palloni. --- @@ -56,7 +56,7 @@ 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, migration `m26`).** 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 +> `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 > 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 diff --git a/supabase/migrations/20261001120000_m27_palloni_solo_3h.sql b/supabase/migrations/20261001120000_m27_palloni_solo_3h.sql new file mode 100644 index 0000000..716b0a8 --- /dev/null +++ b/supabase/migrations/20261001120000_m27_palloni_solo_3h.sql @@ -0,0 +1,22 @@ +-- M27 — Turno palloni: un solo avviso, 3 ore prima dell'evento (DD-042) +-- +-- Con M26 il job mandava l'avviso a 12, 6 e 3 ore. Ne basta uno, 3 ore prima, a chi deve portare +-- i palloni e a chi li deve prendere. Cambia solo il job: i tipi `turno_palloni_12h` e `_6h` +-- restano ammessi dai CHECK perché esistono notifiche e righe di registro già generate (anche la +-- revoca le guarda), ma non ne nascono di nuove. Le funzioni `genera_avvisi_palloni_fascia` e +-- `genera_revoche_palloni` non cambiano. + +CREATE OR REPLACE FUNCTION public.genera_avvisi_palloni() +RETURNS void +LANGUAGE plpgsql +SECURITY DEFINER +SET search_path = public +AS $$ +BEGIN + PERFORM public.genera_avvisi_palloni_fascia('turno_palloni_3h', interval '0', interval '3 hours'); + PERFORM public.genera_revoche_palloni(); +END; +$$; + +REVOKE ALL ON FUNCTION public.genera_avvisi_palloni() FROM PUBLIC, anon, authenticated; +GRANT EXECUTE ON FUNCTION public.genera_avvisi_palloni() TO service_role; diff --git a/test/README.md b/test/README.md index 2979f9d..5b7a5e2 100644 --- a/test/README.md +++ b/test/README.md @@ -24,12 +24,12 @@ la consegna effettiva a schermo bloccato richiede un telefono e il servizio push ## Struttura -| Cartella | Cosa verifica | Serve rete? | -| -------------- | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------- | -| `unit/` | Logica di dominio pura: badge, serie, palloni, pagelle, MVP, cacche, scout, obiettivi, notifiche, parsing CSI, dati della rosa. Più le funzioni pure isolabili nei moduli con hook/rete (validazione upload, guardie push, JWT VAPID, cattura errori, avatar, guardia admin delle route, lettura del `.env` ed elenco dei bucket dello script di backup, logica del worker in `mailer/` (email e push dei promemoria), preferenza «Email» e avviso del sondaggio) | No | -| `integration/` | Le route `/api/public/*` sul server di sviluppo: risposte, cache, validazione degli input. Più schema e permessi del Profilo Giocatore (`schema-profili`) contro il database configurato; permessi per ruolo, incluse le policy di M11, le deroghe dell'amministratore e le tabelle lasciate aperte di proposito (`permessi`), accesso alle route di notifica (`permessi-route`), semantica degli upsert e vincoli (`scritture`), le letture lato server delle route push (`lettori-server`) e la pulizia a cascata dei dati collegati alla cancellazione di un evento (`pulizia-evento`, M14) la funzione di bonifica delle righe orfane (`bonifica-evento`, M16), il centro notifiche in-app (`notifiche-utente`, M17), la coda delle email e le sue funzioni RPC (`notifiche-email`, M22), i promemoria evento che non ricompaiono se eliminati (`promemoria-eventi`, M23), la coda delle push dei promemoria (`push-promemoria`, M24), i job automatici di turno palloni e sollecito presenze con revoca, finestre e testi identici al codice (`notifiche-automatiche`, M26, DD-040) e il sondaggio in-app/email (`sondaggio-notifiche`, M24), i destinatari delle notifiche per ruolo (`destinatari-notifiche`: promemoria, messaggio dello staff, sondaggio, sollecito e turno palloni con allenatore e giocatore), la nascita pubblica sincronizzata dal profilo (`nascita-pubblica`, M18) e il suo backfill per i profili preesistenti (`backfill-nascita-esistenti`, M19 — unico test che esegue SQL diretto via `docker exec` nel container Postgres, perché PostgREST non espone quell'update multi-tabella come RPC) sul database locale | Sì | -| `e2e/` | Percorsi completi sull'app servita: schermate, dati CSI fino alla pagina, file PWA, 404 | Sì | -| `helpers/` | Avvio del server di test e mini-harness condiviso | — | +| Cartella | Cosa verifica | Serve rete? | +| -------------- | --------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | ----------- | +| `unit/` | Logica di dominio pura: badge, serie, palloni, pagelle, MVP, cacche, scout, obiettivi, notifiche, parsing CSI, dati della rosa. Più le funzioni pure isolabili nei moduli con hook/rete (validazione upload, guardie push, JWT VAPID, cattura errori, avatar, guardia admin delle route, lettura del `.env` ed elenco dei bucket dello script di backup, logica del worker in `mailer/` (email e push dei promemoria), preferenza «Email» e avviso del sondaggio) | No | +| `integration/` | Le route `/api/public/*` sul server di sviluppo: risposte, cache, validazione degli input. Più schema e permessi del Profilo Giocatore (`schema-profili`) contro il database configurato; permessi per ruolo, incluse le policy di M11, le deroghe dell'amministratore e le tabelle lasciate aperte di proposito (`permessi`), accesso alle route di notifica (`permessi-route`), semantica degli upsert e vincoli (`scritture`), le letture lato server delle route push (`lettori-server`) e la pulizia a cascata dei dati collegati alla cancellazione di un evento (`pulizia-evento`, M14) la funzione di bonifica delle righe orfane (`bonifica-evento`, M16), il centro notifiche in-app (`notifiche-utente`, M17), la coda delle email e le sue funzioni RPC (`notifiche-email`, M22), i promemoria evento che non ricompaiono se eliminati (`promemoria-eventi`, M23), la coda delle push dei promemoria (`push-promemoria`, M24), i job automatici di turno palloni e sollecito presenze con revoca, finestre e testi identici al codice (`notifiche-automatiche`, M26 e M27, DD-040 e DD-042: palloni un solo avviso a 3 ore) e il sondaggio in-app/email (`sondaggio-notifiche`, M24), i destinatari delle notifiche per ruolo (`destinatari-notifiche`: promemoria, messaggio dello staff, sondaggio, sollecito e turno palloni con allenatore e giocatore), la nascita pubblica sincronizzata dal profilo (`nascita-pubblica`, M18) e il suo backfill per i profili preesistenti (`backfill-nascita-esistenti`, M19 — unico test che esegue SQL diretto via `docker exec` nel container Postgres, perché PostgREST non espone quell'update multi-tabella come RPC) sul database locale | Sì | +| `e2e/` | Percorsi completi sull'app servita: schermate, dati CSI fino alla pagina, file PWA, 404 | Sì | +| `helpers/` | Avvio del server di test e mini-harness condiviso | — | ## Server di sviluppo con Node vecchio diff --git a/test/integration/notifiche-automatiche.test.ts b/test/integration/notifiche-automatiche.test.ts index c7b2245..68e2ab3 100644 --- a/test/integration/notifiche-automatiche.test.ts +++ b/test/integration/notifiche-automatiche.test.ts @@ -5,7 +5,8 @@ * Esegue i job veri del database (`genera_avvisi_palloni`, `genera_solleciti_presenze`) su eventi * creati con orari relativi a «adesso» e verifica: * - * - le finestre contigue e senza sovrapposizione (palloni 12·6·3 ore, solleciti 24·12·6 ore); + * - le finestre (palloni: un solo avviso nelle 3 ore prima; solleciti 24·12·6 ore, contigue e senza + * sovrapposizione); * - i destinatari (incaricato e turno precedente; convocati senza risposta o con «forse»; mai * l'allenatore né chi non è più attivo); * - una sola generazione per giocatore, evento e tipo, anche se la notifica viene eliminata; @@ -46,12 +47,14 @@ if (!locale) { const INATTIVO = "g9"; const ALLENATORE = "g10"; - // Palloni: in ordine cronologico PASSATO < PREC < MAIN < EV6 < EV12 < LONTANO. + // Palloni: in ordine cronologico PASSATO < PREC < MAIN < SECONDO < TERZO < FUORI4 < FUORI9 < LONTANO. const PASSATO = `${PREFISSO}-passato`; const PREC = `${PREFISSO}-prec`; const MAIN = `${PREFISSO}-main`; - const EV6 = `${PREFISSO}-ev6`; - const EV12 = `${PREFISSO}-ev12`; + const SECONDO = `${PREFISSO}-secondo`; + const TERZO = `${PREFISSO}-terzo`; + const FUORI4 = `${PREFISSO}-fuori4`; + const FUORI9 = `${PREFISSO}-fuori9`; const LONTANO = `${PREFISSO}-lontano`; const GENERICO = `${PREFISSO}-generico`; const COPPIA_A = `${PREFISSO}-coppia-a`; @@ -68,8 +71,10 @@ if (!locale) { PASSATO, PREC, MAIN, - EV6, - EV12, + SECONDO, + TERZO, + FUORI4, + FUORI9, LONTANO, GENERICO, COPPIA_A, @@ -266,36 +271,51 @@ if (!locale) { ); await scrivi(`giocatori_squadra?id=eq.${INATTIVO}`, "PATCH", { attivo: false }, tokenAdmin); - // Palloni. Le ore scelte stanno a più di mezz'ora dai confini delle finestre. + // Palloni (DD-042): un solo avviso, nelle 3 ore prima. MAIN, SECONDO e TERZO sono dentro la + // finestra; FUORI4 e FUORI9 hanno un incaricato ma sono ancora fuori (tra 4,5 e 9 ore). + // Le ore scelte stanno a più di mezz'ora dai confini della finestra. await creaEvento(PASSATO, "partita", tra(-60)); await creaEvento(PREC, "allenamento", tra(-30)); - await creaEvento(MAIN, "allenamento", tra(2.5)); - await creaEvento(EV6, "partita", tra(4.5)); - await creaEvento(EV12, "allenamento", tra(9)); + await creaEvento(MAIN, "allenamento", tra(0.8)); + await creaEvento(SECONDO, "partita", tra(1.5)); + await creaEvento(TERZO, "allenamento", tra(2.2)); + await creaEvento(FUORI4, "allenamento", tra(4.5)); + await creaEvento(FUORI9, "allenamento", tra(9)); await creaEvento(LONTANO, "allenamento", tra(40)); - await creaEvento(GENERICO, "evento", tra(2.2)); + await creaEvento(GENERICO, "evento", tra(1.9)); await assegna(PREC, G5); await assegna(MAIN, G4); - await assegna(EV6, G7); - await assegna(EV12, G8); + await assegna(SECONDO, G7); + await assegna(TERZO, G8); + await assegna(FUORI4, G7); + await assegna(FUORI9, G8); await assegna(GENERICO, G4); // extra-campo: il turno non conta // --- Turno palloni: finestre e destinatari --------------------------------------------- - await prova("ogni evento riceve l'avviso della sua fascia e di nessun'altra", async () => { - await esegui("genera_avvisi_palloni"); - assert.deepEqual(await destinatari(MAIN, "turno_palloni_3h"), [G4, G5]); - assert.deepEqual(await destinatari(EV6, "turno_palloni_6h"), [G4, G7]); - assert.deepEqual(await destinatari(EV12, "turno_palloni_12h"), [G7, G8]); - for (const [evento, altri] of [ - [MAIN, ["turno_palloni_6h", "turno_palloni_12h"]], - [EV6, ["turno_palloni_3h", "turno_palloni_12h"]], - [EV12, ["turno_palloni_3h", "turno_palloni_6h"]], - ] as const) { - for (const tipo of altri) { - assert.equal((await notifiche(evento, tipo)).length, 0, `${evento} non ha ${tipo}`); + await prova( + "un solo avviso, 3 ore prima, a chi porta i palloni e a chi li prende", + async () => { + await esegui("genera_avvisi_palloni"); + assert.deepEqual(await destinatari(MAIN, "turno_palloni_3h"), [G4, G5]); + assert.deepEqual(await destinatari(SECONDO, "turno_palloni_3h"), [G4, G7]); + assert.deepEqual(await destinatari(TERZO, "turno_palloni_3h"), [G7, G8]); + for (const evento of [MAIN, SECONDO, TERZO]) { + for (const tipo of ["turno_palloni_12h", "turno_palloni_6h"]) { + assert.equal((await notifiche(evento, tipo)).length, 0, `${evento} non ha ${tipo}`); + } } - } - }); + }, + ); + + await prova( + "a 4,5 e 9 ore dall'inizio non parte nessun avviso, anche con l'incaricato", + async () => { + assert.equal((await notifiche(FUORI4)).length, 0, "tra 4,5 ore: prima era la fascia 6 ore"); + assert.equal((await notifiche(FUORI9)).length, 0, "tra 9 ore: prima era la fascia 12 ore"); + assert.equal((await registro(FUORI4)).length, 0); + assert.equal((await registro(FUORI9)).length, 0); + }, + ); await prova( "nessun avviso per eventi lontani, passati, extra-campo o senza incaricato", @@ -314,8 +334,8 @@ if (!locale) { const turni = await turniDelDatabase(); for (const [evento, tipo] of [ [MAIN, "turno_palloni_3h"], - [EV6, "turno_palloni_6h"], - [EV12, "turno_palloni_12h"], + [SECONDO, "turno_palloni_3h"], + [TERZO, "turno_palloni_3h"], ] as const) { const attesi = avvisiPalloniEvento(turni, eventi, evento); assert.equal(attesi.length, 2, evento); @@ -330,7 +350,7 @@ if (!locale) { const [incarico] = (await notifiche(MAIN, "turno_palloni_3h")).filter( (r) => r.giocatore_id === G4, ); - const eventiEv6 = eventi.find((e) => e.id === EV6)!; + const eventoSecondo = eventi.find((e) => e.id === SECONDO)!; const main = eventi.find((e) => e.id === MAIN)!; assert.equal( incarico?.corpo, @@ -338,7 +358,7 @@ if (!locale) { `Evento: ${PREFISSO} main`, `Data: ${gg(main.data)}, ore ${main.ora}`, "Incarico: custodia dei palloni al termine dell'evento", - `Riconsegna: ${gg(eventiEv6.data)}`, + `Riconsegna: ${gg(eventoSecondo.data)}`, ].join("\n"), "la riconsegna è la data dell'evento successivo", ); @@ -349,8 +369,8 @@ if (!locale) { assert.equal((await registro(MAIN, "turno_palloni_3h")).length, 2); const righe = [ ...(await notifiche(MAIN, "turno_palloni_3h")), - ...(await notifiche(EV6, "turno_palloni_6h")), - ...(await notifiche(EV12, "turno_palloni_12h")), + ...(await notifiche(SECONDO, "turno_palloni_3h")), + ...(await notifiche(TERZO, "turno_palloni_3h")), ]; const ids = righe.map((r) => r.id); assert.equal(ids.length, 6); @@ -385,13 +405,13 @@ if (!locale) { "ora non valida: escluso, senza errori", ); - await scrivi(`turni_palloni?evento_id=eq.${EV12}`, "PATCH", { giocatore_id: INATTIVO }); + await scrivi(`turni_palloni?evento_id=eq.${TERZO}`, "PATCH", { giocatore_id: INATTIVO }); await esegui("genera_avvisi_palloni"); assert.ok( - !(await destinatari(EV12, "turno_palloni_12h")).includes(INATTIVO), + !(await destinatari(TERZO, "turno_palloni_3h")).includes(INATTIVO), "chi non è più attivo non riceve nulla", ); - await cambiaTurno(EV12, G8); + await cambiaTurno(TERZO, G8); }, ); @@ -452,18 +472,19 @@ if (!locale) { "turno tolto: chi era stato avvisato, incaricato o riconsegna, viene revocato", async () => { // La notifica dell'avviso può essere stata eliminata: conta il registro. - const [avvisoG7] = (await notifiche(EV6, "turno_palloni_6h")).filter( + const [avvisoG7] = (await notifiche(SECONDO, "turno_palloni_3h")).filter( (r) => r.giocatore_id === G7, ); await scrivi(`notifiche_utente?id=eq.${avvisoG7!.id}`, "DELETE"); - await scrivi(`turni_palloni?evento_id=eq.${EV6}`, "DELETE"); + await scrivi(`turni_palloni?evento_id=eq.${SECONDO}`, "DELETE"); await esegui("genera_avvisi_palloni"); - const revocati = await destinatari(EV6, "turno_palloni_revocato"); + const revocati = await destinatari(SECONDO, "turno_palloni_revocato"); assert.ok(revocati.includes(G7), "g7 aveva eliminato l'avviso: conta il registro"); assert.ok(!revocati.includes(G8), "g8 non c'entra con questo evento"); - // Senza incaricato non parte nulla: nemmeno un nuovo avviso di fascia per nessuno. - assert.equal((await notifiche(EV6, "turno_palloni_3h")).length, 0); - await assegna(EV6, G7); + // Senza incaricato non parte nulla di nuovo: restano g4 e g6, avvisati prima come + // «riconsegna» quando il turno di MAIN è passato a g6 e poi tornato a g4. + assert.deepEqual(await destinatari(SECONDO, "turno_palloni_3h"), [G4, G6]); + await assegna(SECONDO, G7); }, ); @@ -511,12 +532,12 @@ if (!locale) { // Solo i due eventi della coppia cadono nella fascia: 8 giorni ± un giorno. await esegui("genera_avvisi_palloni_fascia", { - p_tipo: "turno_palloni_12h", + p_tipo: "turno_palloni_3h", p_da: `${24 * 7} hours`, p_a: `${24 * 9} hours`, }); for (const evento of [COPPIA_A, COPPIA_B]) { - const righe = await notifiche(evento, "turno_palloni_12h"); + const righe = await notifiche(evento, "turno_palloni_3h"); const attesi = avvisiPalloniEvento(turni, eventi, evento); assert.equal(righe.length, attesi.length, evento); for (const a of attesi) {