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
@@ -0,0 +1,25 @@
1
+ <?xml version="1.0" encoding="UTF-8"?>
2
+ <!-- editado con XMLSpy v2019 sp1 (x64) (http://www.altova.com) por Puesto de Trabajo (Agencia Estatal de Administracion Tributaria ((AEAT))) -->
3
+ <!-- edited with XMLSpy v2009 sp1 (http://www.altova.com) by PC Corporativo (AGENCIA TRIBUTARIA) -->
4
+ <schema xmlns="http://www.w3.org/2001/XMLSchema" xmlns:sfLR="https://www2.agenciatributaria.gob.es/static_files/common/internet/dep/aplicaciones/es/aeat/tike/cont/ws/SuministroLR.xsd" xmlns:sf="https://www2.agenciatributaria.gob.es/static_files/common/internet/dep/aplicaciones/es/aeat/tike/cont/ws/SuministroInformacion.xsd" targetNamespace="https://www2.agenciatributaria.gob.es/static_files/common/internet/dep/aplicaciones/es/aeat/tike/cont/ws/SuministroLR.xsd" elementFormDefault="qualified">
5
+ <import namespace="https://www2.agenciatributaria.gob.es/static_files/common/internet/dep/aplicaciones/es/aeat/tike/cont/ws/SuministroInformacion.xsd" schemaLocation="SuministroInformacion.xsd"/>
6
+ <element name="RegFactuSistemaFacturacion">
7
+ <complexType>
8
+ <sequence>
9
+ <element name="Cabecera" type="sf:CabeceraType"/>
10
+ <element name="RegistroFactura" type="sfLR:RegistroFacturaType" maxOccurs="1000"/>
11
+ </sequence>
12
+ </complexType>
13
+ </element>
14
+ <complexType name="RegistroFacturaType">
15
+ <annotation>
16
+ <documentation xml:lang="es">Datos correspondientes a los registros de facturacion</documentation>
17
+ </annotation>
18
+ <sequence>
19
+ <choice>
20
+ <element ref="sf:RegistroAlta"/>
21
+ <element ref="sf:RegistroAnulacion"/>
22
+ </choice>
23
+ </sequence>
24
+ </complexType>
25
+ </schema>
@@ -0,0 +1,5 @@
1
+ <?xml version="1.0"?>
2
+ <catalog xmlns="urn:oasis:names:tc:entity:xmlns:xml:catalog">
3
+ <uri name="http://www.w3.org/TR/xmldsig-core/xmldsig-core-schema.xsd" uri="xmldsig-core-schema.xsd"/>
4
+ <system systemId="http://www.w3.org/TR/xmldsig-core/xmldsig-core-schema.xsd" uri="xmldsig-core-schema.xsd"/>
5
+ </catalog>
@@ -0,0 +1,318 @@
1
+ <?xml version="1.0" encoding="utf-8"?>
2
+ <!DOCTYPE schema
3
+ PUBLIC "-//W3C//DTD XMLSchema 200102//EN" "http://www.w3.org/2001/XMLSchema.dtd"
4
+ [
5
+ <!ATTLIST schema
6
+ xmlns:ds CDATA #FIXED "http://www.w3.org/2000/09/xmldsig#">
7
+ <!ENTITY dsig 'http://www.w3.org/2000/09/xmldsig#'>
8
+ <!ENTITY % p ''>
9
+ <!ENTITY % s ''>
10
+ ]>
11
+
12
+ <!-- Schema for XML Signatures
13
+ http://www.w3.org/2000/09/xmldsig#
14
+ $Revision: 1.2 $ on $Date: 2013/04/16 12:48:49 $ by $Author: denis $
15
+
16
+ Copyright 2001 The Internet Society and W3C (Massachusetts Institute
17
+ of Technology, Institut National de Recherche en Informatique et en
18
+ Automatique, Keio University). All Rights Reserved.
19
+ http://www.w3.org/Consortium/Legal/
20
+
21
+ This document is governed by the W3C Software License [1] as described
22
+ in the FAQ [2].
23
+
24
+ [1] http://www.w3.org/Consortium/Legal/copyright-software-19980720
25
+ [2] http://www.w3.org/Consortium/Legal/IPR-FAQ-20000620.html#DTD
26
+ -->
27
+
28
+
29
+ <schema xmlns="http://www.w3.org/2001/XMLSchema"
30
+ xmlns:ds="http://www.w3.org/2000/09/xmldsig#"
31
+ targetNamespace="http://www.w3.org/2000/09/xmldsig#"
32
+ version="0.1" elementFormDefault="qualified">
33
+
34
+ <!-- Basic Types Defined for Signatures -->
35
+
36
+ <simpleType name="CryptoBinary">
37
+ <restriction base="base64Binary">
38
+ </restriction>
39
+ </simpleType>
40
+
41
+ <!-- Start Signature -->
42
+
43
+ <element name="Signature" type="ds:SignatureType"/>
44
+ <complexType name="SignatureType">
45
+ <sequence>
46
+ <element ref="ds:SignedInfo"/>
47
+ <element ref="ds:SignatureValue"/>
48
+ <element ref="ds:KeyInfo" minOccurs="0"/>
49
+ <element ref="ds:Object" minOccurs="0" maxOccurs="unbounded"/>
50
+ </sequence>
51
+ <attribute name="Id" type="ID" use="optional"/>
52
+ </complexType>
53
+
54
+ <element name="SignatureValue" type="ds:SignatureValueType"/>
55
+ <complexType name="SignatureValueType">
56
+ <simpleContent>
57
+ <extension base="base64Binary">
58
+ <attribute name="Id" type="ID" use="optional"/>
59
+ </extension>
60
+ </simpleContent>
61
+ </complexType>
62
+
63
+ <!-- Start SignedInfo -->
64
+
65
+ <element name="SignedInfo" type="ds:SignedInfoType"/>
66
+ <complexType name="SignedInfoType">
67
+ <sequence>
68
+ <element ref="ds:CanonicalizationMethod"/>
69
+ <element ref="ds:SignatureMethod"/>
70
+ <element ref="ds:Reference" maxOccurs="unbounded"/>
71
+ </sequence>
72
+ <attribute name="Id" type="ID" use="optional"/>
73
+ </complexType>
74
+
75
+ <element name="CanonicalizationMethod" type="ds:CanonicalizationMethodType"/>
76
+ <complexType name="CanonicalizationMethodType" mixed="true">
77
+ <sequence>
78
+ <any namespace="##any" minOccurs="0" maxOccurs="unbounded"/>
79
+ <!-- (0,unbounded) elements from (1,1) namespace -->
80
+ </sequence>
81
+ <attribute name="Algorithm" type="anyURI" use="required"/>
82
+ </complexType>
83
+
84
+ <element name="SignatureMethod" type="ds:SignatureMethodType"/>
85
+ <complexType name="SignatureMethodType" mixed="true">
86
+ <sequence>
87
+ <element name="HMACOutputLength" minOccurs="0" type="ds:HMACOutputLengthType"/>
88
+ <any namespace="##other" minOccurs="0" maxOccurs="unbounded"/>
89
+ <!-- (0,unbounded) elements from (1,1) external namespace -->
90
+ </sequence>
91
+ <attribute name="Algorithm" type="anyURI" use="required"/>
92
+ </complexType>
93
+
94
+ <!-- Start Reference -->
95
+
96
+ <element name="Reference" type="ds:ReferenceType"/>
97
+ <complexType name="ReferenceType">
98
+ <sequence>
99
+ <element ref="ds:Transforms" minOccurs="0"/>
100
+ <element ref="ds:DigestMethod"/>
101
+ <element ref="ds:DigestValue"/>
102
+ </sequence>
103
+ <attribute name="Id" type="ID" use="optional"/>
104
+ <attribute name="URI" type="anyURI" use="optional"/>
105
+ <attribute name="Type" type="anyURI" use="optional"/>
106
+ </complexType>
107
+
108
+ <element name="Transforms" type="ds:TransformsType"/>
109
+ <complexType name="TransformsType">
110
+ <sequence>
111
+ <element ref="ds:Transform" maxOccurs="unbounded"/>
112
+ </sequence>
113
+ </complexType>
114
+
115
+ <element name="Transform" type="ds:TransformType"/>
116
+ <complexType name="TransformType" mixed="true">
117
+ <choice minOccurs="0" maxOccurs="unbounded">
118
+ <any namespace="##other" processContents="lax"/>
119
+ <!-- (1,1) elements from (0,unbounded) namespaces -->
120
+ <element name="XPath" type="string"/>
121
+ </choice>
122
+ <attribute name="Algorithm" type="anyURI" use="required"/>
123
+ </complexType>
124
+
125
+ <!-- End Reference -->
126
+
127
+ <element name="DigestMethod" type="ds:DigestMethodType"/>
128
+ <complexType name="DigestMethodType" mixed="true">
129
+ <sequence>
130
+ <any namespace="##other" processContents="lax" minOccurs="0" maxOccurs="unbounded"/>
131
+ </sequence>
132
+ <attribute name="Algorithm" type="anyURI" use="required"/>
133
+ </complexType>
134
+
135
+ <element name="DigestValue" type="ds:DigestValueType"/>
136
+ <simpleType name="DigestValueType">
137
+ <restriction base="base64Binary"/>
138
+ </simpleType>
139
+
140
+ <!-- End SignedInfo -->
141
+
142
+ <!-- Start KeyInfo -->
143
+
144
+ <element name="KeyInfo" type="ds:KeyInfoType"/>
145
+ <complexType name="KeyInfoType" mixed="true">
146
+ <choice maxOccurs="unbounded">
147
+ <element ref="ds:KeyName"/>
148
+ <element ref="ds:KeyValue"/>
149
+ <element ref="ds:RetrievalMethod"/>
150
+ <element ref="ds:X509Data"/>
151
+ <element ref="ds:PGPData"/>
152
+ <element ref="ds:SPKIData"/>
153
+ <element ref="ds:MgmtData"/>
154
+ <any processContents="lax" namespace="##other"/>
155
+ <!-- (1,1) elements from (0,unbounded) namespaces -->
156
+ </choice>
157
+ <attribute name="Id" type="ID" use="optional"/>
158
+ </complexType>
159
+
160
+ <element name="KeyName" type="string"/>
161
+ <element name="MgmtData" type="string"/>
162
+
163
+ <element name="KeyValue" type="ds:KeyValueType"/>
164
+ <complexType name="KeyValueType" mixed="true">
165
+ <choice>
166
+ <element ref="ds:DSAKeyValue"/>
167
+ <element ref="ds:RSAKeyValue"/>
168
+ <any namespace="##other" processContents="lax"/>
169
+ </choice>
170
+ </complexType>
171
+
172
+ <element name="RetrievalMethod" type="ds:RetrievalMethodType"/>
173
+ <complexType name="RetrievalMethodType">
174
+ <sequence>
175
+ <element ref="ds:Transforms" minOccurs="0"/>
176
+ </sequence>
177
+ <attribute name="URI" type="anyURI"/>
178
+ <attribute name="Type" type="anyURI" use="optional"/>
179
+ </complexType>
180
+
181
+ <!-- Start X509Data -->
182
+
183
+ <element name="X509Data" type="ds:X509DataType"/>
184
+ <complexType name="X509DataType">
185
+ <sequence maxOccurs="unbounded">
186
+ <choice>
187
+ <element name="X509IssuerSerial" type="ds:X509IssuerSerialType"/>
188
+ <element name="X509SKI" type="base64Binary"/>
189
+ <element name="X509SubjectName" type="string"/>
190
+ <element name="X509Certificate" type="base64Binary"/>
191
+ <element name="X509CRL" type="base64Binary"/>
192
+ <any namespace="##other" processContents="lax"/>
193
+ </choice>
194
+ </sequence>
195
+ </complexType>
196
+
197
+ <complexType name="X509IssuerSerialType">
198
+ <sequence>
199
+ <element name="X509IssuerName" type="string"/>
200
+ <element name="X509SerialNumber" type="integer"/>
201
+ </sequence>
202
+ </complexType>
203
+
204
+ <!-- End X509Data -->
205
+
206
+ <!-- Begin PGPData -->
207
+
208
+ <element name="PGPData" type="ds:PGPDataType"/>
209
+ <complexType name="PGPDataType">
210
+ <choice>
211
+ <sequence>
212
+ <element name="PGPKeyID" type="base64Binary"/>
213
+ <element name="PGPKeyPacket" type="base64Binary" minOccurs="0"/>
214
+ <any namespace="##other" processContents="lax" minOccurs="0"
215
+ maxOccurs="unbounded"/>
216
+ </sequence>
217
+ <sequence>
218
+ <element name="PGPKeyPacket" type="base64Binary"/>
219
+ <any namespace="##other" processContents="lax" minOccurs="0"
220
+ maxOccurs="unbounded"/>
221
+ </sequence>
222
+ </choice>
223
+ </complexType>
224
+
225
+ <!-- End PGPData -->
226
+
227
+ <!-- Begin SPKIData -->
228
+
229
+ <element name="SPKIData" type="ds:SPKIDataType"/>
230
+ <complexType name="SPKIDataType">
231
+ <sequence maxOccurs="unbounded">
232
+ <element name="SPKISexp" type="base64Binary"/>
233
+ <any namespace="##other" processContents="lax" minOccurs="0"/>
234
+ </sequence>
235
+ </complexType>
236
+
237
+ <!-- End SPKIData -->
238
+
239
+ <!-- End KeyInfo -->
240
+
241
+ <!-- Start Object (Manifest, SignatureProperty) -->
242
+
243
+ <element name="Object" type="ds:ObjectType"/>
244
+ <complexType name="ObjectType" mixed="true">
245
+ <sequence minOccurs="0" maxOccurs="unbounded">
246
+ <any namespace="##any" processContents="lax"/>
247
+ </sequence>
248
+ <attribute name="Id" type="ID" use="optional"/>
249
+ <attribute name="MimeType" type="string" use="optional"/> <!-- add a grep facet -->
250
+ <attribute name="Encoding" type="anyURI" use="optional"/>
251
+ </complexType>
252
+
253
+ <element name="Manifest" type="ds:ManifestType"/>
254
+ <complexType name="ManifestType">
255
+ <sequence>
256
+ <element ref="ds:Reference" maxOccurs="unbounded"/>
257
+ </sequence>
258
+ <attribute name="Id" type="ID" use="optional"/>
259
+ </complexType>
260
+
261
+ <element name="SignatureProperties" type="ds:SignaturePropertiesType"/>
262
+ <complexType name="SignaturePropertiesType">
263
+ <sequence>
264
+ <element ref="ds:SignatureProperty" maxOccurs="unbounded"/>
265
+ </sequence>
266
+ <attribute name="Id" type="ID" use="optional"/>
267
+ </complexType>
268
+
269
+ <element name="SignatureProperty" type="ds:SignaturePropertyType"/>
270
+ <complexType name="SignaturePropertyType" mixed="true">
271
+ <choice maxOccurs="unbounded">
272
+ <any namespace="##other" processContents="lax"/>
273
+ <!-- (1,1) elements from (1,unbounded) namespaces -->
274
+ </choice>
275
+ <attribute name="Target" type="anyURI" use="required"/>
276
+ <attribute name="Id" type="ID" use="optional"/>
277
+ </complexType>
278
+
279
+ <!-- End Object (Manifest, SignatureProperty) -->
280
+
281
+ <!-- Start Algorithm Parameters -->
282
+
283
+ <simpleType name="HMACOutputLengthType">
284
+ <restriction base="integer"/>
285
+ </simpleType>
286
+
287
+ <!-- Start KeyValue Element-types -->
288
+
289
+ <element name="DSAKeyValue" type="ds:DSAKeyValueType"/>
290
+ <complexType name="DSAKeyValueType">
291
+ <sequence>
292
+ <sequence minOccurs="0">
293
+ <element name="P" type="ds:CryptoBinary"/>
294
+ <element name="Q" type="ds:CryptoBinary"/>
295
+ </sequence>
296
+ <element name="G" type="ds:CryptoBinary" minOccurs="0"/>
297
+ <element name="Y" type="ds:CryptoBinary"/>
298
+ <element name="J" type="ds:CryptoBinary" minOccurs="0"/>
299
+ <sequence minOccurs="0">
300
+ <element name="Seed" type="ds:CryptoBinary"/>
301
+ <element name="PgenCounter" type="ds:CryptoBinary"/>
302
+ </sequence>
303
+ </sequence>
304
+ </complexType>
305
+
306
+ <element name="RSAKeyValue" type="ds:RSAKeyValueType"/>
307
+ <complexType name="RSAKeyValueType">
308
+ <sequence>
309
+ <element name="Modulus" type="ds:CryptoBinary"/>
310
+ <element name="Exponent" type="ds:CryptoBinary"/>
311
+ </sequence>
312
+ </complexType>
313
+
314
+ <!-- End KeyValue Element-types -->
315
+
316
+ <!-- End Signature -->
317
+
318
+ </schema>
@@ -0,0 +1,88 @@
1
+ # frozen_string_literal: true
2
+
3
+ require_relative 'formato'
4
+ require_relative 'error'
5
+
6
+ module VerifactuRails
7
+ # Identificación del sistema informático de facturación, obligatoria en CADA
8
+ # registro remitido (sf:SistemaInformaticoType).
9
+ #
10
+ # Ojo a lo que es esto realmente: la declaración de quién produce el SIF, en
11
+ # cada envío. Los valores describen al SIF DESPLEGADO, no a esta gema, y los
12
+ # aporta quien lo despliega. Esta librería es un componente del SIF, no el SIF.
13
+ class SistemaInformatico
14
+ # TipoUsoPosibleSoloVerifactu va fijo a 'S' y no se puede configurar.
15
+ #
16
+ # No es un capricho: declarar 'N' significa que el SIF puede operar en modo
17
+ # NO VERI*FACTU, y eso obliga a llevar registro de eventos (RD 1007/2023;
18
+ # FAQs Desarrolladores v1.3, ap. 15, nota 1). Esta gema no implementa
19
+ # eventos, así que permitir 'N' sería dejar construir un sistema incompleto
20
+ # sin avisar. Si necesitas 'N', esta no es la librería.
21
+ SOLO_VERIFACTU = 'S'
22
+
23
+ attr_reader :nombre_razon, :nif, :nombre_sistema, :id_sistema, :version,
24
+ :numero_instalacion, :multi_ot, :multiples_ot
25
+
26
+ # @param multi_ot [Boolean] TipoUsoPosibleMultiOT: si el SIF puede usarse
27
+ # para varios obligados tributarios.
28
+ # @param multiples_ot [Boolean] IndicadorMultiplesOT: si de hecho se está
29
+ # usando para varios en este momento.
30
+ def initialize(nombre_razon:, nif:, nombre_sistema:, id_sistema:, version:,
31
+ numero_instalacion:, multi_ot: false, multiples_ot: false)
32
+ @nombre_razon = Formato.limitar(nombre_razon, 'NombreRazon', 120)
33
+ @nif = Formato.nif(nif, 'NIF del productor')
34
+ @nombre_sistema = Formato.limitar(nombre_sistema, 'NombreSistemaInformatico', 30)
35
+ @id_sistema = validar_id_sistema(id_sistema)
36
+ @version = Formato.limitar(version, 'Version', 50)
37
+ @numero_instalacion = Formato.limitar(numero_instalacion, 'NumeroInstalacion', 100)
38
+ # Por Formato.si_no y no por valor de verdad: 'N' es truthy en Ruby y se
39
+ # emitía como 'S', invirtiendo lo que el usuario había declarado.
40
+ @multi_ot = Formato.si_no(multi_ot, 'TipoUsoPosibleMultiOT') == 'S'
41
+ @multiples_ot = Formato.si_no(multiples_ot, 'IndicadorMultiplesOT') == 'S'
42
+
43
+ if @multiples_ot && !@multi_ot
44
+ raise ValidacionError,
45
+ 'IndicadorMultiplesOT no puede ser cierto si TipoUsoPosibleMultiOT ' \
46
+ 'es falso: no puedes estar usándolo para varios obligados si el ' \
47
+ 'sistema declara que no lo admite'
48
+ end
49
+ end
50
+
51
+ # Ap. 3.1.5: "deberá tener rellenas siempre las dos posiciones, cada una de
52
+ # las cuales deberá ser una letra mayúscula, excepto la Ñ, o un dígito
53
+ # numérico". El XSD solo limita la longitud a 2, así que sin esto pasaban
54
+ # valores de una posición o en minúscula.
55
+ PATRON_ID_SISTEMA = /\A[A-Z0-9]{2}\z/
56
+
57
+ def solo_verifactu = SOLO_VERIFACTU
58
+ def multi_ot? = @multi_ot
59
+ def multiples_ot? = @multiples_ot
60
+
61
+ # Orden EXACTO de sf:SistemaInformaticoType. Es una <sequence>: alterarlo
62
+ # invalida el documento.
63
+ def a_pares
64
+ {
65
+ 'NombreRazon' => nombre_razon,
66
+ 'NIF' => nif,
67
+ 'NombreSistemaInformatico' => nombre_sistema,
68
+ 'IdSistemaInformatico' => id_sistema,
69
+ 'Version' => version,
70
+ 'NumeroInstalacion' => numero_instalacion,
71
+ 'TipoUsoPosibleSoloVerifactu' => SOLO_VERIFACTU,
72
+ 'TipoUsoPosibleMultiOT' => multi_ot ? 'S' : 'N',
73
+ 'IndicadorMultiplesOT' => multiples_ot ? 'S' : 'N'
74
+ }
75
+ end
76
+
77
+ private
78
+
79
+ def validar_id_sistema(valor)
80
+ cadena = Formato.limitar(valor, 'IdSistemaInformatico', 2)
81
+ return cadena if cadena.match?(PATRON_ID_SISTEMA)
82
+
83
+ raise ValidacionError,
84
+ 'IdSistemaInformatico debe tener exactamente dos posiciones, cada una ' \
85
+ "una letra mayúscula (salvo la Ñ) o un dígito: #{cadena.inspect}"
86
+ end
87
+ end
88
+ end
@@ -0,0 +1,127 @@
1
+ # frozen_string_literal: true
2
+
3
+ require 'net/http'
4
+ require 'uri'
5
+ require_relative 'certificado'
6
+ require_relative 'error'
7
+ require_relative 'formato'
8
+
9
+ module VerifactuRails
10
+ class TransporteError < StandardError
11
+ include Error
12
+ end
13
+
14
+ # Cliente HTTP con autenticación mutua TLS contra el servicio VERI*FACTU.
15
+ #
16
+ # Sin Savon: el servicio es un único endpoint con un sobre SOAP fijo, así que
17
+ # Net::HTTP de la stdlib sobra. Nos ahorra httpi, wasabi, gyoku, akami y nori.
18
+ class Transporte
19
+ # Preproducción y producción se corresponden uno a uno: prewww1 <-> www1,
20
+ # prewww2 <-> www2, prewww10 <-> www10 (este último, el de certificado de
21
+ # sello). Fuente: portal de pruebas externas de la AEAT.
22
+ #
23
+ # Aviso operativo del propio portal: preproducción es para pruebas puntuales,
24
+ # NO para pruebas masivas ni para validaciones integradas en procesos de
25
+ # producción. Un uso que consideren abusivo puede acabar en bloqueo.
26
+ ENDPOINTS = {
27
+ [:pruebas, false] => 'https://prewww1.aeat.es/wlpl/TIKE-CONT/ws/SistemaFacturacion/VerifactuSOAP',
28
+ [:pruebas, true] => 'https://prewww10.aeat.es/wlpl/TIKE-CONT/ws/SistemaFacturacion/VerifactuSOAP',
29
+ [:produccion, false] => 'https://www1.agenciatributaria.gob.es/wlpl/TIKE-CONT/ws/SistemaFacturacion/VerifactuSOAP',
30
+ [:produccion, true] => 'https://www10.agenciatributaria.gob.es/wlpl/TIKE-CONT/ws/SistemaFacturacion/VerifactuSOAP'
31
+ }.freeze
32
+
33
+ SOAP_NS = 'http://schemas.xmlsoap.org/soap/envelope/'
34
+
35
+ attr_reader :certificado, :entorno, :url
36
+
37
+ # @param sello [Boolean, nil] fuerza el endpoint de sello de entidad.
38
+ # Si es nil se deduce del propio certificado.
39
+ def initialize(certificado:, entorno: :pruebas, sello: nil, url: nil,
40
+ ca_file: nil, timeout: 30)
41
+ unless %i[pruebas produccion].include?(entorno)
42
+ raise ValidacionError, "Entorno inválido: #{entorno.inspect} (usa :pruebas o :produccion)"
43
+ end
44
+
45
+ @certificado = Formato.objeto(certificado, 'certificado', Certificado)
46
+ @entorno = entorno
47
+ # Sin normalizar, un sello: 'S' llegaba a ENDPOINTS.fetch([entorno, 'S']) y
48
+ # daba un KeyError, justo al lado de la comprobación de entorno que sí da
49
+ # un error del dominio.
50
+ @sello = sello.nil? ? @certificado.sello? : Formato.si_no(sello, 'sello') == 'S'
51
+ @url = url || ENDPOINTS.fetch([entorno, @sello])
52
+ @ca_file = ca_file
53
+ @timeout = timeout
54
+ end
55
+
56
+ def sello? = @sello
57
+
58
+ # Envía el XML del registro ya construido. Devuelve la respuesta cruda:
59
+ # el parseo es responsabilidad de la capa superior.
60
+ def enviar(xml_registro)
61
+ peticion = Net::HTTP::Post.new(uri)
62
+ peticion['Content-Type'] = 'text/xml; charset=utf-8'
63
+ peticion['SOAPAction'] = '""'
64
+ peticion.body = envolver(xml_registro)
65
+
66
+ respuesta = cliente.request(peticion)
67
+ { codigo: respuesta.code.to_i, cuerpo: respuesta.body }
68
+ rescue OpenSSL::SSL::SSLError => e
69
+ raise TransporteError, mensaje_ssl(e)
70
+ rescue Net::OpenTimeout, Net::ReadTimeout => e
71
+ raise TransporteError, "Timeout contra #{uri.host}: #{e.class}"
72
+ end
73
+
74
+ # Una declaración <?xml?> solo puede ir al principio del documento, y
75
+ # Envio#to_xml emite la suya. Incrustarla tal cual dentro del Body producía
76
+ # un sobre MAL FORMADO con dos declaraciones, que la AEAT contestó con
77
+ # "Codigo[102].Error interno en el servidor": el fallo era de parseo, no de
78
+ # validación, así que el mensaje no orientaba en absoluto.
79
+ DECLARACION_XML = /\A\s*<\?xml[^>]*\?>\s*/
80
+
81
+ def envolver(xml_registro)
82
+ cuerpo = xml_registro.to_s.sub(DECLARACION_XML, '')
83
+ # document/literal según el WSDL: el Body lleva directamente el elemento,
84
+ # sin envoltorio de operación.
85
+ <<~XML
86
+ <?xml version="1.0" encoding="UTF-8"?>
87
+ <soapenv:Envelope xmlns:soapenv="#{SOAP_NS}">
88
+ <soapenv:Header/>
89
+ <soapenv:Body>#{cuerpo}</soapenv:Body>
90
+ </soapenv:Envelope>
91
+ XML
92
+ end
93
+
94
+ def cliente
95
+ http = Net::HTTP.new(uri.host, uri.port)
96
+ http.use_ssl = true
97
+ http.cert = certificado.certificado
98
+ http.key = certificado.clave
99
+ http.extra_chain_cert = certificado.cadena if certificado.cadena.any?
100
+ # NUNCA VERIFY_NONE. Si falla la verificación, el arreglo es aportar la
101
+ # cadena de la CA correcta en ca_file, no desactivar la comprobación.
102
+ http.verify_mode = OpenSSL::SSL::VERIFY_PEER
103
+ http.ca_file = @ca_file if @ca_file
104
+ http.open_timeout = @timeout
105
+ http.read_timeout = @timeout
106
+ http
107
+ end
108
+
109
+ private
110
+
111
+ def uri = @uri ||= URI.parse(@url)
112
+
113
+ def mensaje_ssl(error)
114
+ if error.message.include?('certificate verify failed')
115
+ "Fallo al verificar el certificado de servidor de #{uri.host}. " \
116
+ 'Desde la renovación de noviembre de 2025 la AEAT usa CA públicas ' \
117
+ '(Entrust/Sectigo bajo USERTrust RSA), así que la causa habitual NO es ' \
118
+ 'que falte la cadena: mira si el almacén de confianza del sistema está ' \
119
+ 'anticuado o si un proxy corporativo intercepta el TLS. Si aun así ' \
120
+ 'necesitas anclar la cadena, pásala en ca_file. No uses VERIFY_NONE: ' \
121
+ "(#{error.message})"
122
+ else
123
+ "Error TLS contra #{uri.host}: #{error.message}"
124
+ end
125
+ end
126
+ end
127
+ end
@@ -0,0 +1,5 @@
1
+ # frozen_string_literal: true
2
+
3
+ module VerifactuRails
4
+ VERSION = '0.1.0.pre'
5
+ end