¿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) │
└─────────────────┘ └──────────────┘ └─────────────┘
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
- Ve a billmeperu.com
- Crea una cuenta gratuita
- 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 🔑
💻 Implementación en Python
📦 Dependencias Necesarias
pip install requests python-dotenv
🔧 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
🗂️ 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()
📁 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")
🔐 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
🚀 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
📊 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)
📈 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
}
🛡️ 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')
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])
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}")
⚡ 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)
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)
🎯 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
- Integrar con tu ERP/CRM existente
- Implementar webhooks para notificaciones
- Crear dashboard web con Flask/Django
- Añadir más tipos de comprobantes (notas de crédito, etc.)
- 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)