diff --git a/docs/CHANGELOG.md b/docs/CHANGELOG.md
index b1fc9d2..296226e 100644
--- a/docs/CHANGELOG.md
+++ b/docs/CHANGELOG.md
@@ -6,6 +6,14 @@ qui: sta in [ROADMAP.md](ROADMAP.md).
## Versione attuale — agosto 2026
+### Il sondaggio pre-partita apre alle 8:00 del giorno della partita
+
+- Prima era sempre votabile, anche settimane prima: ora la card resta chiusa con l'avviso di
+ apertura e si sblocca alle 8:00 del giorno stesso.
+- Quando è aperto, gli amministratori hanno nella card il pulsante «Avvisa tutti del
+ sondaggio» (`POST /api/public/apri-sondaggio`), che manda la push a tutti i dispositivi
+ iscritti — stesso meccanismo del sollecito presenze. Nessun cron: l'invio è manuale.
+
### Le note dell'evento si leggono aprendolo
- Il campo Note del form eventi si poteva scrivere ma non lo vedeva nessuno: ora compare
diff --git a/docs/modules/notifiche.md b/docs/modules/notifiche.md
index 46981db..e89edf7 100644
--- a/docs/modules/notifiche.md
+++ b/docs/modules/notifiche.md
@@ -46,6 +46,9 @@ smart in app, che usano lo stesso service worker.
- **`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: mette in coda su
+ `promemoria_push` l'avviso di apertura del sondaggio pre-partita per **tutti** i dispositivi
+ iscritti e manda la push (vedi [Scout Live](scout-live.md)).
- **`push-messaggio`** — non invia nulla: il service worker la interroga **al momento della
ricezione** di una push (che arriva sempre "vuota", senza testo, per compatibilità) per
sapere quale messaggio mostrare. Priorità: un messaggio in coda su `promemoria_push`
diff --git a/docs/modules/scout-live.md b/docs/modules/scout-live.md
index b1049c6..a77eea0 100644
--- a/docs/modules/scout-live.md
+++ b/docs/modules/scout-live.md
@@ -82,8 +82,13 @@ sezione "Report tecnico".
## Sondaggio cacche
`SondaggioCacche.tsx` chiede "quante cacche hai fatto prima di questa partita" (0-5+), sempre
-modificabile, senza gating temporale reale (visibile sia prima sia dopo la partita nonostante
-il nome). `statisticheCacche()` (`cacche.ts`) calcola media, record e `giornateTop`
+modificabile. `sondaggioAperto()` (`cacche.ts`) lo apre alle **8:00 del giorno della partita**
+(ora locale del dispositivo) e da lì lo lascia aperto per sempre; prima la card mostra solo
+l'avviso di apertura. Quando è aperto, gli **amministratori** vedono nella card il pulsante
+«Avvisa tutti del sondaggio»: chiama `POST /api/public/apri-sondaggio` e manda la push a tutti
+i dispositivi iscritti, come il sollecito presenze (vedi [Notifiche](notifiche.md)). Nessun
+invio automatico: parte solo quando un admin lo preme.
+`statisticheCacche()` (`cacche.ts`) calcola media, record e `giornateTop`
(giornate con ≥3), soglia usata per un [badge](badge.md) segreto — coerente con DD-007 (badge
calcolati a runtime).
diff --git a/src/components/crapp/SondaggioCacche.tsx b/src/components/crapp/SondaggioCacche.tsx
index 8cd4ca5..1de6982 100644
--- a/src/components/crapp/SondaggioCacche.tsx
+++ b/src/components/crapp/SondaggioCacche.tsx
@@ -1,18 +1,29 @@
+import { useState } from "react";
+import { BellRing, Loader2 } from "lucide-react";
import { toast } from "sonner";
import { cn } from "@/lib/utils";
import { Card } from "@/components/crapp/ui-bits";
import { nomeCompleto, useGiocatoriSquadra } from "@/lib/giocatori-squadra";
import { useGiocatoreCorrente } from "@/lib/user-store";
-import { mediaPartita, useCacche, useSalvaCacche } from "@/lib/cacche";
+import { useIsAdmin } from "@/lib/ruoli";
+import { mediaPartita, sondaggioAperto, useCacche, useSalvaCacche } from "@/lib/cacche";
const opzioni = [0, 1, 2, 3, 4, 5];
/** Sondaggio goliardico pre-partita: quante cacche prima del fischio d'inizio. */
-export function SondaggioCacche({ eventoId }: { eventoId: string }) {
+export function SondaggioCacche({
+ eventoId,
+ dataEvento,
+}: {
+ eventoId: string;
+ dataEvento: string;
+}) {
const io = useGiocatoreCorrente();
const { righe } = useCacche();
const salva = useSalvaCacche();
const { righe: squadra } = useGiocatoriSquadra();
+ const admin = useIsAdmin();
+ const [avviso, setAvviso] = useState(false);
const dellaPartita = righe.filter((r) => r.evento_id === eventoId);
const mia = dellaPartita.find((r) => r.giocatore_id === io?.id);
@@ -35,6 +46,41 @@ export function SondaggioCacche({ eventoId }: { eventoId: string }) {
}
}
+ async function avvisaTutti() {
+ setAvviso(true);
+ try {
+ const res = await fetch("/api/public/apri-sondaggio", {
+ method: "POST",
+ headers: { "Content-Type": "application/json" },
+ body: JSON.stringify({ eventoId }),
+ });
+ if (!res.ok) throw new Error();
+ const dati = (await res.json()) as { inviate: number; destinatari: number };
+ toast.success(
+ dati.inviate > 0
+ ? `Notifica inviata a ${dati.inviate} dispositivi`
+ : "Nessun dispositivo con le notifiche attive",
+ );
+ } catch {
+ toast.error("Non sono riuscito a inviare la notifica");
+ } finally {
+ setAvviso(false);
+ }
+ }
+
+ if (!sondaggioAperto(dataEvento)) {
+ return (
+
+
+ 💩 Sondaggio pre-partita
+
+
+ Apre alle 8:00 del giorno della partita: riceverai una notifica.
+