Files
CRAPP/docs/modules/notifiche.md
T
davideandClaude Sonnet 5.5 4760e8650c L'allenatore non riceve i promemoria degli eventi; test dei destinatari per ruolo (DD-039)
m25: giocatori_destinatari_evento() include solo i giocatori attivi, quindi
l'allenatore non riceve più i promemoria a 24 e 3 ore, per nessun tipo di
evento (fino alla m24 li riceveva sempre, DD-034). Riceve ancora i messaggi
dello staff.

Test: nuovo destinatari-notifiche.test.ts (14 controlli sulle route vere con un
servizio push finto: promemoria, messaggio dello staff, sondaggio, sollecito,
turno palloni, permessi di allenatore e giocatore) e aggiornato
permessi-allenatore. Documentazione: DD-039, tabella delle notifiche
dell'allenatore, catalogo e tabella dei test che lo verificano.

Co-Authored-By: Claude Sonnet 5.5 <noreply@anthropic.com>
2026-09-29 14:48:30 +02:00

28 KiB
Raw Blame History

Modulo — Notifiche

Stato: implementato — un unico opt-in dispositivo abilita tutto il canale push, più un centro notifiche in-app indipendente, con un pallino sull'avatar del profilo File principali: src/lib/notifiche-smart.ts, src/lib/notifiche-utente.ts, src/lib/push-client.ts, src/lib/webpush.server.ts, src/routes/api/public/push-config.ts, src/routes/api/public/push-subscribe.ts, public/push-sw.js, src/components/crapp/ui-bits.tsx (LinkProfilo, PallinoNotifiche)


Obiettivo

Tenere aggiornati i giocatori senza che debbano aprire l'app, con due meccanismi indipendenti:

  • Push VAPID — arrivano anche ad app chiusa (messaggi dello staff, turno palloni, sollecito presenze, sondaggio pre-partita: elenco completo nel catalogo).
  • Notifiche smart — notifiche locali mostrate solo ad app aperta, generate da badge, serie e obiettivi appena raggiunti; non è un canale push separato.

Gli avvisi fissi in Home (turno palloni, certificati in scadenza) non passano da nessuno dei due: sono card calcolate dai dati, che spariscono da sole quando la condizione finisce. In particolare il certificato in scadenza non genera push né voci nel centro notifiche (DD-035, specifica in profilo-giocatore.md).


Catalogo delle notifiche

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: ogni riga di notifiche_utente genera una mail, quindi la colonna Email coincide con la colonna In-app).

# 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). 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»).

Cosa riceve l'allenatore, notifica per notifica: allenatore.md.

Cosa non genera nessuna notifica

  • Creare, spostare, modificare o cancellare un evento: contano solo i promemoria prima dell'inizio (DD-030).
  • L'arrivo di un risultato, di una pagella o di un voto MVP.
  • Un giocatore che risponde a un evento: nessun avviso a chi gestisce gli eventi.
  • Assenze e infortuni.
  • La registrazione o il collegamento di un nuovo giocatore.
  • Il certificato in scadenza: resta una card in Home (DD-035).

Comportamenti da conoscere

Sono l'effetto attuale della logica, non scelte documentate altrove:

  • I promemoria (1 e 2) non distinguono il tipo di evento: scattano per allenamenti, partite ed eventi extra-campo (per esempio una cena). Il turno palloni (4), invece, esiste solo per allenamenti e partite.
  • Un evento creato a poche ore dall'inizio riceve subito i promemoria che gli spettano: se mancano meno di 24 ore parte il promemoria «24 ore» al primo giro del job orario, e se ne mancano meno di 3 anche quello «3 ore», a distanza di pochi minuti.
  • Ogni promemoria si genera una sola volta per giocatore, evento e tipo, anche se il giocatore lo elimina (M23, DD-037): il job registra ciò che ha generato in promemoria_eventi_generati, una tabella indipendente da notifiche_utente. Eliminare la notifica dal centro notifiche non la fa tornare e non accoda una nuova mail. Prima di M23 tornava: la riga eliminata veniva rigenerata al giro successivo del job, con una nuova mail a ogni giro fino all'inizio dell'evento.
  • 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.
  • 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.
  • 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.
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)

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 catena useIo() → useRosa() → useNotificheSmart() è fatta di hook e si conosce dalla lettura del codice; l'unico pezzo puro, inRosa, è coperto.


Dati

push_subscriptions (un dispositivo per riga, chiave endpoint, con le chiavi p256dh e auth con cui si cifra il payload per quel dispositivo). La tabella promemoria_push non è più usata da nessuno: serviva da coda del testo quando la push partiva vuota (DD-026).


Iscrizione alle notifiche push

In Profilo → Opzioni c’è un solo interruttore («Notifiche»). Non esistono preferenze separate per tipo di messaggio: l’iscrizione registra il dispositivo e lo rende destinatario di tutte le push (promemoria palloni, solleciti presenze) e abilita anche le notifiche smart in app, che usano lo stesso service worker.

  1. Il giocatore attiva «Notifiche» in /profilo → richiesta permesso browser.
  2. GET /api/public/push-config restituisce solo la chiave pubblica VAPID.
  3. Registrazione del service worker public/push-sw.js e pushManager.subscribe().
  4. POST /api/public/push-subscribe registra endpoint e chiavi in push_subscriptions (upsert).

All'avvio e quando l'app torna visibile viene richiesto l'aggiornamento della registrazione push esistente con ServiceWorkerRegistration.update(). Non si chiede un nuovo permesso, non si ricrea la sottoscrizione e non si cambia l'endpoint: anche chi ha già attivato le notifiche deve ricevere le correzioni del worker senza spegnere e riaccendere l'interruttore. Gli aggiornamenti contemporanei sono accorpati; un errore di rete non blocca l'app e si riprova al ritorno in primo piano. Il worker attende skipWaiting() durante l'installazione. Il browser controlla anche autonomamente gli aggiornamenti: questa richiesta esplicita copre in particolare le sessioni lunghe della webapp (vedi il ciclo di vita del service worker).


Ruolo delle tre route pubbliche

  • push-config — espone la sola chiave pubblica VAPID.
  • push-subscribe — registra o rimuove l'iscrizione di un dispositivo.
  • apri-sondaggio — premuto da un admin dalla pagina partita: manda a tutti i dispositivi iscritti (allenatori esclusi) l'avviso di apertura del sondaggio pre-partita e lo scrive anche in notifiche_utente per i giocatori attivi, così compare nel centro notifiche e parte per email (DD-038; vedi Scout Live).

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.

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 rete. È il punto decisivo per la consegna ad app chiusa — il browser sveglia il worker per pochi secondi, e una fetch per recuperare il testo lo faceva morire prima di showNotification (DD-026).

La POST porta Urgency: high. Con l'urgenza predefinita ("normal") un telefono in risparmio energetico accumula i messaggi fino al risveglio: la notifica arriva solo quando il dispositivo è già attivo — cioè, nella pratica, solo con l'app aperta.

Chi può farle partire (DD-024, DD-025)

Queste route usano la service role e saltano la RLS, quindi il permesso deve stare nella route. Tutte e tre partono da un gesto di un amministratore dentro l'app, quindi il controllo è uno solo (richiediAdmin in src/lib/auth-route.server.ts) e non serve configurare nessuna variabile d'ambiente.

Route Controllo Chi la chiama
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

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), non l'invio effettivo. La tab elenca tutti i giocatori attivi della squadra, non solo chi ha le notifiche abilitate: l'icona (campana piena/barrata) distingue chi ha almeno un dispositivo iscritto da chi non l'ha ancora attivata.

notifica-personalizzata manda un messaggio libero scritto dall'admin: senza giocatoreId lo manda a tutti i dispositivi iscritti in push_subscriptions, con giocatoreId solo a quelli di quel giocatore. Titolo fisso ("Messaggio dallo staff"), corpo il testo scritto dall'admin (max 300 caratteri). Stessa logica di pulizia delle altre route: una sottoscrizione che risponde 404/410 viene cancellata dalla tabella. Nella tab "Notifiche" della dashboard admin c'è un bottone "Invia messaggio a tutti" sopra l'elenco e un bottone per riga giocatore.


Notifiche smart

calcolaNotifiche() (notifiche-smart.ts) genera un evento solo quando "c'è qualcosa di reale": badge appena sbloccato, "sei a un passo" da un traguardo, serie che raggiunge un traguardo esatto, obiettivo di squadra tra il 90 e il 100%, badge social vinto. Ogni notifica ha un id deterministico; quelli già mostrati sono salvati in localStorage per non ripetersi — deduplica puramente locale al dispositivo, non sincronizzata.

Chi le riceve. Solo chi ha selezionato sul dispositivo uno slot giocatore in rosa: CelebrazioneBadge (montato una volta in __root.tsx) passa a useNotificheSmart() il giocatore letto da useGiocatoreCorrente(), cioè useIo(), che lo cerca in useRosa(). Quell'elenco esclude gli allenatori (inRosa, DD-034), che non hanno statistiche: per loro useIo() è null e useNotificheSmart() esce subito, senza calcolare nulla né mostrare la card celebrativa o la notifica del sistema. Vedi allenatore.md.


Centro notifiche in-app (M17)

Pallino sull'angolo dell'avatar del profilo in alto a destra (PallinoNotifiche dentro LinkProfilo, src/components/crapp/ui-bits.tsx): rosso con il numero delle non lette; se sono tutte lette resta neutro con il totale, così le lette restano raggiungibili per eliminarle; senza notifiche non compare. Il tap sull'avatar porta sempre a /profilo, solo il tap sul pallino apre il pannello (logica in pallinoNotifiche()). Fino a 0.9.2 era una campanella separata accanto all'avatar. Indipendente dal canale push sopra: non richiede che il dispositivo abbia attivato «Notifiche», ha uno storico persistente in notifiche_utente (vedi 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:

  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).
  2. Promemoria evento — due job pg_cron (ogni ora per la finestra delle 24h, ogni 15 minuti per quella delle 3h) generano una notifica per evento imminente, per i convocati (o tutta la rosa attiva se l'evento non ne specifica), deduplicata dal registro promemoria_eventi_generati (M23, DD-037; il vincolo UNIQUE (giocatore_id, evento_id, tipo) di notifiche_utente resta come seconda difesa) così un cron che gira più volte non manda doppioni, nemmeno se il giocatore elimina la notifica. Volutamente non c'è una notifica sulla sola creazione dell'evento: conta l'avvicinarsi della data, non il momento in cui è stato messo in calendario.
  3. Turno palloni — promemoria-palloni.ts (bottone riservato agli admin sulla pagina evento, vedi Turno palloni) inserisce una riga per ciascun avviso calcolato da avvisiPalloniEvento(), in parallelo alla push.
  4. Sollecito presenze — sollecita-presenze.ts (stesso innesco manuale) inserisce una riga per ciascun giocatore che non ha ancora risposto, in parallelo alla push.
  5. Sondaggio pre-partita — apri-sondaggio.ts (pulsante riservato agli admin sulla pagina della partita) inserisce una riga di tipo sondaggio_cacche per ciascun giocatore attivo, allenatori esclusi (destinatariSondaggio()), in parallelo alla push (M24, DD-038).

Turno palloni, sollecito presenze e sondaggio usano un upsert su (giocatore_id, evento_id, tipo) invece di un semplice insert: se l'admin preme di nuovo il pulsante per lo stesso evento, la notifica esistente viene aggiornata (testo e creato_il freschi, letta riportata a false) invece di duplicarsi o fallire per il vincolo UNIQUE.

Lettura, "segna come letta" ed eliminazione passano dal client Supabase autenticato con RLS (useNotificheMie()/useSegnaLette()/useEliminaNotifica()), senza una route API dedicata — stesso pattern di useSalvaEvento() in src/lib/eventi.ts. Aprire il pannello segna tutte le notifiche del giocatore selezionato come lette; non c'è un pulsante "segna singola" separato.

Non c'è pulizia automatica delle notifiche vecchie (DD-030): l'unico modo per farle sparire per sempre è eliminarle una per una, con uno swipe verso sinistra sulla riga (RigaNotifica in ui-bits.tsx, stessa fisica a molla dello swipe del calendario) o con la × che compare sopra ogni riga per chi non è su touch. Chi non tocca mai una notifica se la ritrova per sempre nell'elenco, solo segnata come letta.

Push dei promemoria (M24)

I promemoria a 24 e 3 ore nascono nel database (job pg_cron), che non può firmare né cifrare una push Web: la manda il worker mailer/, lo stesso che invia le email (WORKER_EMAIL.md, DD-038).

  1. Quando il job crea un promemoria, un trigger lo accoda in notifiche_push_coda (solo i tipi evento_promemoria_24h e evento_promemoria_3h: le altre notifiche mandano già la push dall'app, e una seconda sarebbe un doppione).
  2. Il worker, a ogni giro, chiama prendi_push_promemoria(), che restituisce testo e dispositivi iscritti del giocatore, e manda la push a tutti i suoi dispositivi con lo stesso modulo dell'app (src/lib/webpush.server.ts), quindi con le stesse regole di cifratura, urgenza e TTL.
  3. Un solo esito per notifica: inviata se almeno un dispositivo accetta; riprova con backoff 1 · 5 · 30 · 120 minuti sugli errori temporanei (429, 5xx, rete), poi fallita; fallita subito su un rifiuto permanente (400, 401, 403, 413: tipicamente chiavi VAPID sbagliate); saltata se le iscrizioni erano tutte scadute o il giocatore non ne ha.
  4. Le iscrizioni che rispondono 404 o 410 vengono eliminate da push_subscriptions, come fanno le route.
  5. Una push in coda da oltre 3 ore dalla creazione del promemoria si scarta (saltata): a evento ormai iniziato sarebbe solo rumore. Vale se il worker resta fermo a lungo.

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.

Canale email (M22)

Ogni riga di notifiche_utente viene inviata anche per email da un worker esterno, con un interruttore «Email» per account in Profilo → Opzioni: notifiche-email.md. Non cambia nulla di quanto descritto sopra: push, notifiche smart e centro notifiche restano indipendenti e le sorgenti scrivono notifiche_utente come prima. Il certificato in scadenza resta una card in Home e non genera mail (DD-035).

Limiti noti

  • Non ci sono preferenze granulari (solo palloni / solo presenze / solo smart): un dispositivo è iscritto o no. Separare i canali richiederebbe schema e UI dedicati.

  • La tabella promemoria_push è rimasta nel database ma non la usa più nessuno (DD-026): va eliminata con una migrazione alla prossima occasione.

  • Un 2xx dal server push non significa consegnato. FCM accetta con 201 anche verso registrazioni scadute e poi butta via il messaggio, senza il 404/410 che farebbe pulire push_subscriptions. Il conteggio "inviate a N dispositivi" va letto come "accettate da N server push", non come "arrivate a N telefoni".

  • Compatibilità iOS/Safari non gestita esplicitamente nel codice (nessun branch dedicato): serve l'installazione da schermata Home per funzionare, ma l'app non lo segnala esplicitamente. È il primo sospetto quando una notifica non arriva ad app chiusa su iPhone.

  • Su Android non riceve la webapp: riceve il browser. Il WebAPK è solo l'identità con cui la notifica viene mostrata; la connessione con i server push la tiene Chrome, tramite Google Play Services. Se Android non può avviare Chrome, il messaggio resta in coda e compare tutto insieme al lancio successivo — il sintomo classico è «arriva solo quando riapro l'app». Un 201 dal servizio push non lo distingue in alcun modo da una consegna riuscita.

    Verificato sul campo (settembre 2026, Motorola): con Chrome vivo in secondo piano la push arriva ad app chiusa e schermo bloccato, WebAPK compreso — quindi server, cifratura, service worker, permesso notifiche e canale erano già corretti. L'unica condizione che fallisce è Chrome non in esecuzione. La cura sta in Impostazioni → App → Chrome → Batteria → «Senza restrizioni», più Impostazioni → Batteria → «Batteria adattiva» disattivata. Mettere «Senza restrizioni» solo su CrAPP non basta e depista.

    Come misurarlo invece di indovinare: chrome://gcm-internals sul telefono, sezione «Receive Message Log». Se la riga porta l'orario dell'invio, il messaggio era arrivato e non è stato mostrato (permesso o canale); se porta l'orario in cui si è riaperta l'app, non era stato consegnato (risveglio, quindi batteria). Attenzione: tenere quella scheda aperta tiene Chrome vivo, quindi falsa la prova stretta — per quella, nessuna scheda aperta e Chrome tolto dai recenti.

  • Su Motorola verificare anche le restrizioni del browser che ha installato CrAPP e, dove presente, Impostazioni → Batteria → Ottimizzazione standby app. Il produttore documenta la limitazione dei processi in background (guida Motorola). È una possibile causa del sintomo, non una diagnosi verificata sul dispositivo: il codice web non può rimuovere questi vincoli. La verifica richiede un invio da un altro dispositivo mentre CrAPP è chiusa e lo schermo del Motorola è bloccato. Il pulsante di prova invia subito, quindi da solo non dimostra la ricezione in background.

  • Le notifiche smart dipendono da un service worker già registrato: se il giocatore non ha mai attivato le push, notificaSistema() non ha un reg a cui appoggiarsi e la notifica locale non viene mai mostrata, anche con permesso concesso.

  • Il payload cifrato non può superare i ~4 KB: i testi attuali stanno larghi, ma un messaggio molto lungo verrebbe rifiutato dal servizio push.


Evoluzioni possibili

  • Preferenze per canale (palloni, solleciti, smart), se servono davvero alla squadra.
  • Eliminare promemoria_push con una migrazione.
  • Gestire esplicitamente il caso iOS (messaggio se l'app non è installata da Home).