nibussatws

package module
v0.1.0 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 5, 2026 License: MIT Imports: 1 Imported by: 0

README

nibus-sat-ws

La primera librería Go madura para el Servicio Web de Descarga Masiva de CFDI del SAT (México) — autenticación con e.firma/FIEL, SolicitaDescarga, Verifica y Descarga, más orquestación de alto nivel para bajar el histórico completo sin el límite de 2,000 descargas/día del portal.

CI Go Reference Go Report Card

Reconstrucción idiomática en Go inspirada en la excelente librería de referencia phpcfdi/sat-ws-descarga-masiva, sin dependencias nativas (no CGO, no libxml): un solo binario estático, firmado WS-Security con la FIEL en Go puro. Verificado end-to-end contra el SAT real.

¿Por qué Go / por qué esta librería?

  • Un binario estático — despliega en contenedores, serverless o un CLI sin runtime de PHP/Node.
  • Concurrencia nativa — paraleliza descargas dentro de los límites del SAT.
  • Tipado y errores transparentes — distingue una e.firma revocada de un ajuste de tu solicitud (ver manejo de errores).
  • Orquestación incluida — el backfill trocea, deduplica y reintenta por ti.
nibus-sat-ws (Go) phpcfdi (PHP) nodecfdi (TS)
Binario único sin runtime
Sin dependencias nativas
Concurrencia nativa
Orquestador de histórico (backfill)
CFDI + Retenciones
Errores del SAT tipados

phpcfdi es la implementación de referencia del ecosistema; esta librería reimplementa el protocolo del SAT en Go, sin copiar su código.

¿Por qué?

El portal del SAT (RecuperaCfdi.aspx) limita a 2,000 descargas por día. El Servicio de Descarga Masiva no tiene ese límite: con la e.firma pides rangos de fechas y el SAT te entrega paquetes ZIP de hasta ~200,000 CFDI (o ~1,000,000 en metadata) por solicitud. Este es el mecanismo correcto para extraer el histórico.

Características

  • Validación y vigencia de la e.firma (FIEL): parseo de .cer/.key, verificación de expiración, distinción FIEL vs CSD, y cadena de confianza contra la AC del SAT. (Ver abajo.)
  • Autenticación contra el SAT — firmado WS-Security (Timestamp firmado, BinarySecurityToken, C14N exclusiva, RSA-SHA1) con la FIEL, sin librerías nativas. Verificado E2E: el SAT acepta la firma.
  • ✅ CFDI y Retenciones e Información de Pagos.
  • 🚧 Descarga tipo CFDI (XML) y tipo Metadata, con parser del metadata.
  • 🚧 Filtros: emitidas/recibidas, tipo de comprobante, estado (vigente/cancelado), complemento, RFC emisor/receptores, UUID.
  • 🚧 Backfill: orquestador que trocea por periodo (respeta el tope de 200k), hace polling con backoff, deduplica y descarga todo el histórico — reanudable.
  • 🚧 Transport pluggable con soporte de proxy (el SAT geo-bloquea).
  • 🚧 CLI (nibus-sat-ws).

Instalación

go get github.com/InsaneTreset/nibus-sat-ws@latest

Uso (bosquejo de API)

cred, err := credential.NewFromFiles("fiel.cer", "fiel.key", "contraseña")
if err != nil { log.Fatal(err) }

svc := service.New(cred, service.CFDI) // o service.Retenciones

// 1. Solicitar
q := service.NewQuery(from, to).
    Received().                     // recibidas (o .Issued())
    OfType(service.RequestTypeCFDI) // XML completos
solicitud, err := svc.Query(ctx, q)

// 2. Verificar (polling)
status, err := svc.Verify(ctx, solicitud.RequestID)

// 3. Descargar paquetes
for _, id := range status.PackageIDs {
    pkg, err := svc.Download(ctx, id)
    // pkg es un ZIP; recórrelo con satpackage.NewCFDIReader(pkg)
}

Para bajar el histórico completo sin pensar en troceo/límites:

stats, err := backfill.New(svc).Run(ctx, backfill.Options{
    From:     from,
    To:       to,
    Download: service.Received,
    Request:  service.RequestTypeCFDI,
}, func(zip []byte) error {
    cfdis, _ := satpackage.ReadCFDIs(zip) // o ReadMetadata para metadata
    for _, c := range cfdis {
        _ = c.UUID // ...ingesta
    }
    return nil
})
// stats.Chunks, stats.Packages, stats.CFDIs, stats.Bytes

El backfill trocea el rango por periodo, hace polling con backoff, deduplica paquetes y, ante un 5003 (periodo muy grande), divide el rango a la mitad y reintenta — sin que tú te preocupes por los límites del SAT.

CLI

go install github.com/InsaneTreset/nibus-sat-ws/cmd/nibus-sat-ws@latest

# Verificar una e.firma
nibus-sat-ws validate -cer fiel.cer -key fiel.key -pass '••••'

# Bajar un histórico a un directorio
nibus-sat-ws backfill -cer fiel.cer -key fiel.key -pass '••••' \
    -from 2020-01-01 -to 2024-12-31 -type cfdi -download received -out ./descargas

Validación y vigencia de la e.firma (FIEL)

Antes de autenticar conviene verificar que la FIEL es correcta, vigente y del tipo adecuado. El paquete credential lo hace sin depender de servicios externos:

cred, err := credential.NewFromFiles("fiel.cer", "fiel.key", "contraseña")
if err != nil {
    // .cer/.key ilegibles, contraseña incorrecta, o la llave no corresponde al cert
    log.Fatal(err)
}

// Chequeo integral: junta todos los problemas encontrados.
if err := cred.Validate(); err != nil {
    log.Fatalf("FIEL no utilizable: %v", err) // p.ej. "la e.firma expiró (venció el 2025-…)"
}

Validate() verifica en un solo llamado:

  • Vigencia — que hoy esté dentro de la ventana del certificado. Errores tipados credential.ErrExpired (venció) y credential.ErrNotYetValid (aún no vigente), detectables con errors.Is.
  • Que sea FIEL, no CSD — un Certificado de Sello Digital no puede autenticar contra el web service; se rechaza con credential.ErrNotFIEL.

Accesores para la expiración (útiles para alertas de renovación):

cred.ExpiresAt()          // time.Time — fin de vigencia
cred.Expired()            // bool
cred.NotYetValid()        // bool
cred.TimeUntilExpiry()    // time.Duration (negativo si ya expiró)

Cadena de confianza — confirma que la e.firma fue emitida por la Autoridad Certificadora del SAT (pásale el pool de la AC):

if err := cred.VerifyIssuedBy(satACPool); err != nil {
    log.Fatal(err) // credential.ErrUntrusted si no encadena
}

Un snapshot legible para logs o UI:

st := cred.Status()
fmt.Printf("RFC %s (%s) — serie %s — expira %s — vigente:%v\n",
    st.RFC, st.LegalName, st.SerialNumber,
    st.ExpiresAt.Format("2006-01-02"), len(st.Problems) == 0)

Ejemplo ejecutable: examples/validate-fiel.

Manejo de errores transparente

El SAT responde a cada operación con un código de estatus. La librería lo expone tipado, para que distingas con claridad un problema de la e.firma del usuario (que debe renovarla) de un problema de tu solicitud (que ajustas en código):

_, err := svc.Query(ctx, q)
switch {
case err == nil:
    // ok

// Problema de la e.firma del usuario — pídele una FIEL vigente.
case nibussatws.IsCredentialRejected(err):
    // cubre 304 (revocado/caduco), 305 (inválido), 300, 303
    log.Printf("la e.firma fue rechazada por el SAT: %v", err)

// Ajustes de la solicitud — los maneja tu código/orquestador.
case nibussatws.IsLimitExceeded(err): // 5003 → divide el rango de fechas
    ...
case nibussatws.IsExhausted(err):     // 5002 → varía el periodo ≥1s
    ...
case nibussatws.IsDuplicate(err):     // 5005 → ya hay una solicitud igual en proceso
    ...
case nibussatws.IsNoInfo(err):        // 5004 → no hay CFDI en el periodo
    ...
}

Predicados finos y acceso al código/mensaje crudos:

if nibussatws.IsCertificateRevoked(err) { /* 304 exactamente */ }

if se, ok := nibussatws.AsStatusError(err); ok {
    fmt.Println(se.Code, se.Code.Description(), se.Message) // p.ej. "304  Certificado revocado o caduco"
}

Dos capas de defensa para que el dev nunca se quede a ciegas:

  1. Pre-vuelo local con cred.Validate() — atrapa FIEL expirada o CSD antes de gastar un viaje al SAT.
  2. Estatus del SAT tipado — atrapa lo que solo el SAT sabe (p. ej. una e.firma revocada que localmente se ve vigente) con IsCredentialRejected(err).

Créditos

Reimplementación limpia (clean-room) inspirada en el diseño y la documentación de phpcfdi/sat-ws-descarga-masiva y del ecosistema nodecfdi. Gracias a esa comunidad por documentar el protocolo. Este proyecto no copia su código; reimplementa el protocolo del SAT en Go.

Licencia

MIT.

Documentation

Overview

Package nibussatws provides shared primitives (status codes, errors, versioning) for a native Go client of the Mexican SAT "Descarga Masiva de CFDI" web service.

The concrete functionality lives in subpackages:

  • credential: load and use a FIEL (e.firma) — parse .cer/.key, sign with RSA.
  • xmlsig: WS-Security XML signing required by the SAT SOAP endpoints.
  • service: the four SAT operations (Authenticate, Query, Verify, Download).
  • satpackage: read the downloaded ZIP packages (CFDI XML / Metadata).
  • backfill: high-level orchestration to download a full date range.
  • transport: HTTP client with proxy and retry support.

This is a clean-room reimplementation of the SAT protocol inspired by phpcfdi/sat-ws-descarga-masiva. It does not copy that project's code.

Index

Constants

View Source
const Version = "0.0.0-dev"

Version is the current module version. Kept in sync with the git tag.

Variables

This section is empty.

Functions

func IsCertificateInvalid

func IsCertificateInvalid(err error) bool

IsCertificateInvalid reports whether err is a SAT 305 (e.firma invalid).

func IsCertificateRevoked

func IsCertificateRevoked(err error) bool

IsCertificateRevoked reports whether err is a SAT 304 (e.firma revoked/expired).

func IsCredentialRejected

func IsCredentialRejected(err error) bool

IsCredentialRejected reports whether the SAT rejected the FIEL itself (revoked, expired or invalid). When true, the end user needs to provide a current, valid e.firma — it is not a bug in the request. Combine with the pre-flight credential.Validate() check to catch most of these before sending.

func IsDuplicate

func IsDuplicate(err error) bool

IsDuplicate reports whether err is a SAT 5005 (duplicate in-progress request).

func IsExhausted

func IsExhausted(err error) bool

IsExhausted reports whether err is a SAT 5002 (lifetime requests exhausted).

func IsLimitExceeded

func IsLimitExceeded(err error) bool

IsLimitExceeded reports whether err is a SAT 5003 (split the date range).

func IsNoInfo

func IsNoInfo(err error) bool

IsNoInfo reports whether err is a SAT 5004 (no CFDI in the period).

Types

type SolicitudState

type SolicitudState int

SolicitudState is the processing state of a download request, as reported by VerificaSolicitudDescarga (the "EstadoSolicitud" field).

const (
	StateAccepted   SolicitudState = 1 // received, not yet processed
	StateInProgress SolicitudState = 2 // packages being generated
	StateFinished   SolicitudState = 3 // ready — PackageIDs are available
	StateError      SolicitudState = 4 // processing failed
	StateRejected   SolicitudState = 5 // rejected (e.g. by limit)
	StateExpired    SolicitudState = 6 // expired — packages no longer available
)

func (SolicitudState) String

func (s SolicitudState) String() string

String renders the state name.

func (SolicitudState) Terminal

func (s SolicitudState) Terminal() bool

Terminal reports whether the state is final (no more polling needed).

type StatusCode

type StatusCode string

StatusCode is the SAT "CodEstatus" / "CodigoEstadoSolicitud" returned by the web service operations.

const (
	// StatusAccepted (5000) — request was received/accepted successfully.
	StatusAccepted StatusCode = "5000"

	// StatusUserInvalid (300) — "Usuario No Válido".
	StatusUserInvalid StatusCode = "300"
	// StatusXMLMalformed (301) — "XML Mal Formado".
	StatusXMLMalformed StatusCode = "301"
	// StatusSealMalformed (302) — "Sello Mal Formado" (bad signature).
	StatusSealMalformed StatusCode = "302"
	// StatusSealMismatch (303) — "Sello no corresponde con RfcSolicitante".
	StatusSealMismatch StatusCode = "303"
	// StatusCertRevoked (304) — "Certificado Revocado o Caduco": the e.firma was
	// revoked or has expired. The user must use a current FIEL.
	StatusCertRevoked StatusCode = "304"
	// StatusCertInvalid (305) — "Certificado Inválido".
	StatusCertInvalid StatusCode = "305"

	// StatusExhausted (5002) — the same period+parameters was requested too many
	// times ("Se han agotado las solicitudes de por vida"). Vary the date range by
	// at least one second to make it a new period.
	StatusExhausted StatusCode = "5002"
	// StatusLimitExceeded (5003) — the query exceeds the max records per request
	// (~200k CFDI / ~1M metadata). Split the date range.
	StatusLimitExceeded StatusCode = "5003"
	// StatusNoInfo (5004) — no CFDI found for the requested period.
	StatusNoInfo StatusCode = "5004"
	// StatusDuplicate (5005) — an in-progress request with the same parameters
	// already exists.
	StatusDuplicate StatusCode = "5005"
)

Well-known SAT status codes. The SAT returns many more; these are the ones a caller commonly needs to branch on.

func (StatusCode) Description

func (c StatusCode) Description() string

Description returns the known SAT meaning of the code, or "" if unknown.

type StatusError

type StatusError struct {
	Code    StatusCode
	Message string
	// Operation is the SAT operation that produced the error (e.g. "SolicitaDescarga").
	Operation string
}

StatusError wraps a non-successful SAT status code with its message.

func AsStatusError

func AsStatusError(err error) (*StatusError, bool)

AsStatusError returns the *StatusError in err's chain, if any.

func (*StatusError) Error

func (e *StatusError) Error() string

Directories

Path Synopsis
Package backfill orchestrates downloading a whole date range from the SAT: it splits the range into periods, submits a SolicitaDescarga per period, polls until each is ready, downloads every package and hands it to a callback.
Package backfill orchestrates downloading a whole date range from the SAT: it splits the range into periods, submits a SolicitaDescarga per period, polls until each is ready, downloads every package and hands it to a callback.
cmd
nibus-sat-ws command
Command nibus-sat-ws is a CLI for the SAT Descarga Masiva web service.
Command nibus-sat-ws is a CLI for the SAT Descarga Masiva web service.
Package credential loads and uses a Mexican SAT e.firma (FIEL) — parsing the public certificate (.cer, DER X.509) and the encrypted private key (.key, encrypted PKCS#8 DER), and signing data with it.
Package credential loads and uses a Mexican SAT e.firma (FIEL) — parsing the public certificate (.cer, DER X.509) and the encrypted private key (.key, encrypted PKCS#8 DER), and signing data with it.
examples
authenticate command
Command authenticate loads a FIEL and obtains a SAT Descarga Masiva token.
Command authenticate loads a FIEL and obtains a SAT Descarga Masiva token.
descarga command
Command descarga runs the full Descarga Masiva flow with a real FIEL: Query -> Verify (poll) -> Download.
Command descarga runs the full Descarga Masiva flow with a real FIEL: Query -> Verify (poll) -> Download.
inspect command
Command inspect prints the contents of a DescargaMasiva ZIP package (metadata rows or CFDI documents).
Command inspect prints the contents of a DescargaMasiva ZIP package (metadata rows or CFDI documents).
validate-fiel command
Command validate-fiel loads a FIEL (e.firma) and prints its validation status.
Command validate-fiel loads a FIEL (e.firma) and prints its validation status.
Package satpackage reads the ZIP packages returned by DescargaMasiva: either CFDI packages (containing .xml documents) or Metadata packages (a single tilde-delimited .txt with one row per CFDI).
Package satpackage reads the ZIP packages returned by DescargaMasiva: either CFDI packages (containing .xml documents) or Metadata packages (a single tilde-delimited .txt with one row per CFDI).
Package service implements the four SAT "Descarga Masiva" web-service operations: Authenticate, Query (SolicitaDescarga), Verify (VerificaSolicitudDescarga) and Download (DescargaMasiva).
Package service implements the four SAT "Descarga Masiva" web-service operations: Authenticate, Query (SolicitaDescarga), Verify (VerificaSolicitudDescarga) and Download (DescargaMasiva).
Package transport builds http.Clients for talking to the SAT, including routing through a proxy — the SAT geo-blocks non-Mexican egress, so callers running outside Mexico must provide a Mexican proxy.
Package transport builds http.Clients for talking to the SAT, including routing through a proxy — the SAT geo-blocks non-Mexican egress, so callers running outside Mexico must provide a Mexican proxy.
Package xmlsig builds the WS-Security signed SOAP envelopes required by the SAT "Descarga Masiva" web service.
Package xmlsig builds the WS-Security signed SOAP envelopes required by the SAT "Descarga Masiva" web service.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL