La facturacion electronica en Chile es obligatoria para la mayoria de contribuyentes. Integrarla correctamente requiere entender el flujo completo: desde la generacion del XML hasta la verificacion del estado.

Componentes del Sistema

┌─────────────┐     ┌──────────────┐     ┌─────────┐
│  Tu Sistema │────▶│ XML + Firma  │────▶│   SII   │
└─────────────┘     └──────────────┘     └─────────┘
       │                   │                   │
       │              Certificado              │
       │               Digital                 │
       ▼                                       ▼
┌─────────────┐                         ┌─────────┐
│     PDF     │                         │ Estado  │
│   + QR TED  │◀────────────────────────│ (SOAP)  │
└─────────────┘                         └─────────┘

1. Obtener Token de Sesion

El SII requiere autenticacion via SOAP con tu certificado digital:

import zeep
from signxml import XMLSigner

def obtener_token(cert_path: str, key_path: str) -> str:
    """Obtiene token de sesion del SII."""

    # 1. Obtener semilla
    client = zeep.Client('https://palena.sii.cl/DTEWS/CrSeed.jws?WSDL')
    response = client.service.getSeed()

    # 2. Firmar semilla con certificado
    semilla_firmada = firmar_xml(response, cert_path, key_path)

    # 3. Obtener token
    client = zeep.Client('https://palena.sii.cl/DTEWS/GetTokenFromSeed.jws?WSDL')
    token_response = client.service.getToken(semilla_firmada)

    return extraer_token(token_response)

2. Construir el DTE (Documento Tributario Electronico)

El XML debe seguir el schema exacto del SII:

from lxml import etree

def construir_boleta(venta: Venta, folio: int) -> str:
    """Construye XML de Boleta Electronica (tipo 39)."""

    dte = etree.Element('DTE', version='1.0')
    documento = etree.SubElement(dte, 'Documento', ID=f'DTE-{folio}')

    # Encabezado
    encabezado = etree.SubElement(documento, 'Encabezado')
    id_doc = etree.SubElement(encabezado, 'IdDoc')
    etree.SubElement(id_doc, 'TipoDTE').text = '39'  # Boleta
    etree.SubElement(id_doc, 'Folio').text = str(folio)
    etree.SubElement(id_doc, 'FchEmis').text = venta.fecha.isoformat()

    # Emisor
    emisor = etree.SubElement(encabezado, 'Emisor')
    etree.SubElement(emisor, 'RUTEmisor').text = venta.empresa.rut
    etree.SubElement(emisor, 'RznSoc').text = venta.empresa.razon_social

    # Detalle de items
    for i, item in enumerate(venta.items.all(), 1):
        detalle = etree.SubElement(documento, 'Detalle')
        etree.SubElement(detalle, 'NroLinDet').text = str(i)
        etree.SubElement(detalle, 'NmbItem').text = item.producto.nombre
        etree.SubElement(detalle, 'QtyItem').text = str(item.cantidad)
        etree.SubElement(detalle, 'PrcItem').text = str(item.precio)

    return etree.tostring(dte, encoding='unicode')

3. Firmar con Certificado Digital

from signxml import XMLSigner, methods
from cryptography import x509
from cryptography.hazmat.primitives import serialization

def firmar_dte(xml: str, cert_path: str, key_path: str) -> str:
    """Firma XML con certificado .pfx del SII."""

    # Cargar certificado
    with open(cert_path, 'rb') as f:
        cert = x509.load_pem_x509_certificate(f.read())

    with open(key_path, 'rb') as f:
        key = serialization.load_pem_private_key(f.read(), password=None)

    # Firmar
    root = etree.fromstring(xml.encode())
    signer = XMLSigner(
        method=methods.enveloped,
        signature_algorithm='rsa-sha1',
        digest_algorithm='sha1'
    )

    signed = signer.sign(root, key=key, cert=cert)
    return etree.tostring(signed, encoding='unicode')

4. Generar TED (Timbre Electronico)

El TED es un codigo que permite verificar la autenticidad:

import qrcode
from io import BytesIO

def generar_ted(dte_firmado: str) -> bytes:
    """Genera QR con el TED para impresion."""

    # Extraer datos del TED del XML firmado
    root = etree.fromstring(dte_firmado.encode())
    ted = root.find('.//TED')
    ted_string = etree.tostring(ted, encoding='unicode')

    # Generar QR
    qr = qrcode.QRCode(version=1, box_size=3)
    qr.add_data(ted_string)
    qr.make(fit=True)

    img = qr.make_image()
    buffer = BytesIO()
    img.save(buffer, format='PNG')
    return buffer.getvalue()

5. Verificar Estado

def verificar_estado(rut_emisor: str, tipo_dte: int, folio: int) -> dict:
    """Consulta estado de un DTE en el SII."""

    client = zeep.Client(
        'https://palena.sii.cl/DTEWS/QueryEstDte.jws?WSDL'
    )

    response = client.service.getEstDte(
        RutConsultante=rut_emisor,
        DvConsultante=calcular_dv(rut_emisor),
        RutCompania=rut_emisor,
        DvCompania=calcular_dv(rut_emisor),
        RutReceptor='66666666',  # Boletas van a RUT generico
        DvReceptor='6',
        TipoDte=tipo_dte,
        FolioDte=folio,
        Token=obtener_token()
    )

    return {
        'estado': response.ESTADO,
        'glosa': response.GLOSA_ESTADO,
        'aceptado': response.ESTADO == 'DOK'
    }

Sistema de Reintentos

Los envios pueden fallar. Implementa reintentos robustos:

from celery import shared_task
from datetime import timedelta

@shared_task(bind=True, max_retries=3)
def enviar_dte_async(self, dte_id: int):
    try:
        dte = DTE.objects.get(id=dte_id)
        resultado = enviar_al_sii(dte)
        dte.estado = resultado['estado']
        dte.save()
    except Exception as e:
        # Reintentar en 30 minutos
        raise self.retry(exc=e, countdown=1800)

Conclusion

La integracion con el SII es compleja pero manejable si divides el problema en pasos claros. Lo mas importante es manejar bien los errores y tener un sistema de reintentos robusto.