diff --git a/PROJECT_STATE.md b/PROJECT_STATE.md
index 1a3ce87..6aab49e 100644
--- a/PROJECT_STATE.md
+++ b/PROJECT_STATE.md
@@ -1,6 +1,6 @@
# Project State
-Ultimo aggiornamento: 24/09/2026
+Ultimo aggiornamento: 27/09/2026
## Stato generale
@@ -178,9 +178,9 @@ 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».
+Niente di assegnato: l'avviso certificati in scadenza (DD-035) è implementato e versione
+1.1.0 (vedi `docs/CHANGELOG.md`). Le voci ancora aperte stanno in
+[docs/ROADMAP.md](docs/ROADMAP.md), sotto «Prossimo».
---
diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md
index 9802c5e..cdee1ee 100644
--- a/docs/CHANGELOG.md
+++ b/docs/CHANGELOG.md
@@ -13,6 +13,16 @@ versioni `0.y.z` erano pre-release.
## [Non rilasciato]
+## [1.1.0] - 2026-09-27
+
+### Aggiunto
+
+- **Avviso certificati in scadenza** — in Home, giallo nei 7 giorni prima e nero dopo la
+ scadenza del certificato medico. Gli admin vedono i nomi di tutta la rosa (non
+ cliccabile); il giocatore interessato vede solo il proprio e il tap apre
+ `/profilo?tab=documenti`. Calcolato al volo da `certificato_scadenza`: nessuna migration,
+ nessuna push (DD-035).
+
## [1.0.0] - 2026-09-24
Prima versione stabile. Oltre al ruolo allenatore riunisce il lavoro fatto dalla 0.9.2:
diff --git a/docs/DESIGN_DECISIONS.md b/docs/DESIGN_DECISIONS.md
index 2b1778b..4a7bea2 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 (implementata, versione 1.1.0)
+
+**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..2f329a1 100644
--- a/docs/ROADMAP.md
+++ b/docs/ROADMAP.md
@@ -38,6 +38,9 @@ Tutto quello che è rilasciato, fino alla versione 1.0.0 (vedi `CHANGELOG.md`).
calcolata dal CSI
- [x] Ruolo allenatore — gestisce gli eventi e sollecita le presenze, profilo ridotto senza
stagione e badge ([specifica](modules/allenatore.md), DD-034)
+- [x] 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), DD-035)
## Prossimo
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..785fc6f 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:** implementato (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 `
+
+ {r} +
+ ))} + > + ); + return cliccabile ? ( + + {contenuto} + + ) : ( +