verifactu-rails 0.1.0.pre
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- checksums.yaml +7 -0
- data/COMPLIANCE.md +149 -0
- data/LICENSE +21 -0
- data/README.md +404 -0
- data/doc/FUENTES.md +505 -0
- data/lib/generators/verifactu/install/install_generator.rb +61 -0
- data/lib/generators/verifactu/install/templates/instalar_verifactu.rb.tt +19 -0
- data/lib/generators/verifactu/install/templates/verifactu.rb.tt +64 -0
- data/lib/verifactu-rails.rb +31 -0
- data/lib/verifactu_rails/certificado.rb +122 -0
- data/lib/verifactu_rails/consulta.rb +279 -0
- data/lib/verifactu_rails/desglose.rb +396 -0
- data/lib/verifactu_rails/envio.rb +153 -0
- data/lib/verifactu_rails/error.rb +31 -0
- data/lib/verifactu_rails/formato.rb +199 -0
- data/lib/verifactu_rails/huella.rb +130 -0
- data/lib/verifactu_rails/importe.rb +80 -0
- data/lib/verifactu_rails/libro/autochequeo.rb +67 -0
- data/lib/verifactu_rails/libro/cadena.rb +110 -0
- data/lib/verifactu_rails/libro/migracion.rb +92 -0
- data/lib/verifactu_rails/libro/reconciliacion.rb +224 -0
- data/lib/verifactu_rails/libro/registro.rb +74 -0
- data/lib/verifactu_rails/libro/remesa.rb +120 -0
- data/lib/verifactu_rails/libro.rb +89 -0
- data/lib/verifactu_rails/qr.rb +56 -0
- data/lib/verifactu_rails/railtie.rb +30 -0
- data/lib/verifactu_rails/registro.rb +833 -0
- data/lib/verifactu_rails/respuesta.rb +154 -0
- data/lib/verifactu_rails/schemas/ConsultaLR.xsd +54 -0
- data/lib/verifactu_rails/schemas/EventosSIF.xsd +823 -0
- data/lib/verifactu_rails/schemas/PROCEDENCIA.md +64 -0
- data/lib/verifactu_rails/schemas/RespuestaConsultaLR.xsd +201 -0
- data/lib/verifactu_rails/schemas/RespuestaSuministro.xsd +139 -0
- data/lib/verifactu_rails/schemas/RespuestaValRegistNoVeriFactu.xsd +103 -0
- data/lib/verifactu_rails/schemas/SuministroInformacion.xsd +1390 -0
- data/lib/verifactu_rails/schemas/SuministroLR.xsd +25 -0
- data/lib/verifactu_rails/schemas/catalog.xml +5 -0
- data/lib/verifactu_rails/schemas/xmldsig-core-schema.xsd +318 -0
- data/lib/verifactu_rails/sistema_informatico.rb +88 -0
- data/lib/verifactu_rails/transporte.rb +127 -0
- data/lib/verifactu_rails/version.rb +5 -0
- metadata +124 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: cce658f6fc7e521b621da490c3e3dac1e1e3045ab0daad5d7d23f2b467276ad6
|
|
4
|
+
data.tar.gz: 6c00392ff5e3ebfbe4560b9928c5748f23bd9eb16c3cc46d0a0f46bec7593c4e
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: fc8492295003368381071c2a0754e790e69edd714660ddf1baad2869b9a02ebe57ca8c67e6db6dd168a80e0dc92f7d1134c644fcfb42e633e261acdded35cafe
|
|
7
|
+
data.tar.gz: 0dc9752420bba0ef62fe72297ab7bce5267c5d629f4c0a73b277b69c8a86f2a436d22a18e75ecc92cb13b2a8e7f4639da009082711d962b164b48c3b3e086e52
|
data/COMPLIANCE.md
ADDED
|
@@ -0,0 +1,149 @@
|
|
|
1
|
+
# Cumplimiento: qué cubre esta gema y qué te sigue tocando a ti
|
|
2
|
+
|
|
3
|
+
Este documento delimita responsabilidades. No es asesoramiento legal, no es una
|
|
4
|
+
certificación, y **usar `verifactu-rails` no te hace cumplidor** del RD 1007/2023
|
|
5
|
+
ni de la Orden HAC/1177/2024. Es un componente de tu sistema de facturación; el
|
|
6
|
+
sistema es tuyo.
|
|
7
|
+
|
|
8
|
+
Las citas normativas de aquí vienen de las fuentes recogidas en
|
|
9
|
+
[doc/FUENTES.md](doc/FUENTES.md) (el propio texto de la Orden, las FAQs de
|
|
10
|
+
Desarrolladores y los PDF de Validaciones de la AEAT). Antes de apoyarte en
|
|
11
|
+
cualquiera de ellas para una decisión con consecuencias, contrástala con el BOE:
|
|
12
|
+
esto es documentación de una librería, no una fuente jurídica.
|
|
13
|
+
|
|
14
|
+
## Quién responde de qué
|
|
15
|
+
|
|
16
|
+
La pieza que ordena todo lo demás: **la declaración responsable del art. 13 la
|
|
17
|
+
firma quien produce el sistema informático de facturación**, y quien produce el
|
|
18
|
+
SIF eres tú, no esta gema. Si desarrollas el software para tu propio uso, eres
|
|
19
|
+
productor y usuario a la vez; si lo vendes, sigues siendo el productor.
|
|
20
|
+
|
|
21
|
+
Además, la obligación de trazabilidad recae legalmente sobre el productor y no
|
|
22
|
+
solo sobre quien factura (art. 29.2.j LGT). Certificar por declaración
|
|
23
|
+
responsable un SIF que no cumple el RD 1007/2023 es sancionable.
|
|
24
|
+
|
|
25
|
+
Consecuencia práctica: nadie va a mirar el `Gemfile` de tu aplicación para
|
|
26
|
+
repartir culpas. Lo que se mira es el sistema que has declarado.
|
|
27
|
+
|
|
28
|
+
## Alcance: solo VERI\*FACTU
|
|
29
|
+
|
|
30
|
+
Esta gema implementa **únicamente la modalidad VERI\*FACTU**, la de remisión
|
|
31
|
+
continua a la AEAT. No implementa el modo NO VERI\*FACTU, que exigiría firma
|
|
32
|
+
XAdES de cada registro y llevar registro de eventos.
|
|
33
|
+
|
|
34
|
+
Esa reducción de alcance es legítima y la AEAT la contempla explícitamente (FAQs
|
|
35
|
+
Desarrolladores v1.3, ap. 15, nota 1). Se refleja en el código: el campo
|
|
36
|
+
`TipoUsoPosibleSoloVerifactu` va fijo a `'S'` y **no se puede configurar**.
|
|
37
|
+
Declarar `'N'` significaría que tu SIF puede operar en modo no VERI\*FACTU, y eso
|
|
38
|
+
te obligaría a un registro de eventos que aquí no existe. Si necesitas `'N'`,
|
|
39
|
+
esta no es tu librería.
|
|
40
|
+
|
|
41
|
+
## Qué pone la gema
|
|
42
|
+
|
|
43
|
+
| Requisito | Dónde |
|
|
44
|
+
|---|---|
|
|
45
|
+
| Huella SHA-256 con serialización canónica, alta y anulación | `Huella` |
|
|
46
|
+
| Encadenamiento de cada registro con el anterior | `Libro::Cadena#anotar_alta!`, bajo lock |
|
|
47
|
+
| Imposibilidad de bifurcar la cadena | índice único `(cadena_id, huella_anterior)` |
|
|
48
|
+
| Comprobaciones del art. 7.i) antes de generar cada registro | `Libro::Autochequeo` |
|
|
49
|
+
| Orden cronológico de generación | serialización bajo `SELECT … FOR UPDATE` |
|
|
50
|
+
| XML conforme a los XSD de la AEAT | `RegistroAlta` / `RegistroAnulacion` / `Envio` |
|
|
51
|
+
| Remisión con TLS mutuo al endpoint correcto | `Transporte` |
|
|
52
|
+
| Reenvío de lo pendiente, con control de flujo y reintentos | `Libro::Remesa` |
|
|
53
|
+
| URL de cotejo del código QR | `QR` |
|
|
54
|
+
| Contraste contra lo que la AEAT tiene anotado | `Libro::Reconciliacion` |
|
|
55
|
+
| Conservación del registro tal y como se remitió | columna `payload` del libro |
|
|
56
|
+
|
|
57
|
+
Sobre la última fila, que es la que más peso tiene si algún día hay una
|
|
58
|
+
inspección: el libro guarda el **fragmento XML ya construido**, no los argumentos
|
|
59
|
+
con que se construyó. Lo que se envía es exactamente lo que se calculó, y la
|
|
60
|
+
huella se puede recalcular años después desde las columnas sin volver a derivar
|
|
61
|
+
nada.
|
|
62
|
+
|
|
63
|
+
## Qué NO pone la gema, y tienes que poner tú
|
|
64
|
+
|
|
65
|
+
- **La declaración responsable.** Ver arriba.
|
|
66
|
+
- **El registro de eventos.** No existe. Solo hace falta en modo NO VERI\*FACTU,
|
|
67
|
+
que está fuera de alcance, pero conviene que sepas por qué no está.
|
|
68
|
+
- **La imagen del código QR.** La gema construye la URL de cotejo; convertirla en
|
|
69
|
+
un QR y ponerlo en la factura es tuyo. La AEAT no devuelve esa URL: la
|
|
70
|
+
construye el SIF.
|
|
71
|
+
- **La factura en sí.** Esto no es un sistema de facturación: no numera, no
|
|
72
|
+
calcula impuestos, no emite PDF ni gestiona clientes.
|
|
73
|
+
- **La custodia del certificado.** `Certificado` recibe los bytes ya cargados y
|
|
74
|
+
deliberadamente no sabe leer ficheros ni variables de entorno. Dónde vive el
|
|
75
|
+
`.p12` y quién puede leerlo es decisión y responsabilidad tuyas.
|
|
76
|
+
- **El `NumeroInstalacion`.** No se autogenera **nunca**, y no es un descuido: si
|
|
77
|
+
la gema lo inventara, un contenedor que se recrea en cada despliegue abriría
|
|
78
|
+
una instalación por despliegue y la cadena dejaría de demostrar nada. Elegirlo
|
|
79
|
+
—y no reutilizarlo jamás, ni al reinstalar el mismo software en la misma
|
|
80
|
+
máquina— es un acto deliberado tuyo.
|
|
81
|
+
- **La conservación y el respaldo del libro.** Ver el apartado siguiente, porque
|
|
82
|
+
es el punto que más se subestima.
|
|
83
|
+
- **La política de retención, acceso y borrado** de los datos, y todo lo que
|
|
84
|
+
tenga que ver con protección de datos.
|
|
85
|
+
|
|
86
|
+
## Tres límites que conviene entender antes de firmar nada
|
|
87
|
+
|
|
88
|
+
**1. El libro local es el sistema de registro, no una caché.** Está comprobado
|
|
89
|
+
contra el servicio real que la consulta de la AEAT devuelve **una foto por
|
|
90
|
+
factura, no el histórico**: una subsanación sustituye al alta original y la
|
|
91
|
+
anulación sustituye a lo anulado, así que los eslabones intermedios desaparecen.
|
|
92
|
+
Si pierdes la tabla `verifactu_registros`, el histórico de tu cadena no existe en
|
|
93
|
+
ninguna otra parte. Respáldala como respaldarías la contabilidad.
|
|
94
|
+
|
|
95
|
+
**2. La AEAT acepta una cadena bifurcada sin avisar.** Comprobado, también contra
|
|
96
|
+
el servicio real. No valida el eslabón al recibir, así que un encadenamiento
|
|
97
|
+
incoherente se aceptaría en silencio y quedaría anotado. La única red que hay es
|
|
98
|
+
el índice único local. Si migras el esquema a mano, o replicas la tabla, o
|
|
99
|
+
permites escrituras que esquiven `anotar_alta!`, esa red desaparece y no te vas a
|
|
100
|
+
enterar por la AEAT.
|
|
101
|
+
|
|
102
|
+
**3. Una anomalía de trazabilidad no interrumpe la facturación, por norma.** El
|
|
103
|
+
autochequeo del art. 7.i) se anota y se notifica, pero **no lanza excepción**: la
|
|
104
|
+
Orden dice que ante una anomalía la facturación "nunca debe interrumpirse".
|
|
105
|
+
Traducción operativa: si no enganchas `al_detectar_anomalia` a algo que mires de
|
|
106
|
+
verdad, las anomalías se pierden. Que no bloqueen no significa que no importen.
|
|
107
|
+
|
|
108
|
+
## Cómo demostrar que esto hace lo que dice
|
|
109
|
+
|
|
110
|
+
Si tienes que justificar el componente ante un cliente o un auditor, esto es lo
|
|
111
|
+
que hay:
|
|
112
|
+
|
|
113
|
+
- **Suite de tests** que incluye un caso con ocho hilos demostrando que la cadena
|
|
114
|
+
no se puede bifurcar (quitando el lock, para que el índice tenga que
|
|
115
|
+
sostenerlo), y validación de todo el XML contra los XSD oficiales de la AEAT,
|
|
116
|
+
que se versionan en el repo con su procedencia documentada.
|
|
117
|
+
- **[doc/FUENTES.md](doc/FUENTES.md)**: el registro de qué se ha contrastado
|
|
118
|
+
contra el servicio real de preproducción, con fechas y resultados. Incluye lo
|
|
119
|
+
que salió distinto de lo esperado y lo que sigue siendo suposición.
|
|
120
|
+
- **`Libro::Reconciliacion`**: contraste de solo lectura entre tu libro y lo que
|
|
121
|
+
la AEAT tiene anotado, factura a factura.
|
|
122
|
+
- **`Registro#huella_cuadra?`**: recalcula la huella desde las columnas
|
|
123
|
+
almacenadas. Si alguien editó una fila por debajo del modelo, se ve.
|
|
124
|
+
|
|
125
|
+
## Antes de poner esto en producción
|
|
126
|
+
|
|
127
|
+
Una lista corta de lo que hay que haber decidido, no de lo que hay que programar:
|
|
128
|
+
|
|
129
|
+
1. Quién firma la declaración responsable y con qué versión del SIF.
|
|
130
|
+
2. Qué `NumeroInstalacion` lleva cada fuente de facturación, y quién lo asigna.
|
|
131
|
+
Una por tienda, TPV o sede: son SIF virtuales distintos.
|
|
132
|
+
3. Dónde vive el certificado, quién lo puede leer y qué pasa cuando caduque
|
|
133
|
+
(`Certificado#caduca_pronto?` avisa, pero alguien tiene que escucharlo).
|
|
134
|
+
4. A dónde van las anomalías del art. 7.i) y quién las mira.
|
|
135
|
+
5. Qué se hace cuando un registro es **rechazado**. La remesa detiene la cadena a
|
|
136
|
+
propósito, porque seguir enviando dejaría una cadena incoherente aceptada en
|
|
137
|
+
silencio. Resolverlo es una decisión de negocio, no algo que un job deba
|
|
138
|
+
improvisar.
|
|
139
|
+
6. Cómo se respalda y se restaura el libro, y cada cuánto se prueba la
|
|
140
|
+
restauración.
|
|
141
|
+
7. Con qué frecuencia se reconcilia contra la AEAT y quién lee el informe.
|
|
142
|
+
|
|
143
|
+
## Si algo de aquí no te cuadra
|
|
144
|
+
|
|
145
|
+
Este documento es tan bueno como lo que hay contrastado detrás, y
|
|
146
|
+
[doc/FUENTES.md](doc/FUENTES.md) dice explícitamente qué está comprobado contra
|
|
147
|
+
el servicio real y qué sigue siendo una suposición razonable. Si encuentras una
|
|
148
|
+
afirmación que no se sostiene, es un fallo del documento y merece un issue igual
|
|
149
|
+
que un fallo del código.
|
data/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Sergio Valero
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,404 @@
|
|
|
1
|
+
# verifactu-rails
|
|
2
|
+
|
|
3
|
+
Integración con **VERI\*FACTU** (AEAT) para aplicaciones Rails: libro registro
|
|
4
|
+
encadenado, remisión a la AEAT y reconciliación de lo anotado.
|
|
5
|
+
|
|
6
|
+
Es una librería, no un sistema de facturación: calcula la huella encadenada,
|
|
7
|
+
genera el XML de los registros y habla con la AEAT por TLS mutuo. No numera
|
|
8
|
+
facturas, no calcula impuestos y no emite PDF.
|
|
9
|
+
|
|
10
|
+
> **Estado: en desarrollo, sin release público.** Ejercitada de punta a punta
|
|
11
|
+
> contra el entorno de pruebas de la AEAT —altas encadenadas, lotes, anulación,
|
|
12
|
+
> rectificativa, subsanación y reconciliación—. La API puede cambiar sin aviso.
|
|
13
|
+
|
|
14
|
+
**Solo modalidad VERI\*FACTU** (remisión continua). El modo NO VERI\*FACTU
|
|
15
|
+
exigiría firma XAdES y registro de eventos, y no se implementa; la AEAT contempla
|
|
16
|
+
esta reducción de alcance (FAQs Desarrolladores v1.3, ap. 15, nota 1). Fuera de
|
|
17
|
+
alcance: Facturae/B2G, TicketBAI/Batuz, TPV.
|
|
18
|
+
|
|
19
|
+
Usar esta gema no te hace cumplidor del RD 1007/2023 ni de la Orden HAC/1177/2024.
|
|
20
|
+
Quién responde de qué está en **[COMPLIANCE.md](COMPLIANCE.md)**, y conviene
|
|
21
|
+
leerlo antes de poner esto en producción.
|
|
22
|
+
|
|
23
|
+
## Requisitos
|
|
24
|
+
|
|
25
|
+
- Ruby >= 3.0, Rails >= 7.0
|
|
26
|
+
- Certificado del obligado tributario en PKCS12. Vale el de representante, que es
|
|
27
|
+
el camino contrastado contra el servicio real. El de sello de entidad también
|
|
28
|
+
está soportado (la AEAT le da endpoints propios); con él, declara `sello: true`
|
|
29
|
+
explícitamente en `Transporte`.
|
|
30
|
+
|
|
31
|
+
## Instalación
|
|
32
|
+
|
|
33
|
+
```ruby
|
|
34
|
+
# Gemfile
|
|
35
|
+
gem 'verifactu-rails'
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
```sh
|
|
39
|
+
rails g verifactu:install
|
|
40
|
+
rails db:migrate
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
El generador deja la migración del libro y `config/initializers/verifactu.rb`. La
|
|
44
|
+
migración no copia el esquema: hereda de `VerifactuRails::Libro::Migracion`, así
|
|
45
|
+
que lo que instalas es exactamente lo que ejercita la suite de tests de la gema.
|
|
46
|
+
|
|
47
|
+
## Puesta en marcha
|
|
48
|
+
|
|
49
|
+
### 1. Identifica tu SIF
|
|
50
|
+
|
|
51
|
+
En el initializer que dejó el generador. Describe **tu** sistema, no esta gema:
|
|
52
|
+
verifactu-rails es un componente del SIF, no el SIF. Viene con valores inválidos
|
|
53
|
+
a propósito, para que falle pronto en vez de remitir una identificación
|
|
54
|
+
inventada.
|
|
55
|
+
|
|
56
|
+
```ruby
|
|
57
|
+
VerifactuRails::Libro.configure do |c|
|
|
58
|
+
c.productor_nombre = 'Tu Empresa SL' # quién PRODUCE el software
|
|
59
|
+
c.productor_nif = '89890001K'
|
|
60
|
+
c.nombre_sistema = 'TuFactura'
|
|
61
|
+
c.id_sistema = '01' # dos posiciones, mayúscula o dígito
|
|
62
|
+
c.version = TuApp::VERSION
|
|
63
|
+
c.entorno = Rails.env.production? ? :produccion : :pruebas
|
|
64
|
+
|
|
65
|
+
# Las anomalías del art. 7.i) no interrumpen la facturación, pero si no las
|
|
66
|
+
# mandas a algún sitio que mires, se pierden.
|
|
67
|
+
c.al_detectar_anomalia = ->(anomalias, registro) { Rails.logger.warn(...) }
|
|
68
|
+
end
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
### 2. Abre una cadena por fuente de facturación
|
|
72
|
+
|
|
73
|
+
Una vez, y a conciencia: en un seed, una tarea rake o tu panel de administración.
|
|
74
|
+
Nunca en un initializer. Cada tienda, TPV o sede es un SIF virtual distinto y
|
|
75
|
+
lleva su propia cadena.
|
|
76
|
+
|
|
77
|
+
```ruby
|
|
78
|
+
VerifactuRails::Libro::Cadena.abrir!(
|
|
79
|
+
numero_instalacion: 'TIENDA-VALENCIA-20260807120000',
|
|
80
|
+
nif_obligado: '89890001K', nombre_obligado: 'Tu Empresa SL'
|
|
81
|
+
)
|
|
82
|
+
```
|
|
83
|
+
|
|
84
|
+
El `numero_instalacion` **no se autogenera nunca** y no se reutiliza jamás, ni al
|
|
85
|
+
reinstalar el mismo software en la misma máquina. Guárdalo en tu modelo (la
|
|
86
|
+
tienda, el TPV) para recuperar la cadena después.
|
|
87
|
+
|
|
88
|
+
### 3. Anota cada factura
|
|
89
|
+
|
|
90
|
+
```ruby
|
|
91
|
+
cadena = VerifactuRails::Libro::Cadena.find_by!(numero_instalacion: tienda.numero_instalacion)
|
|
92
|
+
|
|
93
|
+
registro = cadena.anotar_alta!(
|
|
94
|
+
id_emisor: '89890001K', num_serie: 'FA/2026/0001',
|
|
95
|
+
fecha_expedicion: Date.current, nombre_razon_emisor: 'Tu Empresa SL',
|
|
96
|
+
tipo_factura: 'F1', descripcion_operacion: 'Servicios de agosto',
|
|
97
|
+
desglose: [VerifactuRails::Detalle.new(base_imponible: BigDecimal('100.00'),
|
|
98
|
+
calificacion: 'S1',
|
|
99
|
+
tipo_impositivo: BigDecimal('21.00'),
|
|
100
|
+
cuota_repercutida: BigDecimal('21.00'))],
|
|
101
|
+
cuota_total: BigDecimal('21.00'), importe_total: BigDecimal('121.00'),
|
|
102
|
+
fecha_hora_gen: Time.now,
|
|
103
|
+
destinatarios: [VerifactuRails::Destinatario.new(nombre_razon: 'Cliente SL',
|
|
104
|
+
nif: '89890002E')]
|
|
105
|
+
)
|
|
106
|
+
|
|
107
|
+
registro.qr_url # ya disponible: el QR no depende de la AEAT
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
Calcula la huella, encadena y genera el QR en una transacción con la fila de la
|
|
111
|
+
cadena bloqueada. Es síncrono a propósito: el encadenamiento no se puede diferir,
|
|
112
|
+
aunque el envío sí. También hay `anotar_anulacion!`.
|
|
113
|
+
|
|
114
|
+
### 4. Envía
|
|
115
|
+
|
|
116
|
+
```ruby
|
|
117
|
+
class EnviarRemesaJob < ApplicationJob
|
|
118
|
+
def perform(numero_instalacion)
|
|
119
|
+
cadena = VerifactuRails::Libro::Cadena.find_by!(numero_instalacion: numero_instalacion)
|
|
120
|
+
certificado = VerifactuRails::Certificado.desde_pkcs12(bytes_del_p12, password)
|
|
121
|
+
transporte = VerifactuRails::Transporte.new(
|
|
122
|
+
certificado: certificado, entorno: VerifactuRails::Libro.configuracion.entorno
|
|
123
|
+
)
|
|
124
|
+
|
|
125
|
+
VerifactuRails::Libro::Remesa.new(cadena, transporte: transporte).enviar!
|
|
126
|
+
# => #<Resultado estado: :enviado|:esperando|:nada_pendiente|:bloqueada_por_rechazo>
|
|
127
|
+
end
|
|
128
|
+
end
|
|
129
|
+
```
|
|
130
|
+
|
|
131
|
+
La remesa no recibe registros: coge de la base de datos lo que esa cadena tenga
|
|
132
|
+
pendiente. Encólalo tras `anotar_alta!` y ponle además un cron de seguridad por
|
|
133
|
+
si un job se perdió; es idempotente, y un duplicado cuenta como éxito.
|
|
134
|
+
|
|
135
|
+
Construye certificado y transporte **dentro del job**, no al arrancar:
|
|
136
|
+
`Transporte` no guarda conexión (reutilizarlo no ahorra ningún handshake) y
|
|
137
|
+
`Certificado` comprueba la caducidad al construirse, que es un aviso que pierdes
|
|
138
|
+
si lo cacheas durante meses.
|
|
139
|
+
|
|
140
|
+
### 5. Reconcilia
|
|
141
|
+
|
|
142
|
+
La respuesta a un envío dice si la AEAT **aceptó** el registro; no dice qué queda
|
|
143
|
+
almacenado después. La consulta añade `Anulado`, un estado que el canal de envío
|
|
144
|
+
ni siquiera puede expresar.
|
|
145
|
+
|
|
146
|
+
```ruby
|
|
147
|
+
informe = VerifactuRails::Libro::Reconciliacion
|
|
148
|
+
.new(cadena, transporte: transporte)
|
|
149
|
+
.revisar(ejercicio: 2026, periodo: 8)
|
|
150
|
+
|
|
151
|
+
informe.cuadra? # => true si el libro y la AEAT dicen lo mismo de cada factura
|
|
152
|
+
informe.divergencias # => [#<Divergencia tipo: :no_consta, num_serie: 'FA/7', ...>]
|
|
153
|
+
```
|
|
154
|
+
|
|
155
|
+
Solo lee: nunca corrige el libro. Los tipos de divergencia son `:no_consta`,
|
|
156
|
+
`:huella_distinta`, `:estado_distinto`, `:consta_sin_enviar` y `:solo_en_aeat`.
|
|
157
|
+
|
|
158
|
+
## Lo que conviene saber antes de integrarlo
|
|
159
|
+
|
|
160
|
+
- **El libro local es el sistema de registro, no una caché.** La consulta de la
|
|
161
|
+
AEAT devuelve una foto por factura, no el histórico: una subsanación sustituye
|
|
162
|
+
al alta original y desaparece de allí. Si pierdes la tabla, el histórico de tu
|
|
163
|
+
cadena no existe en ninguna otra parte. Respáldala.
|
|
164
|
+
- **Bifurcar la cadena es imposible por un índice único**, no por el lock. La
|
|
165
|
+
AEAT acepta una cadena bifurcada sin avisar, así que esa restricción es la
|
|
166
|
+
única red que hay.
|
|
167
|
+
- **Una anomalía nunca interrumpe la facturación.** El autochequeo del art. 7.i)
|
|
168
|
+
se anota y se notifica, pero no lanza: la norma dice que la facturación "nunca
|
|
169
|
+
debe interrumpirse". Con datos inválidos sí se falla, que ahí tampoco hay
|
|
170
|
+
factura que emitir.
|
|
171
|
+
- **Un rechazo detiene la cadena.** Lo rechazado no consta en la AEAT y todo lo
|
|
172
|
+
que encadena detrás apuntaría a un eslabón inexistente. Resolverlo es una
|
|
173
|
+
decisión de negocio.
|
|
174
|
+
- **Agrupar no es el modo normal.** Las FAQs exigen remisión "inmediata o sin
|
|
175
|
+
demora apreciable"; el tope de 1000 por envío es un techo para quien factura
|
|
176
|
+
rápido, no un objetivo.
|
|
177
|
+
- **`Float` está prohibido** en importes: usa `BigDecimal`, `Integer` o `String`.
|
|
178
|
+
La huella se calcula sobre el string del importe y la AEAT la recalcula sobre
|
|
179
|
+
el XML, así que el importe tiene que ser exacto y su formateo determinista;
|
|
180
|
+
`Float` no da ninguna de las dos cosas. El redondeo se fija explícitamente a
|
|
181
|
+
`ROUND_HALF_UP` porque `BigDecimal.mode` es estado global del proceso.
|
|
182
|
+
- **Los espacios al borde se rechazan**, no se recortan. Es más estricto que la
|
|
183
|
+
norma a propósito: casi siempre son un defecto de los datos de origen.
|
|
184
|
+
- **Nunca `VERIFY_NONE`.** Desde noviembre de 2025 la AEAT sirve con CA públicas,
|
|
185
|
+
así que `ca_file` no hace falta. Si falla, sospecha del almacén del sistema o
|
|
186
|
+
de un proxy que intercepte el TLS.
|
|
187
|
+
- **Los números de factura anulada no se reutilizan.** La excepción es la
|
|
188
|
+
subsanación, que reusa el mismo `IDFactura` a propósito con `subsanacion: 'S'`.
|
|
189
|
+
|
|
190
|
+
El porqué de cada una, con lo que se comprobó contra el servicio real, está en
|
|
191
|
+
[doc/FUENTES.md](doc/FUENTES.md).
|
|
192
|
+
|
|
193
|
+
## Tipos de factura
|
|
194
|
+
|
|
195
|
+
`tipo_factura:`. Las descripciones son literales del XSD oficial, que va
|
|
196
|
+
versionado en el repo:
|
|
197
|
+
|
|
198
|
+
| Clave | Qué es | Destinatario | Exige además |
|
|
199
|
+
|---|---|---|---|
|
|
200
|
+
| `F1` | Factura (art. 6, 7.2 y 7.3 del RD 1619/2012) | obligatorio | — |
|
|
201
|
+
| `F2` | Factura simplificada y facturas sin identificación del destinatario (art. 6.1.d) | **prohibido** | — |
|
|
202
|
+
| `F3` | Factura emitida en sustitución de facturas simplificadas facturadas y declaradas | obligatorio | `facturas_sustituidas:` (opcional) |
|
|
203
|
+
| `R1` | Rectificativa (art. 80.1 y 80.2 y error fundado en derecho) | obligatorio | `tipo_rectificativa:` |
|
|
204
|
+
| `R2` | Rectificativa (art. 80.3) | obligatorio | `tipo_rectificativa:` |
|
|
205
|
+
| `R3` | Rectificativa (art. 80.4) | obligatorio | `tipo_rectificativa:` |
|
|
206
|
+
| `R4` | Rectificativa (resto) | obligatorio | `tipo_rectificativa:` |
|
|
207
|
+
| `R5` | Rectificativa en facturas simplificadas | **prohibido** | `tipo_rectificativa:` |
|
|
208
|
+
|
|
209
|
+
La columna del destinatario no es un matiz: en `F2` y `R5` informarlo es un
|
|
210
|
+
error, y en los demás omitirlo también. `facturas_rectificadas:` es opcional pero
|
|
211
|
+
exclusiva de `R1`–`R5`, y `Cupon` solo se admite con `R1` o `R5`.
|
|
212
|
+
|
|
213
|
+
### Rectificativas: sustitutiva o incremental
|
|
214
|
+
|
|
215
|
+
- **`'S'` sustitutiva** — la factura reexpresa el importe corregido completo, así
|
|
216
|
+
que hay que declarar el original en `importe_rectificacion:`.
|
|
217
|
+
- **`'I'` incremental** — los importes ya *son* la diferencia, y
|
|
218
|
+
`importe_rectificacion:` se rechaza.
|
|
219
|
+
|
|
220
|
+
El XSD deja casi todos estos campos opcionales, así que estas reglas las impone
|
|
221
|
+
la gema: sin ellas se monta un `R1` válido para el esquema que la AEAT rechaza
|
|
222
|
+
con un error mucho menos claro.
|
|
223
|
+
|
|
224
|
+
## Calificación de cada línea del desglose
|
|
225
|
+
|
|
226
|
+
Cada `Detalle` lleva **o** `calificacion:` **o** `exenta:`, exactamente una. No es
|
|
227
|
+
una preferencia de la gema: el esquema las modela como un `choice`, así que
|
|
228
|
+
informar las dos —o ninguna— produce un documento inválido.
|
|
229
|
+
|
|
230
|
+
| Clave | Qué significa |
|
|
231
|
+
|---|---|
|
|
232
|
+
| `S1` | Operación sujeta y no exenta, **sin** inversión del sujeto pasivo |
|
|
233
|
+
| `S2` | Operación sujeta y no exenta, **con** inversión del sujeto pasivo |
|
|
234
|
+
| `N1` | Operación no sujeta (art. 7, 14, otros) |
|
|
235
|
+
| `N2` | Operación no sujeta por reglas de localización |
|
|
236
|
+
|
|
237
|
+
```ruby
|
|
238
|
+
# Lo normal: sujeta y no exenta, con su tipo y su cuota.
|
|
239
|
+
Detalle.new(base_imponible: BigDecimal('100.00'), calificacion: 'S1',
|
|
240
|
+
tipo_impositivo: BigDecimal('21.00'),
|
|
241
|
+
cuota_repercutida: BigDecimal('21.00'))
|
|
242
|
+
|
|
243
|
+
# Exenta: el importe va en base_imponible y NO se informa tipo ni cuota.
|
|
244
|
+
Detalle.new(base_imponible: BigDecimal('100.00'), exenta: 'E1')
|
|
245
|
+
```
|
|
246
|
+
|
|
247
|
+
Tres reglas que conviene tener presentes, porque la AEAT tiene un código de error
|
|
248
|
+
para cada una:
|
|
249
|
+
|
|
250
|
+
- **`N1`, `N2` y las exentas no admiten** `tipo_impositivo:`, `cuota_repercutida:`
|
|
251
|
+
ni recargo. Informarlos da error 1237 o 1238.
|
|
252
|
+
- **`S2` (inversión del sujeto pasivo) solo cabe en `F1`, `F3` y `R1`–`R4`.**
|
|
253
|
+
- **`E7` y `E8` solo existen con IGIC.** Para IVA, las exenciones son `E1`–`E6`.
|
|
254
|
+
|
|
255
|
+
### Causas de exención
|
|
256
|
+
|
|
257
|
+
El XSD las lleva desnudas, sin descripción. La correspondencia es esta, en
|
|
258
|
+
palabras de la propia AEAT, y los artículos son de la Ley 37/1992 del IVA:
|
|
259
|
+
|
|
260
|
+
| Clave | Texto de la AEAT | De qué trata ese artículo |
|
|
261
|
+
|---|---|---|
|
|
262
|
+
| `E1` | *exenta por el artículo 20* | Exenciones en operaciones interiores |
|
|
263
|
+
| `E2` | *exenta por el artículo 21* | Exportaciones de bienes |
|
|
264
|
+
| `E3` | *exenta por el artículo 22* | Operaciones asimiladas a las exportaciones |
|
|
265
|
+
| `E4` | *exenta por los artículos 23 y 24* | Zonas y depósitos francos, regímenes aduaneros y fiscales |
|
|
266
|
+
| `E5` | *exenta por el artículo 25* | Entregas intracomunitarias |
|
|
267
|
+
| `E6` | *exenta por otros* | El resto |
|
|
268
|
+
|
|
269
|
+
La AEAT añade que **si no se dispone de esa información basta con indicar que la
|
|
270
|
+
operación es exenta**. Ojo: eso vale para el libro registro, pero el esquema de
|
|
271
|
+
VERI\*FACTU exige un valor concreto en `OperacionExenta`, así que en la práctica
|
|
272
|
+
hay que elegir uno; `E6` es el cajón previsto para ello.
|
|
273
|
+
|
|
274
|
+
El mapeo sale de la documentación del SII, que comparte esta lista de códigos,
|
|
275
|
+
porque la documentación técnica de VERI\*FACTU no la desarrolla. Las FAQs de la
|
|
276
|
+
propia AEAT sobre el libro registro lo corroboran para los dos casos que citan
|
|
277
|
+
con nombre: exportaciones (`E2`) y entregas intracomunitarias (`E5`).
|
|
278
|
+
|
|
279
|
+
### Impuesto, régimen y tipos
|
|
280
|
+
|
|
281
|
+
`impuesto:` y `clave_regimen:` van **en cada línea del desglose**, no en la
|
|
282
|
+
factura: son parámetros de `Detalle`. En los ejemplos de arriba no se ven porque
|
|
283
|
+
tienen valor por defecto —`'01'` en los dos, IVA y régimen general—, que es el
|
|
284
|
+
caso normal. Explícitos:
|
|
285
|
+
|
|
286
|
+
```ruby
|
|
287
|
+
Detalle.new(base_imponible: BigDecimal('50.00'), calificacion: 'S1',
|
|
288
|
+
impuesto: '03', clave_regimen: '01', # IGIC
|
|
289
|
+
tipo_impositivo: BigDecimal('7.00'),
|
|
290
|
+
cuota_repercutida: BigDecimal('3.50'))
|
|
291
|
+
```
|
|
292
|
+
|
|
293
|
+
Cada línea acaba en su propio `<DetalleDesglose>` con su `<Impuesto>`, así que
|
|
294
|
+
**una misma factura puede mezclar impuestos**. Los valores son `01` IVA, `02`
|
|
295
|
+
IPSI, `03` IGIC y `05` otros.
|
|
296
|
+
|
|
297
|
+
### Clave de régimen
|
|
298
|
+
|
|
299
|
+
Y aquí está la trampa: **qué significa `clave_regimen:` depende del impuesto**.
|
|
300
|
+
Las claves `18`, `19` y `20` no quieren decir lo mismo en IVA que en IPSI, así
|
|
301
|
+
que copiar un valor de un ejemplo de IVA a una línea de IPSI declara otra cosa.
|
|
302
|
+
|
|
303
|
+
Qué admite cada impuesto (la gema lo valida y rechaza el resto):
|
|
304
|
+
|
|
305
|
+
| Impuesto | Claves admitidas |
|
|
306
|
+
|---|---|
|
|
307
|
+
| `01` IVA | `01`–`11`, `14`, `15`, `17`, `18`, `19`, `20` (lista L8A) |
|
|
308
|
+
| `03` IGIC | las de L8A más `20` (operaciones sujetas al IPSI) y `21` (régimen simplificado) |
|
|
309
|
+
| `02` IPSI | solo `01`, `08`, `11`, `18`, `19`, `20`, y con significado propio |
|
|
310
|
+
| `05` Otros | **ninguna**: el campo no se puede informar en absoluto |
|
|
311
|
+
|
|
312
|
+
Las de IPSI, en palabras de la AEAT: `01` régimen general, `08` operaciones
|
|
313
|
+
sujetas al IGIC/IVA, `11` arrendamiento de local de negocio, `18` operaciones del
|
|
314
|
+
art. 73.4 y 5 de la Ordenanza fiscal IPSI (solo Ceuta), `19` operaciones
|
|
315
|
+
interiores exentas y `20` régimen de estimación objetiva.
|
|
316
|
+
|
|
317
|
+
Para IVA las más frecuentes son `01` régimen general, `02` exportación, `07`
|
|
318
|
+
criterio de caja, `11` arrendamiento de local de negocio sujeto a retención y
|
|
319
|
+
`18` recargo de equivalencia. La lista completa con su descripción es la L8A de
|
|
320
|
+
los Diseños de registro de la AEAT; aquí no se reproduce entera porque cambia con
|
|
321
|
+
las versiones y no queremos una copia que envejezca en silencio.
|
|
322
|
+
|
|
323
|
+
Cada clave arrastra además sus propias reglas, que la gema aplica antes de
|
|
324
|
+
enviar (Validaciones ap. 15.6): con `02` solo cabe `OperacionExenta`; con `03`,
|
|
325
|
+
si hay calificación, solo `S1`; con `04`, `S2` o exenta; con `07` no valen `S2`,
|
|
326
|
+
`N1`, `N2` ni las exenciones `E2`–`E5`; con `08` y con `20` en IGIC tiene que ser
|
|
327
|
+
`N2`; con `11` el único tipo admitido es el 21; con `10` la factura tiene que ser
|
|
328
|
+
`F1` y todos los destinatarios llevar NIF; y con `14` hace falta `fecha_operacion:`
|
|
329
|
+
posterior a la de expedición y destinatarios con NIF de administración pública.
|
|
330
|
+
|
|
331
|
+
Dos avisos sobre esto:
|
|
332
|
+
|
|
333
|
+
- **En IPSI no aplican.** La norma las acota a IVA e IGIC, y en IPSI las claves
|
|
334
|
+
`18`, `19` y `20` significan otra cosa. La gema respeta esa frontera.
|
|
335
|
+
- **La clave `06` no se puede usar.** Exige `BaseImponibleACoste`, un campo que
|
|
336
|
+
la gema no emite, así que se rechaza en local con ese motivo en vez de armar un
|
|
337
|
+
registro que la AEAT va a rechazar igual.
|
|
338
|
+
|
|
339
|
+
Los tipos impositivos de IVA y el recargo de equivalencia que admite cada uno se
|
|
340
|
+
validan contra la fecha de la operación:
|
|
341
|
+
|
|
342
|
+
| Tipo | Recargo de equivalencia admitido | Vigencia del tipo |
|
|
343
|
+
|---|---|---|
|
|
344
|
+
| `21.00` | `5.20` o `1.75` | siempre |
|
|
345
|
+
| `10.00` | `1.40` | siempre |
|
|
346
|
+
| `4.00` | `0.50` | siempre |
|
|
347
|
+
| `0.00` | `0.00`, y solo entre 01-01-2023 y 30-09-2024 | siempre |
|
|
348
|
+
| `5.00` | `0.50` hasta 31-12-2022; `0.62` desde 01-01-2023 | **01-07-2022 – 30-09-2024** |
|
|
349
|
+
| `2.00` | `0.26` | **01-10-2024 – 31-12-2024** |
|
|
350
|
+
| `7.50` | `1.00` | **01-10-2024 – 31-12-2024** |
|
|
351
|
+
|
|
352
|
+
Las dos columnas se validan por separado: un tipo puede estar vigente y aun así
|
|
353
|
+
rechazarse el recargo que le acompañe.
|
|
354
|
+
|
|
355
|
+
Los tres marcados fueron rebajas temporales. Y aquí hay una consecuencia que
|
|
356
|
+
sorprende: como `FechaExpedicionFactura` no puede ser anterior al 28-10-2024,
|
|
357
|
+
**el 5 % ya no es declarable** salvo que informes una `fecha_operacion:` dentro
|
|
358
|
+
de su ventana. Es el origen de los errores 1235 y 1236 de la AEAT.
|
|
359
|
+
|
|
360
|
+
## Componentes
|
|
361
|
+
|
|
362
|
+
| Módulo | Qué hace |
|
|
363
|
+
|---|---|
|
|
364
|
+
| `Libro` | Capa Rails: libro registro, encadenamiento bajo lock y autochequeo |
|
|
365
|
+
| `Libro::Remesa` | Envío por lotes de lo pendiente, con control de flujo y reintentos |
|
|
366
|
+
| `Libro::Reconciliacion` | Contraste de solo lectura contra lo que la AEAT tiene anotado |
|
|
367
|
+
| `Huella` | Serialización canónica y SHA-256 del registro |
|
|
368
|
+
| `RegistroAlta` / `RegistroAnulacion` | El registro: calcula su huella y emite su XML |
|
|
369
|
+
| `Envio` | Documento `RegFactuSistemaFacturacion` (lote de hasta 1000) |
|
|
370
|
+
| `Consulta` / `RespuestaConsulta` | Consulta de lo anotado, con paginación |
|
|
371
|
+
| `Certificado` / `Transporte` | PKCS12, caducidad y cliente HTTP con TLS mutuo |
|
|
372
|
+
| `QR` | URL de cotejo. La AEAT no la devuelve: la construye el SIF |
|
|
373
|
+
| `Detalle` / `Desglose` / `Destinatario` / `Tercero` | Piezas del registro |
|
|
374
|
+
| `Importe` / `Formato` | Importes, fechas y NIF normalizados en un único sitio |
|
|
375
|
+
|
|
376
|
+
## Uso sin Rails
|
|
377
|
+
|
|
378
|
+
El núcleo no depende de Rails ni de ActiveRecord: `require 'verifactu-rails'` da
|
|
379
|
+
`Huella`, los registros, `Envio`, `Consulta` y `Transporte` para construir y
|
|
380
|
+
remitir el XML por tu cuenta. La capa `Libro` se carga aparte
|
|
381
|
+
(`require 'verifactu_rails/libro'`) y sí exige ActiveRecord.
|
|
382
|
+
|
|
383
|
+
## Tests
|
|
384
|
+
|
|
385
|
+
```sh
|
|
386
|
+
createdb verifactu_rails_test # solo la primera vez
|
|
387
|
+
bundle exec rake test
|
|
388
|
+
```
|
|
389
|
+
|
|
390
|
+
Los tests del libro necesitan una base de datos de verdad: hay que demostrar que
|
|
391
|
+
un índice único impide bifurcar la cadena y que el lock serializa, y eso no se
|
|
392
|
+
simula con dobles. Si no la encuentra, la suite **aborta** en vez de saltárselos.
|
|
393
|
+
Si el nombre de la base no parece de tests, también aborta: la suite vacía tablas
|
|
394
|
+
y no debe correr contra datos reales.
|
|
395
|
+
|
|
396
|
+
Incluye los tres vectores oficiales de la AEAT (Especificaciones huella v0.1.2,
|
|
397
|
+
ap. 6), verificación cruzada de la huella contra `josemmo/Verifactu-PHP` y
|
|
398
|
+
`mybooking-es/verifactu-rb`, y validación del XML contra los XSD oficiales,
|
|
399
|
+
versionados en
|
|
400
|
+
[lib/verifactu_rails/schemas](lib/verifactu_rails/schemas/PROCEDENCIA.md).
|
|
401
|
+
|
|
402
|
+
## Licencia
|
|
403
|
+
|
|
404
|
+
MIT. Se distribuye sin garantía de ningún tipo; ver [LICENSE](LICENSE).
|