diff --git a/.github/workflows/pages.yml b/.github/workflows/pages.yml
index f7def69..f3e3595 100644
--- a/.github/workflows/pages.yml
+++ b/.github/workflows/pages.yml
@@ -49,6 +49,8 @@ jobs:
run: node --test test/workshop.test.mjs
- name: Test del modulo del rider tecnico completo
run: node --test test/rider-richiesta.test.mjs
+ - name: Test del modulo di Produzione eventi
+ run: node --test test/produzione-richiesta.test.mjs
- name: Test funzioni condivise Supabase
run: deno test --lock=deno.lock --frozen supabase/functions/_shared/*.test.ts
- name: Type-check Edge Functions
@@ -87,7 +89,7 @@ jobs:
robots.txt sitemap.xml llms.txt 4d1fdc8b5b357ff5353a9e928c187bff.txt \
favicon.ico favicon.svg favicon-48.png favicon-96.png \
apple-touch-icon.png icon-192.png icon-512.png logo-512.png preview.png \
- accedi alternative biglietteria consulenza guida img orchestre privacy programma richiesta rider-tecnico-completo stage-plot termini vendor voce workshop \
+ accedi alternative biglietteria consulenza guida img orchestre privacy produzione-eventi programma richiesta rider-tecnico-completo stage-plot termini vendor voce workshop \
./_site/
- name: Timbra il build ID (SHA del commit) negli asset pubblicati (audit L-03)
# il placeholder sta nella shell dell'editor (app/index.html), non nella landing
diff --git a/.github/workflows/pr-controlli.yml b/.github/workflows/pr-controlli.yml
index 9e6e52f..4068708 100644
--- a/.github/workflows/pr-controlli.yml
+++ b/.github/workflows/pr-controlli.yml
@@ -41,6 +41,8 @@ jobs:
run: node --test test/workshop.test.mjs
- name: Test del modulo del rider tecnico completo
run: node --test test/rider-richiesta.test.mjs
+ - name: Test del modulo di Produzione eventi
+ run: node --test test/produzione-richiesta.test.mjs
- name: Test funzioni condivise Supabase
run: deno test --lock=deno.lock --frozen supabase/functions/_shared/*.test.ts
- name: Type-check Edge Functions
diff --git a/consulenza/index.html b/consulenza/index.html
index 25623d3..b89e86c 100644
--- a/consulenza/index.html
+++ b/consulenza/index.html
@@ -749,6 +749,7 @@
Produzione tecnica
Revisione: pagamento sicuro con Stripe (carta, Apple Pay, Google Pay), fattura su richiesta. Rider tecnico completo: nessun pagamento con la richiesta. Produzione tecnica: nessun pagamento prima del preventivo.
+
Dovete organizzare un evento intero, con service, permessi e orari? C’è Produzione eventi: documenti, preventivi confrontati e cronoprogramma, servizio in prova da 290 €.
Non avete tempo di farlo? Il rider tecnico completo lo prepariamo noi: ci mandate quello che avete e in 72 ore ricevete il PDF controllato da un sound engineer, 59 €. Per un evento intero, con più fornitori e permessi, c'è Produzione eventi.
Domande frequenti
diff --git a/llms.txt b/llms.txt
index d6d7bdf..a6e9939 100644
--- a/llms.txt
+++ b/llms.txt
@@ -33,6 +33,7 @@ Uno stage plot (o stage plan) è lo schema in scala del palco che mostra la disp
- [Stage plot per formazione](https://stageplot.it/stage-plot/): indice delle undici formazioni (band, orchestra, coro, big band, jazz, acustico, tributo, chiesa, matrimonio, DJ set, festival)
- [Rider tecnico completo per band](https://stageplot.it/rider-tecnico-completo/): servizio a 59 € — la band manda il materiale che ha (vecchio rider, lista, foto, descrizione), il rider lo prepariamo noi e lo controlla un sound engineer; in 72 ore (giorni lavorativi) arrivano il PDF (stage plot in scala, channel list, ascolti, corrente, luci di base, ospitalità, orari, contatti) e il progetto modificabile su stageplot.it. Una correzione compresa; per band fino a 12 musicisti; nessun pagamento con la richiesta
- [Esempio di rider tecnico completo in PDF](https://stageplot.it/rider-tecnico-completo/esempio-rider-tecnico-completo.pdf): band inventata di 7 elementi (pop e funk), 6 pagine A4 — in breve, pianta in scala 1:50, channel list da 19 canali, ascolti (6 spie + 1 in-ear), luci, orari e ospitalità
+- [Produzione eventi](https://stageplot.it/produzione-eventi/): servizio in prova per Comuni, Pro Loco, associazioni, aziende e location senza un tecnico — tre livelli (documenti da 59 €; coordinamento a distanza da 290 €: richieste ai fornitori, confronto dei preventivi a parità di condizioni, cronoprogramma, elenco dei permessi con chi li firma; produzione in loco da 790 €, con Simone Castellan sul posto); non firma sicurezza, strutture, impianti né SCIA e non prende commissioni dai fornitori; tre esempi con nomi inventati
- [Revisione e produzione tecnica](https://stageplot.it/consulenza/): gratis l'app genera palco, lista canali, monitor, elettrico e rider PDF; revisione 29 € (un tecnico corregge direttamente il tuo progetto e lo restituisce entro 48 ore, senza PDF); da 149 € lo disegniamo noi da zero, su preventivo
- [Privacy policy](https://stageplot.it/privacy/)
- [Termini di utilizzo](https://stageplot.it/termini/)
diff --git a/produzione-eventi/esempio-azienda-copertina.jpg b/produzione-eventi/esempio-azienda-copertina.jpg
new file mode 100644
index 0000000..073eb94
Binary files /dev/null and b/produzione-eventi/esempio-azienda-copertina.jpg differ
diff --git a/produzione-eventi/esempio-azienda-cronoprogramma.jpg b/produzione-eventi/esempio-azienda-cronoprogramma.jpg
new file mode 100644
index 0000000..42e0860
Binary files /dev/null and b/produzione-eventi/esempio-azienda-cronoprogramma.jpg differ
diff --git a/produzione-eventi/esempio-location-copertina.jpg b/produzione-eventi/esempio-location-copertina.jpg
new file mode 100644
index 0000000..ec6e46f
Binary files /dev/null and b/produzione-eventi/esempio-location-copertina.jpg differ
diff --git a/produzione-eventi/esempio-location-palco.jpg b/produzione-eventi/esempio-location-palco.jpg
new file mode 100644
index 0000000..f36576f
Binary files /dev/null and b/produzione-eventi/esempio-location-palco.jpg differ
diff --git a/produzione-eventi/esempio-piazza-copertina.jpg b/produzione-eventi/esempio-piazza-copertina.jpg
new file mode 100644
index 0000000..0ca25c2
Binary files /dev/null and b/produzione-eventi/esempio-piazza-copertina.jpg differ
diff --git a/produzione-eventi/esempio-piazza-preventivi.jpg b/produzione-eventi/esempio-piazza-preventivi.jpg
new file mode 100644
index 0000000..c92fa40
Binary files /dev/null and b/produzione-eventi/esempio-piazza-preventivi.jpg differ
diff --git a/produzione-eventi/index.html b/produzione-eventi/index.html
new file mode 100644
index 0000000..3ea42ac
--- /dev/null
+++ b/produzione-eventi/index.html
@@ -0,0 +1,358 @@
+
+
+
+
+
+
+
+ Produzione eventi: la parte tecnica, già organizzata
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ SPstageplot.it
+
+ Descrivi l'evento
+
+
+
+
+
+ Servizio in prova
+
La parte tecnica del vostro evento, già organizzata
+
Preventivi dei fornitori confrontati a parità di condizioni, piante in scala, cronoprogramma e permessi con chi li firma. Per Comuni, Pro Loco, associazioni, aziende e location che non hanno un tecnico in casa.
+
da 59 €solo documenti
da 290 €coordinamento a distanza
da 790 €con Simone sul posto
+
Prezzi di partenza, in prova: il preventivo scritto indica importo finale e IVA, e lo ricevete prima di decidere.
Controllato da Simone Castellan, sound engineer per spettacoli dal vivo
+
+
+
In breve: «Produzione eventi» è il servizio con cui organizziamo la parte tecnica di un concerto, di una festa aziendale o di una rassegna per chi non ha un tecnico: documenti in scala, richieste ai fornitori e confronto dei preventivi, cronoprogramma, elenco dei permessi con chi li firma e per quando e, se serve, un sound engineer sul posto. Non vendiamo né noleggiamo palchi e impianti e non prendiamo commissioni dai fornitori. Ogni consegna la controlla Simone Castellan.
+
+
+
Vi riconoscete in una di queste?
+
+
«Abbiamo tre preventivi e non capiamo quale conviene.»Hanno voci diverse: il più basso spesso non comprende luci, tecnici o corrente.
+
«Ci chiedono documenti e non sappiamo cosa mandare.»Il teatro, il Comune o il service vogliono pianta, scheda tecnica, orari, chi porta cosa.
+
«Il giorno dell'evento nessuno sa chi arriva e quando.»Palco, service, band e catering si pestano i piedi senza un cronoprogramma.
+
+
+
Cosa ricevete
+
+
+ Livello 1
+
Documenti
+
da 59 €
+
+
Stage plot in scala (A3, 1:50)
+
Channel list e output list
+
Rider tecnico o scheda del vostro palco
+
PDF con versione e data, e progetto da riaprire su stageplot.it
+
+
59 € per una band fino a 12 musicisti; 149 € per la scheda di una location o per organici maggiori. In 3 giorni lavorativi dal materiale completo.
+
+
+ Livello 2
+
Coordinamento a distanza
+
da 290 €
+
+
I documenti del livello 1
+
Richiesta di preventivo a 2-3 fornitori e confronto a parità di condizioni
+
Cronoprogramma e piano di produzione
+
Elenco dei documenti e dei permessi, con chi li firma e per quando
+
Mail ai fornitori, checklist per chi organizza
+
+
Da 290 € per un palco, un fornitore, una giornata; da 490 € con 2-3 preventivi e piano di comunicazione. Primo riscontro entro 1 giorno lavorativo, documenti definitivi 7 giorni prima dell'evento.
+
+
+ Livello 3
+
Produzione in loco
+
da 790 €
+
+
Il livello 2
+
Sopralluogo e controllo del montaggio
+
Simone Castellan sul posto come responsabile tecnico e sound engineer
+
Foglio del giorno: orari, contatti, checklist
+
+
Da 790 € a 1.200 € a evento secondo durata e distanza. L'impianto proprio, se serve, si indica a parte.
+
+
+
+
Cosa non facciamo
+
Coordiniamo, non firmiamo. Vi diciamo quali carte servono, chi le firma, con quali dati e per quando, e controlliamo che arrivino.
+
+
Non firmiamo relazioni di sicurezza né certificazioni di palchi, strutture o impianti elettrici, e non presentiamo la SCIA né il parere della Commissione di vigilanza: li firma un tecnico abilitato.
+
Non presentiamo domande al posto vostro, non curiamo la SIAE, non scegliamo gli artisti e non trattiamo cachet.
+
Non noleggiamo né vendiamo palchi, impianti e luci e non fatturiamo per conto dei fornitori: non prendiamo commissioni da nessuno, per questo possiamo dirvi quale offerta conviene.
+
+
+
+
Tre esempi
+
Sono pacchetti dimostrativi con nomi, luoghi e cifre inventati, costruiti con la stessa sostanza tecnica dei concerti veri: li guardate qui, senza scaricare niente. Se volete vederne uno intero, chiedetelo nel modulo.
+
+
+
+
Natale in piazza
+
Coro, archi, pianoforte e solista per 350 persone, organizzato da una Pro Loco.
+
Stage plot e planimetria della piazza, cronoprogramma di due giorni, permessi con chi li firma, e tre preventivi confrontati: il più basso a prima vista era il secondo più caro una volta messe le stesse voci.
+
9 pagine · livello 3
+
+
+ La scheda dell'evento: tutto quello che il service guarda per primo
+ Il confronto dei preventivi a parità di condizioni (importi inventati)
+
+
+
+
+
Festa di fine anno di un'azienda
+
130 ospiti, saluti, cena e una band di sette elementi; il referente interno non è un tecnico.
+
Stage plot, channel list da 23 canali, cronoprogramma della serata con i momenti sonori, cosa deve contenere un preventivo e una checklist per il referente.
+
7 pagine · livello 2
+
+
+ La scheda dell'evento e i numeri chiave
+ Il cronoprogramma: chi fa cosa e a che ora
+
+
+
+
+
Una location con una rassegna
+
Un agriturismo con sei serate musicali e un palco fisso.
+
La scheda tecnica del palco, cosa trova e cosa deve portare chi suona, il modulo di richiesta per gli artisti e una serata tipo: da riusare a ogni data.
+
6 pagine · livelli 1-2
+
+
+ La scheda del luogo
+ Il palco fisso in scala 1:50
+
+
+
+
Avete un evento di cui potete parlare? Scriveteci: con il vostro permesso scritto lo mostriamo qui al posto di un esempio.
+
+
Come funziona
+
+
Descrivete l'eventoData, luogo, tipo, persone, cosa c'è già, scadenze. Il modulo non chiede soldi. Rispondiamo entro un giorno lavorativo con due o tre domande e il preventivo scritto.
+
Prepariamo e controlliamoDocumenti e richieste ai fornitori con gli strumenti di stageplot.it e le regole che uso sui palchi veri. Prima di ogni consegna e di ogni mail a un fornitore passa da Simone.
+
Decidete voi, noi seguiamoVi presentiamo le offerte sulla stessa lista di voci, cronoprogramma e documenti con le scadenze; solleciti, contatti del giorno e, dopo l'evento, un recap con cosa cambiare la prossima volta.
+
+
+
Per chi è
+
+
Comuni e Pro Loco
+
Associazioni e cori
+
Aziende: feste, convention, cene di gala
+
Band e artisti in tour piccolo
+
Location e agriturismi con serate
+
Rassegne e piccoli festival
+
+
+
Perché fidarsi
+
+
+
Gli stessi documenti dei concerti con orchestra. Sono Simone Castellan, sound engineer: ogni anno preparo stage plot, channel list, cronoprogrammi e richieste ai service per decine di concerti. Qui lo faccio per chi non ha un tecnico.
+
Indipendenti dai fornitori. Nessun palco da vendere, nessuna commissione: le offerte si confrontano su una lista di voci uguale per tutti.
+
Il controllo è umano. Una parte del lavoro è automatica, ma nessun documento e nessuna mail a un fornitore parte senza che li abbia guardati io. Se trovo un errore nostro, lo correggo: una correzione dei documenti è compresa.
+
+
+
Servizio in prova. Stiamo seguendo le prime pratiche e aggiustiamo prezzi e tempi con quello che impariamo: per questo i prezzi sono «da» e il preventivo scritto arriva prima di ogni impegno. Preferite solo il rider della band? C'è il rider tecnico completo a 59 €; o fate da soli con l'editor gratuito.
+
+
Descrivete il vostro evento
+
Bastano cinque minuti. Se avete una pianta del luogo, un programma o un vecchio rider, potete mandarli dopo, rispondendo alla nostra mail.
+
+
+
+
Domande frequenti
+
+ Quanto costa?
Da 59 € per i soli documenti (una band fino a 12 musicisti), da 290 € per il coordinamento a distanza, da 790 € con Simone sul posto il giorno dell'evento. Sono prezzi di partenza, in prova: dopo la vostra richiesta ricevete un preventivo scritto con importo finale e IVA, e si parte solo dopo il vostro sì.
+ Cosa serve per cominciare?
Data, luogo, tipo di evento, numero di persone e cosa c'è già (palco, impianto, corrente). Se avete una pianta del luogo, un programma o un vecchio rider, meglio; altrimenti le domande ve le facciamo noi.
+ Fate anche i permessi?
Non firmiamo né presentiamo domande al posto vostro. Vi diciamo quali carte servono, chi le firma (un tecnico abilitato, il palchista, l'installatore, il Comune), con quali dati e per quando, e controlliamo che arrivino. Le regole cambiano da Comune a Comune: per ogni evento si confermano con l'ufficio competente, in genere lo sportello unico per le attività produttive.
+ Perché non vendete anche il service?
Perché così possiamo dirvi quale offerta conviene. Se volete, vi suggeriamo fornitori a cui chiedere; né loro né noi riceviamo commissioni, e lo scriviamo anche nella richiesta di preventivo.
+ Posso modificare i documenti dopo?
Sì. Lo stage plot si riapre su stageplot.it con «Importa», gratis e senza account, e lo cambiate da soli quando cambia il palco. Una correzione dei documenti consegnati è compresa.
+ Quanto tempo prima devo scrivervi?
Per un evento in piazza meglio otto settimane prima: di solito i service si interpellano da quattro a otto settimane prima e le carte chiedono tempo. Per una festa aziendale quattro-cinque settimane; per i soli documenti bastano tre giorni lavorativi dal materiale completo. Con meno tempo scriveteci lo stesso: vi diciamo cosa si riesce a fare.
+ Lavorate fuori dal Veneto?
I livelli 1 e 2 sì, a distanza. Il livello 3, con Simone sul posto, si valuta in base alla distanza e al giorno.
+ Cosa significa «servizio in prova»?
Che stiamo seguendo le prime pratiche e misurando tempi e lavoro per aggiustare prezzi e consegne. Per voi cambia una cosa: i prezzi sono di partenza e li confermiamo per iscritto prima di cominciare.
+ Chi c'è dietro?
Simone Castellan, sound engineer di Castelfranco Veneto: prepara ogni anno stage plot, channel list e richieste ai service per concerti anche con orchestra e ha creato stageplot.it. Ogni documento e ogni mail ai fornitori passa da lui prima di partire.
+
+
+
+
+
+
+
+
diff --git a/produzione-eventi/richiesta.js b/produzione-eventi/richiesta.js
new file mode 100644
index 0000000..bd99d8f
--- /dev/null
+++ b/produzione-eventi/richiesta.js
@@ -0,0 +1,220 @@
+/* Produzione eventi — il modulo di richiesta (produzione-eventi/, 13/11/2026).
+ Servizio «Produzione eventi» di stageplot.it (livelli 1-3, prezzi «da», in prova): chi organizza un evento descrive
+ cosa gli serve, Simone Castellan risponde entro un giorno lavorativo con 2-3 domande e il preventivo.
+
+ COME ARRIVA. Come per il rider (rider-tecnico-completo/richiesta.js): nessun server nuovo, la richiesta passa dalla
+ Edge Function del box segnalazioni (submit-feedback). Il messaggio (5-1000 caratteri, l'unica parte che la mail di avviso
+ mostra) inizia con «RICHIESTA PRODUZIONE EVENTO» e porta il RIASSUNTO; il tech_context (fino a 32 KB) la richiesta intera,
+ campo per campo. La mail di avviso va dove la manda la funzione (variabile NOTIFY_EMAIL): la casella produzione@stageplot.it
+ è indicata in pagina come alternativa, non è il destinatario di questo modulo.
+
+ PAGAMENTO. Nessuno: il modulo non chiede soldi; il preventivo scritto lo manda Simone dopo la richiesta.
+
+ Il file ha due metà: funzioni PURE esportate su globalThis.ProduzioneRichiesta e, in Node, su module.exports (le prova
+ test/produzione-richiesta.test.mjs); sotto, il collegamento al modulo vero, che parte solo nel browser. */
+(function (root) {
+ "use strict";
+
+ /* ================================================================== 1. FUNZIONI PURE */
+
+ var MSG_MIN = 5, MSG_MAX = 1000; /* gli stessi limiti del server (feedback-validation.ts) */
+ var TECH_MAX = 32768; /* tech_context oltre questo il server lo butta: si resta molto sotto */
+ var INTESTAZIONE = "RICHIESTA PRODUZIONE EVENTO";
+ var SERVIZIO = "produzione-eventi";
+ var URL_INVIO = "https://vsodplqkuvnsdiikvmjb.supabase.co/functions/v1/submit-feedback";
+
+ /* I campi del modulo: nome nel form · lunghezza massima · obbligatorio. Una lista sola per pulizia, controlli e test. */
+ var CAMPI = [
+ ["tipo", 40, true], ["data", 10, false], ["luogo", 100, true], ["persone", 40, false],
+ ["presente", 40, false], ["livello", 40, false], ["budget", 60, false],
+ ["descrizione", 1500, true], ["scadenze", 400, false],
+ ["ente", 100, false], ["nome", 80, true], ["email", 120, true], ["telefono", 30, false],
+ ["privacy", 3, true]
+ ];
+ var TIPI = ["concerto-piazza", "rassegna", "festa-azienda", "convention", "evento-band", "serata-location", "altro"];
+ var PERSONE = ["fino-100", "100-300", "300-1000", "oltre-1000", "non-so"];
+ var PRESENTE = ["niente", "palco", "palco-impianto", "non-so"];
+ var LIVELLI = ["documenti", "distanza", "in-loco", "non-so"];
+ var CAMPI_A_CAPO = { descrizione: 1, scadenze: 1 };
+
+ /* Testo su una riga o a capo, senza caratteri di controllo, tagliato alla misura del campo */
+ function pulito(v, max, aCapo) {
+ var s = String(v == null ? "" : v).replace(/\r\n?/g, "\n");
+ // eslint-disable-next-line no-control-regex
+ s = s.replace(/[\u0000-\u0008\u000b-\u001f\u007f-\u009f\u200b\u200e\u200f\u202a-\u202e\u2066-\u2069]/g, " ");
+ s = aCapo ? s.replace(/[ \t]+/g, " ").replace(/\n{3,}/g, "\n\n").trim() : s.replace(/\s+/g, " ").trim();
+ return s.slice(0, max);
+ }
+
+ function scelta(v, ammessi) { return ammessi.indexOf(v) < 0 ? "" : v; }
+
+ /* Da un oggetto qualsiasi (FormData letta, test) ai soli campi noti, puliti */
+ function datiPuliti(o) {
+ o = o || {};
+ var d = {};
+ for (var i = 0; i < CAMPI.length; i++) {
+ var k = CAMPI[i][0], max = CAMPI[i][1];
+ d[k] = pulito(o[k], max, !!CAMPI_A_CAPO[k]);
+ }
+ d.tipo = scelta(d.tipo, TIPI);
+ d.persone = scelta(d.persone, PERSONE);
+ d.presente = scelta(d.presente, PRESENTE);
+ d.livello = scelta(d.livello, LIVELLI);
+ d.privacy = (o.privacy === true || o.privacy === "si" || o.privacy === "on") ? "si" : "";
+ if (!/^\d{4}-\d{2}-\d{2}$/.test(d.data)) d.data = "";
+ d.email = d.email.toLowerCase();
+ return d;
+ }
+
+ function emailValida(e) { return /^[^\s@]+@[^\s@]+\.[^\s@]{2,}$/.test(String(e || "")); }
+
+ /* {campo: "frase"} per ciò che manca o non va; {} = si può inviare */
+ function errori(d) {
+ var out = {};
+ if (!d.tipo) out.tipo = "Scegli che tipo di evento è.";
+ if (!d.luogo) out.luogo = "Scrivi dove si svolge (anche solo il comune).";
+ if (d.descrizione.length < 10) out.descrizione = "Raccontaci in breve cosa deve succedere.";
+ if (!d.nome) out.nome = "Scrivi il tuo nome.";
+ if (!emailValida(d.email)) out.email = "Serve un indirizzo email valido: rispondiamo lì.";
+ if (d.privacy !== "si") out.privacy = "Serve il consenso per poterti rispondere.";
+ return out;
+ }
+
+ var ETICHETTE = {
+ tipo: {
+ "concerto-piazza": "concerto in piazza", "rassegna": "rassegna o più serate", "festa-azienda": "festa aziendale",
+ "convention": "convention o cena di gala", "evento-band": "evento di una band", "serata-location": "serata in una location",
+ "altro": "altro"
+ },
+ persone: { "fino-100": "fino a 100", "100-300": "100-300", "300-1000": "300-1000", "oltre-1000": "oltre 1000", "non-so": "da definire" },
+ presente: { "niente": "non c'è niente", "palco": "c'è il palco", "palco-impianto": "palco e impianto", "non-so": "da verificare" },
+ livello: { "documenti": "documenti (1)", "distanza": "coordinamento a distanza (2)", "in-loco": "produzione in loco (3)", "non-so": "da capire" }
+ };
+ function data(d) { return d ? d.slice(8, 10) + "/" + d.slice(5, 7) + "/" + d.slice(0, 4) : ""; }
+
+ /* Il riassunto per la mail (≤ 1000 caratteri). Prima i contatti e il tipo di evento: se qualcosa va tagliato,
+ si taglia la descrizione, mai a chi rispondere. */
+ function messaggio(d) {
+ var testa = [
+ INTESTAZIONE,
+ "Evento: " + ETICHETTE.tipo[d.tipo] + " · " + d.luogo + (d.data ? " · " + data(d.data) : ""),
+ "Contatto: " + d.nome + (d.ente ? " (" + d.ente + ")" : "") + " · " + d.email + (d.telefono ? " · " + d.telefono : ""),
+ [d.persone ? "persone: " + ETICHETTE.persone[d.persone] : "", d.presente ? "sul posto: " + ETICHETTE.presente[d.presente] : "",
+ d.livello ? "interessa: " + ETICHETTE.livello[d.livello] : "", d.budget ? "budget: " + d.budget : ""].filter(Boolean).join(" · ")
+ ].filter(function (r) { return r.trim(); }).join("\n");
+ var righe = d.descrizione.split("\n").map(function (r) { return r.trim(); }).filter(Boolean);
+ var coda = "\nDescrizione: " + righe.join(" / ") + (d.scadenze ? "\nScadenze: " + d.scadenze.replace(/\s+/g, " ") : "");
+ var tutto = testa + coda;
+ if (tutto.length <= MSG_MAX) return tutto;
+ var resto = MSG_MAX - testa.length - 30;
+ var codaCorta = resto > 0 ? coda.slice(0, resto).replace(/\s+\S*$/, "") : "";
+ return (testa.slice(0, MSG_MAX - 30) + codaCorta + "\n[continua nella richiesta]").slice(0, MSG_MAX);
+ }
+
+ /* Il corpo per submit-feedback (campi di feedback-validation.ts). Niente progetto, niente schermata, niente account:
+ l'email di chi scrive viaggia nel messaggio e nel tech_context, perché la risposta va a lei. */
+ function corpo(d, ctx) {
+ ctx = ctx || {};
+ return {
+ message: messaggio(d),
+ hint: null,
+ honeypot: String(ctx.honeypot || ""),
+ tech_context: { servizio: SERVIZIO, prova: true, richiesta: d },
+ meta: {
+ app_version: "servizio/" + SERVIZIO,
+ page_url: "https://stageplot.it/" + SERVIZIO + "/",
+ user_agent: String(ctx.userAgent || "").slice(0, 500),
+ viewport: String(ctx.viewport || "").slice(0, 40),
+ language: String(ctx.lingua || "").slice(0, 20)
+ },
+ screenshot: null
+ };
+ }
+
+ /* La risposta del server, in parole */
+ function esitoInvio(status, j) {
+ if (status === 200 && j && j.ok) return { ok: true };
+ if (status === 429) return { ok: false, testo: "Sono arrivate troppe richieste da qui: riprova fra un'ora, o scrivi a produzione@stageplot.it." };
+ if (status === 400) return { ok: false, testo: "Qualcosa nel modulo non va: controlla i campi e riprova." };
+ return { ok: false, testo: "La richiesta non è partita: controlla la connessione e riprova, o scrivi a produzione@stageplot.it." };
+ }
+
+ var PR = {
+ CAMPI: CAMPI, MSG_MIN: MSG_MIN, MSG_MAX: MSG_MAX, TECH_MAX: TECH_MAX, INTESTAZIONE: INTESTAZIONE, URL_INVIO: URL_INVIO,
+ TIPI: TIPI, PERSONE: PERSONE, PRESENTE: PRESENTE, LIVELLI: LIVELLI,
+ pulito: pulito, datiPuliti: datiPuliti, emailValida: emailValida, errori: errori, messaggio: messaggio, corpo: corpo,
+ esitoInvio: esitoInvio
+ };
+ root.ProduzioneRichiesta = PR;
+ if (typeof module === "object" && module && module.exports) module.exports = PR;
+
+ /* ================================================================== 2. IL MODULO VERO (solo browser) */
+
+ if (typeof document === "undefined") return;
+
+ function avvia() {
+ var form = document.getElementById("richiesta-form");
+ if (!form) return;
+ var esito = document.getElementById("richiesta-esito");
+ var bottone = form.querySelector("button[type=submit]");
+
+ function leggi() {
+ var o = {};
+ for (var i = 0; i < CAMPI.length; i++) {
+ var k = CAMPI[i][0], el = form.elements[k];
+ if (!el) continue;
+ o[k] = el.type === "checkbox" ? (el.checked ? "si" : "") : el.value;
+ }
+ return o;
+ }
+ function mostraErrori(err) {
+ var primo = null;
+ for (var i = 0; i < CAMPI.length; i++) {
+ var k = CAMPI[i][0], el = form.elements[k], box = document.getElementById("err-" + k);
+ if (!el) continue;
+ if (err[k]) { el.setAttribute("aria-invalid", "true"); if (!primo) primo = el; }
+ else el.removeAttribute("aria-invalid");
+ if (box) box.textContent = err[k] || "";
+ }
+ if (primo) primo.focus();
+ }
+ function scrivi(testo, tipo) {
+ esito.textContent = testo;
+ esito.className = "esito " + (tipo || "");
+ esito.hidden = !testo;
+ }
+
+ form.addEventListener("submit", function (ev) {
+ ev.preventDefault();
+ var d = datiPuliti(leggi());
+ var err = errori(d);
+ mostraErrori(err);
+ if (Object.keys(err).length) { scrivi("Mancano alcune cose: le trovi segnate in rosso.", "ko"); return; }
+ var body = corpo(d, {
+ honeypot: form.elements.sito ? form.elements.sito.value : "",
+ userAgent: navigator.userAgent, viewport: innerWidth + "x" + innerHeight, lingua: navigator.language
+ });
+ bottone.disabled = true;
+ scrivi("Invio in corso…", "");
+ var ctrl = typeof AbortController === "function" ? new AbortController() : null;
+ var timer = ctrl ? setTimeout(function () { ctrl.abort(); }, 20000) : null; /* una richiesta che non torna mai: dopo 20 s si dice */
+ fetch(URL_INVIO, {
+ method: "POST", headers: { "Content-Type": "application/json" },
+ body: JSON.stringify(body), signal: ctrl ? ctrl.signal : undefined
+ }).then(function (r) {
+ return r.json().catch(function () { return null; }).then(function (j) { return esitoInvio(r.status, j); });
+ }, function () { return esitoInvio(0, null); }).then(function (e) {
+ if (timer) clearTimeout(timer);
+ bottone.disabled = false;
+ if (e.ok) {
+ form.hidden = true;
+ scrivi("Richiesta arrivata, grazie. Ti rispondiamo entro un giorno lavorativo all'indirizzo " +
+ d.email + " con due o tre domande e il preventivo: controlla anche lo spam. Se hai una pianta del luogo, un programma o un vecchio rider, allegali rispondendo a quella mail.", "ok");
+ esito.focus();
+ } else scrivi(e.testo, "ko");
+ });
+ });
+ }
+ if (document.readyState === "loading") document.addEventListener("DOMContentLoaded", avvia);
+ else avvia();
+})(typeof globalThis !== "undefined" ? globalThis : this);
diff --git a/rider-tecnico-completo/index.html b/rider-tecnico-completo/index.html
index 118455f..d46c3d7 100644
--- a/rider-tecnico-completo/index.html
+++ b/rider-tecnico-completo/index.html
@@ -155,6 +155,7 @@
Chi lo controlla
Per chi è: band e gruppi fino a 12 musicisti, una formazione, un palco tipo. Orchestre, cori ed eventi con più palchi o più scene si fanno su preventivo. Non comprende noleggi, contatti col service al posto vostro e sopralluoghi.
+
Per un evento intero: se vi serve anche chi confronta i preventivi dei service, prepara il cronoprogramma e vi dice quali permessi servono e chi li firma, c'è Produzione eventi, in prova.
diff --git a/sitemap.xml b/sitemap.xml
index 247b0d2..1caae51 100644
--- a/sitemap.xml
+++ b/sitemap.xml
@@ -12,6 +12,12 @@
monthly0.8
+
+ https://stageplot.it/produzione-eventi/
+ 2026-11-13
+ monthly
+ 0.7
+ https://stageplot.it/consulenza/2026-11-11
diff --git a/supabase/functions/_shared/produzione-richiesta.test.ts b/supabase/functions/_shared/produzione-richiesta.test.ts
new file mode 100644
index 0000000..6cd8625
--- /dev/null
+++ b/supabase/functions/_shared/produzione-richiesta.test.ts
@@ -0,0 +1,42 @@
+// Contratto fra il modulo di «Produzione eventi» (produzione-eventi/richiesta.js, 13/11/2026) e il server.
+// Il modulo non ha una funzione sua: passa da submit-feedback (come il rider). Se un giorno la validazione cambia
+// (lunghezze, tetti, campi), questo test lo dice prima che le richieste di Comuni e aziende vengano rifiutate in silenzio.
+import { assert, assertEquals } from "jsr:@std/assert@1";
+import { validateFeedback } from "./feedback-validation.ts";
+import "../../../produzione-eventi/richiesta.js";
+
+// deno-lint-ignore no-explicit-any
+const PR = (globalThis as any).ProduzioneRichiesta;
+
+const BASE = {
+ tipo: "festa-azienda", data: "2027-12-10", luogo: "Borgo Salice (inventato)", persone: "100-300", presente: "palco",
+ livello: "distanza", budget: "3.000 €", descrizione: "Cena con saluti e una band di sette elementi.",
+ scadenze: "Preventivi entro novembre", ente: "Azienda di prova", nome: "Mario Prova", email: "prova@esempio.it",
+ telefono: "000 000 0000", privacy: "on",
+};
+
+Deno.test("la richiesta normale passa la validazione del server com'è", () => {
+ const b = PR.corpo(PR.datiPuliti(BASE), { userAgent: "UA", viewport: "390x844", lingua: "it-IT" });
+ const v = validateFeedback(b);
+ assert(v.ok, JSON.stringify(v));
+ if (!v.ok) return;
+ assertEquals(v.value.message, b.message);
+ assertEquals(v.value.tech_context, b.tech_context, "il contesto arriva intero: è lì la richiesta completa");
+ assertEquals(v.value.hint, null);
+});
+
+Deno.test("anche la richiesta più lunga possibile passa intera", () => {
+ // deno-lint-ignore no-explicit-any
+ const pieno = Object.fromEntries(PR.CAMPI.map(([k, max]: any) => [k, "é".repeat(max)]));
+ const b = PR.corpo(PR.datiPuliti({ ...pieno, tipo: "altro", email: "prova@esempio.it", privacy: "on" }));
+ const v = validateFeedback(b);
+ assert(v.ok, JSON.stringify(v));
+ if (!v.ok) return;
+ assertEquals(v.value.tech_context, b.tech_context);
+});
+
+Deno.test("il campo trappola compilato è spam per il server", () => {
+ const b = PR.corpo(PR.datiPuliti(BASE), { honeypot: "http://spam" });
+ const v = validateFeedback(b);
+ assert(!v.ok && v.error === "spam");
+});
diff --git a/test/produzione-richiesta.test.mjs b/test/produzione-richiesta.test.mjs
new file mode 100644
index 0000000..81794ad
--- /dev/null
+++ b/test/produzione-richiesta.test.mjs
@@ -0,0 +1,148 @@
+/* Produzione eventi — il modulo di richiesta e la pagina (produzione-eventi/, 13/11/2026).
+ Parti pure di richiesta.js (pulizia dei campi, controlli, riassunto per la mail ≤ 1000 caratteri con i contatti sempre
+ dentro, corpo per submit-feedback) e la pagina che le usa: un campo per ogni voce di CAMPI, la CSP che lascia uscire
+ solo la richiesta, schema e FAQ coerenti con quanto si vede, prezzi «da» uguali in pagina e nei dati strutturati, nessun
+ PDF scaricabile (decisione PE-05), nessun testimonial. Il contratto col server sta in
+ supabase/functions/_shared/produzione-richiesta.test.ts.
+ node --test test/produzione-richiesta.test.mjs */
+import { test } from "node:test";
+import assert from "node:assert/strict";
+import { createRequire } from "node:module";
+import { readFileSync, existsSync, readdirSync, statSync } from "node:fs";
+import { join, dirname } from "node:path";
+import { fileURLToPath } from "node:url";
+const root = join(dirname(fileURLToPath(import.meta.url)), "..");
+const req = createRequire(import.meta.url);
+const PR = req(join(root, "produzione-eventi/richiesta.js"));
+const leggi = (p) => readFileSync(join(root, p), "utf8");
+
+/* dati finti: il repo è pubblico */
+const BASE = {
+ tipo: "festa-azienda", data: "2027-12-10", luogo: "Borgo Salice (inventato)", persone: "100-300", presente: "palco",
+ livello: "distanza", budget: "3.000 €", descrizione: "Cena con saluti e una band di sette elementi.\nIl referente non è un tecnico.",
+ scadenze: "Preventivi entro novembre", ente: "Azienda di prova", nome: "Mario Prova", email: "Prova@Esempio.IT", telefono: "000 000 0000", privacy: "on"
+};
+
+test("un modulo compilato bene non ha errori", () => {
+ const d = PR.datiPuliti(BASE);
+ assert.deepEqual(PR.errori(d), {});
+ assert.equal(d.email, "prova@esempio.it");
+ assert.equal(d.privacy, "si");
+});
+
+test("mancano tipo, luogo, descrizione, nome, email o consenso: si dice campo per campo", () => {
+ const d = PR.datiPuliti({ ...BASE, tipo: "boh", luogo: " ", descrizione: "poco", nome: "", email: "non-una-mail", privacy: "" });
+ assert.deepEqual(Object.keys(PR.errori(d)).sort(), ["descrizione", "email", "luogo", "nome", "privacy", "tipo"]);
+});
+
+test("solo i campi noti, scelte solo fra quelle previste, data solo AAAA-MM-GG", () => {
+ const d = PR.datiPuliti({ ...BASE, persone: "tantissime