Module: VerifactuRails::Huella
- Defined in:
- lib/verifactu_rails/huella.rb
Overview
Cálculo de la huella (hash) de los registros de facturación.
Referencia normativa: RD 1007/2023 art. 7, Orden HAC/1177/2024, y el documento "Detalle de las especificaciones técnicas para la generación de la huella o hash de los registros" publicado en la web de desarrolladores de la AEAT.
La cadena se construye como pares "Campo=valor" unidos por "&", SIN escapado de los valores, y se le aplica SHA-256 devuelto en hexadecimal MAYÚSCULAS.
Constant Summary collapse
- PATRON_HUELLA =
/\A[0-9A-F]{64}\z/- CAMPOS_ALTA =
%w[ IDEmisorFactura NumSerieFactura FechaExpedicionFactura TipoFactura CuotaTotal ImporteTotal Huella FechaHoraHusoGenRegistro ].freeze
- CAMPOS_ANULACION =
%w[ IDEmisorFacturaAnulada NumSerieFacturaAnulada FechaExpedicionFacturaAnulada Huella FechaHoraHusoGenRegistro ].freeze
Class Method Summary collapse
-
.alta(**datos) ⇒ Object
Huella de un registro de facturación de ALTA.
-
.anulacion(**datos) ⇒ Object
Huella de un registro de facturación de ANULACIÓN.
-
.cadena_alta(id_emisor:, num_serie:, fecha_expedicion:, tipo_factura:, cuota_total:, importe_total:, fecha_hora_gen:, huella_anterior: nil) ⇒ Object
Cadena exacta sobre la que se calcula la huella de un ALTA.
-
.cadena_anulacion(id_emisor:, num_serie:, fecha_expedicion:, fecha_hora_gen:, huella_anterior:) ⇒ Object
Cadena exacta sobre la que se calcula la huella de una ANULACIÓN.
- .digerir(cadena) ⇒ Object
-
.serializar(pares, orden) ⇒ Object
Expone la cadena previa al hash.
Class Method Details
.alta(**datos) ⇒ Object
Huella de un registro de facturación de ALTA.
35 36 37 |
# File 'lib/verifactu_rails/huella.rb', line 35 def alta(**datos) digerir(cadena_alta(**datos)) end |
.anulacion(**datos) ⇒ Object
Huella de un registro de facturación de ANULACIÓN. Aquí huella_anterior es SIEMPRE obligatoria: una anulación nunca puede ser el primer registro de la cadena.
63 64 65 |
# File 'lib/verifactu_rails/huella.rb', line 63 def anulacion(**datos) digerir(cadena_anulacion(**datos)) end |
.cadena_alta(id_emisor:, num_serie:, fecha_expedicion:, tipo_factura:, cuota_total:, importe_total:, fecha_hora_gen:, huella_anterior: nil) ⇒ Object
Cadena exacta sobre la que se calcula la huella de un ALTA.
Es pública a propósito y por dos motivos. Uno: cuando la AEAT rechaza por huella, lo primero que hay que comparar es ESTE string, no el digest. Dos: que los tests puedan afirmar sobre la cadena sin reconstruirla por su cuenta, que es como se quedan ciegos ante un cambio de normalizador.
45 46 47 48 49 50 51 52 53 54 55 56 57 58 |
# File 'lib/verifactu_rails/huella.rb', line 45 def cadena_alta(id_emisor:, num_serie:, fecha_expedicion:, tipo_factura:, cuota_total:, importe_total:, fecha_hora_gen:, huella_anterior: nil) pares = { 'IDEmisorFactura' => texto(id_emisor, 'IDEmisorFactura'), 'NumSerieFactura' => texto(num_serie, 'NumSerieFactura'), 'FechaExpedicionFactura' => fecha(fecha_expedicion), 'TipoFactura' => texto(tipo_factura, 'TipoFactura'), 'CuotaTotal' => Importe.formatear(cuota_total), 'ImporteTotal' => Importe.formatear(importe_total), 'Huella' => encadenamiento(huella_anterior), 'FechaHoraHusoGenRegistro' => marca_temporal(fecha_hora_gen) } serializar(pares, CAMPOS_ALTA) end |
.cadena_anulacion(id_emisor:, num_serie:, fecha_expedicion:, fecha_hora_gen:, huella_anterior:) ⇒ Object
Cadena exacta sobre la que se calcula la huella de una ANULACIÓN.
68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 |
# File 'lib/verifactu_rails/huella.rb', line 68 def cadena_anulacion(id_emisor:, num_serie:, fecha_expedicion:, fecha_hora_gen:, huella_anterior:) if huella_anterior.nil? || huella_anterior.to_s.empty? raise ValidacionError, 'Un registro de anulación exige huella_anterior: no puede iniciar cadena' end pares = { 'IDEmisorFacturaAnulada' => texto(id_emisor, 'IDEmisorFacturaAnulada'), 'NumSerieFacturaAnulada' => texto(num_serie, 'NumSerieFacturaAnulada'), 'FechaExpedicionFacturaAnulada' => fecha(fecha_expedicion), 'Huella' => encadenamiento(huella_anterior), 'FechaHoraHusoGenRegistro' => marca_temporal(fecha_hora_gen) } serializar(pares, CAMPOS_ANULACION) end |
.digerir(cadena) ⇒ Object
91 92 93 |
# File 'lib/verifactu_rails/huella.rb', line 91 def digerir(cadena) Digest::SHA256.hexdigest(cadena.encode(Encoding::UTF_8)).upcase end |
.serializar(pares, orden) ⇒ Object
Expone la cadena previa al hash. Imprescindible para depurar: cuando la AEAT rechaza por huella, lo primero es comparar ESTE string, no el digest.
87 88 89 |
# File 'lib/verifactu_rails/huella.rb', line 87 def serializar(pares, orden) orden.map { |campo| "#{campo}=#{pares.fetch(campo)}" }.join('&') end |