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.
Paso 1: Importar el modelo
Sección titulada «Paso 1: Importar el modelo»from openubl.models import Invoice, Proveedor, Cliente, DocumentoVentaDetalleimport { createInvoice } from "@openubl/sdk";import { zInvoice } from "@openubl/sdk/zod.gen";# No se requieren imports. Asegúrate de tener curl instalado.Paso 2: Crear la factura
Sección titulada «Paso 2: Crear la factura»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",)const invoice = zInvoice.parse({serie: "F001",numero: 1,tipoOperacion: "0101",moneda: "PEN",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", codigoPais: "PE", },},cliente: { tipoDocumentoIdentidad: "6", numeroDocumentoIdentidad: "98765432101", nombre: "Cliente Ejemplo S.A.C.",},detalles: [ { cantidad: 2, descripcion: "Producto de ejemplo", precio: 50.00, unidadMedida: "NIU", tipoAfectacionIGV: "10", },],});curl -X POST http://localhost:8000/api/v1/invoice/create \-H "Content-Type: application/json" \-d '{ "serie": "F001", "numero": 1, "tipoOperacion": "0101", "moneda": "PEN", "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", "codigoPais": "PE" } }, "cliente": { "tipoDocumentoIdentidad": "6", "numeroDocumentoIdentidad": "98765432101", "nombre": "Cliente Ejemplo S.A.C." }, "detalles": [ { "cantidad": 2, "descripcion": "Producto de ejemplo", "precio": 50.00, "unidadMedida": "NIU", "tipoAfectacionIGV": "10" } ]}'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íneavalorVentaTotal,igvTotal,importeTotal→ a nivel de documento
from openubl.enricher import ContentEnricher
enricher = ContentEnricher()enricher.enrich(invoice)
assert invoice.valorVentaTotal == 100.00assert invoice.igvTotal == 18.00assert invoice.importeTotal == 118.00// El endpoint de la API enriquece automáticamente al crear el documento.const { data, error } = await createInvoice({body: invoice,});
if (error) {throw new Error(JSON.stringify(error));}
console.log(data.xml);# El endpoint calcula totales automáticamente a partir del JSON enviado en el paso 2.curl -X POST http://localhost:8000/api/v1/invoice/create -H "Content-Type: application/json" -d @invoice.jsonPaso 4: Renderizar a XML
Sección titulada «Paso 4: Renderizar a XML»from openubl.renderer import render_invoice
xml = render_invoice(invoice)print(xml)// Reutiliza la llamada del paso 3.console.log(data.xml);curl -X POST http://localhost:8000/api/v1/invoice/create -H "Content-Type: application/json" -d @invoice.json | jq '.xml'El XML generado es válido UBL 2.1 y está listo para firmar y enviar a SUNAT.
Paso 5: Firma digital
Sección titulada «Paso 5: Firma digital»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)import { zSignXmlBody } from "@openubl/sdk/zod.gen";
const body = zSignXmlBody.parse({xml,cert_pem: certPem,key_pem: keyPem,});
const { data, error } = await signXml({body,});
if (error) {throw new Error(JSON.stringify(error));}
console.log(data.signed_xml);curl -X POST http://localhost:8000/api/v1/sign \-H "Content-Type: application/json" \-d '{ "xml": "<?xml version=\"1.0\"?><Invoice>...</Invoice>", "cert_pem": "-----BEGIN CERTIFICATE-----\nMIIC...\n-----END CERTIFICATE-----", "key_pem": "-----BEGIN PRIVATE KEY-----\nMIIE...\n-----END PRIVATE KEY-----"}'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}"const { data, error } = await createInvoice({query: { validate: true },body: invoice,});
if (error) {// La API devuelve 422 cuando el XML no cumple las reglas SUNAT.throw new Error(JSON.stringify(error));}
console.log("Factura válida");console.log(data.xml);curl -X POST "http://localhost:8000/api/v1/invoice/create?validate=true" \-H "Content-Type: application/json" \-d @invoice.jsonSi 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.
Conclusión
Sección titulada «Conclusión»Con estos seis pasos has creado, enriquecido, renderizado, firmado y validado una factura electrónica UBL 2.1 lista para SUNAT:
- Importar los modelos.
- Crear la factura con datos de negocio.
- Enriquecer automáticamente totales e impuestos.
- Renderizar a XML UBL 2.1.
- Firmar digitalmente el XML.
- 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.