Professional CFDI 4.0 XML to PDF converter for Mexico SAT
Project description
CFDI PDF
Biblioteca profesional para convertir CFDI 4.0 XML a PDF con templates modernos y extensibles.
El PDF generado siempre se nombra con el UUID del timbre fiscal (
{uuid}.pdf) y el tamaño de página es carta (8.5 × 11 in), estándar en México.
Características
- CFDI 4.0 Completo: Soporte total para la versión 4.0 del SAT
- Tamaño carta: Todos los templates generan PDFs en tamaño carta (estándar SAT México)
- Nombre por UUID: El archivo PDF siempre se nombra
{uuid}.pdfpara trazabilidad - QR SAT Oficial: Generación del código QR con especificaciones oficiales
- 3 Templates incluidos:
minimal,corporativoyclasico - Templates Extensibles: Sistema de templates basado en Jinja2 + HTML/CSS
- Tipado Estricto: Pydantic v2 para validación robusta — Python 3.12+
- Seguridad: Protección contra XXE, XML bombs y otros ataques
- UTF-8 Completo: Manejo correcto de acentos, ñ y caracteres especiales
- Catálogos SAT: Todos los catálogos oficiales del SAT
- CLI Incluida: Herramienta de línea de comandos para conversiones rápidas
Instalación
pip install cfdi-pdf
Requiere Python 3.12 o superior.
Uso Rápido
CLI (Línea de Comandos)
# Un archivo — guarda {uuid}.pdf en el mismo directorio que el XML
cfdi-pdf factura.xml
# Directorio de salida personalizado
cfdi-pdf factura.xml --output-dir ./pdfs
# Procesamiento por lotes
cfdi-pdf enero/*.xml --output-dir ./pdfs
# Con logo y template
cfdi-pdf factura.xml --template corporativo --logo logo.png
# Ver templates disponibles
cfdi-pdf --list-templates
# Salida detallada
cfdi-pdf factura.xml --verbose
El comando imprime la ruta completa del PDF generado:
✓ factura.xml → /ruta/al/cce4d168-1234-5678-9abc-def012345678.pdf
API Python
from cfdi_pdf import CFDIPDF
pdf = CFDIPDF(template="minimal")
# Convierte y guarda {uuid}.pdf en el mismo directorio del XML
output_path = pdf.render(xml_path="factura.xml")
print(output_path) # PosixPath('/ruta/cce4d168-...-uuid.pdf')
# Especificar directorio de salida
output_path = pdf.render(xml_path="factura.xml", output_dir="./pdfs")
# Con logo
output_path = pdf.render(
xml_path="factura.xml",
output_dir="./pdfs",
logo_path="logo.png",
)
Desde string XML
from cfdi_pdf import CFDIPDF
pdf = CFDIPDF()
with open("factura.xml") as f:
xml_content = f.read()
# Guarda {uuid}.pdf en el directorio indicado
output_path = pdf.render_from_string(xml_content, output_dir="./pdfs")
Obtener bytes (sin escribir a disco)
from cfdi_pdf import CFDIPDF
pdf = CFDIPDF()
# Retorna (bytes, nombre_archivo) sin tocar el sistema de archivos
pdf_bytes, filename = pdf.render_bytes(xml_path="factura.xml")
# filename = "cce4d168-1234-5678-9abc-def012345678.pdf"
# Útil para APIs web, S3, etc.
with open(filename, "wb") as f:
f.write(pdf_bytes)
Templates Disponibles
| Template | Descripción | Compatibilidad |
|---|---|---|
minimal |
Diseño limpio y moderno (por defecto) | WeasyPrint, Chromium |
corporativo |
Azul corporativo #1a3a5c, encabezado destacado |
WeasyPrint, Chromium |
clasico |
Blanco/negro, tipografía Times, máxima compatibilidad | WeasyPrint, Chromium, wkhtmltopdf |
Todos los templates:
- Tamaño carta (8.5 × 11 in / 215.9 × 279.4 mm)
- Sin propiedades CSS experimentales (compatible con motores de impresión estrictos)
- Layout con tablas HTML en secciones fiscales (compatible con wkhtmltopdf)
- Zebra striping con clases explícitas (no
:nth-child)
Seleccionar template
from cfdi_pdf import CFDIPDF
pdf = CFDIPDF(template="corporativo")
output_path = pdf.render(xml_path="factura.xml")
cfdi-pdf factura.xml --template clasico
API de Referencia
CFDIPDF
CFDIPDF(
template: str = "minimal",
locale: str = "es_MX",
currency_format: bool = True,
custom_template_paths: list[str | Path] | None = None,
)
| Método | Descripción | Retorna |
|---|---|---|
render(xml_path, output_dir, template, logo_path) |
Convierte XML a PDF, guarda en disco | Path al PDF |
render_from_string(xml_content, output_dir, template, logo_path) |
Igual pero desde string | Path al PDF |
render_bytes(xml_path, template, logo_path) |
Sin escribir a disco | (bytes, filename) |
parse(xml_path) |
Parsea XML sin generar PDF | CFDI |
parse_string(xml_content) |
Parsea string XML | CFDI |
list_templates() |
Lista templates disponibles | list[str] |
Nota: El nombre del archivo siempre es
{uuid}.pdf(UUID en minúsculas del timbre fiscal). Si el CFDI no tiene timbre fiscal, se lanzaCFDIPDFError.
Variables disponibles en templates Jinja2
| Variable | Tipo | Descripción |
|---|---|---|
cfdi |
CFDI |
Objeto completo del CFDI |
formatted.uuid |
str |
UUID formateado |
formatted.fecha |
str |
Fecha de expedición |
formatted.fecha_timbrado |
str | None |
Fecha de timbrado |
formatted.total |
str |
Total formateado con moneda |
formatted.sub_total |
str |
Subtotal formateado |
formatted.descuento |
str | None |
Descuento formateado |
formatted.sello_cfd |
str | None |
Sello CFD completo, dividido en líneas de 64 chars |
formatted.sello_sat |
str | None |
Sello SAT completo, dividido en líneas de 64 chars |
catalogs.tipo_comprobante |
str |
Descripción del tipo |
catalogs.forma_pago |
str |
Descripción forma de pago |
catalogs.metodo_pago |
str |
Descripción método de pago |
catalogs.uso_cfdi |
str |
Descripción uso CFDI |
catalogs.regimen_fiscal_emisor |
str |
Régimen del emisor |
catalogs.regimen_fiscal_receptor |
str |
Régimen del receptor |
catalogs.moneda |
str |
Descripción de moneda |
qr_data |
str |
QR SAT en base64 (PNG) |
logo_data |
str |
Logo en base64 data URL |
cadena_original |
str |
Cadena original del timbre |
format_currency(amount, moneda) |
callable | Formatea importe |
format_tax_rate(rate) |
callable | Formatea tasa de impuesto |
Crear Template Personalizado
- Crea un directorio con el nombre de tu template:
mis_templates/
└── mi_empresa/
├── template.html
└── styles.css
styles.css— usa siempre tamaño carta:
@page {
size: letter; /* 8.5 × 11 in — tamaño carta México */
margin: 15mm 15mm 20mm 15mm;
}
template.html— usa las variables Jinja2 estándar:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<link rel="stylesheet" href="styles.css">
</head>
<body>
<h1>{{ catalogs.tipo_comprobante }}</h1>
<p>UUID: {{ formatted.uuid }}</p>
<p>Emisor: {{ cfdi.emisor.nombre }}</p>
<p>Total: {{ formatted.total }}</p>
{% if qr_data %}
<img src="data:image/png;base64,{{ qr_data }}" alt="QR SAT">
{% endif %}
</body>
</html>
- Registra la ruta y usa tu template:
from pathlib import Path
from cfdi_pdf import CFDIPDF
pdf = CFDIPDF(
template="mi_empresa",
custom_template_paths=[Path("./mis_templates")],
)
output_path = pdf.render(xml_path="factura.xml")
Estructura del Proyecto
src/cfdi_pdf/
├── api.py # API principal (CFDIPDF)
├── cli.py # Interfaz de línea de comandos
├── exceptions.py # Excepciones personalizadas
├── models/ # Modelos Pydantic v2
│ ├── cfdi.py
│ ├── emisor.py
│ ├── receptor.py
│ ├── concepto.py
│ ├── impuestos.py
│ └── timbre.py
├── parser/ # Parser XML seguro
│ ├── xml_parser.py
│ └── sanitizer.py
├── qr/ # Generador QR SAT oficial
│ └── generator.py
├── render/ # Motor de renderizado
│ ├── engine.py
│ └── template.py
├── sat/ # Catálogos SAT
│ ├── catalogs.py
│ └── helpers.py
├── utils/ # Utilidades
│ └── formatters.py
└── templates/ # Templates incluidos
├── minimal/
│ ├── template.html
│ └── styles.css
├── corporativo/
│ ├── template.html
│ └── styles.css
└── clasico/
├── template.html
└── styles.css
Requisitos
- Python 3.12+
- WeasyPrint >= 60.0
- Pydantic >= 2.0.0
- Jinja2 >= 3.1.0
- qrcode >= 7.4
- Pillow >= 10.0.0
- lxml >= 4.9.0
Desarrollo
Configuración del Entorno
git clone https://github.com/jesusmrv81/xmltopdf.git
cd cfdi-pdf
python -m venv venv
source venv/bin/activate # Linux/macOS
# venv\Scripts\activate # Windows
pip install -e ".[dev]"
Ejecutar Tests
# Todos los tests
pytest
# Con cobertura HTML
pytest --cov=cfdi_pdf --cov-report=html
# Tests específicos
pytest tests/test_parser.py -v
Linting y Formateo
ruff check . # linter
ruff check --fix . # auto-fix
black . # formatter
mypy src # type checker
Build
python -m build
twine upload dist/* # requiere credenciales PyPI
Especificaciones Técnicas
CFDI 4.0
- Namespace:
http://www.sat.gob.mx/cfd/4 - Timbre Fiscal Digital: Versión 1.1
- Catálogos SAT: Completos y actualizados
QR SAT
URL de verificación: https://verificacfdi.facturaelectronica.sat.gob.mx/default.aspx
Parámetros: id (UUID), re (RFC emisor), rr (RFC receptor), tt (total 6 decimales), fe (últimos 8 chars del sello).
Seguridad
- Protección XXE:
resolve_entities=Falseen lxml - XML Bombs:
no_network=True,huge_tree=False - Sandboxing:
SandboxedEnvironmentde Jinja2 - Sanitización: Regex compilado contra caracteres inválidos XML 1.0
Compatibilidad CSS
Los templates evitan intencionalmente propiedades CSS experimentales para máxima compatibilidad con motores de impresión:
| Propiedad | Estado |
|---|---|
@page size: letter |
Soportado en todos |
display: table |
Soportado en todos |
display: flex |
Solo en zonas decorativas no-fiscales |
display: grid |
No usado |
CSS variables (var(--)) |
No usado |
:nth-child en tablas |
No usado — zebra con clases explícitas |
border-radius (clasico) |
No usado |
Roadmap
- Soporte para complemento Pagos 2.0
- Soporte para complemento Nómina 1.2
- Soporte para Carta Porte 3.1
- Validación contra esquemas XSD del SAT
- API REST lista para deploy
- Integración con PACs
Licencia
MIT License — ver archivo LICENSE para detalles.
Soporte
- Bugs / Features: GitHub Issues
- Discusiones: GitHub Discussions
Nota: Esta biblioteca no está afiliada oficialmente con el SAT. Verifica siempre la validez de los CFDIs en el portal oficial del SAT.
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file cfdi_pdf-0.1.3.tar.gz.
File metadata
- Download URL: cfdi_pdf-0.1.3.tar.gz
- Upload date:
- Size: 45.0 kB
- Tags: Source
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
438c0cc4dd828fe2612641c9de1928efeecbe840ccfa1071381cd9636bb1f33c
|
|
| MD5 |
7fc4ebf0a8f6bcfd171ca8a496626b7e
|
|
| BLAKE2b-256 |
21f4624e95d0b1bf7d135a99fe1c9b2036c51c2aa351c50a29b9b81f34657f87
|
Provenance
The following attestation bundles were made for cfdi_pdf-0.1.3.tar.gz:
Publisher:
ci.yml on jesusmrv81/xmltopdf
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
cfdi_pdf-0.1.3.tar.gz -
Subject digest:
438c0cc4dd828fe2612641c9de1928efeecbe840ccfa1071381cd9636bb1f33c - Sigstore transparency entry: 1645120808
- Sigstore integration time:
-
Permalink:
jesusmrv81/xmltopdf@e8f23c12f3ba7fa06175ed064fff5dec149df65b -
Branch / Tag:
refs/heads/main - Owner: https://github.com/jesusmrv81
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
ci.yml@e8f23c12f3ba7fa06175ed064fff5dec149df65b -
Trigger Event:
push
-
Statement type:
File details
Details for the file cfdi_pdf-0.1.3-py3-none-any.whl.
File metadata
- Download URL: cfdi_pdf-0.1.3-py3-none-any.whl
- Upload date:
- Size: 49.3 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? Yes
- Uploaded via: twine/6.1.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
052ffe6cd9a322c1298a2aa86004eee873979ce6d63803aca83288361bff2be6
|
|
| MD5 |
657240d401f7f88cb0f058cb38270104
|
|
| BLAKE2b-256 |
f34ae81eafc4ff4fa17999f55a24cdc138fd0cac22ceccd2245b4221c346a769
|
Provenance
The following attestation bundles were made for cfdi_pdf-0.1.3-py3-none-any.whl:
Publisher:
ci.yml on jesusmrv81/xmltopdf
-
Statement:
-
Statement type:
https://in-toto.io/Statement/v1 -
Predicate type:
https://docs.pypi.org/attestations/publish/v1 -
Subject name:
cfdi_pdf-0.1.3-py3-none-any.whl -
Subject digest:
052ffe6cd9a322c1298a2aa86004eee873979ce6d63803aca83288361bff2be6 - Sigstore transparency entry: 1645120921
- Sigstore integration time:
-
Permalink:
jesusmrv81/xmltopdf@e8f23c12f3ba7fa06175ed064fff5dec149df65b -
Branch / Tag:
refs/heads/main - Owner: https://github.com/jesusmrv81
-
Access:
public
-
Token Issuer:
https://token.actions.githubusercontent.com -
Runner Environment:
github-hosted -
Publication workflow:
ci.yml@e8f23c12f3ba7fa06175ed064fff5dec149df65b -
Trigger Event:
push
-
Statement type: