Skip to main content
LEFA implementa los requisitos técnicos del Sistema de Información de Facturación (SIF) definidos en el Real Decreto de facturación y la Orden HAC/1177/2024, a través de cuatro módulos independientes ubicados en lefa/verifactu/. El módulo hash.py calcula el hash SHA-256 encadenado de cada factura emitida. El módulo registro.py genera y persiste en disco el registro JSON inmutable de cada emisión. El módulo qr.py construye la URL de cotejo AEAT y produce la imagen PNG del código QR. El módulo export.py empaqueta todos los registros en un archivo ZIP apto para auditoría o envío futuro a la AEAT. La cadena de hash es única para todo el SIF: las series FACT, RECT y cualquier otra comparten el mismo hilo cronológico; una rectificativa enlaza con la última factura emitida, no con la rectificativa anterior.

RegistroVerifactuService — registro.py

Crea y almacena registros encadenados en disco y en la base de datos. Debe operar siempre dentro de una transacción BEGIN IMMEDIATE para garantizar que la lectura del último hash y la escritura del nuevo sean atómicas.

crear_registro_emision

Genera el registro VeriFactu para una factura recién emitida y asigna los campos de hash en el objeto ORM. El flujo interno es estrictamente ordenado para garantizar la integridad de la cadena:
  1. Bloquea la transacción con BEGIN IMMEDIATE si aún no está en curso.
  2. Consulta la última factura emitida del sistema (cualquier serie) que tenga verifactu_hash asignado, excluyendo la factura actual.
  3. Lee verifactu_hash de esa factura como hash_anterior (cadena vacía si es la primera).
  4. Llama a calcular_hash_registro con los datos de la factura actual y el hash_anterior.
  5. Construye el objeto RegistroVerifactu con todos los campos.
  6. Escribe factura.verifactu_hash y factura.verifactu_hash_anterior en el objeto ORM (dentro de la transacción, antes del commit).
  7. Si persistir_json=True, persiste el JSON en disco en este momento; de lo contrario, el llamador debe invocar guardar_json tras el commit.
Session
required
Sesión SQLAlchemy activa, idealmente abierta con session_scope_immediate() (transacción BEGIN IMMEDIATE).
Factura
required
Objeto Factura ORM con numero_factura y fecha_emision ya asignados (dentro de la misma transacción de emisión).
bool
Si es True, escribe el JSON en disco inmediatamente. Por defecto False; la escritura en disco se realiza tras el commit exitoso para evitar archivos huérfanos en caso de rollback.
Retorna: RegistroVerifactu con todos los campos populados, incluidos los hashes.

guardar_json

Persiste el registro en disco en formato JSON con indentación de 2 espacios y codificación UTF-8. El nombre del archivo es {factura_id}_{numero_factura}.json (con / sustituido por -). Debe llamarse tras el commit de la transacción de emisión para evitar archivos con datos de una transacción fallida.
RegistroVerifactu
required
Objeto RegistroVerifactu devuelto por crear_registro_emision.
Retorna: Path del archivo JSON creado en ~/.lefa/verifactu/registros/.

ruta_registro

Calcula la ruta esperada del archivo JSON de registro sin leerlo ni crearlo.
int
required
ID de la factura.
str
required
Número oficial de la factura (p. ej. "FACT-2026-0001").
Retorna: Path a ~/.lefa/verifactu/registros/{factura_id}_{numero_factura}.json.

calcular_hash_registro — hash.py

Calcula el hash SHA-256 del registro de facturación. El payload es una cadena con los campos separados por |, construida en el siguiente orden:
El NIF se convierte a mayúsculas y se recortan los espacios. La fecha se formatea como DD-MM-YYYY. El importe se formatea con exactamente dos decimales. El hash_anterior puede ser una cadena vacía para la primera factura del sistema.
str
required
NIF/CIF del emisor en mayúsculas (p. ej. "12345678A"). Se normaliza internamente.
str
required
Número oficial de la factura ya asignado.
date
required
Fecha de emisión de la factura.
float
required
Total neto de la factura (subtotal + IVA − IRPF), redondeado a 2 decimales.
str
Hash SHA-256 hexadecimal (64 caracteres) del registro inmediatamente anterior en la cadena. Cadena vacía "" para la primera factura. Por defecto "".
Retorna: Hash SHA-256 en hexadecimal en minúsculas (64 caracteres). Ejemplo de payload:
(hash_anterior vacío para la primera factura)
(con hash de la factura anterior)

Módulo qr.py

Constantes


Devuelve la leyenda legal obligatoria que debe figurar junto al código QR en la factura impresa, según la modalidad configurada.
bool | None
Si es None (valor por defecto), se lee la constante VERIFACTU_MODO_VERIFACTU de lefa.config.
Retorna: "VERI*FACTU" o "SISTEMA INFORMÁTICO NO VERIFICADO".

url_verificacion

Construye la URL de cotejo en la sede electrónica de la AEAT según las especificaciones técnicas del QR (Orden HAC/1177/2024). El endpoint varía según la modalidad:
  • VeriFactu: …/wlpl/TIKE-CONT/ValidarQR?…
  • No-VeriFactu: …/wlpl/TIKE-CONT/ValidarQRNoVerifactu?…
str
required
NIF/CIF del emisor. Se convierte a mayúsculas y se recortan espacios.
str
required
Número oficial de la factura.
date
required
Fecha de emisión de la factura.
float
required
Total neto de la factura con punto decimal y dos decimales.
str | None
Hash SHA-256 del registro VeriFactu (64 hex en minúsculas). Solo se incluye en la URL en modo VeriFactu y si no es None ni vacío.
bool | None
Si es None, se lee de VERIFACTU_MODO_VERIFACTU en lefa.config.
Retorna: URL completa con todos los parámetros codificados con urllib.parse.quote.

generar_qr_png

Genera una imagen PNG del código QR con la URL de verificación, usando la biblioteca qrcode. El directorio destino se crea automáticamente si no existe.
str
required
URL de verificación AEAT a codificar en el QR.
Path
required
Ruta completa del archivo PNG de salida.
int
Tamaño de la imagen resultante en píxeles (ancho y alto). Por defecto 100.
Retorna: Path del archivo PNG generado.

hash_para_url

Normaliza el hash SHA-256 para incluirlo en la URL AEAT: lo convierte a minúsculas y recorta espacios. Devuelve cadena vacía si hash_registro es None o vacío. Centraliza el ajuste del formato del hash si la AEAT modificara la especificación.

VerifactuExport — export.py

exportar_zip

Empaqueta todos los archivos JSON de ~/.lefa/verifactu/registros/ en un archivo ZIP comprimido (deflate) legible por máquinas. Incluye además un archivo indice.json en la raíz del ZIP con el contenido de todos los registros en un array JSON.
Path | None
Ruta completa del archivo ZIP de salida. Si es None, se genera automáticamente como verifactu_export_{fecha_hoy}.zip en el directorio padre de registros/ (normalmente ~/.lefa/verifactu/). Si la ruta no termina en .zip, la extensión se añade automáticamente.
Retorna: Path del archivo ZIP generado. Estructura del ZIP:

Formato del registro JSON

Cada archivo JSON en ~/.lefa/verifactu/registros/ corresponde a una RegistroVerifactu serializada con dataclasses.asdict. La estructura completa es la siguiente:

URL AEAT — formato completo

La URL construida por url_verificacion sigue la estructura siguiente:
En modo no-VeriFactu, el endpoint es ValidarQRNoVerifactu y el parámetro hash se omite.
La URL base (VERIFACTU_URL_BASE) y la activación del modo VeriFactu (VERIFACTU_MODO_VERIFACTU) se configuran en lefa/config.py. El valor por defecto apunta al entorno de preproducción de la AEAT (prewww2.aeat.es).

Ejemplo de uso completo