unit frm_EventosEditor;

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, Data.DB, FireDAC.Comp.Client, uAppTheme, FireDAC.Stan.Param, uBaseForm,
  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
  TfrmEventosEditor = class(TfrmBase)
    lblCodigo: TLabel;
    edtCodigo: TEdit;
    lblNombre: TLabel;
    edtNombre: TEdit;
    lblLugar: TLabel;
    edtLugar: TEdit;
    chkActivo: TCheckBox;
    lblFechaInicio: TLabel;
    dtpFechaInicio: TDateTimePicker;
    lblFechaFin: TLabel;
    dtpFechaFin: TDateTimePicker;
    lblDescripcion: TLabel;
    memDescripcion: TMemo;
    qryLines: TFDQuery;
    dsLines: TDataSource;
    procedure FormShow(Sender: TObject);
    procedure btnSaveClick(Sender: TObject);
    procedure btnSalirClick(Sender: TObject);
    procedure btnAddLineClick(Sender: TObject);
    procedure btnDelLineClick(Sender: TObject);
    procedure btnEliminarClick(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
    procedure btnImprimirClick(Sender: TObject); override;
  protected
    procedure Loaded; override;
    procedure DoAnadir; override;
    procedure DoModificar; override;
    procedure DoPrimero; override;
    procedure DoAnterior; override;
    procedure DoSiguiente; override;
    procedure DoUltimo; override;
  private
    FEventoId: string;
    FParentForm: TForm;
    FMemLines: TFDMemTable;
    FIsLoading: Boolean;
    procedure SetupMemTable;
    procedure LoadData;
    procedure qryLinesNewRecord(DataSet: TDataSet);
    procedure MemLinesAfterPost(DataSet: TDataSet);
    procedure MemLinesBeforeDelete(DataSet: TDataSet);
  public
    property EventoId: string read FEventoId write FEventoId;
    property ParentForm: TForm read FParentForm write FParentForm;
  end;

var
  frmEventosEditor: TfrmEventosEditor;

implementation

uses
  dmg_Main, uDbErrorHandler, frm_Eventos, frm_SelectEventMaterial,
  frxClass, frxDBSet;

{$R *.dfm}

procedure TfrmEventosEditor.Loaded;
begin
  inherited Loaded;
  OnShow := FormShow;
end;

procedure TfrmEventosEditor.SetupMemTable;
begin
  if not Assigned(FMemLines) then
  begin
    FMemLines := TFDMemTable.Create(Self);
    with FMemLines.FieldDefs do
    begin
      Add('id', ftInteger, 0, False);
      Add('evento_id', ftString, 50, False);
      Add('material_id', ftString, 50, False);
      Add('material_codigo', ftWideString, 50, False);
      Add('material_descripcion', ftWideString, 150, False);
      Add('cantidad', ftFloat, 0, False);
      Add('unidad', ftWideString, 10, False);
      Add('observaciones', ftWideString, 255, False);
    end;
    FMemLines.CreateDataSet;
    FMemLines.OnNewRecord := qryLinesNewRecord;
    FMemLines.AfterPost := MemLinesAfterPost;
    FMemLines.BeforeDelete := MemLinesBeforeDelete;
  end;
  dsLines.DataSet := FMemLines;
  dbgItems.DataSource := dsLines;
end;

procedure TfrmEventosEditor.FormShow(Sender: TObject);
begin
  TAppTheme.ApplyToForm(Self);

  btnGuardar.OnClick := btnSaveClick;
  btnSalir.OnClick := btnSalirClick;
  btnEliminar.OnClick := btnEliminarClick;
  btnNuevoArticulo.OnClick := btnAddLineClick;
  btnEliminaArticulo.OnClick := btnDelLineClick;
  btnImprimir.OnClick := btnImprimirClick;

  lblCodigo.Caption := 'ID:';
  edtCodigo.ReadOnly := True;
  edtCodigo.Color := clBtnFace;

  SetupMemTable;

  TsListado.TabVisible := False;
  pgcDetails.ActivePage := tsDatosEnvio;

  dbgItems.Columns.Clear;
  with dbgItems.Columns.Add do
  begin
    FieldName := 'material_codigo';
    Title.Caption := 'Código Material';
    Width := 120;
    ReadOnly := True;
  end;
  with dbgItems.Columns.Add do
  begin
    FieldName := 'material_descripcion';
    Title.Caption := 'Descripción';
    Width := 300;
    ReadOnly := False;
  end;
  with dbgItems.Columns.Add do
  begin
    FieldName := 'cantidad';
    Title.Caption := 'Cantidad';
    Width := 100;
  end;
  with dbgItems.Columns.Add do
  begin
    FieldName := 'unidad';
    Title.Caption := 'Unidad';
    Width := 80;
  end;
  with dbgItems.Columns.Add do
  begin
    FieldName := 'observaciones';
    Title.Caption := 'Observaciones';
    Width := 200;
  end;

  if FEventoId <> '' then
    LoadData
  else
    DoAnadir;
end;

procedure TfrmEventosEditor.FormDestroy(Sender: TObject);
begin
  if Assigned(FMemLines) then
    FreeAndNil(FMemLines);
end;

procedure TfrmEventosEditor.LoadData;
var
  Qry: TFDQuery;
begin
  if FEventoId = '' then Exit;
  SetupMemTable;

  Qry := TFDQuery.Create(nil);
  try
    Qry.Connection := dmgMain.dbConn;
    Qry.SQL.Text := 'SELECT * FROM ge_eventos WHERE id = :id';
    Qry.ParamByName('id').AsString := FEventoId;
    Qry.Open;
    if not Qry.IsEmpty then
    begin
      edtCodigo.Text := Qry.FieldByName('id').AsString;
      edtNombre.Text := Qry.FieldByName('nombre').AsString;
      edtLugar.Text := Qry.FieldByName('lugar').AsString;
      dtpFechaInicio.DateTime := Qry.FieldByName('fecha_inicio').AsDateTime;
      dtpFechaFin.DateTime := Qry.FieldByName('fecha_fin').AsDateTime;
      chkActivo.Checked := Qry.FieldByName('activo').AsInteger <> 0;
      memDescripcion.Lines.Text := Qry.FieldByName('descripcion').AsString;
    end;

    // Cargar líneas de materiales asignados en el dataset de memoria
    FIsLoading := True;
    FMemLines.DisableControls;
    try
      FMemLines.EmptyDataSet;
      Qry.Close;
      Qry.SQL.Text := 
        'SELECT em.id, em.evento_id, em.material_id, em.cantidad, em.unidad, em.observaciones, ' +
        '       COALESCE(mc.codigo, '''') as material_codigo, ' +
        '       IF(em.material_id IS NOT NULL, COALESCE(mc.descripcion, ''''), COALESCE(em.observaciones, '''')) as material_descripcion, ' +
        '       IF(em.material_id IS NOT NULL, COALESCE(em.observaciones, ''''), '''') as line_observaciones ' +
        'FROM ge_eventos_materiales em ' +
        'LEFT JOIN ge_eventos_materiales_cat mc ON em.material_id = mc.id ' +
        'WHERE em.evento_id = :evento_id ' +
        'ORDER BY em.id ASC';
      Qry.ParamByName('evento_id').AsString := FEventoId;
      Qry.Open;

      while not Qry.Eof do
      begin
        FMemLines.Append;
        FMemLines.FieldByName('id').AsInteger := Qry.FieldByName('id').AsInteger;
        FMemLines.FieldByName('evento_id').AsString := FEventoId;
        FMemLines.FieldByName('material_id').AsString := Qry.FieldByName('material_id').AsString;
        FMemLines.FieldByName('material_codigo').AsString := Qry.FieldByName('material_codigo').AsString;
        FMemLines.FieldByName('material_descripcion').AsString := Qry.FieldByName('material_descripcion').AsString;
        FMemLines.FieldByName('cantidad').AsFloat := Qry.FieldByName('cantidad').AsFloat;
        FMemLines.FieldByName('unidad').AsString := Qry.FieldByName('unidad').AsString;
        FMemLines.FieldByName('observaciones').AsString := Qry.FieldByName('line_observaciones').AsString;
        FMemLines.Post;
        Qry.Next;
      end;
      FMemLines.First;
    finally
      FIsLoading := False;
      FMemLines.EnableControls;
    end;
  finally
    Qry.Free;
  end;
end;

procedure TfrmEventosEditor.qryLinesNewRecord(DataSet: TDataSet);
begin
  DataSet.FieldByName('id').AsInteger := 0;
  DataSet.FieldByName('evento_id').AsString := FEventoId;
  DataSet.FieldByName('material_id').AsString := '';
  DataSet.FieldByName('material_codigo').AsString := '';
  DataSet.FieldByName('material_descripcion').AsString := '';
  DataSet.FieldByName('cantidad').AsFloat := 1.0;
  DataSet.FieldByName('unidad').AsString := 'ud';
  DataSet.FieldByName('observaciones').AsString := '';
end;

procedure TfrmEventosEditor.MemLinesAfterPost(DataSet: TDataSet);
var
  LId: Integer;
  LMatIdStr, LMatDesc, LObs: string;
  Qry: TFDQuery;
begin
  // Solo persistir automáticamente si el evento ya existe en base de datos
  if FEventoId = '' then Exit;
  if FIsLoading then Exit;

  LId := DataSet.FieldByName('id').AsInteger;
  LMatIdStr := Trim(DataSet.FieldByName('material_id').AsString);
  LMatDesc := Trim(DataSet.FieldByName('material_descripcion').AsString);
  LObs := Trim(DataSet.FieldByName('observaciones').AsString);

  // Si no tiene material_id (línea libre sin código), el texto se almacena en observaciones
  if LMatIdStr = '' then
  begin
    if LObs <> '' then
      LObs := LMatDesc + ' - ' + LObs
    else
      LObs := LMatDesc;
  end;

  // Si la fila está vacía, no guardar
  if (LMatIdStr = '') and (LObs = '') and (DataSet.FieldByName('cantidad').AsFloat <= 0) then
    Exit;

  Qry := TFDQuery.Create(nil);
  try
    Qry.Connection := dmgMain.dbConn;
    if LId > 0 then
    begin
      Qry.SQL.Text := 
        'UPDATE ge_eventos_materiales SET ' +
        '  material_id = :material_id, ' +
        '  cantidad = :cantidad, ' +
        '  unidad = :unidad, ' +
        '  observaciones = :observaciones, ' +
        '  id_user_update = :user ' +
        'WHERE id = :id';
      Qry.ParamByName('id').AsInteger := LId;
    end
    else
    begin
      Qry.SQL.Text := 
        'INSERT INTO ge_eventos_materiales (evento_id, material_id, cantidad, unidad, observaciones, id_user_creator, id_user_update) ' +
        'VALUES (:evento_id, :material_id, :cantidad, :unidad, :observaciones, :user, :user)';
      Qry.ParamByName('evento_id').AsString := FEventoId;
    end;

    Qry.ParamByName('material_id').DataType := ftInteger;
    if LMatIdStr <> '' then
      Qry.ParamByName('material_id').AsInteger := StrToIntDef(LMatIdStr, 0)
    else
      Qry.ParamByName('material_id').Clear;

    Qry.ParamByName('cantidad').AsFloat := DataSet.FieldByName('cantidad').AsFloat;
    Qry.ParamByName('unidad').AsString := DataSet.FieldByName('unidad').AsString;
    Qry.ParamByName('observaciones').AsString := LObs;
    Qry.ParamByName('user').AsInteger := StrToIntDef(dmgMain.CurrentUserId, 1);
    Qry.ExecSQL;

    if LId <= 0 then
    begin
      Qry.Close;
      Qry.SQL.Text := 'SELECT LAST_INSERT_ID() as new_id';
      Qry.Open;
      LId := Qry.FieldByName('new_id').AsInteger;
      FIsLoading := True;
      try
        DataSet.Edit;
        DataSet.FieldByName('id').AsInteger := LId;
        DataSet.Post;
      finally
        FIsLoading := False;
      end;
    end;
  finally
    Qry.Free;
  end;
end;

procedure TfrmEventosEditor.MemLinesBeforeDelete(DataSet: TDataSet);
var
  LId: Integer;
  Qry: TFDQuery;
begin
  if FEventoId = '' then Exit;
  if FIsLoading then Exit;

  LId := DataSet.FieldByName('id').AsInteger;
  if LId > 0 then
  begin
    Qry := TFDQuery.Create(nil);
    try
      Qry.Connection := dmgMain.dbConn;
      Qry.SQL.Text := 'DELETE FROM ge_eventos_materiales WHERE id = :id';
      Qry.ParamByName('id').AsInteger := LId;
      Qry.ExecSQL;
    finally
      Qry.Free;
    end;
  end;
end;

procedure TfrmEventosEditor.DoAnadir;
begin
  FEventoId := '';
  edtCodigo.Text := '(Autonumérico)';
  edtNombre.Clear;
  edtLugar.Clear;
  dtpFechaInicio.DateTime := Now;
  dtpFechaFin.DateTime := Now + 1.0;
  chkActivo.Checked := True;
  memDescripcion.Clear;

  SetupMemTable;
  FIsLoading := True;
  try
    FMemLines.EmptyDataSet;
  finally
    FIsLoading := False;
  end;
  
  edtNombre.SetFocus;
end;

procedure TfrmEventosEditor.DoModificar;
begin
  edtNombre.SetFocus;
end;

procedure TfrmEventosEditor.DoPrimero;
var
  LParent: TfrmEventos;
begin
  LParent := nil;
  if Assigned(FParentForm) and (FParentForm is TfrmEventos) then
    LParent := TfrmEventos(FParentForm)
  else if Assigned(frmEventos) then
    LParent := frmEventos;

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

procedure TfrmEventosEditor.DoAnterior;
var
  LParent: TfrmEventos;
begin
  LParent := nil;
  if Assigned(FParentForm) and (FParentForm is TfrmEventos) then
    LParent := TfrmEventos(FParentForm)
  else if Assigned(frmEventos) then
    LParent := frmEventos;

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

procedure TfrmEventosEditor.DoSiguiente;
var
  LParent: TfrmEventos;
begin
  LParent := nil;
  if Assigned(FParentForm) and (FParentForm is TfrmEventos) then
    LParent := TfrmEventos(FParentForm)
  else if Assigned(frmEventos) then
    LParent := frmEventos;

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

procedure TfrmEventosEditor.DoUltimo;
var
  LParent: TfrmEventos;
begin
  LParent := nil;
  if Assigned(FParentForm) and (FParentForm is TfrmEventos) then
    LParent := TfrmEventos(FParentForm)
  else if Assigned(frmEventos) then
    LParent := frmEventos;

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

procedure TfrmEventosEditor.btnAddLineClick(Sender: TObject);
var
  i: Integer;
  LMatId, LMatCode, LMatDesc: string;
begin
  SetupMemTable;
  if not Assigned(frmSelectEventMaterial) then
    Application.CreateForm(TfrmSelectEventMaterial, frmSelectEventMaterial);
    
  frmSelectEventMaterial.MultiSelect := True;
  if frmSelectEventMaterial.ShowModal = mrOk then
  begin
    FMemLines.DisableControls;
    try
      for i := 0 to frmSelectEventMaterial.SelectedIds.Count - 1 do
      begin
        LMatId := frmSelectEventMaterial.SelectedIds[i];
        LMatCode := frmSelectEventMaterial.SelectedCodes[i];
        LMatDesc := frmSelectEventMaterial.SelectedDescs[i];
        
        FMemLines.Append;
        FMemLines.FieldByName('id').AsInteger := 0;
        FMemLines.FieldByName('evento_id').AsString := FEventoId;
        FMemLines.FieldByName('material_id').AsString := LMatId;
        FMemLines.FieldByName('material_codigo').AsString := LMatCode;
        FMemLines.FieldByName('material_descripcion').AsString := LMatDesc;
        FMemLines.FieldByName('cantidad').AsFloat := 1.0;
        FMemLines.FieldByName('unidad').AsString := 'ud';
        FMemLines.FieldByName('observaciones').AsString := '';
        FMemLines.Post;
      end;
    finally
      FMemLines.EnableControls;
    end;
  end;
end;

procedure TfrmEventosEditor.btnDelLineClick(Sender: TObject);
begin
  if Assigned(FMemLines) and not FMemLines.IsEmpty then
    FMemLines.Delete;
end;

procedure TfrmEventosEditor.btnSalirClick(Sender: TObject);
begin
  ModalResult := mrCancel;
  Close;
end;

procedure TfrmEventosEditor.btnEliminarClick(Sender: TObject);
var
  LNextId: string;
  LParent: TfrmEventos;
begin
  if FEventoId = '' then
  begin
    ShowMessage('No hay ningún registro guardado para eliminar.');
    Exit;
  end;

  if MessageDlg('¿Está seguro de que desea eliminar este evento y todos sus materiales asignados?', 
                mtWarning, [mbYes, mbNo], 0) = mrYes then
  begin
    LNextId := '';
    LParent := nil;
    if Assigned(FParentForm) and (FParentForm is TfrmEventos) then
      LParent := TfrmEventos(FParentForm)
    else if Assigned(frmEventos) then
      LParent := frmEventos;

    if Assigned(LParent) and Assigned(LParent.QryMain) then
    begin
      LParent.QryMain.Next;
      if not LParent.QryMain.Eof then
      begin
        LNextId := LParent.QryMain.FieldByName('id').AsString;
        LParent.QryMain.Prior;
      end
      else
      begin
        LParent.QryMain.Prior;
        LParent.QryMain.Prior;
        if not LParent.QryMain.Bof then
        begin
          LNextId := LParent.QryMain.FieldByName('id').AsString;
          LParent.QryMain.Next;
        end
        else
        begin
          LParent.QryMain.Next;
        end;
      end;
    end;

    try
      // Borrado en cascada manual
      dmgMain.qryExec.Close;
      dmgMain.qryExec.SQL.Text := 'DELETE FROM ge_eventos_materiales WHERE evento_id = :id';
      dmgMain.qryExec.ParamByName('id').AsString := FEventoId;
      dmgMain.qryExec.ExecSQL;

      dmgMain.qryExec.Close;
      dmgMain.qryExec.SQL.Text := 'DELETE FROM ge_eventos WHERE id = :id';
      dmgMain.qryExec.ParamByName('id').AsString := FEventoId;
      dmgMain.qryExec.ExecSQL;

      if Assigned(LParent) then
      begin
        LParent.btnRefreshClick(nil);
        if LNextId <> '' then
        begin
          LParent.QryMain.Locate('id', LNextId, []);
          FEventoId := LNextId;
          LoadData;
        end;
      end;

      if LNextId = '' then
      begin
        ShowMessage('Se ha eliminado el único registro disponible.');
        ModalResult := mrOk;
      end;
    except
      on E: Exception do
        TDbErrorHandler.HandleException(E, 'Error al eliminar el evento');
    end;
  end;
end;

procedure TfrmEventosEditor.btnSaveClick(Sender: TObject);
var
  LIsNew: Boolean;
  LMatId, LMatDesc, LObs: string;
begin
  if Trim(edtNombre.Text) = '' then
  begin
    ShowMessage('Debe ingresar un nombre para el evento.');
    edtNombre.SetFocus;
    Exit;
  end;

  SetupMemTable;
  if FMemLines.State in [dsEdit, dsInsert] then
    FMemLines.Post;

  LIsNew := (FEventoId = '');

  dmgMain.dbConn.StartTransaction;
  try
    // 1. Cabecera
    dmgMain.qryExec.Close;
    if LIsNew then
    begin
      dmgMain.qryExec.SQL.Text := 
        'INSERT INTO ge_eventos (nombre, lugar, fecha_inicio, fecha_fin, descripcion, activo, id_user_creator, id_user_update) ' +
        'VALUES (:nombre, :lugar, :fecha_inicio, :fecha_fin, :descripcion, :activo, :user, :user)';
    end
    else
    begin
      dmgMain.qryExec.SQL.Text := 
        'UPDATE ge_eventos SET nombre = :nombre, lugar = :lugar, ' +
        ' fecha_inicio = :fecha_inicio, fecha_fin = :fecha_fin, descripcion = :descripcion, ' +
        ' activo = :activo, id_user_update = :user WHERE id = :id';
      dmgMain.qryExec.ParamByName('id').AsString := FEventoId;
    end;
    dmgMain.qryExec.ParamByName('nombre').AsString := Trim(edtNombre.Text);
    dmgMain.qryExec.ParamByName('lugar').AsString := Trim(edtLugar.Text);
    dmgMain.qryExec.ParamByName('fecha_inicio').AsDateTime := dtpFechaInicio.DateTime;
    dmgMain.qryExec.ParamByName('fecha_fin').AsDateTime := dtpFechaFin.DateTime;
    dmgMain.qryExec.ParamByName('descripcion').AsString := memDescripcion.Lines.Text;
    dmgMain.qryExec.ParamByName('activo').AsBoolean := chkActivo.Checked;
    dmgMain.qryExec.ParamByName('user').AsInteger := StrToIntDef(dmgMain.CurrentUserId, 1);
    dmgMain.qryExec.ExecSQL;

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

      // Para un evento recién creado, guardar las líneas acumuladas en memoria
      FMemLines.First;
      while not FMemLines.Eof do
      begin
        LMatId := Trim(FMemLines.FieldByName('material_id').AsString);
        LMatDesc := Trim(FMemLines.FieldByName('material_descripcion').AsString);
        LObs := Trim(FMemLines.FieldByName('observaciones').AsString);

        if LMatId = '' then
        begin
          if LObs <> '' then
            LObs := LMatDesc + ' - ' + LObs
          else
            LObs := LMatDesc;
        end;

        if (LMatId <> '') or (LObs <> '') or (FMemLines.FieldByName('cantidad').AsFloat > 0) then
        begin
          dmgMain.qryExec.Close;
          dmgMain.qryExec.SQL.Text := 
            'INSERT INTO ge_eventos_materiales (evento_id, material_id, cantidad, unidad, observaciones, id_user_creator, id_user_update) ' +
            'VALUES (:evento_id, :material_id, :cantidad, :unidad, :observaciones, :user, :user)';
          dmgMain.qryExec.ParamByName('evento_id').AsString := FEventoId;
          dmgMain.qryExec.ParamByName('material_id').DataType := ftInteger;
          if LMatId <> '' then
            dmgMain.qryExec.ParamByName('material_id').AsInteger := StrToIntDef(LMatId, 0)
          else
            dmgMain.qryExec.ParamByName('material_id').Clear;

          dmgMain.qryExec.ParamByName('cantidad').AsFloat := FMemLines.FieldByName('cantidad').AsFloat;
          dmgMain.qryExec.ParamByName('unidad').AsString := FMemLines.FieldByName('unidad').AsString;
          dmgMain.qryExec.ParamByName('observaciones').AsString := LObs;
          dmgMain.qryExec.ParamByName('user').AsInteger := StrToIntDef(dmgMain.CurrentUserId, 1);
          dmgMain.qryExec.ExecSQL;
        end;
        FMemLines.Next;
      end;
    end;

    dmgMain.dbConn.Commit;
    ResetChangeTracking;
    
    LoadData;
    
    if Assigned(FParentForm) and (FParentForm is TfrmEventos) then
      TfrmEventos(FParentForm).btnRefreshClick(nil);
    ShowMessage('Evento guardado correctamente.');
  except
    on E: Exception do
    begin
      dmgMain.dbConn.Rollback;
      TDbErrorHandler.HandleException(E, 'Error al guardar el evento');
    end;
  end;
end;

procedure TfrmEventosEditor.btnImprimirClick(Sender: TObject);
var
  QryEvento, QryMateriales, QryLogo: TFDQuery;
  LReportPath, LLogoPath: string;
  LImgObj: TfrxComponent;
  LStream: TMemoryStream;
begin
  if Trim(FEventoId) = '' then
  begin
    ShowMessage('Guarde o seleccione un evento antes de imprimir.');
    Exit;
  end;

  LReportPath := ExtractFilePath(ParamStr(0)) + 'reportes\EventoDetalle.fr3';
  if not FileExists(LReportPath) then
  begin
    ShowMessage('No se encuentra la plantilla del reporte en: ' + LReportPath);
    Exit;
  end;

  QryEvento := TFDQuery.Create(nil);
  QryMateriales := TFDQuery.Create(nil);
  try
    // Preparar query del evento maestro
    QryEvento.Connection := dmgMain.dbConn;
    QryEvento.SQL.Text :=
      'SELECT id, id as codigo, nombre, lugar, fecha_inicio, fecha_fin, descripcion, activo ' +
      'FROM ge_eventos WHERE id = :id';
    QryEvento.ParamByName('id').AsString := Trim(FEventoId);
    QryEvento.Open;

    if QryEvento.IsEmpty then
    begin
      ShowMessage('No se ha encontrado el evento en la base de datos.' + sLineBreak +
                  'Por favor, guarde el evento primero.');
      Exit;
    end;

    // Preparar query de materiales detalle
    QryMateriales.Connection := dmgMain.dbConn;
    QryMateriales.SQL.Text :=
      'SELECT em.cantidad, em.unidad, ' +
      '  IF(em.material_id IS NOT NULL, COALESCE(em.observaciones, ''''), '''') as observaciones, ' +
      '  COALESCE(mc.codigo, '''') as material_codigo, ' +
      '  IF(em.material_id IS NOT NULL, COALESCE(mc.descripcion, ''''), COALESCE(em.observaciones, '''')) as material_descripcion ' +
      'FROM ge_eventos_materiales em ' +
      'LEFT JOIN ge_eventos_materiales_cat mc ON em.material_id = mc.id ' +
      'WHERE em.evento_id = :evento_id ' +
      'ORDER BY em.id ASC';
    QryMateriales.ParamByName('evento_id').AsString := Trim(FEventoId);
    QryMateriales.Open;

    // Configurar los frxDBDatasets de diseño
    dmgMain.frxDBMaster.DataSet := QryEvento;
    dmgMain.frxDBMaster.UserName := 'frxDBMaster';

    dmgMain.frxDBDetalle.DataSet := QryMateriales;
    dmgMain.frxDBDetalle.UserName := 'frxDBDetalle';

    // 1. Cargar la plantilla PRIMERO
    dmgMain.frxInforme.LoadFromFile(LReportPath);

    // 2. Cargar logo de empresa en el componente imgLogo de la cabecera
    LImgObj := dmgMain.frxInforme.FindObject('imgLogo');
    if Assigned(LImgObj) and (LImgObj is TfrxPictureView) then
    begin
      QryLogo := TFDQuery.Create(nil);
      try
        QryLogo.Connection := dmgMain.dbConn;
        QryLogo.SQL.Text := 'SELECT logo FROM ge_empresas WHERE id = :emp_id LIMIT 1';
        QryLogo.ParamByName('emp_id').AsString := dmgMain.CurrentCompanyId;
        QryLogo.Open;
        if (not QryLogo.IsEmpty) and (not TBlobField(QryLogo.FieldByName('logo')).IsNull) then
        begin
          LStream := TMemoryStream.Create;
          try
            TBlobField(QryLogo.FieldByName('logo')).SaveToStream(LStream);
            LStream.Position := 0;
            if LStream.Size > 0 then
            begin
              TfrxPictureView(LImgObj).Picture.LoadFromStream(LStream);
              TfrxPictureView(LImgObj).Visible := True;
            end
            else
              TfrxPictureView(LImgObj).Visible := False;
          finally
            LStream.Free;
          end;
        end
        else
        begin
          LLogoPath := ExtractFilePath(ParamStr(0)) + 'logo_raul.png';
          if FileExists(LLogoPath) then
          begin
            TfrxPictureView(LImgObj).Picture.LoadFromFile(LLogoPath);
            TfrxPictureView(LImgObj).Visible := True;
          end
          else
            TfrxPictureView(LImgObj).Visible := False;
        end;
      finally
        QryLogo.Free;
      end;
    end;

    // 3. Registrar datasets DESPUÉS de cargar la plantilla
    dmgMain.frxInforme.DataSets.Clear;
    dmgMain.frxInforme.DataSets.Add(dmgMain.frxDBMaster);
    dmgMain.frxInforme.DataSets.Add(dmgMain.frxDBDetalle);
    dmgMain.frxInforme.EnabledDataSets.Clear;
    dmgMain.frxInforme.EnabledDataSets.Add(dmgMain.frxDBMaster);
    dmgMain.frxInforme.EnabledDataSets.Add(dmgMain.frxDBDetalle);

    // 4. Mostrar el reporte
    dmgMain.frxInforme.ShowReport;
  finally
    QryEvento.Free;
    QryMateriales.Free;
  end;
end;

end.
