Librer铆a para usar el servicio web del SAT de Descarga Masiva
馃嚭馃嚫 The documentation of this project is in spanish as this is the natural language for intended audience.
馃嚥馃嚱 La documentaci贸n del proyecto est谩 en espa帽ol porque ese es el lenguaje principal de los usuarios.
npm i @nodecfdi/sat-ws-descarga-masiva --save
o
yarn add @nodecfdi/sat-ws-descarga-masiva
import { readFileSync } from 'fs';
import { Fiel, AxiosWebClient, FielRequestBuilder, Service } from '@nodecfdi/sat-ws-descarga-masiva';
// Creaci贸n de la FIEL, puede leer archivos DER (como los env铆a el SAT) o PEM (convertidos con openssl)
const fiel = Fiel.create(
readFileSync('fake-fiel/EKU9003173C9.cer', 'binary'),
readFileSync('fake-fiel/EKU9003173C9.key', 'binary'),
'12345678a'
);
// verificar que la FIEL sea v谩lida (no sea CSD y sea vigente acorde a la fecha del sistema)
if (!fiel.isValid()) {
return;
}
// creaci贸n del web client basado en Axios que implementa WebClientInterface
// para usarlo necesitas instalar axios pues no es una dependencia directa
const webClient = new AxiosWebClient();
// creaci贸n del objeto encargado de crear las solicitudes firmadas usando una FIEL
const requestBuilder = new FielRequestBuilder(fiel);
// Creaci贸n del servicio
const service = new Service(requestBuilder, webClient);
Existen dos tipos de Comprobantes Fiscales Digitales, los regulares (ingresos, egresos, traslados, n贸minas y pagos), y los CFDI de retenciones e informaci贸n de pagos (retenciones).
Puede utilizar esta librer铆a para consumir los CFDI de Retenciones. Para lograrlo construya el servicio con la especificaci贸n de ServiceEndpoints.retenciones()
import { AxiosWebClient, RequestBuilderInterface, ServiceEndpoints } from '@nodecfdi/sat-ws-descarga-masiva';
/**
* @var webClient: AxiosWebClient
* @var requestBuilder: RequestBuilderInterface
*/
// Creaci贸n del servicio
const service = new Service(requestBuilder, webClient, undefined, ServiceEndpoints.retenciones());
Una vez creado el servicio, se puede presentar la consulta que tiene estos cuatro par谩metros:
- Periodo: Fecha y hora de inicio y fin de la consulta.
- Tipo de descarga: CFDI emitidos
DownloadType.issued
o recibidosDownloadType.received
. - Tipo de solicitud: De metadatos
RequestType.metadata
o de archivos CFDIRequestType.cfdi
. - Filtrado por RFC: Si se establece, se filtran para obtener 煤nicamente donde la contraparte tenga el RFC indicado.
import { QueryParameters, DownloadType, RequestType, DateTimePeriod } from '@nodecfdi/sat-ws-descarga-masiva';
/**
* El servicio ya existe
* @var service: Service
*/
// Explicaci贸n de la consulta:
// - Del 13/ene/2019 00:00:00 al 13/ene/2019 23:59:59 (inclusive)
// - Todos los emitidos por el due帽o de la FIEL
// - Solicitando la informaci贸n de Metadata
// - Filtrando los CFDI emitidos para RFC MAG041126GT8
const request = QueryParameters.create(
DateTimePeriod.createFromValues('2022-01-01 00:00:00', '2022-02-28 23:59:59'),
DownloadType.issued,
RequestType.metadata,
'MAG041126GT8'
);
// presentar la consulta
const query = await service.query(request);
if (!query.getStatus().isAccepted()) {
console.log(`Fallo al presentar la consulta: ${query.getStatus().getMessage()}`);
return;
}
console.log(`Se gener贸 la solicitud ${query.getRequestId()}`);
Valores predeterminados de una consulta:
- Consultar comprobantes emitidos
DownloadType.issued
. - Solicitar informaci贸n de metadata
RequestType.metadata
. - Sin filtro de RFC.
import { QueryParameters, DateTimePeriod } from '@nodecfdi/sat-ws-descarga-masiva';
// Consulta del d铆a 2019-01-13, solo los emitidos, informaci贸n de tipo metadata, sin filtro de RFC.
const request = QueryParameters.create(DateTimePeriod.createFromValues('2019-01-13 00:00:00', '2019-01-13 23:59:59'));
La verificaci贸n depende de que la consulta haya sido aceptada.
import { Service } from '@nodecfdi/sat-ws-descarga-masiva';
/**
* @var service: Service
* @var requestId: string es el identificador generado al presentar la consulta
*/
// consultar el servicio de verificaci贸n
const verify = await service.verify(requestId);
// revisar que el proceso de verificaci贸n fue correcto
if (!verify.getStatus().isAccepted()) {
console.log(`Fallo al verificar la consulta ${requestId}: ${verify.getStatus().getMessage()}`);
return;
}
// revisar que la consulta no haya sido rechazada: los valores conocidos son: Accepted, Exhausted, MaximumLimitReaded, EmptyResult, Duplicated
if (verify.getCodeRequest().getEntryId() != 'Accepted') {
console.log(`La solicitud ${requestId} fue rechazada: ${verify.getCodeRequest().getMessage()}`);
return;
}
// revisar el progreso de la generaci贸n de los paquetes los valores conocidos son: Accepted, InProgress, Finished, Failure, Rejected, Expired
const statusRequest = verify.getStatusRequest();
if (
statusRequest.getEntryId() == 'Expired' ||
statusRequest.getEntryId() == 'Failure' ||
statusRequest.getEntryId() == 'Rejected'
) {
console.log(`La solicitud ${requestId} no se puede completar`);
return;
}
if (statusRequest.getEntryId() == 'Accepted' || statusRequest.getEntryId() == 'InProgress') {
console.log(`La solicitud ${requestId} se est谩 procesando`);
return;
}
if (statusRequest.getEntryId() == 'Finished') {
console.log(`La solicitud ${requestId} est谩 lista`);
}
console.log(`se encontraron ${verify.countPackages()} paquetes`);
verify.getPackageIds().forEach((packageId) => {
console.log(packageId);
});
La descarga de los paquetes depende de que la consulta haya sido correctamente verificada.
Una consulta genera un identificador de la solicitud, la verificaci贸n retorna uno o varios identificadores de paquetes. Necesitas descargar todos y cada uno de los paquetes para tener la informaci贸n completa de la consulta.
import { Service } from '@nodecfdi/sat-ws-descarga-masiva';
import { writeFileSync } from 'fs';
/**
* @var service: Service
* @var packagesIds: string[] El listado de identificadores de paquetes generado en la (correcta) verificaci贸n
*/
for (const packageId of packagesIds) {
const download = await service.download(packageId);
if (!download.getStatus().isAccepted()) {
console.log(`El paquete ${packageId} no se ha podido descargar: ${download.getStatus().getMessage()}`);
continue;
}
const filezip = 'package.zip';
writeFileSync(filezip, download.getPackageContent(), { encoding: 'binary' });
console.log(`el paquete ${packageId} se ha almacenado`);
}
Los paquetes de Metadata y CFDI se pueden leer con las clases MetadataPackageReader y CfdiPackageReader respectivamente. Para fabricar los objetos, se pueden usar sus m茅todos createFromFile para crearlo a partir de un archivo existente o createFromContents para crearlo a partir del contenido del archivo en memoria.
Cada paquete puede contener uno o m谩s archivos internos. Cada paquete se lee individualmente.
import { MetadaPackageReader, OpenZipFileException, Helpers } from '@nodecfdi/sat-ws-descarga-masiva';
/**
* @var zipfile: string contiene la ruta al archivo de paquete de Metadata
*/
let metadataReader: MetadaPackageReader;
// abrir el archivo de Metadata
try {
metadataReader = await MetadaPackageReader.createFromFile(zipFile);
} catch (error) {
const zipError = error as OpenZipFileException;
console.log(zipError.message);
return;
}
const metadata = await Helpers.iteratorToMap(metadataReader.metadata());
metadata.forEach((data) => {
console.log(`${data.get('uuid')} : ${data.get('fechaEmision')}`);
});
import { CfdiPackageReader, OpenZipFileException } from '@nodecfdi/sat-ws-descarga-masiva';
import { writeFileSync } from 'fs';
/**
* @var zipfile: string contiene la ruta al archivo de paquete de archivos ZIP
*/
let cfdiReader: CfdiPackageReader;
try {
cfdiReader = await CfdiPackageReader.createFromFile(zipFile);
} catch (error) {
const zipError = error as OpenZipFileException;
console.log(zipError.message);
return;
}
const cfdis = cfdiReader.cfdis();
for await (const cfdi of cfdis) {
writeFileSync(`cfdis/${Object.keys(cfdi)[0]}`, Object.values(cfdi)[0]);
}
La informaci贸n t茅cnica puede ser leida del siguiente link: CfdiPackageReader facilitada por la librer铆a que inspir贸 a 茅sta.
The nodecfdi/sat-ws-descarga-masiva
library is copyright 漏 NodeCfdi and OcelotlStudio
and licensed for use under the MIT License (MIT). Please see LICENSE for more information.