unit frm_PresupuestosEditor;

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,
  Vcl.ComCtrls, Vcl.Grids, Vcl.DBGrids, Data.DB, FireDAC.Comp.Client, uAppTheme,
  FireDAC.Stan.Param, 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, uBaseForm, Vcl.Menus,
  System.UITypes, System.Types;

type
  TfrmPresupuestosEditor = class(TfrmBase)
    Label1: TLabel;
    edtNumero: TEdit;
    Label2: TLabel;
    dtpFecha: TDateTimePicker;
    Label3: TLabel;
    cbCliente: TComboBox;
    Label4: TLabel;
    cbSerie: TComboBox;
    lblTotal: TLabel;
    edtTotal: TEdit;
    dsLines: TDataSource;
    qryLines: TFDQuery;
    lblBase: TLabel;
    edtBase: TEdit;
    sgIvaDesglose: TStringGrid;
    lblDestino: TLabel;
    lblEstado: TLabel;
    lblFormaPago: TLabel;
    cbDestino: TComboBox;
    cbEstado: TComboBox;
    cbFormaPago: TComboBox;
    pnlClienteInfo: TPanel;
    lblClienteInfo: TLabel;
    pnlDestinoInfo: TPanel;
    lblDestinoInfo: TLabel;
    lblImpuestos: TLabel;
    edtIva: TEdit;
    lbldesgloseiva: TLabel;
    btnBuscarCliente: TSpeedButton;
    btnNuevoCliente: TSpeedButton;
    btnBuscarDestino: TSpeedButton;
    btnNuevoDestino: TSpeedButton;
    lblObservaciones1: TLabel;
    edtObservaciones1: TMemo;
    procedure FormShow(Sender: TObject);
    procedure btnSaveClick(Sender: TObject);
    procedure btnAddLineClick(Sender: TObject);
    procedure btnDelLineClick(Sender: TObject);
    procedure btnSalirClick(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
    procedure qryLinesAfterPost(DataSet: TDataSet);
    procedure qryLinesAfterDelete(DataSet: TDataSet);
    procedure qryLinesBeforePost(DataSet: TDataSet);
    procedure cbClienteChange(Sender: TObject);
    procedure cbDestinoChange(Sender: TObject);
    procedure cbSerieChange(Sender: TObject);
    procedure btnBuscarClienteClick(Sender: TObject);
    procedure btnNuevoClienteClick(Sender: TObject);
    procedure btnBuscarDestinoClick(Sender: TObject);
    procedure btnNuevoDestinoClick(Sender: TObject);
    procedure btnDocsRelacionadosClick(Sender: TObject);
    procedure btnImprimirClick(Sender: TObject); override;
  protected
    procedure DoAnadir; override;
    procedure DoModificar; override;
    procedure DoPrimero; override;
    procedure DoAnterior; override;
    procedure DoSiguiente; override;
    procedure DoUltimo; override;
    procedure DoDuplicar; override;
    function IsNewRecord: Boolean; override;
  private
    { Private declarations }
    pmGenerar: TPopupMenu;
    FPresupuestoId: string;
    FClientIds: TStringList;
    FDestinationIds: TStringList;
    FFormaPagoIds: TStringList;
    FTotalBase: Double;
    FTotalIva: Double;
    FTotalDoc: Double;
    FParentForm: TForm;
    FIsNewRecord: Boolean;
    procedure LoadData;
    procedure LoadHeader;
    procedure LoadLines;
    procedure LoadClientes;
    procedure LoadFormasPago;
    procedure LoadDestinos(const AClienteId: string);
    procedure RecalculateTotal;
    procedure UpdateClienteDetails;
    procedure UpdateDestinoDetails;
    procedure qryLinesFieldChange(Sender: TField);
    procedure SetupFieldChangeEvents;
    procedure SetupIvaPickList;
    function ValidarYAsegurarDestino: Boolean;
    procedure pmGenerarItemClick(Sender: TObject);
    procedure btnRectClick(Sender: TObject);
    procedure btnEliminarClick(Sender: TObject);
  public
    { Public declarations }
    property PresupuestoId: string read FPresupuestoId write FPresupuestoId;
    property ParentForm: TForm read FParentForm write FParentForm;
  end;

var
  frmPresupuestosEditor: TfrmPresupuestosEditor;

implementation

uses
  dmg_Main, frm_SelectProduct, frm_SelectCliente, frm_SelectDestino, uDbErrorHandler, System.Generics.Collections, frm_Presupuestos,
  frm_ClientesEditor, frm_ClientesDestinosEditor, frm_DocumentosRelacionados, uPresupuestoPrintService;

{$R *.dfm}

procedure TfrmPresupuestosEditor.FormShow(Sender: TObject);
var
  Qry: TFDQuery;
  LItem: TMenuItem;
  LSerieAuto: string;
  LNumAuto, LIdx: Integer;
begin
  Constraints.MinWidth := 1097;
  Constraints.MinHeight := 689;

  if not Assigned(FClientIds) then
    FClientIds := TStringList.Create
  else
    FClientIds.Clear;

  if not Assigned(FDestinationIds) then
    FDestinationIds := TStringList.Create
  else
    FDestinationIds.Clear;

  // Aplicar tema
  TAppTheme.ApplyToForm(Self);

  // Vincular eventos a la barra superior e inferior heredadas
  btnGuardar.OnClick := btnSaveClick;
  btnSalir.OnClick := btnSalirClick;
  btnNuevoArticulo.OnClick := btnAddLineClick;
  btnEliminaArticulo.OnClick := btnDelLineClick;
  btnEliminar.OnClick := btnEliminarClick;
  btnIA.OnClick := btnDocsRelacionadosClick;

  // Vincular la grilla heredada
  dbgItems.DataSource := dsLines;

  // Ocultar botones de navegación/herramientas innecesarios
  btnCancelar.Visible := True;
  btnAnadir.Visible := True;
  btnEliminar.Visible := True;
  btnPrimero.Visible := True;
  btnAnterior.Visible := True;
  btnSiguiente.Visible := True;
  btnUltimo.Visible := True;
  btnModificar.Visible := True;
  btnBuscar.Visible := False;
  btnImprimir.Visible := True;
  btnImprimir.OnClick := btnImprimirClick;
  btnIA.Visible := True;
  btnIA.Caption := 'Relacionados';
  btnIA.ImageName := 'icon_documentos_relacionados';
  btnIA.Width := 75;
  btnDuplicar.Visible := True;
  btnRect.Visible := True;
  btnRect.Caption := 'Generar...';
  btnAyuda.Visible := True;
  btnBuscarArticulo.Visible := False;
  btnExcel.Visible := False;
  tslistado.Visible := False;

  // Configurar menú de operaciones "Generar..." dinámicamente
  if not Assigned(pmGenerar) then
  begin
    pmGenerar := TPopupMenu.Create(Self);
    
    LItem := TMenuItem.Create(pmGenerar);
    LItem.Caption := 'Crear Pedido';
    LItem.Tag := 1;
    LItem.OnClick := pmGenerarItemClick;
    pmGenerar.Items.Add(LItem);
    
    LItem := TMenuItem.Create(pmGenerar);
    LItem.Caption := 'Crear Albarán';
    LItem.Tag := 2;
    LItem.OnClick := pmGenerarItemClick;
    pmGenerar.Items.Add(LItem);
    
    LItem := TMenuItem.Create(pmGenerar);
    LItem.Caption := 'Crear Factura';
    LItem.Tag := 3;
    LItem.OnClick := pmGenerarItemClick;
    pmGenerar.Items.Add(LItem);
  end;
  btnRect.PopupMenu := pmGenerar;
  btnRect.OnClick := btnRectClick;

  // Ocultar pestaña de listado heredada
  TsListado.TabVisible := False;
  pgcDetails.ActivePage := tsDatosEnvio;

  // Configurar columnas de la grilla heredada dbgItems
  dbgItems.Columns.Clear;
  with dbgItems.Columns.Add do
  begin
    FieldName := 'articulo_codigo';
    Title.Caption := 'Ref. Artículo';
    Width := 90;
  end;
  with dbgItems.Columns.Add do
  begin
    FieldName := 'descripcion';
    Title.Caption := 'Descripción';
    Width := 250;
  end;
  with dbgItems.Columns.Add do
  begin
    FieldName := 'cantidad';
    Title.Caption := 'Cant.';
    Width := 50;
  end;
  with dbgItems.Columns.Add do
  begin
    FieldName := 'precio';
    Title.Caption := 'Precio';
    Width := 75;
  end;

  with dbgItems.Columns.Add do
  begin
    FieldName := 'descuento_porcentaje';
    Title.Caption := '% Dto.';
    Width := 55;
  end;
  with dbgItems.Columns.Add do
  begin
    FieldName := 'descuento_precio';
    Title.Caption := 'Dto. Importe';
    Width := 75;
  end;
  with dbgItems.Columns.Add do
  begin
    FieldName := 'tipo_iva';
    Title.Caption := '% IVA';
    Width := 55;
  end;
  with dbgItems.Columns.Add do
  begin
    FieldName := 'total';
    Title.Caption := 'Total';
    Width := 90;
  end;

  LoadClientes;
  LoadFormasPago;
  cbDestino.OnChange := cbDestinoChange;
  lblClienteInfo.Caption := '';
  lblDestinoInfo.Caption := '';

  // Configurar combo Estado
  cbEstado.Items.Clear;
  cbEstado.Items.Add('Pendiente');
  cbEstado.Items.Add('Aceptado');
  cbEstado.Items.Add('Rechazado');
  cbEstado.Items.Add('Facturado');
  cbEstado.ItemIndex := 0;

  qryLines.BeforePost := qryLinesBeforePost;

  // Configurar sgIvaDesglose
  sgIvaDesglose.ColCount := 3;
  sgIvaDesglose.RowCount := 2;
  sgIvaDesglose.FixedRows := 1;
  sgIvaDesglose.FixedCols := 0;
  sgIvaDesglose.Cells[0, 0] := '% IVA';
  sgIvaDesglose.Cells[1, 0] := 'Base Imponible';
  sgIvaDesglose.Cells[2, 0] := 'Cuota IVA';
  sgIvaDesglose.ColWidths[0] := 80;
  sgIvaDesglose.ColWidths[1] := 160;
  sgIvaDesglose.ColWidths[2] := 160;

  // Cargar series reales en el combo una sola vez
  if cbSerie.Items.Count = 0 then
  begin
    cbSerie.Items.Clear;
    Qry := TFDQuery.Create(nil);
    try
      Qry.Connection := dmgMain.dbConn;
      Qry.SQL.Text := 'SELECT DISTINCT serie FROM ge_series WHERE activo = 1 AND empresa_id = :emp ORDER BY serie';
      Qry.ParamByName('emp').AsString := dmgMain.CurrentCompanyId;
      Qry.Open;
      while not Qry.Eof do
      begin
        cbSerie.Items.Add(Qry.FieldByName('serie').AsString);
        Qry.Next;
      end;
    finally
      Qry.Free;
    end;
  end;

  FIsNewRecord := (FPresupuestoId = '');
  if not FIsNewRecord then
    LoadData
  else
  begin
    dtpFecha.Date := Now;
    if dmgMain.GetSerieYNumeroDocumento(dmgMain.CurrentCompanyId, 'PRESUPUESTO', dtpFecha.Date, '', LSerieAuto, LNumAuto) then
    begin
      LIdx := cbSerie.Items.IndexOf(LSerieAuto);
      if LIdx <> -1 then
        cbSerie.ItemIndex := LIdx;
      edtNumero.Text := IntToStr(LNumAuto);
    end
    else if cbSerie.Items.Count > 0 then
    begin
      cbSerie.ItemIndex := 0;
      cbSerieChange(cbSerie);
    end
    else
    begin
      edtNumero.Text := dmgMain.GetNextIDS('ge_presupuestos');
    end;
    edtTotal.Text := '0,00';
    edtIva.Text := '0,00';
    edtBase.Text := '0,00';
    
    sgIvaDesglose.RowCount := 2;
    sgIvaDesglose.Cells[0, 1] := '';
    sgIvaDesglose.Cells[1, 1] := '';
    sgIvaDesglose.Cells[2, 1] := '';
    edtObservaciones1.Text := '';

    FTotalBase := 0;
    FTotalIva := 0;
    FTotalDoc := 0;
    
    // Preparar qryLines vacía
    qryLines.Close;
    qryLines.SQL.Text := 'SELECT *, (total - base) as iva FROM ge_presupuestos_lineas WHERE presupuesto_id = :id';
    qryLines.ParamByName('id').AsInteger := 0;
    qryLines.Open;
    SetupFieldChangeEvents;
  end;
  SetupIvaPickList;
end;

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

procedure TfrmPresupuestosEditor.btnAddLineClick(Sender: TObject);
var
  i: Integer;
  LPrice, LIvaRate, LIva: Double;
begin
  if not Assigned(frmSelectProduct) then
    Application.CreateForm(TfrmSelectProduct, frmSelectProduct);
    
  frmSelectProduct.MultiSelect := True;
  
  if frmSelectProduct.ShowModal = mrOk then
  begin
    qryLines.DisableControls;
    try
      for i := 0 to frmSelectProduct.SelectedIds.Count - 1 do
      begin
        qryLines.Append;
        if FPresupuestoId <> '' then
          qryLines.FieldByName('presupuesto_id').AsInteger := StrToIntDef(FPresupuestoId, 0);
          
        qryLines.FieldByName('articulo_id').AsString := frmSelectProduct.SelectedIds[i];
        qryLines.FieldByName('articulo_codigo').AsString := frmSelectProduct.SelectedCodes[i];
        qryLines.FieldByName('descripcion').AsString := frmSelectProduct.SelectedDescs[i];
        
        LPrice := StrToFloatDef(frmSelectProduct.SelectedPrices[i], 0);
        if cbCliente.ItemIndex <> -1 then
          LPrice := dmgMain.GetPrecioArticuloCliente(FClientIds[cbCliente.ItemIndex], frmSelectProduct.SelectedIds[i], dtpFecha.Date, LPrice);
        LIvaRate := StrToFloatDef(frmSelectProduct.SelectedIvaRates[i], 0);
        
        qryLines.FieldByName('precio').AsFloat := LPrice;
        qryLines.FieldByName('cantidad').AsFloat := 1;
        qryLines.FieldByName('tipo_iva').AsFloat := LIvaRate;
        
        LIva := LPrice * (LIvaRate / 100.0);
        qryLines.FieldByName('base').AsFloat := LPrice;
        qryLines.FieldByName('total').AsFloat := LPrice + LIva;
        if qryLines.FindField('id_user_creator') <> nil then
          qryLines.FieldByName('id_user_creator').AsString := dmgMain.CurrentUserId;
        if qryLines.FindField('id_user_update') <> nil then
          qryLines.FieldByName('id_user_update').AsString := dmgMain.CurrentUserId;
        qryLines.Post;
      end;
    finally
      qryLines.EnableControls;
    end;
  end;
end;

procedure TfrmPresupuestosEditor.btnDelLineClick(Sender: TObject);
begin
  if not qryLines.IsEmpty then
    qryLines.Delete;
end;

procedure TfrmPresupuestosEditor.btnSaveClick(Sender: TObject);
var
  LIsNew: Boolean;
  LSerieAuto: string;
  LNumAuto, LIdx: Integer;
begin
  if (cbCliente.ItemIndex = -1) then
  begin
    ShowMessage('Debe seleccionar un cliente.');
    Exit;
  end;

  if not ValidarYAsegurarDestino then
    Exit;

  // Comprobar si la fecha del documento se encuentra fuera del rango de la serie actual
  if dmgMain.GetSerieYNumeroDocumento(dmgMain.CurrentCompanyId, 'PRESUPUESTO', dtpFecha.Date, cbSerie.Text, LSerieAuto, LNumAuto) then
  begin
    if not SameText(cbSerie.Text, LSerieAuto) then
    begin
      LIdx := cbSerie.Items.IndexOf(LSerieAuto);
      if LIdx <> -1 then
        cbSerie.ItemIndex := LIdx
      else
      begin
        cbSerie.Items.Add(LSerieAuto);
        cbSerie.ItemIndex := cbSerie.Items.Count - 1;
      end;
      edtNumero.Text := IntToStr(LNumAuto);
    end;
  end;

  LIsNew := FIsNewRecord;
  dmgMain.qryExec.Close;
  
  if LIsNew then
  begin
    dmgMain.qryExec.SQL.Text := 
      'INSERT INTO ge_presupuestos (empresa_id, cliente_id, clientedestino_id, serie, numero, fecha, base_imponible, iva, total, estado, id_forma_pago, observaciones1, id_user_creator, id_user_update) ' +
      'VALUES (:emp, :cli, :dest, :ser, :num, :fec, :base, :iva, :tot, :est, :id_forma_pago, :obs1, :user, :user)';
  end
  else
  begin
    dmgMain.qryExec.SQL.Text := 
      'UPDATE ge_presupuestos SET cliente_id = :cli, clientedestino_id = :dest, serie = :ser, numero = :num, ' +
      'fecha = :fec, base_imponible = :base, iva = :iva, total = :tot, estado = :est, id_forma_pago = :id_forma_pago, ' +
      'observaciones1 = :obs1, id_user_update = :user ' +
      'WHERE id = :id';
    dmgMain.qryExec.ParamByName('id').AsInteger := StrToIntDef(FPresupuestoId, 0);
  end;
  if LIsNew then
    dmgMain.qryExec.ParamByName('emp').AsString := dmgMain.CurrentCompanyId;
  dmgMain.qryExec.ParamByName('cli').AsString := FClientIds[cbCliente.ItemIndex];
  
  dmgMain.qryExec.ParamByName('dest').DataType := ftString;
  if cbDestino.ItemIndex <> -1 then
    dmgMain.qryExec.ParamByName('dest').AsString := FDestinationIds[cbDestino.ItemIndex]
  else
    dmgMain.qryExec.ParamByName('dest').Clear;

  dmgMain.qryExec.ParamByName('ser').AsString := cbSerie.Text;
  dmgMain.qryExec.ParamByName('num').AsInteger := StrToIntDef(edtNumero.Text, 0);
  dmgMain.qryExec.ParamByName('fec').AsDateTime := dtpFecha.DateTime;
  dmgMain.qryExec.ParamByName('base').AsFloat := FTotalBase;
  dmgMain.qryExec.ParamByName('iva').AsFloat := FTotalIva;
  dmgMain.qryExec.ParamByName('tot').AsFloat := FTotalDoc;
  dmgMain.qryExec.ParamByName('est').AsString := cbEstado.Text;

  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;

  dmgMain.qryExec.ParamByName('obs1').DataType := ftWideMemo;
  if Trim(edtObservaciones1.Lines.Text) <> '' then
    dmgMain.qryExec.ParamByName('obs1').AsWideMemo := Trim(edtObservaciones1.Lines.Text)
  else
    dmgMain.qryExec.ParamByName('obs1').Clear;



  dmgMain.qryExec.ParamByName('user').AsString := dmgMain.CurrentUserId;
  
  try
    dmgMain.qryExec.ExecSQL;

    if LIsNew then
    begin
      // Obtener el ID auto-generado
      dmgMain.qryExec.Close;
      dmgMain.qryExec.SQL.Text := 'SELECT LAST_INSERT_ID() as new_id';
      dmgMain.qryExec.Open;
      FPresupuestoId := dmgMain.qryExec.FieldByName('new_id').AsString;
      dmgMain.qryExec.Close;

      // Incrementar el contador en la tabla de series
      dmgMain.qryExec.SQL.Text := 'UPDATE ge_series SET sig_numero_presupuesto = sig_numero_presupuesto + 1 ' +
                                  'WHERE activo = 1 AND empresa_id = :emp AND serie = :ser';
      dmgMain.qryExec.ParamByName('emp').AsString := dmgMain.CurrentCompanyId;
      dmgMain.qryExec.ParamByName('ser').AsString := cbSerie.Text;
      dmgMain.qryExec.ExecSQL;

      qryLines.First;
      while not qryLines.Eof do
      begin
        if qryLines.FieldByName('presupuesto_id').IsNull or (qryLines.FieldByName('presupuesto_id').AsInteger = 0) then
        begin
          qryLines.Edit;
          qryLines.FieldByName('presupuesto_id').AsInteger := StrToIntDef(FPresupuestoId, 0);
          qryLines.Post;
        end;
        qryLines.Next;
      end;
    end;
    
    FIsNewRecord := False;
    ResetChangeTracking;
    if Assigned(FParentForm) and (FParentForm is TfrmPresupuestos) then
      TfrmPresupuestos(FParentForm).btnRefreshClick(nil);
    ShowMessage('Presupuesto guardado correctamente.');
  except
    on E: Exception do
      TDbErrorHandler.HandleException(E, dmgMain.qryExec.SQL.Text);
  end;
end;

procedure TfrmPresupuestosEditor.RecalculateTotal;
var
  LBase, LIva, LTotal: Double;
  LBookmark: TBookmark;
  LVatBases: TDictionary<Double, Double>;
  LVatAmounts: TDictionary<Double, Double>;
  LRate: Double;
  LKey: Double;
  LLineBase: Double;
  LRow: Integer;
  LQryUpd: TFDQuery;
begin
  LBase := 0;
  LIva := 0;
  LTotal := 0;
  LVatBases := TDictionary<Double, Double>.Create;
  LVatAmounts := TDictionary<Double, Double>.Create;
  try
    LBookmark := qryLines.GetBookmark;
    qryLines.DisableControls;
    try
      qryLines.First;
      while not qryLines.Eof do
      begin
        LLineBase := (qryLines.FieldByName('cantidad').AsFloat * qryLines.FieldByName('precio').AsFloat) - qryLines.FieldByName('descuento_precio').AsFloat;
        LBase := LBase + LLineBase;
        LIva := LIva + qryLines.FieldByName('iva').AsFloat;
        LTotal := LTotal + qryLines.FieldByName('total').AsFloat;
        
        LRate := qryLines.FieldByName('tipo_iva').AsFloat;
        if LVatBases.ContainsKey(LRate) then
        begin
          LVatBases[LRate] := LVatBases[LRate] + LLineBase;
          LVatAmounts[LRate] := LVatAmounts[LRate] + qryLines.FieldByName('iva').AsFloat;
        end
        else
        begin
          LVatBases.Add(LRate, LLineBase);
          LVatAmounts.Add(LRate, qryLines.FieldByName('iva').AsFloat);
        end;
        qryLines.Next;
      end;
    finally
      qryLines.GotoBookmark(LBookmark);
      qryLines.EnableControls;
    end;

    // Rellenar sgIvaDesglose
    sgIvaDesglose.RowCount := 2;
    sgIvaDesglose.Cells[0, 1] := '';
    sgIvaDesglose.Cells[1, 1] := '';
    sgIvaDesglose.Cells[2, 1] := '';

    LRow := 1;
    for LKey in LVatAmounts.Keys do
    begin
      if LRow >= sgIvaDesglose.RowCount then
        sgIvaDesglose.RowCount := LRow + 1;
      sgIvaDesglose.Cells[0, LRow] := FormatFloat('0.#', LKey) + '%';
      sgIvaDesglose.Cells[1, LRow] := FormatFloat('#,##0.00', LVatBases[LKey]);
      sgIvaDesglose.Cells[2, LRow] := FormatFloat('#,##0.00', LVatAmounts[LKey]);
      Inc(LRow);
    end;
    
    FTotalBase := LBase;
    FTotalIva := LIva;
    FTotalDoc := LTotal;

    edtBase.Text := FormatFloat('#,##0.00', LBase);
    edtIva.Text := FormatFloat('#,##0.00', LIva);
    edtTotal.Text := FormatFloat('#,##0.00', LTotal);

    // Actualizar automáticamente los totales en la cabecera ge_presupuestos si el documento ya está guardado
    if (FPresupuestoId <> '') and not FIsNewRecord then
    begin
      LQryUpd := TFDQuery.Create(nil);
      try
        LQryUpd.Connection := dmgMain.dbConn;
        LQryUpd.SQL.Text :=
          'UPDATE ge_presupuestos ' +
          'SET base_imponible = :base, iva = :iva, total = :tot ' +
          'WHERE id = :id';
        LQryUpd.ParamByName('base').AsFloat := FTotalBase;
        LQryUpd.ParamByName('iva').AsFloat := FTotalIva;
        LQryUpd.ParamByName('tot').AsFloat := FTotalDoc;
        LQryUpd.ParamByName('id').AsInteger := StrToIntDef(FPresupuestoId, 0);
        LQryUpd.ExecSQL;
      finally
        LQryUpd.Free;
      end;
      
      if Assigned(FParentForm) and (FParentForm is TfrmPresupuestos) then
        TfrmPresupuestos(FParentForm).btnRefreshClick(nil);
    end;
  finally
    LVatBases.Free;
    LVatAmounts.Free;
  end;
end;

procedure TfrmPresupuestosEditor.qryLinesAfterDelete(DataSet: TDataSet);
begin
  RecalculateTotal;
end;

procedure TfrmPresupuestosEditor.qryLinesAfterPost(DataSet: TDataSet);
begin
  RecalculateTotal;
end;

procedure TfrmPresupuestosEditor.qryLinesBeforePost(DataSet: TDataSet);
var
  LPrecio, LCantidad, LTipoIva, LSubtotal, LPorcDto, LDtoAmt, LBase, LIva, LTotal: Double;
begin
  // Asegurar que presupuesto_id siempre tenga valor (cubre líneas añadidas desde el grid)
  if DataSet.FieldByName('presupuesto_id').IsNull or (DataSet.FieldByName('presupuesto_id').AsInteger = 0) then
    if FPresupuestoId <> '' then
      DataSet.FieldByName('presupuesto_id').AsInteger := StrToIntDef(FPresupuestoId, 0);

  LPrecio := DataSet.FieldByName('precio').AsFloat;
  LCantidad := DataSet.FieldByName('cantidad').AsFloat;
  LTipoIva := DataSet.FieldByName('tipo_iva').AsFloat;
  LPorcDto := DataSet.FieldByName('descuento_porcentaje').AsFloat;
  LDtoAmt := DataSet.FieldByName('descuento_precio').AsFloat;
  
  LSubtotal := LPrecio * LCantidad;
  
  // Si hay porcentaje de descuento, calcular el importe del descuento
  if LPorcDto <> 0 then
  begin
    LDtoAmt := LSubtotal * (LPorcDto / 100.0);
    DataSet.FieldByName('descuento_precio').AsFloat := LDtoAmt;
  end;
  
  LBase := LSubtotal - LDtoAmt;
  LIva := LBase * (LTipoIva / 100.0);
  LTotal := LBase + LIva;
  
  DataSet.FieldByName('base').AsFloat := LBase;
  DataSet.FieldByName('total').AsFloat := LTotal;
end;

procedure TfrmPresupuestosEditor.LoadData;
begin
  LoadHeader;
  LoadLines;
  RecalculateTotal;
end;

procedure TfrmPresupuestosEditor.LoadHeader;
var
  LQry: TFDQuery;
  LClienteId, LDestinoId: string;
  LObs: string;
begin
  LQry := TFDQuery.Create(nil);
  try
    LQry.Connection := dmgMain.dbConn;
    LQry.SQL.Text := 
      'SELECT p.* ' +
      'FROM ge_presupuestos p ' +
      'WHERE p.id = :id';
    LQry.ParamByName('id').AsString := FPresupuestoId;
    LQry.Open;
    
    if not LQry.IsEmpty then
    begin
      edtNumero.Text := LQry.FieldByName('numero').AsString;
      cbSerie.ItemIndex := cbSerie.Items.IndexOf(LQry.FieldByName('serie').AsString);
      if cbSerie.ItemIndex = -1 then
      begin
        cbSerie.Items.Add(LQry.FieldByName('serie').AsString);
        cbSerie.ItemIndex := cbSerie.Items.Count - 1;
      end;
      dtpFecha.Date := LQry.FieldByName('fecha').AsDateTime;
      edtTotal.Text := FormatFloat('#,##0.00', LQry.FieldByName('total').AsFloat);
      
      LClienteId := TdmgMain.CleanUUID(LQry.FieldByName('cliente_id').AsString);
      cbCliente.ItemIndex := FClientIds.IndexOf(LClienteId);
      if (cbCliente.ItemIndex = -1) and (LClienteId <> '') then
      begin
        var LQryCli := TFDQuery.Create(nil);
        try
          LQryCli.Connection := dmgMain.dbConn;
          LQryCli.SQL.Text := 'SELECT nombre_fiscal FROM ge_clientes WHERE id = :id';
          LQryCli.ParamByName('id').AsString := LClienteId;
          LQryCli.Open;
          if not LQryCli.IsEmpty then
          begin
            cbCliente.Items.Add(LQryCli.FieldByName('nombre_fiscal').AsString);
            FClientIds.Add(LClienteId);
            cbCliente.ItemIndex := cbCliente.Items.Count - 1;
          end;
        finally
          LQryCli.Free;
        end;
      end;
      
      LDestinoId := TdmgMain.CleanUUID(LQry.FieldByName('clientedestino_id').AsString);
      cbEstado.ItemIndex := cbEstado.Items.IndexOf(LQry.FieldByName('estado').AsString);
      if cbEstado.ItemIndex = -1 then
        cbEstado.ItemIndex := 0;

      if LQry.FindField('id_forma_pago') <> nil then
      begin
        var LFormaPagoId := LQry.FieldByName('id_forma_pago').AsString;
        if (LFormaPagoId <> '') and Assigned(FFormaPagoIds) then
        begin
          var LIdxFP := FFormaPagoIds.IndexOf(LFormaPagoId);
          if LIdxFP >= 0 then
            cbFormaPago.ItemIndex := LIdxFP
          else
            cbFormaPago.ItemIndex := 0;
        end
        else
          cbFormaPago.ItemIndex := 0;
      end;
      
      LObs := '';
      if LQry.FindField('observaciones1') <> nil then
        LObs := LQry.FieldByName('observaciones1').AsWideString;
      if (Trim(LObs) = '') and (LQry.FindField('observaciones2') <> nil) then
        LObs := LQry.FieldByName('observaciones2').AsWideString;
      edtObservaciones1.Lines.Text := LObs;

      // Cargar destinos para este cliente
      LoadDestinos(LClienteId);
      cbDestino.ItemIndex := FDestinationIds.IndexOf(LDestinoId);
      UpdateClienteDetails;
      UpdateDestinoDetails;
    end;
  finally
    LQry.Free;
  end;
end;

procedure TfrmPresupuestosEditor.LoadLines;
begin
  qryLines.Close;
  qryLines.SQL.Text := 'SELECT *, (total - base) as iva FROM ge_presupuestos_lineas WHERE presupuesto_id = :id ORDER BY posicion';
  qryLines.ParamByName('id').AsString := FPresupuestoId;
  qryLines.Open;
  SetupFieldChangeEvents;
end;

procedure TfrmPresupuestosEditor.LoadClientes;
begin
  if Assigned(FClientIds) and (FClientIds.Count > 0) and (cbCliente.Items.Count > 0) then Exit;

  cbCliente.Items.BeginUpdate;
  try
    cbCliente.Items.Clear;
    if not Assigned(FClientIds) then
      FClientIds := TStringList.Create
    else
      FClientIds.Clear;

    dmgMain.qryExec.Close;
    dmgMain.qryExec.SQL.Text := 'SELECT id, nombre_fiscal FROM ge_clientes WHERE activo = true ORDER BY nombre_fiscal';
    dmgMain.qryExec.Open;
    while not dmgMain.qryExec.Eof do
    begin
      cbCliente.Items.Add(dmgMain.qryExec.FieldByName('nombre_fiscal').AsString);
      FClientIds.Add(TdmgMain.CleanUUID(dmgMain.qryExec.FieldByName('id').AsString));
      dmgMain.qryExec.Next;
    end;
    dmgMain.qryExec.Close;
  finally
    cbCliente.Items.EndUpdate;
  end;
end;

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

  cbFormaPago.Items.Clear;
  cbFormaPago.Items.Add('[Sin Forma de Pago]');
  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 TfrmPresupuestosEditor.FormDestroy(Sender: TObject);
begin
  if Assigned(FClientIds) then
    FreeAndNil(FClientIds);
  if Assigned(FDestinationIds) then
    FreeAndNil(FDestinationIds);
  if Assigned(FFormaPagoIds) then
    FreeAndNil(FFormaPagoIds);
end;

procedure TfrmPresupuestosEditor.cbClienteChange(Sender: TObject);
var
  LQryCli: TFDQuery;
  LFormaPagoId: string;
  LIdxFP: Integer;
begin
  if cbCliente.ItemIndex <> -1 then
  begin
    LoadDestinos(FClientIds[cbCliente.ItemIndex]);

    // Auto-seleccionar forma de pago predeterminada del cliente
    LQryCli := TFDQuery.Create(nil);
    try
      LQryCli.Connection := dmgMain.dbConn;
      LQryCli.SQL.Text := 'SELECT id_forma_pago FROM ge_clientes WHERE id = :id';
      LQryCli.ParamByName('id').AsString := FClientIds[cbCliente.ItemIndex];
      LQryCli.Open;
      if not LQryCli.IsEmpty then
      begin
        LFormaPagoId := LQryCli.FieldByName('id_forma_pago').AsString;
        if (LFormaPagoId <> '') and Assigned(FFormaPagoIds) then
        begin
          LIdxFP := FFormaPagoIds.IndexOf(LFormaPagoId);
          if LIdxFP >= 0 then
            cbFormaPago.ItemIndex := LIdxFP;
        end;
      end;
    finally
      LQryCli.Free;
    end;
  end
  else
  begin
    cbDestino.Items.Clear;
    FDestinationIds.Clear;
  end;
  UpdateClienteDetails;
  UpdateDestinoDetails;
end;

procedure TfrmPresupuestosEditor.LoadDestinos(const AClienteId: string);
begin
  cbDestino.Items.Clear;
  FDestinationIds.Clear;
  
  if AClienteId = '' then Exit;
  
  dmgMain.qryExec.Close;
  dmgMain.qryExec.SQL.Text := 'SELECT id, descripcion FROM ge_clientes_destinos WHERE id_cliente = :cli AND activo = true ORDER BY descripcion';
  dmgMain.qryExec.ParamByName('cli').AsString := AClienteId;
  dmgMain.qryExec.Open;
  while not dmgMain.qryExec.Eof do
  begin
    cbDestino.Items.Add(dmgMain.qryExec.FieldByName('descripcion').AsString);
    FDestinationIds.Add(TdmgMain.CleanUUID(dmgMain.qryExec.FieldByName('id').AsString));
    dmgMain.qryExec.Next;
  end;
  dmgMain.qryExec.Close;
end;

procedure TfrmPresupuestosEditor.cbDestinoChange(Sender: TObject);
begin
  UpdateDestinoDetails;
end;

procedure TfrmPresupuestosEditor.UpdateClienteDetails;
var
  LClienteId: string;
begin
  if cbCliente.ItemIndex = -1 then
  begin
    lblClienteInfo.Caption := '';
    Exit;
  end;

  LClienteId := FClientIds[cbCliente.ItemIndex];
  dmgMain.qryExec.Close;
  dmgMain.qryExec.SQL.Text := 'SELECT nif, domicilio, telefono, poblacion, codigo_postal, provincia FROM ge_clientes WHERE id = :id';
  dmgMain.qryExec.ParamByName('id').AsString := LClienteId;
  dmgMain.qryExec.Open;
  if not dmgMain.qryExec.IsEmpty then
  begin
    lblClienteInfo.Caption := 
      'NIF: ' + dmgMain.qryExec.FieldByName('nif').AsString + ' | Tel: ' + dmgMain.qryExec.FieldByName('telefono').AsString + sLineBreak +
      'Dir: ' + dmgMain.qryExec.FieldByName('domicilio').AsString + sLineBreak +
      'Pob: ' + dmgMain.qryExec.FieldByName('poblacion').AsString + ' (' + dmgMain.qryExec.FieldByName('codigo_postal').AsString + ') - ' + dmgMain.qryExec.FieldByName('provincia').AsString;
  end
  else
    lblClienteInfo.Caption := '';
  dmgMain.qryExec.Close;
end;

procedure TfrmPresupuestosEditor.UpdateDestinoDetails;
var
  LDestinoId: string;
begin
  if cbDestino.ItemIndex = -1 then
  begin
    lblDestinoInfo.Caption := '';
    Exit;
  end;

  LDestinoId := FDestinationIds[cbDestino.ItemIndex];
  dmgMain.qryExec.Close;
  dmgMain.qryExec.SQL.Text := 'SELECT direccion, poblacion, cp, provincia FROM ge_clientes_destinos WHERE id = :id';
  dmgMain.qryExec.ParamByName('id').AsString := LDestinoId;
  dmgMain.qryExec.Open;
  if not dmgMain.qryExec.IsEmpty then
  begin
    lblDestinoInfo.Caption := 
      'Dir: ' + dmgMain.qryExec.FieldByName('direccion').AsString + sLineBreak +
      'Pob: ' + dmgMain.qryExec.FieldByName('poblacion').AsString + ' (' + dmgMain.qryExec.FieldByName('cp').AsString + ') - ' + dmgMain.qryExec.FieldByName('provincia').AsString;
  end
  else
    lblDestinoInfo.Caption := '';
  dmgMain.qryExec.Close;
end;

procedure TfrmPresupuestosEditor.SetupIvaPickList;
var
  I: Integer;
  LCol: TColumn;
begin
  LCol := nil;
  for I := 0 to dbgItems.Columns.Count - 1 do
  begin
    if SameText(dbgItems.Columns[I].FieldName, 'tipo_iva') then
    begin
      LCol := dbgItems.Columns[I];
      Break;
    end;
  end;
  
  if Assigned(LCol) then
  begin
    if LCol.PickList.Count > 0 then Exit; // Ya configurado en memoria

    dmgMain.qryExec.Close;
    dmgMain.qryExec.SQL.Text := 'SELECT tipo FROM ge_tipos_iva WHERE activo = true ORDER BY tipo';
    dmgMain.qryExec.Open;
    LCol.PickList.Clear;
    while not dmgMain.qryExec.Eof do
    begin
      LCol.PickList.Add(FormatFloat('0.##', dmgMain.qryExec.FieldByName('tipo').AsFloat));
      dmgMain.qryExec.Next;
    end;
    dmgMain.qryExec.Close;
  end;
end;

procedure TfrmPresupuestosEditor.SetupFieldChangeEvents;
begin
  if qryLines.FindField('articulo_codigo') <> nil then
    qryLines.FieldByName('articulo_codigo').OnChange := qryLinesFieldChange;
  if qryLines.FindField('precio') <> nil then
    qryLines.FieldByName('precio').OnChange := qryLinesFieldChange;
  if qryLines.FindField('cantidad') <> nil then
    qryLines.FieldByName('cantidad').OnChange := qryLinesFieldChange;
  if qryLines.FindField('tipo_iva') <> nil then
    qryLines.FieldByName('tipo_iva').OnChange := qryLinesFieldChange;
  if qryLines.FindField('descuento_porcentaje') <> nil then
    qryLines.FieldByName('descuento_porcentaje').OnChange := qryLinesFieldChange;
  if qryLines.FindField('descuento_precio') <> nil then
    qryLines.FieldByName('descuento_precio').OnChange := qryLinesFieldChange;
end;

procedure TfrmPresupuestosEditor.qryLinesFieldChange(Sender: TField);
var
  LPrecio, LCantidad, LTipoIva, LSubtotal, LPorcDto, LDtoAmt, LBase, LIva, LTotal: Double;
  Qry: TFDQuery;
  LCodeStr, LClienteId: string;
  LArtId: Integer;
begin
  if not (qryLines.State in [dsEdit, dsInsert]) then Exit;
  
  if qryLines.FindField('articulo_codigo') <> nil then
    qryLines.FieldByName('articulo_codigo').OnChange := nil;
  if qryLines.FindField('precio') <> nil then
    qryLines.FieldByName('precio').OnChange := nil;
  if qryLines.FindField('cantidad') <> nil then
    qryLines.FieldByName('cantidad').OnChange := nil;
  if qryLines.FindField('tipo_iva') <> nil then
    qryLines.FieldByName('tipo_iva').OnChange := nil;
  if qryLines.FindField('descuento_porcentaje') <> nil then
    qryLines.FieldByName('descuento_porcentaje').OnChange := nil;
  if qryLines.FindField('descuento_precio') <> nil then
    qryLines.FieldByName('descuento_precio').OnChange := nil;

  try
    if SameText(Sender.FieldName, 'articulo_codigo') then
    begin
      LCodeStr := Trim(Sender.AsString);
      if LCodeStr <> '' then
      begin
        Qry := TFDQuery.Create(nil);
        try
          Qry.Connection := dmgMain.dbConn;
          Qry.SQL.Text :=
            'SELECT a.id, a.descripcion, COALESCE(a.pvp1, 0) AS pvp1, COALESCE(t.tipo, 21.00) as tipo_iva ' +
            'FROM ge_articulos a ' +
            'LEFT JOIN ge_tipos_iva t ON a.id_tipo_iva = t.id ' +
            'WHERE (CAST(a.id AS CHAR) = :code OR a.ean = :code OR EXISTS (SELECT 1 FROM ge_articulos_ean ean WHERE ean.articulo_id = a.id AND ean.ean = :code)) AND a.activo = 1 LIMIT 1';
          Qry.ParamByName('code').AsString := LCodeStr;
          Qry.Open;

          if not Qry.IsEmpty then
          begin
            LArtId := Qry.FieldByName('id').AsInteger;
            qryLines.FieldByName('articulo_id').AsInteger := LArtId;
            qryLines.FieldByName('articulo_codigo').AsString := Qry.FieldByName('id').AsString;
            qryLines.FieldByName('descripcion').AsString := Qry.FieldByName('descripcion').AsString;

            LPrecio := Qry.FieldByName('pvp1').AsFloat;
            LTipoIva := Qry.FieldByName('tipo_iva').AsFloat;

            LCantidad := qryLines.FieldByName('cantidad').AsFloat;
            if LCantidad <= 0 then LCantidad := 1.0;

            LClienteId := '';
            if (cbCliente.ItemIndex <> -1) and (FClientIds.Count > cbCliente.ItemIndex) then
              LClienteId := FClientIds[cbCliente.ItemIndex];

            // Jerarquía de precios: 1) Grupo -> 2) Cliente Especial -> 3) PVP1 Tarifa
            if (LClienteId <> '') and (LArtId > 0) then
              LPrecio := dmgMain.GetPrecioArticuloCliente(LClienteId, IntToStr(LArtId), dtpFecha.Date, LPrecio, LCantidad);

            qryLines.FieldByName('precio').AsFloat := LPrecio;
            qryLines.FieldByName('tipo_iva').AsFloat := LTipoIva;
          end
          else
          begin
            MessageDlg('⚠️ No se encontró ningún artículo activo con la referencia "' + LCodeStr + '".', mtWarning, [mbOK], 0);
          end;
        finally
          Qry.Free;
        end;
      end;
    end;

    LPrecio := qryLines.FieldByName('precio').AsFloat;
    LCantidad := qryLines.FieldByName('cantidad').AsFloat;
    LTipoIva := qryLines.FieldByName('tipo_iva').AsFloat;
    
    if SameText(Sender.FieldName, 'descuento_porcentaje') then
    begin
      LPorcDto := qryLines.FieldByName('descuento_porcentaje').AsFloat;
      LDtoAmt := (LPrecio * LCantidad) * (LPorcDto / 100.0);
      qryLines.FieldByName('descuento_precio').AsFloat := LDtoAmt;
    end
    else if SameText(Sender.FieldName, 'descuento_precio') then
    begin
      LDtoAmt := qryLines.FieldByName('descuento_precio').AsFloat;
      if (LPrecio * LCantidad) <> 0 then
        qryLines.FieldByName('descuento_porcentaje').AsFloat := (LDtoAmt / (LPrecio * LCantidad)) * 100.0
      else
        qryLines.FieldByName('descuento_porcentaje').AsFloat := 0;
    end;

    LSubtotal := LPrecio * LCantidad;
    LBase := LSubtotal - qryLines.FieldByName('descuento_precio').AsFloat;
    LIva := LBase * (LTipoIva / 100.0);
    LTotal := LBase + LIva;

    qryLines.FieldByName('base').AsFloat := LBase;
    qryLines.FieldByName('total').AsFloat := LTotal;
    
    if qryLines.FindField('iva') <> nil then
      qryLines.FieldByName('iva').AsFloat := LIva;
  finally
    SetupFieldChangeEvents;
  end;
end;

procedure TfrmPresupuestosEditor.cbSerieChange(Sender: TObject);
var
  Qry: TFDQuery;
begin
  if not FIsNewRecord then Exit;

  Qry := TFDQuery.Create(nil);
  try
    Qry.Connection := dmgMain.dbConn;
    Qry.SQL.Text := 'SELECT sig_numero_presupuesto FROM ge_series WHERE activo = 1 AND empresa_id = :emp AND serie = :ser';
    Qry.ParamByName('emp').AsString := dmgMain.CurrentCompanyId;
    Qry.ParamByName('ser').AsString := cbSerie.Text;
    Qry.Open;
    if not Qry.IsEmpty then
      edtNumero.Text := Qry.FieldByName('sig_numero_presupuesto').AsString
    else
      edtNumero.Text := '1';
  finally
    Qry.Free;
  end;
end;

procedure TfrmPresupuestosEditor.DoAnadir;
begin
  FIsNewRecord := True;
  FPresupuestoId := '';
  if cbSerie.Items.Count > 0 then
  begin
    cbSerie.ItemIndex := 0;
    cbSerieChange(cbSerie);
  end
  else
  begin
    edtNumero.Text := dmgMain.GetNextIDS('ge_presupuestos');
  end;
  dtpFecha.Date := Date;
  edtTotal.Text := '0,00';
  edtIva.Text := '0,00';
  edtBase.Text := '0,00';
  sgIvaDesglose.RowCount := 2;
  sgIvaDesglose.Cells[0, 1] := '';
  sgIvaDesglose.Cells[1, 1] := '';
  sgIvaDesglose.Cells[2, 1] := '';
  FTotalBase := 0;
  FTotalIva := 0;
  FTotalDoc := 0;
  
  cbCliente.ItemIndex := -1;
  cbDestino.Items.Clear;
  FDestinationIds.Clear;
  cbEstado.ItemIndex := 0;
  lblClienteInfo.Caption := '';
  lblDestinoInfo.Caption := '';
  edtObservaciones1.Text := '';

  qryLines.Close;
  qryLines.SQL.Text := 'SELECT *, (total - base) as iva FROM ge_presupuestos_lineas WHERE presupuesto_id = :id';
  qryLines.ParamByName('id').AsInteger := 0;
  qryLines.Open;
  SetupFieldChangeEvents;
end;

procedure TfrmPresupuestosEditor.DoModificar;
begin
  // Ya estamos en modo edición en este formulario.
end;

procedure TfrmPresupuestosEditor.DoPrimero;
begin
  if Assigned(FParentForm) and (FParentForm is TfrmPresupuestos) then
  begin
    TfrmPresupuestos(FParentForm).QryMain.First;
    FPresupuestoId := TfrmPresupuestos(FParentForm).QryMain.FieldByName('id').AsString;
    LoadData;
  end;
end;

procedure TfrmPresupuestosEditor.DoAnterior;
begin
  if Assigned(FParentForm) and (FParentForm is TfrmPresupuestos) then
  begin
    TfrmPresupuestos(FParentForm).QryMain.Prior;
    FPresupuestoId := TfrmPresupuestos(FParentForm).QryMain.FieldByName('id').AsString;
    LoadData;
  end;
end;

procedure TfrmPresupuestosEditor.DoSiguiente;
begin
  if Assigned(FParentForm) and (FParentForm is TfrmPresupuestos) then
  begin
    TfrmPresupuestos(FParentForm).QryMain.Next;
    FPresupuestoId := TfrmPresupuestos(FParentForm).QryMain.FieldByName('id').AsString;
    LoadData;
  end;
end;

procedure TfrmPresupuestosEditor.DoUltimo;
begin
  if Assigned(FParentForm) and (FParentForm is TfrmPresupuestos) then
  begin
    TfrmPresupuestos(FParentForm).QryMain.Last;
    FPresupuestoId := TfrmPresupuestos(FParentForm).QryMain.FieldByName('id').AsString;
    LoadData;
  end;
end;

function TfrmPresupuestosEditor.IsNewRecord: Boolean;
begin
  Result := FIsNewRecord;
end;

procedure TfrmPresupuestosEditor.btnNuevoClienteClick(Sender: TObject);
var
  LNewId: string;
begin
  if not Assigned(frmClientesEditor) then
    Application.CreateForm(TfrmClientesEditor, frmClientesEditor);
  
  frmClientesEditor.ClienteId := ''; // Nuevo cliente
  frmClientesEditor.ParentForm := Self;
  frmClientesEditor.ShowModal;
  
  LNewId := frmClientesEditor.ClienteId;
  if LNewId <> '' then
  begin
    LoadClientes;
    cbCliente.ItemIndex := FClientIds.IndexOf(LNewId);
    if cbCliente.ItemIndex <> -1 then
      cbClienteChange(cbCliente);
  end;
end;

procedure TfrmPresupuestosEditor.btnNuevoDestinoClick(Sender: TObject);
var
  LClienteId, LNewId: string;
  Qry: TFDQuery;
  LFrm: TfrmClientesDestinosEditor;
begin
  if cbCliente.ItemIndex = -1 then
  begin
    ShowMessage('Debe seleccionar un cliente antes de crear un nuevo destino.');
    Exit;
  end;

  LClienteId := FClientIds[cbCliente.ItemIndex];
  
  LFrm := TfrmClientesDestinosEditor.Create(Self);
  try
    Qry := TFDQuery.Create(nil);
    try
      Qry.Connection := dmgMain.dbConn;
      Qry.CachedUpdates := False;
      Qry.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 LIMIT 1';
      Qry.ParamByName('id_cliente').AsString := LClienteId;
      Qry.Open;
      
      Qry.Append;
      Qry.FieldByName('id_cliente').AsString := LClienteId;
      Qry.FieldByName('activo').AsInteger := 1;
      
      LFrm.DataSet := Qry;
      LFrm.IsNew := True;
      LFrm.ClienteId := LClienteId;
      
      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, id_user_update) ' +
            'VALUES (:id_cliente, :desc, :dir, :pob, :cp, :prov, :pais, :act, :dept, :centro, :ean_fac, :ean_cli, :ean_emi, :ean_rec, :user, :user)';

          dmgMain.qryExec.ParamByName('id_cliente').AsString := LClienteId;
          dmgMain.qryExec.ParamByName('desc').AsString := Qry.FieldByName('descripcion').AsString;
          dmgMain.qryExec.ParamByName('dir').AsString := Qry.FieldByName('direccion').AsString;
          dmgMain.qryExec.ParamByName('pob').AsString := Qry.FieldByName('poblacion').AsString;
          dmgMain.qryExec.ParamByName('cp').AsString := Qry.FieldByName('cp').AsString;
          dmgMain.qryExec.ParamByName('prov').AsString := Qry.FieldByName('provincia').AsString;
          dmgMain.qryExec.ParamByName('pais').AsString := Qry.FieldByName('pais').AsString;
          dmgMain.qryExec.ParamByName('act').AsBoolean := Qry.FieldByName('activo').AsInteger <> 0;
          dmgMain.qryExec.ParamByName('dept').AsString := Qry.FieldByName('departamento').AsString;
          dmgMain.qryExec.ParamByName('centro').AsString := Qry.FieldByName('centro').AsString;
          dmgMain.qryExec.ParamByName('ean_fac').AsString := Qry.FieldByName('ean_facturacion').AsString;
          dmgMain.qryExec.ParamByName('ean_cli').AsString := Qry.FieldByName('ean_cliente').AsString;
          dmgMain.qryExec.ParamByName('ean_emi').AsString := Qry.FieldByName('ean_emisor').AsString;
          dmgMain.qryExec.ParamByName('ean_rec').AsString := Qry.FieldByName('ean_receptor').AsString;
          dmgMain.qryExec.ParamByName('user').AsString := dmgMain.CurrentUserId;
          dmgMain.qryExec.ExecSQL;
          
          // Obtener el ID del nuevo registro insertado
          dmgMain.qryExec.Close;
          dmgMain.qryExec.SQL.Text := 'SELECT LAST_INSERT_ID()';
          dmgMain.qryExec.Open;
          LNewId := dmgMain.qryExec.Fields[0].AsString;
          dmgMain.qryExec.Close;
          
          LoadDestinos(LClienteId);
          if LNewId <> '' then
          begin
            cbDestino.ItemIndex := FDestinationIds.IndexOf(LNewId);
            cbDestinoChange(cbDestino);
          end;
        except
          on E: Exception do
            ShowMessage('Error al guardar el nuevo destino: ' + E.Message);
        end;
      end
      else
      begin
        Qry.Cancel;
      end;
    finally
      Qry.Free;
    end;
  finally
    LFrm.Free;
  end;
end;

procedure TfrmPresupuestosEditor.DoDuplicar;
var
  LOldPresupuestoId, LNewPresupuestoId: string;
  LNextNum: Integer;
  Qry: TFDQuery;
begin
  if FPresupuestoId = '' then Exit;
  
  if MessageDlg('¿Está seguro de que desea duplicar este presupuesto?', mtConfirmation, [mbYes, mbNo], 0) <> mrYes then
    Exit;

  LOldPresupuestoId := FPresupuestoId;
  
  LNextNum := 1;
  Qry := TFDQuery.Create(nil);
  try
    Qry.Connection := dmgMain.dbConn;
    Qry.SQL.Text := 'SELECT sig_numero_presupuesto FROM ge_series WHERE activo = 1 AND empresa_id = :emp AND serie = :ser';
    Qry.ParamByName('emp').AsString := dmgMain.CurrentCompanyId;
    Qry.ParamByName('ser').AsString := cbSerie.Text;
    Qry.Open;
    if not Qry.IsEmpty then
      LNextNum := Qry.FieldByName('sig_numero_presupuesto').AsInteger;
  finally
    Qry.Free;
  end;

  dmgMain.dbConn.StartTransaction;
  try
    // 1. Duplicar cabecera (sin id, auto-increment)
    dmgMain.qryExec.Close;
    dmgMain.qryExec.SQL.Text := 
      'INSERT INTO ge_presupuestos (empresa_id, cliente_id, clientedestino_id, serie, numero, fecha, base_imponible, iva, total, estado, observaciones1, observaciones2, id_user_creator, id_user_update) ' +
      'SELECT empresa_id, cliente_id, clientedestino_id, serie, :new_num, CURRENT_TIMESTAMP, base_imponible, iva, total, ''Pendiente'', observaciones1, observaciones2, :user, :user ' +
      'FROM ge_presupuestos WHERE id = :old_id';
    dmgMain.qryExec.ParamByName('old_id').AsInteger := StrToIntDef(LOldPresupuestoId, 0);
    dmgMain.qryExec.ParamByName('new_num').AsInteger := LNextNum;
    dmgMain.qryExec.ParamByName('user').AsString := dmgMain.CurrentUserId;
    dmgMain.qryExec.ExecSQL;

    // Obtener el ID auto-generado
    dmgMain.qryExec.Close;
    dmgMain.qryExec.SQL.Text := 'SELECT LAST_INSERT_ID() as new_id';
    dmgMain.qryExec.Open;
    LNewPresupuestoId := dmgMain.qryExec.FieldByName('new_id').AsString;
    dmgMain.qryExec.Close;

    // 2. Duplicar líneas
    dmgMain.qryExec.SQL.Text :=
      'INSERT INTO ge_presupuestos_lineas (presupuesto_id, articulo_codigo, articulo_id, posicion, descripcion, bultos, cantidad_bulto, cantidad, precio, tipo_iva, base, iva, total, ' +
      'descuento_porcentaje, descuento_precio, id_user_creator, id_user_update) ' +
      'SELECT :new_id, articulo_codigo, articulo_id, posicion, descripcion, bultos, cantidad_bulto, cantidad, precio, tipo_iva, base, iva, total, descuento_porcentaje, descuento_precio, :user, :user ' +
      'FROM ge_presupuestos_lineas WHERE presupuesto_id = :old_id';
    dmgMain.qryExec.ParamByName('new_id').AsInteger := StrToIntDef(LNewPresupuestoId, 0);
    dmgMain.qryExec.ParamByName('old_id').AsInteger := StrToIntDef(LOldPresupuestoId, 0);
    dmgMain.qryExec.ParamByName('user').AsString := dmgMain.CurrentUserId;
    dmgMain.qryExec.ExecSQL;

    // 3. Incrementar contador serie
    dmgMain.qryExec.Close;
    dmgMain.qryExec.SQL.Text := 'UPDATE ge_series SET sig_numero_presupuesto = sig_numero_presupuesto + 1 ' +
                                'WHERE activo = 1 AND empresa_id = :emp AND serie = :ser';
    dmgMain.qryExec.ParamByName('emp').AsString := dmgMain.CurrentCompanyId;
    dmgMain.qryExec.ParamByName('ser').AsString := cbSerie.Text;
    dmgMain.qryExec.ExecSQL;

    dmgMain.dbConn.Commit;
    
    FPresupuestoId := LNewPresupuestoId;
    FIsNewRecord := False;
    LoadData;
    
    if Assigned(FParentForm) and (FParentForm is TfrmPresupuestos) then
      TfrmPresupuestos(FParentForm).btnRefreshClick(nil);
      
    ShowMessage('Presupuesto duplicado con éxito. Nuevo número: ' + IntToStr(LNextNum));
  except
    on E: Exception do
    begin
      dmgMain.dbConn.Rollback;
      ShowMessage('Error al duplicar presupuesto: ' + E.Message);
    end;
  end;
end;

procedure TfrmPresupuestosEditor.btnRectClick(Sender: TObject);
var
  P: TPoint;
begin
  if HasUnsavedChanges or FIsNewRecord then
  begin
    ShowMessage('Guarde los cambios del presupuesto antes de generar otros documentos.');
    Exit;
  end;
  P := btnRect.Parent.ClientToScreen(Point(btnRect.Left, btnRect.Top + btnRect.Height));
  pmGenerar.Popup(P.X, P.Y);
end;

procedure TfrmPresupuestosEditor.pmGenerarItemClick(Sender: TObject);
var
  LTag: Integer;
  LNewDocNum, LNextNum: Integer;
  LNewDocId: Int64;
  LOrderNumStr: string;
  Qry: TFDQuery;
begin
  LTag := TMenuItem(Sender).Tag;
  
  if FPresupuestoId = '' then Exit;
  
  case LTag of
    1: // Crear Pedido
    begin
      if MessageDlg('¿Desea generar un Pedido de venta a partir de este Presupuesto?', mtConfirmation, [mbYes, mbNo], 0) <> mrYes then
        Exit;
        
      LOrderNumStr := cbSerie.Text + '-' + edtNumero.Text;
      
      // Comprobar si ya existe el pedido
      Qry := TFDQuery.Create(nil);
      try
        Qry.Connection := dmgMain.dbConn;
        Qry.SQL.Text := 'SELECT id FROM ge_pedidos WHERE empresa_id = :emp AND numero_pedido = :num';
        Qry.ParamByName('emp').AsString := dmgMain.CurrentCompanyId;
        Qry.ParamByName('num').AsString := LOrderNumStr;
        Qry.Open;
        if not Qry.IsEmpty then
        begin
          ShowMessage('Ya existe un pedido generado para este presupuesto (Nº ' + LOrderNumStr + ').');
          Exit;
        end;
      finally
        Qry.Free;
      end;
      
      dmgMain.dbConn.StartTransaction;
      try
        // Insertar cabecera de pedido
        dmgMain.qryExec.Close;
        dmgMain.qryExec.SQL.Text :=
          'INSERT INTO ge_pedidos (empresa_id, cliente_id, clientedestino_id, numero_pedido, fecha_pedido, fecha_entrega, estado, base_imponible, total, iva, observaciones, id_user_creator, id_user_update) ' +
          'SELECT empresa_id, cliente_id, clientedestino_id, :num_ped, CURRENT_TIMESTAMP, DATE_ADD(CURRENT_TIMESTAMP, INTERVAL 7 DAY), ''PENDIENTE'', base_imponible, total, iva, CONCAT(''Generado desde Presupuesto '', serie, ''-'', numero), :user, :user ' +
          'FROM ge_presupuestos WHERE id = :old_id';
        dmgMain.qryExec.ParamByName('num_ped').AsString := LOrderNumStr;
        dmgMain.qryExec.ParamByName('old_id').AsString := FPresupuestoId;
        dmgMain.qryExec.ParamByName('user').AsString := dmgMain.CurrentUserId;
        dmgMain.qryExec.ExecSQL;
        
        // Obtener ID generado
        dmgMain.qryExec.Close;
        dmgMain.qryExec.SQL.Text := 'SELECT LAST_INSERT_ID()';
        dmgMain.qryExec.Open;
        LNewDocId := dmgMain.qryExec.Fields[0].AsLargeInt;
        
        // Insertar líneas de pedido
        dmgMain.qryExec.Close;
        dmgMain.qryExec.SQL.Text :=
          'INSERT INTO ge_pedidos_lineas (id_pedido, id_articulo, descripcion, cantidad, precio, total) ' +
          'SELECT :new_id, NULLIF(NULLIF(articulo_id, ''''), ''0''), descripcion, cantidad, precio, total FROM ge_presupuestos_lineas WHERE presupuesto_id = :old_id';
        dmgMain.qryExec.ParamByName('new_id').AsLargeInt := LNewDocId;
        dmgMain.qryExec.ParamByName('old_id').AsString := FPresupuestoId;
        dmgMain.qryExec.ExecSQL;
        
        // Actualizar estado del presupuesto
        dmgMain.qryExec.Close;
        dmgMain.qryExec.SQL.Text := 'UPDATE ge_presupuestos SET estado = ''Aceptado'' WHERE id = :old_id';
        dmgMain.qryExec.ParamByName('old_id').AsString := FPresupuestoId;
        dmgMain.qryExec.ExecSQL;
        
        dmgMain.dbConn.Commit;
        
        cbEstado.ItemIndex := cbEstado.Items.IndexOf('Aceptado');
        ShowMessage('Pedido generado con éxito. Nº Pedido: ' + LOrderNumStr);
      except
        on E: Exception do
        begin
          dmgMain.dbConn.Rollback;
          ShowMessage('Error al generar el pedido: ' + E.Message);
        end;
      end;
    end;
    
    2: // Crear Albarán
    begin
      if MessageDlg('¿Desea generar un Albarán a partir de este Presupuesto?', mtConfirmation, [mbYes, mbNo], 0) <> mrYes then
        Exit;
        
      LNextNum := 1;
      Qry := TFDQuery.Create(nil);
      try
        Qry.Connection := dmgMain.dbConn;
        Qry.SQL.Text := 'SELECT sig_numero_albaran FROM ge_series WHERE activo = 1 AND empresa_id = :emp AND serie = :ser';
        Qry.ParamByName('emp').AsString := dmgMain.CurrentCompanyId;
        Qry.ParamByName('ser').AsString := cbSerie.Text;
        Qry.Open;
        if not Qry.IsEmpty then
          LNextNum := Qry.FieldByName('sig_numero_albaran').AsInteger;
      finally
        Qry.Free;
      end;
      
      dmgMain.dbConn.StartTransaction;
      try
        // Insertar cabecera de albarán
        dmgMain.qryExec.Close;
        dmgMain.qryExec.SQL.Text :=
          'INSERT INTO ge_albaranes (empresa_id, cliente_id, clientedestino_id, serie, numero, referencia, fecha, base_imponible, total, iva, observaciones1, observaciones2, facturado, id_forma_pago, id_user_creator, id_user_update) ' +
          'SELECT empresa_id, cliente_id, clientedestino_id, :serie, :numero, CONCAT(''Presupuesto '', serie, ''-'', numero), CURRENT_TIMESTAMP, base_imponible, total, iva, observaciones1, observaciones2, 0, id_forma_pago, :user, :user ' +
          'FROM ge_presupuestos WHERE id = :old_id';
        dmgMain.qryExec.ParamByName('serie').AsString := cbSerie.Text;
        dmgMain.qryExec.ParamByName('numero').AsInteger := LNextNum;
        dmgMain.qryExec.ParamByName('old_id').AsString := FPresupuestoId;
        dmgMain.qryExec.ParamByName('user').AsString := dmgMain.CurrentUserId;
        dmgMain.qryExec.ExecSQL;
        
        // Obtener ID generado
        dmgMain.qryExec.Close;
        dmgMain.qryExec.SQL.Text := 'SELECT LAST_INSERT_ID()';
        dmgMain.qryExec.Open;
        LNewDocId := dmgMain.qryExec.Fields[0].AsLargeInt;
        
        // Insertar líneas de albarán
        dmgMain.qryExec.Close;
        dmgMain.qryExec.SQL.Text :=
          'INSERT INTO ge_albaranes_lineas (albaran_id, articulo_codigo, articulo_id, posicion, descripcion, bultos, cantidad_bulto, cantidad, precio, tipo_iva, base, total, descuento_porcentaje, descuento_precio, iva, id_user_creator, id_user_update) ' +
          'SELECT :new_id, articulo_codigo, NULLIF(NULLIF(articulo_id, ''''), ''0''), posicion, descripcion, bultos, cantidad_bulto, cantidad, precio, tipo_iva, base, total, descuento_porcentaje, descuento_precio, iva, :user, :user ' +
          'FROM ge_presupuestos_lineas WHERE presupuesto_id = :old_id';
        dmgMain.qryExec.ParamByName('new_id').AsLargeInt := LNewDocId;
        dmgMain.qryExec.ParamByName('old_id').AsString := FPresupuestoId;
        dmgMain.qryExec.ParamByName('user').AsString := dmgMain.CurrentUserId;
        dmgMain.qryExec.ExecSQL;
        
        // Incrementar el contador en la tabla de series
        dmgMain.qryExec.Close;
        dmgMain.qryExec.SQL.Text := 'UPDATE ge_series SET sig_numero_albaran = sig_numero_albaran + 1 ' +
                                    'WHERE activo = 1 AND empresa_id = :emp AND serie = :ser';
        dmgMain.qryExec.ParamByName('emp').AsString := dmgMain.CurrentCompanyId;
        dmgMain.qryExec.ParamByName('ser').AsString := cbSerie.Text;
        dmgMain.qryExec.ExecSQL;
        
        // Actualizar estado del presupuesto
        dmgMain.qryExec.Close;
        dmgMain.qryExec.SQL.Text := 'UPDATE ge_presupuestos SET estado = ''Aceptado'' WHERE id = :old_id';
        dmgMain.qryExec.ParamByName('old_id').AsString := FPresupuestoId;
        dmgMain.qryExec.ExecSQL;
        
        dmgMain.dbConn.Commit;
        
        cbEstado.ItemIndex := cbEstado.Items.IndexOf('Aceptado');
        ShowMessage('Albarán generado con éxito. Nº Albarán: ' + cbSerie.Text + '-' + IntToStr(LNextNum));
      except
        on E: Exception do
        begin
          dmgMain.dbConn.Rollback;
          ShowMessage('Error al generar el albarán: ' + E.Message);
        end;
      end;
    end;
    
    3: // Crear Factura
    begin
      if MessageDlg('¿Desea generar una Factura a partir de este Presupuesto?', mtConfirmation, [mbYes, mbNo], 0) <> mrYes then
        Exit;
        
      LNextNum := 1;
      Qry := TFDQuery.Create(nil);
      try
        Qry.Connection := dmgMain.dbConn;
        Qry.SQL.Text := 'SELECT sig_numero_factura FROM ge_series WHERE activo = 1 AND empresa_id = :emp AND serie = :ser';
        Qry.ParamByName('emp').AsString := dmgMain.CurrentCompanyId;
        Qry.ParamByName('ser').AsString := cbSerie.Text;
        Qry.Open;
        if not Qry.IsEmpty then
          LNextNum := Qry.FieldByName('sig_numero_factura').AsInteger;
      finally
        Qry.Free;
      end;
      
      dmgMain.dbConn.StartTransaction;
      try
        // Insertar cabecera de factura
        dmgMain.qryExec.Close;
        dmgMain.qryExec.SQL.Text :=
          'INSERT INTO ge_facturas (empresa_id, cliente_id, clientedestino_id, serie, numero, referencia, fecha, base_imponible, total, iva, pagada, id_forma_pago, id_user_creator, id_user_update) ' +
          'SELECT empresa_id, cliente_id, clientedestino_id, serie, :numero, CONCAT(''Presupuesto '', serie, ''-'', numero), CURRENT_TIMESTAMP, base_imponible, total, iva, 0, id_forma_pago, :user, :user ' +
          'FROM ge_presupuestos WHERE id = :old_id';
        dmgMain.qryExec.ParamByName('numero').AsInteger := LNextNum;
        dmgMain.qryExec.ParamByName('old_id').AsString := FPresupuestoId;
        dmgMain.qryExec.ParamByName('user').AsString := dmgMain.CurrentUserId;
        dmgMain.qryExec.ExecSQL;
        
        // Obtener ID generado
        dmgMain.qryExec.Close;
        dmgMain.qryExec.SQL.Text := 'SELECT LAST_INSERT_ID()';
        dmgMain.qryExec.Open;
        LNewDocId := dmgMain.qryExec.Fields[0].AsLargeInt;
        
        // Insertar líneas de factura
        dmgMain.qryExec.Close;
        dmgMain.qryExec.SQL.Text :=
          'INSERT INTO ge_facturas_lineas (factura_id, articulo_codigo, articulo_id, posicion, descripcion, bultos, cantidad_bulto, cantidad, precio, tipo_iva, base, iva, total, descuento_porc, descuento_cant, id_user_creator, id_user_update) ' +
          'SELECT :new_id, articulo_codigo, NULLIF(NULLIF(articulo_id, ''''), ''0''), posicion, descripcion, bultos, cantidad_bulto, cantidad, precio, tipo_iva, base, iva, total, descuento_porcentaje, descuento_precio, :user, :user ' +
          'FROM ge_presupuestos_lineas WHERE presupuesto_id = :old_id';
        dmgMain.qryExec.ParamByName('new_id').AsLargeInt := LNewDocId;
        dmgMain.qryExec.ParamByName('old_id').AsString := FPresupuestoId;
        dmgMain.qryExec.ParamByName('user').AsString := dmgMain.CurrentUserId;
        dmgMain.qryExec.ExecSQL;
        
        // Incrementar el contador en la tabla de series
        dmgMain.qryExec.Close;
        dmgMain.qryExec.SQL.Text := 'UPDATE ge_series SET sig_numero_factura = sig_numero_factura + 1 ' +
                                    'WHERE activo = 1 AND empresa_id = :emp AND serie = :ser';
        dmgMain.qryExec.ParamByName('emp').AsString := dmgMain.CurrentCompanyId;
        dmgMain.qryExec.ParamByName('ser').AsString := cbSerie.Text;
        dmgMain.qryExec.ExecSQL;
        
        // Actualizar estado del presupuesto
        dmgMain.qryExec.Close;
        dmgMain.qryExec.SQL.Text := 'UPDATE ge_presupuestos SET estado = ''Facturado'' WHERE id = :old_id';
        dmgMain.qryExec.ParamByName('old_id').AsString := FPresupuestoId;
        dmgMain.qryExec.ExecSQL;
        
        dmgMain.dbConn.Commit;
        
        cbEstado.ItemIndex := cbEstado.Items.IndexOf('Facturado');
        ShowMessage('Factura generada con éxito. Nº Factura: ' + cbSerie.Text + '-' + IntToStr(LNextNum));
      except
        on E: Exception do
        begin
          dmgMain.dbConn.Rollback;
          ShowMessage('Error al generar la factura: ' + E.Message);
        end;
      end;
    end;
  end;
end;

procedure TfrmPresupuestosEditor.btnEliminarClick(Sender: TObject);
begin
  if FPresupuestoId = '' then Exit;
  
  if MessageDlg('¿Está seguro de que desea eliminar este presupuesto?', mtConfirmation, [mbYes, mbNo], 0) <> mrYes then
    Exit;

  dmgMain.dbConn.StartTransaction;
  try
    // 1. Borrar líneas de detalle
    dmgMain.qryExec.Close;
    dmgMain.qryExec.SQL.Text := 'DELETE FROM ge_presupuestos_lineas WHERE presupuesto_id = :id';
    dmgMain.qryExec.ParamByName('id').AsString := FPresupuestoId;
    dmgMain.qryExec.ExecSQL;

    // 2. Borrar cabecera
    dmgMain.qryExec.Close;
    dmgMain.qryExec.SQL.Text := 'DELETE FROM ge_presupuestos WHERE id = :id';
    dmgMain.qryExec.ParamByName('id').AsString := FPresupuestoId;
    dmgMain.qryExec.ExecSQL;

    dmgMain.dbConn.Commit;
    
    if Assigned(FParentForm) and (FParentForm is TfrmPresupuestos) then
      TfrmPresupuestos(FParentForm).btnRefreshClick(nil);
      
    ShowMessage('Presupuesto eliminado con éxito.');
    Close;
  except
    on E: Exception do
    begin
      dmgMain.dbConn.Rollback;
      ShowMessage('Error al eliminar el presupuesto: ' + E.Message);
    end;
  end;
end;

procedure TfrmPresupuestosEditor.btnDocsRelacionadosClick(Sender: TObject);
begin
  if (Trim(FPresupuestoId) = '') or FIsNewRecord then
  begin
    ShowMessage('Debe guardar el presupuesto antes de consultar sus documentos relacionados.');
    Exit;
  end;

  TfrmDocumentosRelacionados.MostrarDocumentos(Self, dvtPresupuesto, FPresupuestoId, cbSerie.Text + '/' + edtNumero.Text);
end;

procedure TfrmPresupuestosEditor.btnImprimirClick(Sender: TObject);
begin
  if (Trim(FPresupuestoId) = '') or FIsNewRecord then
  begin
    ShowMessage('Debe guardar el presupuesto antes de poder imprimirlo.');
    Exit;
  end;

  TPresupuestoPrintService.ImprimirPresupuesto(Self, FPresupuestoId);
end;

procedure TfrmPresupuestosEditor.btnBuscarClienteClick(Sender: TObject);
var
  LFrm: TfrmSelectCliente;
  LIdx: Integer;
begin
  LFrm := TfrmSelectCliente.Create(Self);
  try
    if LFrm.ShowModal = mrOk then
    begin
      if LFrm.SelectedId <> '' then
      begin
        LIdx := FClientIds.IndexOf(LFrm.SelectedId);
        if LIdx = -1 then
        begin
          LoadClientes;
          LIdx := FClientIds.IndexOf(LFrm.SelectedId);
        end;

        if LIdx <> -1 then
        begin
          cbCliente.ItemIndex := LIdx;
          cbClienteChange(cbCliente);

          // Si el cliente tiene 1 solo destino, auto-seleccionarlo; si tiene varios, abrir selector de destinos
          if cbDestino.Items.Count = 1 then
          begin
            cbDestino.ItemIndex := 0;
            cbDestinoChange(cbDestino);
          end
          else if cbDestino.Items.Count > 1 then
            btnBuscarDestinoClick(nil);
        end;
      end;
    end;
  finally
    LFrm.Free;
  end;
end;

procedure TfrmPresupuestosEditor.btnBuscarDestinoClick(Sender: TObject);
var
  LFrm: TfrmSelectDestino;
  LClienteId: string;
begin
  if cbCliente.ItemIndex = -1 then
  begin
    ShowMessage('Debe seleccionar un cliente antes de buscar sucursales/destinos.');
    Exit;
  end;

  LClienteId := FClientIds[cbCliente.ItemIndex];
  LFrm := TfrmSelectDestino.Create(Self);
  try
    LFrm.ClienteId := LClienteId;
    if LFrm.ShowModal = mrOk then
    begin
      LoadDestinos(LClienteId);
      if LFrm.SelectedId <> '' then
      begin
        cbDestino.ItemIndex := FDestinationIds.IndexOf(LFrm.SelectedId);
        cbDestinoChange(cbDestino);
      end;
    end;
  finally
    LFrm.Free;
  end;
end;

function TfrmPresupuestosEditor.ValidarYAsegurarDestino: Boolean;
var
  LClienteId: string;
  LDestinoId: string;
  LCantDestinos: Integer;
  LFrmSel: TfrmSelectDestino;
  LFrmDestEdit: TfrmClientesDestinosEditor;
  Qry: TFDQuery;
  LNewId: string;
begin
  Result := False;
  if (cbCliente.ItemIndex = -1) then
  begin
    ShowMessage('Debe seleccionar un cliente.');
    Exit;
  end;

  LClienteId := FClientIds[cbCliente.ItemIndex];

  // Si ya tiene un destino seleccionado válido
  if (cbDestino.ItemIndex <> -1) and Assigned(FDestinationIds) and (cbDestino.ItemIndex < FDestinationIds.Count) then
  begin
    LDestinoId := FDestinationIds[cbDestino.ItemIndex];
    if (LDestinoId <> '') and (LDestinoId <> '0') then
    begin
      Result := True;
      Exit;
    end;
  end;

  // Comprobar si el cliente ya tiene destinos registrados en la BD
  dmgMain.qryExec.Close;
  dmgMain.qryExec.SQL.Text := 'SELECT COUNT(*) as cant FROM ge_clientes_destinos WHERE id_cliente = :cli AND activo = 1';
  dmgMain.qryExec.ParamByName('cli').AsString := LClienteId;
  dmgMain.qryExec.Open;
  LCantDestinos := dmgMain.qryExec.FieldByName('cant').AsInteger;
  dmgMain.qryExec.Close;

  if LCantDestinos > 0 then
  begin
    ShowMessage('Este documento requiere un destino o sucursal de entrega asignado.' + sLineBreak +
                'Por favor, seleccione el destino de la lista a continuación.');
    LFrmSel := TfrmSelectDestino.Create(Self);
    try
      LFrmSel.ClienteId := LClienteId;
      if LFrmSel.ShowModal = mrOk then
      begin
        LoadDestinos(LClienteId);
        if LFrmSel.SelectedId <> '' then
        begin
          cbDestino.ItemIndex := FDestinationIds.IndexOf(LFrmSel.SelectedId);
          cbDestinoChange(cbDestino);
          Result := (cbDestino.ItemIndex <> -1);
        end;
      end;
    finally
      LFrmSel.Free;
    end;
  end
  else
  begin
    ShowMessage('El cliente seleccionado no tiene ninguna sucursal o destino de entrega configurado.' + sLineBreak +
                'A continuación se abrirá el formulario para crear el destino con la dirección del cliente ya cumplimentada.');
    LFrmDestEdit := TfrmClientesDestinosEditor.Create(Self);
    try
      Qry := TFDQuery.Create(nil);
      try
        Qry.Connection := dmgMain.dbConn;
        Qry.CachedUpdates := False;
        Qry.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 LIMIT 1';
        Qry.ParamByName('id_cliente').AsString := LClienteId;
        Qry.Open;

        Qry.Append;
        Qry.FieldByName('id_cliente').AsString := LClienteId;
        Qry.FieldByName('activo').AsInteger := 1;

        LFrmDestEdit.DataSet := Qry;
        LFrmDestEdit.IsNew := True;
        LFrmDestEdit.ClienteId := LClienteId;

        // Precargar automáticamente con los datos del cliente
        LFrmDestEdit.btnCopiarClienteClick(nil);

        if LFrmDestEdit.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, id_user_update) ' +
              'VALUES (:id_cliente, :desc, :dir, :pob, :cp, :prov, :pais, :act, :dept, :centro, :ean_fac, :ean_cli, :ean_emi, :ean_rec, :user, :user)';

            dmgMain.qryExec.ParamByName('id_cliente').AsString := LClienteId;
            dmgMain.qryExec.ParamByName('desc').AsString := Qry.FieldByName('descripcion').AsString;
            dmgMain.qryExec.ParamByName('dir').AsString := Qry.FieldByName('direccion').AsString;
            dmgMain.qryExec.ParamByName('pob').AsString := Qry.FieldByName('poblacion').AsString;
            dmgMain.qryExec.ParamByName('cp').AsString := Qry.FieldByName('cp').AsString;
            dmgMain.qryExec.ParamByName('prov').AsString := Qry.FieldByName('provincia').AsString;
            dmgMain.qryExec.ParamByName('pais').AsString := Qry.FieldByName('pais').AsString;
            dmgMain.qryExec.ParamByName('act').AsBoolean := Qry.FieldByName('activo').AsInteger <> 0;
            dmgMain.qryExec.ParamByName('dept').AsString := Qry.FieldByName('departamento').AsString;
            dmgMain.qryExec.ParamByName('centro').AsString := Qry.FieldByName('centro').AsString;
            dmgMain.qryExec.ParamByName('ean_fac').AsString := Qry.FieldByName('ean_facturacion').AsString;
            dmgMain.qryExec.ParamByName('ean_cli').AsString := Qry.FieldByName('ean_cliente').AsString;
            dmgMain.qryExec.ParamByName('ean_emi').AsString := Qry.FieldByName('ean_emisor').AsString;
            dmgMain.qryExec.ParamByName('ean_rec').AsString := Qry.FieldByName('ean_receptor').AsString;
            dmgMain.qryExec.ParamByName('user').AsString := dmgMain.CurrentUserId;
            dmgMain.qryExec.ExecSQL;

            // Obtener el ID del nuevo registro insertado
            dmgMain.qryExec.Close;
            dmgMain.qryExec.SQL.Text := 'SELECT LAST_INSERT_ID()';
            dmgMain.qryExec.Open;
            LNewId := dmgMain.qryExec.Fields[0].AsString;
            dmgMain.qryExec.Close;

            LoadDestinos(LClienteId);
            if LNewId <> '' then
            begin
              cbDestino.ItemIndex := FDestinationIds.IndexOf(LNewId);
              cbDestinoChange(cbDestino);
              Result := (cbDestino.ItemIndex <> -1);
            end;
          except
            on E: Exception do
              ShowMessage('Error al guardar el nuevo destino: ' + E.Message);
          end;
        end
        else
        begin
          Qry.Cancel;
        end;
      finally
        Qry.Free;
      end;
    finally
      LFrmDestEdit.Free;
    end;
  end;

  if not Result then
    ShowMessage('No se puede guardar el documento sin un destino de entrega asignado.');
end;

end.
