unit frm_RecetasEditor;

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, System.UITypes, 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, Vcl.DBCtrls, Vcl.Mask;

type
  TfrmRecetasEditor = class(TfrmBase)
    lblId: TLabel;
    edtId: TEdit;
    lblDescripcion: TLabel;
    edtDescripcion: TEdit;
    lblVersion: TLabel;
    edtVersion: TEdit;
    chkActivo: TCheckBox;

    // Panel de Procesos
    pnlProcesos: TPanel;
    lblListaProcesos: TLabel;
    pnlProcesosToolbar: TPanel;
    btnProcesoAdd: TSpeedButton;
    btnProcesoDel: TSpeedButton;
    dbgProcesos: TDBGrid;

    // Panel de Detalle del Proceso
    pnlDetalleProceso: TPanel;
    pnlParamsProceso: TPanel;
    lblNomProceso: TLabel;
    dbEdtNombre: TDBEdit;
    lblDuracion: TLabel;
    dbEdtDuracion: TDBEdit;
    lblTemperatura: TLabel;
    dbEdtTemperatura: TDBEdit;
    lblPH: TLabel;
    dbEdtPH: TDBEdit;
    lblFormula: TLabel;
    dbEdtFormula: TDBEdit;
    dbChkProcesoFinal: TDBCheckBox;
    lblArticuloResultado: TLabel;
    dbEdtArticulo: TDBEdit;
    btnSelectArticulo: TSpeedButton;

    dsLines: TDataSource;

    procedure FormShow(Sender: TObject);
    procedure btnSaveClick(Sender: TObject);
    procedure btnSalirClick(Sender: TObject);
    procedure btnAddLineClick(Sender: TObject);
    procedure btnDelLineClick(Sender: TObject);
    procedure FormDestroy(Sender: TObject);

    // Eventos Nuevos
    procedure btnProcesoAddClick(Sender: TObject);
    procedure btnProcesoDelClick(Sender: TObject);
    procedure dbgProcesosKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState);
    procedure btnSelectArticuloClick(Sender: TObject);
    procedure mtProcesosAfterScroll(DataSet: TDataSet);
    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
    FRecetaId: string;
    FParentForm: TForm;
    FLastProcessId: string;
    procedure ConfigMemTables;
    procedure LoadData;
    procedure mtProcesosAfterPost(DataSet: TDataSet);
    procedure mtIngredientesAfterPost(DataSet: TDataSet);
    procedure mtIngredientesArticuloCodigoChange(Sender: TField);
  public
    mtProcesos: TFDMemTable;
    mtIngredientes: TFDMemTable;
    dsProcesos: TDataSource;
    property RecetaId: string read FRecetaId write FRecetaId;
    property ParentForm: TForm read FParentForm write FParentForm;
  end;

var
  frmRecetasEditor: TfrmRecetasEditor;

implementation

uses
  dmg_Main, uDbErrorHandler, frm_Recetas, frm_SelectMateriaPrima, frm_SelectProduct,
  frxClass, frxDBSet, frm_PrintOptions, uFormatosImpresionService;

{$R *.dfm}

procedure TfrmRecetasEditor.Loaded;
begin
  inherited Loaded;
  OnShow := FormShow;
  ConfigMemTables;
end;

procedure TfrmRecetasEditor.ConfigMemTables;
begin
  mtProcesos := TFDMemTable.Create(Self);
  mtProcesos.FieldDefs.Add('id', ftString, 36);
  mtProcesos.FieldDefs.Add('receta_id', ftString, 36);
  mtProcesos.FieldDefs.Add('orden', ftInteger);
  mtProcesos.FieldDefs.Add('nombre', ftString, 100);
  mtProcesos.FieldDefs.Add('duracion_minutos', ftInteger);
  mtProcesos.FieldDefs.Add('temperatura', ftFloat);
  mtProcesos.FieldDefs.Add('ph', ftFloat);
  mtProcesos.FieldDefs.Add('formula_temperatura_otros', ftString, 255);
  mtProcesos.FieldDefs.Add('proceso_final', ftInteger);
  mtProcesos.FieldDefs.Add('id_articulo_resultado', ftString, 36);
  mtProcesos.FieldDefs.Add('articulo_code', ftString, 50);
  mtProcesos.FieldDefs.Add('articulo_descripcion', ftString, 150);
  mtProcesos.AfterScroll := mtProcesosAfterScroll;
  mtProcesos.AfterPost := mtProcesosAfterPost;
  mtProcesos.CreateDataSet;

  mtIngredientes := TFDMemTable.Create(Self);
  mtIngredientes.FieldDefs.Add('id', ftString, 36);
  mtIngredientes.FieldDefs.Add('proceso_id', ftString, 36);
  mtIngredientes.FieldDefs.Add('id_materia_prima', ftString, 36);
  mtIngredientes.FieldDefs.Add('cantidad_base', ftFloat);
  mtIngredientes.FieldDefs.Add('articulo_codigo', ftString, 50);
  mtIngredientes.FieldDefs.Add('articulo_descripcion', ftString, 150);
  mtIngredientes.CreateDataSet;
  mtIngredientes.FieldByName('articulo_codigo').OnChange := mtIngredientesArticuloCodigoChange;
  mtIngredientes.AfterPost := mtIngredientesAfterPost;

  dsProcesos := TDataSource.Create(Self);
  dsProcesos.DataSet := mtProcesos;

  dsLines.DataSet := mtIngredientes;
end;

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

  // Reparentar controles de ingredientes al panel de detalle
  pnlSideToolbar.Parent := pnlDetalleProceso;
  dbgItems.Parent := pnlDetalleProceso;
  pnlSideToolbar.Align := alRight;
  dbgItems.Align := alClient;

  // Asignar DataSource de forma explícita a los controles vinculados
  dbEdtNombre.DataSource := dsProcesos;
  dbEdtDuracion.DataSource := dsProcesos;
  dbEdtTemperatura.DataSource := dsProcesos;
  dbEdtPH.DataSource := dsProcesos;
  dbEdtFormula.DataSource := dsProcesos;
  dbChkProcesoFinal.DataSource := dsProcesos;
  dbEdtArticulo.DataSource := dsProcesos;

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

  dbgItems.DataSource := dsLines;
  dbgProcesos.DataSource := dsProcesos;
  btnProcesoAdd.Tag := 999;
  btnProcesoDel.Tag := 999;
  dbgProcesos.OnKeyDown := dbgProcesosKeyDown;

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

  // Configurar columnas dbgProcesos
  dbgProcesos.Columns.Clear;
  with dbgProcesos.Columns.Add do
  begin
    FieldName := 'orden';
    Title.Caption := 'Nº';
    Width := 30;
    ReadOnly := True;
  end;
  with dbgProcesos.Columns.Add do
  begin
    FieldName := 'nombre';
    Title.Caption := 'Nombre del Proceso';
    Width := 180;
  end;

  // Configurar columnas dbgItems
  dbgItems.Columns.Clear;
  with dbgItems.Columns.Add do
  begin
    FieldName := 'articulo_codigo';
    Title.Caption := 'Ref. Ingrediente';
    Width := 100;
    ReadOnly := False; // Cambiado a False para permitir edición manual del código
  end;
  with dbgItems.Columns.Add do
  begin
    FieldName := 'articulo_descripcion';
    Title.Caption := 'Descripción';
    Width := 300;
    ReadOnly := True;
  end;
  with dbgItems.Columns.Add do
  begin
    FieldName := 'cantidad_base';
    Title.Caption := 'Cant. Base (Gramos)';
    Width := 150;
  end;

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

procedure TfrmRecetasEditor.FormDestroy(Sender: TObject);
begin
  // No-op
end;

procedure TfrmRecetasEditor.LoadData;
var
  Qry: TFDQuery;
begin
  if (FRecetaId = '') or (FRecetaId = '0') then Exit;

  Qry := TFDQuery.Create(nil);
  try
    Qry.Connection := dmgMain.dbConn;
    Qry.SQL.Text := 'SELECT * FROM ob_recetas WHERE id = :id';
    Qry.ParamByName('id').AsString := FRecetaId;
    Qry.Open;
    if not Qry.IsEmpty then
    begin
      edtId.Text := Qry.FieldByName('id').AsString;
      edtDescripcion.Text := Qry.FieldByName('descripcion').AsString;
      edtVersion.Text := Qry.FieldByName('version').AsString;
      chkActivo.Checked := Qry.FieldByName('activo').AsInteger <> 0;
    end;
  finally
    Qry.Free;
  end;

  // Cargar Procesos
  mtProcesos.DisableControls;
  try
    mtProcesos.Active := False;
    mtProcesos.CreateDataSet;
    Qry := TFDQuery.Create(nil);
    try
      Qry.Connection := dmgMain.dbConn;
      Qry.SQL.Text :=
        'SELECT p.*, '''' as articulo_code, a.descripcion as articulo_descripcion ' +
        'FROM ob_recetas_procesos p ' +
        'LEFT JOIN ge_articulos a ON p.id_articulo_resultado = a.id ' +
        'WHERE p.receta_id = :receta_id ORDER BY p.orden';
      Qry.ParamByName('receta_id').AsString := FRecetaId;
      Qry.Open;
      while not Qry.Eof do
      begin
        mtProcesos.Append;
        mtProcesos.FieldByName('id').AsString := Qry.FieldByName('id').AsString;
        mtProcesos.FieldByName('receta_id').AsString := Qry.FieldByName('receta_id').AsString;
        mtProcesos.FieldByName('orden').AsInteger := Qry.FieldByName('orden').AsInteger;
        mtProcesos.FieldByName('nombre').AsString := Qry.FieldByName('nombre').AsString;
        if not Qry.FieldByName('duracion_minutos').IsNull then
          mtProcesos.FieldByName('duracion_minutos').AsInteger := Qry.FieldByName('duracion_minutos').AsInteger;
        if not Qry.FieldByName('temperatura').IsNull then
          mtProcesos.FieldByName('temperatura').AsFloat := Qry.FieldByName('temperatura').AsFloat;
        if not Qry.FieldByName('ph').IsNull then
          mtProcesos.FieldByName('ph').AsFloat := Qry.FieldByName('ph').AsFloat;
        mtProcesos.FieldByName('formula_temperatura_otros').AsString := Qry.FieldByName('formula_temperatura_otros').AsString;
        if Qry.FieldByName('proceso_final').DataType = ftBoolean then
        begin
          if Qry.FieldByName('proceso_final').AsInteger <> 0 then
            mtProcesos.FieldByName('proceso_final').AsInteger := 1
          else
            mtProcesos.FieldByName('proceso_final').AsInteger := 0;
        end
        else
          mtProcesos.FieldByName('proceso_final').AsInteger := Qry.FieldByName('proceso_final').AsInteger;
        mtProcesos.FieldByName('id_articulo_resultado').AsString := Qry.FieldByName('id_articulo_resultado').AsString;
        mtProcesos.FieldByName('articulo_code').AsString := Qry.FieldByName('articulo_code').AsString;
        mtProcesos.FieldByName('articulo_descripcion').AsString := Qry.FieldByName('articulo_descripcion').AsString;
        mtProcesos.Post;
        Qry.Next;
      end;
    finally
      Qry.Free;
    end;
  finally
    mtProcesos.EnableControls;
  end;

  // Cargar Ingredientes
  mtIngredientes.DisableControls;
  try
    mtIngredientes.Active := False;
    mtIngredientes.CreateDataSet;
    Qry := TFDQuery.Create(nil);
    try
      Qry.Connection := dmgMain.dbConn;
      Qry.SQL.Text :=
        'SELECT ri.id, ri.proceso_id, ri.id_materia_prima, ri.cantidad_base, ' +
        ' COALESCE(mp.id, a.id) as articulo_codigo, ' +
        ' COALESCE(mp.descripcion, a.descripcion) as articulo_descripcion ' +
        'FROM ob_procesos_ingredientes ri ' +
        'LEFT JOIN ge_materias_primas mp ON ri.id_materia_prima = mp.id ' +
        'LEFT JOIN ge_articulos a ON ri.id_materia_prima = a.id ' +
        'WHERE ri.proceso_id IN (SELECT id FROM ob_recetas_procesos WHERE receta_id = :receta_id)';
      Qry.ParamByName('receta_id').AsString := FRecetaId;
      Qry.Open;
      while not Qry.Eof do
      begin
        mtIngredientes.Append;
        mtIngredientes.FieldByName('id').AsString := Qry.FieldByName('id').AsString;
        mtIngredientes.FieldByName('proceso_id').AsString := Qry.FieldByName('proceso_id').AsString;
        mtIngredientes.FieldByName('id_materia_prima').AsString := Qry.FieldByName('id_materia_prima').AsString;
        mtIngredientes.FieldByName('cantidad_base').AsFloat := Qry.FieldByName('cantidad_base').AsFloat;
        mtIngredientes.FieldByName('articulo_codigo').AsString := Qry.FieldByName('articulo_codigo').AsString;
        mtIngredientes.FieldByName('articulo_descripcion').AsString := Qry.FieldByName('articulo_descripcion').AsString;
        mtIngredientes.Post;
        Qry.Next;
      end;
    finally
      Qry.Free;
    end;
  finally
    mtIngredientes.EnableControls;
  end;

  if not mtProcesos.IsEmpty then
    mtProcesos.First;
end;

procedure TfrmRecetasEditor.DoAnadir;
begin
  FRecetaId := '';
  edtId.Text := '(Nuevo)';
  edtDescripcion.Clear;
  edtVersion.Text := '1.0';
  chkActivo.Checked := True;

  mtProcesos.Active := False;
  mtProcesos.CreateDataSet;

  mtIngredientes.Active := False;
  mtIngredientes.CreateDataSet;

  edtDescripcion.SetFocus;
end;

procedure TfrmRecetasEditor.DoModificar;
begin
  edtDescripcion.SetFocus;
end;

procedure TfrmRecetasEditor.DoPrimero;
var
  LParent: TfrmRecetas;
begin
  LParent := nil;
  if Assigned(FParentForm) and (FParentForm is TfrmRecetas) then
    LParent := TfrmRecetas(FParentForm)
  else if Assigned(frmRecetas) then
    LParent := frmRecetas;
  if Assigned(LParent) and Assigned(LParent.QryMain) and not LParent.QryMain.IsEmpty then
  begin
    LParent.QryMain.First;
    FRecetaId := LParent.QryMain.FieldByName('id').AsString;
    LoadData;
  end;
end;

procedure TfrmRecetasEditor.DoAnterior;
var
  LParent: TfrmRecetas;
begin
  LParent := nil;
  if Assigned(FParentForm) and (FParentForm is TfrmRecetas) then
    LParent := TfrmRecetas(FParentForm)
  else if Assigned(frmRecetas) then
    LParent := frmRecetas;
  if Assigned(LParent) and Assigned(LParent.QryMain) and not LParent.QryMain.IsEmpty then
  begin
    LParent.QryMain.Prior;
    FRecetaId := LParent.QryMain.FieldByName('id').AsString;
    LoadData;
  end;
end;

procedure TfrmRecetasEditor.DoSiguiente;
var
  LParent: TfrmRecetas;
begin
  LParent := nil;
  if Assigned(FParentForm) and (FParentForm is TfrmRecetas) then
    LParent := TfrmRecetas(FParentForm)
  else if Assigned(frmRecetas) then
    LParent := frmRecetas;
  if Assigned(LParent) and Assigned(LParent.QryMain) and not LParent.QryMain.IsEmpty then
  begin
    LParent.QryMain.Next;
    FRecetaId := LParent.QryMain.FieldByName('id').AsString;
    LoadData;
  end;
end;

procedure TfrmRecetasEditor.DoUltimo;
var
  LParent: TfrmRecetas;
begin
  LParent := nil;
  if Assigned(FParentForm) and (FParentForm is TfrmRecetas) then
    LParent := TfrmRecetas(FParentForm)
  else if Assigned(frmRecetas) then
    LParent := frmRecetas;
  if Assigned(LParent) and Assigned(LParent.QryMain) and not LParent.QryMain.IsEmpty then
  begin
    LParent.QryMain.Last;
    FRecetaId := LParent.QryMain.FieldByName('id').AsString;
    LoadData;
  end;
end;

procedure TfrmRecetasEditor.btnProcesoAddClick(Sender: TObject);
var
  LNextOrd: Integer;
begin
  LNextOrd := mtProcesos.RecordCount + 1;
  mtProcesos.Append;
  mtProcesos.FieldByName('id').AsString := TdmgMain.CleanUUID(TGUID.NewGuid.ToString);
  mtProcesos.FieldByName('receta_id').AsString := FRecetaId;
  mtProcesos.FieldByName('orden').AsInteger := LNextOrd;
  mtProcesos.FieldByName('nombre').AsString := 'Proceso ' + IntToStr(LNextOrd);
  mtProcesos.FieldByName('proceso_final').AsInteger := 0;
  mtProcesos.Post;
  ControlChanged(Sender);
end;

procedure TfrmRecetasEditor.dbgProcesosKeyDown(Sender: TObject; var Key: Word; Shift: TShiftState);
begin
  if Key = VK_DELETE then
  begin
    Key := 0;
    btnProcesoDelClick(btnProcesoDel);
  end;
end;

procedure TfrmRecetasEditor.btnProcesoDelClick(Sender: TObject);
var
  LProcId, LProcNom: string;
  LProcOrd, LNewOrd: Integer;
  LBookmark: TBookmark;
begin
  if mtProcesos.IsEmpty then
  begin
    ShowMessage('No hay ningún proceso de fabricación seleccionado para eliminar.');
    Exit;
  end;

  // Cancelar ediciones pendientes antes de eliminar
  if mtProcesos.State in [dsInsert, dsEdit] then
    mtProcesos.Cancel;
  if mtIngredientes.State in [dsInsert, dsEdit] then
    mtIngredientes.Cancel;

  LProcId := mtProcesos.FieldByName('id').AsString;
  LProcNom := mtProcesos.FieldByName('nombre').AsString;
  LProcOrd := mtProcesos.FieldByName('orden').AsInteger;

  // 1. Confirmación explícita del usuario
  if MessageDlg(Format('¿Está seguro de que desea eliminar el proceso de fabricación Nº %d ("%s") y todos sus ingredientes asociados?',
    [LProcOrd, LProcNom]), mtConfirmation, [mbYes, mbNo], 0) <> mrYes then
    Exit;

  // 2. Control de integridad referencial con órdenes de fabricación en planta (ob_elaboraciones_procesos)
  if (FRecetaId <> '') and (LProcId <> '') then
  begin
    dmgMain.qryExec.Close;
    dmgMain.qryExec.SQL.Text := 'SELECT COUNT(*) FROM ob_elaboraciones_procesos WHERE proceso_id = :pid';
    dmgMain.qryExec.ParamByName('pid').AsString := LProcId;
    dmgMain.qryExec.Open;
    if dmgMain.qryExec.Fields[0].AsInteger > 0 then
    begin
      dmgMain.qryExec.Close;
      ShowMessage(Format('No se puede eliminar el proceso "%s" porque ya está registrado en órdenes de fabricación / elaboraciones en planta.', [LProcNom]));
      Exit;
    end;
    dmgMain.qryExec.Close;
  end;

  // 3. Borrado físico en base de datos si la receta ya existe (cascada: ingredientes -> proceso)
  if (FRecetaId <> '') and (LProcId <> '') then
  begin
    dmgMain.dbConn.StartTransaction;
    try
      // Borrar primero los ingredientes de este proceso en base de datos
      dmgMain.qryExec.Close;
      dmgMain.qryExec.SQL.Text := 'DELETE FROM ob_procesos_ingredientes WHERE proceso_id = :proceso_id';
      dmgMain.qryExec.ParamByName('proceso_id').AsString := LProcId;
      dmgMain.qryExec.ExecSQL;

      // Borrar el proceso en base de datos
      dmgMain.qryExec.Close;
      dmgMain.qryExec.SQL.Text := 'DELETE FROM ob_recetas_procesos WHERE id = :id';
      dmgMain.qryExec.ParamByName('id').AsString := LProcId;
      dmgMain.qryExec.ExecSQL;

      dmgMain.dbConn.Commit;
    except
      on E: Exception do
      begin
        dmgMain.dbConn.Rollback;
        TDbErrorHandler.HandleException(E, 'Error al eliminar el proceso de fabricación de la base de datos');
        Exit;
      end;
    end;
  end;

  // 4. Borrar ingredientes asociados a este proceso en el memtable local
  mtIngredientes.DisableControls;
  try
    mtIngredientes.Filtered := False;
    mtIngredientes.First;
    while not mtIngredientes.Eof do
    begin
      if SameText(mtIngredientes.FieldByName('proceso_id').AsString, LProcId) then
        mtIngredientes.Delete
      else
        mtIngredientes.Next;
    end;
  finally
    mtIngredientes.EnableControls;
  end;

  // 5. Borrar el proceso del memtable local
  mtProcesos.Delete;

  // 6. Renumerar secuencialmente los procesos restantes (1, 2, 3...)
  if not mtProcesos.IsEmpty then
  begin
    mtProcesos.DisableControls;
    try
      LBookmark := mtProcesos.GetBookmark;
      try
        mtProcesos.First;
        LNewOrd := 1;
        while not mtProcesos.Eof do
        begin
          if mtProcesos.FieldByName('orden').AsInteger <> LNewOrd then
          begin
            mtProcesos.Edit;
            mtProcesos.FieldByName('orden').AsInteger := LNewOrd;
            mtProcesos.Post;
          end;
          Inc(LNewOrd);
          mtProcesos.Next;
        end;
      finally
        if mtProcesos.BookmarkValid(LBookmark) then
          mtProcesos.GotoBookmark(LBookmark)
        else
          mtProcesos.First;
        mtProcesos.FreeBookmark(LBookmark);
      end;
    finally
      mtProcesos.EnableControls;
    end;
  end;

  // 7. Refrescar estado visual y detalle de ingredientes
  FLastProcessId := '';
  mtProcesosAfterScroll(mtProcesos);

  // 8. Notificar cambio al formulario base
  ControlChanged(Sender);
end;

procedure TfrmRecetasEditor.btnSelectArticuloClick(Sender: TObject);
begin
  if mtProcesos.IsEmpty then Exit;

  if not Assigned(frmSelectProduct) then
    Application.CreateForm(TfrmSelectProduct, frmSelectProduct);
  frmSelectProduct.MultiSelect := False;
  if frmSelectProduct.ShowModal = mrOk then
  begin
    if not (mtProcesos.State in [dsInsert, dsEdit]) then
      mtProcesos.Edit;
    mtProcesos.FieldByName('id_articulo_resultado').AsString := frmSelectProduct.SelectedId;
    mtProcesos.FieldByName('articulo_code').AsString := frmSelectProduct.SelectedCode;
    mtProcesos.FieldByName('articulo_descripcion').AsString := frmSelectProduct.SelectedDesc;
    mtProcesos.Post;
    ControlChanged(Sender);
  end;
end;

procedure TfrmRecetasEditor.mtProcesosAfterScroll(DataSet: TDataSet);
var
  LCurrentId: string;
  LHasProcesos: Boolean;
begin
  if not mtProcesos.Active then Exit;

  LHasProcesos := not mtProcesos.IsEmpty;
  pnlParamsProceso.Enabled := LHasProcesos;
  dbgItems.Enabled := LHasProcesos;
  pnlSideToolbar.Enabled := LHasProcesos;
  btnProcesoDel.Enabled := LHasProcesos;
  btnSelectArticulo.Enabled := LHasProcesos;

  if not LHasProcesos then
  begin
    FLastProcessId := '';
    mtIngredientes.Filter := 'proceso_id = ''00000000-0000-0000-0000-000000000000''';
    mtIngredientes.Filtered := True;
    Exit;
  end;

  LCurrentId := mtProcesos.FieldByName('id').AsString;
  if (LCurrentId <> '') and (LCurrentId <> FLastProcessId) then
  begin
    FLastProcessId := LCurrentId;
    mtIngredientes.Filter := 'proceso_id = ' + QuotedStr(LCurrentId);
    mtIngredientes.Filtered := True;
  end;
end;

procedure TfrmRecetasEditor.btnAddLineClick(Sender: TObject);
begin
  if mtProcesos.IsEmpty then
  begin
    ShowMessage('Debe añadir y seleccionar un proceso antes de agregar ingredientes.');
    Exit;
  end;

  if not Assigned(frmSelectMateriaPrima) then
    Application.CreateForm(TfrmSelectMateriaPrima, frmSelectMateriaPrima);
  if frmSelectMateriaPrima.ShowModal = mrOk then
  begin
    // Comprobar si ya existe el ingrediente en este proceso
    mtIngredientes.DisableControls;
    try
      mtIngredientes.First;
      while not mtIngredientes.Eof do
      begin
        if (mtIngredientes.FieldByName('proceso_id').AsString = mtProcesos.FieldByName('id').AsString) and
           (mtIngredientes.FieldByName('id_materia_prima').AsString = frmSelectMateriaPrima.SelectedId) then
        begin
          ShowMessage('Esta materia prima ya ha sido añadida a este proceso.');
          Exit;
        end;
        mtIngredientes.Next;
      end;
    finally
      mtIngredientes.EnableControls;
    end;

    mtIngredientes.Append;
    mtIngredientes.FieldByName('id').AsString := TdmgMain.CleanUUID(TGUID.NewGuid.ToString);
    mtIngredientes.FieldByName('proceso_id').AsString := mtProcesos.FieldByName('id').AsString;
    mtIngredientes.FieldByName('id_materia_prima').AsString := frmSelectMateriaPrima.SelectedId;
    mtIngredientes.FieldByName('articulo_codigo').AsString := frmSelectMateriaPrima.SelectedCode;
    mtIngredientes.FieldByName('articulo_descripcion').AsString := frmSelectMateriaPrima.SelectedDesc;
    mtIngredientes.FieldByName('cantidad_base').AsFloat := 100.0;
    mtIngredientes.Post;
    ControlChanged(Sender);
  end;
end;

procedure TfrmRecetasEditor.btnDelLineClick(Sender: TObject);
var
  LIngId: string;
begin
  if not mtIngredientes.IsEmpty then
  begin
    LIngId := mtIngredientes.FieldByName('id').AsString;
    mtIngredientes.Delete;

    // Auto-borrar de la base de datos si la receta ya existe
    if (FRecetaId <> '') and (LIngId <> '') then
    begin
      dmgMain.qryExec.Close;
      dmgMain.qryExec.SQL.Text := 'DELETE FROM ob_procesos_ingredientes WHERE id = :id';
      dmgMain.qryExec.ParamByName('id').AsString := LIngId;
      dmgMain.qryExec.ExecSQL;
    end;
    ControlChanged(Sender);
  end;
end;

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

procedure TfrmRecetasEditor.btnSaveClick(Sender: TObject);
var
  LIsNew: Boolean;
begin
  if Trim(edtDescripcion.Text) = '' then
  begin
    ShowMessage('Debe ingresar una descripción para la receta.');
    edtDescripcion.SetFocus;
    Exit;
  end;
  if mtProcesos.IsEmpty then
  begin
    ShowMessage('Debe añadir al menos un proceso.');
    Exit;
  end;

  // Confirmar que cambios en grid se hayan persistido localmente
  if mtProcesos.State in [dsEdit, dsInsert] then
    mtProcesos.Post;
  if mtIngredientes.State in [dsEdit, dsInsert] then
    mtIngredientes.Post;

  LIsNew := (FRecetaId = '') or (FRecetaId = '0');
  dmgMain.dbConn.StartTransaction;
  try
    // 1. Guardar Receta Cabecera
    dmgMain.qryExec.Close;
    if LIsNew then
    begin
      dmgMain.qryExec.SQL.Text :=
        'INSERT INTO ob_recetas (descripcion, version, activo, id_user_creator, id_user_update) ' +
        'VALUES (:descripcion, :version, :activo, :user, :user)';
    end
    else
    begin
      dmgMain.qryExec.SQL.Text :=
        'UPDATE ob_recetas SET descripcion = :descripcion, version = :version, ' +
        ' activo = :activo, id_user_update = :user WHERE id = :id';
    end;
    if not LIsNew then
      dmgMain.qryExec.ParamByName('id').AsString := FRecetaId;
    dmgMain.qryExec.ParamByName('descripcion').AsString := Trim(edtDescripcion.Text);
    dmgMain.qryExec.ParamByName('version').AsString := Trim(edtVersion.Text);
    if chkActivo.Checked then
      dmgMain.qryExec.ParamByName('activo').AsInteger := 1
    else
      dmgMain.qryExec.ParamByName('activo').AsInteger := 0;
    dmgMain.qryExec.ParamByName('user').AsString := dmgMain.CurrentUserId;
    dmgMain.qryExec.ExecSQL;

    if LIsNew then
    begin
      dmgMain.qryExec.Close;
      dmgMain.qryExec.SQL.Text := 'SELECT LAST_INSERT_ID()';
      dmgMain.qryExec.Open;
      FRecetaId := dmgMain.qryExec.Fields[0].AsString;
      dmgMain.qryExec.Close;
      
      edtId.Text := FRecetaId;
    end;

    // 2. Limpiar procesos antiguos (y por cascada ingredientes)
    dmgMain.qryExec.Close;
    dmgMain.qryExec.SQL.Text := 'DELETE FROM ob_recetas_procesos WHERE receta_id = :receta_id';
    dmgMain.qryExec.ParamByName('receta_id').AsString := FRecetaId;
    dmgMain.qryExec.ExecSQL;

    // 3. Insertar Procesos
    mtProcesos.First;
    while not mtProcesos.Eof do
    begin
      dmgMain.qryExec.Close;
      dmgMain.qryExec.SQL.Text :=
        'INSERT INTO ob_recetas_procesos (id, receta_id, orden, nombre, duracion_minutos, temperatura, ph, formula_temperatura_otros, proceso_final, id_articulo_resultado) ' +
        'VALUES (:id, :receta_id, :orden, :nombre, :duracion, :temp, :ph, :formula, :final, :art_res)';
      
      // Definir tipos de datos explicitamente para evitar error de tipo desconocido al asignar Null
      dmgMain.qryExec.ParamByName('duracion').DataType := ftInteger;
      dmgMain.qryExec.ParamByName('temp').DataType := ftFloat;
      dmgMain.qryExec.ParamByName('ph').DataType := ftFloat;
      dmgMain.qryExec.ParamByName('formula').DataType := ftString;
      dmgMain.qryExec.ParamByName('art_res').DataType := ftString;

      dmgMain.qryExec.ParamByName('id').AsString := mtProcesos.FieldByName('id').AsString;
      dmgMain.qryExec.ParamByName('receta_id').AsString := FRecetaId;
      dmgMain.qryExec.ParamByName('orden').AsInteger := mtProcesos.FieldByName('orden').AsInteger;
      dmgMain.qryExec.ParamByName('nombre').AsString := mtProcesos.FieldByName('nombre').AsString;

      if mtProcesos.FieldByName('duracion_minutos').IsNull or (mtProcesos.FieldByName('duracion_minutos').AsInteger = 0) then
        dmgMain.qryExec.ParamByName('duracion').Value := Null
      else
        dmgMain.qryExec.ParamByName('duracion').AsInteger := mtProcesos.FieldByName('duracion_minutos').AsInteger;

      if mtProcesos.FieldByName('temperatura').IsNull or (mtProcesos.FieldByName('temperatura').AsFloat = 0) then
        dmgMain.qryExec.ParamByName('temp').Value := Null
      else
        dmgMain.qryExec.ParamByName('temp').AsFloat := mtProcesos.FieldByName('temperatura').AsFloat;

      if mtProcesos.FieldByName('ph').IsNull or (mtProcesos.FieldByName('ph').AsFloat = 0) then
        dmgMain.qryExec.ParamByName('ph').Value := Null
      else
        dmgMain.qryExec.ParamByName('ph').AsFloat := mtProcesos.FieldByName('ph').AsFloat;

      if mtProcesos.FieldByName('formula_temperatura_otros').AsString = '' then
        dmgMain.qryExec.ParamByName('formula').Value := Null
      else
        dmgMain.qryExec.ParamByName('formula').AsString := mtProcesos.FieldByName('formula_temperatura_otros').AsString;

      dmgMain.qryExec.ParamByName('final').AsInteger := mtProcesos.FieldByName('proceso_final').AsInteger;

      if (mtProcesos.FieldByName('proceso_final').AsInteger = 1) and (mtProcesos.FieldByName('id_articulo_resultado').AsString <> '') then
        dmgMain.qryExec.ParamByName('art_res').AsString := mtProcesos.FieldByName('id_articulo_resultado').AsString
      else
        dmgMain.qryExec.ParamByName('art_res').Value := Null;

      dmgMain.qryExec.ExecSQL;

      mtProcesos.Next;
    end;

    // 4. Insertar Ingredientes de Procesos
    mtIngredientes.First;
    while not mtIngredientes.Eof do
    begin
      dmgMain.qryExec.Close;
      dmgMain.qryExec.SQL.Text :=
        'INSERT INTO ob_procesos_ingredientes (id, proceso_id, id_materia_prima, cantidad_base) ' +
        'VALUES (:id, :proceso_id, :id_materia_prima, :cantidad_base)';
      dmgMain.qryExec.ParamByName('id').AsString := mtIngredientes.FieldByName('id').AsString;
      dmgMain.qryExec.ParamByName('proceso_id').AsString := mtIngredientes.FieldByName('proceso_id').AsString;
      dmgMain.qryExec.ParamByName('id_materia_prima').AsString := mtIngredientes.FieldByName('id_materia_prima').AsString;
      dmgMain.qryExec.ParamByName('cantidad_base').AsFloat := mtIngredientes.FieldByName('cantidad_base').AsFloat;
      dmgMain.qryExec.ExecSQL;

      mtIngredientes.Next;
    end;

    dmgMain.dbConn.Commit;
    ResetChangeTracking;
    if Assigned(FParentForm) and (FParentForm is TfrmRecetas) then
      TfrmRecetas(FParentForm).btnRefreshClick(nil);
    ShowMessage('Receta guardada correctamente.');
  except
    on E: Exception do
    begin
      dmgMain.dbConn.Rollback;
      TDbErrorHandler.HandleException(E, 'Error al guardar la receta estructurada por procesos');
    end;
  end;
end;

procedure TfrmRecetasEditor.btnImprimirClick(Sender: TObject);
var
  QryReceta: TFDQuery;
  QryIngredientes: TFDQuery;
  LReportPath: string;
  LEmpresaId: Integer;
begin
  if FRecetaId = '' then
  begin
    ShowMessage('Guarde o seleccione una receta antes de imprimir.');
    Exit;
  end;

  LEmpresaId := 1;
  if Assigned(dmgMain) and (Trim(dmgMain.CurrentCompanyId) <> '') then
    LEmpresaId := StrToIntDef(dmgMain.CurrentCompanyId, 1);

  LReportPath := TFormatosImpresionService.GetRutaPlantillaDefecto('RECETA', LEmpresaId);
  if (LReportPath = '') or not FileExists(LReportPath) then
    LReportPath := TFormatosImpresionService.ResolverRutaArchivo('RecetaDetalle.fr3');

  if not FileExists(LReportPath) then
  begin
    ShowMessage('No se encuentra la plantilla del reporte en: ' + LReportPath);
    Exit;
  end;

  QryReceta := TFDQuery.Create(nil);
  QryIngredientes := TFDQuery.Create(nil);
  try
    // Preparar query de la receta maestro
    QryReceta.Connection := dmgMain.dbConn;
    QryReceta.SQL.Text :=
      'SELECT id, codigo, descripcion, version, activo ' +
      'FROM ob_recetas WHERE id = :id';
    QryReceta.ParamByName('id').AsString := FRecetaId;
    QryReceta.Open;

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

    // Preparar query de ingredientes detalle
    QryIngredientes.Connection := dmgMain.dbConn;
    QryIngredientes.SQL.Text :=
      'SELECT rp.orden as proceso_orden, rp.nombre as proceso_nombre, ' +
      '  COALESCE(CAST(mp.id AS CHAR), CAST(a.id AS CHAR)) as ingrediente_codigo, ' +
      '  COALESCE(mp.descripcion, a.descripcion) as ingrediente_descripcion, ' +
      '  ri.cantidad_base, ri.unidad ' +
      'FROM ob_recetas_procesos rp ' +
      'LEFT JOIN ob_procesos_ingredientes ri ON ri.proceso_id = rp.id ' +
      'LEFT JOIN ge_materias_primas mp ON ri.id_materia_prima = mp.id ' +
      'LEFT JOIN ge_articulos a ON ri.id_materia_prima = a.id ' +
      'WHERE rp.receta_id = :receta_id ' +
      'ORDER BY rp.orden, COALESCE(mp.descripcion, a.descripcion)';
    QryIngredientes.ParamByName('receta_id').AsString := FRecetaId;
    QryIngredientes.Open;

    // Configurar los frxDBDatasets de diseño
    dmgMain.frxDBMaster.DataSet := QryReceta;
    dmgMain.frxDBDetalle.DataSet := QryIngredientes;

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

    // 2. 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);

    var LOnFormatChange: TChangeReportFormatCallback :=
      procedure(const ANewReportPath: string)
      begin
        if FileExists(ANewReportPath) then
        begin
          dmgMain.frxInforme.LoadFromFile(ANewReportPath);
          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);
        end;
      end;

    // 3. Mostrar opciones de impresión globales con selector de formato
    TfrmPrintOptions.ShowOptions(Self, dmgMain.frxInforme, nil, 'Receta_' + QryReceta.FieldByName('codigo').AsString, '', '', 1, nil, nil, 'RECETA', LEmpresaId, 0, LOnFormatChange);
  finally
    QryReceta.Free;
    QryIngredientes.Free;
  end;
end;

procedure TfrmRecetasEditor.mtProcesosAfterPost(DataSet: TDataSet);
begin
  if FRecetaId <> '' then
  begin
    dmgMain.qryExec.Close;
    dmgMain.qryExec.SQL.Text :=
      'INSERT INTO ob_recetas_procesos (id, receta_id, orden, nombre, duracion_minutos, temperatura, ph, formula_temperatura_otros, proceso_final, id_articulo_resultado) ' +
      'VALUES (:id, :receta_id, :orden, :nombre, :duracion, :temp, :ph, :formula, :final, :art_res) ' +
      'ON DUPLICATE KEY UPDATE ' +
      '  orden = :orden, nombre = :nombre, duracion_minutos = :duracion, temperatura = :temp, ' +
      '  ph = :ph, formula_temperatura_otros = :formula, proceso_final = :final, id_articulo_resultado = :art_res';
    
    dmgMain.qryExec.ParamByName('duracion').DataType := ftInteger;
    dmgMain.qryExec.ParamByName('temp').DataType := ftFloat;
    dmgMain.qryExec.ParamByName('ph').DataType := ftFloat;
    dmgMain.qryExec.ParamByName('formula').DataType := ftString;
    dmgMain.qryExec.ParamByName('art_res').DataType := ftString;

    dmgMain.qryExec.ParamByName('id').AsString := DataSet.FieldByName('id').AsString;
    dmgMain.qryExec.ParamByName('receta_id').AsString := FRecetaId;
    dmgMain.qryExec.ParamByName('orden').AsInteger := DataSet.FieldByName('orden').AsInteger;
    dmgMain.qryExec.ParamByName('nombre').AsString := DataSet.FieldByName('nombre').AsString;

    if DataSet.FieldByName('duracion_minutos').IsNull or (DataSet.FieldByName('duracion_minutos').AsInteger = 0) then
      dmgMain.qryExec.ParamByName('duracion').Value := Null
    else
      dmgMain.qryExec.ParamByName('duracion').AsInteger := DataSet.FieldByName('duracion_minutos').AsInteger;

    if DataSet.FieldByName('temperatura').IsNull or (DataSet.FieldByName('temperatura').AsFloat = 0) then
      dmgMain.qryExec.ParamByName('temp').Value := Null
    else
      dmgMain.qryExec.ParamByName('temp').AsFloat := DataSet.FieldByName('temperatura').AsFloat;

    if DataSet.FieldByName('ph').IsNull or (DataSet.FieldByName('ph').AsFloat = 0) then
      dmgMain.qryExec.ParamByName('ph').Value := Null
    else
      dmgMain.qryExec.ParamByName('ph').AsFloat := DataSet.FieldByName('ph').AsFloat;

    if DataSet.FieldByName('formula_temperatura_otros').AsString = '' then
      dmgMain.qryExec.ParamByName('formula').Value := Null
    else
      dmgMain.qryExec.ParamByName('formula').AsString := DataSet.FieldByName('formula_temperatura_otros').AsString;

    dmgMain.qryExec.ParamByName('final').AsInteger := DataSet.FieldByName('proceso_final').AsInteger;

    if (DataSet.FieldByName('proceso_final').AsInteger = 1) and (DataSet.FieldByName('id_articulo_resultado').AsString <> '') then
      dmgMain.qryExec.ParamByName('art_res').AsString := DataSet.FieldByName('id_articulo_resultado').AsString
    else
      dmgMain.qryExec.ParamByName('art_res').Value := Null;

    dmgMain.qryExec.ExecSQL;
  end;
end;

procedure TfrmRecetasEditor.mtIngredientesAfterPost(DataSet: TDataSet);
begin
  if (FRecetaId <> '') and (not DataSet.FieldByName('id_materia_prima').IsNull) and (DataSet.FieldByName('id_materia_prima').AsString <> '') then
  begin
    dmgMain.qryExec.Close;
    dmgMain.qryExec.SQL.Text :=
      'INSERT INTO ob_procesos_ingredientes (id, proceso_id, id_materia_prima, cantidad_base) ' +
      'VALUES (:id, :proceso_id, :id_materia_prima, :cantidad_base) ' +
      'ON DUPLICATE KEY UPDATE ' +
      '  proceso_id = :proceso_id, id_materia_prima = :id_materia_prima, cantidad_base = :cantidad_base';
    dmgMain.qryExec.ParamByName('id').AsString := DataSet.FieldByName('id').AsString;
    dmgMain.qryExec.ParamByName('proceso_id').AsString := DataSet.FieldByName('proceso_id').AsString;
    dmgMain.qryExec.ParamByName('id_materia_prima').AsString := DataSet.FieldByName('id_materia_prima').AsString;
    dmgMain.qryExec.ParamByName('cantidad_base').AsFloat := DataSet.FieldByName('cantidad_base').AsFloat;
    dmgMain.qryExec.ExecSQL;
  end;
end;

procedure TfrmRecetasEditor.mtIngredientesArticuloCodigoChange(Sender: TField);
var
  Qry: TFDQuery;
  LCode: string;
begin
  LCode := Trim(Sender.AsString);
  if LCode = '' then Exit;
  
  Sender.OnChange := nil;
  try
    Qry := TFDQuery.Create(nil);
    try
      Qry.Connection := dmgMain.dbConn;
      Qry.SQL.Text := 
        'SELECT id, descripcion FROM (' +
        '  SELECT id, CONVERT(descripcion USING utf8mb4) COLLATE utf8mb4_general_ci AS descripcion FROM ge_materias_primas WHERE id = :id ' +
        '  UNION ALL ' +
        '  SELECT id, CONVERT(descripcion USING utf8mb4) COLLATE utf8mb4_general_ci AS descripcion FROM ge_articulos WHERE id = :id AND ingrediente = 1 ' +
        ') t LIMIT 1';
      Qry.ParamByName('id').AsString := LCode;
      Qry.Open;
      
      if not Qry.IsEmpty then
      begin
        mtIngredientes.FieldByName('id_materia_prima').AsString := Qry.FieldByName('id').AsString;
        mtIngredientes.FieldByName('articulo_codigo').AsString := Qry.FieldByName('id').AsString;
        mtIngredientes.FieldByName('articulo_descripcion').AsString := Qry.FieldByName('descripcion').AsString;
      end
      else
      begin
        mtIngredientes.FieldByName('id_materia_prima').Clear;
        mtIngredientes.FieldByName('articulo_descripcion').AsString := 'NO ENCONTRADO';
      end;
    finally
      Qry.Free;
    end;
  finally
    Sender.OnChange := mtIngredientesArticuloCodigoChange;
  end;
end;

end.
