﻿unit frm_ClientesEditor;

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.Buttons,
  Data.DB, FireDAC.Comp.Client, FireDAC.Stan.Param, uAppTheme, uBaseForm,
  Vcl.ComCtrls, Vcl.Grids, Vcl.DBGrids, FireDAC.Stan.Intf, FireDAC.Stan.Option,
  FireDAC.Stan.Error, FireDAC.DatS, FireDAC.Phys.Intf, FireDAC.DApt.Intf,
  FireDAC.Stan.Async, FireDAC.DApt, FireDAC.Comp.DataSet, System.UITypes;

type
  TfrmClientesEditor = class(TfrmBase)
    lblCodigoCliente: TLabel;
    edtCodigoCliente: TEdit;
    lblNIF: TLabel;
    edtNIF: TEdit;
    lblEanCliente: TLabel;
    edtEanCliente: TEdit;
    lblNombreFiscal: TLabel;
    edtNombreFiscal: TEdit;
    lblNombreComercial: TLabel;
    edtNombreComercial: TEdit;
    lblDomicilio: TLabel;
    edtDomicilio: TEdit;
    lblPoblacion: TLabel;
    edtPoblacion: TEdit;
    lblCodigoPostal: TLabel;
    edtCodigoPostal: TEdit;
    lblProvincia: TLabel;
    edtProvincia: TEdit;
    lblTelefono: TLabel;
    edtTelefono: TEdit;
    lblEmail: TLabel;
    edtEmail: TEdit;
    chkActivo: TCheckBox;
    lblCredito: TLabel;
    lblPorcentajeBloqueo: TLabel;
    edtCredito: TEdit;
    edtPorcentajeBloqueo: TEdit;
    chkBloquear: TCheckBox;
    chkAsegurado: TCheckBox;
    lblGrupoCliente: TLabel;
    cbGrupoCliente: TComboBox;
    lblFormaPago: TLabel;
    cbFormaPago: TComboBox;
    qryDestinos: TFDQuery;
    dsDestinos: TDataSource;
    tsProductosSuministrados: TTabSheet;
    dbgProductosSuministrados: TDBGrid;
    qryProductosSuministrados: TFDQuery;
    dsProductosSuministrados: TDataSource;
    tsDocumentosRelacionados: TTabSheet;
    dbgDocumentosRelacionados: TDBGrid;
    qryDocumentosRelacionados: TFDQuery;
    dsDocumentosRelacionados: TDataSource;

    // Filtros de Productos Suministrados
    pnlFiltroSuministrados: TPanel;
    rgAgrupacionSuministrados: TRadioGroup;
    chkTodasEmpresasSuministrados: TCheckBox;
    pnlFiltroSuministradosSub: TPanel;
    lblFiltroFamilia: TLabel;
    cbbFiltroFamilia: TComboBox;
    lblFiltroDescripcion: TLabel;
    edtFiltroDescripcion: TEdit;
    btnFiltrarSuministrados: TButton;
    lblTotalesSeleccion: TLabel;

    // Filtros de Documentos Relacionados
    pnlFiltroDocs: TPanel;
    lblFiltroTipoDoc: TLabel;
    cbbFiltroTipoDoc: TComboBox;
    lblFiltroDocEjercicio: TLabel;
    cbbFiltroDocEjercicio: TComboBox;
    chkTodasEmpresasDocs: TCheckBox;
    lblFiltroDocTexto: TLabel;
    edtFiltroDocTexto: TEdit;
    btnFiltrarDocs: TButton;
    btnAbrirDoc: TButton;
    btnTrazabilidadDoc: TButton;
    pnlFiltroDocsSub: TPanel;
    lblResumenDocs: TLabel;

    procedure FormShow(Sender: TObject);
    procedure btnSaveClick(Sender: TObject);
    procedure btnSalirClick(Sender: TObject);
    procedure btnAddDestinoClick(Sender: TObject);
    procedure btnDelDestinoClick(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
    procedure dbgItemsDblClick(Sender: TObject);
    procedure RefrescarSuministrados(Sender: TObject);
    procedure RefrescarSuministradosKeyPress(Sender: TObject; var Key: Char);
    procedure RefrescarDocumentosRelacionados(Sender: TObject);
    procedure RefrescarDocumentosKeyPress(Sender: TObject; var Key: Char);
    procedure dbgDocumentosRelacionadosDblClick(Sender: TObject);
    procedure dbgDocumentosRelacionadosTitleClick(Column: TColumn);
    procedure btnAbrirDocClick(Sender: TObject);
    procedure btnTrazabilidadDocClick(Sender: TObject);
  protected
    procedure DoShow; override;
    procedure DoAnadir; override;
    procedure DoModificar; override;
    procedure DoPrimero; override;
    procedure DoAnterior; override;
    procedure DoSiguiente; override;
    procedure DoUltimo; override;
  private
    FClienteId: string;
    FParentForm: TForm;
    FGrupoIds: TStringList;
    FFormaPagoIds: TStringList;
    FSuministradosLoaded: Boolean;
    FDocumentosLoaded: Boolean;
    FLastDocSortColumn: string;
    FLastDocSortDirection: string;

    procedure pgcDetailsChange(Sender: TObject);
    procedure LoadGrupos;
    procedure LoadFormasPago;
    procedure LoadData;
    procedure CalcularTotalesSeleccion;
    procedure dbgProductosSuministradosMouseUp(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
    procedure dbgProductosSuministradosKeyUp(Sender: TObject; var Key: Word; Shift: TShiftState);
    procedure btnPreciosClick(Sender: TObject);
  public
    property ClienteId: string read FClienteId write FClienteId;
    property ParentForm: TForm read FParentForm write FParentForm;
  end;

var
  frmClientesEditor: TfrmClientesEditor;

implementation

uses
  dmg_Main, uDbErrorHandler, frm_Clientes, frm_ClientesDestinosEditor, uValidacionFiscal, frm_ClientesPrecios,
  frm_PresupuestosEditor, frm_PedidosEditor, frm_AlbaranesEditor, frm_FacturasEditor, frm_AlbaranEnvioEditor,
  frm_DocumentosRelacionados, System.DateUtils;

{$R *.dfm}

function SanitizeString(const AStr: string): string;
var
  LPos: Integer;
begin
  LPos := Pos(#0, AStr);
  if LPos > 0 then
    Result := Copy(AStr, 1, LPos - 1)
  else
    Result := AStr;
  Result := Trim(Result);
end;

procedure TfrmClientesEditor.FormShow(Sender: TObject);
begin
  // Empty event maintained for DFM compatibility.
  // Initialization logic was moved to DoShow.
end;

procedure TfrmClientesEditor.DoShow;
var
  Y: Integer;
begin
  inherited DoShow;

  // Cargar combo de familias en Filtro de Suministrados si está vacío
  if cbbFiltroFamilia.Items.Count = 0 then
  begin
    with TFDQuery.Create(nil) do
    try
      Connection := dmgMain.dbConn;
      SQL.Text := 'SELECT descripcion FROM ge_familias WHERE activo = 1 ORDER BY descripcion';
      Open;
      cbbFiltroFamilia.Items.Add('Todas');
      while not EOF do
      begin
        cbbFiltroFamilia.Items.Add(FieldByName('descripcion').AsString);
        Next;
      end;
      cbbFiltroFamilia.ItemIndex := 0;
    finally
      Free;
    end;
  end;

  // Cargar combo de ejercicios en Filtro de Documentos si está vacío
  if cbbFiltroDocEjercicio.Items.Count <= 1 then
  begin
    cbbFiltroDocEjercicio.Items.Clear;
    cbbFiltroDocEjercicio.Items.Add('[Todos]');
    for Y := YearOf(Date) downto YearOf(Date) - 5 do
      cbbFiltroDocEjercicio.Items.Add(IntToStr(Y));
    cbbFiltroDocEjercicio.ItemIndex := 0;
  end;

  // Tema estándar
  TAppTheme.ApplyToForm(Self);

  // Vincular eventos a la barra superior e inferior heredadas
  btnGuardar.OnClick := btnSaveClick;
  btnSalir.OnClick := btnSalirClick;
  btnNuevoArticulo.OnClick := btnAddDestinoClick;
  btnEliminaArticulo.OnClick := btnDelDestinoClick;
  btnIA.Caption := 'Precios';
  btnIA.Visible := True;
  btnIA.OnClick := btnPreciosClick;

  // Vincular la grilla heredada al DataSource de destinos
  dbgItems.DataSource := dsDestinos;
  dbgItems.OnDblClick := dbgItemsDblClick;
  qryDestinos.CachedUpdates := False;

  // Configurar visibilidad de componentes heredados
  TsListado.TabVisible := False;
  pgcDetails.ActivePage := tsDatosEnvio;
  pgcDetails.OnChange := pgcDetailsChange;

  // Configurar el código de cliente como visible y de solo lectura
  lblCodigoCliente.Visible := True;
  edtCodigoCliente.Visible := True;
  edtCodigoCliente.ReadOnly := True;
  edtCodigoCliente.Color := clBtnFace;

  // Configurar columnas de la grilla de destinos (sucursales)
  dbgItems.Columns.Clear;
  with dbgItems.Columns.Add do
  begin
    FieldName := 'departamento';
    Title.Caption := 'Dept.';
    Width := 60;
  end;
  with dbgItems.Columns.Add do
  begin
    FieldName := 'centro';
    Title.Caption := 'Centro';
    Width := 60;
  end;
  with dbgItems.Columns.Add do
  begin
    FieldName := 'descripcion';
    Title.Caption := 'Nombre Sucursal';
    Width := 180;
  end;
  with dbgItems.Columns.Add do
  begin
    FieldName := 'direccion';
    Title.Caption := 'Dirección';
    Width := 200;
  end;
  with dbgItems.Columns.Add do
  begin
    FieldName := 'poblacion';
    Title.Caption := 'Población';
    Width := 130;
  end;
  with dbgItems.Columns.Add do
  begin
    FieldName := 'cp';
    Title.Caption := 'C.P.';
    Width := 55;
  end;
  with dbgItems.Columns.Add do
  begin
    FieldName := 'provincia';
    Title.Caption := 'Provincia';
    Width := 110;
  end;
  with dbgItems.Columns.Add do
  begin
    FieldName := 'ean';
    Title.Caption := 'EAN Sucursal / Entrega';
    Width := 130;
  end;
  with dbgItems.Columns.Add do
  begin
    FieldName := 'ean_cliente';
    Title.Caption := 'EAN Cliente (Comprador)';
    Width := 130;
  end;
  with dbgItems.Columns.Add do
  begin
    FieldName := 'ean_receptor';
    Title.Caption := 'EAN Receptor (Factura)';
    Width := 130;
  end;
  with dbgItems.Columns.Add do
  begin
    FieldName := 'activo';
    Title.Caption := 'Activo';
    Width := 55;
  end;

  // Configurar eventos de selección de la grilla de productos suministrados
  dbgProductosSuministrados.OnMouseUp := dbgProductosSuministradosMouseUp;
  dbgProductosSuministrados.OnKeyUp := dbgProductosSuministradosKeyUp;

  // Configurar columnas de la grilla de productos suministrados
  dbgProductosSuministrados.Columns.Clear;
  with dbgProductosSuministrados.Columns.Add do
  begin
    FieldName := 'codigo';
    Title.Caption := 'Código';
    Width := 40;
  end;
  with dbgProductosSuministrados.Columns.Add do
  begin
    FieldName := 'descripcion';
    Title.Caption := 'Descripción';
    Width := 300;
  end;
  with dbgProductosSuministrados.Columns.Add do
  begin
    FieldName := 'periodo';
    Title.Caption := 'Mes/Año';
    Width := 80;
  end;
  with dbgProductosSuministrados.Columns.Add do
  begin
    FieldName := 'cantidad';
    Title.Caption := 'Cantidad Suministrada';
    Width := 40;
  end;
  with dbgProductosSuministrados.Columns.Add do
  begin
    FieldName := 'precio_medio';
    Title.Caption := 'Precio Medio';
    Width := 40;
  end;
 { with dbgProductosSuministrados.Columns.Add do
  begin
    FieldName := 'ultima_fecha';
    Title.Caption := 'Último Suministro';
    Width := 140;
  end;}

  if FClienteId <> '' then
    LoadData
  else
    DoAnadir;

  ResetChangeTracking;
end;

procedure TfrmClientesEditor.LoadGrupos;
var
  Qry: TFDQuery;
begin
  if not Assigned(FGrupoIds) then
    FGrupoIds := TStringList.Create
  else
    FGrupoIds.Clear;

  cbGrupoCliente.Items.Clear;
  cbGrupoCliente.Items.Add('[Ninguno]');
  FGrupoIds.Add('');

  Qry := TFDQuery.Create(nil);
  try
    Qry.Connection := dmgMain.dbConn;
    Qry.SQL.Text := 'SELECT id, nombre FROM ge_clientes_grupos WHERE activo = 1 ORDER BY nombre';
    Qry.Open;
    while not Qry.Eof do
    begin
      cbGrupoCliente.Items.Add(Qry.FieldByName('nombre').AsString);
      FGrupoIds.Add(Qry.FieldByName('id').AsString);
      Qry.Next;
    end;
  finally
    Qry.Free;
  end;
  cbGrupoCliente.ItemIndex := 0;
end;

procedure TfrmClientesEditor.LoadFormasPago;
var
  Qry: TFDQuery;
begin
  if not Assigned(FFormaPagoIds) then
    FFormaPagoIds := TStringList.Create
  else
    FFormaPagoIds.Clear;

  cbFormaPago.Items.Clear;
  cbFormaPago.Items.Add('[Ninguna]');
  FFormaPagoIds.Add('');

  Qry := TFDQuery.Create(nil);
  try
    Qry.Connection := dmgMain.dbConn;
    Qry.SQL.Text := 'SELECT id, descripcion FROM ge_formas_pago WHERE activo = 1 ORDER BY descripcion';
    Qry.Open;
    while not Qry.Eof do
    begin
      cbFormaPago.Items.Add(Qry.FieldByName('descripcion').AsString);
      FFormaPagoIds.Add(Qry.FieldByName('id').AsString);
      Qry.Next;
    end;
  finally
    Qry.Free;
  end;
  cbFormaPago.ItemIndex := 0;
end;

procedure TfrmClientesEditor.FormDestroy(Sender: TObject);
begin
  if Assigned(FGrupoIds) then
    FreeAndNil(FGrupoIds);
  if Assigned(FFormaPagoIds) then
    FreeAndNil(FFormaPagoIds);
end;

procedure TfrmClientesEditor.LoadData;
var
  Qry: TFDQuery;
  LGrupoId: string;
  LFormaPagoId: string;
  LIdx: Integer;
begin
  LoadGrupos;
  LoadFormasPago;

  if FClienteId = '' then Exit;

  // Cargar datos de cabecera del cliente
  Qry := TFDQuery.Create(nil);
  try
    Qry.Connection := dmgMain.dbConn;
    Qry.SQL.Text := 'SELECT * FROM ge_clientes WHERE id = :id';
    Qry.ParamByName('id').AsString := FClienteId;
    Qry.Open;
    
    if not Qry.IsEmpty then
    begin
      edtCodigoCliente.Text := Qry.FieldByName('id').AsString;
      edtNIF.Text := SanitizeString(Qry.FieldByName('nif').AsString);
      if Qry.FindField('ean') <> nil then
        edtEanCliente.Text := SanitizeString(Qry.FieldByName('ean').AsString)
      else
        edtEanCliente.Clear;
      edtNombreFiscal.Text := SanitizeString(Qry.FieldByName('nombre_fiscal').AsString);
      edtNombreComercial.Text := SanitizeString(Qry.FieldByName('nombre_comercial').AsString);
      edtDomicilio.Text := SanitizeString(Qry.FieldByName('domicilio').AsString);
      edtPoblacion.Text := SanitizeString(Qry.FieldByName('poblacion').AsString);
      edtCodigoPostal.Text := SanitizeString(Qry.FieldByName('codigo_postal').AsString);
      edtProvincia.Text := SanitizeString(Qry.FieldByName('provincia').AsString);
      edtTelefono.Text := SanitizeString(Qry.FieldByName('telefono').AsString);
      edtEmail.Text := SanitizeString(Qry.FieldByName('email').AsString);
      chkActivo.Checked := Qry.FieldByName('activo').AsInteger <> 0;
      edtCredito.Text := FormatFloat('#,##0.00', Qry.FieldByName('credito').AsFloat);
      edtPorcentajeBloqueo.Text := FormatFloat('0.00', Qry.FieldByName('porcentaje_bloqueo').AsFloat);
      chkBloquear.Checked := Qry.FieldByName('bloquear').AsInteger <> 0;
      chkAsegurado.Checked := (Qry.FindField('asegurado') <> nil) and (Qry.FieldByName('asegurado').AsInteger <> 0);

      LGrupoId := Qry.FieldByName('clientesgrupo_id').AsString;
      if (LGrupoId <> '') and Assigned(FGrupoIds) then
      begin
        LIdx := FGrupoIds.IndexOf(LGrupoId);
        if LIdx >= 0 then
          cbGrupoCliente.ItemIndex := LIdx
        else
          cbGrupoCliente.ItemIndex := 0;
      end
      else
        cbGrupoCliente.ItemIndex := 0;

      LFormaPagoId := Qry.FieldByName('id_forma_pago').AsString;
      if (LFormaPagoId <> '') and Assigned(FFormaPagoIds) then
      begin
        LIdx := FFormaPagoIds.IndexOf(LFormaPagoId);
        if LIdx >= 0 then
          cbFormaPago.ItemIndex := LIdx
        else
          cbFormaPago.ItemIndex := 0;
      end
      else
        cbFormaPago.ItemIndex := 0;
    end;
  finally
    Qry.Free;
  end;

  // Cargar destinos (sucursales) del cliente
  qryDestinos.Close;
  qryDestinos.SQL.Text :=
    'SELECT id, id_cliente, descripcion, direccion, poblacion, cp, ' +
    'provincia, pais, activo, departamento, centro, ' +
    'ean_facturacion, ean_cliente, ean_emisor, ean_receptor ' +
    'FROM ge_clientes_destinos ' +
    'WHERE id_cliente = :id_cliente ' +
    'ORDER BY descripcion';
  qryDestinos.ParamByName('id_cliente').AsString := FClienteId;
  qryDestinos.Open;
  // Cargar productos suministrados de forma perezosa (solo si la pestaña está activa)
  FSuministradosLoaded := False;
  if pgcDetails.ActivePage = tsProductosSuministrados then
    RefrescarSuministrados(nil);

  // Cargar documentos relacionados de forma perezosa
  FDocumentosLoaded := False;
  if pgcDetails.ActivePage = tsDocumentosRelacionados then
    RefrescarDocumentosRelacionados(nil);
end;

procedure TfrmClientesEditor.DoAnadir;
begin
  LoadGrupos;
  LoadFormasPago;
  FClienteId := '';
  FSuministradosLoaded := False;
  FDocumentosLoaded := False;
  qryDocumentosRelacionados.Close;
  edtCodigoCliente.Text := '(Autonumérico)';
  edtNIF.Clear;
  edtEanCliente.Clear;
  edtNombreFiscal.Clear;
  edtNombreComercial.Clear;
  edtDomicilio.Clear;
  edtPoblacion.Clear;
  edtCodigoPostal.Clear;
  edtProvincia.Clear;
  edtTelefono.Clear;
  edtEmail.Clear;
  chkActivo.Checked := True;
  edtCredito.Text := '0,00';
  edtPorcentajeBloqueo.Text := '0,00';
  chkBloquear.Checked := False;
  chkAsegurado.Checked := False;
  cbGrupoCliente.ItemIndex := 0;
  cbFormaPago.ItemIndex := 0;

  // Abrir query de destinos vacío
  qryDestinos.Close;
  qryDestinos.SQL.Text :=
    'SELECT id, id_cliente, descripcion, direccion, poblacion, cp, ' +
    'provincia, pais, activo, departamento, centro, ' +
    'ean_facturacion, ean_cliente, ean_emisor, ean_receptor ' +
    'FROM ge_clientes_destinos ' +
    'WHERE id_cliente = :id_cliente';
  qryDestinos.ParamByName('id_cliente').AsString := '0';
  qryDestinos.Open;

  // Cargar productos suministrados vacío si la pestaña está visible
  if pgcDetails.ActivePage = tsProductosSuministrados then
  begin
    qryProductosSuministrados.Close;
    qryProductosSuministrados.ParamByName('cliente_id').AsString := '0';
    qryProductosSuministrados.Open;
  end;
  
  if qryProductosSuministrados.FindField('cantidad') <> nil then
    TNumericField(qryProductosSuministrados.FieldByName('cantidad')).DisplayFormat := '#,##0.00';
  if qryProductosSuministrados.FindField('precio_medio') <> nil then
    TNumericField(qryProductosSuministrados.FieldByName('precio_medio')).DisplayFormat := '#,##0.00 €';

  edtNombreFiscal.SetFocus;
end;

procedure TfrmClientesEditor.DoModificar;
begin
  edtNombreFiscal.SetFocus;
end;

procedure TfrmClientesEditor.DoPrimero;
var
  LParent: TfrmClientes;
begin
  LParent := nil;
  if Assigned(FParentForm) and (FParentForm is TfrmClientes) then
    LParent := TfrmClientes(FParentForm)
  else if Assigned(frmClientes) then
    LParent := frmClientes;

  if Assigned(LParent) and Assigned(LParent.QryMain) and not LParent.QryMain.IsEmpty then
  begin
    LParent.QryMain.First;
    FClienteId := LParent.QryMain.FieldByName('id').AsString;
    LoadData;
  end;
end;

procedure TfrmClientesEditor.DoAnterior;
var
  LParent: TfrmClientes;
begin
  LParent := nil;
  if Assigned(FParentForm) and (FParentForm is TfrmClientes) then
    LParent := TfrmClientes(FParentForm)
  else if Assigned(frmClientes) then
    LParent := frmClientes;

  if Assigned(LParent) and Assigned(LParent.QryMain) and not LParent.QryMain.IsEmpty then
  begin
    LParent.QryMain.Prior;
    FClienteId := LParent.QryMain.FieldByName('id').AsString;
    LoadData;
  end;
end;

procedure TfrmClientesEditor.DoSiguiente;
var
  LParent: TfrmClientes;
begin
  LParent := nil;
  if Assigned(FParentForm) and (FParentForm is TfrmClientes) then
    LParent := TfrmClientes(FParentForm)
  else if Assigned(frmClientes) then
    LParent := frmClientes;

  if Assigned(LParent) and Assigned(LParent.QryMain) and not LParent.QryMain.IsEmpty then
  begin
    LParent.QryMain.Next;
    FClienteId := LParent.QryMain.FieldByName('id').AsString;
    LoadData;
  end;
end;

procedure TfrmClientesEditor.DoUltimo;
var
  LParent: TfrmClientes;
begin
  LParent := nil;
  if Assigned(FParentForm) and (FParentForm is TfrmClientes) then
    LParent := TfrmClientes(FParentForm)
  else if Assigned(frmClientes) then
    LParent := frmClientes;

  if Assigned(LParent) and Assigned(LParent.QryMain) and not LParent.QryMain.IsEmpty then
  begin
    LParent.QryMain.Last;
    FClienteId := LParent.QryMain.FieldByName('id').AsString;
    LoadData;
  end;
end;

{ --- Destinos (Sucursales) --- }

procedure TfrmClientesEditor.btnAddDestinoClick(Sender: TObject);
var
  LFrm: TfrmClientesDestinosEditor;
begin
  if FClienteId = '' then
  begin
    ShowMessage('Debe guardar los datos del cliente primero antes de añadir sucursales/destinos.');
    Exit;
  end;

  // Abrir editor de sucursales en modo inserción
  LFrm := TfrmClientesDestinosEditor.Create(Self);
  try
    qryDestinos.Append;
    qryDestinos.FieldByName('id_cliente').AsString := FClienteId;
    qryDestinos.FieldByName('activo').AsInteger := 1;
    
    LFrm.DataSet := qryDestinos;
    LFrm.IsNew := True;
    LFrm.ClienteId := FClienteId;
    
    if LFrm.ShowModal = mrOk then
    begin
      try
        dmgMain.qryExec.Close;
        dmgMain.qryExec.SQL.Text :=
          'INSERT INTO ge_clientes_destinos (id_cliente, descripcion, direccion, ' +
          'poblacion, cp, provincia, pais, activo, departamento, centro, ' +
          'ean_facturacion, ean_cliente, ean_emisor, ean_receptor, id_user_creator) ' +
          'VALUES (:id_cliente, :desc, :dir, :pob, :cp, :prov, :pais, :act, :dept, :centro, ' +
          ':ean_fac, :ean_cli, :ean_emi, :ean_rec, :user)';

        dmgMain.qryExec.ParamByName('id_cliente').AsString := FClienteId;
        dmgMain.qryExec.ParamByName('desc').AsString := qryDestinos.FieldByName('descripcion').AsString;
        dmgMain.qryExec.ParamByName('dir').AsString := qryDestinos.FieldByName('direccion').AsString;
        dmgMain.qryExec.ParamByName('pob').AsString := qryDestinos.FieldByName('poblacion').AsString;
        dmgMain.qryExec.ParamByName('cp').AsString := qryDestinos.FieldByName('cp').AsString;
        dmgMain.qryExec.ParamByName('prov').AsString := qryDestinos.FieldByName('provincia').AsString;
        dmgMain.qryExec.ParamByName('pais').AsString := qryDestinos.FieldByName('pais').AsString;
        dmgMain.qryExec.ParamByName('act').AsBoolean := qryDestinos.FieldByName('activo').AsInteger <> 0;
        dmgMain.qryExec.ParamByName('dept').AsString := qryDestinos.FieldByName('departamento').AsString;
        dmgMain.qryExec.ParamByName('centro').AsString := qryDestinos.FieldByName('centro').AsString;
        dmgMain.qryExec.ParamByName('ean_fac').AsString := qryDestinos.FieldByName('ean_facturacion').AsString;
        dmgMain.qryExec.ParamByName('ean_cli').AsString := qryDestinos.FieldByName('ean_cliente').AsString;
        dmgMain.qryExec.ParamByName('ean_emi').AsString := qryDestinos.FieldByName('ean_emisor').AsString;
        dmgMain.qryExec.ParamByName('ean_rec').AsString := qryDestinos.FieldByName('ean_receptor').AsString;
        dmgMain.qryExec.ParamByName('user').AsString := dmgMain.CurrentUserId;
        dmgMain.qryExec.ExecSQL;
        
        LoadData; // Refrescar consulta
      except
        on E: Exception do
          ShowMessage('Error al insertar el destino: ' + E.Message);
      end;
    end
    else
    begin
      qryDestinos.Cancel;
    end;
  finally
    LFrm.Free;
  end;
end;

procedure TfrmClientesEditor.dbgItemsDblClick(Sender: TObject);
var
  LDestinoId: string;
  LFrm: TfrmClientesDestinosEditor;
begin
  if qryDestinos.IsEmpty then Exit;

  LDestinoId := qryDestinos.FieldByName('id').AsString;

  LFrm := TfrmClientesDestinosEditor.Create(Self);
  try
    qryDestinos.Edit;
    
    LFrm.DataSet := qryDestinos;
    LFrm.IsNew := False;
    LFrm.ClienteId := FClienteId;
    
    if LFrm.ShowModal = mrOk then
    begin
      try
        dmgMain.qryExec.Close;
        dmgMain.qryExec.SQL.Text :=
          'UPDATE ge_clientes_destinos SET descripcion = :desc, direccion = :dir, ' +
          'poblacion = :pob, cp = :cp, provincia = :prov, pais = :pais, activo = :act, ' +
          'departamento = :dept, centro = :centro, ' +
          'ean_facturacion = :ean_fac, ean_cliente = :ean_cli, ean_emisor = :ean_emi, ean_receptor = :ean_rec, ' +
          'id_user_update = :user, updated_at = CURRENT_TIMESTAMP ' +
          'WHERE id = :id';

        dmgMain.qryExec.ParamByName('id').AsString := LDestinoId;
        dmgMain.qryExec.ParamByName('desc').AsString := qryDestinos.FieldByName('descripcion').AsString;
        dmgMain.qryExec.ParamByName('dir').AsString := qryDestinos.FieldByName('direccion').AsString;
        dmgMain.qryExec.ParamByName('pob').AsString := qryDestinos.FieldByName('poblacion').AsString;
        dmgMain.qryExec.ParamByName('cp').AsString := qryDestinos.FieldByName('cp').AsString;
        dmgMain.qryExec.ParamByName('prov').AsString := qryDestinos.FieldByName('provincia').AsString;
        dmgMain.qryExec.ParamByName('pais').AsString := qryDestinos.FieldByName('pais').AsString;
        dmgMain.qryExec.ParamByName('act').AsBoolean := qryDestinos.FieldByName('activo').AsInteger <> 0;
        dmgMain.qryExec.ParamByName('dept').AsString := qryDestinos.FieldByName('departamento').AsString;
        dmgMain.qryExec.ParamByName('centro').AsString := qryDestinos.FieldByName('centro').AsString;
        dmgMain.qryExec.ParamByName('ean_fac').AsString := qryDestinos.FieldByName('ean_facturacion').AsString;
        dmgMain.qryExec.ParamByName('ean_cli').AsString := qryDestinos.FieldByName('ean_cliente').AsString;
        dmgMain.qryExec.ParamByName('ean_emi').AsString := qryDestinos.FieldByName('ean_emisor').AsString;
        dmgMain.qryExec.ParamByName('ean_rec').AsString := qryDestinos.FieldByName('ean_receptor').AsString;
        dmgMain.qryExec.ParamByName('user').AsString := dmgMain.CurrentUserId;
        dmgMain.qryExec.ExecSQL;
        
        LoadData; // Refrescar consulta
      except
        on E: Exception do
          ShowMessage('Error al actualizar el destino: ' + E.Message);
      end;
    end
    else
    begin
      qryDestinos.Cancel;
    end;
  finally
    LFrm.Free;
  end;
end;

procedure TfrmClientesEditor.btnDelDestinoClick(Sender: TObject);
var
  LDestinoId: string;
begin
  if qryDestinos.IsEmpty then Exit;
  
  if MessageDlg('¿Está seguro de que desea eliminar esta sucursal/destino?',
    mtConfirmation, [mbYes, mbNo], 0) = mrYes then
  begin
    LDestinoId := qryDestinos.FieldByName('id').AsString;
    try
      dmgMain.qryExec.Close;
      dmgMain.qryExec.SQL.Text := 'DELETE FROM ge_clientes_destinos WHERE id = :id';
      dmgMain.qryExec.ParamByName('id').AsString := LDestinoId;
      dmgMain.qryExec.ExecSQL;
      
      LoadData;
    except
      on E: Exception do
        ShowMessage('Error al eliminar el destino: ' + E.Message);
    end;
  end;
end;

procedure TfrmClientesEditor.btnSalirClick(Sender: TObject);
begin
  Close;
end;

procedure TfrmClientesEditor.btnSaveClick(Sender: TObject);
var
  LIsNew: Boolean;
  LTipoDoc: TTipoDocumento;
  LNif: string;
begin
  // Validaciones de campo obligatorio
  if Trim(edtNombreFiscal.Text) = '' then
  begin
    ShowMessage('Debe introducir el nombre fiscal del cliente.');
    edtNombreFiscal.SetFocus;
    Exit;
  end;

  // Validación del dígito de control del NIF (solo si se ha introducido, no es obligatorio)
  LNif := Trim(edtNIF.Text);
  if LNif <> '' then
  begin
    if not TValidacionFiscal.ValidarDocumento(LNif, LTipoDoc) then
    begin
      if MessageDlg(
        'El NIF/CIF "' + LNif + '" no supera la validación del dígito de control.' + sLineBreak +
        '¿Desea guardarlo igualmente?',
        mtWarning, [mbYes, mbNo], 0) = mrNo then
      begin
        edtNIF.SetFocus;
        Exit;
      end;
    end;
  end;

  LIsNew := (FClienteId = '');

  dmgMain.dbConn.StartTransaction;
  try
    // 1. Persistir Cabecera (cliente)
    dmgMain.qryExec.Close;
    if LIsNew then
    begin
      dmgMain.qryExec.SQL.Text := 
        'INSERT INTO ge_clientes (nif, ean, nombre_fiscal, nombre_comercial, ' +
        'domicilio, poblacion, codigo_postal, provincia, telefono, email, activo, credito, porcentaje_bloqueo, bloquear, asegurado, clientesgrupo_id, id_forma_pago, id_user_creator) ' +
        'VALUES (:nif, :ean_cli, :nf, :nc, :dom, :pob, :cp, :prov, :tel, :email, :act, :credito, :porc_bloqueo, :bloquear, :asegurado, :grupo_id, :id_forma_pago, :user)';
    end
    else
    begin
      dmgMain.qryExec.SQL.Text := 
        'UPDATE ge_clientes SET nif = :nif, ean = :ean_cli, nombre_fiscal = :nf, ' +
        'nombre_comercial = :nc, domicilio = :dom, poblacion = :pob, codigo_postal = :cp, ' +
        'provincia = :prov, telefono = :tel, email = :email, activo = :act, ' +
        'credito = :credito, porcentaje_bloqueo = :porc_bloqueo, bloquear = :bloquear, asegurado = :asegurado, ' +
        'clientesgrupo_id = :grupo_id, id_forma_pago = :id_forma_pago, ' +
        'id_user_update = :user, updated_at = CURRENT_TIMESTAMP ' +
        'WHERE id = :id';
    end;

    if not LIsNew then
    begin
      dmgMain.qryExec.ParamByName('id').DataType := ftWideString;
      dmgMain.qryExec.ParamByName('id').Value := FClienteId;
    end;
    dmgMain.qryExec.ParamByName('nif').DataType := ftWideString;
    dmgMain.qryExec.ParamByName('nif').Value := SanitizeString(edtNIF.Text);
    dmgMain.qryExec.ParamByName('ean_cli').DataType := ftWideString;
    dmgMain.qryExec.ParamByName('ean_cli').Value := SanitizeString(edtEanCliente.Text);
    dmgMain.qryExec.ParamByName('nf').DataType := ftWideString;
    dmgMain.qryExec.ParamByName('nf').Value := SanitizeString(edtNombreFiscal.Text);
    dmgMain.qryExec.ParamByName('nc').DataType := ftWideString;
    dmgMain.qryExec.ParamByName('nc').Value := SanitizeString(edtNombreComercial.Text);
    dmgMain.qryExec.ParamByName('dom').DataType := ftWideString;
    dmgMain.qryExec.ParamByName('dom').Value := SanitizeString(edtDomicilio.Text);
    dmgMain.qryExec.ParamByName('pob').DataType := ftWideString;
    dmgMain.qryExec.ParamByName('pob').Value := SanitizeString(edtPoblacion.Text);
    dmgMain.qryExec.ParamByName('cp').DataType := ftWideString;
    dmgMain.qryExec.ParamByName('cp').Value := SanitizeString(edtCodigoPostal.Text);
    dmgMain.qryExec.ParamByName('prov').DataType := ftWideString;
    dmgMain.qryExec.ParamByName('prov').Value := SanitizeString(edtProvincia.Text);
    dmgMain.qryExec.ParamByName('tel').DataType := ftWideString;
    dmgMain.qryExec.ParamByName('tel').Value := SanitizeString(edtTelefono.Text);
    dmgMain.qryExec.ParamByName('email').DataType := ftWideString;
    dmgMain.qryExec.ParamByName('email').Value := SanitizeString(edtEmail.Text);
    dmgMain.qryExec.ParamByName('act').AsBoolean := chkActivo.Checked;
    
    // Asignar grupo de cliente
    dmgMain.qryExec.ParamByName('grupo_id').DataType := ftInteger;
    if (cbGrupoCliente.ItemIndex > 0) and Assigned(FGrupoIds) and (cbGrupoCliente.ItemIndex < FGrupoIds.Count) and (FGrupoIds[cbGrupoCliente.ItemIndex] <> '') then
      dmgMain.qryExec.ParamByName('grupo_id').AsInteger := StrToIntDef(FGrupoIds[cbGrupoCliente.ItemIndex], 0)
    else
      dmgMain.qryExec.ParamByName('grupo_id').Clear;

    // Asignar forma de pago
    dmgMain.qryExec.ParamByName('id_forma_pago').DataType := ftInteger;
    if (cbFormaPago.ItemIndex > 0) and Assigned(FFormaPagoIds) and (cbFormaPago.ItemIndex < FFormaPagoIds.Count) and (FFormaPagoIds[cbFormaPago.ItemIndex] <> '') then
      dmgMain.qryExec.ParamByName('id_forma_pago').AsInteger := StrToIntDef(FFormaPagoIds[cbFormaPago.ItemIndex], 0)
    else
      dmgMain.qryExec.ParamByName('id_forma_pago').Clear;
    
    // Asignar parámetros numéricos nuevos
    dmgMain.qryExec.ParamByName('credito').AsFloat := StrToFloatDef(Trim(edtCredito.Text).Replace('.', '').Replace(',', '.'), 0);
    
    if Trim(edtPorcentajeBloqueo.Text) <> '' then
      dmgMain.qryExec.ParamByName('porc_bloqueo').AsFloat := StrToFloatDef(Trim(edtPorcentajeBloqueo.Text).Replace('.', '').Replace(',', '.'), 0)
    else
      dmgMain.qryExec.ParamByName('porc_bloqueo').Clear;
      
    dmgMain.qryExec.ParamByName('bloquear').AsBoolean := chkBloquear.Checked;
    dmgMain.qryExec.ParamByName('asegurado').AsInteger := Ord(chkAsegurado.Checked);
    dmgMain.qryExec.ParamByName('user').AsString := dmgMain.CurrentUserId;
    
    dmgMain.qryExec.ExecSQL;

    if LIsNew then
    begin
      dmgMain.qryExec.Close;
      dmgMain.qryExec.SQL.Text := 'SELECT LAST_INSERT_ID() as new_id';
      dmgMain.qryExec.Open;
      FClienteId := dmgMain.qryExec.FieldByName('new_id').AsString;
      edtCodigoCliente.Text := FClienteId;
    end;

    dmgMain.dbConn.Commit;
    ResetChangeTracking;

    if Assigned(FParentForm) and (FParentForm is TfrmClientes) then
      TfrmClientes(FParentForm).btnRefreshClick(nil);

    ShowMessage('Cliente guardado correctamente.');
  except
    on E: Exception do
    begin
      dmgMain.dbConn.Rollback;
      TDbErrorHandler.HandleException(E, 'Error al guardar el cliente');
    end;
  end;
end;

procedure TfrmClientesEditor.pgcDetailsChange(Sender: TObject);
begin
  if (pgcDetails.ActivePage = tsProductosSuministrados) and not FSuministradosLoaded and (FClienteId <> '') then
  begin
    RefrescarSuministrados(nil);
  end
  else if (pgcDetails.ActivePage = tsDocumentosRelacionados) and not FDocumentosLoaded and (FClienteId <> '') then
  begin
    RefrescarDocumentosRelacionados(nil);
  end;
end;

procedure TfrmClientesEditor.RefrescarSuministrados(Sender: TObject);
var
  LSelect, LSelectAlbaranes, LSelectFacturas: string;
  LGroup, LOrder: string;
  LEmpresaJoin, LEmpresaWhere: string;
  LWhereCond: string;
begin
  if FClienteId = '' then
  begin
    qryProductosSuministrados.Close;
    Exit;
  end;
  
  FSuministradosLoaded := True;
  qryProductosSuministrados.Close;
  
  LSelect := 'SELECT MAX(codigo) AS codigo, familia, descripcion, precio_neto, ';
  LSelectAlbaranes := 'SELECT CONVERT(COALESCE(al.articulo_codigo, '''') USING utf8mb4) COLLATE utf8mb4_general_ci AS codigo, ' +
                      'CONVERT(COALESCE(fam.descripcion, '''') USING utf8mb4) COLLATE utf8mb4_general_ci AS familia, ' +
                      'CONVERT(COALESCE(al.descripcion, '''') USING utf8mb4) COLLATE utf8mb4_general_ci AS descripcion, ' +
                      'al.cantidad AS cantidad, (al.precio - COALESCE(al.descuento_precio, 0)) AS precio_neto, a.fecha AS fecha ';
  LSelectFacturas := 'SELECT CONVERT(COALESCE(fl.articulo_codigo, '''') USING utf8mb4) COLLATE utf8mb4_general_ci AS codigo, ' +
                     'CONVERT(COALESCE(fam.descripcion, '''') USING utf8mb4) COLLATE utf8mb4_general_ci AS familia, ' +
                     'CONVERT(COALESCE(fl.descripcion, '''') USING utf8mb4) COLLATE utf8mb4_general_ci AS descripcion, ' +
                     'fl.cantidad AS cantidad, (fl.precio - COALESCE(fl.descuento_cant, 0)) AS precio_neto, f.fecha AS fecha ';
  
  LGroup := 'GROUP BY familia, descripcion, precio_neto';
  
  // Agrupación por fecha
  case rgAgrupacionSuministrados.ItemIndex of
    0: // Totales
      begin
        LOrder := 'ORDER BY familia ASC, descripcion ASC, precio_neto ASC';
      end;
    1: // Por Año
      begin
        LSelect := LSelect + 'periodo, periodo_orden, ';
        LSelectAlbaranes := LSelectAlbaranes + ', CONVERT(DATE_FORMAT(a.fecha, ''%Y'') USING utf8mb4) COLLATE utf8mb4_general_ci AS periodo_orden, CONVERT(DATE_FORMAT(a.fecha, ''%Y'') USING utf8mb4) COLLATE utf8mb4_general_ci AS periodo ';
        LSelectFacturas := LSelectFacturas + ', CONVERT(DATE_FORMAT(f.fecha, ''%Y'') USING utf8mb4) COLLATE utf8mb4_general_ci AS periodo_orden, CONVERT(DATE_FORMAT(f.fecha, ''%Y'') USING utf8mb4) COLLATE utf8mb4_general_ci AS periodo ';
        LGroup := LGroup + ', periodo, periodo_orden';
        LOrder := 'ORDER BY periodo_orden DESC, familia ASC, descripcion ASC, precio_neto ASC';
      end;
    2: // Por Mes y Año
      begin
        LSelect := LSelect + 'periodo, periodo_orden, ';
        LSelectAlbaranes := LSelectAlbaranes + ', CONVERT(DATE_FORMAT(a.fecha, ''%Y-%m'') USING utf8mb4) COLLATE utf8mb4_general_ci AS periodo_orden, CONVERT(DATE_FORMAT(a.fecha, ''%m/%Y'') USING utf8mb4) COLLATE utf8mb4_general_ci AS periodo ';
        LSelectFacturas := LSelectFacturas + ', CONVERT(DATE_FORMAT(f.fecha, ''%Y-%m'') USING utf8mb4) COLLATE utf8mb4_general_ci AS periodo_orden, CONVERT(DATE_FORMAT(f.fecha, ''%m/%Y'') USING utf8mb4) COLLATE utf8mb4_general_ci AS periodo ';
        LGroup := LGroup + ', periodo, periodo_orden';
        LOrder := 'ORDER BY periodo_orden DESC, familia ASC, descripcion ASC, precio_neto ASC';
      end;
  end;
  
  // Filtro por empresa
  if chkTodasEmpresasSuministrados.Checked then
  begin
    LSelect := StringReplace(LSelect, 'MAX(codigo) AS codigo,', 'empresa, MAX(codigo) AS codigo,', [rfReplaceAll]);
    LSelectAlbaranes := LSelectAlbaranes + ', CONVERT(COALESCE(e.nombre, '''') USING utf8mb4) COLLATE utf8mb4_general_ci AS empresa ';
    LSelectFacturas := LSelectFacturas + ', CONVERT(COALESCE(e.nombre, '''') USING utf8mb4) COLLATE utf8mb4_general_ci AS empresa ';
    
    LEmpresaJoin := 'INNER JOIN ge_empresas e ON {tabla}.empresa_id = e.id ';
    LEmpresaWhere := ''; // Mostramos todas
    
    LGroup := 'GROUP BY empresa, ' + Copy(LGroup, 10, Length(LGroup)); // Sustituye "GROUP BY " por "GROUP BY empresa, "
    LOrder := 'ORDER BY empresa ASC, ' + Copy(LOrder, 10, Length(LOrder));
  end
  else
  begin
    LEmpresaJoin := '';
    LEmpresaWhere := ' AND {tabla}.empresa_id = :empresa_id';
  end;
  
  // Completamos selects de tablas base
  LSelectAlbaranes := LSelectAlbaranes + ' FROM ge_albaranes_lineas al INNER JOIN ge_albaranes a ON al.albaran_id = a.id ' + 
                      'LEFT JOIN ge_articulos art ON al.articulo_id = art.id ' +
                      'LEFT JOIN ge_familias fam ON art.id_familia = fam.id ' +
                      StringReplace(LEmpresaJoin, '{tabla}', 'a', [rfReplaceAll]) + 
                      ' WHERE a.cliente_id = :cliente_id ' + 
                      StringReplace(LEmpresaWhere, '{tabla}', 'a', [rfReplaceAll]);
                      
  LSelectFacturas := LSelectFacturas + ' FROM ge_facturas_lineas fl INNER JOIN ge_facturas f ON fl.factura_id = f.id ' + 
                     'LEFT JOIN ge_articulos art ON fl.articulo_id = art.id ' +
                     'LEFT JOIN ge_familias fam ON art.id_familia = fam.id ' +
                     StringReplace(LEmpresaJoin, '{tabla}', 'f', [rfReplaceAll]) + 
                     ' WHERE f.cliente_id = :cliente_id ' + 
                     StringReplace(LEmpresaWhere, '{tabla}', 'f', [rfReplaceAll]);
  
  LWhereCond := ' WHERE 1=1';
  if Assigned(cbbFiltroFamilia) and (cbbFiltroFamilia.ItemIndex > 0) then
    LWhereCond := LWhereCond + ' AND IFNULL(familia, '''') = :filtro_fam';
    
  if Assigned(edtFiltroDescripcion) and (Trim(edtFiltroDescripcion.Text) <> '') then
    LWhereCond := LWhereCond + ' AND IFNULL(descripcion, '''') LIKE :filtro_desc';
  
  // Agregar cálculos de importes al select principal
  LSelect := LSelect + 
    'SUM(cantidad) AS cantidad, ' +
    'SUM(cantidad * precio_neto) / NULLIF(SUM(cantidad), 0) AS precio_medio, ' +
    'MAX(fecha) AS ultima_fecha ' +
    'FROM (' + LSelectAlbaranes + ' UNION ALL ' + LSelectFacturas + ') temp ' +
    LWhereCond + ' ' +
    LGroup + ' ' + LOrder;
    
  qryProductosSuministrados.SQL.Text := LSelect;
  qryProductosSuministrados.ParamByName('cliente_id').AsString := TdmgMain.CleanUUID(FClienteId);
  
  if not chkTodasEmpresasSuministrados.Checked then
    qryProductosSuministrados.ParamByName('empresa_id').AsString := dmgMain.CurrentCompanyId;
    
  if Assigned(cbbFiltroFamilia) and (cbbFiltroFamilia.ItemIndex > 0) then
    qryProductosSuministrados.ParamByName('filtro_fam').AsString := cbbFiltroFamilia.Text;
    
  if Assigned(edtFiltroDescripcion) and (Trim(edtFiltroDescripcion.Text) <> '') then
    qryProductosSuministrados.ParamByName('filtro_desc').AsString := '%' + Trim(edtFiltroDescripcion.Text) + '%';
    
  qryProductosSuministrados.Open;
  
  // Configurar títulos y visibilidad de columnas explícitamente
  dbgProductosSuministrados.Columns.Clear;
  
  with dbgProductosSuministrados.Columns.Add do
  begin
    FieldName := 'codigo';
    Title.Caption := 'Código';
    Width := 60;
  end;
  
  with dbgProductosSuministrados.Columns.Add do
  begin
    FieldName := 'descripcion';
    Title.Caption := 'Descripción';
    Width := 300;
  end;
  
  if qryProductosSuministrados.FindField('precio_neto') <> nil then
  begin
    with dbgProductosSuministrados.Columns.Add do
    begin
      FieldName := 'precio_neto';
      Title.Caption := 'Precio Medio';
      Width := 80;
    end;
    TNumericField(qryProductosSuministrados.FieldByName('precio_neto')).DisplayFormat := '#,##0.00 €';
  end;

  if rgAgrupacionSuministrados.ItemIndex > 0 then
  begin
    with dbgProductosSuministrados.Columns.Add do
    begin
      FieldName := 'periodo';
      if rgAgrupacionSuministrados.ItemIndex = 1 then
        Title.Caption := 'Año'
      else
        Title.Caption := 'Mes/Año';
      Width := 80;
    end;
  end;
  
  with dbgProductosSuministrados.Columns.Add do
  begin
    FieldName := 'cantidad';
    Title.Caption := 'Cantidad Suministrada';
    Width := 130;
  end;

  if qryProductosSuministrados.FindField('cantidad') <> nil then
    TNumericField(qryProductosSuministrados.FieldByName('cantidad')).DisplayFormat := '#,##0.00';
  
  CalcularTotalesSeleccion;
end;

procedure TfrmClientesEditor.RefrescarSuministradosKeyPress(Sender: TObject; var Key: Char);
begin
  if Key = #13 then // Enter
  begin
    Key := #0; // Consumir la tecla para que no suene
    RefrescarSuministrados(nil);
  end;
end;

procedure TfrmClientesEditor.dbgProductosSuministradosMouseUp(Sender: TObject; Button: TMouseButton; Shift: TShiftState; X, Y: Integer);
begin
  CalcularTotalesSeleccion;
end;

procedure TfrmClientesEditor.dbgProductosSuministradosKeyUp(Sender: TObject; var Key: Word; Shift: TShiftState);
begin
  CalcularTotalesSeleccion;
end;

procedure TfrmClientesEditor.CalcularTotalesSeleccion;
var
  i: Integer;
  LTotalCant, LTotalImporte: Double;
  LSavePlace: TBookmark;
begin
  LTotalCant := 0;
  LTotalImporte := 0;

  if dbgProductosSuministrados.SelectedRows.Count > 0 then
  begin
    LSavePlace := qryProductosSuministrados.GetBookmark;
    qryProductosSuministrados.DisableControls;
    try
      for i := 0 to dbgProductosSuministrados.SelectedRows.Count - 1 do
      begin
        try
          qryProductosSuministrados.GotoBookmark(dbgProductosSuministrados.SelectedRows[i]);
          if qryProductosSuministrados.FindField('cantidad') <> nil then
            LTotalCant := LTotalCant + qryProductosSuministrados.FieldByName('cantidad').AsFloat;
          if (qryProductosSuministrados.FindField('precio_medio') <> nil) and (qryProductosSuministrados.FindField('cantidad') <> nil) then
            LTotalImporte := LTotalImporte + (qryProductosSuministrados.FieldByName('cantidad').AsFloat * qryProductosSuministrados.FieldByName('precio_medio').AsFloat);
        except
        end;
      end;
    finally
      try qryProductosSuministrados.GotoBookmark(LSavePlace); except end;
      if qryProductosSuministrados.BookmarkValid(LSavePlace) then
        qryProductosSuministrados.FreeBookmark(LSavePlace);
      qryProductosSuministrados.EnableControls;
    end;
  end;

  if Assigned(lblTotalesSeleccion) then
    lblTotalesSeleccion.Caption := Format('Seleccionados: %d | Cantidad: %n | Importe: %n €',
      [dbgProductosSuministrados.SelectedRows.Count, LTotalCant, LTotalImporte]);
end;

procedure TfrmClientesEditor.btnPreciosClick(Sender: TObject);
begin
  if FClienteId = '' then
  begin
    ShowMessage('Debe guardar el cliente antes de registrar precios específicos.');
    Exit;
  end;

  if not Assigned(frmClientesPrecios) then
    Application.CreateForm(TfrmClientesPrecios, frmClientesPrecios);
    
  frmClientesPrecios.ClienteId := FClienteId;
  frmClientesPrecios.ClienteNombre := edtNombreFiscal.Text;
  frmClientesPrecios.ClienteEmail := edtEmail.Text;
  frmClientesPrecios.ClienteTelefono := edtTelefono.Text;
  if (cbGrupoCliente.ItemIndex > 0) and Assigned(FGrupoIds) and (cbGrupoCliente.ItemIndex < FGrupoIds.Count) then
  begin
    frmClientesPrecios.GrupoId := FGrupoIds[cbGrupoCliente.ItemIndex];
    frmClientesPrecios.GrupoNombre := cbGrupoCliente.Text;
  end
  else
  begin
    frmClientesPrecios.GrupoId := '';
    frmClientesPrecios.GrupoNombre := '';
  end;
  frmClientesPrecios.ShowModal;
end;

procedure TfrmClientesEditor.RefrescarDocumentosKeyPress(Sender: TObject; var Key: Char);
begin
  if Key = #13 then
  begin
    Key := #0;
    RefrescarDocumentosRelacionados(nil);
  end;
end;

procedure TfrmClientesEditor.RefrescarDocumentosRelacionados(Sender: TObject);
var
  LSQL: TStringList;
  LTipoFiltro: Integer;
  LEjercicio: string;
  LTodasEmpresas: Boolean;
  LTexto: string;
  LCount: Integer;
  LTotalBase, LTotalImp: Double;
  LUnionAdded: Boolean;
begin
  if Trim(FClienteId) = '' then
  begin
    qryDocumentosRelacionados.Close;
    Exit;
  end;

  LTipoFiltro := 0;
  if Assigned(cbbFiltroTipoDoc) then
    LTipoFiltro := cbbFiltroTipoDoc.ItemIndex; // 0=Todos, 1=Presupuestos, 2=Pedidos, 3=Albaranes, 4=Facturas, 5=Envios

  LEjercicio := '';
  if Assigned(cbbFiltroDocEjercicio) and (cbbFiltroDocEjercicio.ItemIndex > 0) then
    LEjercicio := cbbFiltroDocEjercicio.Text;

  LTodasEmpresas := False;
  if Assigned(chkTodasEmpresasDocs) then
    LTodasEmpresas := chkTodasEmpresasDocs.Checked;

  LTexto := '';
  if Assigned(edtFiltroDocTexto) then
    LTexto := Trim(edtFiltroDocTexto.Text);

  LSQL := TStringList.Create;
  try
    LUnionAdded := False;

    // 1. PRESUPUESTOS (tipo_doc_code = 1)
    if (LTipoFiltro = 0) or (LTipoFiltro = 1) then
    begin
      LSQL.Add('SELECT ');
      LSQL.Add('  CONVERT(''📋 Presupuesto'' USING utf8mb4) COLLATE utf8mb4_general_ci AS tipo_doc_desc, ');
      LSQL.Add('  1 AS tipo_doc_code, ');
      LSQL.Add('  CONVERT(p.id, CHAR) COLLATE utf8mb4_general_ci AS doc_id, ');
      LSQL.Add('  CONVERT(CONCAT(COALESCE(p.serie, ''''), IF(p.serie IS NOT NULL AND p.serie <> '''', ''/'', ''''), p.numero) USING utf8mb4) COLLATE utf8mb4_general_ci AS serie_numero, ');
      LSQL.Add('  CAST(p.fecha AS DATE) AS fecha, ');
      LSQL.Add('  CONVERT(COALESCE(p.estado, ''PENDIENTE'') USING utf8mb4) COLLATE utf8mb4_general_ci AS estado, ');
      LSQL.Add('  p.base_imponible AS base, ');
      LSQL.Add('  p.total AS total, ');
      LSQL.Add('  CONVERT(COALESCE(p.referencia, p.observaciones1, '''') USING utf8mb4) COLLATE utf8mb4_general_ci AS referencia, ');
      LSQL.Add('  CONVERT(COALESCE(e.nombre, '''') USING utf8mb4) COLLATE utf8mb4_general_ci AS empresa_nombre, ');
      LSQL.Add('  p.empresa_id AS empresa_id ');
      LSQL.Add('FROM ge_presupuestos p ');
      LSQL.Add('LEFT JOIN ge_empresas e ON p.empresa_id = e.id ');
      LSQL.Add('WHERE p.cliente_id = :cliente_id ');
      if not LTodasEmpresas then
        LSQL.Add('  AND p.empresa_id = :empresa_id ');
      if LEjercicio <> '' then
        LSQL.Add('  AND YEAR(p.fecha) = ' + LEjercicio + ' ');
      if LTexto <> '' then
        LSQL.Add('  AND (p.numero LIKE ''%' + LTexto + '%'' OR p.serie LIKE ''%' + LTexto + '%'' OR p.referencia LIKE ''%' + LTexto + '%'' OR p.observaciones1 LIKE ''%' + LTexto + '%'') ');
      LUnionAdded := True;
    end;

    // 2. PEDIDOS (tipo_doc_code = 2)
    if (LTipoFiltro = 0) or (LTipoFiltro = 2) then
    begin
      if LUnionAdded then LSQL.Add('UNION ALL ');
      LSQL.Add('SELECT ');
      LSQL.Add('  CONVERT(''📦 Pedido Venta'' USING utf8mb4) COLLATE utf8mb4_general_ci AS tipo_doc_desc, ');
      LSQL.Add('  2 AS tipo_doc_code, ');
      LSQL.Add('  CONVERT(ped.id, CHAR) COLLATE utf8mb4_general_ci AS doc_id, ');
      LSQL.Add('  CONVERT(COALESCE(ped.numero_pedido, '''') USING utf8mb4) COLLATE utf8mb4_general_ci AS serie_numero, ');
      LSQL.Add('  CAST(ped.fecha_pedido AS DATE) AS fecha, ');
      LSQL.Add('  CONVERT(COALESCE(ped.estado, ''PENDIENTE'') USING utf8mb4) COLLATE utf8mb4_general_ci AS estado, ');
      LSQL.Add('  ped.base_imponible AS base, ');
      LSQL.Add('  ped.total AS total, ');
      LSQL.Add('  CONVERT(COALESCE(ped.observaciones, '''') USING utf8mb4) COLLATE utf8mb4_general_ci AS referencia, ');
      LSQL.Add('  CONVERT(COALESCE(e.nombre, '''') USING utf8mb4) COLLATE utf8mb4_general_ci AS empresa_nombre, ');
      LSQL.Add('  ped.empresa_id AS empresa_id ');
      LSQL.Add('FROM ge_pedidos ped ');
      LSQL.Add('LEFT JOIN ge_empresas e ON ped.empresa_id = e.id ');
      LSQL.Add('WHERE ped.cliente_id = :cliente_id ');
      if not LTodasEmpresas then
        LSQL.Add('  AND ped.empresa_id = :empresa_id ');
      if LEjercicio <> '' then
        LSQL.Add('  AND YEAR(ped.fecha_pedido) = ' + LEjercicio + ' ');
      if LTexto <> '' then
        LSQL.Add('  AND (ped.numero_pedido LIKE ''%' + LTexto + '%'' OR ped.observaciones LIKE ''%' + LTexto + '%'') ');
      LUnionAdded := True;
    end;

    // 3. ALBARANES (tipo_doc_code = 3)
    if (LTipoFiltro = 0) or (LTipoFiltro = 3) then
    begin
      if LUnionAdded then LSQL.Add('UNION ALL ');
      LSQL.Add('SELECT ');
      LSQL.Add('  CONVERT(''🚚 Albarán Salida'' USING utf8mb4) COLLATE utf8mb4_general_ci AS tipo_doc_desc, ');
      LSQL.Add('  3 AS tipo_doc_code, ');
      LSQL.Add('  CONVERT(a.id, CHAR) COLLATE utf8mb4_general_ci AS doc_id, ');
      LSQL.Add('  CONVERT(CONCAT(COALESCE(a.serie, ''''), IF(a.serie IS NOT NULL AND a.serie <> '''', ''/'', ''''), a.numero) USING utf8mb4) COLLATE utf8mb4_general_ci AS serie_numero, ');
      LSQL.Add('  CAST(a.fecha AS DATE) AS fecha, ');
      LSQL.Add('  CONVERT(IF(a.anulado = 1, ''ANULADO'', IF(a.facturado = 1, ''FACTURADO'', IF(a.cobrado = 1, ''COBRADO'', ''PENDIENTE''))) USING utf8mb4) COLLATE utf8mb4_general_ci AS estado, ');
      LSQL.Add('  a.base_imponible AS base, ');
      LSQL.Add('  a.total AS total, ');
      LSQL.Add('  CONVERT(COALESCE(a.referencia, a.observaciones1, '''') USING utf8mb4) COLLATE utf8mb4_general_ci AS referencia, ');
      LSQL.Add('  CONVERT(COALESCE(e.nombre, '''') USING utf8mb4) COLLATE utf8mb4_general_ci AS empresa_nombre, ');
      LSQL.Add('  a.empresa_id AS empresa_id ');
      LSQL.Add('FROM ge_albaranes a ');
      LSQL.Add('LEFT JOIN ge_empresas e ON a.empresa_id = e.id ');
      LSQL.Add('WHERE a.cliente_id = :cliente_id ');
      if not LTodasEmpresas then
        LSQL.Add('  AND a.empresa_id = :empresa_id ');
      if LEjercicio <> '' then
        LSQL.Add('  AND YEAR(a.fecha) = ' + LEjercicio + ' ');
      if LTexto <> '' then
        LSQL.Add('  AND (a.numero LIKE ''%' + LTexto + '%'' OR a.serie LIKE ''%' + LTexto + '%'' OR a.referencia LIKE ''%' + LTexto + '%'' OR a.observaciones1 LIKE ''%' + LTexto + '%'') ');
      LUnionAdded := True;
    end;

    // 4. FACTURAS (tipo_doc_code = 4)
    if (LTipoFiltro = 0) or (LTipoFiltro = 4) then
    begin
      if LUnionAdded then LSQL.Add('UNION ALL ');
      LSQL.Add('SELECT ');
      LSQL.Add('  CONVERT(''🧾 Factura Venta'' USING utf8mb4) COLLATE utf8mb4_general_ci AS tipo_doc_desc, ');
      LSQL.Add('  4 AS tipo_doc_code, ');
      LSQL.Add('  CONVERT(f.id, CHAR) COLLATE utf8mb4_general_ci AS doc_id, ');
      LSQL.Add('  CONVERT(CONCAT(COALESCE(f.serie, ''''), IF(f.serie IS NOT NULL AND f.serie <> '''', ''/'', ''''), f.numero) USING utf8mb4) COLLATE utf8mb4_general_ci AS serie_numero, ');
      LSQL.Add('  CAST(f.fecha AS DATE) AS fecha, ');
      LSQL.Add('  CONVERT(IF(f.pagada = 1, ''PAGADA'', ''PENDIENTE PAGO'') USING utf8mb4) COLLATE utf8mb4_general_ci AS estado, ');
      LSQL.Add('  f.base_imponible AS base, ');
      LSQL.Add('  f.total AS total, ');
      LSQL.Add('  CONVERT(COALESCE(f.referencia, '''') USING utf8mb4) COLLATE utf8mb4_general_ci AS referencia, ');
      LSQL.Add('  CONVERT(COALESCE(e.nombre, '''') USING utf8mb4) COLLATE utf8mb4_general_ci AS empresa_nombre, ');
      LSQL.Add('  f.empresa_id AS empresa_id ');
      LSQL.Add('FROM ge_facturas f ');
      LSQL.Add('LEFT JOIN ge_empresas e ON f.empresa_id = e.id ');
      LSQL.Add('WHERE f.cliente_id = :cliente_id ');
      if not LTodasEmpresas then
        LSQL.Add('  AND f.empresa_id = :empresa_id ');
      if LEjercicio <> '' then
        LSQL.Add('  AND YEAR(f.fecha) = ' + LEjercicio + ' ');
      if LTexto <> '' then
        LSQL.Add('  AND (f.numero LIKE ''%' + LTexto + '%'' OR f.serie LIKE ''%' + LTexto + '%'' OR f.referencia LIKE ''%' + LTexto + '%'') ');
      LUnionAdded := True;
    end;

    // 5. ENVIOS / RECOGIDAS (tipo_doc_code = 5)
    if (LTipoFiltro = 0) or (LTipoFiltro = 5) then
    begin
      if LUnionAdded then LSQL.Add('UNION ALL ');
      LSQL.Add('SELECT ');
      LSQL.Add('  CONVERT(''🚛 Envío / Recogida'' USING utf8mb4) COLLATE utf8mb4_general_ci AS tipo_doc_desc, ');
      LSQL.Add('  5 AS tipo_doc_code, ');
      LSQL.Add('  CONVERT(r.id, CHAR) COLLATE utf8mb4_general_ci AS doc_id, ');
      LSQL.Add('  CONVERT(COALESCE(r.cod_recogida, CAST(r.id AS CHAR(50))) USING utf8mb4) COLLATE utf8mb4_general_ci AS serie_numero, ');
      LSQL.Add('  CAST(r.fecha_recogida AS DATE) AS fecha, ');
      LSQL.Add('  CONVERT(COALESCE(r.estado_descripcion, IF(r.anulado = 1, ''ANULADA'', ''SOLICITADA'')) USING utf8mb4) COLLATE utf8mb4_general_ci AS estado, ');
      LSQL.Add('  CAST(0.00 AS DECIMAL(15,2)) AS base, ');
      LSQL.Add('  CAST(0.00 AS DECIMAL(15,2)) AS total, ');
      LSQL.Add('  CONVERT(CONCAT(''Bultos: '', r.num_bultos, IF(r.referencia IS NOT NULL AND r.referencia <> '''', CONCAT('' - '', r.referencia), '''')) USING utf8mb4) COLLATE utf8mb4_general_ci AS referencia, ');
      LSQL.Add('  CONVERT(COALESCE(e.nombre, '''') USING utf8mb4) COLLATE utf8mb4_general_ci AS empresa_nombre, ');
      LSQL.Add('  r.empresa_id AS empresa_id ');
      LSQL.Add('FROM ge_envios_recogidas r ');
      LSQL.Add('LEFT JOIN ge_pedidos ped ON r.pedido_id = ped.id ');
      LSQL.Add('LEFT JOIN ge_albaranes a ON r.albaran_id = a.id ');
      LSQL.Add('LEFT JOIN ge_empresas e ON r.empresa_id = e.id ');
      LSQL.Add('WHERE (ped.cliente_id = :cliente_id OR a.cliente_id = :cliente_id) ');
      if not LTodasEmpresas then
        LSQL.Add('  AND r.empresa_id = :empresa_id ');
      if LEjercicio <> '' then
        LSQL.Add('  AND YEAR(r.fecha_recogida) = ' + LEjercicio + ' ');
      if LTexto <> '' then
        LSQL.Add('  AND (r.cod_recogida LIKE ''%' + LTexto + '%'' OR r.referencia LIKE ''%' + LTexto + '%'' OR r.estado_descripcion LIKE ''%' + LTexto + '%'') ');
    end;

    LSQL.Add('ORDER BY fecha DESC, doc_id DESC');

    qryDocumentosRelacionados.Close;
    qryDocumentosRelacionados.SQL.Text := LSQL.Text;
    qryDocumentosRelacionados.ParamByName('cliente_id').AsString := FClienteId;
    if not LTodasEmpresas then
      qryDocumentosRelacionados.ParamByName('empresa_id').AsString := dmgMain.CurrentCompanyId;

    qryDocumentosRelacionados.Open;

    // Configurar columnas de la grilla
    dbgDocumentosRelacionados.Columns.Clear;
    with dbgDocumentosRelacionados.Columns.Add do
    begin
      FieldName := 'tipo_doc_desc';
      Title.Caption := 'Tipo Documento';
      Width := 140;
    end;
    with dbgDocumentosRelacionados.Columns.Add do
    begin
      FieldName := 'serie_numero';
      Title.Caption := 'Nº / Serie';
      Width := 110;
    end;
    with dbgDocumentosRelacionados.Columns.Add do
    begin
      FieldName := 'fecha';
      Title.Caption := 'Fecha';
      Width := 85;
    end;
    with dbgDocumentosRelacionados.Columns.Add do
    begin
      FieldName := 'estado';
      Title.Caption := 'Estado';
      Width := 120;
    end;
    with dbgDocumentosRelacionados.Columns.Add do
    begin
      FieldName := 'base';
      Title.Caption := 'Base Imponible';
      Width := 105;
      Alignment := taRightJustify;
    end;
    with dbgDocumentosRelacionados.Columns.Add do
    begin
      FieldName := 'total';
      Title.Caption := 'Total';
      Width := 105;
      Alignment := taRightJustify;
    end;
    with dbgDocumentosRelacionados.Columns.Add do
    begin
      FieldName := 'referencia';
      Title.Caption := 'Referencia / Observaciones';
      Width := 250;
    end;
    with dbgDocumentosRelacionados.Columns.Add do
    begin
      FieldName := 'empresa_nombre';
      Title.Caption := 'Empresa';
      Width := 160;
    end;

    if qryDocumentosRelacionados.FindField('base') is TNumericField then
      TNumericField(qryDocumentosRelacionados.FieldByName('base')).DisplayFormat := '#,##0.00 €';
    if qryDocumentosRelacionados.FindField('total') is TNumericField then
      TNumericField(qryDocumentosRelacionados.FieldByName('total')).DisplayFormat := '#,##0.00 €';

    // Calcular resumen
    LCount := 0;
    LTotalBase := 0;
    LTotalImp := 0;

    qryDocumentosRelacionados.DisableControls;
    try
      qryDocumentosRelacionados.First;
      while not qryDocumentosRelacionados.Eof do
      begin
        Inc(LCount);
        LTotalBase := LTotalBase + qryDocumentosRelacionados.FieldByName('base').AsFloat;
        LTotalImp := LTotalImp + qryDocumentosRelacionados.FieldByName('total').AsFloat;
        qryDocumentosRelacionados.Next;
      end;
      qryDocumentosRelacionados.First;
    finally
      qryDocumentosRelacionados.EnableControls;
    end;

    if Assigned(lblResumenDocs) then
      lblResumenDocs.Caption := Format('Total Documentos: %d | Base Acumulada: %s € | Importe Total: %s €',
        [LCount, FormatFloat('#,##0.00', LTotalBase), FormatFloat('#,##0.00', LTotalImp)]);

    FDocumentosLoaded := True;
  finally
    LSQL.Free;
  end;
end;

procedure TfrmClientesEditor.dbgDocumentosRelacionadosTitleClick(Column: TColumn);
begin
  if not qryDocumentosRelacionados.Active or qryDocumentosRelacionados.IsEmpty then Exit;

  if FLastDocSortColumn = Column.FieldName then
  begin
    if FLastDocSortDirection = 'ASC' then
      FLastDocSortDirection := 'DESC'
    else
      FLastDocSortDirection := 'ASC';
  end
  else
  begin
    FLastDocSortColumn := Column.FieldName;
    FLastDocSortDirection := 'ASC';
  end;

  try
    qryDocumentosRelacionados.IndexFieldNames := Column.FieldName + ':' + FLastDocSortDirection;
  except
    try
      qryDocumentosRelacionados.IndexFieldNames := Column.FieldName;
    except
      // Ignore if field doesn't support IndexFieldNames in standard query
    end;
  end;
end;

procedure TfrmClientesEditor.btnAbrirDocClick(Sender: TObject);
var
  LTipoDocCode: Integer;
  LDocId: string;
begin
  if not qryDocumentosRelacionados.Active or qryDocumentosRelacionados.IsEmpty then
  begin
    ShowMessage('Seleccione un documento de la lista para abrirlo.');
    Exit;
  end;

  LTipoDocCode := qryDocumentosRelacionados.FieldByName('tipo_doc_code').AsInteger;
  LDocId := qryDocumentosRelacionados.FieldByName('doc_id').AsString;

  case LTipoDocCode of
    1: // Presupuesto
    begin
      if not Assigned(frmPresupuestosEditor) then
        Application.CreateForm(TfrmPresupuestosEditor, frmPresupuestosEditor);
      frmPresupuestosEditor.PresupuestoId := LDocId;
      frmPresupuestosEditor.ShowModal;
      RefrescarDocumentosRelacionados(nil);
    end;
    2: // Pedido
    begin
      if not Assigned(frmPedidosEditor) then
        Application.CreateForm(TfrmPedidosEditor, frmPedidosEditor);
      frmPedidosEditor.PedidoId := LDocId;
      frmPedidosEditor.ShowModal;
      RefrescarDocumentosRelacionados(nil);
    end;
    3: // Albarán
    begin
      if not Assigned(frmAlbaranesEditor) then
        Application.CreateForm(TfrmAlbaranesEditor, frmAlbaranesEditor);
      frmAlbaranesEditor.AlbaranId := LDocId;
      frmAlbaranesEditor.ShowModal;
      RefrescarDocumentosRelacionados(nil);
    end;
    4: // Factura
    begin
      if not Assigned(frmFacturasEditor) then
        Application.CreateForm(TfrmFacturasEditor, frmFacturasEditor);
      frmFacturasEditor.FacturaId := LDocId;
      frmFacturasEditor.ShowModal;
      RefrescarDocumentosRelacionados(nil);
    end;
    5: // Envío / Recogida
    begin
      if not Assigned(frmAlbaranEnvioEditor) then
        Application.CreateForm(TfrmAlbaranEnvioEditor, frmAlbaranEnvioEditor);
      frmAlbaranEnvioEditor.RecogidaId := StrToIntDef(LDocId, 0);
      frmAlbaranEnvioEditor.ShowModal;
      RefrescarDocumentosRelacionados(nil);
    end;
  end;
end;

procedure TfrmClientesEditor.dbgDocumentosRelacionadosDblClick(Sender: TObject);
begin
  btnAbrirDocClick(Sender);
end;

procedure TfrmClientesEditor.btnTrazabilidadDocClick(Sender: TObject);
var
  LTipoDocCode: Integer;
  LDocId, LSerieNum: string;
  LTipoDocEnum: TDocVentaTipo;
begin
  if not qryDocumentosRelacionados.Active or qryDocumentosRelacionados.IsEmpty then
  begin
    ShowMessage('Seleccione un documento de la lista para ver su trazabilidad.');
    Exit;
  end;

  LTipoDocCode := qryDocumentosRelacionados.FieldByName('tipo_doc_code').AsInteger;
  LDocId := qryDocumentosRelacionados.FieldByName('doc_id').AsString;
  LSerieNum := qryDocumentosRelacionados.FieldByName('serie_numero').AsString;

  case LTipoDocCode of
    1: LTipoDocEnum := dvtPresupuesto;
    2: LTipoDocEnum := dvtPedido;
    3: LTipoDocEnum := dvtAlbaran;
    4: LTipoDocEnum := dvtFactura;
    5: LTipoDocEnum := dvtEnvioRecogida;
  else
    LTipoDocEnum := dvtFactura;
  end;

  TfrmDocumentosRelacionados.MostrarDocumentos(Self, LTipoDocEnum, LDocId, LSerieNum);
  RefrescarDocumentosRelacionados(nil);
end;

end.
