Todas las herramientas

Cotejo · referencia técnica

La huella de un registro Verifactu

Cómo se calcula el hash SHA-256 que encadena los registros de facturación: el orden exacto de los campos, las reglas de formato que provocan casi todos los fallos, y los tres vectores de prueba oficiales de la AEAT.

En corto. Cada registro de facturación lleva una huella SHA-256 que incluye la huella del registro anterior, formando una cadena. Se calcula sobre una cadena de texto campo=valor&campo=valor con los campos en un orden fijo, codificada en UTF-8, y se escribe en hexadecimal, en mayúsculas, con 64 caracteres.

El campo Huella de esa cadena es el del registro anterior, y va vacío (Huella=) en el primero. Aun así, la huella del propio registro se calcula e informa siempre.

1. El algoritmo y la cadena canónica

El algoritmo es SHA-256, y a día de hoy es el único permitido. La entrada es una cadena de texto codificada en UTF-8. La salida va en hexadecimal, en mayúsculas, y siempre ocupa 64 caracteres.

Los campos que entran en el cálculo se concatenan así:

nombreCampo1=valorCampo1&nombreCampo2=valorCampo2&…&nombreCampoN=valorCampoN

Uno de esos campos es Huella y contiene la huella del registro anterior. Ahí está el encadenamiento: el registro N incorpora el hash del N−1, así que alterar un registro pasado invalida todos los posteriores. En el primero de un sistema de facturación no hay anterior, así que el campo va vacío, pero la huella del registro sí debe calcularse e informarse igualmente.

// La huella de un registro de alta. El campo Huella es la del registro ANTERIOR:
// va vacío en el primero de la cadena.
const campos = [
  ["IDEmisorFactura", "89890001K"],
  ["NumSerieFactura", "12345678/G33"],
  ["FechaExpedicionFactura", "01-01-2024"],
  ["TipoFactura", "F1"],
  ["CuotaTotal", "12.35"],
  ["ImporteTotal", "123.45"],
  ["Huella", ""],
  ["FechaHoraHusoGenRegistro", "2024-01-01T19:20:30+01:00"],
];

const cadena = campos.map(([k, v]) => `${k}=${v}`).join("&");

const bytes = new TextEncoder().encode(cadena);          // UTF-8
const digest = await crypto.subtle.digest("SHA-256", bytes);

const huella = [...new Uint8Array(digest)]
  .map((b) => b.toString(16).padStart(2, "0"))
  .join("")
  .toUpperCase();                                        // 64 caracteres

// 3C464DAF61ACB827C65FDA19F352A4E3BDC2C640E9E9FC4CC058073F38F12F60
→ Comprueba tu cadena de hash con la herramienta

2. Los campos, y en qué orden van

El orden no es negociable: es el de aparición en los diseños de registro del anexo de la orden, y cualquier permuta da una huella distinta. Hay tres juegos de campos según el tipo de registro.

Registro de alta

#CampoRuta en el XML
1IDEmisorFacturaRegistroAlta/IDFactura/IDEmisorFactura
2NumSerieFacturaRegistroAlta/IDFactura/NumSerieFactura
3FechaExpedicionFacturaRegistroAlta/IDFactura/FechaExpedicionFactura
4TipoFacturaRegistroAlta/TipoFactura
5CuotaTotalRegistroAlta/CuotaTotal
6ImporteTotalRegistroAlta/ImporteTotal
7HuellaRegistroAlta/Encadenamiento/RegistroAnterior/Huella
8FechaHoraHusoGenRegistroRegistroAlta/FechaHoraHusoGenRegistro

Registro de anulación

#CampoRuta en el XML
1IDEmisorFacturaAnuladaRegistroAnulacion/IDFactura/IDEmisorFacturaAnulada
2NumSerieFacturaAnuladaRegistroAnulacion/IDFactura/NumSerieFacturaAnulada
3FechaExpedicionFacturaAnuladaRegistroAnulacion/IDFactura/FechaExpedicionFacturaAnulada
4HuellaRegistroAnulacion/Encadenamiento/RegistroAnterior/Huella
5FechaHoraHusoGenRegistroRegistroAnulacion/FechaHoraHusoGenRegistro

Registro de evento

#CampoRuta en el XML
1NIFRegistroEvento/Evento/SistemaInformatico/NIF
2IDRegistroEvento/Evento/SistemaInformatico/IDOtro/ID
3IdSistemaInformaticoRegistroEvento/Evento/SistemaInformatico/IdSistemaInformatico
4VersionRegistroEvento/Evento/SistemaInformatico/Version
5NumeroInstalacionRegistroEvento/Evento/SistemaInformatico/NumeroInstalacion
6NIFRegistroEvento/Evento/ObligadoEmision/NIF
7TipoEventoRegistroEvento/Evento/TipoEvento
8HuellaEventoRegistroEvento/Evento/Encadenamiento/EventoAnterior/HuellaEvento
9FechaHoraHusoGenEventoRegistroEvento/Evento/FechaHoraHusoGenEvento

El nombre «NIF» aparece dos veces en los eventos

En el registro de evento, NIF figura en las posiciones 1 y 6: una vez para el sistema informático y otra para el obligado a emitir. Si modelas los campos como un mapa nombre → valor, el segundo pisa al primero y la huella sale mal. Los campos son una lista ordenada, no un objeto.

3. Reglas de formato de los valores

El valor de cada campo es el que lleva el XML, pero con tres matices que provocan casi todos los fallos de huella:

4. Vectores de prueba oficiales

Son los tres ejemplos del documento de la AEAT. Si tu implementación reproduce estas tres huellas, el cálculo es correcto. Cotejo los tiene congelados como tests.

Caso 1 · primer registro de alta · cadena canónica

IDEmisorFactura=89890001K&NumSerieFactura=12345678/G33&FechaExpedicionFactura=01-01-2024&TipoFactura=F1&CuotaTotal=12.35&ImporteTotal=123.45&Huella=&FechaHoraHusoGenRegistro=2024-01-01T19:20:30+01:00

SHA-256

3C464DAF61ACB827C65FDA19F352A4E3BDC2C640E9E9FC4CC058073F38F12F60

Caso 2 · alta encadenada · cadena canónica

IDEmisorFactura=89890001K&NumSerieFactura=12345679/G34&FechaExpedicionFactura=01-01-2024&TipoFactura=F1&CuotaTotal=12.35&ImporteTotal=123.45&Huella=3C464DAF61ACB827C65FDA19F352A4E3BDC2C640E9E9FC4CC058073F38F12F60&FechaHoraHusoGenRegistro=2024-01-01T19:20:35+01:00

SHA-256

F7B94CFD8924EDFF273501B01EE5153E4CE8F259766F88CF6ACB8935802A2B97

Caso 3 · anulación encadenada · cadena canónica

IDEmisorFacturaAnulada=89890001K&NumSerieFacturaAnulada=12345679/G34&FechaExpedicionFacturaAnulada=01-01-2024&Huella=F7B94CFD8924EDFF273501B01EE5153E4CE8F259766F88CF6ACB8935802A2B97&FechaHoraHusoGenRegistro=2024-01-01T19:20:40+01:00

SHA-256

177547C0D57AC74748561D054A9CEC14B4C4EA23D1BEFD6F2E69E3A388F90C68

Fíjate en cómo la huella del caso 1 aparece dentro de la cadena del caso 2, y la del 2 dentro de la del 3. Eso es la cadena.

5. Fuentes oficiales

Todo lo anterior sale del documento de la AEAT, que es la fuente de verdad. Si algo de esta página lo contradice, mándanos un aviso: tiene razón él.

El marco legal es el Real Decreto 1007/2023 y la Orden HAC/1177/2024.

La otra mitad de la norma es el código impreso en la factura: el código QR de Verifactu.