unit fuverifactuhasher;

interface

uses
  SysUtils, Classes, IdHashSHA, StrUtils, Math, IdGlobal;

type
  // Estructura ligera para pasar datos sin acoplarse al DataSet
  TDatosFacturaHash = record
    NifEmisor: string;
    Serie: string;
    Numero: string;      // Tu campo 'id'
    FechaExpedicion: TDateTime;
    TipoFactura: string; // F1, F2, R1...
    BaseImponible: Currency; // Usar Currency en Delphi para dinero
    ImporteTotal: Currency;
    HuellaAnterior: string; // Hash del registro anterior (vaco si es el primero)
  end;

  TVeriFactuHasher = class
  private
    class function FormatearImporte(const AValue: Currency): string;
    class function FormatearFecha(const ADate: TDateTime): string;
    class function LimpiarCadena(const AValue: string): string;
  public
    /// <summary>
    /// Genera la huella SHA-256 segn especificaciones del Art. 264-272 de la doc tcnica.
    /// </summary>
    class function GenerarHuella(const Datos: TDatosFacturaHash): string;
  end;

implementation

{ TVeriFactuHasher }

class function TVeriFactuHasher.FormatearImporte(const AValue: Currency): string;
var
  FormatSettings: TFormatSettings;
begin
  // REQUISITO TCNICO[cite: 269, 270, 277]:
  // Dos decimales, separador decimal punto (.), sin separador de miles.
  GetLocaleFormatSettings(0, FormatSettings);
  FormatSettings.DecimalSeparator := '.';
  FormatSettings.ThousandSeparator := #0; // Null char, sin miles

  // 'f' = fixed, 15 precisin, 2 decimales
  Result := FloatToStrF(AValue, ffFixed, 15, 2, FormatSettings);
end;

class function TVeriFactuHasher.FormatearFecha(const ADate: TDateTime): string;
begin
  // REQUISITO TCNICO[cite: 268]: Formato DDMMAAAA
  Result := FormatDateTime('ddmmyyyy', ADate);
end;

class function TVeriFactuHasher.LimpiarCadena(const AValue: string): string;
begin
  // Asegurar que no hay espacios extra y convertir a maysculas si es necesario
  // (La norma no obliga a maysculas en los datos, pero s en el Hex del hash final)
  Result := Trim(AValue);
end;

class function TVeriFactuHasher.GenerarHuella(const Datos: TDatosFacturaHash): string;
var
  CadenaConcatenada: string;
  SHA256: TIdHashSHA256;
begin
  // 1. Construccin de la cadena de concatenacin ("Payload")
  // El orden es ESTRICTO segn documentacin [cite: 264-272, 275]

  // ID Emisor + Serie + Numero + Fecha + Base + Total + HuellaAnterior
  CadenaConcatenada :=
    LimpiarCadena(Datos.NifEmisor) +
    LimpiarCadena(Datos.Serie) +
    LimpiarCadena(Datos.Numero) +
    FormatearFecha(Datos.FechaExpedicion) +
    FormatearImporte(Datos.BaseImponible) +
    FormatearImporte(Datos.ImporteTotal) +
    LimpiarCadena(Datos.HuellaAnterior); // Vaco si es el primer registro [cite: 273]

  // NOTA PARA DEPURACIN:
  // Es til loguear 'CadenaConcatenada' si el hash no coincide con el validador de la AEAT.

  // 2. Clculo del Hash SHA-256
  SHA256 := TIdHashSHA256.Create;
  try
    // Importante: Indy maneja Strings como UTF-8 por defecto en versiones modernas,
    // pero en XE3 es mejor ser explcito si usas caracteres especiales.
    // La AEAT espera UTF-8[cite: 306].
    Result := SHA256.HashStringAsHex(CadenaConcatenada, IndyTextEncoding_UTF8);

    // El resultado debe estar en maysculas
    Result := UpperCase(Result);
  finally
    SHA256.Free;
  end;
end;

end.
