DEV Community

Luis Dev
Luis Dev

Posted on

🧾 Cómo Automatizar la Facturación Electrónica SUNAT en 5 Minutos con Python

¿Sabías que puedes enviar facturas directamente a SUNAT con solo 20 líneas de código Python? En este tutorial aprenderás a crear un sistema completo de facturación electrónica que ahorrará horas de trabajo manual a tu empresa.

🇵🇪 ¿Por qué la Facturación Electrónica es Obligatoria en Perú?

Desde 2010, SUNAT ha implementado gradualmente la facturación electrónica obligatoria para diferentes tipos de empresas. Si tu empresa tiene ingresos anuales superiores a 150 UIT, debes emitir comprobantes electrónicos.

📊 Beneficios de Automatizar tu Facturación:

  • 95% menos tiempo en emisión de comprobantes
  • 🔒 100% cumplimiento normativo con SUNAT
  • 💰 Ahorro de costos en papel y almacenamiento
  • 📈 Mejor control de inventarios y finanzas
  • 🚀 Integración directa con tu sistema existente

🛠️ Arquitectura de la Solución

┌─────────────────┐    ┌──────────────┐    ┌─────────────┐
│   Tu Sistema    │───▶│  BillMe API  │───▶│    SUNAT    │
│    (Python)     │    │  (Gateway)   │    │ (Oficial)   │
└─────────────────┘    └──────────────┘    └─────────────┘
Enter fullscreen mode Exit fullscreen mode

Usaremos BillMe como gateway porque:

  • ✅ Certificado por SUNAT
  • ✅ API REST simple
  • ✅ Manejo automático de XML y validaciones
  • ✅ Entorno de pruebas gratuito

🚀 Setup Inicial: Creando tu Cuenta de Prueba

Paso 1: Registro en BillMe

  1. Ve a billmeperu.com
  2. Crea una cuenta gratuita
  3. Sigue la documentación oficial

Paso 2: Crear Empresa de Prueba

1. Login → Empresas → Agregar
2. Completa datos ficticios para testing
3. Copia el token de la empresa 🔑
Enter fullscreen mode Exit fullscreen mode

💻 Implementación en Python

📦 Dependencias Necesarias

pip install requests python-dotenv
Enter fullscreen mode Exit fullscreen mode

🔧 Estructura del Proyecto

facturacion_sunat/
├── .env                 # Variables de entorno
├── main.py             # Script principal
├── config.py           # Configuración
├── models.py           # Modelos de datos
└── billme_client.py    # Cliente API
Enter fullscreen mode Exit fullscreen mode

🗂️ Código Principal

# main.py - Sistema completo de facturación electrónica
import os
import requests
import json
from datetime import datetime, date
from typing import Dict, List, Optional
from dataclasses import dataclass, asdict
from dotenv import load_dotenv

# Cargar variables de entorno
load_dotenv()

@dataclass
class Emisor:
    """Datos del emisor (tu empresa)"""
    codigo_tipo_documento: str = "6"  # RUC
    num_documento: str = ""
    razon_social: str = ""
    nombre_comercial: str = ""
    ubigeo: str = ""
    ciudad: str = ""
    distrito: str = ""
    provincia: str = ""
    direccion: str = ""

@dataclass
class Cliente:
    """Datos del cliente"""
    codigo_tipo_documento: str  # 1=DNI, 6=RUC
    num_documento: str
    razon_social: str
    nombre_comercial: str = ""
    ubigeo: str = ""
    ciudad: str = ""
    distrito: str = ""
    provincia: str = ""
    direccion: str = ""

@dataclass
class Impuesto:
    """Estructura de impuestos (IGV, ISC, etc.)"""
    monto: float
    id_categoria: str = "S"  # S=Sujeto, E=Exonerado, I=Inafecto
    porcentaje: float = 18.0
    codigo_afectacion_igv: str = "10"
    codigo_inter_tributo: str = "VAT"
    nombre_tributo: str = "IGV"
    codigo_tributo: str = "1000"

@dataclass
class Producto:
    """Producto o servicio a facturar"""
    unidades: float
    codigo_unidad: str = "NIU"  # Unidad de medida
    nombre: str = ""
    moneda: str = "PEN"
    precio_unitario: float = 0.0
    precio_lista: str = "0.00"
    monto_sin_impuesto: float = 0.0
    monto_impuestos: float = 0.0
    monto_total: float = 0.0
    monto_icbper: float = 0.0
    factor_icbper: float = 0.0
    monto_descuento: float = 0.0
    codigo_tipo_precio: str = "01"
    impuestos: List[Impuesto] = None
    id: str = ""
    codigo_clasificacion: str = ""

    def __post_init__(self):
        if self.impuestos is None:
            # Calcular IGV automáticamente
            igv_monto = round(self.precio_unitario * 0.18, 2)
            self.monto_sin_impuesto = self.precio_unitario
            self.monto_impuestos = igv_monto
            self.monto_total = self.precio_unitario
            self.precio_lista = f"{self.precio_unitario + igv_monto:.2f}"

            self.impuestos = [Impuesto(
                monto=igv_monto,
                porcentaje=18.0
            )]

@dataclass
class Totales:
    """Totales de la factura"""
    total_op_gravadas: float = 0.0
    total_op_inafectas: float = 0.0
    total_op_exoneradas: float = 0.0
    total_impuestos: float = 0.0
    total_sin_impuestos: float = 0.0
    total_con_impuestos: float = 0.0
    total_pagar: float = 0.0
    total_descuento_global: float = 0.0
    total_descuento_productos: float = 0.0

class FacturacionSUNAT:
    """Cliente principal para facturación electrónica"""

    def __init__(self, token: str, base_url: str = None):
        self.token = token
        self.base_url = base_url or "https://www.api.billmeperu.com/api/v1"
        self.session = requests.Session()
        self.session.headers.update({
            'Content-Type': 'application/json',
            'token': self.token,
            'Accept': 'application/json'
        })

    def _calcular_totales(self, productos: List[Producto]) -> Totales:
        """Calcula automáticamente los totales de la factura"""
        total_sin_impuestos = sum(p.monto_sin_impuesto * p.unidades for p in productos)
        total_impuestos = sum(p.monto_impuestos * p.unidades for p in productos)

        return Totales(
            total_op_gravadas=total_sin_impuestos,
            total_impuestos=total_impuestos,
            total_sin_impuestos=total_sin_impuestos,
            total_con_impuestos=total_sin_impuestos + total_impuestos,
            total_pagar=total_sin_impuestos + total_impuestos
        )

    def emitir_factura(
        self,
        serie: str,
        correlativo: str,
        emisor: Emisor,
        cliente: Cliente,
        productos: List[Producto],
        fecha_emision: date = None,
        forma_pago: str = "Contado"
    ) -> Dict:
        """
        Emite una factura electrónica a SUNAT

        Args:
            serie: Serie de la factura (ej: F001)
            correlativo: Correlativo (ej: 00000123)
            emisor: Datos de tu empresa
            cliente: Datos del cliente
            productos: Lista de productos/servicios
            fecha_emision: Fecha de emisión (hoy por defecto)
            forma_pago: Forma de pago

        Returns:
            Respuesta de la API con el resultado
        """
        if fecha_emision is None:
            fecha_emision = date.today()

        # Calcular totales automáticamente
        totales = self._calcular_totales(productos)

        # Construir payload
        data = {
            "tipoOperacion": "010",
            "serie": serie,
            "correlativo": correlativo,
            "fechaEmision": fecha_emision.strftime("%Y-%m-%d"),
            "horaEmision": datetime.now().strftime("%H:%M:%S"),
            "fechaVencimiento": fecha_emision.strftime("%Y-%m-%d"),
            "codigoTipoOperacion": "0101",
            "codigoTipoDocumento": "01",  # Factura
            "moneda": "PEN",
            "montoCredito": 0,
            "formaPago": forma_pago,
            "igv": totales.total_impuestos,
            "icbper": 0,
            "cuotas": [],
            "emisor": asdict(emisor),
            "cliente": asdict(cliente),
            "totales": asdict(totales),
            "productos": [asdict(p) for p in productos]
        }

        return self._enviar_comprobante(data)

    def emitir_boleta(
        self,
        serie: str,
        correlativo: str,
        emisor: Emisor,
        cliente: Cliente,
        productos: List[Producto],
        fecha_emision: date = None
    ) -> Dict:
        """Emite una boleta de venta electrónica"""
        # Similar a factura pero con código de documento "03"
        data = self._construir_payload_boleta(serie, correlativo, emisor, cliente, productos, fecha_emision)
        return self._enviar_comprobante(data)

    def _enviar_comprobante(self, data: Dict) -> Dict:
        """Envía el comprobante a la API de BillMe"""
        url = f"{self.base_url}/Emission/EnviarBoletaFactura"

        try:
            print(f"🚀 Enviando comprobante {data['serie']}-{data['correlativo']}...")

            response = self.session.post(url, json=data, timeout=30)
            response.raise_for_status()

            result = response.json()

            if response.status_code == 200:
                print("✅ Comprobante enviado exitosamente!")
                print(f"📄 Número: {data['serie']}-{data['correlativo']}")
                print(f"💰 Total: S/ {data['totales']['totalPagar']}")

            return result

        except requests.exceptions.Timeout:
            raise Exception("⏰ Timeout: La API tardó mucho en responder")
        except requests.exceptions.HTTPError as e:
            if response.status_code == 401:
                raise Exception("🔐 Token inválido o expirado")
            elif response.status_code == 400:
                raise Exception(f"📋 Datos inválidos: {response.text}")
            else:
                raise Exception(f"❌ Error HTTP {response.status_code}: {response.text}")
        except requests.exceptions.RequestException as e:
            raise Exception(f"🌐 Error de conexión: {str(e)}")

# Ejemplos de uso prácticos
def ejemplo_factura_simple():
    """Ejemplo 1: Factura simple para servicios"""

    # Configurar cliente
    token = os.getenv('BILLME_TOKEN', 'tu-token-aqui')
    cliente_api = FacturacionSUNAT(token)

    # Datos del emisor (tu empresa)
    mi_empresa = Emisor(
        num_documento="20123456789",
        razon_social="MI EMPRESA SAC",
        nombre_comercial="MI EMPRESA",
        ubigeo="150101",
        ciudad="LIMA",
        distrito="LIMA",
        provincia="LIMA",
        direccion="AV. PRINCIPAL 123"
    )

    # Datos del cliente
    cliente = Cliente(
        codigo_tipo_documento="1",  # DNI
        num_documento="12345678",
        razon_social="JUAN PÉREZ LÓPEZ"
    )

    # Producto/servicio
    productos = [
        Producto(
            unidades=1,
            nombre="Desarrollo de Sistema Web",
            precio_unitario=1000.00,
            id="SERV001"
        )
    ]

    # Emitir factura
    try:
        resultado = cliente_api.emitir_factura(
            serie="F001",
            correlativo="00000123",
            emisor=mi_empresa,
            cliente=cliente,
            productos=productos
        )

        print("🎉 Factura emitida exitosamente!")
        return resultado

    except Exception as e:
        print(f"❌ Error: {e}")
        return None

def ejemplo_factura_multiple_productos():
    """Ejemplo 2: Factura con múltiples productos"""

    token = os.getenv('BILLME_TOKEN')
    cliente_api = FacturacionSUNAT(token)

    # Múltiples productos
    productos = [
        Producto(
            unidades=2,
            nombre="Laptop Dell Inspiron",
            precio_unitario=1500.00,
            id="LAP001"
        ),
        Producto(
            unidades=1,
            nombre="Mouse Inalámbrico",
            precio_unitario=50.00,
            id="MOU001"
        ),
        Producto(
            unidades=3,
            nombre="Cable HDMI",
            precio_unitario=25.00,
            id="CAB001"
        )
    ]

    # ... resto del código similar

def ejemplo_integracion_base_datos():
    """Ejemplo 3: Integración con base de datos"""

    # Simulación de datos desde BD
    pedidos_pendientes = [
        {
            'id': 1001,
            'cliente_ruc': '20567890123',
            'cliente_nombre': 'EMPRESA CLIENTE SAC',
            'productos': [
                {'nombre': 'Servicio de Consultoría', 'precio': 2000.00, 'cantidad': 1}
            ]
        }
    ]

    cliente_api = FacturacionSUNAT(os.getenv('BILLME_TOKEN'))

    for pedido in pedidos_pendientes:
        # Convertir datos de BD a objetos del sistema
        cliente = Cliente(
            codigo_tipo_documento="6",  # RUC
            num_documento=pedido['cliente_ruc'],
            razon_social=pedido['cliente_nombre']
        )

        productos = [
            Producto(
                unidades=item['cantidad'],
                nombre=item['nombre'],
                precio_unitario=item['precio']
            ) for item in pedido['productos']
        ]

        # Emitir factura automáticamente
        try:
            resultado = cliente_api.emitir_factura(
                serie="F001",
                correlativo=f"{pedido['id']:08d}",
                emisor=mi_empresa,  # Definir globalmente
                cliente=cliente,
                productos=productos
            )
            print(f"✅ Pedido {pedido['id']} facturado exitosamente")

        except Exception as e:
            print(f"❌ Error en pedido {pedido['id']}: {e}")

if __name__ == "__main__":
    print("🧾 Sistema de Facturación Electrónica SUNAT")
    print("=" * 50)

    # Verificar token
    if not os.getenv('BILLME_TOKEN'):
        print("⚠️  Configura tu BILLME_TOKEN en el archivo .env")
        exit(1)

    # Ejecutar ejemplos
    print("\n1️⃣ Ejemplo: Factura Simple")
    ejemplo_factura_simple()

    print("\n2️⃣ Ejemplo: Factura Múltiples Productos")
    # ejemplo_factura_multiple_productos()

    print("\n3️⃣ Ejemplo: Integración con Base de Datos")
    # ejemplo_integracion_base_datos()
Enter fullscreen mode Exit fullscreen mode

📁 Archivo de Configuración

# config.py
import os
from dotenv import load_dotenv

load_dotenv()

class Config:
    BILLME_TOKEN = os.getenv('BILLME_TOKEN')
    BILLME_API_URL = os.getenv('BILLME_API_URL', 'https://www.api.billmeperu.com/api/v1')

    # Configuración de empresa por defecto
    EMPRESA_RUC = os.getenv('EMPRESA_RUC')
    EMPRESA_RAZON_SOCIAL = os.getenv('EMPRESA_RAZON_SOCIAL')

    # Validaciones
    @classmethod
    def validate(cls):
        if not cls.BILLME_TOKEN:
            raise ValueError("❌ BILLME_TOKEN es requerido")
        if not cls.EMPRESA_RUC:
            raise ValueError("❌ EMPRESA_RUC es requerido")
Enter fullscreen mode Exit fullscreen mode

🔐 Archivo .env

# Variables de entorno
BILLME_TOKEN=tu-token-real-de-billme
BILLME_API_URL=https://www.api.billmeperu.com/api/v1

# Datos de tu empresa
EMPRESA_RUC=20123456789
EMPRESA_RAZON_SOCIAL=MI EMPRESA SAC
EMPRESA_DIRECCION=AV. PRINCIPAL 123, LIMA
Enter fullscreen mode Exit fullscreen mode

🚀 Ejecución

# Instalar dependencias
pip install requests python-dotenv

# Configurar variables de entorno
cp .env.example .env
# Editar .env con tus datos reales

# Ejecutar
python main.py
Enter fullscreen mode Exit fullscreen mode

📊 Casos de Uso Avanzados

🔄 Automatización Completa

# scheduler.py - Automatizar facturación diaria
import schedule
import time
from main import FacturacionSUNAT, ejemplo_integracion_base_datos

def facturar_pedidos_pendientes():
    """Ejecutar facturación automática cada día"""
    print("🕐 Iniciando facturación automática...")
    ejemplo_integracion_base_datos()

# Programar ejecución diaria a las 9:00 AM
schedule.every().day.at("09:00").do(facturar_pedidos_pendientes)

while True:
    schedule.run_pending()
    time.sleep(60)
Enter fullscreen mode Exit fullscreen mode

📈 Dashboard de Facturación

# dashboard.py - Métricas de facturación
import pandas as pd
import matplotlib.pyplot as plt
from datetime import datetime, timedelta

class DashboardFacturacion:
    def __init__(self, cliente_api):
        self.cliente = cliente_api
        self.facturas_emitidas = []

    def generar_reporte_mensual(self):
        """Genera reporte de facturación mensual"""
        df = pd.DataFrame(self.facturas_emitidas)

        # Métricas clave
        total_facturado = df['total'].sum()
        cantidad_facturas = len(df)
        promedio_factura = df['total'].mean()

        print(f"📊 REPORTE MENSUAL DE FACTURACIÓN")
        print(f"💰 Total Facturado: S/ {total_facturado:,.2f}")
        print(f"📄 Facturas Emitidas: {cantidad_facturas}")
        print(f"📈 Promedio por Factura: S/ {promedio_factura:,.2f}")

        return {
            'total_facturado': total_facturado,
            'cantidad_facturas': cantidad_facturas,
            'promedio_factura': promedio_factura
        }
Enter fullscreen mode Exit fullscreen mode

🛡️ Mejores Prácticas de Seguridad

1. 🔐 Manejo Seguro de Tokens

# ❌ MAL: Token hardcodeado
token = "abc123"

# ✅ BIEN: Token desde variable de entorno
token = os.getenv('BILLME_TOKEN')
Enter fullscreen mode Exit fullscreen mode

2. 🚨 Validación de Datos

def validar_ruc(ruc: str) -> bool:
    """Valida formato de RUC peruano"""
    if len(ruc) != 11 or not ruc.isdigit():
        return False

    # Algoritmo de validación RUC SUNAT
    factor = [5, 4, 3, 2, 7, 6, 5, 4, 3, 2]
    suma = sum(int(ruc[i]) * factor[i] for i in range(10))
    digito = (11 - (suma % 11)) % 11

    return digito == int(ruc[10])
Enter fullscreen mode Exit fullscreen mode

3. 📝 Logging y Auditoría

import logging

logging.basicConfig(
    level=logging.INFO,
    format='%(asctime)s - %(levelname)s - %(message)s',
    handlers=[
        logging.FileHandler('facturacion.log'),
        logging.StreamHandler()
    ]
)

def log_factura_emitida(serie, correlativo, total):
    logging.info(f"Factura emitida: {serie}-{correlativo} por S/ {total}")
Enter fullscreen mode Exit fullscreen mode

⚡ Optimizaciones de Performance

1. 🔄 Pool de Conexiones

from requests.adapters import HTTPAdapter
from requests.packages.urllib3.util.retry import Retry

session = requests.Session()
retry_strategy = Retry(
    total=3,
    backoff_factor=1,
    status_forcelist=[429, 500, 502, 503, 504]
)
adapter = HTTPAdapter(max_retries=retry_strategy)
session.mount("http://", adapter)
session.mount("https://", adapter)
Enter fullscreen mode Exit fullscreen mode

2. ⚡ Procesamiento Asíncrono

import asyncio
import aiohttp

async def emitir_facturas_lote(facturas):
    """Emite múltiples facturas en paralelo"""
    async with aiohttp.ClientSession() as session:
        tasks = [
            emitir_factura_async(session, factura) 
            for factura in facturas
        ]
        return await asyncio.gather(*tasks)
Enter fullscreen mode Exit fullscreen mode

🎯 Conclusión

Con este sistema en Python has logrado:

  • Automatizar completamente la facturación electrónica
  • Reducir errores humanos en el proceso
  • Cumplir normativas SUNAT automáticamente
  • Integrar fácilmente con sistemas existentes
  • Escalar para miles de facturas diarias

🚀 Próximos Pasos

  1. Integrar con tu ERP/CRM existente
  2. Implementar webhooks para notificaciones
  3. Crear dashboard web con Flask/Django
  4. Añadir más tipos de comprobantes (notas de crédito, etc.)
  5. Implementar facturación recurrente

🤝 ¿Te Gustó Este Tutorial?

  • 👏 Dale like si te fue útil
  • 💬 Comenta tus casos de uso
  • 🔄 Comparte con otros desarrolladores
  • Sígueme para más tutoriales de Python

Happy Coding! 🐍✨

¿Tienes dudas o quieres que cubra algún tema específico? ¡Déjame un comentario!

Top comments (0)