unit uDecaEmisionAlbaranes;

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes,
  Vcl.Graphics, Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.StdCtrls, Vcl.ExtCtrls,
  Vcl.ComCtrls, Winapi.ShellAPI, datos, ZDataset, Data.DB, System.Math,
  {$IF CompilerVersion >= 27.0}
  System.JSON,
  {$ELSE}
  Data.DBXJSON,
  {$IFEND}
  uDecaConfig, uDecaConfiguracion;

type
  TAlbaranItem = record
    IdEmpresa: Integer;
    Id: Integer;
    Serie: string;
    Documento: string;
    Fecha: TDateTime;
    Cliente: string;
    NifCliente: string;
    Destino: string;
    Direccion: string;
    CP: string;
    Poblacion: string;
    Provincia: string;
    Matricula: string;
    Palets: Integer;
    Bultos: Double;
    Kilos: Double;
    DecaCodigo: string;
  end;
  TAlbaranItemList = TArray<TAlbaranItem>;

  TDecaVehiculoPreset = record
    Id: Integer;
    MatriculaTractora: string;
    MatriculaRemolque: string;
    TipoVehiculo: string;
    ConductorNombre: string;
    ConductorNif: string;
    ConductorTelefono: string;
  end;

  TDecaContactoPreset = record
    Id: Integer;
    Tipo: string;
    Nombre: string;
    Nif: string;
    Direccion: string;
    CP: string;
    Localidad: string;
    Provincia: string;
    Telefono: string;
  end;

  TfDecaEmisionAlbaranes = class(TForm)
    pnlHeader: TPanel;
    lblTitulo: TLabel;
    lblSubtitulo: TLabel;
    pnlAlbaranes: TPanel;
    lblInfoAlbaranes: TLabel;
    lblResumenTotales: TLabel;
    pgcDatos: TPageControl;
    tsCargador: TTabSheet;
    grpCargador: TGroupBox;
    lblCargarCargador: TLabel;
    lblCargadorNombre: TLabel;
    lblCargadorNif: TLabel;
    lblCargadorDir: TLabel;
    lblCargadorCP: TLabel;
    lblCargadorPob: TLabel;
    lblCargadorProv: TLabel;
    lblCargadorTel: TLabel;
    lblAvisoCargador: TLabel;
    cmbPresetCargador: TComboBox;
    btnUsarMiEmpresaCargador: TButton;
    btnLimpiarCargador: TButton;
    edtCargadorNombre: TEdit;
    edtCargadorNif: TEdit;
    edtCargadorDir: TEdit;
    edtCargadorCP: TEdit;
    edtCargadorPob: TEdit;
    edtCargadorProv: TEdit;
    edtCargadorTel: TEdit;
    tsTransporte: TTabSheet;
    grpTransportista: TGroupBox;
    lblCargarTransp: TLabel;
    cmbPresetTransp: TComboBox;
    lblTranspNombre: TLabel;
    lblTranspNif: TLabel;
    edtTranspNombre: TEdit;
    edtTranspNif: TEdit;
    grpVehiculo: TGroupBox;
    lblCargarVehiculo: TLabel;
    cmbPresetVehiculo: TComboBox;
    lblMatriculaTractora: TLabel;
    lblMatriculaRemolque: TLabel;
    lblConductorNombre: TLabel;
    lblConductorNif: TLabel;
    edtMatriculaTractora: TEdit;
    edtMatriculaRemolque: TEdit;
    edtConductorNombre: TEdit;
    edtConductorNif: TEdit;
    grpMercancia: TGroupBox;
    lblMercanciaNaturaleza: TLabel;
    lblPalets: TLabel;
    lblBultos: TLabel;
    lblKilos: TLabel;
    edtMercanciaNaturaleza: TEdit;
    edtPalets: TEdit;
    edtBultos: TEdit;
    edtKilos: TEdit;
    tsRuta: TTabSheet;
    grpOrigen: TGroupBox;
    lblCargarOrigen: TLabel;
    cmbPresetOrigen: TComboBox;
    lblOrigenNombre: TLabel;
    lblOrigenDir: TLabel;
    lblOrigenPob: TLabel;
    edtOrigenNombre: TEdit;
    edtOrigenDir: TEdit;
    edtOrigenPob: TEdit;
    grpDestino: TGroupBox;
    lblCargarDestino: TLabel;
    cmbPresetDestino: TComboBox;
    lblDestinoNombre: TLabel;
    lblDestinoDir: TLabel;
    lblDestinoPob: TLabel;
    edtDestinoNombre: TEdit;
    edtDestinoDir: TEdit;
    edtDestinoPob: TEdit;
    lblParadas: TLabel;
    mmoParadas: TMemo;
    tsDeCA: TTabSheet;
    pnlResultadoDeCA: TPanel;
    lblTituloDeca: TLabel;
    lblCodigoDeca: TLabel;
    lblUrlQr: TLabel;
    edtUrlQr: TEdit;
    lblEstadoEmision: TLabel;
    pnlFooter: TPanel;
    btnEmitirDeCA: TButton;
    btnVerPDF: TButton;
    btnWhatsApp: TButton;
    btnCerrar: TButton;
    btnConfiguracion: TButton;
    procedure FormCreate(Sender: TObject);
    procedure btnEmitirDeCAClick(Sender: TObject);
    procedure btnVerPDFClick(Sender: TObject);
    procedure btnWhatsAppClick(Sender: TObject);
    procedure btnCerrarClick(Sender: TObject);
    procedure btnConfiguracionClick(Sender: TObject);
    procedure cmbPresetTranspChange(Sender: TObject);
    procedure cmbPresetVehiculoChange(Sender: TObject);
    procedure cmbPresetOrigenChange(Sender: TObject);
    procedure cmbPresetDestinoChange(Sender: TObject);
    procedure cmbPresetCargadorChange(Sender: TObject);
    procedure btnUsarMiEmpresaCargadorClick(Sender: TObject);
    procedure btnLimpiarCargadorClick(Sender: TObject);
  private
    FAlbaranes: TAlbaranItemList;
    FCodigoDeca: string;
    FUrlDescargaQr: string;
    FDecaConfig: TDecaConfig;
    FVehiculosPresets: TArray<TDecaVehiculoPreset>;
    FContactosPresets: TArray<TDecaContactoPreset>;
    procedure CargarPresetsDesdeBD;
    procedure CargarDatosEmpresaYAlbaranes;
    function ExtraerValorJson(const AJson, AClave: string): string;
    function EscaparJson(const S: string): string;
    function NormalizarUrlPdf(const AUrl: string): string;
  public
    class procedure Ejecutar(AOwner: TComponent; const AAlbaranes: TAlbaranItemList);
  end;

var
  fDecaEmisionAlbaranes: TfDecaEmisionAlbaranes;

implementation

{$R *.dfm}

{ TfDecaEmisionAlbaranes }

class procedure TfDecaEmisionAlbaranes.Ejecutar(AOwner: TComponent; const AAlbaranes: TAlbaranItemList);
var
  LForm: TfDecaEmisionAlbaranes;
begin
  if Length(AAlbaranes) = 0 then Exit;

  LForm := TfDecaEmisionAlbaranes.Create(AOwner);
  try
    LForm.FAlbaranes := AAlbaranes;
    LForm.CargarDatosEmpresaYAlbaranes;
    LForm.ShowModal;
  finally
    LForm.Free;
  end;
end;

procedure TfDecaEmisionAlbaranes.FormCreate(Sender: TObject);
begin
  FCodigoDeca := '';
  FUrlDescargaQr := '';
  CargarDecaConfig(FDecaConfig);
  pgcDatos.ActivePage := tsTransporte;
end;

procedure TfDecaEmisionAlbaranes.btnConfiguracionClick(Sender: TObject);
begin
  if TfDecaConfiguracion.EjecutarConfiguracion(Self) then
    CargarDecaConfig(FDecaConfig);
end;

function StrToFloatJson(const S: string; ADef: Double = 0): Double;
var
  LStr: string;
  LVal: Double;
begin
  LStr := Trim(S);
  if LStr = '' then Exit(ADef);
  LStr := StringReplace(LStr, ' ', '', [rfReplaceAll]);
  if FormatSettings.DecimalSeparator = ',' then
    LStr := StringReplace(LStr, '.', ',', [rfReplaceAll])
  else
    LStr := StringReplace(LStr, ',', '.', [rfReplaceAll]);
  if TryStrToFloat(LStr, LVal) then
    Result := LVal
  else
    Result := ADef;
end;

procedure ParsearCantidadBultos(const S: string; out APalets: Integer; out ABultos: Double);
var
  pPal, pBul, pSlash: Integer;
  sPal, sBul: string;
begin
  APalets := 0;
  ABultos := 0;
  pPal := Pos('Palet', S);
  pBul := Pos('Bulto', S);
  if pPal > 0 then
  begin
    sPal := Trim(Copy(S, 1, pPal - 1));
    APalets := StrToIntDef(sPal, 0);
    pSlash := Pos('/', S);
    if (pSlash > 0) and (pBul > pSlash) then
    begin
      sBul := Trim(Copy(S, pSlash + 1, pBul - pSlash - 1));
      ABultos := StrToFloatJson(sBul, 0);
    end;
  end
  else if pBul > 0 then
  begin
    sBul := Trim(Copy(S, 1, pBul - 1));
    ABultos := StrToFloatJson(sBul, 0);
  end;
end;

procedure TfDecaEmisionAlbaranes.CargarPresetsDesdeBD;
var
  LQ: TZQuery;
  LVeh: TDecaVehiculoPreset;
  LCon: TDecaContactoPreset;
  LItemText, LJsonVeh, LJsonCon: string;
  LVal, LArrVal: TJSONValue;
  LObj: TJSONObject;
  LArr: TJSONArray;
  i: Integer;

  function GetValStr(AObj: TJSONObject; const AKey: string): string;
  var
    P: TJSONPair;
  begin
    Result := '';
    if AObj = nil then Exit;
    P := AObj.Get(AKey);
    if (P <> nil) and (P.JsonValue <> nil) and not (P.JsonValue is TJSONNull) then
      Result := P.JsonValue.Value;
  end;

  function GetValInt(AObj: TJSONObject; const AKey: string): Integer;
  begin
    Result := StrToIntDef(GetValStr(AObj, AKey), 0);
  end;

begin
  SetLength(FVehiculosPresets, 0);
  SetLength(FContactosPresets, 0);

  cmbPresetCargador.Items.Clear;
  cmbPresetCargador.Items.Add('-- Seleccionar empresa cargadora guardada --');

  cmbPresetTransp.Items.Clear;
  cmbPresetTransp.Items.Add('-- Seleccionar transportista guardado --');

  cmbPresetVehiculo.Items.Clear;
  cmbPresetVehiculo.Items.Add('-- Seleccionar veh'#237'culo / conductor guardado --');

  cmbPresetOrigen.Items.Clear;
  cmbPresetOrigen.Items.Add('-- Seleccionar origen guardado --');

  cmbPresetDestino.Items.Clear;
  cmbPresetDestino.Items.Add('-- Seleccionar destino guardado --');

  // 1. Cargar via DeCA REST API en la nube (misdeca.es / controltime.es)
  if ObtenerPresetsVehiculos(FDecaConfig, LJsonVeh) then
  begin
    try
      LVal := TJSONObject.ParseJSONValue(LJsonVeh);
      if Assigned(LVal) and (LVal is TJSONObject) then
      try
        LArrVal := TJSONObject(LVal).Get('vehiculos').JsonValue;
        if Assigned(LArrVal) and (LArrVal is TJSONArray) then
        begin
          LArr := TJSONArray(LArrVal);
          for i := 0 to LArr.Size - 1 do
          begin
            if LArr.Get(i) is TJSONObject then
            begin
              LObj := TJSONObject(LArr.Get(i));
              SetLength(FVehiculosPresets, Length(FVehiculosPresets) + 1);
              LVeh.Id := GetValInt(LObj, 'id_vehiculo');
              LVeh.MatriculaTractora := GetValStr(LObj, 'matricula_tractora');
              LVeh.MatriculaRemolque := GetValStr(LObj, 'matricula_remolque');
              LVeh.TipoVehiculo := GetValStr(LObj, 'tipo_vehiculo');
              LVeh.ConductorNombre := GetValStr(LObj, 'conductor_nombre');
              LVeh.ConductorNif := GetValStr(LObj, 'conductor_nif');
              LVeh.ConductorTelefono := GetValStr(LObj, 'conductor_telefono');
              FVehiculosPresets[High(FVehiculosPresets)] := LVeh;

              LItemText := Trim(LVeh.MatriculaTractora);
              if Trim(LVeh.MatriculaRemolque) <> '' then
                LItemText := LItemText + ' / ' + Trim(LVeh.MatriculaRemolque);
              if Trim(LVeh.ConductorNombre) <> '' then
                LItemText := LItemText + ' - ' + Trim(LVeh.ConductorNombre);

              cmbPresetVehiculo.Items.AddObject(LItemText, TObject(IntPtr(High(FVehiculosPresets))));
            end;
          end;
        end;
      finally
        LVal.Free;
      end;
    except
    end;
  end;

  if ObtenerPresetsContactos(FDecaConfig, '', LJsonCon) then
  begin
    try
      LVal := TJSONObject.ParseJSONValue(LJsonCon);
      if Assigned(LVal) and (LVal is TJSONObject) then
      try
        LArrVal := TJSONObject(LVal).Get('contactos').JsonValue;
        if Assigned(LArrVal) and (LArrVal is TJSONArray) then
        begin
          LArr := TJSONArray(LArrVal);
          for i := 0 to LArr.Size - 1 do
          begin
            if LArr.Get(i) is TJSONObject then
            begin
              LObj := TJSONObject(LArr.Get(i));
              SetLength(FContactosPresets, Length(FContactosPresets) + 1);
              LCon.Id := GetValInt(LObj, 'id_contacto');
              LCon.Tipo := UpperCase(Trim(GetValStr(LObj, 'tipo')));
              LCon.Nombre := GetValStr(LObj, 'nombre');
              LCon.Nif := GetValStr(LObj, 'nif');
              LCon.Direccion := GetValStr(LObj, 'direccion');
              LCon.CP := GetValStr(LObj, 'cp');
              LCon.Localidad := GetValStr(LObj, 'localidad');
              LCon.Provincia := GetValStr(LObj, 'provincia');
              LCon.Telefono := GetValStr(LObj, 'telefono');
              FContactosPresets[High(FContactosPresets)] := LCon;

              LItemText := Trim(LCon.Nombre);
              if Trim(LCon.Nif) <> '' then
                LItemText := LItemText + ' (' + Trim(LCon.Nif) + ')';
              if Trim(LCon.Localidad) <> '' then
                LItemText := LItemText + ' - ' + Trim(LCon.Localidad);

              if (LCon.Tipo = 'CARGADOR') or (LCon.Tipo = 'AMBOS') then
                cmbPresetCargador.Items.AddObject(LItemText, TObject(IntPtr(High(FContactosPresets))));

              if (LCon.Tipo = 'TRANSPORTISTA') or (LCon.Tipo = 'AMBOS') then
                cmbPresetTransp.Items.AddObject(LItemText, TObject(IntPtr(High(FContactosPresets))));

              if (LCon.Tipo = 'ORIGEN') or (LCon.Tipo = 'CARGADOR') or (LCon.Tipo = 'AMBOS') then
                cmbPresetOrigen.Items.AddObject(LItemText, TObject(IntPtr(High(FContactosPresets))));

              if (LCon.Tipo = 'DESTINO') or (LCon.Tipo = 'AMBOS') then
                cmbPresetDestino.Items.AddObject(LItemText, TObject(IntPtr(High(FContactosPresets))));
            end;
          end;
        end;
      finally
        LVal.Free;
      end;
    except
    end;
  end;

  // Fallback: Si no se recibieron datos de la API (offline), consultar SQL local si existiera la base de datos deca
  if (Length(FVehiculosPresets) = 0) and (Length(FContactosPresets) = 0) and Assigned(DM) and Assigned(DM.DBagrigest) then
  begin
    LQ := TZQuery.Create(nil);
    try
      LQ.Connection := DM.DBagrigest;
      try
        LQ.Close;
        LQ.SQL.Text := 'SELECT id_vehiculo, matricula_tractora, matricula_remolque, tipo_vehiculo, ' +
                       '       conductor_nombre, conductor_nif, conductor_telefono ' +
                       'FROM deca.deca_vehiculos WHERE activo = 1 ORDER BY matricula_tractora';
        LQ.Open;
        while not LQ.Eof do
        begin
          SetLength(FVehiculosPresets, Length(FVehiculosPresets) + 1);
          LVeh.Id := LQ.FieldByName('id_vehiculo').AsInteger;
          LVeh.MatriculaTractora := LQ.FieldByName('matricula_tractora').AsString;
          LVeh.MatriculaRemolque := LQ.FieldByName('matricula_remolque').AsString;
          LVeh.TipoVehiculo := LQ.FieldByName('tipo_vehiculo').AsString;
          LVeh.ConductorNombre := LQ.FieldByName('conductor_nombre').AsString;
          LVeh.ConductorNif := LQ.FieldByName('conductor_nif').AsString;
          LVeh.ConductorTelefono := LQ.FieldByName('conductor_telefono').AsString;
          FVehiculosPresets[High(FVehiculosPresets)] := LVeh;

          LItemText := Trim(LVeh.MatriculaTractora);
          if Trim(LVeh.MatriculaRemolque) <> '' then
            LItemText := LItemText + ' / ' + Trim(LVeh.MatriculaRemolque);
          if Trim(LVeh.ConductorNombre) <> '' then
            LItemText := LItemText + ' - ' + Trim(LVeh.ConductorNombre);

          cmbPresetVehiculo.Items.AddObject(LItemText, TObject(IntPtr(High(FVehiculosPresets))));
          LQ.Next;
        end;
      except
      end;

      try
        LQ.Close;
        LQ.SQL.Text := 'SELECT id_contacto, tipo, nombre, nif, direccion, cp, localidad, provincia, telefono ' +
                       'FROM deca.deca_contactos WHERE activo = 1 ORDER BY nombre';
        LQ.Open;
        while not LQ.Eof do
        begin
          SetLength(FContactosPresets, Length(FContactosPresets) + 1);
          LCon.Id := LQ.FieldByName('id_contacto').AsInteger;
          LCon.Tipo := UpperCase(Trim(LQ.FieldByName('tipo').AsString));
          LCon.Nombre := LQ.FieldByName('nombre').AsString;
          LCon.Nif := LQ.FieldByName('nif').AsString;
          LCon.Direccion := LQ.FieldByName('direccion').AsString;
          LCon.CP := LQ.FieldByName('cp').AsString;
          LCon.Localidad := LQ.FieldByName('localidad').AsString;
          LCon.Provincia := LQ.FieldByName('provincia').AsString;
          LCon.Telefono := LQ.FieldByName('telefono').AsString;
          FContactosPresets[High(FContactosPresets)] := LCon;

          LItemText := Trim(LCon.Nombre);
          if Trim(LCon.Nif) <> '' then
            LItemText := LItemText + ' (' + Trim(LCon.Nif) + ')';
          if Trim(LCon.Localidad) <> '' then
            LItemText := LItemText + ' - ' + Trim(LCon.Localidad);

          if (LCon.Tipo = 'CARGADOR') or (LCon.Tipo = 'AMBOS') then
            cmbPresetCargador.Items.AddObject(LItemText, TObject(IntPtr(High(FContactosPresets))));

          if (LCon.Tipo = 'TRANSPORTISTA') or (LCon.Tipo = 'AMBOS') then
            cmbPresetTransp.Items.AddObject(LItemText, TObject(IntPtr(High(FContactosPresets))));

          if (LCon.Tipo = 'ORIGEN') or (LCon.Tipo = 'CARGADOR') or (LCon.Tipo = 'AMBOS') then
            cmbPresetOrigen.Items.AddObject(LItemText, TObject(IntPtr(High(FContactosPresets))));

          if (LCon.Tipo = 'DESTINO') or (LCon.Tipo = 'AMBOS') then
            cmbPresetDestino.Items.AddObject(LItemText, TObject(IntPtr(High(FContactosPresets))));

          LQ.Next;
        end;
      except
      end;

    finally
      LQ.Free;
    end;
  end;

  if cmbPresetCargador.Items.Count > 0 then cmbPresetCargador.ItemIndex := 0;
  if cmbPresetTransp.Items.Count > 0 then cmbPresetTransp.ItemIndex := 0;
  if cmbPresetVehiculo.Items.Count > 0 then cmbPresetVehiculo.ItemIndex := 0;
  if cmbPresetOrigen.Items.Count > 0 then cmbPresetOrigen.ItemIndex := 0;
  if cmbPresetDestino.Items.Count > 0 then cmbPresetDestino.ItemIndex := 0;
end;

procedure TfDecaEmisionAlbaranes.cmbPresetTranspChange(Sender: TObject);
var
  Idx: Integer;
begin
  if (cmbPresetTransp.ItemIndex > 0) and (cmbPresetTransp.Items.Objects[cmbPresetTransp.ItemIndex] <> nil) then
  begin
    Idx := Integer(IntPtr(cmbPresetTransp.Items.Objects[cmbPresetTransp.ItemIndex]));
    if (Idx >= 0) and (Idx <= High(FContactosPresets)) then
    begin
      edtTranspNombre.Text := FContactosPresets[Idx].Nombre;
      edtTranspNif.Text := FContactosPresets[Idx].Nif;
    end;
  end;
end;

procedure TfDecaEmisionAlbaranes.cmbPresetVehiculoChange(Sender: TObject);
var
  Idx: Integer;
begin
  if (cmbPresetVehiculo.ItemIndex > 0) and (cmbPresetVehiculo.Items.Objects[cmbPresetVehiculo.ItemIndex] <> nil) then
  begin
    Idx := Integer(IntPtr(cmbPresetVehiculo.Items.Objects[cmbPresetVehiculo.ItemIndex]));
    if (Idx >= 0) and (Idx <= High(FVehiculosPresets)) then
    begin
      edtMatriculaTractora.Text := FVehiculosPresets[Idx].MatriculaTractora;
      edtMatriculaRemolque.Text := FVehiculosPresets[Idx].MatriculaRemolque;
      edtConductorNombre.Text := FVehiculosPresets[Idx].ConductorNombre;
      edtConductorNif.Text := FVehiculosPresets[Idx].ConductorNif;
    end;
  end;
end;

procedure TfDecaEmisionAlbaranes.cmbPresetOrigenChange(Sender: TObject);
var
  Idx: Integer;
begin
  if (cmbPresetOrigen.ItemIndex > 0) and (cmbPresetOrigen.Items.Objects[cmbPresetOrigen.ItemIndex] <> nil) then
  begin
    Idx := Integer(IntPtr(cmbPresetOrigen.Items.Objects[cmbPresetOrigen.ItemIndex]));
    if (Idx >= 0) and (Idx <= High(FContactosPresets)) then
    begin
      edtOrigenNombre.Text := FContactosPresets[Idx].Nombre;
      edtOrigenDir.Text := FContactosPresets[Idx].Direccion;
      edtOrigenPob.Text := Trim(FContactosPresets[Idx].CP + ' ' + FContactosPresets[Idx].Localidad +
        ' (' + FContactosPresets[Idx].Provincia + ')');
    end;
  end;
end;

procedure TfDecaEmisionAlbaranes.cmbPresetDestinoChange(Sender: TObject);
var
  Idx: Integer;
begin
  if (cmbPresetDestino.ItemIndex > 0) and (cmbPresetDestino.Items.Objects[cmbPresetDestino.ItemIndex] <> nil) then
  begin
    Idx := Integer(IntPtr(cmbPresetDestino.Items.Objects[cmbPresetDestino.ItemIndex]));
    if (Idx >= 0) and (Idx <= High(FContactosPresets)) then
    begin
      edtDestinoNombre.Text := FContactosPresets[Idx].Nombre;
      edtDestinoDir.Text := FContactosPresets[Idx].Direccion;
      edtDestinoPob.Text := Trim(FContactosPresets[Idx].CP + ' ' + FContactosPresets[Idx].Localidad +
        ' (' + FContactosPresets[Idx].Provincia + ')');
    end;
  end;
end;

procedure TfDecaEmisionAlbaranes.cmbPresetCargadorChange(Sender: TObject);
var
  Idx: Integer;
begin
  if (cmbPresetCargador.ItemIndex > 0) and (cmbPresetCargador.Items.Objects[cmbPresetCargador.ItemIndex] <> nil) then
  begin
    Idx := Integer(IntPtr(cmbPresetCargador.Items.Objects[cmbPresetCargador.ItemIndex]));
    if (Idx >= 0) and (Idx <= High(FContactosPresets)) then
    begin
      edtCargadorNombre.Text := FContactosPresets[Idx].Nombre;
      edtCargadorNif.Text := FContactosPresets[Idx].Nif;
      edtCargadorDir.Text := FContactosPresets[Idx].Direccion;
      edtCargadorCP.Text := FContactosPresets[Idx].CP;
      edtCargadorPob.Text := FContactosPresets[Idx].Localidad;
      edtCargadorProv.Text := FContactosPresets[Idx].Provincia;
      edtCargadorTel.Text := FContactosPresets[Idx].Telefono;
    end;
  end;
end;

procedure TfDecaEmisionAlbaranes.btnUsarMiEmpresaCargadorClick(Sender: TObject);
var
  LQ: TZQuery;
begin
  edtCargadorNombre.Text := 'S.A.T. N'#186'2 CV FRUTAS ENRIQUE PRIETO E HIJOS';
  edtCargadorNif.Text := 'V53022166';
  edtCargadorDir.Text := 'C/ SAGUNTO, 11 ENTLO. APTDO CORREOS 195';
  edtCargadorCP.Text := '03680';
  edtCargadorPob.Text := 'ASPE';
  edtCargadorProv.Text := 'ALICANTE';
  edtCargadorTel.Text := '';

  if Assigned(DM) and Assigned(DM.DBagrigest) and Assigned(DM.tempresa) and DM.tempresa.Active then
  begin
    if Trim(DM.tempresanombre.AsString) <> '' then
      edtCargadorNombre.Text := Trim(DM.tempresanombre.AsString);
    if Trim(DM.tempresacif.AsString) <> '' then
      edtCargadorNif.Text := Trim(DM.tempresacif.AsString);
    if Trim(DM.tempresadireccion.AsString) <> '' then
      edtCargadorDir.Text := Trim(DM.tempresadireccion.AsString);
    if Trim(DM.tempresapoblacion.AsString) <> '' then
      edtCargadorPob.Text := Trim(DM.tempresapoblacion.AsString);

    LQ := TZQuery.Create(nil);
    try
      LQ.Connection := DM.DBagrigest;
      try
        LQ.SQL.Text := 'SELECT codpost, poblacion, provincia, telefono FROM empresa WHERE id = :id LIMIT 1';
        LQ.ParamByName('id').AsString := DM.tempresaid.AsString;
        LQ.Open;
        if not LQ.IsEmpty then
        begin
          if Trim(LQ.FieldByName('codpost').AsString) <> '' then
            edtCargadorCP.Text := Trim(LQ.FieldByName('codpost').AsString);
          if Trim(LQ.FieldByName('poblacion').AsString) <> '' then
            edtCargadorPob.Text := Trim(LQ.FieldByName('poblacion').AsString);
          if (LQ.FindField('provincia') <> nil) and (Trim(LQ.FieldByName('provincia').AsString) <> '') then
            edtCargadorProv.Text := Trim(LQ.FieldByName('provincia').AsString);
          if (LQ.FindField('telefono') <> nil) and (Trim(LQ.FieldByName('telefono').AsString) <> '') then
            edtCargadorTel.Text := Trim(LQ.FieldByName('telefono').AsString);
        end;
      except
      end;
    finally
      LQ.Free;
    end;
  end;

  if cmbPresetCargador.Items.Count > 0 then
    cmbPresetCargador.ItemIndex := 0;
end;

procedure TfDecaEmisionAlbaranes.btnLimpiarCargadorClick(Sender: TObject);
begin
  edtCargadorNombre.Text := '';
  edtCargadorNif.Text := '';
  edtCargadorDir.Text := '';
  edtCargadorCP.Text := '';
  edtCargadorPob.Text := '';
  edtCargadorProv.Text := '';
  edtCargadorTel.Text := '';
  cmbPresetCargador.ItemIndex := 0;
end;

procedure TfDecaEmisionAlbaranes.CargarDatosEmpresaYAlbaranes;
var
  i, j, LTotalPalets, LPesoBrutoInt, LPaletsAlb, LPaletsParsed: Integer;
  LTotalBultos, LTotalKilos, LBultosAlb, LKilosAlb, LBultosParsed, LKilosVal: Double;
  LAlbNombres, LDecaCodigo, LBultosStr, LRefAlb, LRespJson: string;
  LQuery: TZQuery;
  LFirst: TAlbaranItem;
  LDeCAEncontrado: Boolean;
begin
  CargarPresetsDesdeBD;

  LTotalPalets := 0;
  LTotalBultos := 0;
  LTotalKilos := 0;
  LAlbNombres := '';

  LQuery := TZQuery.Create(nil);
  try
    LQuery.Connection := DM.DBagrigest;

    // 1. Obtener totales reales de albaranes_detalles para cada albarán (priorizando PESO BRUTO)
    for i := 0 to High(FAlbaranes) do
    begin
      LPaletsAlb := 0;
      LBultosAlb := 0;
      LKilosAlb := 0;

      try
        LQuery.Close;
        LQuery.SQL.Text :=
          'SELECT COALESCE(MAX(palets), 0) AS max_palets, ' +
          '       COALESCE(SUM(bultos), 0) AS sum_bultos, ' +
          '       COALESCE(SUM(pesobruto), 0) AS sum_pesobruto, ' +
          '       COALESCE(SUM(cantidad), 0) AS sum_kilos ' +
          'FROM albaranes_detalles ' +
          'WHERE idempresa = :emp AND serie = :ser AND idalbaran = :id';
        LQuery.ParamByName('emp').AsInteger := FAlbaranes[i].IdEmpresa;
        LQuery.ParamByName('ser').AsString := FAlbaranes[i].Serie;
        LQuery.ParamByName('id').AsInteger := FAlbaranes[i].Id;
        LQuery.Open;

        if not LQuery.IsEmpty then
        begin
          LPaletsAlb := Round(LQuery.FieldByName('max_palets').AsFloat);
          LBultosAlb := LQuery.FieldByName('sum_bultos').AsFloat;
          if LQuery.FieldByName('sum_pesobruto').AsFloat > 0 then
            LKilosAlb := LQuery.FieldByName('sum_pesobruto').AsFloat
          else if FAlbaranes[i].Kilos > 0 then
            LKilosAlb := FAlbaranes[i].Kilos
          else
            LKilosAlb := LQuery.FieldByName('sum_kilos').AsFloat;
        end;
      except
        LPaletsAlb := 0;
        LBultosAlb := FAlbaranes[i].Bultos;
        LKilosAlb := FAlbaranes[i].Kilos;
      end;

      if (LBultosAlb <= 0) and (FAlbaranes[i].Bultos > 0) then
        LBultosAlb := FAlbaranes[i].Bultos;
      if (LKilosAlb <= 0) and (FAlbaranes[i].Kilos > 0) then
        LKilosAlb := FAlbaranes[i].Kilos;

      FAlbaranes[i].Palets := LPaletsAlb;
      FAlbaranes[i].Bultos := LBultosAlb;
      FAlbaranes[i].Kilos := LKilosAlb;

      LTotalPalets := LTotalPalets + LPaletsAlb;
      LTotalBultos := LTotalBultos + LBultosAlb;
      LTotalKilos := LTotalKilos + LKilosAlb;

      if LAlbNombres <> '' then
        LAlbNombres := LAlbNombres + ', ';
      LAlbNombres := LAlbNombres + Trim(FAlbaranes[i].Serie + '/' + FAlbaranes[i].Documento);
    end;

    // Redondeo al alza obligatorio para peso bruto en Kg enteros
    LPesoBrutoInt := Ceil(LTotalKilos);
    if LPesoBrutoInt <= 0 then LPesoBrutoInt := 1;
    if LTotalBultos <= 0 then LTotalBultos := 1;

    lblInfoAlbaranes.Caption := 'Albaranes incluidos (' + IntToStr(Length(FAlbaranes)) + '): ' + LAlbNombres;
    lblResumenTotales.Caption := Format('Total Palets: %d  |  Total Bultos: %.0f  |  Peso Bruto: %d Kg',
      [LTotalPalets, LTotalBultos, LPesoBrutoInt]);

    edtPalets.Text := IntToStr(LTotalPalets);
    edtBultos.Text := FormatFloat('0', LTotalBultos);
    edtKilos.Text := IntToStr(LPesoBrutoInt);

    // 2. Comprobar si el albarán ya tiene un documento DeCA emitido
    LDecaCodigo := '';
    for i := 0 to High(FAlbaranes) do
    begin
      if Trim(FAlbaranes[i].DecaCodigo) <> '' then
      begin
        LDecaCodigo := Trim(FAlbaranes[i].DecaCodigo);
        Break;
      end;
    end;

    if (LDecaCodigo = '') and (Length(FAlbaranes) > 0) then
    begin
      try
        LQuery.Close;
        LQuery.SQL.Text := 'SELECT deca_codigo FROM albaranes WHERE idempresa = :emp AND serie = :ser AND id = :id LIMIT 1';
        LQuery.ParamByName('emp').AsInteger := FAlbaranes[0].IdEmpresa;
        LQuery.ParamByName('ser').AsString := FAlbaranes[0].Serie;
        LQuery.ParamByName('id').AsInteger := FAlbaranes[0].Id;
        LQuery.Open;
        if not LQuery.IsEmpty then
          LDecaCodigo := Trim(LQuery.FieldByName('deca_codigo').AsString);
      except
      end;
    end;

    if (LDecaCodigo = '') and (Length(FAlbaranes) > 0) then
    begin
      try
        LQuery.Close;
        LQuery.SQL.Text := 'SELECT codigo_deca FROM deca.deca_cabecera WHERE referencia_albaran = :ref OR referencia_albaran LIKE :reflike ORDER BY id_deca DESC LIMIT 1';
        LQuery.ParamByName('ref').AsString := Trim(FAlbaranes[0].Serie + '/' + IntToStr(FAlbaranes[0].Id));
        LQuery.ParamByName('reflike').AsString := '%' + Trim(FAlbaranes[0].Serie + '/' + IntToStr(FAlbaranes[0].Id)) + '%';
        LQuery.Open;
        if not LQuery.IsEmpty then
          LDecaCodigo := Trim(LQuery.FieldByName('codigo_deca').AsString);
      except
      end;
    end;

    LDeCAEncontrado := False;
    if LDecaCodigo <> '' then
    begin
      try
        LQuery.Close;
        LQuery.SQL.Text := 'SELECT * FROM deca.deca_cabecera WHERE codigo_deca = :cod LIMIT 1';
        LQuery.ParamByName('cod').AsString := LDecaCodigo;
        LQuery.Open;
        if not LQuery.IsEmpty then
        begin
          LDeCAEncontrado := True;

          edtCargadorNombre.Text := LQuery.FieldByName('cargador_nombre').AsString;
          edtCargadorNif.Text := LQuery.FieldByName('cargador_nif').AsString;
          edtCargadorDir.Text := LQuery.FieldByName('cargador_direccion').AsString;
          edtCargadorCP.Text := LQuery.FieldByName('cargador_cp').AsString;
          edtCargadorPob.Text := LQuery.FieldByName('cargador_localidad').AsString;
          edtCargadorProv.Text := LQuery.FieldByName('cargador_provincia').AsString;

          edtTranspNombre.Text := LQuery.FieldByName('transportista_nombre').AsString;
          edtTranspNif.Text := LQuery.FieldByName('transportista_nif').AsString;
          edtMatriculaTractora.Text := LQuery.FieldByName('matricula_tractora').AsString;
          edtMatriculaRemolque.Text := LQuery.FieldByName('matricula_remolque').AsString;
          edtConductorNombre.Text := LQuery.FieldByName('conductor_nombre').AsString;
          edtConductorNif.Text := LQuery.FieldByName('conductor_nif').AsString;
          edtMercanciaNaturaleza.Text := LQuery.FieldByName('mercancia_naturaleza').AsString;

          if (LQuery.FindField('peso_bruto_kg') <> nil) and (LQuery.FieldByName('peso_bruto_kg').AsFloat > 0) then
            edtKilos.Text := IntToStr(Ceil(LQuery.FieldByName('peso_bruto_kg').AsFloat))
          else if (LQuery.FindField('peso_neto_kg') <> nil) and (LQuery.FieldByName('peso_neto_kg').AsFloat > 0) then
            edtKilos.Text := IntToStr(Ceil(LQuery.FieldByName('peso_neto_kg').AsFloat))
          else
            edtKilos.Text := IntToStr(LPesoBrutoInt);

          LBultosStr := LQuery.FieldByName('cantidad_bultos').AsString;
          if LBultosStr <> '' then
          begin
            ParsearCantidadBultos(LBultosStr, LPaletsParsed, LBultosParsed);
            if LPaletsParsed > 0 then
              edtPalets.Text := IntToStr(LPaletsParsed)
            else
              edtPalets.Text := IntToStr(LTotalPalets);

            if LBultosParsed > 0 then
              edtBultos.Text := FormatFloat('0', LBultosParsed)
            else
              edtBultos.Text := FormatFloat('0', LTotalBultos);
          end
          else
          begin
            edtPalets.Text := IntToStr(LTotalPalets);
            edtBultos.Text := FormatFloat('0', LTotalBultos);
          end;

          edtOrigenNombre.Text := LQuery.FieldByName('origen_nombre').AsString;
          edtOrigenDir.Text := LQuery.FieldByName('origen_direccion').AsString;
          edtOrigenPob.Text := Trim(LQuery.FieldByName('origen_cp').AsString + ' ' +
                                    LQuery.FieldByName('origen_localidad').AsString + ' (' +
                                    LQuery.FieldByName('origen_provincia').AsString + ')');

          edtDestinoNombre.Text := LQuery.FieldByName('destino_nombre').AsString;
          edtDestinoDir.Text := LQuery.FieldByName('destino_direccion').AsString;
          edtDestinoPob.Text := Trim(LQuery.FieldByName('destino_cp').AsString + ' ' +
                                     LQuery.FieldByName('destino_localidad').AsString + ' (' +
                                     LQuery.FieldByName('destino_provincia').AsString + ')');

          mmoParadas.Text := LQuery.FieldByName('observaciones').AsString;

          FCodigoDeca := LDecaCodigo;
          FUrlDescargaQr := NormalizarUrlPdf(LQuery.FieldByName('url_descarga_qr').AsString);

          lblCodigoDeca.Caption := FCodigoDeca;
          edtUrlQr.Text := FUrlDescargaQr;
          lblEstadoEmision.Caption := '[EMITIDO] Documento registrado oficialmente. Inspecci'#243'n QR activa y v'#225'lida.';
          lblEstadoEmision.Font.Color := clGreen;

          btnVerPDF.Enabled := True;
          btnWhatsApp.Enabled := True;
          btnEmitirDeCA.Caption := 'Reemitir / Actualizar DeCA';
          btnEmitirDeCA.Enabled := True;

          lblResumenTotales.Caption := Format('Total Palets: %s  |  Total Bultos: %s  |  Peso Bruto: %s Kg',
            [edtPalets.Text, edtBultos.Text, edtKilos.Text]);
        end;
      except
        LDeCAEncontrado := False;
      end;
    end;

    // Fallback: Si no se pudo consultar por SQL directo (o no se encontró), consultar vía servicio API DeCA
    if not LDeCAEncontrado then
    begin
      LRefAlb := '';
      if Length(FAlbaranes) > 0 then
        LRefAlb := Trim(FAlbaranes[0].Serie + '/' + IntToStr(FAlbaranes[0].Id));

      if (LDecaCodigo <> '') or (LRefAlb <> '') then
      begin
        if ConsultarDocumentoDeca(FDecaConfig, LDecaCodigo, LRefAlb, LRespJson) then
        begin
          LDeCAEncontrado := True;

          FCodigoDeca := ExtraerValorJson(LRespJson, 'codigo_deca');
          if FCodigoDeca = '' then FCodigoDeca := LDecaCodigo;
          FUrlDescargaQr := NormalizarUrlPdf(ExtraerValorJson(LRespJson, 'url_descarga_qr'));

          edtCargadorNombre.Text := ExtraerValorJson(LRespJson, 'cargador_nombre');
          edtCargadorNif.Text := ExtraerValorJson(LRespJson, 'cargador_nif');
          edtCargadorDir.Text := ExtraerValorJson(LRespJson, 'cargador_direccion');
          edtCargadorCP.Text := ExtraerValorJson(LRespJson, 'cargador_cp');
          edtCargadorPob.Text := ExtraerValorJson(LRespJson, 'cargador_localidad');
          edtCargadorProv.Text := ExtraerValorJson(LRespJson, 'cargador_provincia');

          edtTranspNombre.Text := ExtraerValorJson(LRespJson, 'transportista_nombre');
          edtTranspNif.Text := ExtraerValorJson(LRespJson, 'transportista_nif');
          edtMatriculaTractora.Text := ExtraerValorJson(LRespJson, 'matricula_tractora');
          edtMatriculaRemolque.Text := ExtraerValorJson(LRespJson, 'matricula_remolque');
          edtConductorNombre.Text := ExtraerValorJson(LRespJson, 'conductor_nombre');
          edtConductorNif.Text := ExtraerValorJson(LRespJson, 'conductor_nif');
          edtMercanciaNaturaleza.Text := ExtraerValorJson(LRespJson, 'mercancia_naturaleza');

          LBultosStr := ExtraerValorJson(LRespJson, 'cantidad_bultos');
          if LBultosStr <> '' then
          begin
            ParsearCantidadBultos(LBultosStr, LPaletsParsed, LBultosParsed);
            if LPaletsParsed > 0 then
              edtPalets.Text := IntToStr(LPaletsParsed)
            else
              edtPalets.Text := IntToStr(LTotalPalets);

            if LBultosParsed > 0 then
              edtBultos.Text := FormatFloat('0', LBultosParsed)
            else
              edtBultos.Text := FormatFloat('0', LTotalBultos);
          end
          else
          begin
            edtPalets.Text := IntToStr(LTotalPalets);
            edtBultos.Text := FormatFloat('0', LTotalBultos);
          end;

          LKilosVal := StrToFloatJson(ExtraerValorJson(LRespJson, 'peso_bruto_kg'), 0);
          if LKilosVal <= 0 then
            LKilosVal := StrToFloatJson(ExtraerValorJson(LRespJson, 'peso_neto_kg'), 0);
          if LKilosVal > 0 then
            edtKilos.Text := IntToStr(Ceil(LKilosVal))
          else
            edtKilos.Text := IntToStr(LPesoBrutoInt);

          edtOrigenNombre.Text := ExtraerValorJson(LRespJson, 'origen_nombre');
          edtOrigenDir.Text := ExtraerValorJson(LRespJson, 'origen_direccion');
          edtOrigenPob.Text := Trim(ExtraerValorJson(LRespJson, 'origen_cp') + ' ' +
                                    ExtraerValorJson(LRespJson, 'origen_localidad') + ' (' +
                                    ExtraerValorJson(LRespJson, 'origen_provincia') + ')');

          edtDestinoNombre.Text := ExtraerValorJson(LRespJson, 'destino_nombre');
          edtDestinoDir.Text := ExtraerValorJson(LRespJson, 'destino_direccion');
          edtDestinoPob.Text := Trim(ExtraerValorJson(LRespJson, 'destino_cp') + ' ' +
                                     ExtraerValorJson(LRespJson, 'destino_localidad') + ' (' +
                                     ExtraerValorJson(LRespJson, 'destino_provincia') + ')');

          mmoParadas.Text := ExtraerValorJson(LRespJson, 'observaciones');

          lblCodigoDeca.Caption := FCodigoDeca;
          edtUrlQr.Text := FUrlDescargaQr;
          lblEstadoEmision.Caption := '[EMITIDO] Documento registrado oficialmente. Inspecci'#243'n QR activa y v'#225'lida.';
          lblEstadoEmision.Font.Color := clGreen;

          btnVerPDF.Enabled := True;
          btnWhatsApp.Enabled := True;
          btnEmitirDeCA.Caption := 'Reemitir / Actualizar DeCA';
          btnEmitirDeCA.Enabled := True;

          lblResumenTotales.Caption := Format('Total Palets: %s  |  Total Bultos: %s  |  Peso Bruto: %s Kg',
            [edtPalets.Text, edtBultos.Text, edtKilos.Text]);
        end;
      end;
    end;

    // Si se cargó el documento DeCA y no estaba vinculado en albaranes.deca_codigo, enlazarlo de forma permanente
    if LDeCAEncontrado and (FCodigoDeca <> '') then
    begin
      for i := 0 to High(FAlbaranes) do
      begin
        FAlbaranes[i].DecaCodigo := FCodigoDeca;
        try
          DM.DBagrigest.ExecuteDirect(Format(
            'UPDATE albaranes SET deca_codigo = ''%s'' WHERE idempresa = %d AND serie = ''%s'' AND id = %d AND (deca_codigo IS NULL OR deca_codigo = '''')',
            [FCodigoDeca, FAlbaranes[i].IdEmpresa, FAlbaranes[i].Serie, FAlbaranes[i].Id]
          ));
        except
        end;
      end;
      pgcDatos.ActivePage := tsTransporte;
    end;

    // 3. Si no existe emisión previa, cargar valores por defecto de la empresa y del albarán
    if not LDeCAEncontrado then
    begin
      if Assigned(DM) and Assigned(DM.tempresa) and DM.tempresa.Active then
      begin
        edtOrigenNombre.Text := DM.tempresanombre.AsString;
        edtOrigenDir.Text := DM.tempresadireccion.AsString;
        edtOrigenPob.Text := Trim(DM.tempresacodpost.AsString + ' ' + DM.tempresapoblacion.AsString);
      end
      else
      begin
        edtOrigenNombre.Text := 'S.A.T. Nº2 CV FRUTAS ENRIQUE PRIETO E HIJOS';
        edtOrigenDir.Text := 'C/ SAGUNTO, 11 ENTLO.';
        edtOrigenPob.Text := '03680 ASPE (ALICANTE)';
      end;
      if Trim(edtOrigenNombre.Text) = '' then
        edtOrigenNombre.Text := 'S.A.T. Nº2 CV FRUTAS ENRIQUE PRIETO E HIJOS';
      if Trim(edtOrigenDir.Text) = '' then
        edtOrigenDir.Text := 'C/ SAGUNTO, 11 ENTLO.';

      LFirst := FAlbaranes[0];
      edtDestinoNombre.Text := Trim(LFirst.Destino);
      if edtDestinoNombre.Text = '' then
        edtDestinoNombre.Text := Trim(LFirst.Cliente);
      if edtDestinoNombre.Text = '' then
        edtDestinoNombre.Text := 'Cliente Destinatario';

      edtDestinoDir.Text := Trim(LFirst.Direccion);
      if edtDestinoDir.Text = '' then
        edtDestinoDir.Text := 'Mercado Central / Destino Habitual';

      edtDestinoPob.Text := Trim(LFirst.CP + ' ' + LFirst.Poblacion + ' (' + LFirst.Provincia + ')');
      if Trim(edtDestinoPob.Text) = '()' then
        edtDestinoPob.Text := 'Destino';

      mmoParadas.Lines.Clear;
      if Length(FAlbaranes) > 1 then
      begin
        mmoParadas.Lines.Add('ITINERARIO CON ' + IntToStr(Length(FAlbaranes)) + ' PARADAS DE DESCARGA:');
        for i := 0 to High(FAlbaranes) do
        begin
          mmoParadas.Lines.Add(Format('  * [Parada %d] %s - %s - %d Palets / %.0f Bultos / %d Kg bruto (Alb: %s)', [
            i + 1,
            FAlbaranes[i].Destino,
            FAlbaranes[i].Poblacion,
            FAlbaranes[i].Palets,
            FAlbaranes[i].Bultos,
            Ceil(FAlbaranes[i].Kilos),
            FAlbaranes[i].Documento
          ]));
        end;
      end;

      edtTranspNombre.Text := '';
      edtTranspNif.Text := '';
      edtConductorNombre.Text := '';
      edtConductorNif.Text := '';
      edtMatriculaRemolque.Text := '';

      LFirst := FAlbaranes[0];
      if Trim(LFirst.Matricula) <> '' then
        edtMatriculaTractora.Text := Trim(LFirst.Matricula)
      else
        edtMatriculaTractora.Text := '';

      // Si la matrícula coincide con algún vehículo preset guardado, autocompletar remolque y conductor
      if Trim(edtMatriculaTractora.Text) <> '' then
      begin
        for i := 0 to High(FVehiculosPresets) do
        begin
          if SameText(Trim(FVehiculosPresets[i].MatriculaTractora), Trim(edtMatriculaTractora.Text)) or
             (Pos(UpperCase(Trim(FVehiculosPresets[i].MatriculaTractora)), UpperCase(Trim(edtMatriculaTractora.Text))) > 0) or
             (Pos(UpperCase(Trim(edtMatriculaTractora.Text)), UpperCase(Trim(FVehiculosPresets[i].MatriculaTractora))) > 0) then
          begin
            edtMatriculaTractora.Text := FVehiculosPresets[i].MatriculaTractora;
            edtMatriculaRemolque.Text := FVehiculosPresets[i].MatriculaRemolque;
            edtConductorNombre.Text := FVehiculosPresets[i].ConductorNombre;
            edtConductorNif.Text := FVehiculosPresets[i].ConductorNif;
            for j := 1 to cmbPresetVehiculo.Items.Count - 1 do
            begin
              if Integer(IntPtr(cmbPresetVehiculo.Items.Objects[j])) = i then
              begin
                cmbPresetVehiculo.ItemIndex := j;
                Break;
              end;
            end;
            Break;
          end;
        end;
      end;

      // Comprobar si la matrícula o texto del albarán coincide con algún transportista preset
      for i := 0 to High(FContactosPresets) do
      begin
        if (FContactosPresets[i].Tipo = 'TRANSPORTISTA') or (FContactosPresets[i].Tipo = 'AMBOS') then
        begin
          if (Trim(FContactosPresets[i].Nombre) <> '') and
             (SameText(Trim(FContactosPresets[i].Nombre), Trim(LFirst.Matricula)) or
              (Pos(UpperCase(Trim(FContactosPresets[i].Nombre)), UpperCase(Trim(LFirst.Matricula))) > 0)) then
          begin
            edtTranspNombre.Text := FContactosPresets[i].Nombre;
            edtTranspNif.Text := FContactosPresets[i].Nif;
            for j := 1 to cmbPresetTransp.Items.Count - 1 do
            begin
              if Integer(IntPtr(cmbPresetTransp.Items.Objects[j])) = i then
              begin
                cmbPresetTransp.ItemIndex := j;
                Break;
              end;
            end;
            Break;
          end;
        end;
      end;

      // Comprobar si el destino coincide con algún contacto preset guardado
      for i := 0 to High(FContactosPresets) do
      begin
        if (FContactosPresets[i].Tipo = 'DESTINO') or (FContactosPresets[i].Tipo = 'AMBOS') then
        begin
          if (Trim(FContactosPresets[i].Nombre) <> '') and
             (SameText(Trim(FContactosPresets[i].Nombre), Trim(edtDestinoNombre.Text)) or
              (Pos(UpperCase(Trim(FContactosPresets[i].Nombre)), UpperCase(Trim(edtDestinoNombre.Text))) > 0) or
              (Pos(UpperCase(Trim(edtDestinoNombre.Text)), UpperCase(Trim(FContactosPresets[i].Nombre))) > 0)) then
          begin
            edtDestinoNombre.Text := FContactosPresets[i].Nombre;
            if Trim(FContactosPresets[i].Direccion) <> '' then
              edtDestinoDir.Text := FContactosPresets[i].Direccion;
            if Trim(FContactosPresets[i].Localidad) <> '' then
              edtDestinoPob.Text := Trim(FContactosPresets[i].CP + ' ' + FContactosPresets[i].Localidad +
                ' (' + FContactosPresets[i].Provincia + ')');
            for j := 1 to cmbPresetDestino.Items.Count - 1 do
            begin
              if Integer(IntPtr(cmbPresetDestino.Items.Objects[j])) = i then
              begin
                cmbPresetDestino.ItemIndex := j;
                Break;
              end;
            end;
            Break;
          end;
        end;
      end;

      edtCargadorNombre.Text := '';
      edtCargadorNif.Text := '';
      edtCargadorDir.Text := '';
      edtCargadorCP.Text := '';
      edtCargadorPob.Text := '';
      edtCargadorProv.Text := '';
      edtCargadorTel.Text := '';
      if cmbPresetCargador.Items.Count > 0 then
        cmbPresetCargador.ItemIndex := 0;

      FCodigoDeca := '';
      FUrlDescargaQr := '';
      lblCodigoDeca.Caption := 'Pendiente de emisi'#243'n';
      edtUrlQr.Text := '';
      lblEstadoEmision.Caption := 'Documento no emitido a'#250'n.';
      lblEstadoEmision.Font.Color := clGray;

      btnEmitirDeCA.Caption := 'Emitir Documento Oficial DeCA';
      btnEmitirDeCA.Enabled := True;
      btnVerPDF.Enabled := False;
      btnWhatsApp.Enabled := False;
      pgcDatos.ActivePage := tsCargador;
    end;

    // Sincronizar combos con valores cargados (si coinciden con algún preset)
    if Trim(edtCargadorNombre.Text) <> '' then
    begin
      for i := 1 to cmbPresetCargador.Items.Count - 1 do
      begin
        j := Integer(IntPtr(cmbPresetCargador.Items.Objects[i]));
        if (j >= 0) and (j <= High(FContactosPresets)) and
           SameText(Trim(FContactosPresets[j].Nombre), Trim(edtCargadorNombre.Text)) then
        begin
          cmbPresetCargador.ItemIndex := i;
          Break;
        end;
      end;
    end;

    if Trim(edtTranspNombre.Text) <> '' then
    begin
      for i := 1 to cmbPresetTransp.Items.Count - 1 do
      begin
        j := Integer(IntPtr(cmbPresetTransp.Items.Objects[i]));
        if (j >= 0) and (j <= High(FContactosPresets)) and
           SameText(Trim(FContactosPresets[j].Nombre), Trim(edtTranspNombre.Text)) then
        begin
          cmbPresetTransp.ItemIndex := i;
          Break;
        end;
      end;
    end;

    if Trim(edtMatriculaTractora.Text) <> '' then
    begin
      for i := 1 to cmbPresetVehiculo.Items.Count - 1 do
      begin
        j := Integer(IntPtr(cmbPresetVehiculo.Items.Objects[i]));
        if (j >= 0) and (j <= High(FVehiculosPresets)) and
           SameText(Trim(FVehiculosPresets[j].MatriculaTractora), Trim(edtMatriculaTractora.Text)) then
        begin
          cmbPresetVehiculo.ItemIndex := i;
          Break;
        end;
      end;
    end;

    if Trim(edtOrigenNombre.Text) <> '' then
    begin
      for i := 1 to cmbPresetOrigen.Items.Count - 1 do
      begin
        j := Integer(IntPtr(cmbPresetOrigen.Items.Objects[i]));
        if (j >= 0) and (j <= High(FContactosPresets)) and
           SameText(Trim(FContactosPresets[j].Nombre), Trim(edtOrigenNombre.Text)) then
        begin
          cmbPresetOrigen.ItemIndex := i;
          Break;
        end;
      end;
    end;

    if Trim(edtDestinoNombre.Text) <> '' then
    begin
      for i := 1 to cmbPresetDestino.Items.Count - 1 do
      begin
        j := Integer(IntPtr(cmbPresetDestino.Items.Objects[i]));
        if (j >= 0) and (j <= High(FContactosPresets)) and
           SameText(Trim(FContactosPresets[j].Nombre), Trim(edtDestinoNombre.Text)) then
        begin
          cmbPresetDestino.ItemIndex := i;
          Break;
        end;
      end;
    end;

  finally
    LQuery.Free;
  end;
end;

function TfDecaEmisionAlbaranes.EscaparJson(const S: string): string;
begin
  Result := StringReplace(S, '\', '\\', [rfReplaceAll]);
  Result := StringReplace(Result, '"', '\"', [rfReplaceAll]);
  Result := StringReplace(Result, #13, '', [rfReplaceAll]);
  Result := StringReplace(Result, #10, '\n', [rfReplaceAll]);
end;

function TfDecaEmisionAlbaranes.ExtraerValorJson(const AJson, AClave: string): string;
var
  P1, P2: Integer;
  SearchKey: string;
  IsQuoted: Boolean;
begin
  Result := '';
  SearchKey := '"' + AClave + '":';
  P1 := Pos(SearchKey, AJson);
  if P1 > 0 then
  begin
    P1 := P1 + Length(SearchKey);
    while (P1 <= Length(AJson)) and (AJson[P1] in [' ', #9, #13, #10]) do
      Inc(P1);
    if P1 > Length(AJson) then Exit;

    IsQuoted := AJson[P1] = '"';
    if IsQuoted then
    begin
      Inc(P1);
      P2 := P1;
      while (P2 <= Length(AJson)) do
      begin
        if (AJson[P2] = '"') and (AJson[P2 - 1] <> '\') then
          Break;
        Inc(P2);
      end;
    end
    else
    begin
      P2 := P1;
      while (P2 <= Length(AJson)) and not (AJson[P2] in [',', '}', #13, #10]) do
        Inc(P2);
    end;

    Result := Copy(AJson, P1, P2 - P1);
    if SameText(Result, 'null') then
    begin
      Result := '';
      Exit;
    end;
    Result := StringReplace(Result, '\/', '/', [rfReplaceAll]);
    Result := StringReplace(Result, '\"', '"', [rfReplaceAll]);
    Result := StringReplace(Result, '\\', '\', [rfReplaceAll]);
  end;
end;

function TfDecaEmisionAlbaranes.NormalizarUrlPdf(const AUrl: string): string;
var
  LProtocol, LRest, LToken: string;
  P: Integer;
begin
  Result := StringReplace(Trim(AUrl), '\/', '/', [rfReplaceAll]);
  if Pos('https://', Result) = 1 then
  begin
    LProtocol := 'https://';
    LRest := Copy(Result, 9, Length(Result));
    while Pos('//', LRest) > 0 do
      LRest := StringReplace(LRest, '//', '/', [rfReplaceAll]);
    Result := LProtocol + LRest;
  end
  else if Pos('http://', Result) = 1 then
  begin
    LProtocol := 'http://';
    LRest := Copy(Result, 8, Length(Result));
    while Pos('//', LRest) > 0 do
      LRest := StringReplace(LRest, '//', '/', [rfReplaceAll]);
    Result := LProtocol + LRest;
  end;

  // Si la URL apunta a /doc/[token], convertir a /doc.php?token=[token] para entrega directa 100% garantizada
  P := Pos('/doc/', Result);
  if P > 0 then
  begin
    LToken := Copy(Result, P + 5, Length(Result));
    while (Length(LToken) > 0) and (LToken[Length(LToken)] = '/') do
      Delete(LToken, Length(LToken), 1);
    Result := Copy(Result, 1, P - 1) + '/doc.php?token=' + LToken;
  end;
end;



procedure TfDecaEmisionAlbaranes.btnEmitirDeCAClick(Sender: TObject);
var
  LJson, LRespJson, LAlbRefs, LDestinoDir, LDestinoPob, LDestinoCP, LDestinoProv: string;
  LDestinoNombre, LOrigenNombre, LOrigenDir, LOrigenNif: string;
  LCargadorNombre, LCargadorNif, LCargadorDir, LCargadorCP, LCargadorPob, LCargadorProv, LCargadorTel: string;
  LTranspNombre, LTranspNif, LMatTractora, LMatRemolque, LCondNombre, LCondNif: string;
  LMercanciaNat, LBultosStr, LMotivoError: string;
  LKgBruto: Double;
  LPesoBrutoInt, LPaletsNum: Integer;
  LBultosNum: Double;
  i: Integer;
  LQuery: TZQuery;
begin
  LCargadorNombre := Trim(edtCargadorNombre.Text);
  LCargadorNif := Trim(edtCargadorNif.Text);
  LCargadorDir := Trim(edtCargadorDir.Text);
  LCargadorCP := Trim(edtCargadorCP.Text);
  LCargadorPob := Trim(edtCargadorPob.Text);
  LCargadorProv := Trim(edtCargadorProv.Text);
  LCargadorTel := Trim(edtCargadorTel.Text);

  LTranspNombre := Trim(edtTranspNombre.Text);
  LTranspNif := Trim(edtTranspNif.Text);
  LMatTractora := Trim(edtMatriculaTractora.Text);
  LMatRemolque := Trim(edtMatriculaRemolque.Text);
  LCondNombre := Trim(edtConductorNombre.Text);
  LCondNif := Trim(edtConductorNif.Text);
  LMercanciaNat := Trim(edtMercanciaNaturaleza.Text);

  // 1. Validaciones normativas
  if LCargadorNombre = '' then
  begin
    ShowMessage('Debe indicar la Empresa Cargadora (Nombre o Raz'#243'n Social).'#13#10 +
                '(Es la empresa o entidad que contrata o solicita el servicio de transporte, ej: SEUR, el cliente o su propia empresa)');
    pgcDatos.ActivePage := tsCargador;
    edtCargadorNombre.SetFocus;
    Exit;
  end;

  if LCargadorNif = '' then
  begin
    ShowMessage('Debe indicar el NIF / CIF de la Empresa Cargadora.');
    pgcDatos.ActivePage := tsCargador;
    edtCargadorNif.SetFocus;
    Exit;
  end;

  if LCargadorDir = '' then
  begin
    ShowMessage('Debe indicar el Domicilio Fiscal de la Empresa Cargadora.');
    pgcDatos.ActivePage := tsCargador;
    edtCargadorDir.SetFocus;
    Exit;
  end;

  if LTranspNombre = '' then
  begin
    ShowMessage('Debe indicar el nombre o raz'#243'n social del transportista efectivo.'#13#10 +
                '(Exigencia obligatoria seg'#250'n Orden FOM/2861/2012 para el DeCA)');
    pgcDatos.ActivePage := tsTransporte;
    edtTranspNombre.SetFocus;
    Exit;
  end;

  if LTranspNif = '' then
  begin
    ShowMessage('Debe indicar el CIF/NIF del transportista efectivo.'#13#10 +
                '(Exigencia obligatoria seg'#250'n Orden FOM/2861/2012 para el DeCA)');
    pgcDatos.ActivePage := tsTransporte;
    edtTranspNif.SetFocus;
    Exit;
  end;

  if LMatTractora = '' then
  begin
    ShowMessage('Debe indicar la matr'#237'cula del veh'#237'culo tractor.'#13#10 +
                '(Exigencia obligatoria seg'#250'n Orden FOM/2861/2012 para el DeCA)');
    pgcDatos.ActivePage := tsTransporte;
    edtMatriculaTractora.SetFocus;
    Exit;
  end;

  if LMercanciaNat = '' then
    LMercanciaNat := 'Uva embolsada de mesa Vinalop'#243' DOP / Fruta fresca';

  // Peso bruto: redondeado al alza a número entero (sin decimales)
  LKgBruto := StrToFloatDef(StringReplace(edtKilos.Text, ',', '.', [rfReplaceAll]), 0);
  LPesoBrutoInt := Ceil(LKgBruto);
  if LPesoBrutoInt <= 0 then
  begin
    ShowMessage('El peso bruto en Kg debe ser mayor a 0.'#13#10 +
                'Indique el peso bruto en kilogramos enteros.');
    pgcDatos.ActivePage := tsTransporte;
    edtKilos.SetFocus;
    Exit;
  end;

  LPaletsNum := StrToIntDef(edtPalets.Text, 0);
  LBultosNum := StrToFloatDef(edtBultos.Text, 1);
  if LBultosNum <= 0 then LBultosNum := 1;

  if LPaletsNum > 0 then
    LBultosStr := Format('%d Palets / %.0f Bultos', [LPaletsNum, LBultosNum])
  else
    LBultosStr := Format('%.0f Bultos', [LBultosNum]);

  // 2. Construcción de Referencias de Albaranes
  LAlbRefs := '';
  for i := 0 to High(FAlbaranes) do
  begin
    if LAlbRefs <> '' then LAlbRefs := LAlbRefs + ', ';
    LAlbRefs := LAlbRefs + Trim(FAlbaranes[i].Serie + '/' + IntToStr(FAlbaranes[i].Id));
  end;

  LOrigenNombre := Trim(edtOrigenNombre.Text);
  if LOrigenNombre = '' then LOrigenNombre := 'S.A.T. N'#186'2 CV FRUTAS ENRIQUE PRIETO E HIJOS';

  LOrigenDir := Trim(edtOrigenDir.Text);
  if LOrigenDir = '' then LOrigenDir := 'C/ SAGUNTO, 11 ENTLO.';

  LOrigenNif := 'V53022166';
  if Assigned(DM) and Assigned(DM.tempresa) and DM.tempresa.Active and (Trim(DM.tempresacif.AsString) <> '') then
    LOrigenNif := Trim(DM.tempresacif.AsString);

  LDestinoNombre := Trim(edtDestinoNombre.Text);
  if LDestinoNombre = '' then LDestinoNombre := 'Cliente Destinatario';

  LDestinoDir := Trim(edtDestinoDir.Text);
  if LDestinoDir = '' then LDestinoDir := 'Mercado Central / Destino Habitual';

  LDestinoPob := Trim(FAlbaranes[0].Poblacion);
  if LDestinoPob = '' then LDestinoPob := 'Poblaci'#243'n Destino';

  LDestinoCP := Trim(FAlbaranes[0].CP);
  if LDestinoCP = '' then LDestinoCP := '03680';

  LDestinoProv := Trim(FAlbaranes[0].Provincia);
  if LDestinoProv = '' then LDestinoProv := 'Alicante';

  // 3. Preparar JSON estructurado para el servicio DeCA (peso bruto y neto ambos informados)
  LJson := '{' +
    '"origen_sistema":"ERP_AGRIGEST",' +
    '"referencia_albaran":"' + EscaparJson(LAlbRefs) + '",' +
    '"fecha_servicio":"' + FormatDateTime('yyyy-mm-dd', Now) + '",' +
    '"hora_servicio":"' + FormatDateTime('hh:nn', Now) + '",' +
    '"cargador_nombre":"' + EscaparJson(LCargadorNombre) + '",' +
    '"cargador_nif":"' + EscaparJson(LCargadorNif) + '",' +
    '"cargador_direccion":"' + EscaparJson(LCargadorDir) + '",' +
    '"cargador_cp":"' + EscaparJson(LCargadorCP) + '",' +
    '"cargador_localidad":"' + EscaparJson(LCargadorPob) + '",' +
    '"cargador_provincia":"' + EscaparJson(LCargadorProv) + '",' +
    '"transportista_nombre":"' + EscaparJson(LTranspNombre) + '",' +
    '"transportista_nif":"' + EscaparJson(LTranspNif) + '",' +
    '"matricula_tractora":"' + EscaparJson(LMatTractora) + '",' +
    '"matricula_remolque":"' + EscaparJson(LMatRemolque) + '",' +
    '"conductor_nombre":"' + EscaparJson(LCondNombre) + '",' +
    '"conductor_nif":"' + EscaparJson(LCondNif) + '",' +
    '"origen_nombre":"' + EscaparJson(LOrigenNombre) + '",' +
    '"origen_nif":"' + EscaparJson(LOrigenNif) + '",' +
    '"origen_direccion":"' + EscaparJson(LOrigenDir) + '",' +
    '"origen_cp":"03680",' +
    '"origen_localidad":"ASPE",' +
    '"origen_provincia":"ALICANTE",' +
    '"destino_nombre":"' + EscaparJson(LDestinoNombre) + '",' +
    '"destino_nif":"' + EscaparJson(FAlbaranes[0].NifCliente) + '",' +
    '"destino_direccion":"' + EscaparJson(LDestinoDir) + '",' +
    '"destino_cp":"' + EscaparJson(LDestinoCP) + '",' +
    '"destino_localidad":"' + EscaparJson(LDestinoPob) + '",' +
    '"destino_provincia":"' + EscaparJson(LDestinoProv) + '",' +
    '"mercancia_naturaleza":"' + EscaparJson(LMercanciaNat) + '",' +
    '"cantidad_bultos":"' + EscaparJson(LBultosStr) + '",' +
    '"peso_bruto_kg":' + IntToStr(LPesoBrutoInt) + ',' +
    '"peso_neto_kg":' + IntToStr(LPesoBrutoInt) + ',' +
    '"observaciones":"' + EscaparJson(mmoParadas.Text) + '"' +
  '}';

  Screen.Cursor := crHourGlass;
  try
    if not EmitirDocumentoDeca(FDecaConfig, LJson, LRespJson) then
    begin
      LMotivoError := ExtraerValorJson(LRespJson, 'error');
      if LMotivoError = '' then
        LMotivoError := Trim(LRespJson);
      if LMotivoError = '' then
        LMotivoError := 'No se recibi'#243' respuesta del servicio DeCA. Compruebe la configuraci'#243'n y la conexi'#243'n a la red.';
      ShowMessage('Error al emitir el documento DeCA.'#13#10#13#10 +
                  'Motivo reportado:'#13#10 + LMotivoError);
      Exit;
    end;

    FCodigoDeca := ExtraerValorJson(LRespJson, 'codigo_deca');
    FUrlDescargaQr := NormalizarUrlPdf(ExtraerValorJson(LRespJson, 'url_descarga_qr'));

    if FCodigoDeca = '' then
    begin
      LMotivoError := ExtraerValorJson(LRespJson, 'error');
      if LMotivoError = '' then LMotivoError := LRespJson;
      ShowMessage('Respuesta incompleta del emisor DeCA.'#13#10#13#10 +
                  'Motivo: ' + LMotivoError);
      Exit;
    end;

    // Actualizar campos en la interfaz
    lblCodigoDeca.Caption := FCodigoDeca;
    edtUrlQr.Text := FUrlDescargaQr;
    lblEstadoEmision.Caption := '[OK] Documento registrado oficialmente. Inspecci'#243'n QR activa y v'#225'lida.';
    lblEstadoEmision.Font.Color := clGreen;

    btnVerPDF.Enabled := True;
    btnWhatsApp.Enabled := True;
    btnEmitirDeCA.Caption := 'Reemitir / Actualizar DeCA';
    btnEmitirDeCA.Enabled := True;

    lblResumenTotales.Caption := Format('Total Palets: %s  |  Total Bultos: %s  |  Peso Bruto: %s Kg',
      [edtPalets.Text, edtBultos.Text, edtKilos.Text]);



    // Actualizar registros de albaranes en la base de datos de Agrigest con clave completa
    LQuery := TZQuery.Create(nil);
    try
      LQuery.Connection := DM.DBagrigest;

      // 1. Asegurar que la columna deca_codigo exista en la tabla albaranes
      try
        DM.DBagrigest.ExecuteDirect('ALTER TABLE albaranes ADD COLUMN deca_codigo VARCHAR(50) DEFAULT NULL');
      except
        // Ignorar si la columna ya existe
      end;

      // 2. Grabar deca_codigo y observaciones de forma directa y garantizada
      for i := 0 to High(FAlbaranes) do
      begin
        FAlbaranes[i].DecaCodigo := FCodigoDeca;
        try
          DM.DBagrigest.ExecuteDirect(Format(
            'UPDATE albaranes SET deca_codigo = ''%s'', ' +
            'observaciones = CASE ' +
            '  WHEN observaciones IS NULL OR TRIM(observaciones) = '''' THEN ''[DeCA: %s]'' ' +
            '  WHEN observaciones LIKE ''%%[DeCA:%%'' THEN observaciones ' +
            '  ELSE CONCAT(TRIM(observaciones), '' [DeCA: %s]'') ' +
            'END ' +
            'WHERE idempresa = %d AND serie = ''%s'' AND id = %d',
            [FCodigoDeca, FCodigoDeca, FCodigoDeca, FAlbaranes[i].IdEmpresa, FAlbaranes[i].Serie, FAlbaranes[i].Id]
          ));
        except
          // Fallback con TZQuery si ExecuteDirect diera algún problema
          try
            LQuery.Close;
            LQuery.SQL.Text :=
              'UPDATE albaranes SET deca_codigo = :cod ' +
              'WHERE idempresa = :emp AND serie = :ser AND id = :id';
            LQuery.ParamByName('cod').AsString := FCodigoDeca;
            LQuery.ParamByName('emp').AsInteger := FAlbaranes[i].IdEmpresa;
            LQuery.ParamByName('ser').AsString := FAlbaranes[i].Serie;
            LQuery.ParamByName('id').AsInteger := FAlbaranes[i].Id;
            LQuery.ExecSQL;
          except
          end;
        end;
      end;

      // 3. Confirmar transacción si está activa
      try
        if DM.DBagrigest.InTransaction then
          DM.DBagrigest.Commit;
      except
      end;
    finally
      LQuery.Free;
    end;

    pgcDatos.ActivePage := tsDeCA;

    ShowMessage('Documento Oficial DeCA emitido con '#233'xito.'#13#10#13#10 +
      'C'#243'digo: ' + FCodigoDeca + #13#10 +
      'Inspecci'#243'n QR: ' + FUrlDescargaQr);

  finally
    Screen.Cursor := crDefault;
  end;
end;

procedure TfDecaEmisionAlbaranes.btnVerPDFClick(Sender: TObject);
var
  LUrl: string;
begin
  LUrl := NormalizarUrlPdf(FUrlDescargaQr);
  if LUrl <> '' then
    ShellExecute(0, 'open', PChar(LUrl), nil, nil, SW_SHOWNORMAL)
  else
    ShowMessage('El documento a'#250'n no ha sido emitido.');
end;

function UrlEncode(const S: string): string;
var
  i: Integer;
begin
  Result := '';
  for i := 1 to Length(S) do
  begin
    case S[i] of
      'A'..'Z', 'a'..'z', '0'..'9', '-', '_', '.', '~': Result := Result + S[i];
      ' ': Result := Result + '%20';
      #13: Result := Result + '%0D';
      #10: Result := Result + '%0A';
      else Result := Result + '%' + IntToHex(Ord(S[i]), 2);
    end;
  end;
end;

procedure TfDecaEmisionAlbaranes.btnWhatsAppClick(Sender: TObject);
var
  LMsg, LWaUrl, LUrlFinal: string;
begin
  LUrlFinal := NormalizarUrlPdf(FUrlDescargaQr);
  if LUrlFinal = '' then
  begin
    ShowMessage('Debe emitir el documento antes de enviarlo por WhatsApp.');
    Exit;
  end;

  LMsg := Format(
    'Hola %s, adjunto el Documento Electronico de Control Administrativo (DeCA) obligatorio para tu ruta:%s%sDocumento: %s%sVerificacion e Inspeccion DGT / Guardia Civil:%s%s',
    [edtConductorNombre.Text, #13#10, #13#10, FCodigoDeca, #13#10, #13#10, LUrlFinal]
  );

  LWaUrl := 'https://api.whatsapp.com/send?text=' + UrlEncode(LMsg);
  ShellExecute(0, 'open', PChar(LWaUrl), nil, nil, SW_SHOWNORMAL);
end;

procedure TfDecaEmisionAlbaranes.btnCerrarClick(Sender: TObject);
begin
  Close;
end;

end.
