@dwast/verifactu-lib 0.1.0-beta.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/aeat/client.d.ts +57 -0
- package/dist/aeat/client.d.ts.map +1 -0
- package/dist/aeat/client.js +87 -0
- package/dist/aeat/client.js.map +1 -0
- package/dist/aeat/index.d.ts +2 -0
- package/dist/aeat/index.d.ts.map +1 -0
- package/dist/aeat/index.js +2 -0
- package/dist/aeat/index.js.map +1 -0
- package/dist/core/hash/chain.d.ts +97 -0
- package/dist/core/hash/chain.d.ts.map +1 -0
- package/dist/core/hash/chain.js +115 -0
- package/dist/core/hash/chain.js.map +1 -0
- package/dist/core/hash/index.d.ts +2 -0
- package/dist/core/hash/index.d.ts.map +1 -0
- package/dist/core/hash/index.js +2 -0
- package/dist/core/hash/index.js.map +1 -0
- package/dist/core/index.d.ts +5 -0
- package/dist/core/index.d.ts.map +1 -0
- package/dist/core/index.js +5 -0
- package/dist/core/index.js.map +1 -0
- package/dist/core/models/event.d.ts +25 -0
- package/dist/core/models/event.d.ts.map +1 -0
- package/dist/core/models/event.js +2 -0
- package/dist/core/models/event.js.map +1 -0
- package/dist/core/models/index.d.ts +3 -0
- package/dist/core/models/index.d.ts.map +1 -0
- package/dist/core/models/index.js +3 -0
- package/dist/core/models/index.js.map +1 -0
- package/dist/core/models/invoice.d.ts +90 -0
- package/dist/core/models/invoice.d.ts.map +1 -0
- package/dist/core/models/invoice.js +2 -0
- package/dist/core/models/invoice.js.map +1 -0
- package/dist/core/qr/index.d.ts +2 -0
- package/dist/core/qr/index.d.ts.map +1 -0
- package/dist/core/qr/index.js +2 -0
- package/dist/core/qr/index.js.map +1 -0
- package/dist/core/qr/qr.d.ts +13 -0
- package/dist/core/qr/qr.d.ts.map +1 -0
- package/dist/core/qr/qr.js +16 -0
- package/dist/core/qr/qr.js.map +1 -0
- package/dist/core/xml/generator.d.ts +32 -0
- package/dist/core/xml/generator.d.ts.map +1 -0
- package/dist/core/xml/generator.js +103 -0
- package/dist/core/xml/generator.js.map +1 -0
- package/dist/core/xml/index.d.ts +2 -0
- package/dist/core/xml/index.d.ts.map +1 -0
- package/dist/core/xml/index.js +2 -0
- package/dist/core/xml/index.js.map +1 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +4 -0
- package/dist/index.js.map +1 -0
- package/dist/integration/index.d.ts +3 -0
- package/dist/integration/index.d.ts.map +1 -0
- package/dist/integration/index.js +3 -0
- package/dist/integration/index.js.map +1 -0
- package/dist/integration/repository.d.ts +77 -0
- package/dist/integration/repository.d.ts.map +1 -0
- package/dist/integration/repository.js +2 -0
- package/dist/integration/repository.js.map +1 -0
- package/dist/integration/service.d.ts +36 -0
- package/dist/integration/service.d.ts.map +1 -0
- package/dist/integration/service.js +140 -0
- package/dist/integration/service.js.map +1 -0
- package/package.json +58 -0
- package/readme.md +89 -0
- package/src/aeat/client.ts +137 -0
- package/src/aeat/index.ts +1 -0
- package/src/core/hash/chain.ts +169 -0
- package/src/core/hash/index.ts +1 -0
- package/src/core/index.ts +4 -0
- package/src/core/models/event.ts +25 -0
- package/src/core/models/index.ts +2 -0
- package/src/core/models/invoice.ts +105 -0
- package/src/core/qr/index.ts +1 -0
- package/src/core/qr/qr.ts +19 -0
- package/src/core/xml/generator.ts +133 -0
- package/src/core/xml/index.ts +1 -0
- package/src/index.ts +3 -0
- package/src/integration/index.ts +2 -0
- package/src/integration/repository.ts +86 -0
- package/src/integration/schema.sql +51 -0
- package/src/integration/service.ts +158 -0
|
@@ -0,0 +1,32 @@
|
|
|
1
|
+
import type { Invoice } from '../models/invoice.js';
|
|
2
|
+
/**
|
|
3
|
+
* Datos de la factura que se anula.
|
|
4
|
+
*/
|
|
5
|
+
export interface AnulacionInput {
|
|
6
|
+
/** NIF del emisor de la factura anulada. */
|
|
7
|
+
idEmisorFacturaAnulada: string;
|
|
8
|
+
/** Número y serie de la factura anulada. */
|
|
9
|
+
numSerieFacturaAnulada: string;
|
|
10
|
+
/** Fecha de expedición de la factura anulada. */
|
|
11
|
+
fechaExpedicionFacturaAnulada: string;
|
|
12
|
+
/** Huella (hash) de la factura anulada. */
|
|
13
|
+
huellaFacturaAnulada: string;
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Genera el XML Verifactu de una factura (registro de alta).
|
|
17
|
+
*
|
|
18
|
+
* Estructura basada en el RD 1007/2023 y la documentación técnica de la
|
|
19
|
+
* AEAT. NOTA: el esquema XSD oficial debe validarse antes de producción;
|
|
20
|
+
* este esqueleto refleja los bloques principales (IDFactura, IDEmisorFactura,
|
|
21
|
+
* TipoFactura, TipoDesglose, Desglose, CuotaTotal, Encadenamiento, Huella).
|
|
22
|
+
*/
|
|
23
|
+
export declare function generateInvoiceXml(invoice: Invoice, previousHash: string | null): string;
|
|
24
|
+
/**
|
|
25
|
+
* Genera el XML Verifactu de un registro de anulación.
|
|
26
|
+
*
|
|
27
|
+
* Referencia la factura anulada (IDFactura + Huella) y encadena con el hash
|
|
28
|
+
* anterior. Estructura basada en el RD 1007/2023; validar contra el XSD
|
|
29
|
+
* oficial antes de producción.
|
|
30
|
+
*/
|
|
31
|
+
export declare function generateAnulacionXml(anulacion: AnulacionInput, previousHash: string | null): string;
|
|
32
|
+
//# sourceMappingURL=generator.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"generator.d.ts","sourceRoot":"","sources":["../../../src/core/xml/generator.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,sBAAsB,CAAC;AAEpD;;GAEG;AACH,MAAM,WAAW,cAAc;IAC7B,4CAA4C;IAC5C,sBAAsB,EAAE,MAAM,CAAC;IAC/B,4CAA4C;IAC5C,sBAAsB,EAAE,MAAM,CAAC;IAC/B,iDAAiD;IACjD,6BAA6B,EAAE,MAAM,CAAC;IACtC,2CAA2C;IAC3C,oBAAoB,EAAE,MAAM,CAAC;CAC9B;AAqBD;;;;;;;GAOG;AACH,wBAAgB,kBAAkB,CAAC,OAAO,EAAE,OAAO,EAAE,YAAY,EAAE,MAAM,GAAG,IAAI,GAAG,MAAM,CAwDxF;AAED;;;;;;GAMG;AACH,wBAAgB,oBAAoB,CAClC,SAAS,EAAE,cAAc,EACzB,YAAY,EAAE,MAAM,GAAG,IAAI,GAC1B,MAAM,CAqBR"}
|
|
@@ -0,0 +1,103 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Escapa caracteres especiales para XML.
|
|
3
|
+
*/
|
|
4
|
+
function escapeXml(value) {
|
|
5
|
+
return value
|
|
6
|
+
.replace(/&/g, '&')
|
|
7
|
+
.replace(/</g, '<')
|
|
8
|
+
.replace(/>/g, '>')
|
|
9
|
+
.replace(/"/g, '"')
|
|
10
|
+
.replace(/'/g, ''');
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* Formatea un número con 2 decimales (formato Verifactu).
|
|
14
|
+
*/
|
|
15
|
+
function formatAmount(value) {
|
|
16
|
+
return value.toFixed(2);
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Genera el XML Verifactu de una factura (registro de alta).
|
|
20
|
+
*
|
|
21
|
+
* Estructura basada en el RD 1007/2023 y la documentación técnica de la
|
|
22
|
+
* AEAT. NOTA: el esquema XSD oficial debe validarse antes de producción;
|
|
23
|
+
* este esqueleto refleja los bloques principales (IDFactura, IDEmisorFactura,
|
|
24
|
+
* TipoFactura, TipoDesglose, Desglose, CuotaTotal, Encadenamiento, Huella).
|
|
25
|
+
*/
|
|
26
|
+
export function generateInvoiceXml(invoice, previousHash) {
|
|
27
|
+
const { id, emisor, desglose, tipoFactura, tipoDesglose } = invoice;
|
|
28
|
+
const detalles = desglose.tipoOperaciones
|
|
29
|
+
.map((op) => {
|
|
30
|
+
const iva = op.detallesIva
|
|
31
|
+
.map((d) => ` <DetalleIVA>` +
|
|
32
|
+
`<TipoImpositivo>${formatAmount(d.tipoImpositivo)}</TipoImpositivo>` +
|
|
33
|
+
`<BaseImponible>${formatAmount(d.baseImponible)}</BaseImponible>` +
|
|
34
|
+
`<CuotaRepercutida>${formatAmount(d.cuotaRepercutida)}</CuotaRepercutida>` +
|
|
35
|
+
`</DetalleIVA>`)
|
|
36
|
+
.join('\n');
|
|
37
|
+
return (` <DesgloseTipoOperacion>` +
|
|
38
|
+
`<TipoOperacion>${escapeXml(op.tipoOperacion)}</TipoOperacion>` +
|
|
39
|
+
`\n <DesgloseIVA>\n${iva}\n </DesgloseIVA>` +
|
|
40
|
+
`\n </DesgloseTipoOperacion>`);
|
|
41
|
+
})
|
|
42
|
+
.join('\n');
|
|
43
|
+
const xml = [
|
|
44
|
+
'<?xml version="1.0" encoding="UTF-8"?>',
|
|
45
|
+
'<RegistroFactura>',
|
|
46
|
+
' <IDFactura>',
|
|
47
|
+
' <IDEmisorFactura>',
|
|
48
|
+
` <NIF>${escapeXml(emisor.nif)}</NIF>`,
|
|
49
|
+
` <NombreRazon>${escapeXml(emisor.nombreRazon)}</NombreRazon>`,
|
|
50
|
+
emisor.pais ? ` <Pais>${escapeXml(emisor.pais)}</Pais>` : '',
|
|
51
|
+
emisor.codigoPostal ? ` <CodigoPostal>${escapeXml(emisor.codigoPostal)}</CodigoPostal>` : '',
|
|
52
|
+
emisor.municipio ? ` <Municipio>${escapeXml(emisor.municipio)}</Municipio>` : '',
|
|
53
|
+
emisor.direccion ? ` <Direccion>${escapeXml(emisor.direccion)}</Direccion>` : '',
|
|
54
|
+
' </IDEmisorFactura>',
|
|
55
|
+
id.serie ? ` <SerieFactura>${escapeXml(id.serie)}</SerieFactura>` : '',
|
|
56
|
+
` <NumeroFactura>${escapeXml(id.numero)}</NumeroFactura>`,
|
|
57
|
+
` <FechaExpedicionFactura>${id.fechaExpedicion}</FechaExpedicionFactura>`,
|
|
58
|
+
id.fechaOperacion ? ` <FechaOperacion>${id.fechaOperacion}</FechaOperacion>` : '',
|
|
59
|
+
' </IDFactura>',
|
|
60
|
+
` <TipoFactura>${tipoFactura}</TipoFactura>`,
|
|
61
|
+
` <TipoDesglose>${tipoDesglose}</TipoDesglose>`,
|
|
62
|
+
' <Desglose>',
|
|
63
|
+
detalles,
|
|
64
|
+
' </Desglose>',
|
|
65
|
+
` <CuotaTotal>${formatAmount(desglose.cuotaTotal)}</CuotaTotal>`,
|
|
66
|
+
' <Encadenamiento>',
|
|
67
|
+
` <HashAnterior>${previousHash ?? ''}</HashAnterior>`,
|
|
68
|
+
' </Encadenamiento>',
|
|
69
|
+
'</RegistroFactura>',
|
|
70
|
+
]
|
|
71
|
+
.filter((line) => line !== '')
|
|
72
|
+
.join('\n');
|
|
73
|
+
return xml;
|
|
74
|
+
}
|
|
75
|
+
/**
|
|
76
|
+
* Genera el XML Verifactu de un registro de anulación.
|
|
77
|
+
*
|
|
78
|
+
* Referencia la factura anulada (IDFactura + Huella) y encadena con el hash
|
|
79
|
+
* anterior. Estructura basada en el RD 1007/2023; validar contra el XSD
|
|
80
|
+
* oficial antes de producción.
|
|
81
|
+
*/
|
|
82
|
+
export function generateAnulacionXml(anulacion, previousHash) {
|
|
83
|
+
const xml = [
|
|
84
|
+
'<?xml version="1.0" encoding="UTF-8"?>',
|
|
85
|
+
'<RegistroFactura>',
|
|
86
|
+
' <RegistroAnulacion>',
|
|
87
|
+
' <IDFactura>',
|
|
88
|
+
' <IDEmisorFactura>',
|
|
89
|
+
` <NIF>${escapeXml(anulacion.idEmisorFacturaAnulada)}</NIF>`,
|
|
90
|
+
' </IDEmisorFactura>',
|
|
91
|
+
` <NumeroFactura>${escapeXml(anulacion.numSerieFacturaAnulada)}</NumeroFactura>`,
|
|
92
|
+
` <FechaExpedicionFactura>${anulacion.fechaExpedicionFacturaAnulada}</FechaExpedicionFactura>`,
|
|
93
|
+
' </IDFactura>',
|
|
94
|
+
` <Huella>${anulacion.huellaFacturaAnulada}</Huella>`,
|
|
95
|
+
' </RegistroAnulacion>',
|
|
96
|
+
' <Encadenamiento>',
|
|
97
|
+
` <HashAnterior>${previousHash ?? ''}</HashAnterior>`,
|
|
98
|
+
' </Encadenamiento>',
|
|
99
|
+
'</RegistroFactura>',
|
|
100
|
+
].join('\n');
|
|
101
|
+
return xml;
|
|
102
|
+
}
|
|
103
|
+
//# sourceMappingURL=generator.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"generator.js","sourceRoot":"","sources":["../../../src/core/xml/generator.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../../src/core/xml/index.ts"],"names":[],"mappings":"AAAA,cAAc,gBAAgB,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../../src/core/xml/index.ts"],"names":[],"mappings":"AAAA,cAAc,gBAAgB,CAAC"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,iBAAiB,CAAC;AAChC,cAAc,iBAAiB,CAAC;AAChC,cAAc,wBAAwB,CAAC"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,cAAc,iBAAiB,CAAC;AAChC,cAAc,iBAAiB,CAAC;AAChC,cAAc,wBAAwB,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/integration/index.ts"],"names":[],"mappings":"AAAA,cAAc,iBAAiB,CAAC;AAChC,cAAc,cAAc,CAAC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/integration/index.ts"],"names":[],"mappings":"AAAA,cAAc,iBAAiB,CAAC;AAChC,cAAc,cAAc,CAAC"}
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import type { Event } from '../core/models/event.js';
|
|
2
|
+
/**
|
|
3
|
+
* Estado de un registro de facturación en el sistema consumidor.
|
|
4
|
+
*/
|
|
5
|
+
export type RecordStatus = 'PENDING' | 'SENT' | 'CONFIRMED' | 'REJECTED' | 'ANNULLED';
|
|
6
|
+
/**
|
|
7
|
+
* Registro de facturación persistido en el sistema consumidor.
|
|
8
|
+
*/
|
|
9
|
+
export interface StoredRecord {
|
|
10
|
+
/** Identificador interno del registro. */
|
|
11
|
+
id: string;
|
|
12
|
+
/** Referencia a la factura (serie + número). */
|
|
13
|
+
invoiceId: string;
|
|
14
|
+
/** XML Verifactu generado. */
|
|
15
|
+
xml: string;
|
|
16
|
+
/** Hash del propio registro (huella). */
|
|
17
|
+
hash: string;
|
|
18
|
+
/** Hash del registro anterior en la cadena. */
|
|
19
|
+
previousHash: string | null;
|
|
20
|
+
/** Payload del QR Verifactu (URL de verificación con el hash). */
|
|
21
|
+
qrPayload?: string;
|
|
22
|
+
/** Estado del registro. */
|
|
23
|
+
status: RecordStatus;
|
|
24
|
+
/** Fecha/hora de creación. */
|
|
25
|
+
createdAt: string;
|
|
26
|
+
/** Fecha/hora de última actualización. */
|
|
27
|
+
updatedAt: string;
|
|
28
|
+
}
|
|
29
|
+
/**
|
|
30
|
+
* Respuesta de AEAT persistida en el sistema consumidor.
|
|
31
|
+
*
|
|
32
|
+
* La AEAT exige conservar la respuesta de cada envío para poder acreditar
|
|
33
|
+
* la remisión. El sistema consumidor DEBE guardar estas respuestas en una
|
|
34
|
+
* tabla (ver `schema.sql`).
|
|
35
|
+
*/
|
|
36
|
+
export interface StoredResponse {
|
|
37
|
+
/** Identificador interno de la respuesta. */
|
|
38
|
+
id: string;
|
|
39
|
+
/** Referencia al registro enviado. */
|
|
40
|
+
recordId: string;
|
|
41
|
+
/** Identificador de la operación devuelto por AEAT. */
|
|
42
|
+
operationId: string;
|
|
43
|
+
/** Estado devuelto por AEAT. */
|
|
44
|
+
status: 'OK' | 'ERROR';
|
|
45
|
+
/** Mensaje de AEAT. */
|
|
46
|
+
message?: string;
|
|
47
|
+
/** Código de error de AEAT (si lo hay). */
|
|
48
|
+
errorCode?: string;
|
|
49
|
+
/** Cuerpo completo de la respuesta (para auditoría). */
|
|
50
|
+
rawResponse?: string;
|
|
51
|
+
/** Fecha/hora de la respuesta. */
|
|
52
|
+
receivedAt: string;
|
|
53
|
+
}
|
|
54
|
+
/**
|
|
55
|
+
* Repositorio que el sistema consumidor DEBE implementar para persistir
|
|
56
|
+
* los registros y las respuestas de Verifactu.
|
|
57
|
+
*
|
|
58
|
+
* La librería no impone un ORM concreto (Mongoose, Prisma, etc.): define
|
|
59
|
+
* esta interfaz y el sistema consumidor la adapta a su stack MERN.
|
|
60
|
+
*/
|
|
61
|
+
export interface VerifactuRepository {
|
|
62
|
+
/** Guarda un registro de facturación. */
|
|
63
|
+
saveRecord(record: StoredRecord): Promise<void>;
|
|
64
|
+
/** Actualiza el estado de un registro. */
|
|
65
|
+
updateRecordStatus(id: string, status: RecordStatus): Promise<void>;
|
|
66
|
+
/** Obtiene un registro por su id. */
|
|
67
|
+
getRecord(id: string): Promise<StoredRecord | null>;
|
|
68
|
+
/** Obtiene el último registro (para conocer el hash anterior). */
|
|
69
|
+
getLastRecord(): Promise<StoredRecord | null>;
|
|
70
|
+
/** Guarda la respuesta de AEAT. */
|
|
71
|
+
saveResponse(response: StoredResponse): Promise<void>;
|
|
72
|
+
/** Obtiene las respuestas de un registro. */
|
|
73
|
+
getResponses(recordId: string): Promise<StoredResponse[]>;
|
|
74
|
+
/** Guarda un evento. */
|
|
75
|
+
saveEvent(event: Event): Promise<void>;
|
|
76
|
+
}
|
|
77
|
+
//# sourceMappingURL=repository.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"repository.d.ts","sourceRoot":"","sources":["../../src/integration/repository.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,KAAK,EAAE,MAAM,yBAAyB,CAAC;AAErD;;GAEG;AACH,MAAM,MAAM,YAAY,GACpB,SAAS,GACT,MAAM,GACN,WAAW,GACX,UAAU,GACV,UAAU,CAAC;AAEf;;GAEG;AACH,MAAM,WAAW,YAAY;IAC3B,0CAA0C;IAC1C,EAAE,EAAE,MAAM,CAAC;IACX,gDAAgD;IAChD,SAAS,EAAE,MAAM,CAAC;IAClB,8BAA8B;IAC9B,GAAG,EAAE,MAAM,CAAC;IACZ,yCAAyC;IACzC,IAAI,EAAE,MAAM,CAAC;IACb,+CAA+C;IAC/C,YAAY,EAAE,MAAM,GAAG,IAAI,CAAC;IAC5B,kEAAkE;IAClE,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,2BAA2B;IAC3B,MAAM,EAAE,YAAY,CAAC;IACrB,8BAA8B;IAC9B,SAAS,EAAE,MAAM,CAAC;IAClB,0CAA0C;IAC1C,SAAS,EAAE,MAAM,CAAC;CACnB;AAED;;;;;;GAMG;AACH,MAAM,WAAW,cAAc;IAC7B,6CAA6C;IAC7C,EAAE,EAAE,MAAM,CAAC;IACX,sCAAsC;IACtC,QAAQ,EAAE,MAAM,CAAC;IACjB,uDAAuD;IACvD,WAAW,EAAE,MAAM,CAAC;IACpB,gCAAgC;IAChC,MAAM,EAAE,IAAI,GAAG,OAAO,CAAC;IACvB,uBAAuB;IACvB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,2CAA2C;IAC3C,SAAS,CAAC,EAAE,MAAM,CAAC;IACnB,wDAAwD;IACxD,WAAW,CAAC,EAAE,MAAM,CAAC;IACrB,kCAAkC;IAClC,UAAU,EAAE,MAAM,CAAC;CACpB;AAED;;;;;;GAMG;AACH,MAAM,WAAW,mBAAmB;IAClC,yCAAyC;IACzC,UAAU,CAAC,MAAM,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IAChD,0CAA0C;IAC1C,kBAAkB,CAAC,EAAE,EAAE,MAAM,EAAE,MAAM,EAAE,YAAY,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACpE,qCAAqC;IACrC,SAAS,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,GAAG,IAAI,CAAC,CAAC;IACpD,kEAAkE;IAClE,aAAa,IAAI,OAAO,CAAC,YAAY,GAAG,IAAI,CAAC,CAAC;IAC9C,mCAAmC;IACnC,YAAY,CAAC,QAAQ,EAAE,cAAc,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;IACtD,6CAA6C;IAC7C,YAAY,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,cAAc,EAAE,CAAC,CAAC;IAC1D,wBAAwB;IACxB,SAAS,CAAC,KAAK,EAAE,KAAK,GAAG,OAAO,CAAC,IAAI,CAAC,CAAC;CACxC"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"repository.js","sourceRoot":"","sources":["../../src/integration/repository.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
import type { Invoice } from '../core/models/invoice.js';
|
|
2
|
+
import type { VerifactuClient } from '../aeat/client.js';
|
|
3
|
+
import type { VerifactuRepository, StoredRecord } from './repository.js';
|
|
4
|
+
/**
|
|
5
|
+
* Servicio de orquestación de Verifactu.
|
|
6
|
+
*
|
|
7
|
+
* Une el núcleo puro (generación XML + hash) con el cliente AEAT y la
|
|
8
|
+
* persistencia. El sistema consumidor DEBE proporcionar un repositorio que
|
|
9
|
+
* implemente `VerifactuRepository` (ver `repository.ts` y `schema.sql`).
|
|
10
|
+
*/
|
|
11
|
+
export declare class VerifactuService {
|
|
12
|
+
private readonly repository;
|
|
13
|
+
private readonly client;
|
|
14
|
+
constructor(repository: VerifactuRepository, client: VerifactuClient);
|
|
15
|
+
/**
|
|
16
|
+
* Registra una factura: genera el XML, calcula el hash encadenado, guarda
|
|
17
|
+
* el registro y lo envía a AEAT, persistiendo la respuesta.
|
|
18
|
+
*/
|
|
19
|
+
registerInvoice(invoice: Invoice): Promise<StoredRecord>;
|
|
20
|
+
/**
|
|
21
|
+
* Anula una factura previamente registrada.
|
|
22
|
+
*
|
|
23
|
+
* Genera un registro de anulación que referencia la factura anulada,
|
|
24
|
+
* lo envía a AEAT y marca la factura original como ANNULLED.
|
|
25
|
+
*/
|
|
26
|
+
annulInvoice(invoiceId: string): Promise<StoredRecord>;
|
|
27
|
+
/**
|
|
28
|
+
* Modifica una factura previamente registrada.
|
|
29
|
+
*
|
|
30
|
+
* Verifactu no tiene un tipo "modificación": se implementa como anulación
|
|
31
|
+
* del registro original + alta con los datos corregidos. El usuario solo
|
|
32
|
+
* envía la referencia a la factura original y la factura corregida.
|
|
33
|
+
*/
|
|
34
|
+
modifyInvoice(invoiceId: string, correctedInvoice: Invoice): Promise<StoredRecord>;
|
|
35
|
+
}
|
|
36
|
+
//# sourceMappingURL=service.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"service.d.ts","sourceRoot":"","sources":["../../src/integration/service.ts"],"names":[],"mappings":"AAAA,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,2BAA2B,CAAC;AAIzD,OAAO,KAAK,EAAE,eAAe,EAAE,MAAM,mBAAmB,CAAC;AACzD,OAAO,KAAK,EAAE,mBAAmB,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAEzE;;;;;;GAMG;AACH,qBAAa,gBAAgB;IAEzB,OAAO,CAAC,QAAQ,CAAC,UAAU;IAC3B,OAAO,CAAC,QAAQ,CAAC,MAAM;gBADN,UAAU,EAAE,mBAAmB,EAC/B,MAAM,EAAE,eAAe;IAG1C;;;OAGG;IACG,eAAe,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,CAAC,YAAY,CAAC;IAmD9D;;;;;OAKG;IACG,YAAY,CAAC,SAAS,EAAE,MAAM,GAAG,OAAO,CAAC,YAAY,CAAC;IA8D5D;;;;;;OAMG;IACG,aAAa,CAAC,SAAS,EAAE,MAAM,EAAE,gBAAgB,EAAE,OAAO,GAAG,OAAO,CAAC,YAAY,CAAC;CAOzF"}
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
import { generateInvoiceXml, generateAnulacionXml } from '../core/xml/generator.js';
|
|
2
|
+
import { computeRegistroAlta, computeRegistroAnulacion } from '../core/hash/chain.js';
|
|
3
|
+
import { buildQrPayload } from '../core/qr/qr.js';
|
|
4
|
+
/**
|
|
5
|
+
* Servicio de orquestación de Verifactu.
|
|
6
|
+
*
|
|
7
|
+
* Une el núcleo puro (generación XML + hash) con el cliente AEAT y la
|
|
8
|
+
* persistencia. El sistema consumidor DEBE proporcionar un repositorio que
|
|
9
|
+
* implemente `VerifactuRepository` (ver `repository.ts` y `schema.sql`).
|
|
10
|
+
*/
|
|
11
|
+
export class VerifactuService {
|
|
12
|
+
repository;
|
|
13
|
+
client;
|
|
14
|
+
constructor(repository, client) {
|
|
15
|
+
this.repository = repository;
|
|
16
|
+
this.client = client;
|
|
17
|
+
}
|
|
18
|
+
/**
|
|
19
|
+
* Registra una factura: genera el XML, calcula el hash encadenado, guarda
|
|
20
|
+
* el registro y lo envía a AEAT, persistiendo la respuesta.
|
|
21
|
+
*/
|
|
22
|
+
async registerInvoice(invoice) {
|
|
23
|
+
// 1. Obtener el hash del último registro para encadenar.
|
|
24
|
+
const last = await this.repository.getLastRecord();
|
|
25
|
+
const previousHash = last?.hash ?? null;
|
|
26
|
+
// 2. Generar XML y calcular la huella (hash) del registro.
|
|
27
|
+
const xml = generateInvoiceXml(invoice, previousHash);
|
|
28
|
+
const hash = computeRegistroAlta({
|
|
29
|
+
idEmisorFactura: invoice.emisor.nif,
|
|
30
|
+
numSerieFactura: `${invoice.id.serie ?? ''}${invoice.id.numero}`,
|
|
31
|
+
fechaExpedicionFactura: invoice.id.fechaExpedicion,
|
|
32
|
+
tipoFactura: invoice.tipoFactura,
|
|
33
|
+
cuotaTotal: invoice.desglose.cuotaTotal.toFixed(2),
|
|
34
|
+
importeTotal: invoice.desglose.importeTotal.toFixed(2),
|
|
35
|
+
huellaAnterior: previousHash,
|
|
36
|
+
fechaHoraHusoGenRegistro: new Date().toISOString(),
|
|
37
|
+
});
|
|
38
|
+
// 3. Persistir el registro como PENDING.
|
|
39
|
+
const now = new Date().toISOString();
|
|
40
|
+
const record = {
|
|
41
|
+
id: `${invoice.id.serie ?? ''}${invoice.id.numero}`,
|
|
42
|
+
invoiceId: `${invoice.id.serie ?? ''}${invoice.id.numero}`,
|
|
43
|
+
xml,
|
|
44
|
+
hash,
|
|
45
|
+
previousHash,
|
|
46
|
+
qrPayload: buildQrPayload(hash),
|
|
47
|
+
status: 'PENDING',
|
|
48
|
+
createdAt: now,
|
|
49
|
+
updatedAt: now,
|
|
50
|
+
};
|
|
51
|
+
await this.repository.saveRecord(record);
|
|
52
|
+
// 4. Enviar a AEAT y persistir la respuesta.
|
|
53
|
+
const result = await this.client.sendRecord(xml);
|
|
54
|
+
await this.repository.saveResponse({
|
|
55
|
+
id: `${record.id}-${Date.now()}`,
|
|
56
|
+
recordId: record.id,
|
|
57
|
+
operationId: result.operationId,
|
|
58
|
+
status: result.status,
|
|
59
|
+
message: result.message,
|
|
60
|
+
receivedAt: new Date().toISOString(),
|
|
61
|
+
});
|
|
62
|
+
// 5. Actualizar el estado del registro según la respuesta.
|
|
63
|
+
const status = result.status === 'OK' ? 'CONFIRMED' : 'REJECTED';
|
|
64
|
+
await this.repository.updateRecordStatus(record.id, status);
|
|
65
|
+
return { ...record, status };
|
|
66
|
+
}
|
|
67
|
+
/**
|
|
68
|
+
* Anula una factura previamente registrada.
|
|
69
|
+
*
|
|
70
|
+
* Genera un registro de anulación que referencia la factura anulada,
|
|
71
|
+
* lo envía a AEAT y marca la factura original como ANNULLED.
|
|
72
|
+
*/
|
|
73
|
+
async annulInvoice(invoiceId) {
|
|
74
|
+
// 1. Obtener la factura original (para su huella) y el último registro.
|
|
75
|
+
const original = await this.repository.getRecord(invoiceId);
|
|
76
|
+
if (!original) {
|
|
77
|
+
throw new Error(`No existe el registro a anular: ${invoiceId}`);
|
|
78
|
+
}
|
|
79
|
+
const last = await this.repository.getLastRecord();
|
|
80
|
+
const previousHash = last?.hash ?? null;
|
|
81
|
+
// 2. Generar XML de anulación y calcular la huella.
|
|
82
|
+
const xml = generateAnulacionXml({
|
|
83
|
+
idEmisorFacturaAnulada: original.invoiceId,
|
|
84
|
+
numSerieFacturaAnulada: original.invoiceId,
|
|
85
|
+
fechaExpedicionFacturaAnulada: new Date().toISOString().slice(0, 10),
|
|
86
|
+
huellaFacturaAnulada: original.hash,
|
|
87
|
+
}, previousHash);
|
|
88
|
+
const hash = computeRegistroAnulacion({
|
|
89
|
+
idEmisorFacturaAnulada: original.invoiceId,
|
|
90
|
+
numSerieFacturaAnulada: original.invoiceId,
|
|
91
|
+
fechaExpedicionFacturaAnulada: new Date().toISOString().slice(0, 10),
|
|
92
|
+
huellaAnterior: previousHash,
|
|
93
|
+
fechaHoraHusoGenRegistro: new Date().toISOString(),
|
|
94
|
+
});
|
|
95
|
+
// 3. Persistir el registro de anulación como PENDING.
|
|
96
|
+
const now = new Date().toISOString();
|
|
97
|
+
const record = {
|
|
98
|
+
id: `${invoiceId}-ANUL`,
|
|
99
|
+
invoiceId,
|
|
100
|
+
xml,
|
|
101
|
+
hash,
|
|
102
|
+
previousHash,
|
|
103
|
+
status: 'PENDING',
|
|
104
|
+
createdAt: now,
|
|
105
|
+
updatedAt: now,
|
|
106
|
+
};
|
|
107
|
+
await this.repository.saveRecord(record);
|
|
108
|
+
// 4. Enviar a AEAT y persistir la respuesta.
|
|
109
|
+
const result = await this.client.sendRecord(xml);
|
|
110
|
+
await this.repository.saveResponse({
|
|
111
|
+
id: `${record.id}-${Date.now()}`,
|
|
112
|
+
recordId: record.id,
|
|
113
|
+
operationId: result.operationId,
|
|
114
|
+
status: result.status,
|
|
115
|
+
message: result.message,
|
|
116
|
+
receivedAt: new Date().toISOString(),
|
|
117
|
+
});
|
|
118
|
+
// 5. Actualizar estados.
|
|
119
|
+
const status = result.status === 'OK' ? 'CONFIRMED' : 'REJECTED';
|
|
120
|
+
await this.repository.updateRecordStatus(record.id, status);
|
|
121
|
+
if (result.status === 'OK') {
|
|
122
|
+
await this.repository.updateRecordStatus(invoiceId, 'ANNULLED');
|
|
123
|
+
}
|
|
124
|
+
return { ...record, status };
|
|
125
|
+
}
|
|
126
|
+
/**
|
|
127
|
+
* Modifica una factura previamente registrada.
|
|
128
|
+
*
|
|
129
|
+
* Verifactu no tiene un tipo "modificación": se implementa como anulación
|
|
130
|
+
* del registro original + alta con los datos corregidos. El usuario solo
|
|
131
|
+
* envía la referencia a la factura original y la factura corregida.
|
|
132
|
+
*/
|
|
133
|
+
async modifyInvoice(invoiceId, correctedInvoice) {
|
|
134
|
+
// 1. Anular la factura original.
|
|
135
|
+
await this.annulInvoice(invoiceId);
|
|
136
|
+
// 2. Emitir la factura corregida como un nuevo alta.
|
|
137
|
+
return this.registerInvoice(correctedInvoice);
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
//# sourceMappingURL=service.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"service.js","sourceRoot":"","sources":["../../src/integration/service.ts"],"names":[],"mappings":"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"}
|
package/package.json
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@dwast/verifactu-lib",
|
|
3
|
+
"version": "0.1.0-beta.0",
|
|
4
|
+
"description": "Librería para la gestión de Verifactu (RD 1007/2023): modelo de datos, encadenado hash, generación XML, QR y cliente AEAT.",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "./dist/index.js",
|
|
7
|
+
"types": "./dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"types": "./dist/index.d.ts",
|
|
11
|
+
"import": "./dist/index.js"
|
|
12
|
+
},
|
|
13
|
+
"./core": {
|
|
14
|
+
"types": "./dist/core/index.d.ts",
|
|
15
|
+
"import": "./dist/core/index.js"
|
|
16
|
+
},
|
|
17
|
+
"./aeat": {
|
|
18
|
+
"types": "./dist/aeat/index.d.ts",
|
|
19
|
+
"import": "./dist/aeat/index.js"
|
|
20
|
+
},
|
|
21
|
+
"./integration": {
|
|
22
|
+
"types": "./dist/integration/index.d.ts",
|
|
23
|
+
"import": "./dist/integration/index.js"
|
|
24
|
+
}
|
|
25
|
+
},
|
|
26
|
+
"files": [
|
|
27
|
+
"dist",
|
|
28
|
+
"src"
|
|
29
|
+
],
|
|
30
|
+
"scripts": {
|
|
31
|
+
"build": "tsc -p tsconfig.json",
|
|
32
|
+
"typecheck": "tsc -p tsconfig.json --noEmit",
|
|
33
|
+
"test": "vitest run",
|
|
34
|
+
"test:watch": "vitest",
|
|
35
|
+
"lint": "eslint src test",
|
|
36
|
+
"prepublishOnly": "npm run build && npm test"
|
|
37
|
+
},
|
|
38
|
+
"keywords": [
|
|
39
|
+
"verifactu",
|
|
40
|
+
"aeat",
|
|
41
|
+
"factura",
|
|
42
|
+
"facturacion",
|
|
43
|
+
"hash",
|
|
44
|
+
"qr"
|
|
45
|
+
],
|
|
46
|
+
"license": "MIT",
|
|
47
|
+
"publishConfig": {
|
|
48
|
+
"access": "public"
|
|
49
|
+
},
|
|
50
|
+
"engines": {
|
|
51
|
+
"node": ">=18"
|
|
52
|
+
},
|
|
53
|
+
"devDependencies": {
|
|
54
|
+
"@types/node": "^20.11.0",
|
|
55
|
+
"typescript": "^5.4.0",
|
|
56
|
+
"vitest": "^1.6.0"
|
|
57
|
+
}
|
|
58
|
+
}
|
package/readme.md
ADDED
|
@@ -0,0 +1,89 @@
|
|
|
1
|
+
# @dwast/verifactu-lib
|
|
2
|
+
|
|
3
|
+
Librería privada (npm) para la gestión de **Verifactu** (Ley 18/2022 y RD 1007/2023) en un stack MERN (JS/TS).
|
|
4
|
+
|
|
5
|
+
## ¿Qué resuelve?
|
|
6
|
+
|
|
7
|
+
Verifactu no es solo "endpoints": el núcleo es la generación de facturas en formato XML con los campos obligatorios, el **encadenado hash** (SHA-256) que garantiza integridad e inmutabilidad, el **QR Verifactu** y el registro de eventos. La comunicación con AEAT es solo una parte.
|
|
8
|
+
|
|
9
|
+
La librería se divide en tres capas:
|
|
10
|
+
|
|
11
|
+
- **`core`** — Núcleo puro, sin dependencias de red ni de framework. Fácil de testear y de usar en cualquier parte del stack.
|
|
12
|
+
- **`aeat`** — Cliente para los servicios de AEAT (depende de credenciales y entornos).
|
|
13
|
+
- **`integration`** — Capa de orquestación que define los **requisitos del sistema consumidor** (persistencia de registros y respuestas en tablas) y une núcleo + AEAT.
|
|
14
|
+
|
|
15
|
+
## Estructura
|
|
16
|
+
|
|
17
|
+
```
|
|
18
|
+
src/
|
|
19
|
+
├── core/
|
|
20
|
+
│ ├── models/ # Tipos: Invoice, Emisor, Desglose, Event, etc.
|
|
21
|
+
│ ├── hash/ # Encadenado hash (SHA-256)
|
|
22
|
+
│ ├── xml/ # Generación XML Verifactu (RegistroFactura)
|
|
23
|
+
│ └── qr/ # Generación QR Verifactu
|
|
24
|
+
├── aeat/ # Cliente de servicios AEAT
|
|
25
|
+
├── integration/ # Repositorio, servicio de orquestación y schema.sql
|
|
26
|
+
└── index.ts
|
|
27
|
+
```
|
|
28
|
+
|
|
29
|
+
## Requisitos del sistema consumidor
|
|
30
|
+
|
|
31
|
+
La capa `integration` define qué debe aportar el sistema que use la librería:
|
|
32
|
+
|
|
33
|
+
1. **Persistencia de registros y respuestas.** El sistema DEBE implementar la interfaz `VerifactuRepository` (ver `src/integration/repository.ts`) y disponer de las tablas de `src/integration/schema.sql` (`verifactu_records`, `verifactu_responses`, `verifactu_events`). La AEAT exige conservar la respuesta de cada envío para acreditar la remisión.
|
|
34
|
+
2. **Cliente AEAT configurado.** El sistema DEBE proporcionar un `VerifactuClient` con las credenciales y el entorno adecuados.
|
|
35
|
+
|
|
36
|
+
### Ejemplo de uso
|
|
37
|
+
|
|
38
|
+
```ts
|
|
39
|
+
import { VerifactuService } from '@dwast/verifactu-lib/integration';
|
|
40
|
+
import { VerifactuClient } from '@dwast/verifactu-lib/aeat';
|
|
41
|
+
import { MyMongoRepository } from './my-repo'; // implementa VerifactuRepository
|
|
42
|
+
|
|
43
|
+
const repo = new MyMongoRepository();
|
|
44
|
+
const client = new VerifactuClient({ baseUrl: 'https://...', authToken: '...' });
|
|
45
|
+
const service = new VerifactuService(repo, client);
|
|
46
|
+
|
|
47
|
+
await service.registerInvoice({
|
|
48
|
+
id: { serie: 'A', numero: '2025-001', fechaExpedicion: '2025-07-01' },
|
|
49
|
+
emisor: { nif: 'B12345678', nombreRazon: 'Empresa S.L.' },
|
|
50
|
+
tipoFactura: 'F1',
|
|
51
|
+
tipoDesglose: 'S1',
|
|
52
|
+
desglose: {
|
|
53
|
+
tipoOperaciones: [
|
|
54
|
+
{ tipoOperacion: '01', detallesIva: [{ tipoImpositivo: 21, baseImponible: 100, cuotaRepercutida: 21 }] },
|
|
55
|
+
],
|
|
56
|
+
cuotaTotal: 21,
|
|
57
|
+
importeTotal: 121,
|
|
58
|
+
},
|
|
59
|
+
});
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## Scripts
|
|
63
|
+
|
|
64
|
+
```bash
|
|
65
|
+
npm run build # compila a dist/
|
|
66
|
+
npm run typecheck # typecheck sin emitir
|
|
67
|
+
npm test # ejecuta vitest
|
|
68
|
+
```
|
|
69
|
+
|
|
70
|
+
## Publicación
|
|
71
|
+
|
|
72
|
+
Paquete privado (`"private": true`). Instálalo desde un registry privado (GitHub Packages, Verdaccio, GitLab Package Registry) o directamente desde el repo:
|
|
73
|
+
|
|
74
|
+
```bash
|
|
75
|
+
npm install git+https://github.com/tu-org/verifactu-lib.git
|
|
76
|
+
```
|
|
77
|
+
|
|
78
|
+
## Algoritmo de huella (hash)
|
|
79
|
+
|
|
80
|
+
La huella de un registro Verifactu **no se calcula sobre el XML**, sino sobre una cadena canónica de campos concatenados en el formato `nombreCampo=valor&...`:
|
|
81
|
+
|
|
82
|
+
- **Alta (8 campos):** `IDEmisorFactura`, `NumSerieFactura`, `FechaExpedicionFactura`, `TipoFactura`, `CuotaTotal`, `ImporteTotal`, `Huella` (hash anterior), `FechaHoraHusoGenRegistro`.
|
|
83
|
+
- **Anulación (5 campos):** `IDEmisorFacturaAnulada`, `NumSerieFacturaAnulada`, `FechaExpedicionFacturaAnulada`, `Huella`, `FechaHoraHusoGenRegistro`.
|
|
84
|
+
|
|
85
|
+
Reglas: se recortan espacios al inicio/final, los campos vacíos van como `nombreCampo=`, **no se aplica URL-encoding**, y el resultado es **SHA-256 en hexadecimal MAYÚSCULA** (64 caracteres). El hash anterior se coloca en el campo `Huella` en su posición exacta. La implementación está verificada contra los [vectores oficiales de la AEAT](https://www.agenciatributaria.es/static_files/AEAT_Desarrolladores/EEDD/IVA/VERI-FACTU/Veri-Factu_especificaciones_huella_hash_registros.pdf).
|
|
86
|
+
|
|
87
|
+
## Nota sobre el esquema oficial
|
|
88
|
+
|
|
89
|
+
El XML generado sigue la estructura del RD 1007/2023 y la documentación de la AEAT, pero **debe validarse contra el XSD oficial** (descargable desde el [portal de desarrolladores de la AEAT](https://www.agenciatributaria.es/AEAT.desarrolladores/Desarrolladores/_menu_/Documentacion/Sistemas_Informaticos_de_Facturacion_y_Sistemas_VERI_FACTU/Sistemas_Informaticos_de_Facturacion_y_Sistemas_VERI_FACTU.html)) antes de producción.
|