Skip to main content

Cliente Python Web Service del SAT para la descarga masiva de CFDIs

Project description

python-cfdiclient

Cliente Python para consumir los servicios web del SAT relacionados con CFDI:

  • Autenticación con e.firma/FIEL.
  • Solicitud de descarga masiva de CFDI o metadata.
  • Verificación del estado de una solicitud.
  • Descarga de paquetes generados por el SAT.
  • Consulta del estado de un CFDI.

El paquete está pensado para integrarse en aplicaciones que ya cuentan con los archivos .cer, .key, la contraseña de la e.firma y el RFC del contribuyente. No incluye credenciales reales ni debe usarse para almacenarlas.

Servicio SAT de referencia: https://www.sat.gob.mx/consultas/42968/consulta-y-recuperacion-de-comprobantes-(nuevo)

Requisitos

  • Python 3.9 o superior.
  • Certificado .cer y llave privada .key de la e.firma en formato DER.
  • Contraseña de la llave privada.
  • Acceso de red a los servicios del SAT.

Instalación

Instala la versión publicada en PyPI:

python -m pip install cfdiclient

Para trabajar con el proyecto localmente:

python -m venv .venv
source .venv/bin/activate
python -m pip install -e ".[dev]"
pytest

Flujo de descarga masiva

La descarga masiva no entrega los archivos inmediatamente. El flujo normal es:

  1. Cargar la e.firma con Fiel.
  2. Obtener un token con Autenticacion.
  3. Crear una solicitud con SolicitaDescargaEmitidos o SolicitaDescargaRecibidos.
  4. Consultar periódicamente la solicitud con VerificaSolicitudDescarga.
  5. Cuando estado_solicitud sea 3, descargar cada paquete con DescargaMasiva.

Estados de solicitud reportados por el SAT:

Estado Significado
0 Token inválido
1 Aceptada
2 En proceso
3 Terminada
4 Error
5 Rechazada
6 Vencida

Ejemplo completo

Este ejemplo solicita CFDI recibidos, espera a que el SAT termine la solicitud y guarda cada paquete como archivo .zip.

import base64
import datetime
import time
from pathlib import Path

from cfdiclient import (
    Autenticacion,
    DescargaMasiva,
    Fiel,
    SolicitaDescargaRecibidos,
    VerificaSolicitudDescarga,
)

RFC = "XAXX010101000"
FIEL_CER = Path("certificados/ejemploCer.cer")
FIEL_KEY = Path("certificados/ejemploKey.key")
FIEL_PASS = "contrasena"

FECHA_INICIAL = datetime.datetime(2025, 1, 1)
FECHA_FINAL = datetime.datetime(2025, 1, 31, 23, 59, 59)

cer_der = FIEL_CER.read_bytes()
key_der = FIEL_KEY.read_bytes()
fiel = Fiel(cer_der, key_der, FIEL_PASS)

auth = Autenticacion(fiel)
token = auth.obtener_token()

solicitador = SolicitaDescargaRecibidos(fiel)
solicitud = solicitador.solicitar_descarga(
    token=token,
    rfc_solicitante=RFC,
    fecha_inicial=FECHA_INICIAL,
    fecha_final=FECHA_FINAL,
    rfc_receptor=RFC,
    tipo_solicitud="CFDI",
)

if solicitud["cod_estatus"] != "5000":
    raise RuntimeError(f"Solicitud no aceptada por el SAT: {solicitud}")

id_solicitud = solicitud["id_solicitud"]
verificador = VerificaSolicitudDescarga(fiel)

while True:
    token = auth.obtener_token()
    verificacion = verificador.verificar_descarga(token, RFC, id_solicitud)
    estado = int(verificacion["estado_solicitud"])

    if estado in (1, 2):
        time.sleep(60)
        continue

    if estado != 3:
        raise RuntimeError(f"La solicitud no terminó correctamente: {verificacion}")

    descargador = DescargaMasiva(fiel)

    for id_paquete in verificacion["paquetes"]:
        respuesta = descargador.descargar_paquete(token, RFC, id_paquete)
        paquete = base64.b64decode(respuesta["paquete_b64"])
        Path(f"{id_paquete}.zip").write_bytes(paquete)

    break

Preparar e.firma y token

Los ejemplos siguientes asumen que ya cargaste cer_der, key_der y token. Puedes obtenerlos así:

from pathlib import Path

from cfdiclient import Autenticacion, Fiel

cer_der = Path("certificado.cer").read_bytes()
key_der = Path("llave.key").read_bytes()

fiel = Fiel(cer_der, key_der, "contrasena")
token = Autenticacion(fiel).obtener_token()

Solicitar CFDI emitidos

Usa SolicitaDescargaEmitidos cuando quieras comprobantes emitidos por el RFC solicitante.

import datetime

from cfdiclient import Fiel, SolicitaDescargaEmitidos

fiel = Fiel(cer_der, key_der, "contrasena")
descarga = SolicitaDescargaEmitidos(fiel)

resultado = descarga.solicitar_descarga(
    token=token,
    rfc_solicitante="XAXX010101000",
    fecha_inicial=datetime.datetime(2025, 1, 1),
    fecha_final=datetime.datetime(2025, 1, 31, 23, 59, 59),
    rfc_emisor="XAXX010101000",
    tipo_solicitud="CFDI",
)

print(resultado)
# {
#     "id_solicitud": "be2a3e76-684f-416a-afdf-0f9378c346be",
#     "cod_estatus": "5000",
#     "mensaje": "Solicitud Aceptada",
# }

Solicitar CFDI recibidos

Usa SolicitaDescargaRecibidos cuando quieras comprobantes recibidos por el RFC solicitante.

import datetime

from cfdiclient import Fiel, SolicitaDescargaRecibidos

fiel = Fiel(cer_der, key_der, "contrasena")
descarga = SolicitaDescargaRecibidos(fiel)

resultado = descarga.solicitar_descarga(
    token=token,
    rfc_solicitante="XAXX010101000",
    fecha_inicial=datetime.datetime(2025, 1, 1),
    fecha_final=datetime.datetime(2025, 1, 31, 23, 59, 59),
    rfc_receptor="XAXX010101000",
    tipo_solicitud="Metadata",
    estado_comprobante="Vigente",
)

print(resultado)

Verificar una solicitud

from cfdiclient import Fiel, VerificaSolicitudDescarga

fiel = Fiel(cer_der, key_der, "contrasena")
verificador = VerificaSolicitudDescarga(fiel)

resultado = verificador.verificar_descarga(
    token="eyJhbGci...",
    rfc_solicitante="XAXX010101000",
    id_solicitud="6331caae-c253-406f-9332-126f89cc474a",
)

print(resultado)
# {
#     "cod_estatus": "5000",
#     "estado_solicitud": "3",
#     "codigo_estado_solicitud": "5000",
#     "numero_cfdis": "8",
#     "mensaje": "Solicitud Aceptada",
#     "paquetes": ["a4897f62-a279-4f52-bc35-03bde4081627_01"],
# }

Descargar un paquete

import base64
from pathlib import Path

from cfdiclient import DescargaMasiva, Fiel

fiel = Fiel(cer_der, key_der, "contrasena")
descarga = DescargaMasiva(fiel)

resultado = descarga.descargar_paquete(
    token="eyJhbGci...",
    rfc_solicitante="XAXX010101000",
    id_paquete="2d8bbdf1-c36d-4b51-a57c-c1744acdd89c_01",
)

Path("paquete.zip").write_bytes(base64.b64decode(resultado["paquete_b64"]))

Consultar estado de un CFDI

from cfdiclient import Validacion

validacion = Validacion()

estado = validacion.obtener_estado(
    rfc_emisor="XAXX010101000",
    rfc_receptor="XAXX010101000",
    total="1000.41",
    uuid="0XXX0X00-000-0XX0-XX0X-000X0X0XXX00",
)

print(estado)
# {
#     "codigo_estatus": "S - Comprobante obtenido satisfactoriamente.",
#     "es_cancelable": "Cancelable con aceptación",
#     "estado": "Vigente",
# }

Parámetros comunes de solicitud

SolicitaDescargaEmitidos.solicitar_descarga y SolicitaDescargaRecibidos.solicitar_descarga aceptan estos parámetros:

Parámetro Descripción
token Token obtenido con Autenticacion.obtener_token().
rfc_solicitante RFC del contribuyente que realiza la solicitud.
fecha_inicial Inicio del rango como datetime.datetime o datetime.date.
fecha_final Fin del rango como datetime.datetime o datetime.date.
rfc_emisor RFC emisor. Úsalo normalmente en solicitudes de emitidos.
rfc_receptor RFC receptor. Úsalo normalmente en solicitudes de recibidos.
tipo_solicitud "CFDI" para XML o "Metadata" para metadata.
tipo_comprobante Filtro opcional por tipo de comprobante.
estado_comprobante Filtro opcional; por defecto "Vigente".
rfc_a_cuenta_terceros Filtro opcional para comprobantes a cuenta de terceros.
complemento Filtro opcional por complemento.
uuid Filtro opcional por UUID.

Desarrollo

Comandos útiles para colaboradores:

pytest
pylint --rcfile=pylint.rc cfdiclient tests
python -m build

Las pruebas deben ejecutarse sin credenciales reales y sin depender de servicios vivos del SAT. Al agregar cambios sobre SOAP/XML, mantén sincronizados los templates cfdiclient/*.xml con el código que los llena.

Seguridad

  • No subas certificados, llaves privadas, contraseñas, tokens ni paquetes CFDI reales al repositorio.
  • Los archivos en certificados/ son datos de ejemplo.
  • Prefiere variables de entorno o un secreto administrado por tu plataforma para cargar credenciales en aplicaciones productivas.

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

cfdiclient-1.6.3.tar.gz (26.6 kB view details)

Uploaded Source

Built Distribution

If you're not sure about the file name format, learn more about wheel file names.

cfdiclient-1.6.3-py3-none-any.whl (28.7 kB view details)

Uploaded Python 3

File details

Details for the file cfdiclient-1.6.3.tar.gz.

File metadata

  • Download URL: cfdiclient-1.6.3.tar.gz
  • Upload date:
  • Size: 26.6 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for cfdiclient-1.6.3.tar.gz
Algorithm Hash digest
SHA256 3cdeeee0c755e6fc9952bca2fb56e2d80358e8880e4f6227caf70b18313d85f1
MD5 681a06a9cf189e39b82b27574282292c
BLAKE2b-256 2938746c4b0c60c0c6e4af6be0162956fa187ff1a77fe97b617bfaa9fd419a9f

See more details on using hashes here.

Provenance

The following attestation bundles were made for cfdiclient-1.6.3.tar.gz:

Publisher: release.yml on luisiturrios1/python-cfdiclient

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

File details

Details for the file cfdiclient-1.6.3-py3-none-any.whl.

File metadata

  • Download URL: cfdiclient-1.6.3-py3-none-any.whl
  • Upload date:
  • Size: 28.7 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? Yes
  • Uploaded via: twine/6.1.0 CPython/3.13.12

File hashes

Hashes for cfdiclient-1.6.3-py3-none-any.whl
Algorithm Hash digest
SHA256 59af23a864837b538b1dcfef983cce62e45fe48b4fd9cf361f0b230d3377637c
MD5 c9775c2550f3f3ef680bd1a2124cdf9f
BLAKE2b-256 832b7015b95b9e8482c6b80a194566c851e8699deb6fd7de19c2f60b217a4497

See more details on using hashes here.

Provenance

The following attestation bundles were made for cfdiclient-1.6.3-py3-none-any.whl:

Publisher: release.yml on luisiturrios1/python-cfdiclient

Attestations: Values shown here reflect the state when the release was signed and may no longer be current.

Supported by

AWS Cloud computing and Security Sponsor Datadog Monitoring Depot Continuous Integration Fastly CDN Google Download Analytics Pingdom Monitoring Sentry Error logging StatusPage Status page