@charlydev/sat-service
v1.1.4
Published
Lib runtime-agnostic para interactuar con los servicios de Descarga Masiva del SAT (v1.5). Compatible con Deno, Node.js y Supabase Edge Functions.
Maintainers
Readme
@contab/sat-service
SDK runtime-agnostic para interactuar con los servicios de Descarga Masiva del SAT v1.5 (México).
Compatible con:
- ✅ Deno / Supabase Edge Functions
- ✅ Node.js / NestJS
- ✅ Cualquier runtime con
fetchyWeb Crypto API
Características
- 🚀 Runtime-agnostic: Funciona en Deno, Node.js y navegadores modernos
- 🔐 Firma FIEL nativa: Utiliza Web Crypto API (SHA-1 + RSA)
- 📦 Sin servidor: Librería pura, sin dependencias de servidores HTTP
- 🎯 API moderna: Promise-based con TypeScript completo
- 🌐 SOAP v1.5: Endpoints actualizados del SAT (vigentes desde mayo 2025)
Instalación
Deno / Supabase Edge Functions
No requiere instalación. Importa directamente desde npm:
import { SatClient } from "npm:@contab/[email protected]";Node.js / NestJS
npm install @contab/sat-service
# o
yarn add @contab/sat-service
# o
pnpm add @contab/sat-serviceUso
Ejemplo básico (Deno / Supabase Edge)
import { SatClient } from "npm:@contab/[email protected]";
// Cargar certificados FIEL (puedes leerlos de archivos o desde variables de entorno)
const cerPem = Deno.env.get("SAT_CER_PEM")!;
const keyPem = Deno.env.get("SAT_KEY_PEM")!;
const keyPassphrase = Deno.env.get("SAT_KEY_PASSPHRASE")!;
// Crear cliente
const client = await SatClient.create({
rfcSolicitante: "XAXX010101000",
cerPem,
keyPem,
keyPassphrase,
});
// 1. Autenticación (obtener token)
const auth = await client.autentica();
console.log("Token:", auth.token);
// 2. Solicitar descarga de CFDIs emitidos
const solicitud = await client.solicita({
scope: "emitidos",
fechaInicial: "2024-01-01T00:00:00",
fechaFinal: "2024-01-31T23:59:59",
tipoSolicitud: "CFDI",
}, auth.token);
console.log("Request ID:", solicitud.requestId);
// 3. Verificar el estado de la solicitud
const verificacion = await client.verifica(solicitud.requestId, auth.token);
console.log("Estado:", verificacion.estadoSolicitud);
console.log("Paquetes disponibles:", verificacion.paquetes);
// 4. Descargar paquete ZIP
if (verificacion.paquetes && verificacion.paquetes.length > 0) {
const packageId = verificacion.paquetes[0];
const descarga = await client.descarga(packageId, auth.token);
// descarga.zip es un Uint8Array con el contenido del ZIP
await Deno.writeFile(`./cfdi-${packageId}.zip`, descarga.zip);
console.log(`Descargado: ${descarga.zip.length} bytes`);
}Ejemplo con Supabase Edge Functions
// supabase/functions/sat-download/index.ts
import { serve } from "https://deno.land/[email protected]/http/server.ts";
import { SatClient } from "npm:@charlydev/sat-service@latest";
serve(async (req) => {
try {
const { rfc, scope, fechaInicial, fechaFinal } = await req.json();
// Leer certificados desde Supabase Secrets
const client = await SatClient.create({
rfcSolicitante: rfc,
cerPem: Deno.env.get("SAT_CER_PEM")!,
keyPem: Deno.env.get("SAT_KEY_PEM")!,
keyPassphrase: Deno.env.get("SAT_KEY_PASSPHRASE")!,
});
// Autenticar y solicitar
const auth = await client.autentica();
const result = await client.solicita({
scope,
fechaInicial,
fechaFinal,
tipoSolicitud: "CFDI",
}, auth.token);
return new Response(JSON.stringify({
success: true,
requestId: result.requestId,
}), {
headers: { "Content-Type": "application/json" },
});
} catch (error) {
return new Response(JSON.stringify({
success: false,
error: error.message,
}), {
status: 500,
headers: { "Content-Type": "application/json" },
});
}
});Ejemplo con Node.js / NestJS
// sat.service.ts
import { Injectable } from '@nestjs/common';
import { SatClient } from '@contab/sat-service';
import * as fs from 'fs/promises';
@Injectable()
export class SatService {
private client: SatClient | null = null;
async initClient(rfc: string) {
if (this.client) return this.client;
// Leer certificados desde archivos
const cerPem = await fs.readFile('./certs/certificado.cer.pem', 'utf-8');
const keyPem = await fs.readFile('./certs/llave.key.pem', 'utf-8');
const keyPassphrase = process.env.SAT_KEY_PASSPHRASE!;
this.client = await SatClient.create({
rfcSolicitante: rfc,
cerPem,
keyPem,
keyPassphrase,
// En Node.js 18+, fetch y crypto están disponibles globalmente
// Si no, puedes inyectarlos:
// deps: {
// fetch: fetch,
// crypto: globalThis.crypto
// }
});
return this.client;
}
async solicitarDescarga(params: {
rfc: string;
scope: 'emitidos' | 'recibidos';
fechaInicial: string;
fechaFinal: string;
}) {
const client = await this.initClient(params.rfc);
const auth = await client.autentica();
const result = await client.solicita({
scope: params.scope,
fechaInicial: params.fechaInicial,
fechaFinal: params.fechaFinal,
tipoSolicitud: 'CFDI',
}, auth.token);
return {
requestId: result.requestId,
token: auth.token,
};
}
async verificarDescarga(requestId: string, token: string, rfc: string) {
const client = await this.initClient(rfc);
return await client.verifica(requestId, token);
}
async descargarPaquete(packageId: string, token: string, rfc: string) {
const client = await this.initClient(rfc);
const result = await client.descarga(packageId, token);
// Guardar ZIP
await fs.writeFile(`./downloads/${packageId}.zip`, result.zip);
return {
packageId,
size: result.zip.length,
path: `./downloads/${packageId}.zip`,
};
}
}Extracción de Datos Fiscales del Certificado
El certificado .cer de la FIEL contiene datos fiscales embebidos en su estructura X.509:
import { FielFactory } from '@contab/sat-service';
// Leer certificado y llave privada
const cerContent = await Deno.readFile('./certificado.cer');
const keyContent = await Deno.readFile('./llave.key');
const password = "tu_contraseña";
// Crear FIEL (parsea el certificado automáticamente)
const fiel = await FielFactory.create(cerContent, keyContent, password);
// Obtener datos fiscales completos
const taxpayerData = fiel.getTaxpayerData();
console.log("RFC:", taxpayerData.rfc);
console.log("Nombre:", taxpayerData.commonName);
console.log("Calle:", taxpayerData.street);
console.log("Localidad:", taxpayerData.locality);
console.log("Estado:", taxpayerData.state);
console.log("CP:", taxpayerData.postalCode);
// O usar métodos helper
console.log("Nombre completo:", fiel.getName());
console.log("Dirección:", fiel.getAddress());Datos disponibles en el certificado:
- ✅ RFC
- ✅ Nombre completo / Razón social
- ✅ Domicilio fiscal (calle, localidad, estado, CP)
- ✅ Fechas de validez del certificado
NO disponibles en el certificado:
- ❌ Régimen fiscal
- ❌ Actividades económicas
- ❌ Obligaciones fiscales
Ver examples/taxpayer-data.ts para un ejemplo completo.
API
SatClient.create(options)
Crea una instancia del cliente (método asíncrono).
interface SatClientOptions {
rfcSolicitante: string;
cerPem: string;
keyPem: string;
keyPassphrase: string;
endpoints?: Partial<Endpoints>; // Opcional: endpoints personalizados
deps?: RuntimeDeps; // Opcional: inyectar fetch/crypto
clock?: () => Date; // Opcional: para testing
}FielFactory.create(cerContent, keyContent, password)
Crea una instancia de FIEL parseando el certificado X.509.
interface FielTaxpayerData {
rfc: string;
commonName: string;
organization?: string;
organizationalUnit?: string;
serialNumber?: string;
street?: string;
locality?: string;
state?: string;
postalCode?: string;
country?: string;
}
const fiel = await FielFactory.create(cerContent, keyContent, password);
const data = fiel.getTaxpayerData(); // Retorna FielTaxpayerData
const name = fiel.getName(); // Retorna commonName
const address = fiel.getAddress(); // Retorna dirección formateadaclient.autentica()
Obtiene un token de autenticación WRAP del SAT. El token tiene validez de ~5 minutos.
const result = await client.autentica();
// result.token: string
// result.rawXml: stringclient.solicita(params, token?)
Solicita una descarga masiva de CFDIs.
interface SolicitaParams {
scope: "emitidos" | "recibidos" | "folio";
fechaInicial: string; // ISO format: "2024-01-01T00:00:00" o "2024-01-01"
fechaFinal: string;
tipoSolicitud: "CFDI" | "Metadata";
rfcEmisor?: string;
rfcReceptor?: string;
folio?: string; // Para scope="folio"
estadoComprobante?: "0" | "1"; // 0=vigente, 1=cancelado
}
const result = await client.solicita(params, token);
// result.requestId: string
// result.statusCode: number
// result.rawXml: stringclient.verifica(requestId, token)
Verifica el estado de una solicitud de descarga.
const result = await client.verifica(requestId, token);
// result.estadoSolicitud: number (1=aceptada, 2=en proceso, 3=terminada, 5=rechazada)
// result.paquetes?: string[] // IDs de paquetes disponibles
// result.numeroCfdis?: numberclient.descarga(packageId, token)
Descarga un paquete ZIP de CFDIs.
const result = await client.descarga(packageId, token);
// result.zip: Uint8Array // Contenido del ZIP
// result.statusCode: numberEndpoints SAT v1.5
El SDK utiliza los endpoints oficiales del SAT (vigentes desde mayo 2025):
- Autenticación:
https://cfdidescargamasivasolicitud.clouda.sat.gob.mx/Autenticacion/Autenticacion.svc - Solicitud:
https://cfdidescargamasivasolicitud.clouda.sat.gob.mx/SolicitaDescargaService.svc - Verificación:
https://cfdidescargamasivasolicitud.clouda.sat.gob.mx/VerificaSolicitudDescargaService.svc - Descarga:
https://cfdidescargamasiva.clouda.sat.gob.mx/DescargaMasivaService.svc
Estructura del Proyecto
├── src/
│ ├── client/ # Cliente principal (SatClient)
│ ├── domain/ # Entidades y servicios de dominio
│ ├── infrastructure/ # Implementaciones (SOAP, HTTP, FIEL)
│ └── index.ts # Exports públicos
├── examples/
│ ├── server.ts # Ejemplo: servidor HTTP con Deno
│ └── core-usage.ts # Ejemplo: uso básico
├── test/ # Tests
├── mod.ts # Entry point para Deno
├── package.json # Configuración npm
└── README.mdTesting
# Deno
deno test --allow-net --allow-read --allow-env
# Node.js (con vitest o jest)
npm testPublicación a npm
# Login
npm login
# Build (genera dist/ con JS + .d.ts)
npm run build
# Publicar
npm publish --access publicRoadmap
- [ ] Soporte completo para custom signer (sin FIEL)
- [ ] Helpers para parsear XML de CFDIs
- [ ] Soporte para descomprimir ZIP (opcional)
- [ ] Rate limiting automático
- [ ] Retry con backoff exponencial
Licencia
MIT
Créditos
Desarrollado por Contab para la comunidad mexicana de desarrolladores.
Soporte
- 📧 Email: [email protected]
- 🐛 Issues: GitHub Issues
- 📚 Documentación SAT: Documentación oficial del SAT
