npm package discovery and stats viewer.

Discover Tips

  • General search

    [free text search, go nuts!]

  • Package details

    pkg:[package-name]

  • User packages

    @[username]

Sponsor

Optimize Toolset

I’ve always been into building performant and accessible sites, but lately I’ve been taking it extremely seriously. So much so that I’ve been building a tool to help me optimize and monitor the sites that I build to make sure that I’m making an attempt to offer the best experience to those who visit them. If you’re into performant, accessible and SEO friendly sites, you might like it too! You can check it out at Optimize Toolset.

About

Hi, 👋, I’m Ryan Hefner  and I built this site for me, and you! The goal of this site was to provide an easy way for me to check the stats on my npm packages, both for prioritizing issues and updates, and to give me a little kick in the pants to keep up on stuff.

As I was building it, I realized that I was actually using the tool to build the tool, and figured I might as well put this out there and hopefully others will find it to be a fast and useful way to search and browse npm packages as I have.

If you’re interested in other things I’m working on, follow me on Twitter or check out the open source projects I’ve been publishing on GitHub.

I am also working on a Twitter bot for this site to tweet the most popular, newest, random packages from npm. Please follow that account now and it will start sending out packages soon–ish.

Open Software & Tools

This site wouldn’t be possible without the immense generosity and tireless efforts from the people who make contributions to the world and share their work via open source initiatives. Thank you 🙏

© 2026 – Pkg Stats / Ryan Hefner

@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.

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 fetch y Web 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-service

Uso

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 formateada

client.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: string

client.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: string

client.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?: number

client.descarga(packageId, token)

Descarga un paquete ZIP de CFDIs.

const result = await client.descarga(packageId, token);
// result.zip: Uint8Array  // Contenido del ZIP
// result.statusCode: number

Endpoints 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.md

Testing

# Deno
deno test --allow-net --allow-read --allow-env

# Node.js (con vitest o jest)
npm test

Publicación a npm

# Login
npm login

# Build (genera dist/ con JS + .d.ts)
npm run build

# Publicar
npm publish --access public

Roadmap

  • [ ] 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