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
- Bloquea la transacción con
BEGIN IMMEDIATEsi aún no está en curso. - Consulta la última factura emitida del sistema (cualquier serie) que tenga
verifactu_hashasignado, excluyendo la factura actual. - Lee
verifactu_hashde esa factura comohash_anterior(cadena vacía si es la primera). - Llama a
calcular_hash_registrocon los datos de la factura actual y elhash_anterior. - Construye el objeto
RegistroVerifactucon todos los campos. - Escribe
factura.verifactu_hashyfactura.verifactu_hash_anterioren el objeto ORM (dentro de la transacción, antes del commit). - Si
persistir_json=True, persiste el JSON en disco en este momento; de lo contrario, el llamador debe invocarguardar_jsontras 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.RegistroVerifactu con todos los campos populados, incluidos los hashes.
guardar_json
{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.Path del archivo JSON creado en ~/.lefa/verifactu/registros/.
ruta_registro
int
required
ID de la factura.
str
required
Número oficial de la factura (p. ej.
"FACT-2026-0001").Path a ~/.lefa/verifactu/registros/{factura_id}_{numero_factura}.json.
calcular_hash_registro — hash.py
|, construida en el siguiente orden:
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 "".Módulo qr.py
Constantes
texto_legal_qr
bool | None
Si es
None (valor por defecto), se lee la constante VERIFACTU_MODO_VERIFACTU de lefa.config."VERI*FACTU" o "SISTEMA INFORMÁTICO NO VERIFICADO".
url_verificacion
- 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.urllib.parse.quote.
generar_qr_png
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.Path del archivo PNG generado.
hash_para_url
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
~/.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.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 porurl_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).