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.
Files changed (42) hide show
  1. checksums.yaml +7 -0
  2. data/COMPLIANCE.md +149 -0
  3. data/LICENSE +21 -0
  4. data/README.md +404 -0
  5. data/doc/FUENTES.md +505 -0
  6. data/lib/generators/verifactu/install/install_generator.rb +61 -0
  7. data/lib/generators/verifactu/install/templates/instalar_verifactu.rb.tt +19 -0
  8. data/lib/generators/verifactu/install/templates/verifactu.rb.tt +64 -0
  9. data/lib/verifactu-rails.rb +31 -0
  10. data/lib/verifactu_rails/certificado.rb +122 -0
  11. data/lib/verifactu_rails/consulta.rb +279 -0
  12. data/lib/verifactu_rails/desglose.rb +396 -0
  13. data/lib/verifactu_rails/envio.rb +153 -0
  14. data/lib/verifactu_rails/error.rb +31 -0
  15. data/lib/verifactu_rails/formato.rb +199 -0
  16. data/lib/verifactu_rails/huella.rb +130 -0
  17. data/lib/verifactu_rails/importe.rb +80 -0
  18. data/lib/verifactu_rails/libro/autochequeo.rb +67 -0
  19. data/lib/verifactu_rails/libro/cadena.rb +110 -0
  20. data/lib/verifactu_rails/libro/migracion.rb +92 -0
  21. data/lib/verifactu_rails/libro/reconciliacion.rb +224 -0
  22. data/lib/verifactu_rails/libro/registro.rb +74 -0
  23. data/lib/verifactu_rails/libro/remesa.rb +120 -0
  24. data/lib/verifactu_rails/libro.rb +89 -0
  25. data/lib/verifactu_rails/qr.rb +56 -0
  26. data/lib/verifactu_rails/railtie.rb +30 -0
  27. data/lib/verifactu_rails/registro.rb +833 -0
  28. data/lib/verifactu_rails/respuesta.rb +154 -0
  29. data/lib/verifactu_rails/schemas/ConsultaLR.xsd +54 -0
  30. data/lib/verifactu_rails/schemas/EventosSIF.xsd +823 -0
  31. data/lib/verifactu_rails/schemas/PROCEDENCIA.md +64 -0
  32. data/lib/verifactu_rails/schemas/RespuestaConsultaLR.xsd +201 -0
  33. data/lib/verifactu_rails/schemas/RespuestaSuministro.xsd +139 -0
  34. data/lib/verifactu_rails/schemas/RespuestaValRegistNoVeriFactu.xsd +103 -0
  35. data/lib/verifactu_rails/schemas/SuministroInformacion.xsd +1390 -0
  36. data/lib/verifactu_rails/schemas/SuministroLR.xsd +25 -0
  37. data/lib/verifactu_rails/schemas/catalog.xml +5 -0
  38. data/lib/verifactu_rails/schemas/xmldsig-core-schema.xsd +318 -0
  39. data/lib/verifactu_rails/sistema_informatico.rb +88 -0
  40. data/lib/verifactu_rails/transporte.rb +127 -0
  41. data/lib/verifactu_rails/version.rb +5 -0
  42. 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).