diff --git a/PROJECT_STATE.md b/PROJECT_STATE.md
index 1a3ce87..1e95fbb 100644
--- a/PROJECT_STATE.md
+++ b/PROJECT_STATE.md
@@ -178,9 +178,10 @@ collega uno slot lo occupa anche in produzione, e va liberato da un admin.
## Prossimo sviluppo
-Niente di assegnato: tutto quello che era in lavorazione è chiuso, tesseramento CSI incluso
-(numero e data di tessera registrabili da `/admin`, migration `m8_tesseramento_csi`). Le voci
-ancora aperte stanno in [docs/ROADMAP.md](docs/ROADMAP.md), sotto «Prossimo».
+**Avviso certificati in scadenza** (24/09/2026, branch `develop`): specifica scritta in
+[docs/modules/profilo-giocatore.md](docs/modules/profilo-giocatore.md#avviso-certificati),
+decisione in DD-035, codice non ancora iniziato. Solo client: nessuna migration e nessuna
+push. Le altre voci aperte stanno in [docs/ROADMAP.md](docs/ROADMAP.md), sotto «Prossimo».
---
diff --git a/docs/DESIGN_DECISIONS.md b/docs/DESIGN_DECISIONS.md
index 2b1778b..fb829fd 100644
--- a/docs/DESIGN_DECISIONS.md
+++ b/docs/DESIGN_DECISIONS.md
@@ -50,6 +50,7 @@ Serve a rispondere a domande del tipo:
| [DD-032](#dd-032--la-stagione-csi-si-ricava-dalle-date-delle-gare) | Stagione CSI dalle date delle gare |
| [DD-033](#dd-033--gestione-eventi-solo-calendario-giorno-fissato-dal-tocco) | Gestione eventi solo da calendario |
| [DD-034](#dd-034--lallenatore-è-uno-slot-della-squadra-con-tipo-diverso-non-un-giocatore) | Ruolo allenatore |
+| [DD-035](#dd-035--avviso-certificati-calcolato-in-home-visibile-solo-al-titolare-e-agli-admin) | Avviso certificati in Home |
**In valutazione**
@@ -1451,3 +1452,61 @@ collega uno slot di tipo allenatore: è quindi l'admin a concederlo, registrando
**Riesame**
Se arriva un secondo tipo di staff (dirigente, segreteria) o se i filtri sulla rosa diventano
fonte di bug, passare alla tabella `staff` separata.
+
+### DD-035 — Avviso certificati: calcolato in Home, visibile solo al titolare e agli admin
+
+**Data:** 24 settembre 2026
+**Stato:** Accettata (da implementare)
+
+**Contesto**
+Il certificato medico ha una data di scadenza (`profili_giocatore.certificato_scadenza`), ma
+oggi ci si accorge che è scaduto solo aprendo la tab Profili di `/admin`: spesso quando il
+giocatore è già fuori regola. La richiesta è un avviso in Home, giallo nei 7 giorni prima
+della scadenza e nero dopo, con i nomi dei giocatori, che sparisca da solo quando il
+certificato viene rinnovato. Specifica completa in
+[modules/profilo-giocatore.md](modules/profilo-giocatore.md#avviso-certificati).
+
+**Decisione**
+
+1. L'avviso si **calcola al volo** dalla data già salvata, con una funzione pura in
+ `profili-core.ts`: nessuna tabella, nessuno stato «avvisato», nessuna migration.
+2. Lo vedono **solo gli admin**, con i nomi di tutta la rosa, e **il giocatore interessato**,
+ riferito solo a sé. L'allenatore e i compagni non lo vedono.
+3. L'avviso dello staff **non è cliccabile**; quello personale apre
+ `/profilo?tab=documenti`.
+4. Soglia fissa di **7 giorni** in una costante; il giorno della scadenza il certificato vale
+ ancora (come `statoScadenza()`).
+5. **Niente push**: l'avviso vive solo in Home.
+
+**Alternative scartate**
+
+- Mostrarlo anche all'allenatore → il certificato è un dato sanitario che oggi l'allenatore
+ non vede (DD-034); servirebbe una funzione `SECURITY DEFINER` apposta per un'informazione
+ che all'allenatore non serve ad agire.
+- Salvare gli avvisi in una tabella o nel centro notifiche (DD-030) → uno stato da tenere
+ allineato alla data, che si disallinea appena il giocatore corregge la scadenza; calcolarlo
+ è gratis e sempre giusto.
+- Avviso dello staff cliccabile verso `/admin?tab=profili` → l'admin non può caricare il file
+ al posto del giocatore (DD-017), quindi il tocco non gli farebbe fare nulla in più, e
+ richiederebbe di aggiungere a `/admin` la lettura della tab dall'URL.
+- Mostrare al massimo alcuni nomi e un «e altri N» → a inizio stagione ne scadono molti
+ insieme ed è proprio lì che l'elenco completo serve.
+- Push quotidiana a chi ha il certificato in scadenza → servirebbe un cron, che oggi non
+ esiste per nessuna push (DD-025), e le push vanno riservate agli eventi importanti
+ ([EFFICIENZA_CLOUD.md](EFFICIENZA_CLOUD.md)).
+- Includere i certificati mancanti → è un problema diverso (profilo incompleto), già coperto
+ dal widget «Completa il tuo profilo» e dalla tab Profili.
+
+**Conseguenze**
+
+- Nessuna query nuova: si riusa `useProfili()`, già in cache per Profilo e `/admin`, e le RLS
+ esistenti decidono quanti profili arrivano (tutti all'admin, il proprio al giocatore).
+- Il rinnovo non richiede nessuna azione di chiusura: cambiata la data, l'avviso si aggiorna
+ da solo.
+- Chi non apre l'app non viene avvisato: l'avviso raggiunge solo chi entra in Home.
+- Un admin che è anche giocatore vede solo l'avviso dello staff, dove compare il suo nome.
+
+**Riesame**
+Se gli avvisi in Home non bastano (certificati che scadono comunque senza essere rinnovati),
+valutare una push, legata a un cron o a un invio manuale dell'admin come per i palloni. Se 7
+giorni si rivelano pochi per prenotare una visita, alzare la costante o renderla regolabile.
diff --git a/docs/ROADMAP.md b/docs/ROADMAP.md
index a2fccee..38be512 100644
--- a/docs/ROADMAP.md
+++ b/docs/ROADMAP.md
@@ -41,6 +41,10 @@ Tutto quello che è rilasciato, fino alla versione 1.0.0 (vedi `CHANGELOG.md`).
## Prossimo
+- [ ] Avviso certificati in scadenza — in Home, giallo nei 7 giorni prima e nero dopo la
+ scadenza: agli admin con i nomi dei giocatori, al giocatore solo per il proprio
+ ([specifica](modules/profilo-giocatore.md#avviso-certificati))
+
- [ ] 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 c876881..608c154 100644
--- a/docs/modules/allenatore.md
+++ b/docs/modules/allenatore.md
@@ -69,7 +69,8 @@ quando quei campi sono compilati.
La Home dell'allenatore tiene ciò che riguarda la squadra e toglie ciò che riguarda il
giocatore: restano classifica e bilancio, prossimo impegno e ultima partita; spariscono lo
streak personale, il promemoria palloni, «Da confermare», l'obiettivo di squadra e il
-«Colpo d'occhio» con le statistiche personali.
+«Colpo d'occhio» con le statistiche personali. Non compare nemmeno l'avviso certificati: lo
+vedono solo gli admin e il giocatore interessato (DD-035).
## Squadra
@@ -148,19 +149,20 @@ giocatore).
## Permessi (riepilogo)
-| Azione | Giocatore | Allenatore | Admin |
-| ----------------------------------------------- | --------- | ------------------ | ------------------ |
-| Modificare nome e cognome | no | sì, i propri | tutti |
-| Dati personali e foto profilo | i propri | i propri (ridotti) | tutti (non i file) |
-| Documento, certificato, foto tessera | i propri | — | lettura/download |
-| Creare/modificare/eliminare eventi, convocati | no | sì | sì |
-| Sollecitare le presenze | no | sì | sì |
-| Promemoria palloni, notifica personalizzata | no | no | sì |
-| Rispondere alle presenze, votare, essere votato | sì | no | sì (se giocatore) |
-| Tab Stagione e Badge del profilo | sì | no | sì |
-| Tab Obiettivi e Badge di Squadra | sì | no | sì |
-| Badge e classifiche/dati sulle cacche | sì | no | sì |
-| Dashboard `/admin` | no | no | sì |
+| Azione | Giocatore | Allenatore | Admin |
+| ----------------------------------------------- | ---------- | ------------------ | ------------------ |
+| Modificare nome e cognome | no | sì, i propri | tutti |
+| Dati personali e foto profilo | i propri | i propri (ridotti) | tutti (non i file) |
+| Documento, certificato, foto tessera | i propri | — | lettura/download |
+| Avviso certificati in Home | il proprio | no | tutta la rosa |
+| Creare/modificare/eliminare eventi, convocati | no | sì | sì |
+| Sollecitare le presenze | no | sì | sì |
+| Promemoria palloni, notifica personalizzata | no | no | sì |
+| Rispondere alle presenze, votare, essere votato | sì | no | sì (se giocatore) |
+| Tab Stagione e Badge del profilo | sì | no | sì |
+| Tab Obiettivi e Badge di Squadra | sì | no | sì |
+| Badge e classifiche/dati sulle cacche | sì | no | sì |
+| Dashboard `/admin` | no | no | sì |
## Implementazione
diff --git a/docs/modules/notifiche.md b/docs/modules/notifiche.md
index d404825..9778260 100644
--- a/docs/modules/notifiche.md
+++ b/docs/modules/notifiche.md
@@ -18,6 +18,11 @@ indipendenti:
- **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](profilo-giocatore.md#avviso-certificati)).
+
---
## Dati
diff --git a/docs/modules/profilo-giocatore.md b/docs/modules/profilo-giocatore.md
index 0b100cc..f2ff735 100644
--- a/docs/modules/profilo-giocatore.md
+++ b/docs/modules/profilo-giocatore.md
@@ -64,7 +64,7 @@ Se il profilo non è completo compare automaticamente un widget di completamento
## Home
-Il giocatore visualizza un widget dedicato.
+Il giocatore visualizza un widget dedicato e, quando serve, l'avviso sul certificato medico.
### Completa il tuo profilo
@@ -80,6 +80,128 @@ Quando tutte le sezioni sono complete il widget scompare automaticamente.
Il tap apre Profilo sulla sottosezione **Documenti** (`/profilo?tab=documenti`), non
sulla tab Stagione.
+### Avviso certificati
+
+**Stato:** da implementare (DD-035).
+
+Un avviso in Home segnala i certificati medici in scadenza o scaduti, così nessuno se ne
+accorge quando il giocatore è già fuori regola. Si calcola al volo dalla data di scadenza
+già salvata: niente tabella, niente migration, niente push.
+
+#### Chi lo vede
+
+| Utente | Cosa vede | Al tocco |
+| --------------------------- | --------------------------------------------------------------- | ----------------------------- |
+| Admin | l'avviso **dello staff**: i nomi di tutti i giocatori coinvolti | niente, non è cliccabile |
+| Giocatore interessato | l'avviso **personale**: solo il proprio certificato | apre `/profilo?tab=documenti` |
+| Admin che è anche giocatore | solo l'avviso dello staff, dove compare già il suo nome | niente |
+| Altri giocatori | niente | — |
+| Allenatore | niente: non vede i certificati altrui e non ne ha uno (DD-034) | — |
+
+L'avviso dello staff non è cliccabile, come quello dei palloni: l'admin non può caricare il
+certificato al posto del giocatore (DD-017), quindi può solo sollecitarlo, e il giocatore
+riceve già il proprio avviso. Quello personale porta invece dove si carica il certificato
+nuovo, come il widget «Completa il tuo profilo».
+
+I certificati sono dati sanitari: fuori da `/admin` li legge solo il titolare (DD-016).
+Nessuna policy cambia, perché `useProfili()` restituisce già tutti i profili a un admin e il
+solo proprio a un giocatore.
+
+#### Quando compare
+
+`giorni` è la differenza in giorni di calendario tra la scadenza e oggi (ora locale), soglia
+`GIORNI_AVVISO_CERTIFICATO = 7`.
+
+| Condizione | Stato | Avviso |
+| -------------------- | ----------- | ------------------ |
+| `giorni > 7` | valido | nessuno |
+| `0 ≤ giorni ≤ 7` | in scadenza | giallo (`warning`) |
+| `giorni < 0` | scaduto | nero (`primary`) |
+| data o file mancanti | mancante | nessuno |
+
+- Il giorno della scadenza il certificato vale ancora, coerente con `statoScadenza()`: il
+ giocatore compare nel giallo con «scade oggi» e passa al nero dal giorno dopo.
+- L'avviso nero resta finché il giocatore non aggiorna la data: non c'è un limite di tempo.
+- Il certificato **mancante** non genera avviso: è un problema diverso, già visibile nella
+ tab Profili di `/admin` e nel widget di completamento del giocatore.
+- Sono esclusi i giocatori disattivati e gli allenatori: conta solo la rosa (`inRosa()`,
+ cioè `attivo` e `tipo = 'giocatore'`).
+
+#### Testi
+
+| Avviso | Titolo (staff / personale) | Riga |
+| ----------------- | -------------------------- | ------------------------------------------------------------------------ |
+| Giallo, staff | «Certificati in scadenza» | «Mario Rossi — scade tra 5 giorni», «… — scade domani», «… — scade oggi» |
+| Nero, staff | «Certificati scaduti» | «Mario Rossi — scaduto il 12/09/2026» |
+| Giallo, personale | «Certificato in scadenza» | «Il tuo certificato medico scade tra 5 giorni» / «domani» / «oggi» |
+| Nero, personale | «Certificato scaduto» | «Il tuo certificato medico è scaduto il 12/09/2026: caricane uno nuovo» |
+
+Il numero di giorni scende da solo ogni giorno (7, 6, 5… domani, oggi): l'avviso non
+conserva niente, rilegge la data a ogni apertura della Home.
+
+#### Più giocatori
+
+- Più giocatori nello stesso stato stanno in un **unico avviso**, una riga ciascuno, e
+ compaiono **tutti**: nessun limite al numero di nomi, anche a inizio stagione quando ne
+ scadono molti insieme.
+- Se ci sono sia certificati in scadenza sia scaduti compaiono **due avvisi**: prima quello
+ nero, poi quello giallo.
+- Ordine delle righe: dalla scadenza più vicina nel giallo, dal certificato scaduto da più
+ tempo nel nero; a parità di data, alfabetico per cognome e poi per nome. L'ordine è
+ stabile, quindi l'elenco non si rimescola tra un'apertura e l'altra.
+
+#### Aspetto e posizione
+
+- Stessa forma del banner palloni (`PromemoriaPalloni`): card arrotondata a tutta larghezza
+ con icona e titolo in `font-display` maiuscolo, righe in testo piccolo sotto.
+- Giallo: sfondo `bg-warning`, testo `text-warning-foreground`. Nero: sfondo `bg-primary`,
+ testo `text-primary-foreground`. L'app è solo chiara (DD-022), quindi il nero non rischia
+ di confondersi con lo sfondo.
+- In Home sta subito **sotto il banner palloni** e sopra «Completa il tuo profilo»: prima
+ ciò che scade oggi, poi ciò che manca.
+
+#### Rinnovo
+
+Il giocatore rinnova dal proprio profilo, sottosezione Documenti: carica il file nuovo e
+aggiorna la data. Il salvataggio aggiorna la cache dei profili (`setQueryData`), quindi il
+suo avviso personale sparisce subito. Nell'avviso dello staff il nome sparisce alla lettura
+successiva dei profili da parte dell'admin (cache di 30 minuti, `useProfili()`), senza
+nessuna azione da parte sua. Quando non resta nessuno, l'avviso non compare più.
+
+Basta aggiornare la data: l'avviso guarda solo quella (e la presenza di un file). Che il
+file caricato sia davvero quello nuovo resta un controllo dell'admin, come oggi. Anche l'admin
+può correggere la data dalla scheda del giocatore (DD-017, non il file): l'avviso segue la
+data, chiunque l'abbia scritta.
+
+#### Caricamento
+
+Finché ruoli e profili non sono arrivati, l'avviso non compare: così un admin che è anche
+giocatore non vede per un attimo l'avviso personale prima di quello dello staff. Se la
+lettura dei profili fallisce l'avviso semplicemente non compare, senza messaggio d'errore:
+la Home non deve rompersi per un dato accessorio.
+
+#### Implementazione
+
+| Pezzo | Ruolo |
+| ----------------------------------------------- | ------------------------------------------------------------------------------------------------------------------------------------------- |
+| `GIORNI_AVVISO_CERTIFICATO` (`profili-core.ts`) | la soglia, 7 |
+| `giorniAllaScadenza(scadenza, oggi)` | differenza in giorni di calendario tra due date `AAAA-MM-GG`, negativa se scaduto |
+| `avvisiCertificati(rosa, profili, oggi)` | funzione pura: restituisce `{ scaduti, inScadenza }`, ognuno una lista ordinata di `{ giocatoreId, nome, cognome, scadenza, giorni }` |
+| `testoScadenza(giorni)` | «scade oggi» / «scade domani» / «scade tra N giorni» |
+| `src/components/crapp/AvvisoCertificati.tsx` | legge `useGiocatoriSquadra()`, `useProfili()`, `useIsAdmin()` e l'utente corrente; sceglie avviso dello staff o personale e disegna le card |
+| `src/routes/index.tsx` | monta `` subito dopo `` |
+
+- "Oggi" viene da `oggiISO()` (`palloni-core.ts`), in ora locale. Non da
+ `new Date().toISOString()`, che è in UTC e tra mezzanotte e le 2 darebbe il giorno prima.
+- Nessuna query nuova: `useProfili()` ha la stessa chiave di cache usata da Profilo e
+ `/admin`, quindi al massimo una lettura di `profili_giocatore` per sessione (vedi
+ [EFFICIENZA_CLOUD.md](../EFFICIENZA_CLOUD.md)). Per l'allenatore il componente non legge
+ i profili.
+- Test in `test/unit/profili-core.test.ts`: soglia (8 giorni no, 7 sì, 0 sì, −1 scaduto),
+ certificato senza file o senza data escluso, giocatore disattivato e allenatore esclusi,
+ ordinamento (per data, poi alfabetico), testi al singolare e al plurale, cambio di mese e
+ di anno nel calcolo dei giorni.
+
## Profilo
Il profilo viene suddiviso in sette aree.
@@ -132,6 +254,10 @@ Upload.
Il giocatore può aggiornare liberamente sia la data sia il file.
+Quando mancano 7 giorni o meno alla scadenza, o il certificato è scaduto, il giocatore
+riceve un avviso nella propria Home e compare in quello degli admin (vedi
+[Avviso certificati](#avviso-certificati)).
+
Lo storico non viene mantenuto nella prima versione.
### Foto tessera
@@ -255,6 +381,9 @@ Quando tutte le sezioni risultano complete il profilo raggiunge il 100%.
dati, modificare dati squadra e dati personali di chiunque e scollegare un account (DD-017).
Non carica file al posto di altri.
+**Avviso certificati** — il giocatore vede solo il proprio, l'admin i nomi di tutta la rosa,
+nessun altro ne vede (vedi [Avviso certificati](#avviso-certificati)).
+
## Versione 1
- Profilo giocatore
@@ -269,6 +398,7 @@ Non carica file al posto di altri.
## Versioni future
- Storico certificati medici
+- Avviso certificati anche via push, o con una soglia regolabile dall'admin (DD-035, Riesame)
- Gestione documenti aggiuntivi
- Consensi privacy
- Firma digitale