Metadata-Version: 2.4
Name: verifactu-lint
Version: 0.3.0
Summary: Audita registros de facturación ya emitidos contra el RRSIF (RD 1007/2023 y Orden HAC/1177/2024). No genera facturas ni las remite.
Project-URL: Homepage, https://github.com/easybytehub/verifactu-lint
Project-URL: Source, https://github.com/easybytehub/verifactu-lint
Project-URL: Issues, https://github.com/easybytehub/verifactu-lint/issues
Author: EasyByte Hub, S. Coop. Mad.
License-Expression: Apache-2.0
License-File: LICENSE
Keywords: aeat,auditoria,cumplimiento,facturacion,rrsif,verifactu
Classifier: Development Status :: 3 - Alpha
Classifier: Environment :: Console
Classifier: Intended Audience :: Developers
Classifier: License :: OSI Approved :: Apache Software License
Classifier: Natural Language :: Spanish
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Programming Language :: Python :: 3.13
Classifier: Programming Language :: Python :: 3.14
Classifier: Topic :: Office/Business :: Financial :: Accounting
Classifier: Typing :: Typed
Requires-Python: >=3.11
Provides-Extra: dev
Requires-Dist: lxml>=5.0; extra == 'dev'
Requires-Dist: mypy>=1.13; extra == 'dev'
Requires-Dist: pytest>=8.3; extra == 'dev'
Requires-Dist: pyyaml>=6.0; extra == 'dev'
Requires-Dist: ruff>=0.8; extra == 'dev'
Requires-Dist: types-pyyaml>=6.0; extra == 'dev'
Description-Content-Type: text/markdown

# verifactu-lint

[![ci](https://github.com/easybytehub/verifactu-lint/actions/workflows/ci.yml/badge.svg)](https://github.com/easybytehub/verifactu-lint/actions/workflows/ci.yml)
[![OpenSSF Scorecard](https://api.scorecard.dev/projects/github.com/easybytehub/verifactu-lint/badge)](https://scorecard.dev/viewer/?uri=github.com/easybytehub/verifactu-lint)
[![PyPI](https://img.shields.io/pypi/v/verifactu-lint)](https://pypi.org/project/verifactu-lint/)
[![Python](https://img.shields.io/pypi/pyversions/verifactu-lint)](https://pypi.org/project/verifactu-lint/)
[![License](https://img.shields.io/badge/license-Apache--2.0-blue)](LICENSE)

Audita registros de facturación **ya emitidos** contra el Reglamento de requisitos de los sistemas informáticos de facturación (RD 1007/2023) y su Orden de desarrollo (HAC/1177/2024).

Le das el XML que tu sistema genera y te dice dónde incumple, citando el artículo.

```console
$ verifactu-lint registros-2026-01.xml

verifactu-lint · 1.284 registros · registros-2026-01.xml

ERROR      RRSIF003 [#412 FA/2026/0412]: La cadena se rompe en este registro
           Declara como huella anterior 9F2C…A31B, y la huella de #411 FA/2026/0411 es 4D77…C0E9.
           Una cadena rota significa que la secuencia conservada no es la que se generó:
           falta un registro por medio, se reordenaron, o se modificó uno después de emitirlo.
           norma: Orden HAC/1177/2024, art. 13 y especificaciones técnicas de la huella

1 errores · 0 avisos · 0 sin determinar
```

## Qué es, y qué no es

Existen ya varias librerías buenas para **generar** registros Verifactu. Ninguna responde a la pregunta que se hace quien ya tiene un sistema en marcha: *¿lo que llevo emitido cumple?*

Eso es lo que hace esta herramienta.

**No es un sistema informático de facturación.** No expide facturas, no genera registros, no los firma y no los remite a la AEAT. Sólo lee. Por tanto no le corresponde emitir declaración responsable alguna, ni la emite.

**Un resultado sin errores no acredita conformidad.** Significa que estas reglas, sobre esos registros, no han encontrado un incumplimiento. La declaración responsable del artículo 13 del RRSIF la emite el productor del SIF bajo su propia responsabilidad, y ninguna herramienta puede emitirla por él.

## Instalación

```bash
pip install verifactu-lint
```

Sin dependencias en tiempo de ejecución: sólo la biblioteca estándar de Python (3.11+).

## Uso

```bash
# informe legible
verifactu-lint registros.xml

# varios ficheros; cada uno se audita como su propia cadena
verifactu-lint enero.xml febrero.xml

# para tratarlo con jq, o para archivarlo
verifactu-lint registros.xml --formato json

# para GitHub Code Scanning
verifactu-lint registros.xml --formato sarif > verifactu.sarif

# que los avisos también rompan la build
verifactu-lint registros.xml --estricto
```

**Códigos de salida:** `0` sin errores · `1` con errores (o con avisos si `--estricto`) · `2` si el fichero no se pudo leer.

Como librería:

```python
from verifactu_lint.registros import lee
from verifactu_lint.reglas import audita

informe = audita(lee("registros.xml"))
for hallazgo in informe.errores:
    print(hallazgo.regla, hallazgo.titulo, hallazgo.referencia)
```

### En tu CI

Auditar en cada cambio cuesta menos que descubrir la cadena rota en una inspección. Con salida SARIF los hallazgos aparecen en la pestaña **Security** del repositorio, sin que nadie tenga que abrir un log:

```yaml
name: verifactu
on: [push, pull_request]

permissions:
  contents: read

jobs:
  auditar:
    runs-on: ubuntu-latest
    permissions:
      contents: read
      security-events: write
    steps:
      - uses: actions/checkout@v4
      - uses: easybytehub/verifactu-lint@v0.3.0
        with:
          ficheros: "registros/*.xml"
          fallar: "false"      # que no aborte antes de subir el informe
      - uses: github/codeql-action/upload-sarif@v3
        with: { sarif_file: verifactu-lint.sarif }
```

`fallar: false` es deliberado: sin él, un hallazgo abortaría el job **antes** de subir el SARIF y verías que falla sin poder ver por qué. El fallo lo señala la pestaña Security, que es donde se puede leer.

| Entrada | Por defecto | |
|---|---|---|
| `ficheros` | — | Ficheros a auditar. Admite comodines. |
| `formato` | `sarif` | `texto`, `json` o `sarif`. |
| `estricto` | `false` | Que los avisos también hagan fallar. |
| `fallar` | `true` | Si `false`, el paso no falla aunque haya hallazgos. |
| `salida` | `verifactu-lint.sarif` | Fichero del informe. |
| `version` | la de la action | Versión de `verifactu-lint` a instalar. |

Salidas: `errores`, `avisos` (con `formato: json`) y `fichero-informe`.

La action **fija la versión** que instala en vez de coger la última: una action que instala «lo último» cambia de comportamiento sin que nadie haya tocado nada.

Si prefieres no usar la action, la CLI hace lo mismo:

```bash
pip install verifactu-lint
verifactu-lint registros/*.xml --formato sarif > verifactu.sarif
```

## Las reglas

**Registros de facturación** (alta y anulación):

| Regla | Comprueba |
|---|---|
| `RRSIF001` | La huella declarada sale de los campos del registro |
| `RRSIF002` | La huella tiene 64 caracteres hexadecimales en mayúsculas |
| `RRSIF003` | Cada registro encadena con la huella del anterior |
| `RRSIF004` | `PrimerRegistro` y `RegistroAnterior` son coherentes y hay un único inicio de cadena |
| `RRSIF005` | `TipoHuella` es `01` (SHA-256), el único que admite la lista L12 |
| `RRSIF010` | No hay numeración de factura duplicada |
| `RRSIF011` | El SIF se identifica con NIF + `IdSistemaInformatico` + `NumeroInstalacion` |
| `RRSIF012` | `IndicadorMultiplesOT` es coherente con `TipoUsoPosibleMultiOT` |
| `RRSIF013` | La cadena pertenece a un único obligado tributario |
| `RRSIF030` | `TipoFactura` está entre los ocho de la lista L2 |
| `RRSIF031` | Una rectificativa declara su modalidad y qué factura rectifica |
| `RRSIF032` | Los campos de rectificación no aparecen en facturas normales |
| `RRSIF033` | `ImporteRectificacion` va exactamente en las sustitutivas |
| `RRSIF034` | Una F3 identifica las simplificadas a las que sustituye |
| `RRSIF035` | `Subsanacion` y `RechazoPrevio` son válidos y no se confunden con rectificar |
| `RRSIF040` | El registro de alta lleva desglose |
| `RRSIF041` | `CuotaTotal` = Σ cuotas + Σ recargos *(error AEAT 2006)* |
| `RRSIF042` | `ImporteTotal` = Σ bases + Σ cuotas + Σ recargos *(errores 1210 y 2005)* |
| `RRSIF043` | La cuota de cada línea sale de su base y su tipo *(error 1142)* |
| `RRSIF044` | Base y cuota de una línea llevan el mismo signo *(errores 1140 y 1143)* |
| `RRSIF045` | `Impuesto`, `ClaveRegimen`, calificación y exención existen |
| `RRSIF046` | Lo exento, lo no sujeto y la inversión del sujeto pasivo no repercuten cuota |
| `RRSIF047` | El recargo de equivalencia corresponde a su tipo *(errores 1160 y 1162-1170)* |
| `RRSIF048` | `Macrodato` marca los importes de ±100.000.000 *(errores 1137-1139)* |
| `RRSIF049` | F1, F3 y R1-R4 llevan destinatario *(error 1189)* |

**Registros de evento** — es decir, la modalidad **NO VERI\*FACTU**:

| Regla | Comprueba |
|---|---|
| `RRSIF020` | `TipoEvento` está entre los once del esquema oficial |
| `RRSIF021` | La huella del evento sale de sus nueve campos |
| `RRSIF022` | Formato de `HuellaEvento` |
| `RRSIF023` | Cada evento encadena con la huella del evento anterior |
| `RRSIF024` | `PrimerEvento` y `EventoAnterior` son excluyentes, y hay un único origen |
| `RRSIF025` | Todo registro de evento lleva firma electrónica |
| `RRSIF026` | `DatosPropiosEvento` corresponde al tipo de evento |
| `RRSIF027` | Los arranques y paradas como NO VERI\*FACTU se emparejan |
| `RRSIF028` | Existe registro resumen de eventos |

> Si tu sistema opera en **NO VERI\*FACTU**, esta segunda tabla es la que te concierne. La AEAT es explícita en que esa modalidad es **técnicamente más exigente** que VERI\*FACTU: al no remitir los registros a la sede, la integridad y la trazabilidad hay que demostrarlas con el registro de eventos, su encadenamiento propio y su firma. Mucha implementación la elige creyendo que es la opción de menos trabajo.

### Tres severidades, no dos

- **`error`** — incumple. Lo que hay en el fichero contradice la norma citada.
- **`aviso`** — muy probablemente incumple, o incumplirá en cuanto se dé una condición previsible.
- **`incompleto`** — no se puede determinar con este fichero, y el hallazgo dice qué habría que mirar.

`incompleto` existe porque en una herramienta de cumplimiento **afirmar un incumplimiento que no existe es peor que callar uno que sí**: quien lo lee cambia código correcto y deja de creerse el resto del informe. Cuando una regla no puede concluir, lo dice.

Un ejemplo real de esa cautela: la orden admite `123.1` y `123.10` como el mismo importe, y cada forma produce un SHA-256 distinto. Un verificador que calculase sólo una declararía incorrecta una huella que la AEAT acepta. `verifactu-lint` prueba las formas admisibles antes de afirmar nada.

## Validar contra el XSD no es cumplir el reglamento

Es la distinción que justifica esta herramienta, y está demostrada en la suite en vez
de afirmada: **los ejemplos con defectos de este repositorio validan contra los
esquemas oficiales de la AEAT**.

`ejemplos/cadena-rota.xml` es impecable para `SuministroLR.xsd` y tiene la cadena de
huellas partida. `ejemplos/eventos-con-defectos.xml` valida y le faltan los datos
propios de un evento de exportación.

Un esquema comprueba **forma**; no sabe calcular un SHA-256, no conoce el orden de
los registros y no puede saber que a un tipo de evento le corresponde un bloque
concreto. Esa franja —lo estructuralmente correcto y sustantivamente incorrecto— es
donde trabaja `verifactu-lint`.

Los XSD oficiales están versionados en [`esquemas/`](esquemas/) y la suite los usa
para comprobar que los ejemplos son ficheros que un sistema real podría haber
emitido. Sin ese ancla, las pruebas se construirían con la misma interpretación del
reglamento que luego verifican.

## Sobre qué se apoya

- **RD 1007/2023**, de 5 de diciembre — Reglamento de requisitos de los sistemas informáticos de facturación.
- **Orden HAC/1177/2024**, de 17 de octubre — especificaciones técnicas, funcionales y de contenido.
- **AEAT — *Detalle de las especificaciones técnicas para generación de la huella o hash de los registros de facturación*, v0.1.2** (27/08/2024). Los tres vectores de su apartado 6 están en la suite de tests y se ejecutan en cada cambio: son la definición de correcto para el cálculo de la huella.
- **AEAT — *Aclaraciones a dudas de los desarrolladores*, v1.3** (04/12/2025).
- **AEAT — *Listado de códigos de error***. Las reglas que citan un código (1118, 1142, 1189, 2006…) comprueban exactamente lo que rechazaría el validador de la AEAT, y el hallazgo lo dice para que se pueda contrastar.

Cuando una regla y la norma discrepen, la norma tiene razón y la regla es un bug. [Abre un issue](https://github.com/easybytehub/verifactu-lint/issues) citando el apartado.

## Alcance actual

Cubre el encadenamiento, el formato de la huella y la identificación del SIF sobre registros de **alta** y **anulación**, y el encadenamiento, la firma y la coherencia de los registros de **evento**.

Cada fichero se audita detectando qué contiene. Las dos cadenas —facturación y eventos— se auditan por separado porque son independientes: un evento no encadena con una factura ni al revés.

Todavía **no** cubre: los requisitos de conservación de la modalidad NO VERI\*FACTU que no se pueden observar desde un fichero de registros, ni el seguimiento del `NumeroInstalacion` entre ejecuciones. Están en los [issues](https://github.com/easybytehub/verifactu-lint/issues).

## Contribuir

Una regla es una función `list[Registro] -> list[Hallazgo]`. No hay clase base, ni registro por decorador, ni sistema de plugins: escribes la función, la añades a la tupla `REGLAS` de su familia en `src/verifactu_lint/reglas/`, y le pones un test.

Toda regla nueva necesita: la cita normativa concreta, una severidad justificada, y un test con un caso que la dispare y otro que no. Las reglas que producirían ruido en ficheros grandes deben agregar — un hallazgo por campo, no uno por registro.

```bash
git clone https://github.com/easybytehub/verifactu-lint
cd verifactu-lint
python -m venv .venv && .venv/bin/pip install -e ".[dev]"
.venv/bin/python -m pytest
.venv/bin/python -m ruff check .
.venv/bin/python -m mypy
```

## Procedencia

Cada release publica su procedencia mediante [attestations de GitHub](https://docs.github.com/actions/security-guides/using-artifact-attestations), firmadas con OIDC efímero. No hay ninguna clave privada custodiada por nadie.

```bash
gh attestation verify --owner easybytehub verifactu_lint-*.whl
```

La atestación dice qué commit, qué workflow y qué runner produjeron ese artefacto exacto. Si vas a meter código de terceros en el sistema del que respondes tú, esto es lo que deberías poder comprobar de cualquiera de ellos.

## Licencia

[Apache-2.0](LICENSE).
