unit frm_CentrosZonas;

interface

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

type
  TNodeType = (ntRoot, ntCentro, ntZona, ntEquipo, ntSensor);

  TNodeData = class
  public
    NodeType: TNodeType;
    TipoCentro: string; // 'OBRADOR' o 'ALMACEN'
    IdCentro: Integer;
    IdZona: Integer;
    IdEquipo: Integer;
    IdSensor: Integer;
    Nombre: string;
  end;

  TfrmCentrosZonas = class(TForm)
    pnlTop: TPanel;
    lblFiltroTipo: TLabel;
    cbbFiltroTipo: TComboBox;
    lblBuscar: TLabel;
    edtBuscar: TEdit;
    btnRefrescar: TSpeedButton;
    btnNuevaZona: TSpeedButton;
    btnEditarZona: TSpeedButton;
    btnEliminarZona: TSpeedButton;
    btnNuevoEquipo: TSpeedButton;
    btnEditarEquipo: TSpeedButton;
    btnEliminarEquipo: TSpeedButton;
    pnlLeft: TPanel;
    splLeft: TSplitter;
    pnlCenter: TPanel;
    tvJerarquia: TTreeView;
    pnlHeaderTree: TPanel;
    lblTreeTitle: TLabel;
    pgcDetalles: TPageControl;
    tsEquipos: TTabSheet;
    tsSensores: TTabSheet;
    tsTiempos: TTabSheet;
    tsProcesos: TTabSheet;
    pnlToolbarSensores: TPanel;
    btnNuevoSensor: TSpeedButton;
    btnEditarSensor: TSpeedButton;
    btnEliminarSensor: TSpeedButton;
    dbgEquipos: TDBGrid;
    dbgSensores: TDBGrid;
    dbgTiempos: TDBGrid;
    dbgProcesos: TDBGrid;
    dsEquipos: TDataSource;
    qryEquipos: TFDQuery;
    dsSensores: TDataSource;
    qrySensores: TFDQuery;
    dsTiempos: TDataSource;
    qryTiempos: TFDQuery;
    dsProcesos: TDataSource;
    qryProcesos: TFDQuery;
    pnlInfoSeleccion: TPanel;
    lblInfoSeleccion: TLabel;
    procedure FormCreate(Sender: TObject);
    procedure FormShow(Sender: TObject);
    procedure FormDestroy(Sender: TObject);
    procedure btnRefrescarClick(Sender: TObject);
    procedure cbbFiltroTipoChange(Sender: TObject);
    procedure edtBuscarChange(Sender: TObject);
    procedure tvJerarquiaChange(Sender: TObject; Node: TTreeNode);
    procedure tvJerarquiaDblClick(Sender: TObject);
    procedure btnNuevaZonaClick(Sender: TObject);
    procedure btnEditarZonaClick(Sender: TObject);
    procedure btnEliminarZonaClick(Sender: TObject);
    procedure btnNuevoEquipoClick(Sender: TObject);
    procedure btnEditarEquipoClick(Sender: TObject);
    procedure btnEliminarEquipoClick(Sender: TObject);
    procedure btnNuevoSensorClick(Sender: TObject);
    procedure btnEditarSensorClick(Sender: TObject);
    procedure btnEliminarSensorClick(Sender: TObject);
    procedure dbgEquiposDblClick(Sender: TObject);
    procedure dbgSensoresDblClick(Sender: TObject);
    procedure dbgCustomDrawColumnCell(Sender: TObject; const Rect: TRect; DataCol: Integer; Column: TColumn; State: TGridDrawState);
  private
    procedure LimpiarTreeData;
    procedure CargarArbolJerarquico;
    procedure ActualizarDetalles(ANode: TTreeNode);
    function GetSelectedNodeData: TNodeData;
  public
    procedure RefrescarTodo;
  end;

var
  frmCentrosZonas: TfrmCentrosZonas;

implementation

uses
  dmg_Main, uDbErrorHandler, frm_CentrosZonasEditor, frm_ZonasEquiposEditor,
  frm_EquiposSensoresEditor, frm_RegistrarOperarioZona, frm_LotesProcesosEditor;

{$R *.dfm}

procedure TfrmCentrosZonas.FormCreate(Sender: TObject);
begin
  qryEquipos.Connection := dmgMain.dbConn;
  qrySensores.Connection := dmgMain.dbConn;
  qryTiempos.Connection := dmgMain.dbConn;
  qryProcesos.Connection := dmgMain.dbConn;
end;

procedure TfrmCentrosZonas.FormShow(Sender: TObject);
begin
  TAppTheme.ApplyToForm(Self);
  RefrescarTodo;
end;

procedure TfrmCentrosZonas.FormDestroy(Sender: TObject);
begin
  LimpiarTreeData;
  if frmCentrosZonas = Self then
    frmCentrosZonas := nil;
end;

procedure TfrmCentrosZonas.LimpiarTreeData;
var
  i: Integer;
begin
  for i := 0 to tvJerarquia.Items.Count - 1 do
  begin
    if Assigned(tvJerarquia.Items[i].Data) then
    begin
      TObject(tvJerarquia.Items[i].Data).Free;
      tvJerarquia.Items[i].Data := nil;
    end;
  end;
  tvJerarquia.Items.Clear;
end;

procedure TfrmCentrosZonas.RefrescarTodo;
begin
  CargarArbolJerarquico;
  if tvJerarquia.Items.Count > 0 then
  begin
    tvJerarquia.Items[0].Selected := True;
    ActualizarDetalles(tvJerarquia.Items[0]);
  end;
end;

procedure TfrmCentrosZonas.btnRefrescarClick(Sender: TObject);
begin
  RefrescarTodo;
end;

procedure TfrmCentrosZonas.cbbFiltroTipoChange(Sender: TObject);
begin
  RefrescarTodo;
end;

procedure TfrmCentrosZonas.edtBuscarChange(Sender: TObject);
begin
  RefrescarTodo;
end;

procedure TfrmCentrosZonas.CargarArbolJerarquico;
var
  QryCentros, QryZonas, QryEquipos, QrySensores: TFDQuery;
  LFiltroTipo, LSearchText: string;
  LRootObradores, LRootAlmacenes, LCentroNode, LZonaNode, LEquipoNode, LSensorNode: TTreeNode;
  LData: TNodeData;
begin
  tvJerarquia.Items.BeginUpdate;
  try
    LimpiarTreeData;

    LFiltroTipo := 'TODOS';
    if cbbFiltroTipo.ItemIndex = 1 then LFiltroTipo := 'OBRADOR'
    else if cbbFiltroTipo.ItemIndex = 2 then LFiltroTipo := 'ALMACEN';

    LSearchText := Trim(edtBuscar.Text);

    QryCentros := TFDQuery.Create(nil);
    QryZonas := TFDQuery.Create(nil);
    QryEquipos := TFDQuery.Create(nil);
    QrySensores := TFDQuery.Create(nil);
    try
      QryCentros.Connection := dmgMain.dbConn;
      QryZonas.Connection := dmgMain.dbConn;
      QryEquipos.Connection := dmgMain.dbConn;
      QrySensores.Connection := dmgMain.dbConn;

      // ── RAMA 1: OBRADORES / PRODUCCIÓN ──
      if (LFiltroTipo = 'TODOS') or (LFiltroTipo = 'OBRADOR') then
      begin
        LRootObradores := tvJerarquia.Items.Add(nil, '🏭 Obradores de Producción');
        LData := TNodeData.Create;
        LData.NodeType := ntRoot;
        LData.TipoCentro := 'OBRADOR';
        LRootObradores.Data := LData;

        QryCentros.SQL.Text :=
          'SELECT id, nombre FROM ge_obradores WHERE id_empresa = :emp AND activo = 1 ORDER BY nombre ASC';
        QryCentros.ParamByName('emp').AsString := dmgMain.CurrentCompanyId;
        QryCentros.Open;

        while not QryCentros.Eof do
        begin
          var LCentroId := QryCentros.FieldByName('id').AsInteger;
          var LCentroNom := QryCentros.FieldByName('nombre').AsString;

          LCentroNode := tvJerarquia.Items.AddChild(LRootObradores, '🏠 ' + LCentroNom);
          LData := TNodeData.Create;
          LData.NodeType := ntCentro;
          LData.TipoCentro := 'OBRADOR';
          LData.IdCentro := LCentroId;
          LData.Nombre := LCentroNom;
          LCentroNode.Data := LData;

          // Cargar Zonas del Obrador
          QryZonas.Close;
          QryZonas.SQL.Text :=
            'SELECT id, codigo, nombre, color, activo FROM ge_centros_zonas ' +
            'WHERE tipo_centro = ''OBRADOR'' AND id_centro = :cid AND id_empresa = :emp ' +
            'ORDER BY orden ASC, nombre ASC';
          QryZonas.ParamByName('cid').AsInteger := LCentroId;
          QryZonas.ParamByName('emp').AsString := dmgMain.CurrentCompanyId;
          QryZonas.Open;

          while not QryZonas.Eof do
          begin
            var LZonaId := QryZonas.FieldByName('id').AsInteger;
            var LZonaCod := QryZonas.FieldByName('codigo').AsString;
            var LZonaNom := QryZonas.FieldByName('nombre').AsString;

            LZonaNode := tvJerarquia.Items.AddChild(LCentroNode, '📍 [' + LZonaCod + '] ' + LZonaNom);
            LData := TNodeData.Create;
            LData.NodeType := ntZona;
            LData.TipoCentro := 'OBRADOR';
            LData.IdCentro := LCentroId;
            LData.IdZona := LZonaId;
            LData.Nombre := LZonaNom;
            LZonaNode.Data := LData;

            // Cargar Equipos de la Zona
            QryEquipos.Close;
            QryEquipos.SQL.Text :=
              'SELECT id, codigo, nombre, tipo_equipo FROM ge_zonas_equipos ' +
              'WHERE id_zona = :zid ORDER BY tipo_equipo ASC, codigo ASC';
            QryEquipos.ParamByName('zid').AsInteger := LZonaId;
            QryEquipos.Open;

            while not QryEquipos.Eof do
            begin
              var LEqId := QryEquipos.FieldByName('id').AsInteger;
              var LEqCod := QryEquipos.FieldByName('codigo').AsString;
              var LEqNom := QryEquipos.FieldByName('nombre').AsString;
              var LEqTipo := QryEquipos.FieldByName('tipo_equipo').AsString;

              var LIcon := '⚙️ ';
              if LEqTipo = 'HORNO' then LIcon := '🔥 '
              else if LEqTipo = 'AMASADORA' then LIcon := '🥣 '
              else if LEqTipo = 'FERMENTADORA' then LIcon := '🍞 '
              else if LEqTipo = 'CAMARA' then LIcon := '❄️ ';

              LEquipoNode := tvJerarquia.Items.AddChild(LZonaNode, LIcon + '[' + LEqCod + '] ' + LEqNom);
              LData := TNodeData.Create;
              LData.NodeType := ntEquipo;
              LData.TipoCentro := 'OBRADOR';
              LData.IdCentro := LCentroId;
              LData.IdZona := LZonaId;
              LData.IdEquipo := LEqId;
              LData.Nombre := LEqNom;
              LEquipoNode.Data := LData;

              // Cargar Sensores del Equipo
              QrySensores.Close;
              QrySensores.SQL.Text :=
                'SELECT id, nombre_sensor, tipo_sensor, identificador_hardware FROM ge_equipos_sensores WHERE id_equipo = :eid';
              QrySensores.ParamByName('eid').AsInteger := LEqId;
              QrySensores.Open;

              while not QrySensores.Eof do
              begin
                var LSenId := QrySensores.FieldByName('id').AsInteger;
                var LSenNom := QrySensores.FieldByName('nombre_sensor').AsString;
                var LSenHw := QrySensores.FieldByName('identificador_hardware').AsString;

                LSensorNode := tvJerarquia.Items.AddChild(LEquipoNode, '📡 ' + LSenNom + ' (' + LSenHw + ')');
                LData := TNodeData.Create;
                LData.NodeType := ntSensor;
                LData.TipoCentro := 'OBRADOR';
                LData.IdCentro := LCentroId;
                LData.IdZona := LZonaId;
                LData.IdEquipo := LEqId;
                LData.IdSensor := LSenId;
                LData.Nombre := LSenNom;
                LSensorNode.Data := LData;

                QrySensores.Next;
              end;

              QryEquipos.Next;
            end;

            QryZonas.Next;
          end;

          QryCentros.Next;
        end;
        LRootObradores.Expand(True);
      end;

      // ── RAMA 2: ALMACENES / LOGÍSTICA ──
      if (LFiltroTipo = 'TODOS') or (LFiltroTipo = 'ALMACEN') then
      begin
        LRootAlmacenes := tvJerarquia.Items.Add(nil, '📦 Almacenes Logísticos');
        LData := TNodeData.Create;
        LData.NodeType := ntRoot;
        LData.TipoCentro := 'ALMACEN';
        LRootAlmacenes.Data := LData;

        QryCentros.Close;
        QryCentros.SQL.Text :=
          'SELECT id, descripcion AS nombre FROM ge_almacenes WHERE (id_empresa = :emp OR compartido = 1) AND activo = 1 ORDER BY descripcion ASC';
        QryCentros.ParamByName('emp').AsString := dmgMain.CurrentCompanyId;
        QryCentros.Open;

        while not QryCentros.Eof do
        begin
          var LCentroId := QryCentros.FieldByName('id').AsInteger;
          var LCentroNom := QryCentros.FieldByName('nombre').AsString;

          LCentroNode := tvJerarquia.Items.AddChild(LRootAlmacenes, '🏢 ' + LCentroNom);
          LData := TNodeData.Create;
          LData.NodeType := ntCentro;
          LData.TipoCentro := 'ALMACEN';
          LData.IdCentro := LCentroId;
          LData.Nombre := LCentroNom;
          LCentroNode.Data := LData;

          // Zonas de Almacén
          QryZonas.Close;
          QryZonas.SQL.Text :=
            'SELECT id, codigo, nombre, color, activo FROM ge_centros_zonas ' +
            'WHERE tipo_centro = ''ALMACEN'' AND id_centro = :cid AND id_empresa = :emp ' +
            'ORDER BY orden ASC, nombre ASC';
          QryZonas.ParamByName('cid').AsInteger := LCentroId;
          QryZonas.ParamByName('emp').AsString := dmgMain.CurrentCompanyId;
          QryZonas.Open;

          while not QryZonas.Eof do
          begin
            var LZonaId := QryZonas.FieldByName('id').AsInteger;
            var LZonaCod := QryZonas.FieldByName('codigo').AsString;
            var LZonaNom := QryZonas.FieldByName('nombre').AsString;

            LZonaNode := tvJerarquia.Items.AddChild(LCentroNode, '📍 [' + LZonaCod + '] ' + LZonaNom);
            LData := TNodeData.Create;
            LData.NodeType := ntZona;
            LData.TipoCentro := 'ALMACEN';
            LData.IdCentro := LCentroId;
            LData.IdZona := LZonaId;
            LData.Nombre := LZonaNom;
            LZonaNode.Data := LData;

            // Equipos / Cámaras de Almacén
            QryEquipos.Close;
            QryEquipos.SQL.Text :=
              'SELECT id, codigo, nombre, tipo_equipo FROM ge_zonas_equipos ' +
              'WHERE id_zona = :zid ORDER BY tipo_equipo ASC, codigo ASC';
            QryEquipos.ParamByName('zid').AsInteger := LZonaId;
            QryEquipos.Open;

            while not QryEquipos.Eof do
            begin
              var LEqId := QryEquipos.FieldByName('id').AsInteger;
              var LEqCod := QryEquipos.FieldByName('codigo').AsString;
              var LEqNom := QryEquipos.FieldByName('nombre').AsString;

              LEquipoNode := tvJerarquia.Items.AddChild(LZonaNode, '❄️ [' + LEqCod + '] ' + LEqNom);
              LData := TNodeData.Create;
              LData.NodeType := ntEquipo;
              LData.TipoCentro := 'ALMACEN';
              LData.IdCentro := LCentroId;
              LData.IdZona := LZonaId;
              LData.IdEquipo := LEqId;
              LData.Nombre := LEqNom;
              LEquipoNode.Data := LData;

              // Sensores
              QrySensores.Close;
              QrySensores.SQL.Text :=
                'SELECT id, nombre_sensor, tipo_sensor, identificador_hardware FROM ge_equipos_sensores WHERE id_equipo = :eid';
              QrySensores.ParamByName('eid').AsInteger := LEqId;
              QrySensores.Open;

              while not QrySensores.Eof do
              begin
                var LSenId := QrySensores.FieldByName('id').AsInteger;
                var LSenNom := QrySensores.FieldByName('nombre_sensor').AsString;
                var LSenHw := QrySensores.FieldByName('identificador_hardware').AsString;

                LSensorNode := tvJerarquia.Items.AddChild(LEquipoNode, '📡 ' + LSenNom + ' (' + LSenHw + ')');
                LData := TNodeData.Create;
                LData.NodeType := ntSensor;
                LData.TipoCentro := 'ALMACEN';
                LData.IdCentro := LCentroId;
                LData.IdZona := LZonaId;
                LData.IdEquipo := LEqId;
                LData.IdSensor := LSenId;
                LData.Nombre := LSenNom;
                LSensorNode.Data := LData;

                QrySensores.Next;
              end;

              QryEquipos.Next;
            end;

            QryZonas.Next;
          end;

          QryCentros.Next;
        end;
        LRootAlmacenes.Expand(True);
      end;
    finally
      QryCentros.Free;
      QryZonas.Free;
      QryEquipos.Free;
      QrySensores.Free;
    end;
  finally
    tvJerarquia.Items.EndUpdate;
  end;
end;

function TfrmCentrosZonas.GetSelectedNodeData: TNodeData;
begin
  Result := nil;
  if Assigned(tvJerarquia.Selected) and Assigned(tvJerarquia.Selected.Data) then
    Result := TNodeData(tvJerarquia.Selected.Data);
end;

procedure TfrmCentrosZonas.tvJerarquiaChange(Sender: TObject; Node: TTreeNode);
begin
  ActualizarDetalles(Node);
end;

procedure TfrmCentrosZonas.tvJerarquiaDblClick(Sender: TObject);
var
  LData: TNodeData;
begin
  LData := GetSelectedNodeData;
  if not Assigned(LData) then Exit;

  if LData.NodeType = ntZona then
    btnEditarZonaClick(Sender)
  else if LData.NodeType = ntEquipo then
    btnEditarEquipoClick(Sender)
  else if LData.NodeType = ntSensor then
    btnEditarSensorClick(Sender);
end;

procedure TfrmCentrosZonas.ActualizarDetalles(ANode: TTreeNode);
var
  LData: TNodeData;
  LZonaId, LEquipoId: Integer;
begin
  if not Assigned(ANode) or not Assigned(ANode.Data) then
  begin
    lblInfoSeleccion.Caption := 'Seleccione un elemento del árbol para consultar detalles.';
    Exit;
  end;

  LData := TNodeData(ANode.Data);
  LZonaId := LData.IdZona;
  LEquipoId := LData.IdEquipo;

  case LData.NodeType of
    ntRoot:
      lblInfoSeleccion.Caption := 'Vista General de ' + LData.TipoCentro;
    ntCentro:
      lblInfoSeleccion.Caption := 'Centro: ' + LData.Nombre;
    ntZona:
      lblInfoSeleccion.Caption := '📍 Zona Operativa: ' + LData.Nombre;
    ntEquipo:
      lblInfoSeleccion.Caption := '⚙️ Equipo / Máquina: ' + LData.Nombre;
    ntSensor:
      lblInfoSeleccion.Caption := '📡 Sensor IoT: ' + LData.Nombre;
  end;

  // 1. Cargar Rejilla de Equipos
  qryEquipos.Close;
  if LZonaId > 0 then
  begin
    qryEquipos.SQL.Text :=
      'SELECT e.id, e.codigo, e.nombre, e.tipo_equipo, e.marca_modelo, e.capacidad_max, ' +
      '       e.coste_hora_maquina, e.activo, z.nombre AS zona_nombre ' +
      'FROM ge_zonas_equipos e ' +
      'INNER JOIN ge_centros_zonas z ON e.id_zona = z.id ' +
      'WHERE e.id_zona = :zid ORDER BY e.codigo ASC';
    qryEquipos.ParamByName('zid').AsInteger := LZonaId;
  end
  else
  begin
    qryEquipos.SQL.Text :=
      'SELECT e.id, e.codigo, e.nombre, e.tipo_equipo, e.marca_modelo, e.capacidad_max, ' +
      '       e.coste_hora_maquina, e.activo, z.nombre AS zona_nombre ' +
      'FROM ge_zonas_equipos e ' +
      'INNER JOIN ge_centros_zonas z ON e.id_zona = z.id ' +
      'WHERE z.id_empresa = :emp ORDER BY z.nombre ASC, e.codigo ASC';
    qryEquipos.ParamByName('emp').AsString := dmgMain.CurrentCompanyId;
  end;
  qryEquipos.Open;

  // 2. Cargar Rejilla de Sensores
  qrySensores.Close;
  if LEquipoId > 0 then
  begin
    qrySensores.SQL.Text :=
      'SELECT s.id, s.nombre_sensor, s.tipo_sensor, s.identificador_hardware, ' +
      '       s.umbral_min_temp, s.umbral_max_temp, s.activo, e.nombre AS equipo_nombre ' +
      'FROM ge_equipos_sensores s ' +
      'INNER JOIN ge_zonas_equipos e ON s.id_equipo = e.id ' +
      'WHERE s.id_equipo = :eid ORDER BY s.nombre_sensor ASC';
    qrySensores.ParamByName('eid').AsInteger := LEquipoId;
  end
  else
  begin
    qrySensores.SQL.Text :=
      'SELECT s.id, s.nombre_sensor, s.tipo_sensor, s.identificador_hardware, ' +
      '       s.umbral_min_temp, s.umbral_max_temp, s.activo, e.nombre AS equipo_nombre ' +
      'FROM ge_equipos_sensores s ' +
      'INNER JOIN ge_zonas_equipos e ON s.id_equipo = e.id ' +
      'INNER JOIN ge_centros_zonas z ON e.id_zona = z.id ' +
      'WHERE z.id_empresa = :emp ORDER BY e.nombre ASC, s.nombre_sensor ASC';
    qrySensores.ParamByName('emp').AsString := dmgMain.CurrentCompanyId;
  end;
  qrySensores.Open;

  // 3. Cargar Tiempos y Costes de Mano de Obra en Zona
  qryTiempos.Close;
  if LZonaId > 0 then
  begin
    qryTiempos.SQL.Text :=
      'SELECT t.id, t.fecha, CONCAT(tr.nombre, '' '', COALESCE(tr.apellidos, '''')) AS trabajador, ' +
      '       z.nombre AS zona_nombre, TIME_FORMAT(t.hora_inicio, ''%H:%i'') AS hora_inicio, TIME_FORMAT(t.hora_fin, ''%H:%i'') AS hora_fin, ' +
      '       t.minutos_totales, t.horas_decimal, t.coste_hora_aplicado, t.coste_total, t.observaciones ' +
      'FROM ge_control_tiempos_zonas t ' +
      'INNER JOIN ge_trabajadores tr ON t.id_trabajador = tr.id ' +
      'INNER JOIN ge_centros_zonas z ON t.id_zona = z.id ' +
      'WHERE t.id_zona = :zid ORDER BY t.fecha DESC, t.hora_inicio DESC LIMIT 100';
    qryTiempos.ParamByName('zid').AsInteger := LZonaId;
  end
  else
  begin
    qryTiempos.SQL.Text :=
      'SELECT t.id, t.fecha, CONCAT(tr.nombre, '' '', COALESCE(tr.apellidos, '''')) AS trabajador, ' +
      '       z.nombre AS zona_nombre, TIME_FORMAT(t.hora_inicio, ''%H:%i'') AS hora_inicio, TIME_FORMAT(t.hora_fin, ''%H:%i'') AS hora_fin, ' +
      '       t.minutos_totales, t.horas_decimal, t.coste_hora_aplicado, t.coste_total, t.observaciones ' +
      'FROM ge_control_tiempos_zonas t ' +
      'INNER JOIN ge_trabajadores tr ON t.id_trabajador = tr.id ' +
      'INNER JOIN ge_centros_zonas z ON t.id_zona = z.id ' +
      'WHERE t.id_empresa = :emp ORDER BY t.fecha DESC, t.hora_inicio DESC LIMIT 100';
    qryTiempos.ParamByName('emp').AsString := dmgMain.CurrentCompanyId;
  end;
  qryTiempos.Open;

  // 4. Cargar Procesos de Lotes en Equipos
  qryProcesos.Close;
  if LEquipoId > 0 then
  begin
    qryProcesos.SQL.Text :=
      'SELECT pr.id, COALESCE(NULLIF(l.codigo_antiguo, ''''), l.codigo) AS lote_cod, ' +
      '       art.descripcion AS articulo_desc, eq.nombre AS equipo_nombre, pr.tipo_proceso, ' +
      '       pr.fecha_inicio, pr.fecha_fin, pr.duracion_minutos, pr.temp_minima, pr.temp_maxima, ' +
      '       pr.temp_media, pr.estado, CONCAT(tr.nombre, '' '', COALESCE(tr.apellidos, '''')) AS operario ' +
      'FROM ge_lotes_procesos_registro pr ' +
      'INNER JOIN ge_lotes l ON pr.id_lote = l.id ' +
      'LEFT JOIN ge_articulos art ON pr.id_articulo = art.id ' +
      'INNER JOIN ge_zonas_equipos eq ON pr.id_equipo = eq.id ' +
      'LEFT JOIN ge_trabajadores tr ON pr.operario_responsable_id = tr.id ' +
      'WHERE pr.id_equipo = :eid ORDER BY pr.id DESC LIMIT 100';
    qryProcesos.ParamByName('eid').AsInteger := LEquipoId;
  end
  else
  begin
    qryProcesos.SQL.Text :=
      'SELECT pr.id, COALESCE(NULLIF(l.codigo_antiguo, ''''), l.codigo) AS lote_cod, ' +
      '       art.descripcion AS articulo_desc, eq.nombre AS equipo_nombre, pr.tipo_proceso, ' +
      '       pr.fecha_inicio, pr.fecha_fin, pr.duracion_minutos, pr.temp_minima, pr.temp_maxima, ' +
      '       pr.temp_media, pr.estado, CONCAT(tr.nombre, '' '', COALESCE(tr.apellidos, '''')) AS operario ' +
      'FROM ge_lotes_procesos_registro pr ' +
      'INNER JOIN ge_lotes l ON pr.id_lote = l.id ' +
      'LEFT JOIN ge_articulos art ON pr.id_articulo = art.id ' +
      'INNER JOIN ge_zonas_equipos eq ON pr.id_equipo = eq.id ' +
      'INNER JOIN ge_centros_zonas z ON eq.id_zona = z.id ' +
      'LEFT JOIN ge_trabajadores tr ON pr.operario_responsable_id = tr.id ' +
      'WHERE z.id_empresa = :emp ORDER BY pr.id DESC LIMIT 100';
    qryProcesos.ParamByName('emp').AsString := dmgMain.CurrentCompanyId;
  end;
  qryProcesos.Open;
end;

procedure TfrmCentrosZonas.btnNuevaZonaClick(Sender: TObject);
var
  LFrm: TfrmCentrosZonasEditor;
  LData: TNodeData;
begin
  LFrm := TfrmCentrosZonasEditor.Create(Self);
  try
    LData := GetSelectedNodeData;
    if Assigned(LData) then
    begin
      LFrm.TipoCentroFijo := LData.TipoCentro;
      LFrm.IdCentroFijo := LData.IdCentro;
    end;
    if LFrm.ShowModal = mrOk then
      RefrescarTodo;
  finally
    LFrm.Free;
  end;
end;

procedure TfrmCentrosZonas.btnEditarZonaClick(Sender: TObject);
var
  LFrm: TfrmCentrosZonasEditor;
  LData: TNodeData;
begin
  LData := GetSelectedNodeData;
  if not Assigned(LData) or (LData.IdZona <= 0) then
  begin
    MessageDlg('Seleccione una zona para editar.', mtInformation, [mbOK], 0);
    Exit;
  end;

  LFrm := TfrmCentrosZonasEditor.Create(Self);
  try
    LFrm.EditId := LData.IdZona;
    if LFrm.ShowModal = mrOk then
      RefrescarTodo;
  finally
    LFrm.Free;
  end;
end;

procedure TfrmCentrosZonas.btnEliminarZonaClick(Sender: TObject);
var
  LData: TNodeData;
begin
  LData := GetSelectedNodeData;
  if not Assigned(LData) or (LData.IdZona <= 0) then
  begin
    MessageDlg('Seleccione una zona para eliminar.', mtInformation, [mbOK], 0);
    Exit;
  end;

  if MessageDlg(Format('¿Está seguro de eliminar la zona "%s" y todos sus equipos asociados?', [LData.Nombre]),
     mtConfirmation, [mbYes, mbNo], 0) = mrYes then
  begin
    try
      dmgMain.qryExec.Close;
      dmgMain.qryExec.SQL.Text := 'DELETE FROM ge_centros_zonas WHERE id = :id';
      dmgMain.qryExec.ParamByName('id').AsInteger := LData.IdZona;
      dmgMain.qryExec.ExecSQL;
      RefrescarTodo;
    except
      on E: Exception do
        TDbErrorHandler.HandleException(E, 'Error al eliminar la zona operativa');
    end;
  end;
end;

procedure TfrmCentrosZonas.btnNuevoEquipoClick(Sender: TObject);
var
  LFrm: TfrmZonasEquiposEditor;
  LData: TNodeData;
begin
  LFrm := TfrmZonasEquiposEditor.Create(Self);
  try
    LData := GetSelectedNodeData;
    if Assigned(LData) and (LData.IdZona > 0) then
      LFrm.IdZonaFija := LData.IdZona;

    if LFrm.ShowModal = mrOk then
      RefrescarTodo;
  finally
    LFrm.Free;
  end;
end;

procedure TfrmCentrosZonas.btnEditarEquipoClick(Sender: TObject);
var
  LFrm: TfrmZonasEquiposEditor;
  LData: TNodeData;
  LEqId: Integer;
begin
  LEqId := 0;
  LData := GetSelectedNodeData;
  if Assigned(LData) and (LData.IdEquipo > 0) then
    LEqId := LData.IdEquipo
  else if not qryEquipos.IsEmpty then
    LEqId := qryEquipos.FieldByName('id').AsInteger;

  if LEqId <= 0 then
  begin
    MessageDlg('Seleccione un equipo o máquina para editar.', mtInformation, [mbOK], 0);
    Exit;
  end;

  LFrm := TfrmZonasEquiposEditor.Create(Self);
  try
    LFrm.EditId := LEqId;
    if LFrm.ShowModal = mrOk then
      RefrescarTodo;
  finally
    LFrm.Free;
  end;
end;

procedure TfrmCentrosZonas.btnEliminarEquipoClick(Sender: TObject);
var
  LEqId: Integer;
  LData: TNodeData;
begin
  LEqId := 0;
  LData := GetSelectedNodeData;
  if Assigned(LData) and (LData.IdEquipo > 0) then
    LEqId := LData.IdEquipo
  else if not qryEquipos.IsEmpty then
    LEqId := qryEquipos.FieldByName('id').AsInteger;

  if LEqId <= 0 then
  begin
    MessageDlg('Seleccione un equipo para eliminar.', mtInformation, [mbOK], 0);
    Exit;
  end;

  if MessageDlg('¿Está seguro de eliminar este equipo/máquina?', mtConfirmation, [mbYes, mbNo], 0) = mrYes then
  begin
    try
      dmgMain.qryExec.Close;
      dmgMain.qryExec.SQL.Text := 'DELETE FROM ge_zonas_equipos WHERE id = :id';
      dmgMain.qryExec.ParamByName('id').AsInteger := LEqId;
      dmgMain.qryExec.ExecSQL;
      RefrescarTodo;
    except
      on E: Exception do
        TDbErrorHandler.HandleException(E, 'Error al eliminar el equipo');
    end;
  end;
end;

procedure TfrmCentrosZonas.btnNuevoSensorClick(Sender: TObject);
var
  LFrm: TfrmEquiposSensoresEditor;
  LData: TNodeData;
begin
  LFrm := TfrmEquiposSensoresEditor.Create(Self);
  try
    LData := GetSelectedNodeData;
    if Assigned(LData) and (LData.IdEquipo > 0) then
      LFrm.IdEquipoFijo := LData.IdEquipo;

    if LFrm.ShowModal = mrOk then
    begin
      RefrescarTodo;
      pgcDetalles.ActivePage := tsSensores;
    end;
  finally
    LFrm.Free;
  end;
end;

procedure TfrmCentrosZonas.btnEditarSensorClick(Sender: TObject);
var
  LFrm: TfrmEquiposSensoresEditor;
  LSensorId: Integer;
  LData: TNodeData;
begin
  LSensorId := 0;
  LData := GetSelectedNodeData;
  if Assigned(LData) and (LData.NodeType = ntSensor) and (LData.IdSensor > 0) then
    LSensorId := LData.IdSensor
  else if qrySensores.Active and not qrySensores.IsEmpty then
    LSensorId := qrySensores.FieldByName('id').AsInteger;

  if LSensorId <= 0 then
  begin
    MessageDlg('Seleccione el sensor que desea editar.', mtWarning, [mbOK], 0);
    Exit;
  end;

  LFrm := TfrmEquiposSensoresEditor.Create(Self);
  try
    LFrm.EditId := LSensorId;
    if LFrm.ShowModal = mrOk then
    begin
      RefrescarTodo;
      pgcDetalles.ActivePage := tsSensores;
    end;
  finally
    LFrm.Free;
  end;
end;

procedure TfrmCentrosZonas.btnEliminarSensorClick(Sender: TObject);
var
  LSensorId: Integer;
  LData: TNodeData;
begin
  LSensorId := 0;
  LData := GetSelectedNodeData;
  if Assigned(LData) and (LData.NodeType = ntSensor) and (LData.IdSensor > 0) then
    LSensorId := LData.IdSensor
  else if qrySensores.Active and not qrySensores.IsEmpty then
    LSensorId := qrySensores.FieldByName('id').AsInteger;

  if LSensorId <= 0 then
  begin
    MessageDlg('Seleccione el sensor que desea eliminar.', mtWarning, [mbOK], 0);
    Exit;
  end;

  if MessageDlg('¿Está seguro de que desea eliminar este sensor?', mtConfirmation, [mbYes, mbNo], 0) = mrYes then
  begin
    try
      dmgMain.qryExec.Close;
      dmgMain.qryExec.SQL.Text := 'DELETE FROM ge_equipos_sensores WHERE id = :id';
      dmgMain.qryExec.ParamByName('id').AsInteger := LSensorId;
      dmgMain.qryExec.ExecSQL;
      RefrescarTodo;
      pgcDetalles.ActivePage := tsSensores;
    except
      on E: Exception do
        TDbErrorHandler.HandleException(E, 'Error al eliminar el sensor');
    end;
  end;
end;

procedure TfrmCentrosZonas.dbgEquiposDblClick(Sender: TObject);
begin
  btnEditarEquipoClick(Sender);
end;

procedure TfrmCentrosZonas.dbgSensoresDblClick(Sender: TObject);
begin
  btnEditarSensorClick(Sender);
end;

function SafeFieldAsBoolean(AField: TField): Boolean;
var
  LInt: Integer;
  LStr: string;
begin
  if not Assigned(AField) then Exit(False);
  if AField is TBooleanField then
    Exit(AField.AsBoolean);
  LStr := Trim(AField.AsString);
  if TryStrToInt(LStr, LInt) then
    Exit(LInt <> 0);
  Result := SameText(LStr, 'True') or SameText(LStr, 'S') or SameText(LStr, 'T') or SameText(LStr, '1');
end;

procedure TfrmCentrosZonas.dbgCustomDrawColumnCell(Sender: TObject; const Rect: TRect; DataCol: Integer; Column: TColumn; State: TGridDrawState);
var
  LGrid: TDBGrid;
  LChecked: Boolean;
  LBoxRect: TRect;
begin
  if not (Sender is TDBGrid) then Exit;
  LGrid := TDBGrid(Sender);

  if Assigned(Column.Field) and (Pos('activo', LowerCase(Column.Field.FieldName)) > 0) then
  begin
    LGrid.Canvas.FillRect(Rect);
    LChecked := SafeFieldAsBoolean(Column.Field);
    LBoxRect.Left := Rect.Left + (Rect.Width - 14) div 2;
    LBoxRect.Top := Rect.Top + (Rect.Height - 14) div 2;
    LBoxRect.Right := LBoxRect.Left + 14;
    LBoxRect.Bottom := LBoxRect.Top + 14;
    if LChecked then
      DrawFrameControl(LGrid.Canvas.Handle, LBoxRect, DFC_BUTTON, DFCS_BUTTONCHECK or DFCS_CHECKED)
    else
      DrawFrameControl(LGrid.Canvas.Handle, LBoxRect, DFC_BUTTON, DFCS_BUTTONCHECK);
    Exit;
  end;

  LGrid.DefaultDrawColumnCell(Rect, DataCol, Column, State);
end;

end.
