Genera la tua prima fattura elettronica XML per lo SDI in TypeScript (in 10 minuti)
Se hai mai dovuto integrare la fatturazione elettronica italiana in un progetto Node.js, sai già quanto è scomodo: specifiche FatturaPA di 200 pagine, regole cross-field non documentate, codici errore SDI criptici, e librerie npm o abbandonate o in PHP.
Questo articolo mostra come generare un XML valido per il Sistema di Interscambio (SDI) usando fattura-elettronica-sdi-builder, una libreria TypeScript open-source che copre B2B (FPR12) e Pubblica Amministrazione (FPA12).
Installazione
npm install fattura-elettronica-sdi-builder
Nessuna dipendenza pesante. La validazione è custom e tipizzata, zero runtime esterni.
Il flusso in tre funzioni
La libreria espone tre funzioni pubbliche che si usano sempre in sequenza:
import { applyDefaults, validate, buildXml } from 'fattura-elettronica-sdi-builder';
| Funzione | Input | Output |
|---|---|---|
applyDefaults(input) |
FatturaElettronicaInput (campi deducibili opzionali) |
FatturaElettronica completa |
validate(fattura) |
FatturaElettronica |
Result<void, ValidationError> |
buildXml(fattura, options?) |
FatturaElettronica |
Result<string, BuildError> |
Tutte le funzioni restituiscono un Result<T, E> — mai eccezioni non gestite:
type Result<T, E> =
| { ok: true; value: T }
| { ok: false; error: E }
Esempio completo: fattura B2B con IVA ordinaria
Genera una fattura TD01 da una Srl italiana a un cliente italiano, IVA al 22%, pagamento con bonifico.
import { applyDefaults, validate, buildXml } from 'fattura-elettronica-sdi-builder';
import type { FatturaElettronicaInput } from 'fattura-elettronica-sdi-builder';
import { writeFileSync } from 'fs';
const input: FatturaElettronicaInput = {
FatturaElettronicaHeader: {
DatiTrasmissione: {
ProgressivoInvio: '00001',
CodiceDestinatario: 'ABC1234', // 7 caratteri per FPR12
},
CedentePrestatore: {
DatiAnagrafici: {
IdFiscaleIVA: { IdPaese: 'IT', IdCodice: '01234567890' },
Anagrafica: { Denominazione: 'La Mia Azienda Srl' },
RegimeFiscale: 'RF01',
},
Sede: {
Indirizzo: 'Via Roma 1',
CAP: '00100',
Comune: 'Roma',
Provincia: 'RM',
Nazione: 'IT',
},
},
CessionarioCommittente: {
DatiAnagrafici: {
IdFiscaleIVA: { IdPaese: 'IT', IdCodice: '09876543210' },
Anagrafica: { Denominazione: 'Cliente Spa' },
},
Sede: {
Indirizzo: 'Via Milano 10',
CAP: '20100',
Comune: 'Milano',
Provincia: 'MI',
Nazione: 'IT',
},
},
},
FatturaElettronicaBody: {
DatiGenerali: {
DatiGeneraliDocumento: {
TipoDocumento: 'TD01',
Numero: 'FT-2026-001',
// Divisa e Data sono opzionali: applyDefaults li imposta a 'EUR' e oggi
},
},
DatiBeniServizi: {
DettaglioLinee: [
{
NumeroLinea: 1,
Descrizione: 'Servizio di consulenza',
Quantita: 1,
PrezzoUnitario: 100.00,
PrezzoTotale: 100.00,
AliquotaIVA: 22,
},
],
DatiRiepilogo: [
{
AliquotaIVA: 22,
ImponibileImporto: 100.00,
Imposta: 22.00, // opzionale: applyDefaults lo calcola
EsigibilitaIVA: 'I',
},
],
},
DatiPagamento: [
{
CondizioniPagamento: 'TP02',
DettaglioPagamento: [
{
ModalitaPagamento: 'MP05', // bonifico
DataScadenzaPagamento: '2026-06-18',
ImportoPagamento: 122.00,
IBAN: 'IT60X0542811101000000123456',
},
],
},
],
},
};
// 1. Deduce i valori opzionali
const fattura = applyDefaults(input);
// 2. Valida
const validation = validate(fattura);
if (!validation.ok) {
validation.error.fields.forEach(({ code, field, message }) => {
console.error(`[${code}] ${field}: ${message}`);
});
process.exit(1);
}
// 3. Genera XML
const build = buildXml(fattura, { prettyPrint: true });
if (!build.ok) {
console.error(build.error.message);
process.exit(1);
}
// Naming convention SDI: {IdPaese}{IdCodice}_{Formato}_{Progressivo}.xml
writeFileSync('IT01234567890_FPR12_00001.xml', build.value, 'utf-8');
console.log('Fattura generata.');
Nota: IdTrasmittente e FormatoTrasmissione sono omessi — applyDefaults li deduce automaticamente da CedentePrestatore.
I default che applyDefaults gestisce per te
Uno dei problemi più fastidiosi della FatturaPA è la quantità di campi che si possono dedurre ma che comunque vanno valorizzati nell'XML. applyDefaults li gestisce:
| Campo omesso | Valore dedotto |
|---|---|
IdTrasmittente |
Copiato da CedentePrestatore.DatiAnagrafici.IdFiscaleIVA
|
FormatoTrasmissione |
"FPR12" |
CodiceDestinatario |
"XXXXXXX" se cliente estero |
Divisa |
"EUR" |
Data |
Data odierna YYYY-MM-DD
|
DatiRiepilogo.Imposta |
Calcolato da ImponibileImporto × AliquotaIVA / 100
|
Gestione degli errori SDI
La validazione restituisce errori tipizzati con i codici errore SDI ufficiali. Esempio: se CodiceDestinatario ha lunghezza sbagliata per il formato scelto, o se AliquotaIVA = 0 senza Natura, o se PrezzoTotale non corrisponde al calcolo atteso (SDI 00423):
const validation = validate(fattura);
if (!validation.ok) {
validation.error.fields.forEach(({ code, field, message }) => {
console.error(`[${code}] ${field}: ${message}`);
// Esempio: [MISSING_NATURA] DatiBeniServizi.DettaglioLinee[0].Natura: ...
// Esempio: [INVALID_VALUE] DatiTrasmissione.CodiceDestinatario: ...
});
}
I codici errore sono tipizzati (ErrorCode) — puoi fare switch su di essi senza magic string.
Tre casi d'uso in 30 righe
Fattura PA (FPA12)
DatiTrasmissione: {
FormatoTrasmissione: 'FPA12',
CodiceDestinatario: 'ABCDE1', // 6 caratteri: codice IPA
ProgressivoInvio: '00001',
},
Cliente estero
// CodiceDestinatario deve essere 'XXXXXXX' se cliente non IT
DatiTrasmissione: {
CodiceDestinatario: 'XXXXXXX',
},
CessionarioCommittente: {
DatiAnagrafici: {
IdFiscaleIVA: { IdPaese: 'DE', IdCodice: 'DE123456789' },
Anagrafica: { Denominazione: 'German GmbH' },
},
Sede: { Indirizzo: 'Hauptstrasse 1', CAP: '10115', Comune: 'Berlin', Nazione: 'DE' },
},
Operazione esente IVA (es. prestazione medica)
DettaglioLinee: [{
NumeroLinea: 1,
Descrizione: 'Prestazione medica',
PrezzoUnitario: 200.00,
PrezzoTotale: 200.00,
AliquotaIVA: 0,
Natura: 'N4', // obbligatorio quando AliquotaIVA = 0
}],
DatiRiepilogo: [{
AliquotaIVA: 0,
Natura: 'N4',
ImponibileImporto: 200.00,
Imposta: 0,
RiferimentoNormativo: 'art. 10 DPR 633/72',
}],
Tipi di documento supportati
La libreria copre tutti i TipoDocumento da TD01 a TD29, incluse autofatture (TD16–TD23), fatture differite (TD24/TD25), parcelle con ritenuta (TD06), e fatture semplificate (TD07–TD09). Per ognuno vengono applicate le regole cross-field specifiche delle specifiche SDI v1.7.1.
Link
- npm: https://www.npmjs.com/package/fattura-elettronica-sdi-builder
- GitHub: https://github.com/GiuseppeSerio/fattura-elettronica-sdi-builder
- Licenza MIT — contributi benvenuti
Se lo stai usando o valutando, apri una issue o scrivi a gserio95@gmail.com. Qualsiasi feedback è utile.
Top comments (0)