Skip to content

Commit 4b3937e

Browse files
committed
feat: initial code base for converting CFDI to PDF
1 parent 2a4f21b commit 4b3937e

File tree

8 files changed

+547
-0
lines changed

8 files changed

+547
-0
lines changed

README.md

Lines changed: 134 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,134 @@
1+
# Conversión de CFDI a PDF
2+
3+
- English documentation available [here](README_EN.md)
4+
5+
## @redocmx/client
6+
7+
El módulo `@redocmx/client` es un cliente de Node.js diseñado para interactuar con la API REST de [redoc.mx](https://redoc.mx) para convertir CFDIs (Comprobante Fiscal Digital por Internet) a PDFs.
8+
9+
Este cliente facilita el proceso de enviar datos XML y recibir el PDF convertido, junto con los detalles de la transacción y metadatos.
10+
11+
Este paquete incluye definiciones de TypeScript que te permiten integrarlo sin problemas en tus proyectos de TypeScript.
12+
13+
## Instalación
14+
15+
Para instalar el módulo, ejecuta:
16+
17+
```bash
18+
npm install @redocmx/client
19+
```
20+
21+
o si usas `yarn`:
22+
23+
```bash
24+
yarn add @redocmx/client
25+
```
26+
27+
## Uso
28+
29+
Primero, importa el módulo y crea una instancia del cliente Redoc.
30+
31+
Puedes pasar opcionalmente tu clave API como un argumento, o el cliente intentará cargarla de la variable de entorno `REDOC_API_KEY`.
32+
33+
```javascript
34+
import Redoc from '@redocmx/client';
35+
36+
const redoc = new Redoc('tu_clave_api_aquí');
37+
```
38+
39+
### Convirtiendo CFDI a PDF
40+
41+
`@redocmx/client` proporciona dos opciones para cargar datos CFDI: desde un archivo o directamente desde una cadena.
42+
43+
#### Opción 1: Cargar XML desde el Sistema de Archivos
44+
45+
```javascript
46+
const cfdi = redoc.cfdi.fromFile('./ruta/a/tu/archivo.xml');
47+
```
48+
49+
#### Opción 2: Usar una Cadena de Contenido XML
50+
51+
```javascript
52+
const cfdi = redoc.cfdi.fromString('<cadena_de_contenido_xml_aquí>');
53+
```
54+
55+
### Generando el PDF
56+
57+
Para convertir el CFDI cargado a un PDF:
58+
59+
```javascript
60+
try {
61+
const pdf = await cfdi.toPdf();
62+
const buffer = pdf.toBuffer();
63+
64+
// Escribiendo el buffer del PDF a un archivo
65+
await fs.writeFile('./ruta/para/guardar/archivo.pdf', buffer);
66+
67+
console.log(`ID de Transacción: ${pdf.getTransactionId()}`);
68+
console.log(`Total de Páginas: ${pdf.getTotalPages()}`);
69+
console.log(`Tiempo Total: ${pdf.getTotalTimeMs()} ms`);
70+
console.log(`Metadatos: ${pdf.getMetadata()}`);
71+
} catch (error) {
72+
console.error("Ocurrió un error durante la conversión:", error);
73+
}
74+
```
75+
76+
## Ejemplos
77+
78+
- [Ejemplo básico](https://github.com/redocmx/cfdi-a-pdf-ejemplos)
79+
- [Logotipo y colores personalizados](https://github.com/redocmx/cfdi-a-pdf-ejemplos)
80+
- [Cambiar idioma a inglés](https://github.com/redocmx/cfdi-a-pdf-ejemplos)
81+
- [Agregar contenido enriquecido adicional](https://github.com/redocmx/cfdi-a-pdf-ejemplos)
82+
83+
## Referencia API
84+
85+
### Redoc
86+
87+
El objeto `redoc` es una instancia de `Redoc`, creada usando `new Redoc(api_key)`.
88+
89+
| Método | Descripción |
90+
| ------------------------------- | --------------------------------------------------------------------------------------------- |
91+
| redoc.cfdi.**fromFile(filePath)** | Devuelve: **Cfdi** - **Instancia**<br>Carga contenido de archivo del sistema para convertir un CFDI a PDF. El archivo debe ser XML válido para un CFDI.<br>Devuelve una instancia de la clase Cfdi, que se puede usar para obtener el PDF. |
92+
| redoc.cfdi.**fromString(fileContent)** | Devuelve: **Cfdi** - **Instancia**<br>Usa un CFDI como cadena para convertir el CFDI a PDF. La cadena debe ser XML válido para un CFDI.<br>Devuelve una instancia de la clase Cfdi, que se puede usar para obtener el PDF. |
93+
94+
### Cfdi
95+
96+
El objeto `cfdi` es una instancia de `Cfdi`, creada usando `redoc.cfdi.fromFile(rutaDelArchivo)` o `redoc.cfdi.fromString(contenidoDelArchivo)`.
97+
98+
| Método | Descripción |
99+
| --------------------------- | ----------- |
100+
| cfdi.**setAddenda(str)** | Parámetros: **String**<br>Permite el uso de una [addenda de redoc](https://redoc.mx/docs/addenda) para tener control total sobre el diseño del PDF final. |
101+
| cfdi.**toPdf(opciones)** | Parámetros: **Object** - [OpcionesPdf](#opcionespdf)<br>Devuelve: **Pdf** - **Instancia**<br>Una instancia de la clase Pdf, que al invocarse, convierte el CFDI a PDF y lo almacena, junto con los datos generados de la solicitud de conversión. |
102+
103+
##### OpcionesPdf
104+
```js
105+
{
106+
"estilo_pdf": "John"
107+
}
108+
```
109+
### Pdf
110+
111+
El objeto `pdf` es una instancia de `Pdf`, creado a partir de `cfdi.toPdf(opciones)`.
112+
113+
| Método | Descripción |
114+
| ----------------------------- | ----------- |
115+
| pdf.**toBuffer()** | Devuelve: **Buffer**<br>El documento PDF como buffer, listo para almacenarse en el sistema de archivos o para enviarse de vuelta en una solicitud HTTP. |
116+
| pdf.**getTransactionId()** | Devuelve: **String - UUID**<br>Un ID único para la solicitud de transacción al servicio de redoc. |
117+
| pdf.**getTotalPages()** | Devuelve: **Integer**<br>El número total de páginas generadas para el archivo PDF. |
118+
| pdf.**getTotalTimeMs()** | Devuelve: **Integer**<br>Tiempo en milisegundos tomado para convertir el CFDI a PDF. |
119+
| pdf.**getMetadata()** | Devuelve: **Object** - [MetadatosCfdi]()<br>Información general del CFDI convertido. |
120+
121+
##### MetadatosCfdi
122+
```js
123+
{
124+
TDB...
125+
}
126+
```
127+
128+
## Contribuciones
129+
130+
¡Las contribuciones son bienvenidas! No dudes en enviarnos una solicitud de extracción o abrir un problema para cualquier error, característica o mejora.
131+
132+
## Licencia
133+
134+
Este proyecto está licenciado bajo la Licencia MIT - vea el archivo [LICENSE](LICENSE) para más detalles.

README_EN.md

Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
# Converting CFDI to PDF
2+
3+
## @redocmx/client
4+
5+
The `@redocmx/client` module is a Node.js client for interacting with the [redoc.mx](https://redoc.mx) REST API to convert CFDIs (Comprobante Fiscal Digital por Internet) to PDFs.
6+
7+
This client simplifies the process of sending XML data and retrieving the converted PDF, along with transaction details and metadata.
8+
9+
This package includes TypeScript definitions allowing you to integrate it seamlessly into your TypeScript projects.
10+
11+
## Installation
12+
13+
To install the module, run:
14+
15+
```bash
16+
npm install @redocmx/client
17+
```
18+
19+
or if you use `yarn`:
20+
21+
```bash
22+
yarn add @redocmx/client
23+
```
24+
25+
## Usage
26+
27+
First, import the module and create an instance of the Redoc client.
28+
29+
You can optionally pass your API key as an argument, or the client will attempt to load it from the REDOC_API_KEY environment variable.
30+
31+
```javascript
32+
import Redoc from '@redocmx/client';
33+
34+
const redoc = new Redoc('your_api_key_here');
35+
```
36+
37+
### Converting CFDI to PDF
38+
39+
The `@redocmx/client` provides two options for loading CFDI data: from a file or directly from a string.
40+
41+
#### Option 1: Load XML from the File System
42+
43+
```javascript
44+
const cfdi = redoc.cfdi.fromFile('./path/to/your/file.xml');
45+
```
46+
47+
#### Option 2: Use an XML Content String
48+
49+
```javascript
50+
const cfdi = redoc.cfdi.fromString('<xml_content_string_here>');
51+
```
52+
53+
### Generating the PDF
54+
55+
To convert the loaded CFDI to a PDF:
56+
57+
```javascript
58+
try {
59+
const pdf = await cfdi.toPdf();
60+
const buffer = pdf.toBuffer();
61+
62+
// Writing the PDF buffer to a file
63+
await fs.writeFile('./path/to/save/file.pdf', buffer);
64+
65+
console.log(`Transaction ID: ${pdf.getTransactionId()}`);
66+
console.log(`Total Pages: ${pdf.getTotalPages()}`);
67+
console.log(`Total Time: ${pdf.getTotalTimeMs()} ms`);
68+
console.log(`Metadata: ${pdf.getMetadata()}`);
69+
} catch (error) {
70+
console.error("An error occurred during the conversion:", error);
71+
}
72+
```
73+
74+
## Examples
75+
76+
- [Basic example](https://github.com/redocmx/cfdi-a-pdf-ejemplos)
77+
- [Custom logo and colors](https://github.com/redocmx/cfdi-a-pdf-ejemplos)
78+
- [Change language to English](https://github.com/redocmx/cfdi-a-pdf-ejemplos)
79+
- [Add additional rich content](https://github.com/redocmx/cfdi-a-pdf-ejemplos)
80+
81+
## API Reference
82+
83+
### Redoc
84+
85+
The `redoc` object is an instance of `Redoc`, created using `new Redoc(api_key)`.
86+
87+
| Method | Description |
88+
| -------- | ------- |
89+
| redoc.cfdi.**fromFile(filePath)** | Returns: **Cfdi** - **Instance**<br>Loads file content from the file system for converting a CFDI to PDF. The file should be valid XML for a CFDI.<br>It returns an instance of the Cfdi class, which can be used to obtain the PDF.|
90+
| redoc.cfdi.**fromString(fileContent)** | Returns: **Cfdi** - **Instance**<br>Uses a CFDI as a string for converting the CFDI to PDF. The string should be valid XML for a CFDI.<br>It returns an instance of the Cfdi class, which can be used to obtain the PDF.|
91+
92+
### Cfdi
93+
94+
The `cfdi` object is an instance of `Cfdi`, created using `redoc.cfdi.fromFile(filePath)` or `redoc.cfdi.fromString(fileContent)`.
95+
96+
| Method | Description |
97+
| -------- | ------- |
98+
| cfdi.**setAddenda(str)** | Params: **String**<br>Allows the use of a [redoc addenda](https://redoc.mx/docs/addenda) for full control over the design of the final PDF.|
99+
| cfdi.**toPdf(options)** | Params: **Object** - [PdfOptions](#pdfoptions)<br>Returns: **Pdf** - **Instance**<br>An instance of the Pdf class, which, when invoked, converts the CFDI into a PDF and stores it, along with the generated data from the conversion request.|
100+
101+
##### PdfOptions
102+
```js
103+
{
104+
"style_pdf": "John"
105+
}
106+
```
107+
### Pdf
108+
109+
The `pdf` object is an instance of `Pdf`, created from `cfdi.toPdf(options)`.
110+
111+
| Method | Description |
112+
| -------- | ------- |
113+
| pdf.**toBuffer()** | Returns: **Buffer**<br>The PDF document as a buffer, ready for storage in the file system or to be sent back in an HTTP request.|
114+
| pdf.**getTransactionId()** | Returns: **String - UUID**<br>A unique ID for the transaction request to the redoc service.|
115+
| pdf.**getTotalPages()** | Returns: **Integer**<br>The total number of pages generated for the PDF file. |
116+
| pdf.**getTotalTimeMs()** | Returns: **Integer**<br>Time in milliseconds taken to convert the CFDI to PDF. |
117+
| pdf.**getMetadata()** | Returns: **Object** - [CfdiMetadata]()<br>General information from the converted CFDI. |
118+
119+
##### CfdiMetadata
120+
```js
121+
{
122+
TDB...
123+
}
124+
```
125+
126+
## Contributing
127+
128+
Contributions are welcome! Please feel free to submit a pull request or open an issue for any bugs, features, or improvements.
129+
130+
## License
131+
132+
This project is licensed under the MIT License - see the [LICENSE](LICENSE) file for details.

package.json

Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
{
2+
"name": "client",
3+
"version": "0.0.1",
4+
"description": "Conversión CFDI a PDF",
5+
"main": "src/index.js",
6+
"types": "src/types.d.ts",
7+
"type": "module",
8+
"engines": {
9+
"node": ">= 18.0.0"
10+
},
11+
"scripts": {
12+
"test": "echo \"Error: no test specified\" && exit 1"
13+
},
14+
"author": {
15+
"name": "redoc.mx"
16+
},
17+
"license": "MIT",
18+
"repository": {
19+
"type": "git",
20+
"url": "https://github.com/redocmx/client-node.git"
21+
},
22+
"homepage": "https://github.com/redocmx/client-node#readme",
23+
"bugs": {
24+
"url": "https://github.com/redocmx/client-node/issues"
25+
}
26+
}

src/cfdi.js

Lines changed: 90 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,90 @@
1+
import fs from 'fs/promises';
2+
import Service from './service.js';
3+
import Pdf from './pdf.js';
4+
5+
const { F_OK, R_OK } = fs.constants;
6+
7+
export default class Cfdi {
8+
constructor() {
9+
this.pdf = null;
10+
this.addenda = null;
11+
this.filePath = null;
12+
this.fileBuffer = null;
13+
this.fileContent = null;
14+
this.service = Service.getInstance();
15+
}
16+
17+
fromFile(filePath) {
18+
this.filePath = filePath;
19+
return this;
20+
}
21+
22+
fromString(fileContent) {
23+
this.fileContent = fileContent;
24+
return this;
25+
}
26+
27+
async getXmlContent() {
28+
if(this.fileContent){
29+
return { content: this.fileContent, type: 'string' }
30+
}
31+
32+
if(this.fileBuffer){
33+
return { content: this.fileBuffer, type: 'buffer' }
34+
}
35+
36+
if(this.filePath){
37+
try {
38+
await fs.access(this.filePath, F_OK | R_OK);
39+
} catch (err) {
40+
if (err.code === 'ENOENT') {
41+
throw new Error(`Failed to read XML content from file: ${this.filePath}. The file does not exist.`);
42+
} else if (err.code === 'EACCES') {
43+
throw new Error(`Permission denied: ${this.filePath}. The file exists but cannot be read.`);
44+
} else {
45+
throw err;
46+
}
47+
}
48+
49+
this.fileBuffer = await fs.readFile(this.filePath)
50+
return { content: this.fileBuffer, type: 'buffer' }
51+
}
52+
53+
throw new Error('XML content for the CFDI must be provided.');
54+
}
55+
56+
setAddenda ( addenda ) {
57+
if (typeof addenda !== 'string') {
58+
throw new TypeError('setAddenda function only accepts a string parameter.');
59+
}
60+
61+
this.addenda = addenda
62+
}
63+
64+
getAddenda () {
65+
return this.addenda
66+
}
67+
68+
async toPdf( payload = {} ) {
69+
70+
if(this.pdf){
71+
return this.pdf;
72+
}
73+
74+
if (Object.prototype.toString.call(payload) !== '[object Object]') {
75+
throw new TypeError('toPdf function only accepts an object as a parameter.');
76+
}
77+
78+
const file = await this.getXmlContent();
79+
payload.format = 'pdf';
80+
81+
if (this.getAddenda()) {
82+
payload.addenda = this.getAddenda();
83+
}
84+
85+
const result = await this.service.cfdisConvert({file, payload});
86+
this.pdf = new Pdf(result);
87+
88+
return this.pdf;
89+
}
90+
}

0 commit comments

Comments
 (0)