Ir al contenido

Tu primer documento

Este ejemplo crea una factura electrónica (Invoice), enriquece sus campos calculados, la renderiza a XML UBL 2.1, la firma digitalmente y la valida contra las reglas de SUNAT.

from openubl.models import Invoice, Proveedor, Cliente, DocumentoVentaDetalle

Define solo los datos de negocio. openUBL calculará los totales, impuestos y precios de venta automáticamente en el siguiente paso.

invoice = Invoice(
serie="F001",
numero=1,
proveedor=Proveedor(
ruc="12345678901",
razonSocial="Mi Empresa S.A.C.",
nombreComercial="Mi Empresa",
address={
"ubigeo": "150101",
"departamento": "Lima",
"provincia": "Lima",
"distrito": "Lima",
"direccion": "Av. Ejemplo 123",
},
),
cliente=Cliente(
tipoDocumentoIdentidad="6",
numeroDocumentoIdentidad="98765432101",
nombre="Cliente Ejemplo S.A.C.",
address={
"ubigeo": "150101",
"departamento": "Lima",
"provincia": "Lima",
"distrito": "Lima",
"direccion": "Calle Ficticia 456",
},
),
detalles=[
DocumentoVentaDetalle(
cantidad=2,
descripcion="Producto de ejemplo",
precio=50.00,
unidadMedida="NIU",
)
],
moneda="PEN",
)

Paso 3: Enriquecimiento automático con ContentEnricher

Sección titulada «Paso 3: Enriquecimiento automático con ContentEnricher»

ContentEnricher calcula los campos faltantes de cada línea y los totales del documento:

  • fechaEmision → fecha actual (si no se proporcionó)
  • valorVenta, igv, precioVenta → por cada línea
  • valorVentaTotal, igvTotal, importeTotal → a nivel de documento
from openubl.enricher import ContentEnricher
enricher = ContentEnricher()
enricher.enrich(invoice)
assert invoice.valorVentaTotal == 100.00
assert invoice.igvTotal == 18.00
assert invoice.importeTotal == 118.00
from openubl.renderer import render_invoice
xml = render_invoice(invoice)
print(xml)

El XML generado es válido UBL 2.1 y está listo para firmar y enviar a SUNAT.

SUNAT exige que el XML esté firmado digitalmente con un certificado X.509 válido. openUBL usa RSA-SHA-256, digestión SHA-256 y canonicalización C14N, colocando la firma XMLDSig dentro de UBLExtension/ExtensionContent, conforme a la estructura exigida por SUNAT y a los requisitos SHA-2 de INDECOPI/IOFE y la Directiva PCM 002-2024.

Necesitas el certificado y la clave privada en formato PEM. Si tienes un archivo .pfx, conviértelo primero:

from openubl.signer import load_pfx, sign_ubl_xml
with open("certificado.pfx", "rb") as f:
key_pem, cert_pem = load_pfx(f.read(), password="mi-password")
signed_xml = sign_ubl_xml(xml, cert_pem=cert_pem, key_pem=key_pem)
print(signed_xml)

Paso 6: Validación explícita con SunatValidator

Sección titulada «Paso 6: Validación explícita con SunatValidator»
from openubl.validator import SunatValidator
validator = SunatValidator()
errors = validator.validate_invoice(xml)
assert len(errors) == 0, f"Errores de validación: {errors}"

Si la lista de errores está vacía, el documento superó las validaciones de SUNAT y puede enviarse a través de los servicios web de la entidad.

Con estos seis pasos has creado, enriquecido, renderizado, firmado y validado una factura electrónica UBL 2.1 lista para SUNAT:

  1. Importar los modelos.
  2. Crear la factura con datos de negocio.
  3. Enriquecer automáticamente totales e impuestos.
  4. Renderizar a XML UBL 2.1.
  5. Firmar digitalmente el XML.
  6. Validar contra las reglas de SUNAT.

El siguiente paso es empaquetar el XML en un ZIP con la convención de nombres de SUNAT y enviarlo a la plataforma de facturación electrónica.