unit frm_AlbaranEnvioEditor;

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, Vcl.Graphics,
  Vcl.Controls, Vcl.Forms, Vcl.Dialogs, Vcl.ExtCtrls, Vcl.StdCtrls, Vcl.Buttons,
  Vcl.ComCtrls, Vcl.Grids, Vcl.DBGrids, uBaseForm, Data.DB, FireDAC.Stan.Param,
  FireDAC.Comp.Client, System.UITypes, Vcl.Imaging.pngimage, Vcl.Imaging.jpeg,
  System.NetEncoding;

type
  TfrmAlbaranEnvioEditor = class(TfrmBase)
    lblPedido: TLabel;
    edtPedido: TEdit;
    lblTransportista: TLabel;
    edtTransportista: TEdit;
    lblCodRecogida: TLabel;
    edtCodRecogida: TEdit;
    lblFechaRecogida: TLabel;
    edtFechaRecogida: TEdit;
    lblHoraIni: TLabel;
    edtHoraIni: TEdit;
    lblHoraFin: TLabel;
    edtHoraFin: TEdit;
    lblNumBultos: TLabel;
    edtNumBultos: TEdit;
    lblEstado: TLabel;
    edtEstado: TEdit;
    lblConductor: TLabel;
    edtConductor: TEdit;
    lblMatricula: TLabel;
    edtMatricula: TEdit;
    // ── Datos del Destinatario ──────────────────────────────────────────────
    grpDestinatario: TGroupBox;
    lblDestNombre: TLabel;
    edtDestNombre: TEdit;
    lblDestTelefono: TLabel;
    edtDestTelefono: TEdit;
    lblDestDireccion: TLabel;
    edtDestDireccion: TEdit;
    lblDestCP: TLabel;
    edtDestCP: TEdit;
    lblDestPoblacion: TLabel;
    edtDestPoblacion: TEdit;
    lblDestProvincia: TLabel;
    edtDestProvincia: TEdit;
    lblDestPais: TLabel;
    edtDestPais: TEdit;
    // ── Tracking ────────────────────────────────────────────────────────────
    btnConsultarTracking: TSpeedButton;
    btnCancelarEnvio: TSpeedButton;
    memTracking: TMemo;
    // ── Firma del Conductor ────────────────────────────────────────────────
    tsFirma: TTabSheet;
    lblFirmaNombre: TLabel;
    edtFirmaNombre: TEdit;
    lblFirmaDoc: TLabel;
    edtFirmaDoc: TEdit;
    lblFirmaFecha: TLabel;
    edtFirmaFecha: TEdit;
    grpFirmaImagen: TGroupBox;
    imgFirma: TImage;
    lblSinFirma: TLabel;
    procedure FormShow(Sender: TObject);
    procedure btnConsultarTrackingClick(Sender: TObject);
    procedure btnCancelarEnvioClick(Sender: TObject);
    procedure btnImprimirEtiquetasClick(Sender: TObject);
    procedure btnVerCargaClick(Sender: TObject);
  private
    FRecogidaId: Integer;
    FConfigId: Integer;
  public
    property RecogidaId: Integer read FRecogidaId write FRecogidaId;
  end;

var
  frmAlbaranEnvioEditor: TfrmAlbaranEnvioEditor;

implementation

uses
  dmg_Main, uDbErrorHandler, uEnviosClient, uEnviosTypes, Winapi.ShellAPI, frm_SelectImpresoraEnvio,
  uPdfRotateService;

{$R *.dfm}

procedure CargarFirmaDeCampo(AField: TField; APicture: TPicture; AImg: TImage; ALblSinFirma: TLabel);
var
  LStream: TStream;
  LMemStream: TMemoryStream;
  LStrStream: TStringStream;
  LHeader: array[0..15] of AnsiChar;
  LRawStr, LBase64: string;
  LCommaPos: Integer;
  LBytes: TBytes;
  LLoaded: Boolean;
  LWic: TWICImage;
  LPng: TPngImage;
begin
  if (AField = nil) or AField.IsNull then
  begin
    APicture.Assign(nil);
    if AImg <> nil then AImg.Visible := False;
    if ALblSinFirma <> nil then ALblSinFirma.Visible := True;
    Exit;
  end;

  LStream := AField.DataSet.CreateBlobStream(AField, bmRead);
  try
    if LStream.Size = 0 then
    begin
      APicture.Assign(nil);
      if AImg <> nil then AImg.Visible := False;
      if ALblSinFirma <> nil then ALblSinFirma.Visible := True;
      Exit;
    end;

    LLoaded := False;
    LMemStream := TMemoryStream.Create;
    try
      FillChar(LHeader, SizeOf(LHeader), 0);
      LStream.Position := 0;
      LStream.Read(LHeader, SizeOf(LHeader));
      LStream.Position := 0;

      if (Copy(string(LHeader), 1, 10) = 'data:image') or (Copy(string(LHeader), 1, 5) = 'iVBOR') then
      begin
        LStrStream := TStringStream.Create('', TEncoding.ANSI);
        try
          LStrStream.CopyFrom(LStream, LStream.Size);
          LRawStr := LStrStream.DataString;
          LCommaPos := Pos('base64,', LRawStr);
          if LCommaPos > 0 then
            LBase64 := Copy(LRawStr, LCommaPos + 7, Length(LRawStr))
          else
            LBase64 := LRawStr;
          LBytes := TNetEncoding.Base64.DecodeStringToBytes(Trim(LBase64));
          if Length(LBytes) > 0 then
          begin
            LMemStream.WriteBuffer(LBytes[0], Length(LBytes));
            LMemStream.Position := 0;
          end;
        finally
          LStrStream.Free;
        end;
      end
      else
      begin
        LMemStream.LoadFromStream(LStream);
        LMemStream.Position := 0;
      end;

      if LMemStream.Size > 0 then
      begin
        try
          LWic := TWICImage.Create;
          try
            LWic.LoadFromStream(LMemStream);
            APicture.Assign(LWic);
            LLoaded := True;
          finally
            LWic.Free;
          end;
        except
          LLoaded := False;
        end;

        if not LLoaded then
        begin
          LMemStream.Position := 0;
          try
            LPng := TPngImage.Create;
            try
              LPng.LoadFromStream(LMemStream);
              APicture.Assign(LPng);
              LLoaded := True;
            finally
              LPng.Free;
            end;
          except
            LLoaded := False;
          end;
        end;

        if not LLoaded then
        begin
          LMemStream.Position := 0;
          try
            APicture.Bitmap.LoadFromStream(LMemStream);
            LLoaded := True;
          except
            LLoaded := False;
          end;
        end;
      end;
    finally
      LMemStream.Free;
    end;

    if AImg <> nil then AImg.Visible := LLoaded;
    if ALblSinFirma <> nil then ALblSinFirma.Visible := not LLoaded;
    if not LLoaded then APicture.Assign(nil);
  finally
    LStream.Free;
  end;
end;

procedure TfrmAlbaranEnvioEditor.FormShow(Sender: TObject);
var
  Qry: TFDQuery;
begin
  inherited;
  frmAlbaranEnvioEditor := Self;
  memTracking.Lines.Clear;

  // Configurar barra superior de herramientas
  btnImprimir.Visible := True;
  btnImprimir.Caption := 'Etiquetas';
  btnImprimir.Hint := 'Descargar e imprimir etiquetas del transportista';
  btnImprimir.ImageName := 'icon_imprimir';
  btnImprimir.Width := 75;
  btnImprimir.OnClick := btnImprimirEtiquetasClick;

  btnRect.Visible := True;
  btnRect.Caption := 'Doc. Carga';
  btnRect.Hint := 'Ver / imprimir Albarán de Carga';
  btnRect.ImageName := 'icon_albaran';
  btnRect.Width := 85;
  btnRect.OnClick := btnVerCargaClick;

  btnGuardar.Visible := False;
  btnCancelar.Visible := False;
  btnAnadir.Visible := False;
  btnEliminar.Visible := False;
  btnModificar.Visible := False;
  btnPrimero.Visible := False;
  btnAnterior.Visible := False;
  btnSiguiente.Visible := False;
  btnUltimo.Visible := False;
  btnBuscar.Visible := False;
  btnIA.Visible := False;
  btnDuplicar.Visible := False;
  btnAyuda.Visible := False;
  btnSalir.Visible := True;
  
  if FRecogidaId <= 0 then Exit;
  
  Qry := TFDQuery.Create(nil);
  try
    Qry.Connection := dmgMain.dbConn;
    Qry.SQL.Text := 
      'SELECT r.id, COALESCE(p.numero_pedido, '''') as numero_pedido, ' +
      '       COALESCE(t.nombre, '''') as transportista_nombre, r.cod_recogida, ' +
      '       r.fecha_recogida, r.hora_ini, r.hora_fin, r.num_bultos, ' +
      '       r.matricula, r.conductor, r.estado_descripcion, r.persona_contacto, ' +
      '       r.config_id, ' +
      '       r.firma_nombre, r.firma_documento, r.firma_fecha, r.firma_imagen, ' +
      '       COALESCE(r.persona_contacto, d.descripcion, c.nombre_fiscal, '''') AS dest_nombre, ' +
      '       COALESCE(r.direccion, d.direccion, c.domicilio, '''')       AS dest_direccion, ' +
      '       COALESCE(r.cp, d.cp, c.codigo_postal, '''')          AS dest_cp, ' +
      '       COALESCE(r.poblacion, d.poblacion, c.poblacion, '''')       AS dest_poblacion, ' +
      '       COALESCE(r.provincia, d.provincia, c.provincia, '''')       AS dest_provincia, ' +
      '       COALESCE(r.pais, d.pais, ''ES'')                 AS dest_pais, ' +
      '       COALESCE(r.telefono, c.telefono, '''')           AS dest_telefono ' +
      'FROM ge_envios_recogidas r ' +
      'LEFT JOIN ge_pedidos p ON r.pedido_id = p.id ' +
      'LEFT JOIN ge_envios_config ec ON r.config_id = ec.id ' +
      'LEFT JOIN ge_envios_transportistas t ON ec.transportista_id = t.id ' +
      'LEFT JOIN ge_clientes c ON p.cliente_id = c.id ' +
      'LEFT JOIN ge_clientes_destinos d ON p.clientedestino_id = d.id ' +
      'WHERE r.id = :id';
    Qry.ParamByName('id').AsInteger := FRecogidaId;
    Qry.Open;
    
    if not Qry.IsEmpty then
    begin
      FConfigId := Qry.FieldByName('config_id').AsInteger;
      // Datos del envío
      edtPedido.Text := Qry.FieldByName('numero_pedido').AsString;
      edtTransportista.Text := Qry.FieldByName('transportista_nombre').AsString;
      edtCodRecogida.Text := Qry.FieldByName('cod_recogida').AsString;
      edtFechaRecogida.Text := FormatDateTime('dd/mm/yyyy', Qry.FieldByName('fecha_recogida').AsDateTime);
      edtHoraIni.Text := FormatDateTime('hh:nn', Qry.FieldByName('hora_ini').AsDateTime);
      edtHoraFin.Text := FormatDateTime('hh:nn', Qry.FieldByName('hora_fin').AsDateTime);
      edtNumBultos.Text := Qry.FieldByName('num_bultos').AsString;
      edtEstado.Text := Qry.FieldByName('estado_descripcion').AsString;
      edtConductor.Text := Qry.FieldByName('conductor').AsString;
      edtMatricula.Text := Qry.FieldByName('matricula').AsString;
      // Datos del destinatario (desde ge_clientes / ge_clientes_destinos)
      edtDestNombre.Text := Qry.FieldByName('dest_nombre').AsString;
      edtDestTelefono.Text := Qry.FieldByName('dest_telefono').AsString;
      edtDestDireccion.Text := Qry.FieldByName('dest_direccion').AsString;
      edtDestCP.Text := Qry.FieldByName('dest_cp').AsString;
      edtDestPoblacion.Text := Qry.FieldByName('dest_poblacion').AsString;
      edtDestProvincia.Text := Qry.FieldByName('dest_provincia').AsString;
      edtDestPais.Text := Qry.FieldByName('dest_pais').AsString;

      // Datos de la firma del conductor
      edtFirmaNombre.Text := Qry.FieldByName('firma_nombre').AsString;
      if edtFirmaNombre.Text = '' then
        edtFirmaNombre.Text := Qry.FieldByName('conductor').AsString;
      edtFirmaDoc.Text := Qry.FieldByName('firma_documento').AsString;
      if not Qry.FieldByName('firma_fecha').IsNull then
        edtFirmaFecha.Text := FormatDateTime('dd/mm/yyyy hh:nn', Qry.FieldByName('firma_fecha').AsDateTime)
      else
        edtFirmaFecha.Text := '';

      // Carga segura y multi-formato de la firma
      CargarFirmaDeCampo(Qry.FindField('firma_imagen'), imgFirma.Picture, imgFirma, lblSinFirma);
    end;
  finally
    Qry.Free;
  end;
end;

procedure TfrmAlbaranEnvioEditor.btnConsultarTrackingClick(Sender: TObject);
var
  LConfig: TEnviosConfig;
  LClient: IEnviosClient;
  LError: string;
  LEstados: TArray<TEstadoEnvio>;
  i: Integer;
begin
  if FConfigId <= 0 then
  begin
    ShowMessage('ID de configuración no válido.');
    Exit;
  end;
  
  if Trim(edtCodRecogida.Text) = '' then
  begin
    ShowMessage('El envío no tiene un código de recogida/referencia asignado.');
    Exit;
  end;

  memTracking.Lines.Clear;
  memTracking.Lines.Add('Conectando con el servidor de la agencia de transportes...');
  
  if not CargarEnviosConfig(FConfigId, LConfig) then
  begin
    ShowMessage('No se pudo cargar la configuración de envíos del transportista.');
    Exit;
  end;
  
  LClient := TEnviosClientFactory.Create(LConfig, LError);
  if not Assigned(LClient) then
  begin
    ShowMessage(LError);
    Exit;
  end;
  
  Screen.Cursor := crHourGlass;
  try
    try
      if LClient.Login then
      begin
        LEstados := LClient.GetEstados(edtCodRecogida.Text);
        memTracking.Lines.Clear;
        memTracking.Lines.Add('=== SEGUIMIENTO DEL ENVÍO ===');
        memTracking.Lines.Add('Referencia: ' + edtCodRecogida.Text);
        memTracking.Lines.Add('Agencia: ' + LConfig.NombreTransp);
        memTracking.Lines.Add('');
        
        if Length(LEstados) = 0 then
        begin
          memTracking.Lines.Add('No se han registrado estados o eventos de tracking para este envío.');
        end
        else
        begin
          for i := 0 to Length(LEstados) - 1 do
          begin
            memTracking.Lines.Add(Format('[%s] - %s', [
              FormatDateTime('dd/mm/yyyy hh:nn:ss', LEstados[i].FechaHora),
              LEstados[i].Descripcion
            ]));
          end;
        end;
      end
      else
      begin
        memTracking.Lines.Add('Error: Autenticación rechazada por el transportista.');
      end;
    except
      on E: Exception do
      begin
        memTracking.Lines.Clear;
        memTracking.Lines.Add('ERROR AL OBTENER SEGUIMIENTO:');
        memTracking.Lines.Add(E.Message);
      end;
    end;
  finally
    Screen.Cursor := crDefault;
  end;
end;

procedure TfrmAlbaranEnvioEditor.btnCancelarEnvioClick(Sender: TObject);
var
  LConfig: TEnviosConfig;
  LClient: IEnviosClient;
  LError, LMsg: string;
  Qry, QryTemp: TFDQuery;
  LPedidoId, LAlbaranId, LArtId, LStockId: Integer;
  LCant: Double;
begin
  if FConfigId <= 0 then
  begin
    ShowMessage('ID de configuración no válido.');
    Exit;
  end;

  if Trim(edtCodRecogida.Text) = '' then
  begin
    ShowMessage('El envío no tiene un código de recogida/referencia asignado.');
    Exit;
  end;

  if MessageDlg('¿Está seguro de que desea cancelar esta expedición en la agencia de transportes y ANULAR el albarán generado?',
    mtConfirmation, [mbYes, mbNo], 0) <> mrYes then
    Exit;

  memTracking.Lines.Clear;
  memTracking.Lines.Add('Conectando con el servidor de la agencia de transportes para cancelar...');

  if not CargarEnviosConfig(FConfigId, LConfig) then
  begin
    ShowMessage('No se pudo cargar la configuración de envíos del transportista.');
    Exit;
  end;

  LClient := TEnviosClientFactory.Create(LConfig, LError);
  if not Assigned(LClient) then
  begin
    ShowMessage(LError);
    Exit;
  end;

  Screen.Cursor := crHourGlass;
  try
    try
      if LClient.Login then
      begin
        if LClient.AnulaEnvio(edtCodRecogida.Text, LMsg) then
        begin
          memTracking.Lines.Clear;
          memTracking.Lines.Add('=== ANULACIÓN REALIZADA ===');
          memTracking.Lines.Add('Referencia: ' + edtCodRecogida.Text);
          memTracking.Lines.Add('Respuesta del transportista:');
          memTracking.Lines.Add(LMsg);

          // Realizar la limpieza de base de datos local en una transacción
          dmgMain.dbConn.StartTransaction;
          try
            Qry := TFDQuery.Create(nil);
            try
              Qry.Connection := dmgMain.dbConn;
              
              // 1. Obtener pedido_id y albaran_id de la recogida
              Qry.SQL.Text := 'SELECT pedido_id, albaran_id FROM ge_envios_recogidas WHERE id = :id';
              Qry.ParamByName('id').AsInteger := FRecogidaId;
              Qry.Open;
              
              if not Qry.IsEmpty then
              begin
                LPedidoId := Qry.FieldByName('pedido_id').AsInteger;
                LAlbaranId := Qry.FieldByName('albaran_id').AsInteger;
                Qry.Close;
                
                // 2. Si hay albarán, restaurar stock de sus líneas y eliminarlo
                if LAlbaranId > 0 then
                begin
                  // Obtener líneas del albarán
                  Qry.SQL.Text :=
                    'SELECT al.articulo_id, al.cantidad, COALESCE(NULLIF(a.id_maestro, 0), al.articulo_id) AS id_articulo_stock ' +
                    'FROM ge_albaranes_lineas al ' +
                    'LEFT JOIN ge_articulos a ON a.id = al.articulo_id ' +
                    'WHERE al.albaran_id = :alb';
                  Qry.ParamByName('alb').AsInteger := LAlbaranId;
                  Qry.Open;
                  
                  while not Qry.Eof do
                  begin
                    LArtId := Qry.FieldByName('id_articulo_stock').AsInteger;
                    LCant := Qry.FieldByName('cantidad').AsFloat;
                    
                    // Buscar el lote más reciente en ge_stock_envasado para este artículo
                    QryTemp := TFDQuery.Create(nil);
                    try
                      QryTemp.Connection := dmgMain.dbConn;
                      QryTemp.SQL.Text := 'SELECT id FROM ge_stock_envasado WHERE id_articulo = :art ORDER BY id_lote DESC, id DESC LIMIT 1';
                      QryTemp.ParamByName('art').AsInteger := LArtId;
                      QryTemp.Open;
                      
                      if not QryTemp.IsEmpty then
                      begin
                        LStockId := QryTemp.FieldByName('id').AsInteger;
                        // Liberar la cantidad reservada en el stock
                        dmgMain.qryExec.Close;
                        dmgMain.qryExec.SQL.Text := 
                          'UPDATE ge_stock_envasado SET cantidad_reservada = GREATEST(0, COALESCE(cantidad_reservada, 0) - :cant), ' +
                          'updated_at = NOW(), id_user_update = :user WHERE id = :id';
                        dmgMain.qryExec.ParamByName('cant').AsFloat := LCant;
                        dmgMain.qryExec.ParamByName('user').AsString := dmgMain.CurrentUserId;
                        dmgMain.qryExec.ParamByName('id').AsInteger := LStockId;
                        dmgMain.qryExec.ExecSQL;
                      end;
                    finally
                      QryTemp.Free;
                    end;
                    
                    Qry.Next;
                  end;
                  Qry.Close;
                  
                  // Marcar albarán como anulado (se conservan cabecera y líneas para auditoría legal)
                  dmgMain.qryExec.Close;
                  dmgMain.qryExec.SQL.Text := 'UPDATE ge_albaranes SET anulado = 1, id_user_update = :user WHERE id = :alb';
                  dmgMain.qryExec.ParamByName('alb').AsInteger := LAlbaranId;
                  dmgMain.qryExec.ParamByName('user').AsString := dmgMain.CurrentUserId;
                  dmgMain.qryExec.ExecSQL;
                end;
                
                // 3. Marcar el registro de ge_envios_recogidas como anulado
                dmgMain.qryExec.Close;
                dmgMain.qryExec.SQL.Text := 
                  'UPDATE ge_envios_recogidas SET estado_codigo = 5, estado_descripcion = ''ANULADA'', id_user_update = :user WHERE id = :id';
                dmgMain.qryExec.ParamByName('id').AsInteger := FRecogidaId;
                dmgMain.qryExec.ParamByName('user').AsString := dmgMain.CurrentUserId;
                dmgMain.qryExec.ExecSQL;
                
                // 4. Restaurar estado del pedido a 'PENDIENTE'
                if LPedidoId > 0 then
                begin
                  dmgMain.qryExec.Close;
                  dmgMain.qryExec.SQL.Text := 
                    'UPDATE ge_pedidos SET estado = ''PENDIENTE'', id_user_update = :user WHERE id = :id';
                  dmgMain.qryExec.ParamByName('user').AsString := dmgMain.CurrentUserId;
                  dmgMain.qryExec.ParamByName('id').AsInteger := LPedidoId;
                  dmgMain.qryExec.ExecSQL;
                end;
              end;
            finally
              Qry.Free;
            end;
            
            dmgMain.dbConn.Commit;
            ShowMessage('Expedición cancelada en Nacex y albarán anulado correctamente. El pedido vuelve a estar PENDIENTE.');
            ModalResult := mrOk; // Cerrar el formulario
          except
            on E: Exception do
            begin
              dmgMain.dbConn.Rollback;
              raise Exception.Create('Error en la base de datos al revertir el envío: ' + E.Message);
            end;
          end;
        end
        else
        begin
          memTracking.Lines.Clear;
          memTracking.Lines.Add('FALLO AL CANCELAR EXPEDICIÓN:');
          memTracking.Lines.Add(LMsg);
          ShowMessage('No se pudo cancelar la expedición: ' + LMsg);
        end;
      end
      else
      begin
        memTracking.Lines.Add('Error: Autenticación rechazada por el transportista.');
      end;
    except
      on E: Exception do
      begin
        memTracking.Lines.Clear;
        memTracking.Lines.Add('ERROR AL ANULAR ENVÍO:');
        memTracking.Lines.Add(E.Message);
      end;
    end;
  finally
    Screen.Cursor := crDefault;
  end;
end;

procedure TfrmAlbaranEnvioEditor.btnImprimirEtiquetasClick(Sender: TObject);
var
  LCodRec: string;
  LConfig: TEnviosConfig;
  LClient: IEnviosClient;
  LError: string;
  LEtiquetaBytes: TBytes;
  LPdfFile: string;
  LFS: TFileStream;
  LImpresora: string;
  LEsPDF: Boolean;
  LGirar180: Boolean;
begin
  LCodRec := Trim(edtCodRecogida.Text);
  if LCodRec = '' then
  begin
    ShowMessage('Este envío no dispone de un código de recogida asignado.');
    Exit;
  end;

  if FConfigId <= 0 then
  begin
    ShowMessage('No se ha encontrado la configuración del transportista.');
    Exit;
  end;

  if not CargarEnviosConfig(FConfigId, LConfig) then
  begin
    ShowMessage('Error al cargar la configuración de envío del transportista.');
    Exit;
  end;

  LGirar180 := True;
  // 1. Preguntar al usuario el destino (impresora térmica directa o PDF)
  if not TfrmSelectImpresoraEnvio.SeleccionarDestino(Self, LConfig.NombreTransp, LConfig.ModeloImpresora, LImpresora, LEsPDF, LGirar180) then
    Exit; // Usuario canceló

  Screen.Cursor := crHourGlass;
  try
    try
      LClient := TEnviosClientFactory.Create(LConfig, LError);
      if not Assigned(LClient) then
      begin
        ShowMessage('Aviso: ' + LError);
        Exit;
      end;

      if not LClient.Login then
      begin
        ShowMessage('No se pudo autenticar con la plataforma del transportista ' + LConfig.NombreTransp + '.');
        Exit;
      end;

      // 2. Impresión térmica directa
      if not LEsPDF then
      begin
        if LClient.ImprimirEtiquetaTermica(LCodRec, LImpresora, LError, LGirar180) then
        begin
          ShowMessage('🏷️ Etiqueta enviada correctamente a la impresora "' + LImpresora + '".');
          Exit;
        end;

        if MessageDlg('No se pudo enviar la impresión a la impresora seleccionada (' + LError + ').' + sLineBreak + sLineBreak +
                      '¿Desea previsualizar y abrir la etiqueta en formato PDF?', mtConfirmation, [mbYes, mbNo], 0) <> mrYes then
          Exit;
      end;

      // 3. Descargar etiqueta en formato PDF para visualización/impresión estándar
      LEtiquetaBytes := LClient.GetEtiqueta(LCodRec, True);
      if Length(LEtiquetaBytes) = 0 then
      begin
        ShowMessage('El transportista ' + LConfig.NombreTransp + ' no devolvió datos de etiqueta para la recogida ' + LCodRec + '.');
        Exit;
      end;

      LPdfFile := GetEnvironmentVariable('TEMP') + '\Etiqueta_' +
                  StringReplace(LCodRec, '/', '_', [rfReplaceAll]) +
                  '_' + FormatDateTime('yyyymmddhhnnss', Now) + '.pdf';
      LFS := TFileStream.Create(LPdfFile, fmCreate);
      try
        LFS.WriteBuffer(LEtiquetaBytes[0], Length(LEtiquetaBytes));
      finally
        LFS.Free;
      end;

      if LGirar180 then
        TPdfRotateService.RotarPdfArchivo180(LPdfFile);

      ShellExecute(0, 'open', PChar(LPdfFile), nil, nil, SW_SHOWNORMAL);
    except
      on E: Exception do
        ShowMessage('Error al solicitar/descargar la etiqueta del transportista:' + sLineBreak + E.Message);
    end;
  finally
    Screen.Cursor := crDefault;
  end;
end;

procedure TfrmAlbaranEnvioEditor.btnVerCargaClick(Sender: TObject);
var
  Qry: TFDQuery;
  LDocText, LCodRec, LSafeCod, LTemp: string;
  c: Char;
  LDoc: TStringList;
begin
  if FRecogidaId <= 0 then Exit;

  Qry := TFDQuery.Create(nil);
  try
    Qry.Connection := dmgMain.dbConn;
    Qry.SQL.Text := 'SELECT cod_recogida, albaran_carga FROM ge_envios_recogidas WHERE id = :id';
    Qry.ParamByName('id').AsInteger := FRecogidaId;
    Qry.Open;
    if Qry.IsEmpty then Exit;

    LDocText := Qry.FieldByName('albaran_carga').AsString;
    LCodRec  := Qry.FieldByName('cod_recogida').AsString;
  finally
    Qry.Free;
  end;

  if Trim(LDocText) = '' then
  begin
    ShowMessage('No hay texto de Albarán de Carga registrado para esta recogida.');
    Exit;
  end;

  LSafeCod := LCodRec;
  for c in ['<', '>', ':', '"', '/', '\', '|', '?', '*'] do
    LSafeCod := LSafeCod.Replace(c, '_');
  LSafeCod := Trim(LSafeCod);

  LDoc := TStringList.Create;
  try
    LDoc.Text := LDocText;
    LTemp := GetEnvironmentVariable('TEMP') + '\DocCarga_' + LSafeCod + '_' + FormatDateTime('yyyymmddhhnnss', Now) + '.txt';
    LDoc.SaveToFile(LTemp, TEncoding.UTF8);
    ShellExecute(0, 'open', PChar(LTemp), nil, nil, SW_SHOWNORMAL);
  finally
    LDoc.Free;
  end;
end;

end.
