unit frm_FichaTecnicaEditor;

interface

uses
  Winapi.Windows, Winapi.Messages, System.SysUtils, System.Variants, System.Classes, System.Math, Vcl.Graphics,
  Vcl.Controls, Vcl.Forms, Vcl.Dialogs, uBaseForm, FireDAC.Stan.Intf,
  FireDAC.Stan.Option, FireDAC.Stan.Param, FireDAC.Stan.Error, FireDAC.DatS,
  FireDAC.Phys.Intf, FireDAC.DApt.Intf, FireDAC.Stan.Async, FireDAC.DApt,
  Data.DB, FireDAC.Comp.DataSet, FireDAC.Comp.Client, Vcl.StdCtrls, Vcl.Buttons,
  Vcl.ExtCtrls, Vcl.ComCtrls, uAppTheme, Vcl.Grids, Vcl.DBGrids;

type
  TfrmFichaTecnicaEditor = class(TfrmBase)
    tsGeneral: TTabSheet;
    tsAlergenos: TTabSheet;
    tsMicrobio: TTabSheet;
    tsNutricional: TTabSheet;
    tsConservacion: TTabSheet;
    qryFicha: TFDQuery;
    lblRevision: TLabel;
    edtRevision: TEdit;
    lblFechaAprobacion: TLabel;
    dtpFechaAprobacion: TDateTimePicker;
    lblFechaRevision: TLabel;
    dtpFechaRevision: TDateTimePicker;
    lblElaboradoPor: TLabel;
    edtElaboradoPor: TEdit;
    lblRevisadoPor: TLabel;
    edtRevisadoPor: TEdit;
    lblAprobadoPor: TLabel;
    edtAprobadoPor: TEdit;
    lblAceptadoPor: TLabel;
    edtAceptadoPor: TEdit;
    lblFechaAceptacion: TLabel;
    dtpFechaAceptacion: TDateTimePicker;
    lblRazonSocial: TLabel;
    edtRazonSocial: TEdit;
    lblDireccionOperador: TLabel;
    edtDireccionOperador: TEdit;
    lblRGSEAA: TLabel;
    edtRGSEAA: TEdit;
    lblDenominacionLegal: TLabel;
    edtDenominacionLegal: TEdit;
    lblCodigoBarras: TLabel;
    edtCodigoBarras: TEdit;
    lblDescripcionProducto: TLabel;
    memDescripcionProducto: TMemo;
    lblIngredientes: TLabel;
    memIngredientes: TMemo;
    lblOMG: TLabel;
    memOMG: TMemo;

    // Tab 2: Alérgenos y Analítica
    lblAlergenosReceta: TLabel;
    memAlergenosReceta: TMemo;
    lblAlergenosTrazas: TLabel;
    memAlergenosTrazas: TMemo;
    lblAW: TLabel;
    edtAW: TEdit;
    lblAWCritica: TLabel;
    edtAWCritica: TEdit;
    lblHumedad: TLabel;
    edtHumedad: TEdit;
    lblPH: TLabel;
    edtPH: TEdit;

    // Tab 3: Microbiología
    lblMicroMohos: TLabel;
    edtMicroMohos: TEdit;
    lblMicroLevaduras: TLabel;
    edtMicroLevaduras: TEdit;
    lblMicroAerobios: TLabel;
    edtMicroAerobios: TEdit;
    lblMicroEntero: TLabel;
    edtMicroEntero: TEdit;
    lblMicroSalmonella: TLabel;
    edtMicroSalmonella: TEdit;
    lblMicroListeria: TLabel;
    edtMicroListeria: TEdit;
    lblMicroEcoli: TLabel;
    edtMicroEcoli: TEdit;

    // Tab 4: Nutricional y Organolépticas
    lblEnergiaKj: TLabel;
    edtEnergiaKj: TEdit;
    lblEnergiaKcal: TLabel;
    edtEnergiaKcal: TEdit;
    lblGrasas: TLabel;
    edtGrasas: TEdit;
    lblGrasasSat: TLabel;
    edtGrasasSat: TEdit;
    lblHidratos: TLabel;
    edtHidratos: TEdit;
    lblAzucares: TLabel;
    edtAzucares: TEdit;
    lblFibra: TLabel;
    edtFibra: TEdit;
    lblProteinas: TLabel;
    edtProteinas: TEdit;
    lblSal: TLabel;
    edtSal: TEdit;
    lblAspecto: TLabel;
    memAspecto: TMemo;
    lblOlor: TLabel;
    memOlor: TMemo;
    lblTextura: TLabel;
    memTextura: TMemo;

    // Tab 5: Conservación y Uso
    lblInstruccionesUso: TLabel;
    memInstruccionesUso: TMemo;
    lblPublicoObjetivo: TLabel;
    edtPublicoObjetivo: TEdit;
    lblGruposVulnerables: TLabel;
    memGruposVulnerables: TMemo;
    lblCantidadNeta: TLabel;
    edtCantidadNeta: TEdit;
    lblAlmacenamiento: TLabel;
    memAlmacenamiento: TMemo;
    lblVidaUtil: TLabel;
    edtVidaUtil: TEdit;
    lblGestionLotes: TLabel;
    memGestionLotes: TMemo;
    lblTransporte: TLabel;
    memTransporte: TMemo;
    lblCuerposExtranos: TLabel;
    memCuerposExtranos: TMemo;
    lblHistoricoCambios: TLabel;
    memHistoricoCambios: TMemo;
    lblCodigo: TLabel;
    edtCodigoFicha: TEdit;

    procedure FormShow(Sender: TObject);
    procedure btnSaveClick(Sender: TObject);
    procedure btnSalirClick(Sender: TObject);
    procedure btnImprimirClick(Sender: TObject);
    procedure btnBuscarClick(Sender: TObject);
    procedure btnEliminarClick(Sender: TObject);
    procedure NumericEditKeyPress(Sender: TObject; var Key: Char);
    procedure NumericEditExit(Sender: TObject);
  private
    FArticuloId: Integer;
    FIsNewRecord: Boolean;
    procedure LoadData;
  public
    property ArticuloId: Integer read FArticuloId write FArticuloId;
  end;

var
  frmFichaTecnicaEditor: TfrmFichaTecnicaEditor;

implementation

uses
  dmg_Main, uDbErrorHandler, frm_PrintOptions, frm_SelectProduct, uFormatosImpresionService;

{$R *.dfm}

procedure SetParamMemo(AParam: TFDParam; const AValue: string);
begin
  if AParam = nil then Exit;
  AParam.DataType := ftWideMemo;
  AParam.AsWideMemo := AValue;
end;

procedure SetParamString(AParam: TFDParam; const AValue: string);
begin
  if AParam = nil then Exit;
  AParam.DataType := ftWideString;
  AParam.Size := Max(100, (Length(AValue) * 4) + 64);
  AParam.AsWideString := AValue;
end;

function ParseFloatUniversal(const AStr: string): Double;
var
  LStr: string;
  LFS: TFormatSettings;
  LDotPos, LCommaPos: Integer;
begin
  Result := 0.0;
  LStr := Trim(AStr);
  if LStr = '' then Exit;

  // Eliminar espacios
  LStr := StringReplace(LStr, ' ', '', [rfReplaceAll]);

  LDotPos := LastDelimiter('.', LStr);
  LCommaPos := LastDelimiter(',', LStr);

  if (LDotPos > 0) and (LCommaPos > 0) then
  begin
    if LDotPos > LCommaPos then
    begin
      // Formato con comas de miles y punto decimal: 1,234.56 -> 1234.56
      LStr := StringReplace(LStr, ',', '', [rfReplaceAll]);
    end
    else
    begin
      // Formato con puntos de miles y coma decimal: 1.234,56 -> 1234.56
      LStr := StringReplace(LStr, '.', '', [rfReplaceAll]);
      LStr := StringReplace(LStr, ',', '.', [rfReplaceAll]);
    end;
  end
  else if LCommaPos > 0 then
  begin
    // Solo tiene coma como separador decimal: 12,50 -> 12.50
    LStr := StringReplace(LStr, ',', '.', [rfReplaceAll]);
  end;

  LFS := TFormatSettings.Create;
  LFS.DecimalSeparator := '.';
  if not TryStrToFloat(LStr, Result, LFS) then
  begin
    TryStrToFloat(Trim(AStr), Result);
  end;
end;

procedure TfrmFichaTecnicaEditor.NumericEditKeyPress(Sender: TObject; var Key: Char);
begin
  // Permitir teclas de control estándar (Backspace, Enter, Tab, etc.)
  if Key < #32 then Exit;

  // Permitir dígitos, signos y separadores decimales (punto y coma)
  if not (CharInSet(Key, ['0'..'9', '.', ',', '-'])) then
    Key := #0;
end;

procedure TfrmFichaTecnicaEditor.NumericEditExit(Sender: TObject);
var
  LEdit: TEdit;
  LVal: Double;
begin
  if Sender is TEdit then
  begin
    LEdit := TEdit(Sender);
    if Trim(LEdit.Text) <> '' then
    begin
      LVal := ParseFloatUniversal(LEdit.Text);
      LEdit.Text := FormatFloat('#,##0.00', LVal);
    end;
  end;
end;

procedure TfrmFichaTecnicaEditor.FormShow(Sender: TObject);
begin
  inherited;
  TAppTheme.ApplyToForm(Self);
  
  // Re-enlazar botones base si no lo están
  btnGuardar.OnClick := btnSaveClick;
  btnSalir.OnClick := btnSalirClick;
  btnImprimir.OnClick := btnImprimirClick;
  btnBuscar.OnClick := btnBuscarClick;
  btnBuscar.Caption := 'Copiar Ficha';
  btnBuscar.Visible := True;
  
  btnEliminar.OnClick := btnEliminarClick;
  btnEliminar.Visible := True;
  
  // Ocultar botones no usados de TfrmBase
  btnNuevoArticulo.Visible := False;
  btnEliminaArticulo.Visible := False;
  btnBuscarArticulo.Visible := False;
  btnExcel.Visible := False;
  
  // Forzar que se pinte correctamente la primera pestaña al abrir
  pgcDetails.ActivePage := tsGeneral;

  // Asignar eventos de control para campos numéricos/decimales
  edtEnergiaKj.OnKeyPress := NumericEditKeyPress;
  edtEnergiaKj.OnExit := NumericEditExit;
  edtEnergiaKcal.OnKeyPress := NumericEditKeyPress;
  edtEnergiaKcal.OnExit := NumericEditExit;
  edtGrasas.OnKeyPress := NumericEditKeyPress;
  edtGrasas.OnExit := NumericEditExit;
  edtGrasasSat.OnKeyPress := NumericEditKeyPress;
  edtGrasasSat.OnExit := NumericEditExit;
  edtHidratos.OnKeyPress := NumericEditKeyPress;
  edtHidratos.OnExit := NumericEditExit;
  edtAzucares.OnKeyPress := NumericEditKeyPress;
  edtAzucares.OnExit := NumericEditExit;
  edtFibra.OnKeyPress := NumericEditKeyPress;
  edtFibra.OnExit := NumericEditExit;
  edtProteinas.OnKeyPress := NumericEditKeyPress;
  edtProteinas.OnExit := NumericEditExit;
  edtSal.OnKeyPress := NumericEditKeyPress;
  edtSal.OnExit := NumericEditExit;
  
  LoadData;
end;

procedure TfrmFichaTecnicaEditor.LoadData;
var
  QryIng, QryAlg, QryCheck, QryUpd: TFDQuery;
  LIngStr, LRecetaStr, LTrazasStr: string;
  LRecordExists: Boolean;
begin
  if FArticuloId <= 0 then
  begin
    ShowMessage('ID de artículo no válido.');
    Close;
    Exit;
  end;

  // 1. Obtener ingredientes del artículo desde ge_articulos.ingredientes
  QryIng := TFDQuery.Create(nil);
  try
    QryIng.Connection := dmgMain.dbConn;
    QryIng.SQL.Text := 'SELECT ingredientes FROM ge_articulos WHERE id = :id';
    QryIng.ParamByName('id').AsInteger := FArticuloId;
    QryIng.Open;
    if not QryIng.IsEmpty then
      LIngStr := QryIng.FieldByName('ingredientes').AsString
    else
      LIngStr := '';
  finally
    QryIng.Free;
  end;

  // 2. Calcular alérgenos de ge_articulos_alergenos de forma dinámica
  QryAlg := TFDQuery.Create(nil);
  try
    QryAlg.Connection := dmgMain.dbConn;
    QryAlg.SQL.Text := 
      'SELECT a.nombre, aa.presencia FROM ge_articulos_alergenos aa ' +
      'INNER JOIN ge_alergenos a ON aa.id_alergeno = a.id ' +
      'WHERE aa.id_articulo = :id_articulo ORDER BY a.nombre';
    QryAlg.ParamByName('id_articulo').AsInteger := FArticuloId;
    QryAlg.Open;
    LRecetaStr := '';
    LTrazasStr := '';
    while not QryAlg.Eof do
    begin
      var LNombre := QryAlg.FieldByName('nombre').AsString;
      var LPresencia := QryAlg.FieldByName('presencia').AsString;
      if SameText(LPresencia, 'Contiene') then
      begin
        if LRecetaStr <> '' then LRecetaStr := LRecetaStr + ', ';
        LRecetaStr := LRecetaStr + LNombre;
      end
      else if SameText(LPresencia, 'Trazas') then
      begin
        if LTrazasStr <> '' then LTrazasStr := LTrazasStr + ', ';
        LTrazasStr := LTrazasStr + LNombre;
      end;
      QryAlg.Next;
    end;
  finally
    QryAlg.Free;
  end;

  // 3. Comprobar si el registro existe y actualizar los campos de ingredientes/alérgenos en la DB antes de cargar qryFicha
  LRecordExists := False;
  QryCheck := TFDQuery.Create(nil);
  try
    QryCheck.Connection := dmgMain.dbConn;
    QryCheck.SQL.Text := 'SELECT COUNT(*) FROM ge_articulos_fichas_tecnicas WHERE id_articulo = :id_articulo';
    QryCheck.ParamByName('id_articulo').AsInteger := FArticuloId;
    QryCheck.Open;
    LRecordExists := QryCheck.Fields[0].AsInteger > 0;
  finally
    QryCheck.Free;
  end;

  if LRecordExists then
  begin
    QryUpd := TFDQuery.Create(nil);
    try
      QryUpd.Connection := dmgMain.dbConn;
      QryUpd.SQL.Text := 
        'UPDATE ge_articulos_fichas_tecnicas SET ' +
        '  ingredientes = :ingredientes, ' +
        '  alergenos_receta = :alergenos_receta, ' +
        '  alergenos_trazas = :alergenos_trazas ' +
        'WHERE id_articulo = :id_articulo';
      SetParamMemo(QryUpd.ParamByName('ingredientes'), LIngStr);
      SetParamMemo(QryUpd.ParamByName('alergenos_receta'), LRecetaStr);
      SetParamMemo(QryUpd.ParamByName('alergenos_trazas'), LTrazasStr);
      QryUpd.ParamByName('id_articulo').AsInteger := FArticuloId;
      QryUpd.ExecSQL;
    finally
      QryUpd.Free;
    end;
  end;

  // 4. Abrir la consulta de la ficha técnica (ahora ya tendrá los datos sincronizados en la BD)
  qryFicha.Close;
  qryFicha.SQL.Text := 'SELECT * FROM ge_articulos_fichas_tecnicas WHERE id_articulo = :id_articulo';
  qryFicha.ParamByName('id_articulo').AsInteger := FArticuloId;
  qryFicha.Open;

  FIsNewRecord := qryFicha.IsEmpty;

  if FIsNewRecord then
  begin
    // Inicializar campos vacíos
    edtCodigoFicha.Text := '';
    edtRevision.Text := '';
    dtpFechaAprobacion.Checked := False;
    dtpFechaRevision.Checked := False;
    edtElaboradoPor.Text := '';
    edtRevisadoPor.Text := '';
    edtAprobadoPor.Text := '';
    edtAceptadoPor.Text := '';
    dtpFechaAceptacion.Checked := False;
    edtRazonSocial.Text := '';
    edtDireccionOperador.Text := '';
    edtRGSEAA.Text := '';
    edtDenominacionLegal.Text := '';
    edtCodigoBarras.Text := '';
    memDescripcionProducto.Lines.Clear;
    memIngredientes.Text := LIngStr;
    memOMG.Lines.Clear;
    
    memAlergenosReceta.Text := LRecetaStr;
    memAlergenosTrazas.Text := LTrazasStr;
    edtAW.Text := '';
    edtAWCritica.Text := '';
    edtHumedad.Text := '';
    edtPH.Text := '';
    
    edtMicroMohos.Text := '';
    edtMicroLevaduras.Text := '';
    edtMicroAerobios.Text := '';
    edtMicroEntero.Text := '';
    edtMicroSalmonella.Text := '';
    edtMicroListeria.Text := '';
    edtMicroEcoli.Text := '';
    
    edtEnergiaKj.Text := '0,00';
    edtEnergiaKcal.Text := '0,00';
    edtGrasas.Text := '0,00';
    edtGrasasSat.Text := '0,00';
    edtHidratos.Text := '0,00';
    edtAzucares.Text := '0,00';
    edtFibra.Text := '0,00';
    edtProteinas.Text := '0,00';
    edtSal.Text := '0,00';
    memAspecto.Lines.Clear;
    memOlor.Lines.Clear;
    memTextura.Lines.Clear;
    
    memInstruccionesUso.Lines.Clear;
    edtPublicoObjetivo.Text := '';
    memGruposVulnerables.Lines.Clear;
    edtCantidadNeta.Text := '';
    memAlmacenamiento.Lines.Clear;
    edtVidaUtil.Text := '';
    memGestionLotes.Lines.Clear;
    memTransporte.Lines.Clear;
    memCuerposExtranos.Lines.Clear;
    memHistoricoCambios.Lines.Clear;
  end
  else
  begin
    // Cargar datos
    edtCodigoFicha.Text := qryFicha.FieldByName('codigo_ficha').AsString;
    edtRevision.Text := qryFicha.FieldByName('revision').AsString;
    if not qryFicha.FieldByName('fecha_aprobacion').IsNull then
    begin
      dtpFechaAprobacion.Date := qryFicha.FieldByName('fecha_aprobacion').AsDateTime;
      dtpFechaAprobacion.Checked := True;
    end else dtpFechaAprobacion.Checked := False;
    
    if not qryFicha.FieldByName('fecha_revision').IsNull then
    begin
      dtpFechaRevision.Date := qryFicha.FieldByName('fecha_revision').AsDateTime;
      dtpFechaRevision.Checked := True;
    end else dtpFechaRevision.Checked := False;
    
    edtElaboradoPor.Text := qryFicha.FieldByName('elaborado_por').AsString;
    edtRevisadoPor.Text := qryFicha.FieldByName('revisado_por').AsString;
    edtAprobadoPor.Text := qryFicha.FieldByName('aprobado_por').AsString;
    edtAceptadoPor.Text := qryFicha.FieldByName('aceptado_por_cliente').AsString;
    
    if not qryFicha.FieldByName('fecha_aceptacion').IsNull then
    begin
      dtpFechaAceptacion.Date := qryFicha.FieldByName('fecha_aceptacion').AsDateTime;
      dtpFechaAceptacion.Checked := True;
    end else dtpFechaAceptacion.Checked := False;
    
    edtRazonSocial.Text := qryFicha.FieldByName('razon_social_operador').AsString;
    edtDireccionOperador.Text := qryFicha.FieldByName('direccion_operador').AsString;
    edtRGSEAA.Text := qryFicha.FieldByName('rgseaa').AsString;
    edtDenominacionLegal.Text := qryFicha.FieldByName('denominacion_legal').AsString;
    edtCodigoBarras.Text := qryFicha.FieldByName('codigo_barras').AsString;
    memDescripcionProducto.Text := qryFicha.FieldByName('descripcion_producto').AsString;
    memIngredientes.Text := qryFicha.FieldByName('ingredientes').AsString;
    memOMG.Text := qryFicha.FieldByName('omg').AsString;
    
    memAlergenosReceta.Text := qryFicha.FieldByName('alergenos_receta').AsString;
    memAlergenosTrazas.Text := qryFicha.FieldByName('alergenos_trazas').AsString;
    edtAW.Text := qryFicha.FieldByName('aw').AsString;
    edtAWCritica.Text := qryFicha.FieldByName('aw_critica').AsString;
    edtHumedad.Text := qryFicha.FieldByName('humedad').AsString;
    edtPH.Text := qryFicha.FieldByName('ph').AsString;
    
    edtMicroMohos.Text := qryFicha.FieldByName('micro_mohos').AsString;
    edtMicroLevaduras.Text := qryFicha.FieldByName('micro_levaduras').AsString;
    edtMicroAerobios.Text := qryFicha.FieldByName('micro_aerobios').AsString;
    edtMicroEntero.Text := qryFicha.FieldByName('micro_enterobacterias').AsString;
    edtMicroSalmonella.Text := qryFicha.FieldByName('micro_salmonella').AsString;
    edtMicroListeria.Text := qryFicha.FieldByName('micro_listeria').AsString;
    edtMicroEcoli.Text := qryFicha.FieldByName('micro_ecoli').AsString;
    
    edtEnergiaKj.Text := FormatFloat('#,##0.00', qryFicha.FieldByName('nutricional_energia_kj').AsFloat);
    edtEnergiaKcal.Text := FormatFloat('#,##0.00', qryFicha.FieldByName('nutricional_energia_kcal').AsFloat);
    edtGrasas.Text := FormatFloat('#,##0.00', qryFicha.FieldByName('nutricional_grasas').AsFloat);
    edtGrasasSat.Text := FormatFloat('#,##0.00', qryFicha.FieldByName('nutricional_grasas_saturadas').AsFloat);
    edtHidratos.Text := FormatFloat('#,##0.00', qryFicha.FieldByName('nutricional_hidratos').AsFloat);
    edtAzucares.Text := FormatFloat('#,##0.00', qryFicha.FieldByName('nutricional_azucares').AsFloat);
    edtFibra.Text := FormatFloat('#,##0.00', qryFicha.FieldByName('nutricional_fibra').AsFloat);
    edtProteinas.Text := FormatFloat('#,##0.00', qryFicha.FieldByName('nutricional_proteinas').AsFloat);
    edtSal.Text := FormatFloat('#,##0.00', qryFicha.FieldByName('nutricional_sal').AsFloat);
    memAspecto.Text := qryFicha.FieldByName('organolepticas_aspecto').AsString;
    memOlor.Text := qryFicha.FieldByName('organolepticas_olor').AsString;
    memTextura.Text := qryFicha.FieldByName('organolepticas_textura').AsString;
    
    memInstruccionesUso.Text := qryFicha.FieldByName('instrucciones_uso').AsString;
    edtPublicoObjetivo.Text := qryFicha.FieldByName('publico_objetivo').AsString;
    memGruposVulnerables.Text := qryFicha.FieldByName('grupos_vulnerables').AsString;
    edtCantidadNeta.Text := qryFicha.FieldByName('cantidad_neta').AsString;
    memAlmacenamiento.Text := qryFicha.FieldByName('condiciones_almacenamiento').AsString;
    edtVidaUtil.Text := qryFicha.FieldByName('vida_util').AsString;
    memGestionLotes.Text := qryFicha.FieldByName('gestion_lotes').AsString;
    memTransporte.Text := qryFicha.FieldByName('condiciones_transporte').AsString;
    memCuerposExtranos.Text := qryFicha.FieldByName('mitigacion_cuerpos_extranos').AsString;
    memHistoricoCambios.Text := qryFicha.FieldByName('historico_cambios').AsString;
  end;
end;

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

procedure TfrmFichaTecnicaEditor.btnSaveClick(Sender: TObject);
begin
  dmgMain.qryExec.Close;
  if FIsNewRecord then
  begin
    dmgMain.qryExec.SQL.Text :=
      'INSERT INTO ge_articulos_fichas_tecnicas ( ' +
      '  id_articulo, codigo_ficha, revision, fecha_aprobacion, fecha_revision, ' +
      '  elaborado_por, revisado_por, aprobado_por, aceptado_por_cliente, fecha_aceptacion, ' +
      '  razon_social_operador, direccion_operador, rgseaa, denominacion_legal, ' +
      '  codigo_barras, descripcion_producto, ingredientes, omg, alergenos_receta, ' +
      '  alergenos_trazas, aw, aw_critica, humedad, ph, micro_mohos, micro_levaduras, ' +
      '  micro_aerobios, micro_enterobacterias, micro_salmonella, micro_listeria, ' +
      '  micro_ecoli, organolepticas_aspecto, organolepticas_olor, organolepticas_textura, ' +
      '  nutricional_energia_kj, nutricional_energia_kcal, nutricional_grasas, ' +
      '  nutricional_grasas_saturadas, nutricional_hidratos, nutricional_azucares, ' +
      '  nutricional_fibra, nutricional_proteinas, nutricional_sal, instrucciones_uso, publico_objetivo, ' +
      '  grupos_vulnerables, cantidad_neta, condiciones_almacenamiento, vida_util, ' +
      '  gestion_lotes, condiciones_transporte, mitigacion_cuerpos_extranos, historico_cambios ' +
      ') VALUES ( ' +
      '  :id_articulo, :codigo_ficha, :revision, :fecha_aprobacion, :fecha_revision, ' +
      '  :elaborado_por, :revisado_por, :aprobado_por, :aceptado_por_cliente, :fecha_aceptacion, ' +
      '  :razon_social_operador, :direccion_operador, :rgseaa, :denominacion_legal, ' +
      '  :codigo_barras, :descripcion_producto, :ingredientes, :omg, :alergenos_receta, ' +
      '  :alergenos_trazas, :aw, :aw_critica, :humedad, :ph, :micro_mohos, :micro_levaduras, ' +
      '  :micro_aerobios, :micro_enterobacterias, :micro_salmonella, :micro_listeria, ' +
      '  :micro_ecoli, :organolepticas_aspecto, :organolepticas_olor, :organolepticas_textura, ' +
      '  :nutricional_energia_kj, :nutricional_energia_kcal, :nutricional_grasas, ' +
      '  :nutricional_grasas_saturadas, :nutricional_hidratos, :nutricional_azucares, ' +
      '  :nutricional_fibra, :nutricional_proteinas, :nutricional_sal, :instrucciones_uso, :publico_objetivo, ' +
      '  :grupos_vulnerables, :cantidad_neta, :condiciones_almacenamiento, :vida_util, ' +
      '  :gestion_lotes, :condiciones_transporte, :mitigacion_cuerpos_extranos, :historico_cambios ' +
      ')';
  end
  else
  begin
    dmgMain.qryExec.SQL.Text :=
      'UPDATE ge_articulos_fichas_tecnicas SET ' +
      '  codigo_ficha = :codigo_ficha, revision = :revision, fecha_aprobacion = :fecha_aprobacion, ' +
      '  fecha_revision = :fecha_revision, elaborado_por = :elaborado_por, revisado_por = :revisado_por, ' +
      '  aprobado_por = :aprobado_por, aceptado_por_cliente = :aceptado_por_cliente, fecha_aceptacion = :fecha_aceptacion, ' +
      '  razon_social_operador = :razon_social_operador, direccion_operador = :direccion_operador, rgseaa = :rgseaa, ' +
      '  denominacion_legal = :denominacion_legal, codigo_barras = :codigo_barras, descripcion_producto = :descripcion_producto, ' +
      '  ingredientes = :ingredientes, omg = :omg, alergenos_receta = :alergenos_receta, ' +
      '  alergenos_trazas = :alergenos_trazas, aw = :aw, aw_critica = :aw_critica, humedad = :humedad, ph = :ph, ' +
      '  micro_mohos = :micro_mohos, micro_levaduras = :micro_levaduras, micro_aerobios = :micro_aerobios, ' +
      '  micro_enterobacterias = :micro_enterobacterias, micro_salmonella = :micro_salmonella, micro_listeria = :micro_listeria, ' +
      '  micro_ecoli = :micro_ecoli, organolepticas_aspecto = :organolepticas_aspecto, organolepticas_olor = :organolepticas_olor, ' +
      '  organolepticas_textura = :organolepticas_textura, nutricional_energia_kj = :nutricional_energia_kj, ' +
      '  nutricional_energia_kcal = :nutricional_energia_kcal, nutricional_grasas = :nutricional_grasas, ' +
      '  nutricional_grasas_saturadas = :nutricional_grasas_saturadas, nutricional_hidratos = :nutricional_hidratos, ' +
      '  nutricional_azucares = :nutricional_azucares, nutricional_fibra = :nutricional_fibra, ' +
      '  nutricional_proteinas = :nutricional_proteinas, nutricional_sal = :nutricional_sal, ' +
      '  instrucciones_uso = :instrucciones_uso, publico_objetivo = :publico_objetivo, grupos_vulnerables = :grupos_vulnerables, ' +
      '  cantidad_neta = :cantidad_neta, condiciones_almacenamiento = :condiciones_almacenamiento, vida_util = :vida_util, ' +
      '  gestion_lotes = :gestion_lotes, condiciones_transporte = :condiciones_transporte, ' +
      '  mitigacion_cuerpos_extranos = :mitigacion_cuerpos_extranos, historico_cambios = :historico_cambios ' +
      'WHERE id_articulo = :id_articulo';
  end;

  dmgMain.qryExec.ParamByName('id_articulo').AsInteger := FArticuloId;
  
  // Strings & Memos
  SetParamString(dmgMain.qryExec.ParamByName('codigo_ficha'), edtCodigoFicha.Text);
  SetParamString(dmgMain.qryExec.ParamByName('revision'), edtRevision.Text);
  SetParamString(dmgMain.qryExec.ParamByName('elaborado_por'), edtElaboradoPor.Text);
  SetParamString(dmgMain.qryExec.ParamByName('revisado_por'), edtRevisadoPor.Text);
  SetParamString(dmgMain.qryExec.ParamByName('aprobado_por'), edtAprobadoPor.Text);
  SetParamString(dmgMain.qryExec.ParamByName('aceptado_por_cliente'), edtAceptadoPor.Text);
  SetParamString(dmgMain.qryExec.ParamByName('razon_social_operador'), edtRazonSocial.Text);
  SetParamString(dmgMain.qryExec.ParamByName('direccion_operador'), edtDireccionOperador.Text);
  SetParamString(dmgMain.qryExec.ParamByName('rgseaa'), edtRGSEAA.Text);
  SetParamString(dmgMain.qryExec.ParamByName('denominacion_legal'), edtDenominacionLegal.Text);
  SetParamString(dmgMain.qryExec.ParamByName('codigo_barras'), edtCodigoBarras.Text);
  
  SetParamMemo(dmgMain.qryExec.ParamByName('descripcion_producto'), memDescripcionProducto.Text);
  SetParamMemo(dmgMain.qryExec.ParamByName('ingredientes'), memIngredientes.Text);
  SetParamMemo(dmgMain.qryExec.ParamByName('omg'), memOMG.Text);
  SetParamMemo(dmgMain.qryExec.ParamByName('alergenos_receta'), memAlergenosReceta.Text);
  SetParamMemo(dmgMain.qryExec.ParamByName('alergenos_trazas'), memAlergenosTrazas.Text);
  
  SetParamString(dmgMain.qryExec.ParamByName('aw'), edtAW.Text);
  SetParamString(dmgMain.qryExec.ParamByName('aw_critica'), edtAWCritica.Text);
  SetParamString(dmgMain.qryExec.ParamByName('humedad'), edtHumedad.Text);
  SetParamString(dmgMain.qryExec.ParamByName('ph'), edtPH.Text);
  
  SetParamString(dmgMain.qryExec.ParamByName('micro_mohos'), edtMicroMohos.Text);
  SetParamString(dmgMain.qryExec.ParamByName('micro_levaduras'), edtMicroLevaduras.Text);
  SetParamString(dmgMain.qryExec.ParamByName('micro_aerobios'), edtMicroAerobios.Text);
  SetParamString(dmgMain.qryExec.ParamByName('micro_enterobacterias'), edtMicroEntero.Text);
  SetParamString(dmgMain.qryExec.ParamByName('micro_salmonella'), edtMicroSalmonella.Text);
  SetParamString(dmgMain.qryExec.ParamByName('micro_listeria'), edtMicroListeria.Text);
  SetParamString(dmgMain.qryExec.ParamByName('micro_ecoli'), edtMicroEcoli.Text);
  
  SetParamMemo(dmgMain.qryExec.ParamByName('organolepticas_aspecto'), memAspecto.Text);
  SetParamMemo(dmgMain.qryExec.ParamByName('organolepticas_olor'), memOlor.Text);
  SetParamMemo(dmgMain.qryExec.ParamByName('organolepticas_textura'), memTextura.Text);
  
  SetParamMemo(dmgMain.qryExec.ParamByName('instrucciones_uso'), memInstruccionesUso.Text);
  SetParamString(dmgMain.qryExec.ParamByName('publico_objetivo'), edtPublicoObjetivo.Text);
  SetParamMemo(dmgMain.qryExec.ParamByName('grupos_vulnerables'), memGruposVulnerables.Text);
  SetParamString(dmgMain.qryExec.ParamByName('cantidad_neta'), edtCantidadNeta.Text);
  SetParamMemo(dmgMain.qryExec.ParamByName('condiciones_almacenamiento'), memAlmacenamiento.Text);
  SetParamString(dmgMain.qryExec.ParamByName('vida_util'), edtVidaUtil.Text);
  SetParamMemo(dmgMain.qryExec.ParamByName('gestion_lotes'), memGestionLotes.Text);
  SetParamMemo(dmgMain.qryExec.ParamByName('condiciones_transporte'), memTransporte.Text);
  SetParamMemo(dmgMain.qryExec.ParamByName('mitigacion_cuerpos_extranos'), memCuerposExtranos.Text);
  SetParamMemo(dmgMain.qryExec.ParamByName('historico_cambios'), memHistoricoCambios.Text);

  // Fechas (manejando Nulos)
  dmgMain.qryExec.ParamByName('fecha_aprobacion').DataType := ftDate;
  if dtpFechaAprobacion.Checked then
    dmgMain.qryExec.ParamByName('fecha_aprobacion').AsDate := dtpFechaAprobacion.Date
  else
    dmgMain.qryExec.ParamByName('fecha_aprobacion').Clear;
    
  dmgMain.qryExec.ParamByName('fecha_revision').DataType := ftDate;
  if dtpFechaRevision.Checked then
    dmgMain.qryExec.ParamByName('fecha_revision').AsDate := dtpFechaRevision.Date
  else
    dmgMain.qryExec.ParamByName('fecha_revision').Clear;
    
  dmgMain.qryExec.ParamByName('fecha_aceptacion').DataType := ftDate;
  if dtpFechaAceptacion.Checked then
    dmgMain.qryExec.ParamByName('fecha_aceptacion').AsDate := dtpFechaAceptacion.Date
  else
    dmgMain.qryExec.ParamByName('fecha_aceptacion').Clear;

  // Numeros / Decimals
  dmgMain.qryExec.ParamByName('nutricional_energia_kj').AsFloat := ParseFloatUniversal(edtEnergiaKj.Text);
  dmgMain.qryExec.ParamByName('nutricional_energia_kcal').AsFloat := ParseFloatUniversal(edtEnergiaKcal.Text);
  dmgMain.qryExec.ParamByName('nutricional_grasas').AsFloat := ParseFloatUniversal(edtGrasas.Text);
  dmgMain.qryExec.ParamByName('nutricional_grasas_saturadas').AsFloat := ParseFloatUniversal(edtGrasasSat.Text);
  dmgMain.qryExec.ParamByName('nutricional_hidratos').AsFloat := ParseFloatUniversal(edtHidratos.Text);
  dmgMain.qryExec.ParamByName('nutricional_azucares').AsFloat := ParseFloatUniversal(edtAzucares.Text);
  dmgMain.qryExec.ParamByName('nutricional_fibra').AsFloat := ParseFloatUniversal(edtFibra.Text);
  dmgMain.qryExec.ParamByName('nutricional_proteinas').AsFloat := ParseFloatUniversal(edtProteinas.Text);
  dmgMain.qryExec.ParamByName('nutricional_sal').AsFloat := ParseFloatUniversal(edtSal.Text);

  try
    dmgMain.qryExec.ExecSQL;
    FIsNewRecord := False;
    ResetChangeTracking;
    ShowMessage('Ficha técnica guardada correctamente.');
  except
    on E: Exception do
      TDbErrorHandler.HandleException(E, dmgMain.qryExec.SQL.Text);
  end;
end;

procedure TfrmFichaTecnicaEditor.btnImprimirClick(Sender: TObject);
var
  LReportPath: string;
  LEmpresaId: Integer;
begin
  if FArticuloId <= 0 then
  begin
    ShowMessage('Debe seleccionar un artículo para imprimir su ficha técnica.');
    Exit;
  end;

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

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

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

  if not qryFicha.Active then
    LoadData;

  if qryFicha.IsEmpty then
  begin
    ShowMessage('No se ha encontrado la ficha técnica. Por favor, guarde los datos primero.');
    Exit;
  end;

  // Configurar los frxDBDatasets de diseño apuntando al query de la ficha
  dmgMain.frxDBMaster.DataSet := qryFicha;
  
  // 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.EnabledDataSets.Clear;
  dmgMain.frxInforme.EnabledDataSets.Add(dmgMain.frxDBMaster);

  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.EnabledDataSets.Clear;
        dmgMain.frxInforme.EnabledDataSets.Add(dmgMain.frxDBMaster);
      end;
    end;

  // 3. Mostrar opciones de impresión globales con selector de formato
  TfrmPrintOptions.ShowOptions(Self, dmgMain.frxInforme, nil, 'FichaTecnica', '', '', 1, nil, nil, 'FICHA_TECNICA', LEmpresaId, 0, LOnFormatChange);
end;

procedure TfrmFichaTecnicaEditor.btnBuscarClick(Sender: TObject);
var
  QrySrc: TFDQuery;
  LSourceId: Integer;
begin
  if not Assigned(frmSelectProduct) then
    Application.CreateForm(TfrmSelectProduct, frmSelectProduct);
  frmSelectProduct.MultiSelect := False;
  frmSelectProduct.OnlyWithFicha := True;
  try
    if frmSelectProduct.ShowModal = mrOk then
    begin
    if TryStrToInt(frmSelectProduct.SelectedId, LSourceId) then
    begin
      if LSourceId = FArticuloId then
      begin
        ShowMessage('No se puede copiar la ficha técnica del propio artículo.');
        Exit;
      end;
      
      QrySrc := TFDQuery.Create(nil);
      try
        QrySrc.Connection := dmgMain.dbConn;
        QrySrc.SQL.Text := 'SELECT * FROM ge_articulos_fichas_tecnicas WHERE id_articulo = :id_articulo';
        QrySrc.ParamByName('id_articulo').AsInteger := LSourceId;
        QrySrc.Open;
        
        if QrySrc.IsEmpty then
        begin
          ShowMessage('El artículo seleccionado no tiene una ficha técnica registrada.');
          Exit;
        end;
        
        // Copiar los datos del query origen a los controles del formulario
        edtCodigoFicha.Text := QrySrc.FieldByName('codigo_ficha').AsString;
        edtRevision.Text := QrySrc.FieldByName('revision').AsString;
        
        if not QrySrc.FieldByName('fecha_aprobacion').IsNull then
        begin
          dtpFechaAprobacion.Date := QrySrc.FieldByName('fecha_aprobacion').AsDateTime;
          dtpFechaAprobacion.Checked := True;
        end else dtpFechaAprobacion.Checked := False;
        
        if not QrySrc.FieldByName('fecha_revision').IsNull then
        begin
          dtpFechaRevision.Date := QrySrc.FieldByName('fecha_revision').AsDateTime;
          dtpFechaRevision.Checked := True;
        end else dtpFechaRevision.Checked := False;
        
        edtElaboradoPor.Text := QrySrc.FieldByName('elaborado_por').AsString;
        edtRevisadoPor.Text := QrySrc.FieldByName('revisado_por').AsString;
        edtAprobadoPor.Text := QrySrc.FieldByName('aprobado_por').AsString;
        edtAceptadoPor.Text := QrySrc.FieldByName('aceptado_por_cliente').AsString;
        
        if not QrySrc.FieldByName('fecha_aceptacion').IsNull then
        begin
          dtpFechaAceptacion.Date := QrySrc.FieldByName('fecha_aceptacion').AsDateTime;
          dtpFechaAceptacion.Checked := True;
        end else dtpFechaAceptacion.Checked := False;
        
        edtRazonSocial.Text := QrySrc.FieldByName('razon_social_operador').AsString;
        edtDireccionOperador.Text := QrySrc.FieldByName('direccion_operador').AsString;
        edtRGSEAA.Text := QrySrc.FieldByName('rgseaa').AsString;
        edtDenominacionLegal.Text := QrySrc.FieldByName('denominacion_legal').AsString;
        edtCodigoBarras.Text := QrySrc.FieldByName('codigo_barras').AsString;
        memDescripcionProducto.Text := QrySrc.FieldByName('descripcion_producto').AsString;
        memIngredientes.Text := QrySrc.FieldByName('ingredientes').AsString;
        memOMG.Text := QrySrc.FieldByName('omg').AsString;
        
        memAlergenosReceta.Text := QrySrc.FieldByName('alergenos_receta').AsString;
        memAlergenosTrazas.Text := QrySrc.FieldByName('alergenos_trazas').AsString;
        edtAW.Text := QrySrc.FieldByName('aw').AsString;
        edtAWCritica.Text := QrySrc.FieldByName('aw_critica').AsString;
        edtHumedad.Text := QrySrc.FieldByName('humedad').AsString;
        edtPH.Text := QrySrc.FieldByName('ph').AsString;
        
        edtMicroMohos.Text := QrySrc.FieldByName('micro_mohos').AsString;
        edtMicroLevaduras.Text := QrySrc.FieldByName('micro_levaduras').AsString;
        edtMicroAerobios.Text := QrySrc.FieldByName('micro_aerobios').AsString;
        edtMicroEntero.Text := QrySrc.FieldByName('micro_enterobacterias').AsString;
        edtMicroSalmonella.Text := QrySrc.FieldByName('micro_salmonella').AsString;
        edtMicroListeria.Text := QrySrc.FieldByName('micro_listeria').AsString;
        edtMicroEcoli.Text := QrySrc.FieldByName('micro_ecoli').AsString;
        
        edtEnergiaKj.Text := FormatFloat('#,##0.00', QrySrc.FieldByName('nutricional_energia_kj').AsFloat);
        edtEnergiaKcal.Text := FormatFloat('#,##0.00', QrySrc.FieldByName('nutricional_energia_kcal').AsFloat);
        edtGrasas.Text := FormatFloat('#,##0.00', QrySrc.FieldByName('nutricional_grasas').AsFloat);
        edtGrasasSat.Text := FormatFloat('#,##0.00', QrySrc.FieldByName('nutricional_grasas_saturadas').AsFloat);
        edtHidratos.Text := FormatFloat('#,##0.00', QrySrc.FieldByName('nutricional_hidratos').AsFloat);
        edtAzucares.Text := FormatFloat('#,##0.00', QrySrc.FieldByName('nutricional_azucares').AsFloat);
        edtFibra.Text := FormatFloat('#,##0.00', QrySrc.FieldByName('nutricional_fibra').AsFloat);
        edtProteinas.Text := FormatFloat('#,##0.00', QrySrc.FieldByName('nutricional_proteinas').AsFloat);
        edtSal.Text := FormatFloat('#,##0.00', QrySrc.FieldByName('nutricional_sal').AsFloat);
        
        memAspecto.Text := QrySrc.FieldByName('organolepticas_aspecto').AsString;
        memOlor.Text := QrySrc.FieldByName('organolepticas_olor').AsString;
        memTextura.Text := QrySrc.FieldByName('organolepticas_textura').AsString;
        
        memInstruccionesUso.Text := QrySrc.FieldByName('instrucciones_uso').AsString;
        edtPublicoObjetivo.Text := QrySrc.FieldByName('publico_objetivo').AsString;
        memGruposVulnerables.Text := QrySrc.FieldByName('grupos_vulnerables').AsString;
        edtCantidadNeta.Text := QrySrc.FieldByName('cantidad_neta').AsString;
        memAlmacenamiento.Text := QrySrc.FieldByName('condiciones_almacenamiento').AsString;
        edtVidaUtil.Text := QrySrc.FieldByName('vida_util').AsString;
        memGestionLotes.Text := QrySrc.FieldByName('gestion_lotes').AsString;
        memTransporte.Text := QrySrc.FieldByName('condiciones_transporte').AsString;
        memCuerposExtranos.Text := QrySrc.FieldByName('mitigacion_cuerpos_extranos').AsString;
        memHistoricoCambios.Text := QrySrc.FieldByName('historico_cambios').AsString;
        
        // Copiar ingredientes de ge_articulos del artículo origen al destino
        dmgMain.qryExec.Close;
        dmgMain.qryExec.SQL.Text :=
          'UPDATE ge_articulos SET ingredientes = (SELECT ingredientes FROM ge_articulos WHERE id = :id_source) ' +
          'WHERE id = :id_target';
        dmgMain.qryExec.ParamByName('id_target').AsInteger := FArticuloId;
        dmgMain.qryExec.ParamByName('id_source').AsInteger := LSourceId;
        dmgMain.qryExec.ExecSQL;

        // Copiar alérgenos (ge_articulos_alergenos) del artículo origen al destino
        dmgMain.qryExec.Close;
        dmgMain.qryExec.SQL.Text := 'DELETE FROM ge_articulos_alergenos WHERE id_articulo = :id_target';
        dmgMain.qryExec.ParamByName('id_target').AsInteger := FArticuloId;
        dmgMain.qryExec.ExecSQL;

        dmgMain.qryExec.Close;
        dmgMain.qryExec.SQL.Text :=
          'INSERT INTO ge_articulos_alergenos (id_articulo, id_alergeno, presencia, calculado_receta, id_user_creator, id_user_update) ' +
          'SELECT :id_target, id_alergeno, presencia, calculado_receta, :user, :user ' +
          'FROM ge_articulos_alergenos ' +
          'WHERE id_articulo = :id_source';
        dmgMain.qryExec.ParamByName('id_target').AsInteger := FArticuloId;
        dmgMain.qryExec.ParamByName('id_source').AsInteger := LSourceId;
        dmgMain.qryExec.ParamByName('user').AsString := dmgMain.CurrentUserId;
        dmgMain.qryExec.ExecSQL;

        // Guardar automáticamente la ficha técnica en el artículo destino y recargar
        btnSaveClick(nil);
        LoadData;
        
        ShowMessage('Ficha técnica, ingredientes y alérgenos copiados y guardados correctamente.');
      finally
        QrySrc.Free;
      end;
    end;
  end;
  finally
    frmSelectProduct.OnlyWithFicha := False;
  end;
end;

procedure TfrmFichaTecnicaEditor.btnEliminarClick(Sender: TObject);
begin
  if FIsNewRecord then
  begin
    ShowMessage('No hay ninguna ficha técnica guardada para este artículo.');
    Exit;
  end;

  if MessageDlg('¿Está seguro de que desea eliminar por completo la ficha técnica de este artículo?', 
                 mtConfirmation, [mbYes, mbNo], 0) <> mrYes then
    Exit;

  dmgMain.qryExec.Close;
  dmgMain.qryExec.SQL.Text := 'DELETE FROM ge_articulos_fichas_tecnicas WHERE id_articulo = :id_articulo';
  dmgMain.qryExec.ParamByName('id_articulo').AsInteger := FArticuloId;

  try
    dmgMain.qryExec.ExecSQL;
    FIsNewRecord := True;
    LoadData; // Recarga la ficha vacía en la pantalla
    Self.UpdateActionButtonsState;
    ShowMessage('La ficha técnica ha sido eliminada con éxito.');
  except
    on E: Exception do
      TDbErrorHandler.HandleException(E, dmgMain.qryExec.SQL.Text);
  end;
end;

end.
