{"id":2107,"date":"2026-09-17T12:59:49","date_gmt":"2026-09-17T12:59:49","guid":{"rendered":"https:\/\/www.gestioneindustriale.it\/?page_id=2107"},"modified":"2026-09-17T12:59:50","modified_gmt":"2026-09-17T12:59:50","slug":"pagina-didattica","status":"publish","type":"page","link":"https:\/\/www.gestioneindustriale.it\/it_it\/pagina-didattica\/","title":{"rendered":"Pagina didattica"},"content":{"rendered":"    <div id=\"gf-app\" class=\"gf-app\">\n\n        <nav class=\"gf-tabs\" role=\"tablist\">\n            <button class=\"gf-tab gf-tab-attiva\" data-tab=\"genera\" type=\"button\">Genera Fattura<\/button>\n            <button class=\"gf-tab\" data-tab=\"importa-xml\" type=\"button\">Importa XML<\/button>\n            <button class=\"gf-tab\" data-tab=\"importa-pdf\" type=\"button\">Importa PDF<\/button>\n            <button class=\"gf-tab\" data-tab=\"anagrafiche\" type=\"button\">Anagrafiche<\/button>\n            <button class=\"gf-tab\" data-tab=\"genera-massiva\" type=\"button\">Genera Massiva<\/button>\n        <\/nav>\n\n        <div class=\"gf-riquadro gf-risorse\">\n            <strong>Verifica ufficiale del file XML<\/strong> - questo strumento non sostituisce il controllo formale del Sistema di Interscambio.\n            Il validatore ufficiale (\"Controlla fattura\") si trova dentro il portale autenticato Fatture e Corrispettivi\n            dell'Agenzia delle Entrate (serve SPID, CNS o CIE) - non e' un programma scaricabile a parte:\n            <a href=\"https:\/\/www.fatturapa.gov.it\/it\/sistemainterscambio\/controlli-ed-errori\/\" target=\"_blank\" rel=\"noopener noreferrer\">pagina informativa \"Controlli ed errori\"<\/a>\n            \/\n            <a href=\"https:\/\/ivaservizi.agenziaentrate.gov.it\/portale\/\" target=\"_blank\" rel=\"noopener noreferrer\">accedi al portale Fatture e Corrispettivi<\/a>.\n        <\/div>\n\n        <!-- ================= SCHEDA: GENERA FATTURA ================= -->\n        <section class=\"gf-pannello gf-pannello-attivo\" data-pannello=\"genera\">\n\n            <fieldset class=\"gf-riquadro\">\n                <legend>Cedente \/ Prestatore (i tuoi dati)<\/legend>\n                <div class=\"gf-griglia\">\n                    <label>Denominazione\n                        <input type=\"text\" id=\"gf-ced-denom\" placeholder=\"Es. Studio Didattico Rossi\">\n                    <\/label>\n                    <label>Partita IVA\n                        <input type=\"text\" id=\"gf-ced-piva\" maxlength=\"11\" placeholder=\"11 cifre\">\n                    <\/label>\n                    <label>Indirizzo\n                        <input type=\"text\" id=\"gf-ced-indirizzo\" placeholder=\"Via Roma 1\">\n                    <\/label>\n                    <label>CAP\n                        <input type=\"text\" id=\"gf-ced-cap\" maxlength=\"5\">\n                    <\/label>\n                    <label>Comune\n                        <input type=\"text\" id=\"gf-ced-comune\">\n                    <\/label>\n                    <label>Provincia\n                        <input type=\"text\" id=\"gf-ced-provincia\" maxlength=\"2\" placeholder=\"Es. TO\">\n                    <\/label>\n                    <label>Regime fiscale\n                        <select id=\"gf-ced-regime\">\n                            <option value=\"RF01\">RF01 - Ordinario<\/option>\n                            <option value=\"RF19\">RF19 - Forfetario<\/option>\n                        <\/select>\n                    <\/label>\n                <\/div>\n                <p class=\"gf-nota\">Questi dati vengono salvati solo nel tuo browser (localStorage), per non doverli reinserire ogni volta.<\/p>\n            <\/fieldset>\n\n            <fieldset class=\"gf-riquadro\">\n                <legend>Cessionario \/ Committente (controparte fittizia)<\/legend>\n                <div class=\"gf-azioni\">\n                    <button type=\"button\" id=\"gf-btn-genera-controparte\" class=\"gf-btn gf-btn-primario\">Genera controparte fittizia<\/button>\n                    <label style=\"display:inline-flex; align-items:center; gap:6px; margin-left:10px;\">oppure usa dall'elenco:\n                        <select id=\"gf-ces-da-elenco\"><option value=\"\">-- nessuna --<\/option><\/select>\n                    <\/label>\n                <\/div>\n                <div class=\"gf-griglia\">\n                    <label>Denominazione\n                        <input type=\"text\" id=\"gf-ces-denom\">\n                    <\/label>\n                    <label>Partita IVA\n                        <input type=\"text\" id=\"gf-ces-piva\" maxlength=\"11\">\n                    <\/label>\n                    <label>Indirizzo\n                        <input type=\"text\" id=\"gf-ces-indirizzo\">\n                    <\/label>\n                    <label>CAP\n                        <input type=\"text\" id=\"gf-ces-cap\" maxlength=\"5\">\n                    <\/label>\n                    <label>Comune\n                        <input type=\"text\" id=\"gf-ces-comune\">\n                    <\/label>\n                    <label>Provincia\n                        <input type=\"text\" id=\"gf-ces-provincia\" maxlength=\"2\">\n                    <\/label>\n                <\/div>\n            <\/fieldset>\n\n            <fieldset class=\"gf-riquadro\">\n                <legend>Dati generali documento<\/legend>\n                <div class=\"gf-griglia\">\n                    <label>Numero fattura\n                        <input type=\"text\" id=\"gf-doc-numero\" value=\"1\">\n                    <\/label>\n                    <label>Data\n                        <input type=\"date\" id=\"gf-doc-data\">\n                    <\/label>\n                    <label>Tipo documento\n                        <select id=\"gf-doc-tipo\"><\/select>\n                    <\/label>\n                    <label>Modalita' di pagamento\n                        <select id=\"gf-doc-pagamento\">\n                            <option value=\"MP05\">MP05 - Bonifico<\/option>\n                            <option value=\"MP01\">MP01 - Contanti<\/option>\n                            <option value=\"MP08\">MP08 - Carta di pagamento<\/option>\n                        <\/select>\n                    <\/label>\n                <\/div>\n            <\/fieldset>\n\n            <fieldset class=\"gf-riquadro\">\n                <legend>Righe fattura<\/legend>\n                <div class=\"gf-tabella-scroll\">\n                <table class=\"gf-tabella-righe\" id=\"gf-tabella-righe\">\n                    <thead>\n                        <tr>\n                            <th>Descrizione<\/th>\n                            <th>Quantita'<\/th>\n                            <th>Prezzo unitario (EUR)<\/th>\n                            <th>Aliquota IVA<\/th>\n                            <th><\/th>\n                        <\/tr>\n                    <\/thead>\n                    <tbody id=\"gf-righe-body\"><\/tbody>\n                <\/table>\n                <\/div>\n                <div class=\"gf-azioni\">\n                    <button type=\"button\" id=\"gf-btn-aggiungi-riga\" class=\"gf-btn\">Aggiungi riga<\/button>\n                <\/div>\n            <\/fieldset>\n\n            <fieldset class=\"gf-riquadro\">\n                <legend>Totali<\/legend>\n                <div id=\"gf-totali\" class=\"gf-totali\">Imponibile: 0,00 EUR - IVA: 0,00 EUR - Totale: 0,00 EUR<\/div>\n            <\/fieldset>\n\n            <div class=\"gf-azioni gf-azioni-finali\">\n                <button type=\"button\" id=\"gf-btn-genera-xml\" class=\"gf-btn gf-btn-primario gf-btn-grande\">Genera XML e scarica<\/button>\n            <\/div>\n            <div id=\"gf-messaggio\" class=\"gf-messaggio\"><\/div>\n\n        <\/section>\n\n        <!-- ================= SCHEDE PLACEHOLDER ================= -->\n        <section class=\"gf-pannello\" data-pannello=\"importa-xml\">\n\n            <fieldset class=\"gf-riquadro\">\n                <legend>1. Carica il file XML reale<\/legend>\n                <input type=\"file\" id=\"gfx-file\" accept=\".xml\">\n                <p class=\"gf-nota\">Il file viene letto solo nel tuo browser (FileReader). Non viene inviato a nessun server.<\/p>\n                <div id=\"gfx-messaggio\" class=\"gf-messaggio\"><\/div>\n            <\/fieldset>\n\n            <fieldset class=\"gf-riquadro\">\n                <legend>2. Quale soggetto vuoi anonimizzare?<\/legend>\n                <label><input type=\"radio\" name=\"gfx-ruolo\" value=\"cessionario\" checked> Cessionario\/Committente (il cliente - caso tipico: fattura di vendita)<\/label><br>\n                <label><input type=\"radio\" name=\"gfx-ruolo\" value=\"cedente\"> Cedente\/Prestatore (il fornitore - caso tipico: fattura di acquisto)<\/label>\n                <div class=\"gf-azioni\">\n                    <button type=\"button\" id=\"gfx-btn-genera-controparte\" class=\"gf-btn gf-btn-primario\">Genera anagrafica fittizia per il soggetto selezionato<\/button>\n                <\/div>\n            <\/fieldset>\n\n            <fieldset class=\"gf-riquadro\">\n                <legend>Cedente \/ Prestatore<\/legend>\n                <div class=\"gf-griglia\">\n                    <label>Denominazione <input type=\"text\" id=\"gfx-ced-denom\"><\/label>\n                    <label>Partita IVA <input type=\"text\" id=\"gfx-ced-piva\" maxlength=\"11\"><\/label>\n                    <label>Indirizzo <input type=\"text\" id=\"gfx-ced-indirizzo\"><\/label>\n                    <label>CAP <input type=\"text\" id=\"gfx-ced-cap\" maxlength=\"5\"><\/label>\n                    <label>Comune <input type=\"text\" id=\"gfx-ced-comune\"><\/label>\n                    <label>Provincia <input type=\"text\" id=\"gfx-ced-provincia\" maxlength=\"2\"><\/label>\n                    <label>Regime fiscale\n                        <select id=\"gfx-ced-regime\">\n                            <option value=\"RF01\">RF01 - Ordinario<\/option>\n                            <option value=\"RF19\">RF19 - Forfetario<\/option>\n                        <\/select>\n                    <\/label>\n                <\/div>\n            <\/fieldset>\n\n            <fieldset class=\"gf-riquadro\">\n                <legend>Cessionario \/ Committente<\/legend>\n                <div class=\"gf-griglia\">\n                    <label>Denominazione <input type=\"text\" id=\"gfx-ces-denom\"><\/label>\n                    <label>Partita IVA <input type=\"text\" id=\"gfx-ces-piva\" maxlength=\"11\"><\/label>\n                    <label>Indirizzo <input type=\"text\" id=\"gfx-ces-indirizzo\"><\/label>\n                    <label>CAP <input type=\"text\" id=\"gfx-ces-cap\" maxlength=\"5\"><\/label>\n                    <label>Comune <input type=\"text\" id=\"gfx-ces-comune\"><\/label>\n                    <label>Provincia <input type=\"text\" id=\"gfx-ces-provincia\" maxlength=\"2\"><\/label>\n                <\/div>\n            <\/fieldset>\n\n            <fieldset class=\"gf-riquadro\">\n                <legend>Dati generali documento<\/legend>\n                <div class=\"gf-griglia\">\n                    <label>Numero fattura <input type=\"text\" id=\"gfx-doc-numero\"><\/label>\n                    <label>Data <input type=\"date\" id=\"gfx-doc-data\"><\/label>\n                    <label>Tipo documento <select id=\"gfx-doc-tipo\"><\/select><\/label>\n                    <label>Modalita' di pagamento\n                        <select id=\"gfx-doc-pagamento\">\n                            <option value=\"MP05\">MP05 - Bonifico<\/option>\n                            <option value=\"MP01\">MP01 - Contanti<\/option>\n                            <option value=\"MP08\">MP08 - Carta di pagamento<\/option>\n                        <\/select>\n                    <\/label>\n                <\/div>\n            <\/fieldset>\n\n            <fieldset class=\"gf-riquadro\">\n                <legend>Righe fattura (lette dal file, modificabili)<\/legend>\n                <div class=\"gf-tabella-scroll\">\n                <table class=\"gf-tabella-righe\" id=\"gfx-tabella-righe\">\n                    <thead>\n                        <tr><th>Descrizione<\/th><th>Quantita'<\/th><th>Prezzo unitario (EUR)<\/th><th>Aliquota IVA<\/th><th><\/th><\/tr>\n                    <\/thead>\n                    <tbody id=\"gfx-righe-body\"><\/tbody>\n                <\/table>\n                <\/div>\n                <div class=\"gf-azioni\">\n                    <button type=\"button\" id=\"gfx-btn-aggiungi-riga\" class=\"gf-btn\">Aggiungi riga<\/button>\n                <\/div>\n            <\/fieldset>\n\n            <fieldset class=\"gf-riquadro\">\n                <legend>Totali<\/legend>\n                <div id=\"gfx-totali\" class=\"gf-totali\">Imponibile: 0,00 EUR - IVA: 0,00 EUR - Totale: 0,00 EUR<\/div>\n            <\/fieldset>\n\n            <div class=\"gf-azioni gf-azioni-finali\">\n                <button type=\"button\" id=\"gfx-btn-genera-xml\" class=\"gf-btn gf-btn-primario gf-btn-grande\">Genera XML anonimizzato e scarica<\/button>\n            <\/div>\n\n        <\/section>\n        <section class=\"gf-pannello\" data-pannello=\"importa-pdf\">\n\n            <fieldset class=\"gf-riquadro\">\n                <legend>1. Carica il PDF reale (schema Aruba)<\/legend>\n                <input type=\"file\" id=\"gfp-file\" accept=\".pdf\">\n                <p class=\"gf-nota\">Il PDF viene letto solo nel tuo browser tramite la libreria pdf.js (caricata da CDN pubblico, ma il contenuto del file non lascia mai il browser). Nessun upload su server.<\/p>\n                <div id=\"gfp-messaggio\" class=\"gf-messaggio\"><\/div>\n                <details class=\"gf-diagnostica\">\n                    <summary>Diagnostica testo estratto (apri se qualcosa non torna)<\/summary>\n                    <p class=\"gf-nota\">Se l'anagrafica di Fornitore\/Cliente risulta vuota o sbagliata, apri questo pannello, copia il contenuto e incollalo in chat: serve per correggere l'estrazione senza indovinare la struttura del PDF.<\/p>\n                    <textarea id=\"gfp-diagnostica\" rows=\"10\" readonly style=\"width:100%; font-family:monospace; font-size:11px;\"><\/textarea>\n                <\/details>\n            <\/fieldset>\n\n            <fieldset class=\"gf-riquadro\">\n                <legend>2. Quale soggetto vuoi anonimizzare?<\/legend>\n                <label><input type=\"radio\" name=\"gfp-ruolo\" value=\"cessionario\" checked> Cessionario\/Committente (cliente)<\/label><br>\n                <label><input type=\"radio\" name=\"gfp-ruolo\" value=\"cedente\"> Cedente\/Prestatore (fornitore)<\/label>\n                <div class=\"gf-azioni\">\n                    <button type=\"button\" id=\"gfp-btn-genera-controparte\" class=\"gf-btn gf-btn-primario\">Genera anagrafica fittizia per il soggetto selezionato<\/button>\n                <\/div>\n            <\/fieldset>\n\n            <fieldset class=\"gf-riquadro\">\n                <legend>Cedente \/ Prestatore (Fornitore)<\/legend>\n                <div class=\"gf-griglia\">\n                    <label>Denominazione <input type=\"text\" id=\"gfp-ced-denom\"><\/label>\n                    <label>Partita IVA <input type=\"text\" id=\"gfp-ced-piva\" maxlength=\"11\"><\/label>\n                    <label>Indirizzo <input type=\"text\" id=\"gfp-ced-indirizzo\"><\/label>\n                    <label>CAP <input type=\"text\" id=\"gfp-ced-cap\" maxlength=\"5\"><\/label>\n                    <label>Comune <input type=\"text\" id=\"gfp-ced-comune\"><\/label>\n                    <label>Provincia <input type=\"text\" id=\"gfp-ced-provincia\" maxlength=\"2\"><\/label>\n                    <label>Regime fiscale\n                        <select id=\"gfp-ced-regime\">\n                            <option value=\"RF01\">RF01 - Ordinario<\/option>\n                            <option value=\"RF19\">RF19 - Forfetario<\/option>\n                        <\/select>\n                    <\/label>\n                <\/div>\n            <\/fieldset>\n\n            <fieldset class=\"gf-riquadro\">\n                <legend>Cessionario \/ Committente (Cliente)<\/legend>\n                <div class=\"gf-griglia\">\n                    <label>Denominazione <input type=\"text\" id=\"gfp-ces-denom\"><\/label>\n                    <label>Partita IVA <input type=\"text\" id=\"gfp-ces-piva\" maxlength=\"11\"><\/label>\n                    <label>Indirizzo <input type=\"text\" id=\"gfp-ces-indirizzo\"><\/label>\n                    <label>CAP <input type=\"text\" id=\"gfp-ces-cap\" maxlength=\"5\"><\/label>\n                    <label>Comune <input type=\"text\" id=\"gfp-ces-comune\"><\/label>\n                    <label>Provincia <input type=\"text\" id=\"gfp-ces-provincia\" maxlength=\"2\"><\/label>\n                <\/div>\n            <\/fieldset>\n\n            <fieldset class=\"gf-riquadro\">\n                <legend>Dati generali documento<\/legend>\n                <div class=\"gf-griglia\">\n                    <label>Numero fattura <input type=\"text\" id=\"gfp-doc-numero\"><\/label>\n                    <label>Data <input type=\"date\" id=\"gfp-doc-data\"><\/label>\n                    <label>Tipo documento <select id=\"gfp-doc-tipo\"><\/select><\/label>\n                    <label>Modalita' di pagamento\n                        <select id=\"gfp-doc-pagamento\">\n                            <option value=\"MP05\">MP05 - Bonifico<\/option>\n                            <option value=\"MP01\">MP01 - Contanti<\/option>\n                            <option value=\"MP08\">MP08 - Carta di pagamento<\/option>\n                        <\/select>\n                    <\/label>\n                <\/div>\n            <\/fieldset>\n\n            <fieldset class=\"gf-riquadro\">\n                <legend>Importi (il PDF non elenca le righe di dettaglio: viene creata una riga sintetica \"Importo fattura\")<\/legend>\n                <div class=\"gf-griglia\">\n                    <label>Imponibile (EUR) <input type=\"number\" step=\"0.01\" id=\"gfp-imponibile\"><\/label>\n                    <label>Totale IVA (EUR) <input type=\"number\" step=\"0.01\" id=\"gfp-iva\"><\/label>\n                    <label>Aliquota IVA calcolata (%) <input type=\"number\" step=\"0.01\" id=\"gfp-aliquota\"><\/label>\n                    <label>Totale documento (EUR) <input type=\"number\" step=\"0.01\" id=\"gfp-totale\"><\/label>\n                <\/div>\n                <p class=\"gf-nota\">Se l'aliquota calcolata non torna (es. IVA mista su piu' aliquote), correggila manualmente: qui si genera una sola riga XML con l'aliquota indicata.<\/p>\n            <\/fieldset>\n\n            <div class=\"gf-azioni gf-azioni-finali\">\n                <button type=\"button\" id=\"gfp-btn-genera-xml\" class=\"gf-btn gf-btn-primario gf-btn-grande\">Genera XML anonimizzato e scarica<\/button>\n            <\/div>\n\n        <\/section>\n        <section class=\"gf-pannello\" data-pannello=\"anagrafiche\">\n\n            <fieldset class=\"gf-riquadro\">\n                <legend>Genera nuova anagrafica fittizia (arricchita dal web)<\/legend>\n                <p class=\"gf-nota\">Recupera un comune reale casuale da un elenco pubblico dei comuni italiani e un cognome casuale da un servizio pubblico, per avere piu' varieta' rispetto ai pochi nomi fissi. Se il servizio non risponde (rete assente\/bloccata), si usa in automatico l'elenco locale ridotto come riserva.<\/p>\n                <div class=\"gf-griglia\">\n                    <label>Tipo\n                        <select id=\"ga-nuovo-tipo\">\n                            <option value=\"Cliente\">Cliente<\/option>\n                            <option value=\"Fornitore\">Fornitore<\/option>\n                        <\/select>\n                    <\/label>\n                <\/div>\n                <div class=\"gf-azioni\">\n                    <button type=\"button\" id=\"ga-btn-genera-web\" class=\"gf-btn gf-btn-primario\">Genera con arricchimento web<\/button>\n                    <span id=\"ga-stato-generazione\" class=\"gf-nota\"><\/span>\n                <\/div>\n            <\/fieldset>\n\n            <fieldset class=\"gf-riquadro\">\n                <legend>Aggiungi o modifica prima di salvare<\/legend>\n                <div class=\"gf-griglia\">\n                    <label>Tipo\n                        <select id=\"ga-tipo\">\n                            <option value=\"Cliente\">Cliente<\/option>\n                            <option value=\"Fornitore\">Fornitore<\/option>\n                        <\/select>\n                    <\/label>\n                    <label>Denominazione <input type=\"text\" id=\"ga-denom\"><\/label>\n                    <label>Partita IVA <input type=\"text\" id=\"ga-piva\" maxlength=\"11\"><\/label>\n                    <label>Indirizzo <input type=\"text\" id=\"ga-indirizzo\"><\/label>\n                    <label>CAP <input type=\"text\" id=\"ga-cap\" maxlength=\"5\"><\/label>\n                    <label>Comune <input type=\"text\" id=\"ga-comune\"><\/label>\n                    <label>Provincia <input type=\"text\" id=\"ga-provincia\" maxlength=\"2\"><\/label>\n                <\/div>\n                <div class=\"gf-azioni\">\n                    <button type=\"button\" id=\"ga-btn-aggiungi\" class=\"gf-btn gf-btn-primario\">Aggiungi all'elenco<\/button>\n                <\/div>\n                <div id=\"ga-messaggio\" class=\"gf-messaggio\"><\/div>\n            <\/fieldset>\n\n            <fieldset class=\"gf-riquadro\">\n                <legend>Elenco anagrafiche salvate<\/legend>\n                <div class=\"gf-tabella-scroll\">\n                <table class=\"gf-tabella-righe\" id=\"ga-tabella\">\n                    <thead>\n                        <tr><th>Tipo<\/th><th>Denominazione<\/th><th>P.IVA<\/th><th>Comune<\/th><th>Prov.<\/th><th><\/th><\/tr>\n                    <\/thead>\n                    <tbody id=\"ga-tabella-body\"><\/tbody>\n                <\/table>\n                <\/div>\n                <p class=\"gf-nota\" id=\"ga-contatore\">0 anagrafiche salvate.<\/p>\n                <div class=\"gf-azioni\">\n                    <button type=\"button\" id=\"ga-btn-esporta\" class=\"gf-btn\">Esporta backup (JSON)<\/button>\n                    <button type=\"button\" id=\"ga-btn-importa-apri\" class=\"gf-btn\">Importa backup (JSON)<\/button>\n                    <input type=\"file\" id=\"ga-file-importa\" accept=\".json\" style=\"display:none;\">\n                <\/div>\n                <p class=\"gf-nota\">I dati restano solo in questo browser: usa Esporta\/Importa per portarli su un altro dispositivo o come copia di sicurezza.<\/p>\n            <\/fieldset>\n\n        <\/section>\n\n        <section class=\"gf-pannello\" data-pannello=\"genera-massiva\">\n\n            <fieldset class=\"gf-riquadro\">\n                <legend>Dati aziendali propri<\/legend>\n                <div class=\"gf-griglia\">\n                    <label>Denominazione <input type=\"text\" id=\"gmx-propria-denom\"><\/label>\n                    <label>Partita IVA <input type=\"text\" id=\"gmx-propria-piva\" maxlength=\"11\"><\/label>\n                <\/div>\n                <p class=\"gf-nota\">Precompilati dai dati del Cedente inseriti nella scheda \"Genera Fattura\", modificabili qui senza toccarli laggiu'.<\/p>\n                <label><input type=\"radio\" name=\"gmx-tipo-anagrafica\" value=\"C\" checked> Vendita (i tuoi dati sono il Cedente, le controparti sono Clienti)<\/label><br>\n                <label><input type=\"radio\" name=\"gmx-tipo-anagrafica\" value=\"F\"> Acquisto (i tuoi dati sono il Cessionario, le controparti sono Fornitori)<\/label>\n            <\/fieldset>\n\n            <fieldset class=\"gf-riquadro\">\n                <legend>Periodo e imponibile<\/legend>\n                <div class=\"gf-griglia\">\n                    <label>Mese\n                        <select id=\"gmx-mese\">\n                            <option value=\"1\">Gennaio<\/option><option value=\"2\">Febbraio<\/option><option value=\"3\">Marzo<\/option>\n                            <option value=\"4\">Aprile<\/option><option value=\"5\">Maggio<\/option><option value=\"6\">Giugno<\/option>\n                            <option value=\"7\">Luglio<\/option><option value=\"8\">Agosto<\/option><option value=\"9\">Settembre<\/option>\n                            <option value=\"10\">Ottobre<\/option><option value=\"11\">Novembre<\/option><option value=\"12\">Dicembre<\/option>\n                        <\/select>\n                    <\/label>\n                    <label>Anno <input type=\"number\" id=\"gmx-anno\" min=\"2000\" max=\"2100\"><\/label>\n                    <label>Imponibile netto (EUR) <input type=\"number\" step=\"0.01\" id=\"gmx-imponibile\" placeholder=\"Fatture meno note di credito\"><\/label>\n                    <label>Aliquota IVA (%) <input type=\"number\" step=\"0.01\" id=\"gmx-aliquota\" value=\"22\"><\/label>\n                <\/div>\n                <label><input type=\"checkbox\" id=\"gmx-escludi-weekend\" checked> Escludi sabato e domenica dalle date di emissione<\/label><br>\n                <label><input type=\"checkbox\" id=\"gmx-escludi-festivi\" checked> Escludi le festivita' italiane (fisse + Pasqua\/Pasquetta)<\/label>\n            <\/fieldset>\n\n            <fieldset class=\"gf-riquadro\">\n                <legend>Mix tipologie documento (es. 15 TD01, 3 TD04, 2 TD21)<\/legend>\n                <div id=\"gmx-mix-lista\"><\/div>\n                <p class=\"gf-nota\" id=\"gmx-mix-vuoto\">Nessuna riga aggiunta.<\/p>\n                <button type=\"button\" id=\"gmx-btn-aggiungi-mix\" class=\"gf-btn\">+ Aggiungi<\/button>\n            <\/fieldset>\n\n            <fieldset class=\"gf-riquadro\">\n                <legend>Distribuzione scadenze pagamento (es. 2 a 30gg, 5 a fine mese, 10 a 90gg)<\/legend>\n                <div id=\"gmx-scad-lista\"><\/div>\n                <datalist id=\"gmx-giorni-comuni\">\n                    <option value=\"0\"><option value=\"30\"><option value=\"45\"><option value=\"60\">\n                    <option value=\"90\"><option value=\"120\"><option value=\"150\"><option value=\"180\">\n                <\/datalist>\n                <p class=\"gf-nota\" id=\"gmx-scad-vuoto\">Nessuna riga aggiunta.<\/p>\n                <button type=\"button\" id=\"gmx-btn-aggiungi-scad\" class=\"gf-btn\">+ Aggiungi<\/button>\n            <\/fieldset>\n\n            <fieldset class=\"gf-riquadro\">\n                <legend>Controparti da usare (a rotazione)<\/legend>\n                <div id=\"gmx-controparti-lista\" class=\"gf-nota\">Nessuna anagrafica salvata: vai nella scheda Anagrafiche per aggiungerne.<\/div>\n                <div class=\"gf-azioni\">\n                    <button type=\"button\" id=\"gmx-btn-aggiorna-controparti\" class=\"gf-btn\">Aggiorna elenco dalla scheda Anagrafiche<\/button>\n                <\/div>\n            <\/fieldset>\n\n            <div class=\"gf-azioni gf-azioni-finali\">\n                <button type=\"button\" id=\"gmx-btn-genera-anteprima\" class=\"gf-btn gf-btn-primario gf-btn-grande\">Genera set fatture (anteprima)<\/button>\n            <\/div>\n            <div id=\"gmx-messaggio\" class=\"gf-messaggio\"><\/div>\n\n            <fieldset class=\"gf-riquadro\" id=\"gmx-riquadro-anteprima\" style=\"display:none;\">\n                <legend>Anteprima set generato<\/legend>\n                <div id=\"gmx-verifica-totale\" class=\"gf-totali\"><\/div>\n                <div class=\"gf-tabella-scroll\">\n                <table class=\"gf-tabella-righe\">\n                    <thead><tr><th>#<\/th><th>Tipo<\/th><th>Emissione<\/th><th>Scadenza<\/th><th>Importo<\/th><th>Controparte<\/th><\/tr><\/thead>\n                    <tbody id=\"gmx-anteprima-body\"><\/tbody>\n                <\/table>\n                <\/div>\n                <div class=\"gf-azioni gf-azioni-finali\">\n                    <button type=\"button\" id=\"gmx-btn-scarica-zip\" class=\"gf-btn gf-btn-primario gf-btn-grande\">Scarica tutte in ZIP<\/button>\n                <\/div>\n            <\/fieldset>\n\n        <\/section>\n    <\/div>\n\n    <style>\n        \/* Palette coerente con il tema grigio\/antracite del progetto originale *\/\n        .gf-app { --gf-sfondo:#f3f4f6; --gf-superficie:#ffffff; --gf-accento:#374151; --gf-bordo:#d1d5db; --gf-testo:#111827;\n            background:var(--gf-sfondo); color:var(--gf-testo); padding:20px; border-radius:8px; font-family:sans-serif; max-width:960px; }\n        .gf-tabs { display:flex; flex-wrap:wrap; gap:6px; margin-bottom:16px; }\n        .gf-tab { padding:8px 14px; border:1px solid var(--gf-bordo); background:var(--gf-superficie); color:var(--gf-testo);\n            border-radius:6px; cursor:pointer; font-size:14px; }\n        .gf-tab-attiva { background:var(--gf-accento); color:#ffffff; border-color:var(--gf-accento); }\n        .gf-pannello { display:none; }\n        .gf-pannello-attivo { display:block; }\n        .gf-riquadro { border:1px solid var(--gf-bordo); border-radius:8px; padding:14px; margin-bottom:16px; background:var(--gf-superficie); }\n        .gf-riquadro legend { font-weight:bold; padding:0 6px; }\n        .gf-griglia { display:grid; grid-template-columns:repeat(auto-fit, minmax(200px, 1fr)); gap:10px; }\n        .gf-griglia label { display:flex; flex-direction:column; font-size:13px; gap:4px; }\n        .gf-griglia input, .gf-griglia select { padding:6px 8px; border:1px solid var(--gf-bordo); border-radius:4px; font-size:14px; }\n        .gf-azioni { margin:10px 0; }\n        .gf-btn { padding:8px 14px; border:1px solid var(--gf-accento); background:var(--gf-superficie); color:var(--gf-accento);\n            border-radius:6px; cursor:pointer; font-size:14px; }\n        .gf-btn-primario { background:var(--gf-accento); color:#ffffff; }\n        .gf-btn-grande { padding:12px 22px; font-size:16px; }\n        .gf-tabella-righe { width:100%; border-collapse:collapse; font-size:13px; }\n        .gf-tabella-righe th, .gf-tabella-righe td { border:1px solid var(--gf-bordo); padding:6px; text-align:left; }\n        .gf-tabella-righe input, .gf-tabella-righe select { width:100%; box-sizing:border-box; padding:4px; border:1px solid var(--gf-bordo); border-radius:4px; }\n        .gf-totali { font-size:15px; font-weight:bold; }\n        .gf-nota { font-size:12px; color:#4b5563; }\n        .gf-diagnostica { margin-top:8px; font-size:12px; }\n        .gf-diagnostica summary { cursor:pointer; color:var(--gf-accento); }\n        .gf-risorse { font-size:13px; }\n        .gf-risorse a { color:var(--gf-accento); }\n        .gf-messaggio { margin-top:10px; font-size:13px; }\n        .gf-messaggio.errore { color:#b91c1c; }\n        .gf-messaggio.successo { color:#15803d; }\n\n        \/* ============ Ottimizzazioni per smartphone\/tablet ============ *\/\n        \/* Tutto qui dentro si attiva SOLO sotto 640px: il desktop resta identico a prima *\/\n        .gf-tabella-scroll { overflow-x:visible; }\n        @media (max-width: 640px) {\n            .gf-app { padding:10px; }\n            .gf-tabs { gap:4px; }\n            .gf-tab { flex:1 1 40%; text-align:center; padding:10px 4px; font-size:13px; }\n            .gf-riquadro { padding:10px; }\n            .gf-griglia { grid-template-columns:1fr; gap:8px; }\n            .gf-griglia input, .gf-griglia select {\n                font-size:16px; \/* evita lo zoom automatico di iOS quando si tocca un campo *\/\n                padding:8px;\n            }\n            .gf-azioni { display:flex; flex-direction:column; gap:6px; }\n            .gf-azioni .gf-btn { width:100%; box-sizing:border-box; }\n            .gf-azioni label { display:block !important; margin-left:0 !important; margin-top:6px; }\n            .gf-btn-grande { padding:14px; font-size:16px; }\n            .gf-tabella-scroll { overflow-x:auto; -webkit-overflow-scrolling:touch; margin:0 -4px; }\n            .gf-tabella-righe { min-width:560px; }\n            .gf-totali { font-size:14px; }\n        }\n    <\/style>\n\n    <script src=\"https:\/\/cdnjs.cloudflare.com\/ajax\/libs\/pdf.js\/3.11.174\/pdf.min.js\"><\/script>\n    <script src=\"https:\/\/cdnjs.cloudflare.com\/ajax\/libs\/jszip\/3.10.1\/jszip.min.js\"><\/script>\n    <script>\n    (function () {\n        'use strict';\n        if (window.pdfjsLib) {\n            pdfjsLib.GlobalWorkerOptions.workerSrc = 'https:\/\/cdnjs.cloudflare.com\/ajax\/libs\/pdf.js\/3.11.174\/pdf.worker.min.js';\n        }\n\n\n        \/\/ ---------- Gestione schede ----------\n        var scheda = document.querySelectorAll('.gf-tab');\n        for (var i = 0; i < scheda.length; i++) {\n            scheda[i].addEventListener('click', function (ev) {\n                var nomeScheda = ev.target.getAttribute('data-tab');\n                document.querySelectorAll('.gf-tab').forEach(function (b) { b.classList.remove('gf-tab-attiva'); });\n                document.querySelectorAll('.gf-pannello').forEach(function (p) { p.classList.remove('gf-pannello-attivo'); });\n                ev.target.classList.add('gf-tab-attiva');\n                document.querySelector('.gf-pannello[data-pannello=\"' + nomeScheda + '\"]').classList.add('gf-pannello-attivo');\n            });\n        }\n\n        \/\/ ---------- Persistenza dati Cedente (localStorage) ----------\n        var CHIAVE_CEDENTE = 'gf_cedente_v1';\n\n        function salvaCedente() {\n            var dati = {\n                denom: document.getElementById('gf-ced-denom').value,\n                piva: document.getElementById('gf-ced-piva').value,\n                indirizzo: document.getElementById('gf-ced-indirizzo').value,\n                cap: document.getElementById('gf-ced-cap').value,\n                comune: document.getElementById('gf-ced-comune').value,\n                provincia: document.getElementById('gf-ced-provincia').value,\n                regime: document.getElementById('gf-ced-regime').value\n            };\n            try { localStorage.setItem(CHIAVE_CEDENTE, JSON.stringify(dati)); } catch (e) { \/* localStorage non disponibile, si ignora *\/ }\n        }\n\n        function caricaCedente() {\n            try {\n                var grezzo = localStorage.getItem(CHIAVE_CEDENTE);\n                if (!grezzo) return;\n                var dati = JSON.parse(grezzo);\n                document.getElementById('gf-ced-denom').value = dati.denom || '';\n                document.getElementById('gf-ced-piva').value = dati.piva || '';\n                document.getElementById('gf-ced-indirizzo').value = dati.indirizzo || '';\n                document.getElementById('gf-ced-cap').value = dati.cap || '';\n                document.getElementById('gf-ced-comune').value = dati.comune || '';\n                document.getElementById('gf-ced-provincia').value = dati.provincia || '';\n                document.getElementById('gf-ced-regime').value = dati.regime || 'RF01';\n            } catch (e) { \/* dati salvati non validi, si riparte da vuoto *\/ }\n        }\n\n        document.querySelectorAll('#gf-app fieldset:first-of-type input, #gf-app fieldset:first-of-type select')\n            .forEach(function (campo) { campo.addEventListener('change', salvaCedente); });\n\n        \/\/ ---------- Generatore Partita IVA fittizia (con cifra di controllo valida) ----------\n        function calcolaCifraControllo(cifre) {\n            var somma = 0;\n            for (var i = 0; i < 10; i++) {\n                var c = cifre[i];\n                if (i % 2 === 0) {\n                    somma += c;\n                } else {\n                    var doppio = c * 2;\n                    if (doppio > 9) doppio -= 9;\n                    somma += doppio;\n                }\n            }\n            return (10 - (somma % 10)) % 10;\n        }\n\n        function generaPartitaIvaFittizia() {\n            var cifre = [];\n            for (var i = 0; i < 7; i++) cifre.push(Math.floor(Math.random() * 10));\n            \/\/ codice ufficio provinciale: valore generico fittizio\n            cifre.push(0, 0, 1);\n            var cifraControllo = calcolaCifraControllo(cifre);\n            cifre.push(cifraControllo);\n            return cifre.join('');\n        }\n\n        \/\/ ---------- Dati fittizi di comodo ----------\n        var DENOMINAZIONI_FITTIZIE = [\n            'Alfa Servizi Srl', 'Beta Commerciale Snc', 'Gamma Forniture Srl',\n            'Delta Logistica Spa', 'Epsilon Consulenze Srl', 'Zeta Materiali Srl',\n            'Eta Distribuzione Srl', 'Theta Impianti Snc'\n        ];\n        var CITTA_FITTIZIE = [\n            { comune: 'Casalpusterlengo', provincia: 'LO', cap: '26841' },\n            { comune: 'Fossano', provincia: 'CN', cap: '12045' },\n            { comune: 'Chiavari', provincia: 'GE', cap: '16043' },\n            { comune: 'Fidenza', provincia: 'PR', cap: '43036' },\n            { comune: 'Chieri', provincia: 'TO', cap: '10023' }\n        ];\n\n        function scegliCasuale(elenco) {\n            return elenco[Math.floor(Math.random() * elenco.length)];\n        }\n\n        \/\/ ---------- Via\/piazza\/largo reale del comune, da elenco pubblico (OpenStreetMap via Overpass) ----------\n        var VIE_GENERICHE_RISERVA = [\n            'Via Roma', 'Via Garibaldi', 'Via Dante Alighieri', 'Corso Vittorio Emanuele',\n            'Via Cavour', 'Piazza della Repubblica', 'Via Mazzini', 'Largo Europa',\n            'Piazza Duomo', 'Via Giuseppe Verdi'\n        ];\n        var cacheViePerComune = {}; \/\/ nomeComune -> elenco nomi via gia' recuperati in questa sessione\n\n        async function recuperaViaCasualeDalWeb(nomeComune) {\n            if (!nomeComune) return null;\n            if (cacheViePerComune[nomeComune] && cacheViePerComune[nomeComune].length) {\n                return scegliCasuale(cacheViePerComune[nomeComune]);\n            }\n            try {\n                var query = '[out:json][timeout:15];area[\"name\"=\"' + nomeComune.replace(\/\"\/g, '') +\n                    '\"][\"boundary\"=\"administrative\"]->.a;(way(area.a)[highway][name];);out tags 60;';\n                var url = 'https:\/\/overpass-api.de\/api\/interpreter?data=' + encodeURIComponent(query);\n                var controller = new AbortController();\n                var timeoutId = setTimeout(function () { controller.abort(); }, 9000);\n                var risposta = await fetch(url, { signal: controller.signal });\n                clearTimeout(timeoutId);\n                if (!risposta.ok) throw new Error('Overpass non disponibile');\n                var dati = await risposta.json();\n                var nomi = [];\n                (dati.elements || []).forEach(function (el) {\n                    if (el.tags && el.tags.name && nomi.indexOf(el.tags.name) === -1) nomi.push(el.tags.name);\n                });\n                if (nomi.length === 0) return null;\n                cacheViePerComune[nomeComune] = nomi;\n                return scegliCasuale(nomi);\n            } catch (e) {\n                return null; \/\/ il chiamante ricadra' sull'elenco generico locale\n            }\n        }\n\n        \/\/ Restituisce sempre un indirizzo completo (via + civico casuale): via reale se disponibile, altrimenti di riserva\n        async function generaIndirizzoCasuale(nomeComune) {\n            var via = await recuperaViaCasualeDalWeb(nomeComune);\n            if (!via) via = scegliCasuale(VIE_GENERICHE_RISERVA);\n            return via + ' ' + (1 + Math.floor(Math.random() * 120));\n        }\n        document.getElementById('gf-btn-genera-controparte').addEventListener('click', async function () {\n            var citta = scegliCasuale(CITTA_FITTIZIE);\n            document.getElementById('gf-ces-denom').value = scegliCasuale(DENOMINAZIONI_FITTIZIE);\n            document.getElementById('gf-ces-piva').value = generaPartitaIvaFittizia();\n            document.getElementById('gf-ces-cap').value = citta.cap;\n            document.getElementById('gf-ces-comune').value = citta.comune;\n            document.getElementById('gf-ces-provincia').value = citta.provincia;\n            document.getElementById('gf-ces-indirizzo').value = 'Recupero indirizzo...';\n            document.getElementById('gf-ces-indirizzo').value = await generaIndirizzoCasuale(citta.comune);\n        });\n\n        \/\/ ---------- Righe fattura ----------\n        var corpoRighe = document.getElementById('gf-righe-body');\n        var contatoreRighe = 0;\n\n        function aggiungiRiga(descrizione, quantita, prezzo, aliquota) {\n            contatoreRighe++;\n            var tr = document.createElement('tr');\n            tr.innerHTML =\n                '<td><input type=\"text\" class=\"gf-riga-descrizione\" value=\"' + (descrizione || '') + '\"><\/td>' +\n                '<td><input type=\"number\" class=\"gf-riga-quantita\" min=\"0\" step=\"0.01\" value=\"' + (quantita || 1) + '\"><\/td>' +\n                '<td><input type=\"number\" class=\"gf-riga-prezzo\" min=\"0\" step=\"0.01\" value=\"' + (prezzo || 0) + '\"><\/td>' +\n                '<td><select class=\"gf-riga-aliquota\">' +\n                    '<option value=\"22.00\"' + (aliquota === '22.00' ? ' selected' : '') + '>22%<\/option>' +\n                    '<option value=\"10.00\"' + (aliquota === '10.00' ? ' selected' : '') + '>10%<\/option>' +\n                    '<option value=\"4.00\"' + (aliquota === '4.00' ? ' selected' : '') + '>4%<\/option>' +\n                    '<option value=\"0.00\"' + (aliquota === '0.00' ? ' selected' : '') + '>0%<\/option>' +\n                '<\/select><\/td>' +\n                '<td><button type=\"button\" class=\"gf-btn gf-btn-rimuovi-riga\">Rimuovi<\/button><\/td>';\n            corpoRighe.appendChild(tr);\n            tr.querySelector('.gf-btn-rimuovi-riga').addEventListener('click', function () {\n                tr.remove();\n                aggiornaTotali();\n            });\n            tr.querySelectorAll('input, select').forEach(function (campo) {\n                campo.addEventListener('input', aggiornaTotali);\n            });\n            aggiornaTotali();\n        }\n\n        document.getElementById('gf-btn-aggiungi-riga').addEventListener('click', function () {\n            aggiungiRiga('', 1, 0, '22.00');\n        });\n\n        function leggiRighe() {\n            var righe = [];\n            corpoRighe.querySelectorAll('tr').forEach(function (tr) {\n                var descrizione = tr.querySelector('.gf-riga-descrizione').value.trim();\n                var quantita = parseFloat(tr.querySelector('.gf-riga-quantita').value) || 0;\n                var prezzo = parseFloat(tr.querySelector('.gf-riga-prezzo').value) || 0;\n                var aliquota = tr.querySelector('.gf-riga-aliquota').value;\n                righe.push({ descrizione: descrizione, quantita: quantita, prezzo: prezzo, aliquota: aliquota });\n            });\n            return righe;\n        }\n\n        function formattaImporto(numero) {\n            return numero.toFixed(2);\n        }\n\n        function calcolaRiepilogo(righe) {\n            \/\/ Raggruppa per aliquota IVA, come richiesto dallo schema FatturaPA (DatiRiepilogo)\n            var perAliquota = {};\n            righe.forEach(function (r) {\n                var totaleRiga = r.quantita * r.prezzo;\n                if (!perAliquota[r.aliquota]) perAliquota[r.aliquota] = 0;\n                perAliquota[r.aliquota] += totaleRiga;\n            });\n            var riepilogo = [];\n            var imponibileTotale = 0;\n            var ivaTotale = 0;\n            Object.keys(perAliquota).forEach(function (aliquota) {\n                var imponibile = perAliquota[aliquota];\n                var imposta = imponibile * (parseFloat(aliquota) \/ 100);\n                riepilogo.push({ aliquota: aliquota, imponibile: imponibile, imposta: imposta });\n                imponibileTotale += imponibile;\n                ivaTotale += imposta;\n            });\n            return { righeRiepilogo: riepilogo, imponibileTotale: imponibileTotale, ivaTotale: ivaTotale, totaleDocumento: imponibileTotale + ivaTotale };\n        }\n\n        function aggiornaTotali() {\n            var righe = leggiRighe();\n            var r = calcolaRiepilogo(righe);\n            document.getElementById('gf-totali').textContent =\n                'Imponibile: ' + formattaImporto(r.imponibileTotale) + ' EUR - IVA: ' + formattaImporto(r.ivaTotale) +\n                ' EUR - Totale: ' + formattaImporto(r.totaleDocumento) + ' EUR';\n        }\n\n        \/\/ ---------- Costruzione XML FatturaPA ----------\n        function escapeXml(testo) {\n            return String(testo || '')\n                .replace(\/&\/g, '&amp;')\n                .replace(\/<\/g, '&lt;')\n                .replace(\/>\/g, '&gt;')\n                .replace(\/\"\/g, '&quot;');\n        }\n\n        \/\/ ---------- Tabella ufficiale codici Tipo Documento FatturaPA ----------\n        \/\/ Fonte: Specifiche tecniche Agenzia delle Entrate v1.9.1 (in vigore dal 15\/05\/2026)\n        \/\/ https:\/\/www.fatturapa.gov.it\/it\/norme-e-regole\/documentazione-fattura-elettronica\/formato-fatturapa\/\n        \/\/ NOTA: il sito dell'Agenzia delle Entrate non espone intestazioni CORS per l'accesso\n        \/\/ da script di terze parti (a differenza dei servizi pubblici usati altrove in\n        \/\/ quest'app, come l'elenco comuni o le vie OpenStreetMap): un fetch live da questo\n        \/\/ browser fallirebbe sempre per policy del browser, non per un problema di rete.\n        \/\/ La tabella e' quindi integrata qui, aggiornata a mano dalla fonte ufficiale sopra.\n        var TABELLA_TIPO_DOCUMENTO = [\n            ['TD01', 'Fattura', 'Fattura'],\n            ['TD02', 'Acconto\/anticipo su fattura', 'Fattura'],\n            ['TD03', 'Acconto\/anticipo su parcella', 'Fattura'],\n            ['TD04', 'Nota di credito', 'Nota di variazione'],\n            ['TD05', 'Nota di debito', 'Nota di variazione'],\n            ['TD06', 'Parcella', 'Fattura'],\n            ['TD07', 'Fattura semplificata', 'Fattura'],\n            ['TD08', 'Nota di credito semplificata', 'Nota di variazione'],\n            ['TD09', 'Nota di debito semplificata', 'Nota di variazione'],\n            ['TD10', 'Fattura per acquisto intracomunitario beni', 'Fattura'],\n            ['TD11', 'Fattura per acquisto intracomunitario servizi', 'Fattura'],\n            ['TD12', 'Documento riepilogativo', 'Fattura'],\n            ['TD16', 'Integrazione fattura reverse charge interno', 'Autofattura'],\n            ['TD17', 'Integrazione\/autofattura acquisto servizi estero', 'Autofattura'],\n            ['TD18', 'Integrazione acquisto beni intracomunitari', 'Autofattura'],\n            ['TD19', 'Integrazione\/autofattura beni ex art.17 c.2', 'Autofattura'],\n            ['TD20', 'Autofattura per regolarizzazione', 'Autofattura'],\n            ['TD21', 'Autofattura per splafonamento', 'Autofattura'],\n            ['TD22', 'Estrazione beni da Deposito IVA', 'Autofattura'],\n            ['TD23', 'Estrazione beni da Deposito IVA con versamento', 'Autofattura'],\n            ['TD24', 'Fattura differita (art.21 c.4 lett. a)', 'Fattura'],\n            ['TD25', 'Fattura differita (art.21 c.4 lett. b)', 'Fattura'],\n            ['TD26', 'Cessione beni ammortizzabili\/passaggi interni', 'Fattura'],\n            ['TD27', 'Fattura per autoconsumo\/cessioni gratuite', 'Autofattura'],\n            ['TD28', 'Acquisti da San Marino con IVA', 'Fattura'],\n            ['TD29', 'Comunicazione omessa\/irregolare fatturazione', 'Autofattura']\n        ];\n\n        \/\/ Popola una <select> con tutti i codici Tipo Documento ufficiali, con tooltip\n        \/\/ (title) contenente la descrizione. valoreSelezionato viene preselezionato se presente.\n        function popolaSelectTipoDocumento(select, valoreSelezionato) {\n            select.innerHTML = '';\n            TABELLA_TIPO_DOCUMENTO.forEach(function (voce) {\n                var codice = voce[0], descrizione = voce[1], categoria = voce[2];\n                var opzione = document.createElement('option');\n                opzione.value = codice;\n                opzione.textContent = codice + ' - ' + descrizione;\n                opzione.title = categoria;\n                if (codice === valoreSelezionato) opzione.selected = true;\n                select.appendChild(opzione);\n            });\n        }\n\n        function costruisciXmlFatturaPA(input) {\n            var righeXml = '';\n            input.righe.forEach(function (r, indice) {\n                var totaleRiga = r.quantita * r.prezzo;\n                righeXml +=\n                    '  <DettaglioLinee>\\n' +\n                    '   <NumeroLinea>' + (indice + 1) + '<\/NumeroLinea>\\n' +\n                    '   <Descrizione>' + escapeXml(r.descrizione) + '<\/Descrizione>\\n' +\n                    '   <Quantita>' + formattaImporto(r.quantita) + '<\/Quantita>\\n' +\n                    '   <PrezzoUnitario>' + formattaImporto(r.prezzo) + '<\/PrezzoUnitario>\\n' +\n                    '   <PrezzoTotale>' + formattaImporto(totaleRiga) + '<\/PrezzoTotale>\\n' +\n                    '   <AliquotaIVA>' + r.aliquota + '<\/AliquotaIVA>\\n' +\n                    '  <\/DettaglioLinee>\\n';\n            });\n\n            var riepilogoXml = '';\n            input.riepilogo.righeRiepilogo.forEach(function (rr) {\n                riepilogoXml +=\n                    '  <DatiRiepilogo>\\n' +\n                    '   <AliquotaIVA>' + rr.aliquota + '<\/AliquotaIVA>\\n' +\n                    '   <ImponibileImporto>' + formattaImporto(rr.imponibile) + '<\/ImponibileImporto>\\n' +\n                    '   <Imposta>' + formattaImporto(rr.imposta) + '<\/Imposta>\\n' +\n                    '   <EsigibilitaIVA>I<\/EsigibilitaIVA>\\n' +\n                    '  <\/DatiRiepilogo>\\n';\n            });\n\n            return '<' + '?xml version=\"1.0\" encoding=\"UTF-8\"?>\\n' +\n                '<p:FatturaElettronica xmlns:p=\"http:\/\/ivaservizi.agenziaentrate.gov.it\/docs\/xsd\/fatture\/v1.2\" versione=\"FPR12\" xmlns:xsi=\"http:\/\/www.w3.org\/2001\/XMLSchema-instance\">\\n' +\n                ' <FatturaElettronicaHeader>\\n' +\n                '  <DatiTrasmissione>\\n' +\n                '   <IdTrasmittente>\\n' +\n                '    <IdPaese>IT<\/IdPaese>\\n' +\n                '    <IdCodice>' + escapeXml(input.cedente.piva) + '<\/IdCodice>\\n' +\n                '   <\/IdTrasmittente>\\n' +\n                '   <ProgressivoInvio>' + escapeXml(input.doc.numero) + '<\/ProgressivoInvio>\\n' +\n                '   <FormatoTrasmissione>FPR12<\/FormatoTrasmissione>\\n' +\n                '   <CodiceDestinatario>0000000<\/CodiceDestinatario>\\n' +\n                '  <\/DatiTrasmissione>\\n' +\n                '  <CedentePrestatore>\\n' +\n                '   <DatiAnagrafici>\\n' +\n                '    <IdFiscaleIVA>\\n' +\n                '     <IdPaese>IT<\/IdPaese>\\n' +\n                '     <IdCodice>' + escapeXml(input.cedente.piva) + '<\/IdCodice>\\n' +\n                '    <\/IdFiscaleIVA>\\n' +\n                '    <Anagrafica>\\n' +\n                '     <Denominazione>' + escapeXml(input.cedente.denom) + '<\/Denominazione>\\n' +\n                '    <\/Anagrafica>\\n' +\n                '    <RegimeFiscale>' + escapeXml(input.cedente.regime) + '<\/RegimeFiscale>\\n' +\n                '   <\/DatiAnagrafici>\\n' +\n                '   <Sede>\\n' +\n                '    <Indirizzo>' + escapeXml(input.cedente.indirizzo) + '<\/Indirizzo>\\n' +\n                '    <CAP>' + escapeXml(input.cedente.cap) + '<\/CAP>\\n' +\n                '    <Comune>' + escapeXml(input.cedente.comune) + '<\/Comune>\\n' +\n                '    <Provincia>' + escapeXml(input.cedente.provincia) + '<\/Provincia>\\n' +\n                '    <Nazione>IT<\/Nazione>\\n' +\n                '   <\/Sede>\\n' +\n                '  <\/CedentePrestatore>\\n' +\n                '  <CessionarioCommittente>\\n' +\n                '   <DatiAnagrafici>\\n' +\n                '    <IdFiscaleIVA>\\n' +\n                '     <IdPaese>IT<\/IdPaese>\\n' +\n                '     <IdCodice>' + escapeXml(input.cessionario.piva) + '<\/IdCodice>\\n' +\n                '    <\/IdFiscaleIVA>\\n' +\n                '    <Anagrafica>\\n' +\n                '     <Denominazione>' + escapeXml(input.cessionario.denom) + '<\/Denominazione>\\n' +\n                '    <\/Anagrafica>\\n' +\n                '   <\/DatiAnagrafici>\\n' +\n                '   <Sede>\\n' +\n                '    <Indirizzo>' + escapeXml(input.cessionario.indirizzo) + '<\/Indirizzo>\\n' +\n                '    <CAP>' + escapeXml(input.cessionario.cap) + '<\/CAP>\\n' +\n                '    <Comune>' + escapeXml(input.cessionario.comune) + '<\/Comune>\\n' +\n                '    <Provincia>' + escapeXml(input.cessionario.provincia) + '<\/Provincia>\\n' +\n                '    <Nazione>IT<\/Nazione>\\n' +\n                '   <\/Sede>\\n' +\n                '  <\/CessionarioCommittente>\\n' +\n                ' <\/FatturaElettronicaHeader>\\n' +\n                ' <FatturaElettronicaBody>\\n' +\n                '  <DatiGenerali>\\n' +\n                '   <DatiGeneraliDocumento>\\n' +\n                '    <TipoDocumento>' + escapeXml(input.doc.tipoDocumento || 'TD01') + '<\/TipoDocumento>\\n' +\n                '    <Divisa>EUR<\/Divisa>\\n' +\n                '    <Data>' + escapeXml(input.doc.data) + '<\/Data>\\n' +\n                '    <Numero>' + escapeXml(input.doc.numero) + '<\/Numero>\\n' +\n                '    <ImportoTotaleDocumento>' + formattaImporto(input.riepilogo.totaleDocumento) + '<\/ImportoTotaleDocumento>\\n' +\n                '   <\/DatiGeneraliDocumento>\\n' +\n                '  <\/DatiGenerali>\\n' +\n                '  <DatiBeniServizi>\\n' +\n                righeXml +\n                riepilogoXml +\n                '  <\/DatiBeniServizi>\\n' +\n                '  <DatiPagamento>\\n' +\n                '   <CondizioniPagamento>TP02<\/CondizioniPagamento>\\n' +\n                '   <DettaglioPagamento>\\n' +\n                '    <ModalitaPagamento>' + escapeXml(input.doc.pagamento) + '<\/ModalitaPagamento>\\n' +\n                '    <ImportoPagamento>' + formattaImporto(input.riepilogo.totaleDocumento) + '<\/ImportoPagamento>\\n' +\n                '   <\/DettaglioPagamento>\\n' +\n                '  <\/DatiPagamento>\\n' +\n                ' <\/FatturaElettronicaBody>\\n' +\n                '<\/p:FatturaElettronica>\\n';\n        }\n\n        function mostraMessaggio(testo, tipo) {\n            var el = document.getElementById('gf-messaggio');\n            el.textContent = testo;\n            el.className = 'gf-messaggio ' + (tipo || '');\n        }\n\n        function scaricaTesto(nomeFile, contenuto) {\n            var blob = new Blob([contenuto], { type: 'application\/xml' });\n            var url = URL.createObjectURL(blob);\n            var a = document.createElement('a');\n            a.href = url;\n            a.download = nomeFile;\n            document.body.appendChild(a);\n            a.click();\n            document.body.removeChild(a);\n            URL.revokeObjectURL(url);\n        }\n\n        document.getElementById('gf-btn-genera-xml').addEventListener('click', function () {\n            var cedente = {\n                denom: document.getElementById('gf-ced-denom').value.trim(),\n                piva: document.getElementById('gf-ced-piva').value.trim(),\n                indirizzo: document.getElementById('gf-ced-indirizzo').value.trim(),\n                cap: document.getElementById('gf-ced-cap').value.trim(),\n                comune: document.getElementById('gf-ced-comune').value.trim(),\n                provincia: document.getElementById('gf-ced-provincia').value.trim(),\n                regime: document.getElementById('gf-ced-regime').value\n            };\n            var cessionario = {\n                denom: document.getElementById('gf-ces-denom').value.trim(),\n                piva: document.getElementById('gf-ces-piva').value.trim(),\n                indirizzo: document.getElementById('gf-ces-indirizzo').value.trim(),\n                cap: document.getElementById('gf-ces-cap').value.trim(),\n                comune: document.getElementById('gf-ces-comune').value.trim(),\n                provincia: document.getElementById('gf-ces-provincia').value.trim()\n            };\n            var doc = {\n                numero: document.getElementById('gf-doc-numero').value.trim(),\n                data: document.getElementById('gf-doc-data').value,\n                tipoDocumento: document.getElementById('gf-doc-tipo').value,\n                pagamento: document.getElementById('gf-doc-pagamento').value\n            };\n            var righe = leggiRighe();\n\n            \/\/ Controlli minimi prima di generare\n            if (!cedente.denom || !cedente.piva) {\n                mostraMessaggio('Inserisci almeno Denominazione e Partita IVA del Cedente\/Prestatore.', 'errore');\n                return;\n            }\n            if (!cessionario.denom || !cessionario.piva) {\n                mostraMessaggio('Inserisci o genera i dati del Cessionario\/Committente.', 'errore');\n                return;\n            }\n            if (righe.length === 0) {\n                mostraMessaggio('Aggiungi almeno una riga fattura.', 'errore');\n                return;\n            }\n            if (!doc.data) {\n                mostraMessaggio('Seleziona la data del documento.', 'errore');\n                return;\n            }\n\n            var riepilogo = calcolaRiepilogo(righe);\n            var xml = costruisciXmlFatturaPA({ cedente: cedente, cessionario: cessionario, doc: doc, righe: righe, riepilogo: riepilogo });\n\n            var nomeFile = 'IT' + cedente.piva + '_' + doc.numero + '.xml';\n            scaricaTesto(nomeFile, xml);\n            mostraMessaggio('XML generato e scaricato: ' + nomeFile, 'successo');\n        });\n\n        \/\/ ================= MODULO: IMPORTA XML =================\n        \/\/ Riusa le funzioni condivise definite sopra: escapeXml, formattaImporto,\n        \/\/ calcolaRiepilogo, costruisciXmlFatturaPA, generaPartitaIvaFittizia,\n        \/\/ DENOMINAZIONI_FITTIZIE, CITTA_FITTIZIE, scegliCasuale, scaricaTesto, mostraMessaggio\n\n        \/\/ Cerca elementi per NOME LOCALE del tag, ignorando il prefisso di namespace\n        \/\/ (i file FatturaPA reali possono usare prefissi diversi da quello del nostro generatore)\n        function trovaElementiPerNome(radice, nomeLocale) {\n            var tutti = radice.getElementsByTagName('*');\n            var risultato = [];\n            for (var i = 0; i < tutti.length; i++) {\n                if (tutti[i].localName === nomeLocale) risultato.push(tutti[i]);\n            }\n            return risultato;\n        }\n        function trovaElementoUnico(radice, nomeLocale) {\n            if (!radice) return null;\n            var elenco = trovaElementiPerNome(radice, nomeLocale);\n            return elenco.length ? elenco[0] : null;\n        }\n        function trovaTesto(radice, nomeLocale) {\n            var el = trovaElementoUnico(radice, nomeLocale);\n            return el ? el.textContent.trim() : '';\n        }\n\n        function mostraMessaggioX(testo, tipo) {\n            var el = document.getElementById('gfx-messaggio');\n            el.textContent = testo;\n            el.className = 'gf-messaggio ' + (tipo || '');\n        }\n\n        \/\/ ---------- Righe fattura (tabella indipendente da quella della scheda Genera) ----------\n        var corpoRigheX = document.getElementById('gfx-righe-body');\n\n        function aggiornaTotaliX() {\n            var righe = leggiRigheX();\n            var r = calcolaRiepilogo(righe);\n            document.getElementById('gfx-totali').textContent =\n                'Imponibile: ' + formattaImporto(r.imponibileTotale) + ' EUR - IVA: ' + formattaImporto(r.ivaTotale) +\n                ' EUR - Totale: ' + formattaImporto(r.totaleDocumento) + ' EUR';\n        }\n\n        function aggiungiRigaX(descrizione, quantita, prezzo, aliquota) {\n            var tr = document.createElement('tr');\n            tr.innerHTML =\n                '<td><input type=\"text\" class=\"gf-riga-descrizione\" value=\"' + (descrizione || '') + '\"><\/td>' +\n                '<td><input type=\"number\" class=\"gf-riga-quantita\" min=\"0\" step=\"0.01\" value=\"' + (quantita || 1) + '\"><\/td>' +\n                '<td><input type=\"number\" class=\"gf-riga-prezzo\" min=\"0\" step=\"0.01\" value=\"' + (prezzo || 0) + '\"><\/td>' +\n                '<td><select class=\"gf-riga-aliquota\">' +\n                    '<option value=\"22.00\"' + (aliquota === '22.00' ? ' selected' : '') + '>22%<\/option>' +\n                    '<option value=\"10.00\"' + (aliquota === '10.00' ? ' selected' : '') + '>10%<\/option>' +\n                    '<option value=\"4.00\"' + (aliquota === '4.00' ? ' selected' : '') + '>4%<\/option>' +\n                    '<option value=\"0.00\"' + (aliquota === '0.00' ? ' selected' : '') + '>0%<\/option>' +\n                '<\/select><\/td>' +\n                '<td><button type=\"button\" class=\"gf-btn gf-btn-rimuovi-riga\">Rimuovi<\/button><\/td>';\n            corpoRigheX.appendChild(tr);\n            tr.querySelector('.gf-btn-rimuovi-riga').addEventListener('click', function () {\n                tr.remove();\n                aggiornaTotaliX();\n            });\n            tr.querySelectorAll('input, select').forEach(function (campo) {\n                campo.addEventListener('input', aggiornaTotaliX);\n            });\n            aggiornaTotaliX();\n        }\n\n        function leggiRigheX() {\n            var righe = [];\n            corpoRigheX.querySelectorAll('tr').forEach(function (tr) {\n                righe.push({\n                    descrizione: tr.querySelector('.gf-riga-descrizione').value.trim(),\n                    quantita: parseFloat(tr.querySelector('.gf-riga-quantita').value) || 0,\n                    prezzo: parseFloat(tr.querySelector('.gf-riga-prezzo').value) || 0,\n                    aliquota: tr.querySelector('.gf-riga-aliquota').value\n                });\n            });\n            return righe;\n        }\n\n        document.getElementById('gfx-btn-aggiungi-riga').addEventListener('click', function () {\n            aggiungiRigaX('', 1, 0, '22.00');\n        });\n\n        \/\/ ---------- Lettura e parsing del file XML reale ----------\n        function svuotaTabellaX() {\n            corpoRigheX.innerHTML = '';\n        }\n\n        function popolaCampiSoggetto(prefisso, elementoXml) {\n            document.getElementById(prefisso + '-denom').value = trovaTesto(elementoXml, 'Denominazione');\n            document.getElementById(prefisso + '-piva').value = trovaTesto(trovaElementoUnico(elementoXml, 'IdFiscaleIVA'), 'IdCodice');\n            document.getElementById(prefisso + '-indirizzo').value = trovaTesto(elementoXml, 'Indirizzo');\n            document.getElementById(prefisso + '-cap').value = trovaTesto(elementoXml, 'CAP');\n            document.getElementById(prefisso + '-comune').value = trovaTesto(elementoXml, 'Comune');\n            document.getElementById(prefisso + '-provincia').value = trovaTesto(elementoXml, 'Provincia');\n        }\n\n        document.getElementById('gfx-file').addEventListener('change', function (ev) {\n            var file = ev.target.files[0];\n            if (!file) return;\n            var lettore = new FileReader();\n            lettore.onload = function (e) {\n                try {\n                    var testoXml = e.target.result;\n                    var parser = new DOMParser();\n                    var xmlDoc = parser.parseFromString(testoXml, 'application\/xml');\n\n                    var erroreParsing = xmlDoc.getElementsByTagName('parsererror');\n                    if (erroreParsing.length > 0) {\n                        mostraMessaggioX('Il file non sembra un XML valido: impossibile analizzarlo.', 'errore');\n                        return;\n                    }\n\n                    var cedenteEl = trovaElementoUnico(xmlDoc, 'CedentePrestatore');\n                    var cessionarioEl = trovaElementoUnico(xmlDoc, 'CessionarioCommittente');\n                    if (!cedenteEl || !cessionarioEl) {\n                        mostraMessaggioX('Non trovo CedentePrestatore\/CessionarioCommittente: verifica che sia un file FatturaPA.', 'errore');\n                        return;\n                    }\n\n                    popolaCampiSoggetto('gfx-ced', cedenteEl);\n                    document.getElementById('gfx-ced-regime').value = trovaTesto(cedenteEl, 'RegimeFiscale') || 'RF01';\n                    popolaCampiSoggetto('gfx-ces', cessionarioEl);\n\n                    var datiGenDocEl = trovaElementoUnico(xmlDoc, 'DatiGeneraliDocumento');\n                    document.getElementById('gfx-doc-numero').value = trovaTesto(datiGenDocEl, 'Numero');\n                    var dataGrezza = trovaTesto(datiGenDocEl, 'Data');\n                    document.getElementById('gfx-doc-data').value = dataGrezza || '';\n                    var tipoDocumentoLetto = trovaTesto(datiGenDocEl, 'TipoDocumento');\n                    if (tipoDocumentoLetto && TABELLA_TIPO_DOCUMENTO.some(function (v) { return v[0] === tipoDocumentoLetto; })) {\n                        document.getElementById('gfx-doc-tipo').value = tipoDocumentoLetto;\n                    }\n\n                    var pagamentoEl = trovaElementoUnico(xmlDoc, 'DettaglioPagamento');\n                    var modalitaPagamento = trovaTesto(pagamentoEl, 'ModalitaPagamento');\n                    if (modalitaPagamento) document.getElementById('gfx-doc-pagamento').value = modalitaPagamento;\n\n                    svuotaTabellaX();\n                    var righeXml = trovaElementiPerNome(xmlDoc, 'DettaglioLinee');\n                    if (righeXml.length === 0) {\n                        aggiungiRigaX('', 1, 0, '22.00');\n                    } else {\n                        righeXml.forEach(function (rigaEl) {\n                            var descrizione = trovaTesto(rigaEl, 'Descrizione');\n                            var quantita = parseFloat(trovaTesto(rigaEl, 'Quantita')) || 1;\n                            var prezzo = parseFloat(trovaTesto(rigaEl, 'PrezzoUnitario')) || 0;\n                            var aliquotaGrezza = trovaTesto(rigaEl, 'AliquotaIVA');\n                            var aliquota = aliquotaGrezza ? parseFloat(aliquotaGrezza).toFixed(2) : '22.00';\n                            aggiungiRigaX(descrizione, quantita, prezzo, aliquota);\n                        });\n                    }\n\n                    mostraMessaggioX('File letto correttamente. Ora scegli quale soggetto anonimizzare e genera l\\'XML.', 'successo');\n                } catch (erroreGenerico) {\n                    mostraMessaggioX('Errore durante la lettura del file: ' + erroreGenerico.message, 'errore');\n                }\n            };\n            lettore.readAsText(file, 'UTF-8');\n        });\n\n        \/\/ ---------- Anonimizzazione del soggetto selezionato ----------\n        document.getElementById('gfx-btn-genera-controparte').addEventListener('click', async function () {\n            var ruolo = document.querySelector('input[name=\"gfx-ruolo\"]:checked').value;\n            var prefisso = (ruolo === 'cedente') ? 'gfx-ced' : 'gfx-ces';\n            var citta = scegliCasuale(CITTA_FITTIZIE);\n            document.getElementById(prefisso + '-denom').value = scegliCasuale(DENOMINAZIONI_FITTIZIE);\n            document.getElementById(prefisso + '-piva').value = generaPartitaIvaFittizia();\n            document.getElementById(prefisso + '-cap').value = citta.cap;\n            document.getElementById(prefisso + '-comune').value = citta.comune;\n            document.getElementById(prefisso + '-provincia').value = citta.provincia;\n            document.getElementById(prefisso + '-indirizzo').value = 'Recupero indirizzo...';\n            document.getElementById(prefisso + '-indirizzo').value = await generaIndirizzoCasuale(citta.comune);\n        });\n\n        \/\/ ---------- Generazione e download dell'XML anonimizzato ----------\n        document.getElementById('gfx-btn-genera-xml').addEventListener('click', function () {\n            var cedente = {\n                denom: document.getElementById('gfx-ced-denom').value.trim(),\n                piva: document.getElementById('gfx-ced-piva').value.trim(),\n                indirizzo: document.getElementById('gfx-ced-indirizzo').value.trim(),\n                cap: document.getElementById('gfx-ced-cap').value.trim(),\n                comune: document.getElementById('gfx-ced-comune').value.trim(),\n                provincia: document.getElementById('gfx-ced-provincia').value.trim(),\n                regime: document.getElementById('gfx-ced-regime').value\n            };\n            var cessionario = {\n                denom: document.getElementById('gfx-ces-denom').value.trim(),\n                piva: document.getElementById('gfx-ces-piva').value.trim(),\n                indirizzo: document.getElementById('gfx-ces-indirizzo').value.trim(),\n                cap: document.getElementById('gfx-ces-cap').value.trim(),\n                comune: document.getElementById('gfx-ces-comune').value.trim(),\n                provincia: document.getElementById('gfx-ces-provincia').value.trim()\n            };\n            var doc = {\n                numero: document.getElementById('gfx-doc-numero').value.trim(),\n                data: document.getElementById('gfx-doc-data').value,\n                tipoDocumento: document.getElementById('gfx-doc-tipo').value,\n                pagamento: document.getElementById('gfx-doc-pagamento').value\n            };\n            var righe = leggiRigheX();\n\n            if (!cedente.denom || !cedente.piva || !cessionario.denom || !cessionario.piva) {\n                mostraMessaggioX('Mancano dati di Cedente o Cessionario: carica un file oppure completa i campi.', 'errore');\n                return;\n            }\n            if (righe.length === 0) {\n                mostraMessaggioX('Nessuna riga fattura presente.', 'errore');\n                return;\n            }\n            if (!doc.data) {\n                mostraMessaggioX('Manca la data del documento.', 'errore');\n                return;\n            }\n\n            var riepilogo = calcolaRiepilogo(righe);\n            var xml = costruisciXmlFatturaPA({ cedente: cedente, cessionario: cessionario, doc: doc, righe: righe, riepilogo: riepilogo });\n            var nomeFile = 'ANONIMIZZATA_' + (doc.numero || 'documento') + '.xml';\n            scaricaTesto(nomeFile, xml);\n            mostraMessaggioX('XML anonimizzato generato e scaricato: ' + nomeFile, 'successo');\n        });\n\n        \/\/ ================= MODULO: IMPORTA PDF =================\n        \/\/ Riusa le funzioni condivise: calcolaRiepilogo, costruisciXmlFatturaPA, formattaImporto,\n        \/\/ generaPartitaIvaFittizia, DENOMINAZIONI_FITTIZIE, CITTA_FITTIZIE, scegliCasuale, scaricaTesto\n\n        function mostraMessaggioP(testo, tipo) {\n            var el = document.getElementById('gfp-messaggio');\n            el.textContent = testo;\n            el.className = 'gf-messaggio ' + (tipo || '');\n        }\n\n        \/\/ Estrae tutti gli elementi di testo di tutte le pagine con le loro coordinate,\n        \/\/ poi li raggruppa in \"righe\" per coordinata Y (con una tolleranza), ordinando\n        \/\/ per X all'interno di ogni riga. Questo ricostruisce un testo con un layout\n        \/\/ simile a quello che produceva \"pdftotext -layout\" nel progetto VB6 originale,\n        \/\/ ma qui partendo dalle coordinate REALI restituite da pdf.js invece che da una\n        \/\/ griglia a larghezza fissa.\n        async function estraiElementiConCoordinate(arrayBuffer) {\n            var pdf = await pdfjsLib.getDocument({ data: arrayBuffer }).promise;\n            var elementi = [];\n            for (var numPagina = 1; numPagina <= pdf.numPages; numPagina++) {\n                var pagina = await pdf.getPage(numPagina);\n                var contenuto = await pagina.getTextContent();\n                contenuto.items.forEach(function (voce) {\n                    if (!voce.str || !voce.str.trim()) return;\n                    elementi.push({\n                        testo: voce.str,\n                        x: voce.transform[4],\n                        y: voce.transform[5],\n                        pagina: numPagina\n                    });\n                });\n            }\n            return elementi;\n        }\n\n        function raggruppaInRighe(elementi) {\n            var TOLLERANZA_Y = 3;\n            var ordinati = elementi.slice().sort(function (a, b) {\n                if (a.pagina !== b.pagina) return a.pagina - b.pagina;\n                if (Math.abs(a.y - b.y) > TOLLERANZA_Y) return b.y - a.y; \/\/ dall'alto verso il basso\n                return a.x - b.x;\n            });\n            var righe = [];\n            var rigaCorrente = null;\n            ordinati.forEach(function (el) {\n                if (!rigaCorrente || Math.abs(rigaCorrente.y - el.y) > TOLLERANZA_Y || rigaCorrente.pagina !== el.pagina) {\n                    rigaCorrente = { y: el.y, pagina: el.pagina, elementi: [] };\n                    righe.push(rigaCorrente);\n                }\n                rigaCorrente.elementi.push(el);\n            });\n            return righe;\n        }\n\n        function testoDellaRiga(riga) {\n            return riga.elementi.map(function (e) { return e.testo; }).join(' ').replace(\/\\s+\/g, ' ').trim();\n        }\n\n        function testoCompletoLineare(righe) {\n            return righe.map(testoDellaRiga).join('\\n');\n        }\n\n        \/\/ Cerca un'etichetta e restituisce il valore associato: se subito dopo l'etichetta\n        \/\/ c'e' un a-capo immediato (etichetta e valore su righe separate, come scoperto nel\n        \/\/ progetto Dart con Syncfusion), salta quell'a-capo e prende la riga successiva.\n        function estraiValoreDopoEtichetta(testoCompleto, etichetta) {\n            var indice = testoCompleto.toLowerCase().indexOf(etichetta.toLowerCase());\n            if (indice === -1) return '';\n            var resto = testoCompleto.substring(indice + etichetta.length);\n            resto = resto.replace(\/^[ \\t]*\\n\/, ''); \/\/ salta un eventuale a-capo immediato\n            var finePrimaRiga = resto.indexOf('\\n');\n            var primaRiga = finePrimaRiga === -1 ? resto : resto.substring(0, finePrimaRiga);\n            return primaRiga.trim();\n        }\n\n        function estraiNumero(testoValore) {\n            if (!testoValore) return 0;\n            var pulito = testoValore.replace(\/[^\\d,.-]\/g, '').replace(\/\\.\/g, '').replace(',', '.');\n            var numero = parseFloat(pulito);\n            return isNaN(numero) ? 0 : numero;\n        }\n\n        function estraiData(testoValore) {\n            var match = testoValore.match(\/(\\d{2})[\\\/\\-.](\\d{2})[\\\/\\-.](\\d{4})\/);\n            if (!match) return '';\n            return match[3] + '-' + match[2] + '-' + match[1]; \/\/ formato input[type=date]: AAAA-MM-GG\n        }\n\n        \/\/ Tentativo di estrazione colonne FORNITORE\/CLIENTE per POSIZIONE X reale.\n        \/\/ Provvisorio: verificare sempre con la diagnostica su un PDF vero prima di fidarsi\n        \/\/ ciecamente del risultato, la struttura esatta puo' variare.\n        function estraiAnagraficheDaColonne(righe) {\n            var risultato = { fornitore: { denom: '', piva: '' }, cliente: { denom: '', piva: '' } };\n            var rigaIntestazione = null;\n            var xFornitore = null, xCliente = null;\n            righe.forEach(function (riga) {\n                riga.elementi.forEach(function (el) {\n                    var testoMaiuscolo = el.testo.toUpperCase();\n                    if (testoMaiuscolo.indexOf('FORNITORE') !== -1 && xFornitore === null) {\n                        xFornitore = el.x;\n                        rigaIntestazione = riga;\n                    }\n                    if (testoMaiuscolo.indexOf('CLIENTE') !== -1 && xCliente === null) {\n                        xCliente = el.x;\n                    }\n                });\n            });\n            if (xFornitore === null) return risultato; \/\/ struttura non trovata, si lascia vuoto\n\n            var indiceRigaIntestazione = righe.indexOf(rigaIntestazione);\n            var sogliaX = (xCliente !== null) ? (xFornitore + xCliente) \/ 2 : (xFornitore + 300);\n            var testoFornitore = [];\n            var testoCliente = [];\n\n            \/\/ Analizza un ragionevole numero di righe successive all'intestazione (blocco anagrafica)\n            for (var i = indiceRigaIntestazione + 1; i < Math.min(righe.length, indiceRigaIntestazione + 15); i++) {\n                righe[i].elementi.forEach(function (el) {\n                    if (xCliente !== null && el.x >= sogliaX) {\n                        testoCliente.push(el.testo);\n                    } else {\n                        testoFornitore.push(el.testo);\n                    }\n                });\n            }\n\n            var bloccoFornitore = testoFornitore.join(' ');\n            var bloccoCliente = testoCliente.join(' ');\n\n            risultato.fornitore.denom = testoFornitore.length ? testoFornitore[0] : '';\n            risultato.cliente.denom = testoCliente.length ? testoCliente[0] : '';\n\n            var matchPivaFornitore = bloccoFornitore.match(\/P\\.?\\s?IVA[:\\s]*([0-9]{11})\/i);\n            var matchPivaCliente = bloccoCliente.match(\/P\\.?\\s?IVA[:\\s]*([0-9]{11})\/i);\n            risultato.fornitore.piva = matchPivaFornitore ? matchPivaFornitore[1] : '';\n            risultato.cliente.piva = matchPivaCliente ? matchPivaCliente[1] : '';\n\n            return risultato;\n        }\n\n        document.getElementById('gfp-file').addEventListener('change', function (ev) {\n            var file = ev.target.files[0];\n            if (!file) return;\n            if (!window.pdfjsLib) {\n                mostraMessaggioP('La libreria pdf.js non si e\\' caricata (verifica la connessione o eventuali blocchi del sito a script esterni).', 'errore');\n                return;\n            }\n            var lettore = new FileReader();\n            lettore.onload = async function (e) {\n                try {\n                    mostraMessaggioP('Lettura del PDF in corso...', '');\n                    var arrayBuffer = e.target.result;\n                    var elementi = await estraiElementiConCoordinate(arrayBuffer);\n                    var righe = raggruppaInRighe(elementi);\n                    var testoCompleto = testoCompletoLineare(righe);\n\n                    \/\/ Diagnostica: 350 caratteri dopo \"FORNITORE\", con gli a-capo resi visibili\n                    var indiceFornitore = testoCompleto.toUpperCase().indexOf('FORNITORE');\n                    var diagnostica;\n                    if (indiceFornitore === -1) {\n                        diagnostica = 'Etichetta \"FORNITORE\" non trovata nel testo estratto. Primi 500 caratteri del documento:\\n' +\n                            testoCompleto.substring(0, 500).replace(\/\\n\/g, '[NL]\\n');\n                    } else {\n                        diagnostica = testoCompleto.substring(indiceFornitore, indiceFornitore + 350).replace(\/\\n\/g, '[NL]\\n');\n                    }\n                    document.getElementById('gfp-diagnostica').value = diagnostica;\n\n                    \/\/ Totali e date (stessa logica \"salta a-capo immediato\" gia' corretta nel progetto Dart)\n                    var totaleDocumento = estraiNumero(estraiValoreDopoEtichetta(testoCompleto, 'Totale documento'));\n                    var totaleIva = estraiNumero(estraiValoreDopoEtichetta(testoCompleto, 'Totale IVA'));\n                    var nettoAPagare = estraiNumero(estraiValoreDopoEtichetta(testoCompleto, 'Netto a pagare'));\n                    var dataGrezza = estraiValoreDopoEtichetta(testoCompleto, 'Data');\n                    var numeroGrezzo = estraiValoreDopoEtichetta(testoCompleto, 'Numero');\n\n                    var totaleFinale = totaleDocumento || nettoAPagare;\n                    var imponibile = totaleFinale - totaleIva;\n                    var aliquotaCalcolata = imponibile > 0 ? (totaleIva \/ imponibile * 100) : 22;\n\n                    document.getElementById('gfp-imponibile').value = imponibile.toFixed(2);\n                    document.getElementById('gfp-iva').value = totaleIva.toFixed(2);\n                    document.getElementById('gfp-aliquota').value = aliquotaCalcolata.toFixed(2);\n                    document.getElementById('gfp-totale').value = totaleFinale.toFixed(2);\n                    if (dataGrezza) document.getElementById('gfp-doc-data').value = estraiData(dataGrezza);\n                    if (numeroGrezzo) document.getElementById('gfp-doc-numero').value = numeroGrezzo;\n\n                    \/\/ Tentativo anagrafica Fornitore\/Cliente per posizione X reale (provvisorio)\n                    var anagrafiche = estraiAnagraficheDaColonne(righe);\n                    document.getElementById('gfp-ced-denom').value = anagrafiche.fornitore.denom;\n                    document.getElementById('gfp-ced-piva').value = anagrafiche.fornitore.piva;\n                    document.getElementById('gfp-ces-denom').value = anagrafiche.cliente.denom;\n                    document.getElementById('gfp-ces-piva').value = anagrafiche.cliente.piva;\n\n                    if (!anagrafiche.fornitore.denom && !anagrafiche.cliente.denom) {\n                        mostraMessaggioP('Totali\/data letti. Anagrafica Fornitore\/Cliente NON riconosciuta automaticamente: apri la diagnostica qui sopra, incollala in chat cosi\\' correggiamo l\\'estrazione, oppure completa i campi a mano.', 'errore');\n                    } else {\n                        mostraMessaggioP('PDF letto. Controlla i campi (l\\'anagrafica Fornitore\/Cliente e\\' un\\'estrazione automatica provvisoria: verificala prima di generare l\\'XML).', 'successo');\n                    }\n                } catch (erroreGenerico) {\n                    mostraMessaggioP('Errore durante la lettura del PDF: ' + erroreGenerico.message, 'errore');\n                }\n            };\n            lettore.readAsArrayBuffer(file);\n        });\n\n        document.getElementById('gfp-btn-genera-controparte').addEventListener('click', async function () {\n            var ruolo = document.querySelector('input[name=\"gfp-ruolo\"]:checked').value;\n            var prefisso = (ruolo === 'cedente') ? 'gfp-ced' : 'gfp-ces';\n            var citta = scegliCasuale(CITTA_FITTIZIE);\n            document.getElementById(prefisso + '-denom').value = scegliCasuale(DENOMINAZIONI_FITTIZIE);\n            document.getElementById(prefisso + '-piva').value = generaPartitaIvaFittizia();\n            document.getElementById(prefisso + '-cap').value = citta.cap;\n            document.getElementById(prefisso + '-comune').value = citta.comune;\n            document.getElementById(prefisso + '-provincia').value = citta.provincia;\n            document.getElementById(prefisso + '-indirizzo').value = 'Recupero indirizzo...';\n            document.getElementById(prefisso + '-indirizzo').value = await generaIndirizzoCasuale(citta.comune);\n        });\n\n        document.getElementById('gfp-btn-genera-xml').addEventListener('click', function () {\n            var cedente = {\n                denom: document.getElementById('gfp-ced-denom').value.trim(),\n                piva: document.getElementById('gfp-ced-piva').value.trim(),\n                indirizzo: document.getElementById('gfp-ced-indirizzo').value.trim(),\n                cap: document.getElementById('gfp-ced-cap').value.trim(),\n                comune: document.getElementById('gfp-ced-comune').value.trim(),\n                provincia: document.getElementById('gfp-ced-provincia').value.trim(),\n                regime: document.getElementById('gfp-ced-regime').value\n            };\n            var cessionario = {\n                denom: document.getElementById('gfp-ces-denom').value.trim(),\n                piva: document.getElementById('gfp-ces-piva').value.trim(),\n                indirizzo: document.getElementById('gfp-ces-indirizzo').value.trim(),\n                cap: document.getElementById('gfp-ces-cap').value.trim(),\n                comune: document.getElementById('gfp-ces-comune').value.trim(),\n                provincia: document.getElementById('gfp-ces-provincia').value.trim()\n            };\n            var doc = {\n                numero: document.getElementById('gfp-doc-numero').value.trim(),\n                data: document.getElementById('gfp-doc-data').value,\n                tipoDocumento: document.getElementById('gfp-doc-tipo').value,\n                pagamento: document.getElementById('gfp-doc-pagamento').value\n            };\n            var imponibile = parseFloat(document.getElementById('gfp-imponibile').value) || 0;\n            var aliquota = parseFloat(document.getElementById('gfp-aliquota').value);\n            if (isNaN(aliquota)) aliquota = 22;\n            var righe = [{ descrizione: 'Importo fattura', quantita: 1, prezzo: imponibile, aliquota: aliquota.toFixed(2) }];\n\n            if (!cedente.denom || !cedente.piva || !cessionario.denom || !cessionario.piva) {\n                mostraMessaggioP('Mancano dati di Fornitore o Cliente: completa i campi (l\\'estrazione automatica e\\' provvisoria).', 'errore');\n                return;\n            }\n            if (!doc.data) {\n                mostraMessaggioP('Manca la data del documento.', 'errore');\n                return;\n            }\n\n            var riepilogo = calcolaRiepilogo(righe);\n            var xml = costruisciXmlFatturaPA({ cedente: cedente, cessionario: cessionario, doc: doc, righe: righe, riepilogo: riepilogo });\n            var nomeFile = 'ANONIMIZZATA_PDF_' + (doc.numero || 'documento') + '.xml';\n            scaricaTesto(nomeFile, xml);\n            mostraMessaggioP('XML generato e scaricato: ' + nomeFile, 'successo');\n        });\n\n        \/\/ ================= MODULO: ANAGRAFICHE FITTIZIE =================\n        var CHIAVE_ANAGRAFICHE = 'gf_anagrafiche_v1';\n        var URL_COMUNI_ITALIANI = 'https:\/\/raw.githubusercontent.com\/matteocontrini\/comuni-json\/master\/comuni.json';\n        var URL_COGNOME_CASUALE = 'https:\/\/randomuser.me\/api\/?nat=it&results=1&inc=name';\n        var cacheComuniItaliani = null; \/\/ caricata al primo utilizzo, valida per la sessione corrente\n\n        var SUFFISSI_AZIENDALI = ['Srl', 'Snc', 'Spa', 'e Figli Srl', '& C. Snc', 'Industriale Srl'];\n\n        function mostraMessaggioA(testo, tipo) {\n            var el = document.getElementById('ga-messaggio');\n            el.textContent = testo;\n            el.className = 'gf-messaggio ' + (tipo || '');\n        }\n\n        function caricaAnagrafiche() {\n            try {\n                var grezzo = localStorage.getItem(CHIAVE_ANAGRAFICHE);\n                return grezzo ? JSON.parse(grezzo) : [];\n            } catch (e) { return []; }\n        }\n\n        function salvaAnagrafiche(elenco) {\n            try {\n                localStorage.setItem(CHIAVE_ANAGRAFICHE, JSON.stringify(elenco));\n                return true;\n            } catch (e) {\n                mostraMessaggioA('Impossibile salvare: memoria del browser piena o non disponibile.', 'errore');\n                return false;\n            }\n        }\n\n        function generaIdUnivoco() {\n            return 'a' + Date.now().toString(36) + Math.floor(Math.random() * 10000).toString(36);\n        }\n\n        function renderizzaTabellaAnagrafiche() {\n            var elenco = caricaAnagrafiche();\n            var corpo = document.getElementById('ga-tabella-body');\n            corpo.innerHTML = '';\n            elenco.forEach(function (voce) {\n                var tr = document.createElement('tr');\n                tr.innerHTML =\n                    '<td>' + escapeXml(voce.tipo) + '<\/td>' +\n                    '<td>' + escapeXml(voce.denom) + '<\/td>' +\n                    '<td>' + escapeXml(voce.piva) + '<\/td>' +\n                    '<td>' + escapeXml(voce.comune) + '<\/td>' +\n                    '<td>' + escapeXml(voce.provincia) + '<\/td>' +\n                    '<td><button type=\"button\" class=\"gf-btn ga-btn-rimuovi\" data-id=\"' + voce.id + '\">Rimuovi<\/button><\/td>';\n                corpo.appendChild(tr);\n            });\n            corpo.querySelectorAll('.ga-btn-rimuovi').forEach(function (btn) {\n                btn.addEventListener('click', function () {\n                    var id = btn.getAttribute('data-id');\n                    var nuovoElenco = caricaAnagrafiche().filter(function (v) { return v.id !== id; });\n                    salvaAnagrafiche(nuovoElenco);\n                    renderizzaTabellaAnagrafiche();\n                    aggiornaSelectAnagraficheInGenera();\n                });\n            });\n            document.getElementById('ga-contatore').textContent = elenco.length + ' anagrafiche salvate.';\n        }\n\n        \/\/ Aggiorna la select \"oppure usa dall'elenco\" nella scheda Genera Fatture\n        function aggiornaSelectAnagraficheInGenera() {\n            var select = document.getElementById('gf-ces-da-elenco');\n            var elenco = caricaAnagrafiche();\n            var valoreAttuale = select.value;\n            select.innerHTML = '<option value=\"\">-- nessuna --<\/option>';\n            elenco.forEach(function (voce) {\n                var opzione = document.createElement('option');\n                opzione.value = voce.id;\n                opzione.textContent = voce.tipo + ' - ' + voce.denom + ' (' + voce.comune + ')';\n                select.appendChild(opzione);\n            });\n            select.value = valoreAttuale; \/\/ ripristina la selezione se ancora presente\n        }\n\n        document.getElementById('gf-ces-da-elenco').addEventListener('change', function () {\n            var id = this.value;\n            if (!id) return;\n            var voce = caricaAnagrafiche().filter(function (v) { return v.id === id; })[0];\n            if (!voce) return;\n            document.getElementById('gf-ces-denom').value = voce.denom;\n            document.getElementById('gf-ces-piva').value = voce.piva;\n            document.getElementById('gf-ces-indirizzo').value = voce.indirizzo;\n            document.getElementById('gf-ces-cap').value = voce.cap;\n            document.getElementById('gf-ces-comune').value = voce.comune;\n            document.getElementById('gf-ces-provincia').value = voce.provincia;\n        });\n\n        document.getElementById('ga-btn-aggiungi').addEventListener('click', function () {\n            var voce = {\n                id: generaIdUnivoco(),\n                tipo: document.getElementById('ga-tipo').value,\n                denom: document.getElementById('ga-denom').value.trim(),\n                piva: document.getElementById('ga-piva').value.trim(),\n                indirizzo: document.getElementById('ga-indirizzo').value.trim(),\n                cap: document.getElementById('ga-cap').value.trim(),\n                comune: document.getElementById('ga-comune').value.trim(),\n                provincia: document.getElementById('ga-provincia').value.trim()\n            };\n            if (!voce.denom || !voce.piva) {\n                mostraMessaggioA('Denominazione e Partita IVA sono obbligatorie.', 'errore');\n                return;\n            }\n            var elenco = caricaAnagrafiche();\n            elenco.push(voce);\n            if (salvaAnagrafiche(elenco)) {\n                mostraMessaggioA('Anagrafica aggiunta: ' + voce.denom, 'successo');\n                renderizzaTabellaAnagrafiche();\n                aggiornaSelectAnagraficheInGenera();\n            }\n        });\n\n        \/\/ ---------- Generazione arricchita dal web ----------\n        async function recuperaComuneCasualeDalWeb() {\n            try {\n                if (!cacheComuniItaliani) {\n                    var risposta = await fetch(URL_COMUNI_ITALIANI);\n                    if (!risposta.ok) throw new Error('Risposta non valida dal servizio comuni');\n                    cacheComuniItaliani = await risposta.json();\n                }\n                var comune = cacheComuniItaliani[Math.floor(Math.random() * cacheComuniItaliani.length)];\n                var cap = (comune.cap && comune.cap.length) ? comune.cap[Math.floor(Math.random() * comune.cap.length)] : '00000';\n                return {\n                    comune: comune.nome,\n                    provincia: comune.sigla || (comune.provincia && comune.provincia.nome ? comune.provincia.nome.substring(0, 2).toUpperCase() : 'XX'),\n                    cap: cap\n                };\n            } catch (e) {\n                return null; \/\/ il chiamante ricadra' sull'elenco locale\n            }\n        }\n\n        async function recuperaCognomeCasualeDalWeb() {\n            try {\n                var risposta = await fetch(URL_COGNOME_CASUALE);\n                if (!risposta.ok) throw new Error('Risposta non valida dal servizio cognomi');\n                var dati = await risposta.json();\n                return dati.results[0].name.last;\n            } catch (e) {\n                return null;\n            }\n        }\n\n        document.getElementById('ga-btn-genera-web').addEventListener('click', async function () {\n            var statoEl = document.getElementById('ga-stato-generazione');\n            statoEl.textContent = 'Recupero dati dal web...';\n            var tipo = document.getElementById('ga-nuovo-tipo').value;\n\n            var comune = await recuperaComuneCasualeDalWeb();\n            var cognome = await recuperaCognomeCasualeDalWeb();\n\n            var usatoWeb = true;\n            if (!comune) {\n                comune = scegliCasuale(CITTA_FITTIZIE);\n                usatoWeb = false;\n            }\n            var denominazione;\n            if (cognome) {\n                denominazione = cognome + ' ' + scegliCasuale(SUFFISSI_AZIENDALI);\n            } else {\n                denominazione = scegliCasuale(DENOMINAZIONI_FITTIZIE);\n                usatoWeb = false;\n            }\n\n            document.getElementById('ga-tipo').value = tipo;\n            document.getElementById('ga-denom').value = denominazione;\n            document.getElementById('ga-piva').value = generaPartitaIvaFittizia();\n            document.getElementById('ga-cap').value = comune.cap;\n            document.getElementById('ga-comune').value = comune.comune;\n            document.getElementById('ga-provincia').value = comune.provincia;\n            document.getElementById('ga-indirizzo').value = 'Recupero indirizzo...';\n            document.getElementById('ga-indirizzo').value = await generaIndirizzoCasuale(comune.comune);\n\n            statoEl.textContent = usatoWeb\n                ? 'Dati recuperati dal web. Controlla i campi qui sotto e premi \"Aggiungi all\\'elenco\".'\n                : 'Il servizio web non ha risposto: usati i dati locali di riserva. Controlla i campi e premi \"Aggiungi all\\'elenco\".';\n        });\n\n        \/\/ ---------- Backup: esporta\/importa JSON ----------\n        document.getElementById('ga-btn-esporta').addEventListener('click', function () {\n            var elenco = caricaAnagrafiche();\n            var blob = new Blob([JSON.stringify(elenco, null, 2)], { type: 'application\/json' });\n            var url = URL.createObjectURL(blob);\n            var a = document.createElement('a');\n            a.href = url;\n            a.download = 'anagrafiche-fittizie-backup.json';\n            document.body.appendChild(a);\n            a.click();\n            document.body.removeChild(a);\n            URL.revokeObjectURL(url);\n        });\n\n        document.getElementById('ga-btn-importa-apri').addEventListener('click', function () {\n            document.getElementById('ga-file-importa').click();\n        });\n\n        document.getElementById('ga-file-importa').addEventListener('change', function (ev) {\n            var file = ev.target.files[0];\n            if (!file) return;\n            var lettore = new FileReader();\n            lettore.onload = function (e) {\n                try {\n                    var importati = JSON.parse(e.target.result);\n                    if (!Array.isArray(importati)) throw new Error('Il file non contiene un elenco valido.');\n                    var esistenti = caricaAnagrafiche();\n                    var idEsistenti = esistenti.map(function (v) { return v.id; });\n                    var aggiunti = 0;\n                    importati.forEach(function (voce) {\n                        if (!voce.id || idEsistenti.indexOf(voce.id) === -1) {\n                            if (!voce.id) voce.id = generaIdUnivoco();\n                            esistenti.push(voce);\n                            aggiunti++;\n                        }\n                    });\n                    salvaAnagrafiche(esistenti);\n                    renderizzaTabellaAnagrafiche();\n                    aggiornaSelectAnagraficheInGenera();\n                    mostraMessaggioA('Importazione completata: ' + aggiunti + ' nuove anagrafiche aggiunte (duplicati ignorati).', 'successo');\n                } catch (erroreGenerico) {\n                    mostraMessaggioA('File non valido: ' + erroreGenerico.message, 'errore');\n                }\n            };\n            lettore.readAsText(file, 'UTF-8');\n        });\n\n        \/\/ ================= MODULO: GENERA MASSIVA =================\n        \/\/ Porting fedele di generatore_fatture.dart e genera_xml.dart (progetto Dart).\n        \/\/ Riusa: formattaImporto, scaricaTesto (per il pattern, qui serve una variante blob)\n\n        \/\/ ---------- Calendario festivita' italiane (fisse + Pasqua\/Pasquetta) ----------\n        function calcolaPasqua(anno) {\n            \/\/ Algoritmo di Gauss (Meeus\/Jones\/Butcher) per la Pasqua gregoriana\n            var a = anno % 19, b = Math.floor(anno \/ 100), c = anno % 100;\n            var d = Math.floor(b \/ 4), e = b % 4, f = Math.floor((b + 8) \/ 25);\n            var g = Math.floor((b - f + 1) \/ 3);\n            var h = (19 * a + b - d - g + 15) % 30;\n            var i = Math.floor(c \/ 4), k = c % 4;\n            var l = (32 + 2 * e + 2 * i - h - k) % 7;\n            var m = Math.floor((a + 11 * h + 22 * l) \/ 451);\n            var mese = Math.floor((h + l - 7 * m + 114) \/ 31);\n            var giorno = ((h + l - 7 * m + 114) % 31) + 1;\n            return new Date(anno, mese - 1, giorno);\n        }\n\n        function festivitaItaliane(anno) {\n            var pasqua = calcolaPasqua(anno);\n            var pasquetta = new Date(pasqua.getFullYear(), pasqua.getMonth(), pasqua.getDate() + 1);\n            var fisse = [[1,1], [1,6], [4,25], [5,1], [6,2], [8,15], [11,1], [12,8], [12,25], [12,26]]\n                .map(function (mg) { return new Date(anno, mg[0] - 1, mg[1]); });\n            return fisse.concat([pasqua, pasquetta]);\n        }\n\n        function stessaData(a, b) {\n            return a.getFullYear() === b.getFullYear() && a.getMonth() === b.getMonth() && a.getDate() === b.getDate();\n        }\n\n        \/\/ mese: 1-12. Restituisce un Date casuale nel mese che rispetta i vincoli richiesti\n        function giornoLavorativoCasualeInMese(anno, mese, escludiWeekend, escludiFestivi) {\n            var ultimoGiorno = new Date(anno, mese, 0).getDate();\n            var festivita = escludiFestivi ? festivitaItaliane(anno) : [];\n            var candidati = [];\n            for (var g = 1; g <= ultimoGiorno; g++) {\n                var data = new Date(anno, mese - 1, g);\n                var weekday = data.getDay();\n                if (escludiWeekend && (weekday === 0 || weekday === 6)) continue;\n                if (escludiFestivi && festivita.some(function (f) { return stessaData(f, data); })) continue;\n                candidati.push(data);\n            }\n            if (candidati.length === 0) return new Date(anno, mese - 1, 1); \/\/ fallback estremo\n            return candidati[Math.floor(Math.random() * candidati.length)];\n        }\n\n        function ultimoGiornoDelMeseData(anno, mese) {\n            return new Date(anno, mese, 0);\n        }\n\n        function calcolaScadenzaData(dataEmissione, giorni) {\n            var d = new Date(dataEmissione.getFullYear(), dataEmissione.getMonth(), dataEmissione.getDate());\n            d.setDate(d.getDate() + giorni);\n            return d;\n        }\n\n        \/\/ Aggiunge mesi interi a una data secondo la regola del calendario commerciale:\n        \/\/ se la data di partenza e' l'ultimo giorno del suo mese, il risultato e' l'ultimo\n        \/\/ giorno del mese di arrivo (es. 30 settembre + 4 mesi = 31 gennaio, non 30 gennaio);\n        \/\/ altrimenti mantiene lo stesso numero di giorno, troncato se il mese di arrivo e' piu' corto.\n        function aggiungiMesiConEOM(data, mesi) {\n            var ultimoGiornoMesePartenza = new Date(data.getFullYear(), data.getMonth() + 1, 0).getDate();\n            var eraFineMese = data.getDate() === ultimoGiornoMesePartenza;\n            var meseTotale = data.getMonth() + mesi;\n            var nuovoAnno = data.getFullYear() + Math.floor(meseTotale \/ 12);\n            var nuovoMese = ((meseTotale % 12) + 12) % 12;\n            var ultimoGiornoMeseArrivo = new Date(nuovoAnno, nuovoMese + 1, 0).getDate();\n            var giorno = eraFineMese ? ultimoGiornoMeseArrivo : Math.min(data.getDate(), ultimoGiornoMeseArrivo);\n            return new Date(nuovoAnno, nuovoMese, giorno);\n        }\n\n        \/\/ Sposta una data al primo giorno non festivo\/non festivo utile (sabato, domenica\n        \/\/ e festivita' italiane escluse), avanzando di un giorno alla volta\n        function rollaAGiornoLavorativoUtile(data) {\n            var d = new Date(data.getFullYear(), data.getMonth(), data.getDate());\n            var tentativi = 0;\n            while (tentativi < 30) {\n                var giornoSettimana = d.getDay();\n                var festivitaAnnoCorrente = festivitaItaliane(d.getFullYear());\n                var isFestivo = festivitaAnnoCorrente.some(function (f) { return stessaData(f, d); });\n                if (giornoSettimana !== 0 && giornoSettimana !== 6 && !isFestivo) break;\n                d.setDate(d.getDate() + 1);\n                tentativi++;\n            }\n            return d;\n        }\n\n        \/\/ Calcola la data di scadenza secondo la modalita' scelta per la riga\n        \/\/ (vista fattura \/ fine mese \/ a tot giorni con base data-fattura o fine-mese),\n        \/\/ poi applica sempre il rinvio al primo giorno lavorativo utile\n        function calcolaDataScadenzaCompleta(scadenzaParam, dataEmissione, anno, mese) {\n            var dataGrezza;\n            if (scadenzaParam.modalita === 'vista') {\n                dataGrezza = new Date(dataEmissione.getFullYear(), dataEmissione.getMonth(), dataEmissione.getDate());\n            } else if (scadenzaParam.modalita === 'finemese') {\n                dataGrezza = ultimoGiornoDelMeseData(anno, mese);\n            } else { \/\/ 'giorni'\n                var giorni = scadenzaParam.giorni || 0;\n                if (scadenzaParam.base === 'finemese') {\n                    var baseFineMese = ultimoGiornoDelMeseData(anno, mese);\n                    var mesiInteri = Math.floor(giorni \/ 30);\n                    var giorniResidui = giorni % 30;\n                    dataGrezza = aggiungiMesiConEOM(baseFineMese, mesiInteri);\n                    if (giorniResidui > 0) dataGrezza.setDate(dataGrezza.getDate() + giorniResidui);\n                } else {\n                    dataGrezza = calcolaScadenzaData(dataEmissione, giorni);\n                }\n            }\n            return rollaAGiornoLavorativoUtile(dataGrezza);\n        }\n\n        function formattaDataISO(data) {\n            var anno = data.getFullYear().toString().padStart(4, '0');\n            var mese = (data.getMonth() + 1).toString().padStart(2, '0');\n            var giorno = data.getDate().toString().padStart(2, '0');\n            return anno + '-' + mese + '-' + giorno;\n        }\n\n\n        \/\/ ---------- Motore di generazione (porting di GeneratoreFatture.generaSetFatture) ----------\n        var TIPO_DOC_NOTA_CREDITO = 'TD04';\n\n        function arrotondaACentesimi(valore) { return Math.round(valore * 100) \/ 100; }\n\n        function distribuisciImportoMassivo(importoTotale, numeroVoci) {\n            var pesi = [];\n            for (var i = 0; i < numeroVoci; i++) pesi.push(Math.random() + 0.1);\n            var sommaPesi = pesi.reduce(function (s, p) { return s + p; }, 0);\n            var importi = new Array(numeroVoci).fill(0);\n            var sommaParziale = 0;\n            for (var i = 0; i < numeroVoci - 1; i++) {\n                var imp = arrotondaACentesimi(importoTotale * pesi[i] \/ sommaPesi);\n                importi[i] = imp;\n                sommaParziale += imp;\n            }\n            importi[numeroVoci - 1] = arrotondaACentesimi(importoTotale - sommaParziale);\n            return importi;\n        }\n\n        function espandiMixTipologie(mix, numeroRichiesto) {\n            var totaleMix = mix.reduce(function (s, m) { return s + m.numeroDocumenti; }, 0);\n            if (totaleMix === 0) return new Array(numeroRichiesto).fill('TD01');\n            var elenco = [];\n            mix.forEach(function (m) { for (var i = 0; i < m.numeroDocumenti; i++) elenco.push(m.tipoDocumento); });\n            var risultato = [];\n            for (var i = 0; i < numeroRichiesto; i++) risultato.push(elenco[i % totaleMix]);\n            return risultato;\n        }\n\n        function espandiScadenze(scadenze, numeroRichiesto) {\n            var totale = scadenze.reduce(function (s, sc) { return s + sc.numeroDocumenti; }, 0);\n            if (totale === 0) return new Array(numeroRichiesto).fill({ modalita: 'giorni', giorni: 30, base: 'finemese' });\n            var elenco = [];\n            scadenze.forEach(function (sc) { for (var i = 0; i < sc.numeroDocumenti; i++) elenco.push(sc); });\n            var risultato = [];\n            for (var i = 0; i < numeroRichiesto; i++) risultato.push(elenco[i % totale]);\n            return risultato;\n        }\n\n        function mescolaIndici(numeroElementi) {\n            var indici = [];\n            for (var i = 0; i < numeroElementi; i++) indici.push(i);\n            for (var i = numeroElementi - 1; i > 0; i--) {\n                var j = Math.floor(Math.random() * (i + 1));\n                var tmp = indici[i]; indici[i] = indici[j]; indici[j] = tmp;\n            }\n            return indici;\n        }\n\n        function generaSetFatture(parametri) {\n            var controparti = parametri.controparti;\n            if (!controparti || controparti.length === 0) {\n                throw new Error('Nessuna controparte (cliente\/fornitore) selezionata per la generazione.');\n            }\n\n            var numeroFatture = parametri.mixTipologie.reduce(function (s, m) { return s + m.numeroDocumenti; }, 0);\n            if (numeroFatture === 0) return [];\n\n            var tipiEspansi = espandiMixTipologie(parametri.mixTipologie, numeroFatture);\n            var scadenzeEspanse = espandiScadenze(parametri.distribuzioneScadenze, numeroFatture);\n            var indiciScadenzeMescolati = mescolaIndici(numeroFatture);\n\n            var numNoteCredito = tipiEspansi.filter(function (t) { return t === TIPO_DOC_NOTA_CREDITO; }).length;\n            var numPositivi = numeroFatture - numNoteCredito;\n\n            var importoNoteCredito, importoPositiviTotale;\n            if (numPositivi === 0) {\n                importoNoteCredito = Math.abs(parametri.imponibileTarget);\n                importoPositiviTotale = 0;\n            } else {\n                if (numNoteCredito > 0) {\n                    var quota = 0.05 + Math.random() * 0.15;\n                    importoNoteCredito = arrotondaACentesimi(parametri.imponibileTarget * quota);\n                } else {\n                    importoNoteCredito = 0;\n                }\n                importoPositiviTotale = parametri.imponibileTarget + importoNoteCredito;\n            }\n\n            var importiPositivi = numPositivi > 0 ? distribuisciImportoMassivo(importoPositiviTotale, numPositivi) : [];\n            var importiNoteCredito = numNoteCredito > 0 ? distribuisciImportoMassivo(importoNoteCredito, numNoteCredito) : [];\n\n            var risultato = [];\n            var cursorePositivi = 0, cursoreNoteCredito = 0;\n\n            for (var i = 0; i < numeroFatture; i++) {\n                var tipo = tipiEspansi[i];\n                var importo;\n                if (tipo === TIPO_DOC_NOTA_CREDITO) {\n                    importo = importiNoteCredito[cursoreNoteCredito]; cursoreNoteCredito++;\n                } else {\n                    importo = importiPositivi[cursorePositivi]; cursorePositivi++;\n                }\n\n                var dataEmissione = giornoLavorativoCasualeInMese(\n                    parametri.anno, parametri.mese, parametri.escludiWeekend, parametri.escludiFestivi\n                );\n\n                var scadenzaParam = scadenzeEspanse[indiciScadenzeMescolati[i]];\n                var dataScadenza = calcolaDataScadenzaCompleta(\n                    scadenzaParam, dataEmissione, parametri.anno, parametri.mese\n                );\n\n                var controparte = controparti[i % controparti.length];\n\n                risultato.push({\n                    tipoDocumento: tipo,\n                    dataEmissione: dataEmissione,\n                    dataScadenza: dataScadenza,\n                    importo: importo,\n                    partitaIvaControparte: controparte.piva,\n                    nomeControparte: controparte.denom,\n                    indirizzoControparte: controparte.indirizzo,\n                    capControparte: controparte.cap,\n                    comuneControparte: controparte.comune,\n                    provinciaControparte: controparte.provincia\n                });\n            }\n            return risultato;\n        }\n\n        function verificaImponibileNetto(righe) {\n            var totale = 0;\n            righe.forEach(function (r) {\n                totale += (r.tipoDocumento === TIPO_DOC_NOTA_CREDITO) ? -r.importo : r.importo;\n            });\n            return totale;\n        }\n\n        \/\/ ---------- Costruzione XML per riga generata (porting di GeneraXml) ----------\n        function separaPrefissoECodice(piva) {\n            var t = (piva || '').trim();\n            if (t.length >= 2 && \/^[A-Za-z]{2}$\/.test(t.substring(0, 2))) {\n                return [t.substring(0, 2).toUpperCase(), t.substring(2)];\n            }\n            return ['IT', t];\n        }\n\n        function descrizioneRigaGenerica(tipoDocumento) {\n            if (tipoDocumento === 'TD04') return 'Nota di credito relativa a fattura precedente';\n            if (tipoDocumento === 'TD21') return 'Autofattura per splafonamento';\n            return 'Prestazione di servizi\/cessione di beni';\n        }\n\n        function pulisciPerNomeFile(testo) {\n            return (testo || '').replace(\/[\\\\\/:*?\"<>|]\/g, '_');\n        }\n\n        function nomeFileFatturaPAMassivo(partitaIvaPropria, numeroProgressivo, numeroDocumentoOriginale) {\n            var progressivo = numeroProgressivo.toString().padStart(5, '0');\n            var pivaPulita = pulisciPerNomeFile(partitaIvaPropria);\n            if (numeroDocumentoOriginale && numeroDocumentoOriginale.trim()) {\n                return pivaPulita + '_' + pulisciPerNomeFile(numeroDocumentoOriginale.trim()) + '_' + progressivo + '.xml';\n            }\n            return pivaPulita + '_' + progressivo + '.xml';\n        }\n\n        \/\/ Indirizzo di riserva usato quando la controparte non ha un indirizzo salvato\n        \/\/ (stesso placeholder del progetto Dart originale)\n        var SEDE_PLACEHOLDER = { indirizzo: 'Via Roma 1', cap: '00100', comune: 'Roma', provincia: 'RM' };\n\n        function costruisciXmlMassivo(riga, numeroDocumento, partitaIvaPropria, denominazionePropria, tipoAnagrafica, aliquotaIvaPercento) {\n            var aliquotaIva = aliquotaIvaPercento \/ 100;\n            var imposta = arrotondaACentesimi(riga.importo * aliquotaIva);\n            var importoTotale = riga.importo + imposta;\n            var esente = aliquotaIva === 0;\n            var aliquotaFormattata = (aliquotaIva * 100).toFixed(2);\n\n            var prefissoPropria_codicePropria = separaPrefissoECodice(partitaIvaPropria);\n            var prefissoContro_codiceContro = separaPrefissoECodice(riga.partitaIvaControparte);\n            var prefissoPropria = prefissoPropria_codicePropria[0], codicePropria = prefissoPropria_codicePropria[1];\n            var prefissoContro = prefissoContro_codiceContro[0], codiceContro = prefissoContro_codiceContro[1];\n\n            var sedeContro = (riga.indirizzoControparte)\n                ? { indirizzo: riga.indirizzoControparte, cap: riga.capControparte, comune: riga.comuneControparte, provincia: riga.provinciaControparte }\n                : SEDE_PLACEHOLDER;\n\n            var prefissoCedente, codiceCedente, denomCedente, sedeCedente;\n            var prefissoCessionario, codiceCessionario, denomCessionario, sedeCessionario;\n\n            if (tipoAnagrafica === 'C') {\n                prefissoCedente = prefissoPropria; codiceCedente = codicePropria; denomCedente = denominazionePropria; sedeCedente = SEDE_PLACEHOLDER;\n                prefissoCessionario = prefissoContro; codiceCessionario = codiceContro; denomCessionario = riga.nomeControparte; sedeCessionario = sedeContro;\n            } else {\n                prefissoCedente = prefissoContro; codiceCedente = codiceContro; denomCedente = riga.nomeControparte; sedeCedente = sedeContro;\n                prefissoCessionario = prefissoPropria; codiceCessionario = codicePropria; denomCessionario = denominazionePropria; sedeCessionario = SEDE_PLACEHOLDER;\n            }\n\n            var numeroFattura = numeroDocumento.toString();\n\n            var xml = '<' + '?xml version=\"1.0\" encoding=\"UTF-8\"?>\\n' +\n                '<p:FatturaElettronica xmlns:p=\"http:\/\/ivaservizi.agenziaentrate.gov.it\/docs\/xsd\/fatture\/v1.2\" versione=\"FPR12\" xmlns:xsi=\"http:\/\/www.w3.org\/2001\/XMLSchema-instance\">\\n' +\n                ' <FatturaElettronicaHeader>\\n' +\n                '  <DatiTrasmissione>\\n' +\n                '   <IdTrasmittente>\\n' +\n                '    <IdPaese>' + escapeXml(prefissoPropria) + '<\/IdPaese>\\n' +\n                '    <IdCodice>' + escapeXml(codicePropria) + '<\/IdCodice>\\n' +\n                '   <\/IdTrasmittente>\\n' +\n                '   <ProgressivoInvio>' + escapeXml(numeroFattura) + '<\/ProgressivoInvio>\\n' +\n                '   <FormatoTrasmissione>FPR12<\/FormatoTrasmissione>\\n' +\n                '   <CodiceDestinatario>0000000<\/CodiceDestinatario>\\n' +\n                '  <\/DatiTrasmissione>\\n' +\n                '  <CedentePrestatore>\\n' +\n                '   <DatiAnagrafici>\\n' +\n                '    <IdFiscaleIVA>\\n' +\n                '     <IdPaese>' + escapeXml(prefissoCedente) + '<\/IdPaese>\\n' +\n                '     <IdCodice>' + escapeXml(codiceCedente) + '<\/IdCodice>\\n' +\n                '    <\/IdFiscaleIVA>\\n' +\n                '    <Anagrafica>\\n' +\n                '     <Denominazione>' + escapeXml(denomCedente) + '<\/Denominazione>\\n' +\n                '    <\/Anagrafica>\\n' +\n                '    <RegimeFiscale>RF01<\/RegimeFiscale>\\n' +\n                '   <\/DatiAnagrafici>\\n' +\n                '   <Sede>\\n' +\n                '    <Indirizzo>' + escapeXml(sedeCedente.indirizzo) + '<\/Indirizzo>\\n' +\n                '    <CAP>' + escapeXml(sedeCedente.cap) + '<\/CAP>\\n' +\n                '    <Comune>' + escapeXml(sedeCedente.comune) + '<\/Comune>\\n' +\n                '    <Provincia>' + escapeXml(sedeCedente.provincia) + '<\/Provincia>\\n' +\n                '    <Nazione>' + escapeXml(prefissoCedente) + '<\/Nazione>\\n' +\n                '   <\/Sede>\\n' +\n                '  <\/CedentePrestatore>\\n' +\n                '  <CessionarioCommittente>\\n' +\n                '   <DatiAnagrafici>\\n' +\n                '    <IdFiscaleIVA>\\n' +\n                '     <IdPaese>' + escapeXml(prefissoCessionario) + '<\/IdPaese>\\n' +\n                '     <IdCodice>' + escapeXml(codiceCessionario) + '<\/IdCodice>\\n' +\n                '    <\/IdFiscaleIVA>\\n' +\n                '    <Anagrafica>\\n' +\n                '     <Denominazione>' + escapeXml(denomCessionario) + '<\/Denominazione>\\n' +\n                '    <\/Anagrafica>\\n' +\n                '   <\/DatiAnagrafici>\\n' +\n                '   <Sede>\\n' +\n                '    <Indirizzo>' + escapeXml(sedeCessionario.indirizzo) + '<\/Indirizzo>\\n' +\n                '    <CAP>' + escapeXml(sedeCessionario.cap) + '<\/CAP>\\n' +\n                '    <Comune>' + escapeXml(sedeCessionario.comune) + '<\/Comune>\\n' +\n                '    <Provincia>' + escapeXml(sedeCessionario.provincia) + '<\/Provincia>\\n' +\n                '    <Nazione>' + escapeXml(prefissoCessionario) + '<\/Nazione>\\n' +\n                '   <\/Sede>\\n' +\n                '  <\/CessionarioCommittente>\\n' +\n                ' <\/FatturaElettronicaHeader>\\n' +\n                ' <FatturaElettronicaBody>\\n' +\n                '  <DatiGenerali>\\n' +\n                '   <DatiGeneraliDocumento>\\n' +\n                '    <TipoDocumento>' + escapeXml(riga.tipoDocumento) + '<\/TipoDocumento>\\n' +\n                '    <Divisa>EUR<\/Divisa>\\n' +\n                '    <Data>' + formattaDataISO(riga.dataEmissione) + '<\/Data>\\n' +\n                '    <Numero>' + escapeXml(numeroFattura) + '<\/Numero>\\n' +\n                '    <ImportoTotaleDocumento>' + formattaImporto(importoTotale) + '<\/ImportoTotaleDocumento>\\n' +\n                '   <\/DatiGeneraliDocumento>\\n' +\n                '  <\/DatiGenerali>\\n' +\n                '  <DatiBeniServizi>\\n' +\n                '   <DettaglioLinee>\\n' +\n                '    <NumeroLinea>1<\/NumeroLinea>\\n' +\n                '    <Descrizione>' + escapeXml(descrizioneRigaGenerica(riga.tipoDocumento)) + '<\/Descrizione>\\n' +\n                '    <Quantita>1.00<\/Quantita>\\n' +\n                '    <PrezzoUnitario>' + formattaImporto(riga.importo) + '<\/PrezzoUnitario>\\n' +\n                '    <PrezzoTotale>' + formattaImporto(riga.importo) + '<\/PrezzoTotale>\\n' +\n                '    <AliquotaIVA>' + aliquotaFormattata + '<\/AliquotaIVA>\\n' +\n                (esente ? '    <Natura>N1<\/Natura>\\n' : '') +\n                '   <\/DettaglioLinee>\\n' +\n                '   <DatiRiepilogo>\\n' +\n                '    <AliquotaIVA>' + aliquotaFormattata + '<\/AliquotaIVA>\\n' +\n                (esente ? '    <Natura>N1<\/Natura>\\n' : '') +\n                '    <ImponibileImporto>' + formattaImporto(riga.importo) + '<\/ImponibileImporto>\\n' +\n                '    <Imposta>' + formattaImporto(imposta) + '<\/Imposta>\\n' +\n                '    <EsigibilitaIVA>I<\/EsigibilitaIVA>\\n' +\n                (esente ? '    <RiferimentoNormativo>Operazione non imponibile - documento didattico<\/RiferimentoNormativo>\\n' : '') +\n                '   <\/DatiRiepilogo>\\n' +\n                '  <\/DatiBeniServizi>\\n' +\n                '  <DatiPagamento>\\n' +\n                '   <CondizioniPagamento>TP02<\/CondizioniPagamento>\\n' +\n                '   <DettaglioPagamento>\\n' +\n                '    <ModalitaPagamento>MP05<\/ModalitaPagamento>\\n' +\n                '    <DataScadenzaPagamento>' + formattaDataISO(riga.dataScadenza) + '<\/DataScadenzaPagamento>\\n' +\n                '    <ImportoPagamento>' + formattaImporto(importoTotale) + '<\/ImportoPagamento>\\n' +\n                '   <\/DettaglioPagamento>\\n' +\n                '  <\/DatiPagamento>\\n' +\n                ' <\/FatturaElettronicaBody>\\n' +\n                '<\/p:FatturaElettronica>\\n';\n\n            return { xml: xml, nomeFile: nomeFileFatturaPAMassivo(partitaIvaPropria, numeroDocumento, numeroFattura) };\n        }\n\n        \/\/ ---------- Gestione righe dinamiche: Mix tipologie ----------\n        var righeMix = []; \/\/ { id, tipoDocumento, numeroDocumenti }\n        var righeScadenze = []; \/\/ { id, modalita: 'vista'|'finemese'|'giorni', giorni, base: 'finemese'|'fattura', numeroDocumenti }\n\n        function rendMix() {\n            var contenitore = document.getElementById('gmx-mix-lista');\n            contenitore.innerHTML = '';\n            righeMix.forEach(function (riga) {\n                var div = document.createElement('div');\n                div.className = 'gf-griglia';\n                div.style.marginBottom = '6px';\n                div.innerHTML =\n                    '<label>Tipo documento<select data-campo=\"tipoDocumento\"><\/select><\/label>' +\n                    '<label>Numero documenti<input type=\"number\" min=\"0\" data-campo=\"numeroDocumenti\" value=\"' + riga.numeroDocumenti + '\"><\/label>' +\n                    '<label>&nbsp;<button type=\"button\" class=\"gf-btn gmx-rimuovi-mix\">Rimuovi<\/button><\/label>';\n                var selectTipo = div.querySelector('[data-campo=\"tipoDocumento\"]');\n                popolaSelectTipoDocumento(selectTipo, riga.tipoDocumento);\n                selectTipo.addEventListener('change', function () { riga.tipoDocumento = this.value; });\n                div.querySelector('[data-campo=\"numeroDocumenti\"]').addEventListener('input', function () { riga.numeroDocumenti = parseInt(this.value, 10) || 0; });\n                div.querySelector('.gmx-rimuovi-mix').addEventListener('click', function () {\n                    righeMix = righeMix.filter(function (r) { return r.id !== riga.id; });\n                    rendMix();\n                });\n                contenitore.appendChild(div);\n            });\n            document.getElementById('gmx-mix-vuoto').style.display = righeMix.length ? 'none' : 'block';\n        }\n\n        function rendScadenze() {\n            var contenitore = document.getElementById('gmx-scad-lista');\n            contenitore.innerHTML = '';\n            righeScadenze.forEach(function (riga) {\n                var div = document.createElement('div');\n                div.className = 'gf-griglia';\n                div.style.marginBottom = '6px';\n                var mostraGiorni = riga.modalita === 'giorni';\n                div.innerHTML =\n                    '<label>Tipologia<select data-campo=\"modalita\">' +\n                        '<option value=\"vista\"' + (riga.modalita === 'vista' ? ' selected' : '') + '>Vista fattura (scadenza = emissione)<\/option>' +\n                        '<option value=\"finemese\"' + (riga.modalita === 'finemese' ? ' selected' : '') + '>Fine mese<\/option>' +\n                        '<option value=\"giorni\"' + (riga.modalita === 'giorni' ? ' selected' : '') + '>A tot giorni<\/option>' +\n                    '<\/select><\/label>' +\n                    (mostraGiorni ?\n                        '<label>Giorni<input type=\"number\" min=\"0\" list=\"gmx-giorni-comuni\" data-campo=\"giorni\" value=\"' + riga.giorni + '\"><\/label>' +\n                        '<label>Calcolo<select data-campo=\"base\">' +\n                            '<option value=\"finemese\"' + (riga.base === 'finemese' ? ' selected' : '') + '>Calendario commerciale (fine mese)<\/option>' +\n                            '<option value=\"fattura\"' + (riga.base === 'fattura' ? ' selected' : '') + '>Data fattura (giorni esatti)<\/option>' +\n                        '<\/select><\/label>'\n                        : '') +\n                    '<label>Numero documenti<input type=\"number\" min=\"0\" data-campo=\"numeroDocumenti\" value=\"' + riga.numeroDocumenti + '\"><\/label>' +\n                    '<label>&nbsp;<button type=\"button\" class=\"gf-btn gmx-rimuovi-scad\">Rimuovi<\/button><\/label>';\n\n                div.querySelector('[data-campo=\"modalita\"]').addEventListener('change', function () {\n                    riga.modalita = this.value;\n                    rendScadenze(); \/\/ ri-renderizza per mostrare\/nascondere i campi giorni\/calcolo\n                });\n                if (mostraGiorni) {\n                    div.querySelector('[data-campo=\"giorni\"]').addEventListener('input', function () { riga.giorni = parseInt(this.value, 10) || 0; });\n                    div.querySelector('[data-campo=\"base\"]').addEventListener('change', function () { riga.base = this.value; });\n                }\n                div.querySelector('[data-campo=\"numeroDocumenti\"]').addEventListener('input', function () { riga.numeroDocumenti = parseInt(this.value, 10) || 0; });\n                div.querySelector('.gmx-rimuovi-scad').addEventListener('click', function () {\n                    righeScadenze = righeScadenze.filter(function (r) { return r.id !== riga.id; });\n                    rendScadenze();\n                });\n                contenitore.appendChild(div);\n            });\n            document.getElementById('gmx-scad-vuoto').style.display = righeScadenze.length ? 'none' : 'block';\n        }\n\n        document.getElementById('gmx-btn-aggiungi-mix').addEventListener('click', function () {\n            righeMix.push({ id: generaIdUnivoco(), tipoDocumento: 'TD01', numeroDocumenti: 1 });\n            rendMix();\n        });\n        document.getElementById('gmx-btn-aggiungi-scad').addEventListener('click', function () {\n            righeScadenze.push({ id: generaIdUnivoco(), modalita: 'giorni', giorni: 30, base: 'finemese', numeroDocumenti: 1 });\n            rendScadenze();\n        });\n\n        \/\/ ---------- Selezione controparti dall'elenco Anagrafiche ----------\n        function aggiornaListaContropartiMassiva() {\n            var elenco = caricaAnagrafiche();\n            var contenitore = document.getElementById('gmx-controparti-lista');\n            if (elenco.length === 0) {\n                contenitore.innerHTML = 'Nessuna anagrafica salvata: vai nella scheda Anagrafiche per aggiungerne.';\n                return;\n            }\n            contenitore.innerHTML = '';\n            elenco.forEach(function (voce) {\n                var label = document.createElement('label');\n                label.style.display = 'block';\n                label.innerHTML = '<input type=\"checkbox\" class=\"gmx-controparte-check\" value=\"' + voce.id + '\"> ' +\n                    escapeXml(voce.tipo) + ' - ' + escapeXml(voce.denom) + ' (' + escapeXml(voce.comune) + ')';\n                contenitore.appendChild(label);\n            });\n        }\n        document.getElementById('gmx-btn-aggiorna-controparti').addEventListener('click', aggiornaListaContropartiMassiva);\n\n        \/\/ ---------- Genera anteprima ----------\n        var ultimoSetGenerato = null;\n\n        document.getElementById('gmx-btn-genera-anteprima').addEventListener('click', function () {\n            try {\n                var idSelezionati = Array.from(document.querySelectorAll('.gmx-controparte-check:checked')).map(function (c) { return c.value; });\n                var controparti = caricaAnagrafiche().filter(function (v) { return idSelezionati.indexOf(v.id) !== -1; });\n\n                var parametri = {\n                    anno: parseInt(document.getElementById('gmx-anno').value, 10),\n                    mese: parseInt(document.getElementById('gmx-mese').value, 10),\n                    imponibileTarget: parseFloat(document.getElementById('gmx-imponibile').value) || 0,\n                    mixTipologie: righeMix.filter(function (r) { return r.numeroDocumenti > 0; }),\n                    distribuzioneScadenze: righeScadenze.filter(function (r) { return r.numeroDocumenti > 0; }),\n                    controparti: controparti,\n                    escludiWeekend: document.getElementById('gmx-escludi-weekend').checked,\n                    escludiFestivi: document.getElementById('gmx-escludi-festivi').checked\n                };\n\n                if (controparti.length === 0) {\n                    mostraMessaggioGmx('Seleziona almeno una controparte dall\\'elenco Anagrafiche.', 'errore');\n                    return;\n                }\n                if (parametri.mixTipologie.length === 0) {\n                    mostraMessaggioGmx('Aggiungi almeno una riga al Mix tipologie documento.', 'errore');\n                    return;\n                }\n                if (parametri.distribuzioneScadenze.length === 0) {\n                    mostraMessaggioGmx('Aggiungi almeno una riga alla Distribuzione scadenze pagamento.', 'errore');\n                    return;\n                }\n\n                var righe = generaSetFatture(parametri);\n                ultimoSetGenerato = {\n                    righe: righe,\n                    partitaIvaPropria: document.getElementById('gmx-propria-piva').value.trim(),\n                    denominazionePropria: document.getElementById('gmx-propria-denom').value.trim(),\n                    tipoAnagrafica: document.querySelector('input[name=\"gmx-tipo-anagrafica\"]:checked').value,\n                    aliquotaIva: parseFloat(document.getElementById('gmx-aliquota').value) || 22\n                };\n\n                var corpo = document.getElementById('gmx-anteprima-body');\n                corpo.innerHTML = '';\n                righe.forEach(function (r, indice) {\n                    var tr = document.createElement('tr');\n                    tr.innerHTML =\n                        '<td>' + (indice + 1) + '<\/td>' +\n                        '<td>' + escapeXml(r.tipoDocumento) + '<\/td>' +\n                        '<td>' + formattaDataISO(r.dataEmissione) + '<\/td>' +\n                        '<td>' + formattaDataISO(r.dataScadenza) + '<\/td>' +\n                        '<td>' + (r.tipoDocumento === TIPO_DOC_NOTA_CREDITO ? '-' : '') + formattaImporto(r.importo) + ' EUR<\/td>' +\n                        '<td>' + escapeXml(r.nomeControparte) + '<\/td>';\n                    corpo.appendChild(tr);\n                });\n\n                var nettoGenerato = verificaImponibileNetto(righe);\n                document.getElementById('gmx-verifica-totale').textContent =\n                    'Documenti generati: ' + righe.length + ' - Imponibile netto ottenuto: ' + formattaImporto(nettoGenerato) +\n                    ' EUR (target: ' + formattaImporto(parametri.imponibileTarget) + ' EUR)';\n\n                document.getElementById('gmx-riquadro-anteprima').style.display = 'block';\n                mostraMessaggioGmx('Set generato. Controlla l\\'anteprima prima di scaricare lo ZIP.', 'successo');\n            } catch (erroreGenerico) {\n                mostraMessaggioGmx(erroreGenerico.message, 'errore');\n            }\n        });\n\n        function mostraMessaggioGmx(testo, tipo) {\n            var el = document.getElementById('gmx-messaggio');\n            el.textContent = testo;\n            el.className = 'gf-messaggio ' + (tipo || '');\n        }\n\n        \/\/ ---------- Download ZIP ----------\n        function scaricaBlob(nomeFile, blob) {\n            var url = URL.createObjectURL(blob);\n            var a = document.createElement('a');\n            a.href = url;\n            a.download = nomeFile;\n            document.body.appendChild(a);\n            a.click();\n            document.body.removeChild(a);\n            URL.revokeObjectURL(url);\n        }\n\n        document.getElementById('gmx-btn-scarica-zip').addEventListener('click', async function () {\n            if (!ultimoSetGenerato || !ultimoSetGenerato.righe.length) {\n                mostraMessaggioGmx('Genera prima l\\'anteprima del set.', 'errore');\n                return;\n            }\n            if (!window.JSZip) {\n                mostraMessaggioGmx('La libreria ZIP non si e\\' caricata (verifica la connessione).', 'errore');\n                return;\n            }\n            if (!ultimoSetGenerato.partitaIvaPropria || !ultimoSetGenerato.denominazionePropria) {\n                mostraMessaggioGmx('Inserisci Denominazione e Partita IVA dei dati aziendali propri.', 'errore');\n                return;\n            }\n\n            var zip = new JSZip();\n            ultimoSetGenerato.righe.forEach(function (riga, indice) {\n                var risultato = costruisciXmlMassivo(\n                    riga, indice + 1,\n                    ultimoSetGenerato.partitaIvaPropria,\n                    ultimoSetGenerato.denominazionePropria,\n                    ultimoSetGenerato.tipoAnagrafica,\n                    ultimoSetGenerato.aliquotaIva\n                );\n                zip.file(risultato.nomeFile, risultato.xml);\n            });\n\n            mostraMessaggioGmx('Preparazione ZIP in corso...', '');\n            var blob = await zip.generateAsync({ type: 'blob' });\n            scaricaBlob('fatture_generate_' + ultimoSetGenerato.righe.length + '.zip', blob);\n            mostraMessaggioGmx('ZIP scaricato con ' + ultimoSetGenerato.righe.length + ' fatture.', 'successo');\n        });\n\n        \/\/ ---------- Inizializzazione ----------\n        caricaCedente();\n        document.getElementById('gf-doc-data').value = new Date().toISOString().slice(0, 10);\n        popolaSelectTipoDocumento(document.getElementById('gf-doc-tipo'), 'TD01');\n        popolaSelectTipoDocumento(document.getElementById('gfx-doc-tipo'), 'TD01');\n        popolaSelectTipoDocumento(document.getElementById('gfp-doc-tipo'), 'TD01');\n        aggiungiRiga('', 1, 0, '22.00');\n        aggiungiRigaX('', 1, 0, '22.00');\n        renderizzaTabellaAnagrafiche();\n        aggiornaSelectAnagraficheInGenera();\n\n        \/\/ Genera Massiva: precompila dai dati Cedente gia' salvati, imposta mese\/anno correnti\n        try {\n            var datiCedenteSalvati = localStorage.getItem(CHIAVE_CEDENTE);\n            if (datiCedenteSalvati) {\n                var cedSalvato = JSON.parse(datiCedenteSalvati);\n                document.getElementById('gmx-propria-denom').value = cedSalvato.denom || '';\n                document.getElementById('gmx-propria-piva').value = cedSalvato.piva || '';\n            }\n        } catch (e) { \/* nessun dato salvato, si parte da vuoto *\/ }\n        var oggi = new Date();\n        document.getElementById('gmx-anno').value = oggi.getFullYear();\n        document.getElementById('gmx-mese').value = (oggi.getMonth() + 1).toString();\n        righeMix.push({ id: generaIdUnivoco(), tipoDocumento: 'TD01', numeroDocumenti: 10 });\n        rendMix();\n        righeScadenze.push({ id: generaIdUnivoco(), modalita: 'giorni', giorni: 30, base: 'finemese', numeroDocumenti: 10 });\n        rendScadenze();\n        aggiornaListaContropartiMassiva();\n\n    })();\n    <\/script>\n    \n\n\n\n<p class=\"wp-block-paragraph\"><\/p>\n","protected":false},"excerpt":{"rendered":"","protected":false},"author":1,"featured_media":0,"parent":0,"menu_order":0,"comment_status":"closed","ping_status":"closed","template":"","meta":{"footnotes":""},"class_list":["post-2107","page","type-page","status-publish"],"_links":{"self":[{"href":"https:\/\/www.gestioneindustriale.it\/it_it\/wp-json\/wp\/v2\/pages\/2107","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/www.gestioneindustriale.it\/it_it\/wp-json\/wp\/v2\/pages"}],"about":[{"href":"https:\/\/www.gestioneindustriale.it\/it_it\/wp-json\/wp\/v2\/types\/page"}],"author":[{"embeddable":true,"href":"https:\/\/www.gestioneindustriale.it\/it_it\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/www.gestioneindustriale.it\/it_it\/wp-json\/wp\/v2\/comments?post=2107"}],"version-history":[{"count":1,"href":"https:\/\/www.gestioneindustriale.it\/it_it\/wp-json\/wp\/v2\/pages\/2107\/revisions"}],"predecessor-version":[{"id":2108,"href":"https:\/\/www.gestioneindustriale.it\/it_it\/wp-json\/wp\/v2\/pages\/2107\/revisions\/2108"}],"wp:attachment":[{"href":"https:\/\/www.gestioneindustriale.it\/it_it\/wp-json\/wp\/v2\/media?parent=2107"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}