C2MV commited on
Commit
2b7c742
1 Parent(s): e2287bf

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +148 -102
app.py CHANGED
@@ -41,20 +41,24 @@ class RSM_BoxBehnken:
41
  """
42
  design = []
43
 
44
- # Generar todas las combinaciones de dos factores
45
- factor_indices = list(range(self.n_factors))
46
- for pair in itertools.combinations(factor_indices, 2):
47
- for levels in [(-1, -1), (-1, 1), (1, -1), (1, 1)]:
48
- run = [0] * self.n_factors
49
- run[pair[0]] = levels[0]
50
- run[pair[1]] = levels[1]
51
- design.append(run)
 
 
 
52
 
53
  # Añadir corridas centrales
54
  for _ in range(center_runs):
55
- design.append([0] * self.n_factors)
 
56
 
57
- design_df = pd.DataFrame(design, columns=self.factor_names)
58
  self.design = design_df
59
 
60
  # Mapear niveles codificados a naturales
@@ -178,7 +182,9 @@ class RSM_BoxBehnken:
178
 
179
  # Obtener las combinaciones de factores para gráficos
180
  for fixed_index, fixed_variable in enumerate(self.factor_names):
181
- for level in self.factor_levels[fixed_index]['levels']:
 
 
182
  fig = self.plot_rsm_individual(fixed_variable, level)
183
  if fig is not None:
184
  self.all_figures.append(fig)
@@ -231,7 +237,10 @@ class RSM_BoxBehnken:
231
 
232
  # Añadir puntos de los experimentos
233
  experiments = self.data.copy()
234
- experiments = experiments[(experiments[fixed_variable] == fixed_level)]
 
 
 
235
  fig.add_trace(go.Scatter3d(
236
  x=experiments[var1],
237
  y=experiments[var2],
@@ -267,6 +276,7 @@ class RSM_BoxBehnken:
267
  'Glucosa': 'g/L',
268
  'Extracto_de_Levadura': 'g/L',
269
  'Triptofano': 'g/L',
 
270
  'AIA_ppm': 'ppm',
271
  # Agrega más unidades según tus variables
272
  }
@@ -306,6 +316,28 @@ class RSM_BoxBehnken:
306
 
307
  return fig
308
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
309
  def generate_prediction_table(self):
310
  """
311
  Genera una tabla con los valores actuales, predichos y residuales.
@@ -420,10 +452,12 @@ class RSM_BoxBehnken:
420
  })
421
 
422
  # Calcular la suma de cuadrados y grados de libertad para la curvatura
423
- ss_curvature = anova_reduced['sum_sq'].get(f'I({self.factor_names[0]}**2)', 0) + \
424
- anova_reduced['sum_sq'].get(f'I({self.factor_names[1]}**2)', 0) + \
425
- anova_reduced['sum_sq'].get(f'I({self.factor_names[2]}**2)', 0)
426
- df_curvature = 3
 
 
427
 
428
  # Añadir la fila de curvatura a la tabla ANOVA
429
  detailed_anova_table.loc[len(detailed_anova_table)] = ['Curvatura', ss_curvature, df_curvature, ss_curvature / df_curvature, np.nan, np.nan]
@@ -566,14 +600,13 @@ class RSM_BoxBehnken:
566
 
567
  # --- Funciones para la Interfaz de Gradio ---
568
 
569
- def load_data(n_factors, factor_details, y_name, example=False):
570
  """
571
- Carga los datos del diseño Box-Behnken según el número de factores y sus detalles.
572
- Si example=True, carga un ejemplo predefinido.
573
  """
574
  try:
575
- if example:
576
- # Ejemplo para 3 factores
577
  if n_factors == 3:
578
  factor_names = ['Glucosa', 'Extracto_de_Levadura', 'Triptofano']
579
  factor_levels = [
@@ -581,6 +614,7 @@ def load_data(n_factors, factor_details, y_name, example=False):
581
  {'min': 0.03, 'max': 0.3, 'levels': [0.03, 0.2, 0.3]},
582
  {'min': 0.4, 'max': 0.9, 'levels': [0.4, 0.65, 0.9]}
583
  ]
 
584
  # Crear instancia
585
  rsm = RSM_BoxBehnken(factor_names, factor_levels, y_name)
586
  design = rsm.generate_box_behnken_design()
@@ -588,7 +622,6 @@ def load_data(n_factors, factor_details, y_name, example=False):
588
  response_values = [166.594, 177.557, 127.261, 147.573, 188.883, 224.527, 190.238, 226.483, 195.550, 149.493, 187.683, 148.621, 278.951, 297.238, 280.896]
589
  rsm.set_response(response_values)
590
  return rsm, design
591
- # Ejemplo para 4 factores
592
  elif n_factors == 4:
593
  factor_names = ['Glucosa', 'Extracto_de_Levadura', 'Triptofano', 'Tiempo']
594
  factor_levels = [
@@ -597,6 +630,7 @@ def load_data(n_factors, factor_details, y_name, example=False):
597
  {'min': 0.4, 'max': 0.9, 'levels': [0.4, 0.65, 0.9]},
598
  {'min': 24, 'max': 72, 'levels': [24, 48, 72]}
599
  ]
 
600
  # Crear instancia
601
  rsm = RSM_BoxBehnken(factor_names, factor_levels, y_name)
602
  design = rsm.generate_box_behnken_design()
@@ -608,14 +642,20 @@ def load_data(n_factors, factor_details, y_name, example=False):
608
  raise ValueError("Ejemplos solo disponibles para 3 y 4 factores.")
609
  else:
610
  # Cargar según la entrada del usuario
611
- factor_names = [detail['name'] for detail in factor_details]
612
- factor_levels = [{'min': detail['min'], 'max': detail['max'], 'levels': [detail['min'], (detail['min'] + detail['max']) / 2, detail['max']]} for detail in factor_details]
 
 
 
 
 
 
613
  # Crear instancia
614
  rsm = RSM_BoxBehnken(factor_names, factor_levels, y_name)
615
  design = rsm.generate_box_behnken_design()
616
  return rsm, design
617
  except Exception as e:
618
- print(f"Error al cargar los datos: {str(e)}")
619
  return None, None
620
 
621
  def fit_and_optimize(rsm, response_values):
@@ -636,7 +676,7 @@ def fit_and_optimize(rsm, response_values):
636
  rsm.generate_all_plots()
637
 
638
  # Formatear la ecuación para que se vea mejor en Markdown
639
- equation_formatted = equation.replace(" + ", "<br>+ ").replace(" ** 2", "^2").replace("*", " × ")
640
  equation_formatted = f"### Ecuación del Modelo Simplificado:<br>{equation_formatted}"
641
 
642
  # Guardar las tablas en Excel temporal
@@ -709,7 +749,7 @@ def create_gradio_interface():
709
  factor_inputs = []
710
  for i in range(6):
711
  with gr.Row():
712
- factor_name = gr.Textbox(label=f"Factor {i+1} Nombre", placeholder=f"Nombre del Factor {i+1}")
713
  factor_min = gr.Number(label=f"Factor {i+1} Min", value=0.0)
714
  factor_max = gr.Number(label=f"Factor {i+1} Max", value=1.0)
715
  factor_inputs.append({'name': factor_name, 'min': factor_min, 'max': factor_max})
@@ -724,7 +764,7 @@ def create_gradio_interface():
724
  gr.Markdown("### Diseño Box-Behnken")
725
  design_output = gr.Dataframe(
726
  headers=None,
727
- label="Diseño Generado (Completa y Rellena la Columna de Respuestas)",
728
  interactive=True
729
  )
730
  submit_response_button = gr.Button("✅ Enviar Respuestas")
@@ -769,81 +809,45 @@ def create_gradio_interface():
769
  with gr.Row():
770
  download_plot_button = gr.DownloadButton("💾 Descargar Gráfico Actual (PNG)")
771
  download_all_plots_button = gr.DownloadButton("💾 Descargar Todos los Gráficos (ZIP)")
772
-
773
- with gr.Row():
774
- copiar_btn = gr.Button("📋 Copiar Informe", variant="secondary")
775
- exportar_word_btn = gr.Button("💾 Exportar Informe Word", variant="primary")
776
- exportar_excel_btn = gr.Button("💾 Exportar Informe Excel", variant="primary")
777
-
778
  # --- Funciones de la Interfaz ---
779
 
780
- def handle_load_design(n_factors, factor_details, y_name, load_example):
781
  """
782
- Genera el diseño Box-Behnken según la configuración o carga un ejemplo.
783
  """
784
- if load_example:
785
- # Cargar ejemplos predefinidos
786
- if n_factors == 3:
787
- factor_names = ['Glucosa', 'Extracto_de_Levadura', 'Triptofano']
788
- factor_levels = [
789
- {'min': 1.0, 'max': 5.5, 'levels': [1.0, 3.5, 5.5]},
790
- {'min': 0.03, 'max': 0.3, 'levels': [0.03, 0.2, 0.3]},
791
- {'min': 0.4, 'max': 0.9, 'levels': [0.4, 0.65, 0.9]}
792
- ]
793
- y_name = 'AIA_ppm'
794
- # Crear instancia
795
- rsm = RSM_BoxBehnken(factor_names, factor_levels, y_name)
796
- design = rsm.generate_box_behnken_design()
797
- # Ejemplo de valores de respuesta
798
- response_values = [166.594, 177.557, 127.261, 147.573, 188.883, 224.527, 190.238, 226.483, 195.550, 149.493, 187.683, 148.621, 278.951, 297.238, 280.896]
799
- rsm.set_response(response_values)
800
- return rsm, design
801
- elif n_factors == 4:
802
- factor_names = ['Glucosa', 'Extracto_de_Levadura', 'Triptofano', 'Tiempo']
803
- factor_levels = [
804
- {'min': 1.0, 'max': 5.5, 'levels': [1.0, 3.5, 5.5]},
805
- {'min': 0.03, 'max': 0.3, 'levels': [0.03, 0.2, 0.3]},
806
- {'min': 0.4, 'max': 0.9, 'levels': [0.4, 0.65, 0.9]},
807
- {'min': 24, 'max': 72, 'levels': [24, 48, 72]}
808
- ]
809
- y_name = 'AIA_ppm'
810
- # Crear instancia
811
- rsm = RSM_BoxBehnken(factor_names, factor_levels, y_name)
812
- design = rsm.generate_box_behnken_design()
813
- # Ejemplo de valores de respuesta (30 corridas para 4 factores)
814
- response_values = [200 + np.random.normal(0, 10) for _ in range(len(design))]
815
- rsm.set_response(response_values)
816
- return rsm, design
817
- else:
818
- raise ValueError("Ejemplos solo disponibles para 3 y 4 factores.")
819
- else:
820
- # Cargar según la entrada del usuario
821
- factor_names = [detail['name'] for detail in factor_details]
822
- factor_levels = [{'min': detail['min'], 'max': detail['max'], 'levels': [detail['min'], (detail['min'] + detail['max']) / 2, detail['max']]} for detail in factor_details]
823
- # Crear instancia
824
- rsm = RSM_BoxBehnken(factor_names, factor_levels, y_name)
825
- design = rsm.generate_box_behnken_design()
826
- return rsm, design
827
-
828
- def prepare_download_files(excel_path, zip_path):
829
  """
830
- Prepara los archivos para descarga.
831
  """
832
- return excel_path, zip_path
 
 
 
 
 
 
833
 
834
  # Cargar Diseño
835
  load_button.click(
836
- fn=handle_load_design,
837
- inputs=[gr.Slider,
838
- [gr.Row().components for gr.Row in gr.Blocks().__class__],
839
- y_name_input,
840
- load_example_checkbox],
841
  outputs=[gr.State(), design_output]
842
  )
843
-
844
- # Enviar Respuestas
845
  submit_response_button.click(
846
- fn=fit_and_optimize,
847
  inputs=[gr.State(), design_output],
848
  outputs=[
849
  model_completo_output,
@@ -857,31 +861,73 @@ def create_gradio_interface():
857
  anova_table_output,
858
  download_all_plots_button,
859
  download_excel_button,
860
- tables_dict_output := gr.State()
861
  ]
862
  )
863
 
864
- # Descargar Tablas en Excel
865
- download_excel_button.click(
866
- fn=lambda excel_path: excel_path,
867
- inputs=[download_excel_button],
868
- outputs=[download_excel_button]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
869
  )
870
 
871
  # Descargar Todos los Gráficos en ZIP
872
  download_all_plots_button.click(
873
- fn=lambda zip_path: zip_path,
874
- inputs=[download_all_plots_button],
875
- outputs=[download_all_plots_button]
 
 
 
 
 
 
 
876
  )
877
 
878
  # Descargar Tablas en Word
879
  download_word_button.click(
880
- fn=lambda rsm_instance, tables_dict: export_word(rsm_instance, tables_dict),
881
  inputs=[gr.State(), gr.State()],
882
  outputs=[download_word_button]
883
  )
884
-
885
  return demo
886
 
887
  # --- Función Principal ---
 
41
  """
42
  design = []
43
 
44
+ # Generar todas las combinaciones de tres niveles para cada par de factores
45
+ for combination in itertools.combinations(self.factor_names, 2):
46
+ var1, var2 = combination
47
+ for level1 in [-1, 0, 1]:
48
+ for level2 in [-1, 0, 1]:
49
+ # Solo agregar puntos que formen el diseño Box-Behnken
50
+ if abs(level1) == 1 and abs(level2) == 1:
51
+ run = {var: 0 for var in self.factor_names}
52
+ run[var1] = level1
53
+ run[var2] = level2
54
+ design.append(run)
55
 
56
  # Añadir corridas centrales
57
  for _ in range(center_runs):
58
+ run = {var: 0 for var in self.factor_names}
59
+ design.append(run)
60
 
61
+ design_df = pd.DataFrame(design)
62
  self.design = design_df
63
 
64
  # Mapear niveles codificados a naturales
 
182
 
183
  # Obtener las combinaciones de factores para gráficos
184
  for fixed_index, fixed_variable in enumerate(self.factor_names):
185
+ # Usar los niveles originales para fijar la variable
186
+ fixed_levels = self.factor_levels[fixed_index]['levels']
187
+ for level in fixed_levels:
188
  fig = self.plot_rsm_individual(fixed_variable, level)
189
  if fig is not None:
190
  self.all_figures.append(fig)
 
237
 
238
  # Añadir puntos de los experimentos
239
  experiments = self.data.copy()
240
+ # Convertir el nivel fijo a codificado para filtrar
241
+ fixed_level_coded = self.natural_to_coded(fixed_level, self.factor_names.index(fixed_variable))
242
+ # Filtrar experimentos donde la variable fija está cerca del nivel seleccionado (tolerancia pequeña)
243
+ experiments = experiments[np.isclose(experiments[fixed_variable], fixed_level, atol=1e-3)]
244
  fig.add_trace(go.Scatter3d(
245
  x=experiments[var1],
246
  y=experiments[var2],
 
276
  'Glucosa': 'g/L',
277
  'Extracto_de_Levadura': 'g/L',
278
  'Triptofano': 'g/L',
279
+ 'Tiempo': 'Horas',
280
  'AIA_ppm': 'ppm',
281
  # Agrega más unidades según tus variables
282
  }
 
316
 
317
  return fig
318
 
319
+ def get_simplified_equation(self):
320
+ """
321
+ Retorna la ecuación del modelo simplificado como una cadena de texto.
322
+ """
323
+ if self.model_simplified is None:
324
+ print("Error: Ajusta el modelo simplificado primero.")
325
+ return None
326
+
327
+ coefficients = self.model_simplified.params
328
+ equation = f"{self.y_name} = {coefficients['Intercept']:.3f}"
329
+
330
+ for term, coef in coefficients.items():
331
+ if term != 'Intercept':
332
+ if term in self.factor_names:
333
+ equation += f" + {coef:.3f}*{term}"
334
+ elif term.startswith("I("):
335
+ equation += f" + {coef:.3f}*{term[2:-1]}"
336
+ else:
337
+ equation += f" + {coef:.3f}*{term.replace(':', '×')}"
338
+
339
+ return equation
340
+
341
  def generate_prediction_table(self):
342
  """
343
  Genera una tabla con los valores actuales, predichos y residuales.
 
452
  })
453
 
454
  # Calcular la suma de cuadrados y grados de libertad para la curvatura
455
+ ss_curvature = 0
456
+ for var in self.factor_names:
457
+ curvature_term = f"I({var}**2)"
458
+ if curvature_term in anova_reduced.index:
459
+ ss_curvature += anova_reduced.loc[curvature_term, 'sum_sq']
460
+ df_curvature = self.n_factors
461
 
462
  # Añadir la fila de curvatura a la tabla ANOVA
463
  detailed_anova_table.loc[len(detailed_anova_table)] = ['Curvatura', ss_curvature, df_curvature, ss_curvature / df_curvature, np.nan, np.nan]
 
600
 
601
  # --- Funciones para la Interfaz de Gradio ---
602
 
603
+ def handle_load_design(n_factors, factor_inputs, y_name, load_example):
604
  """
605
+ Genera el diseño Box-Behnken según la configuración o carga un ejemplo.
 
606
  """
607
  try:
608
+ if load_example:
609
+ # Cargar ejemplos predefinidos
610
  if n_factors == 3:
611
  factor_names = ['Glucosa', 'Extracto_de_Levadura', 'Triptofano']
612
  factor_levels = [
 
614
  {'min': 0.03, 'max': 0.3, 'levels': [0.03, 0.2, 0.3]},
615
  {'min': 0.4, 'max': 0.9, 'levels': [0.4, 0.65, 0.9]}
616
  ]
617
+ y_name = 'AIA_ppm'
618
  # Crear instancia
619
  rsm = RSM_BoxBehnken(factor_names, factor_levels, y_name)
620
  design = rsm.generate_box_behnken_design()
 
622
  response_values = [166.594, 177.557, 127.261, 147.573, 188.883, 224.527, 190.238, 226.483, 195.550, 149.493, 187.683, 148.621, 278.951, 297.238, 280.896]
623
  rsm.set_response(response_values)
624
  return rsm, design
 
625
  elif n_factors == 4:
626
  factor_names = ['Glucosa', 'Extracto_de_Levadura', 'Triptofano', 'Tiempo']
627
  factor_levels = [
 
630
  {'min': 0.4, 'max': 0.9, 'levels': [0.4, 0.65, 0.9]},
631
  {'min': 24, 'max': 72, 'levels': [24, 48, 72]}
632
  ]
633
+ y_name = 'AIA_ppm'
634
  # Crear instancia
635
  rsm = RSM_BoxBehnken(factor_names, factor_levels, y_name)
636
  design = rsm.generate_box_behnken_design()
 
642
  raise ValueError("Ejemplos solo disponibles para 3 y 4 factores.")
643
  else:
644
  # Cargar según la entrada del usuario
645
+ factor_names = []
646
+ factor_levels = []
647
+ for i in range(n_factors):
648
+ name = factor_inputs[i]['name']
649
+ min_val = factor_inputs[i]['min']
650
+ max_val = factor_inputs[i]['max']
651
+ factor_names.append(name)
652
+ factor_levels.append({'min': min_val, 'max': max_val, 'levels': [min_val, (min_val + max_val) / 2, max_val]})
653
  # Crear instancia
654
  rsm = RSM_BoxBehnken(factor_names, factor_levels, y_name)
655
  design = rsm.generate_box_behnken_design()
656
  return rsm, design
657
  except Exception as e:
658
+ print(f"Error al cargar el diseño: {str(e)}")
659
  return None, None
660
 
661
  def fit_and_optimize(rsm, response_values):
 
676
  rsm.generate_all_plots()
677
 
678
  # Formatear la ecuación para que se vea mejor en Markdown
679
+ equation_formatted = equation.replace(" + ", "<br>+ ").replace("** 2", "^2").replace("*", " × ")
680
  equation_formatted = f"### Ecuación del Modelo Simplificado:<br>{equation_formatted}"
681
 
682
  # Guardar las tablas en Excel temporal
 
749
  factor_inputs = []
750
  for i in range(6):
751
  with gr.Row():
752
+ factor_name = gr.Textbox(label=f"Factor {i+1} Nombre", placeholder=f"Nombre del Factor {i+1}", value=f"Factor_{i+1}")
753
  factor_min = gr.Number(label=f"Factor {i+1} Min", value=0.0)
754
  factor_max = gr.Number(label=f"Factor {i+1} Max", value=1.0)
755
  factor_inputs.append({'name': factor_name, 'min': factor_min, 'max': factor_max})
 
764
  gr.Markdown("### Diseño Box-Behnken")
765
  design_output = gr.Dataframe(
766
  headers=None,
767
+ label="Diseño Generado (Completa la Columna de Respuestas)",
768
  interactive=True
769
  )
770
  submit_response_button = gr.Button("✅ Enviar Respuestas")
 
809
  with gr.Row():
810
  download_plot_button = gr.DownloadButton("💾 Descargar Gráfico Actual (PNG)")
811
  download_all_plots_button = gr.DownloadButton("💾 Descargar Todos los Gráficos (ZIP)")
812
+
 
 
 
 
 
813
  # --- Funciones de la Interfaz ---
814
 
815
+ def handle_load_design_wrapper(n_factors, factor_inputs, y_name, load_example):
816
  """
817
+ Wrapper para manejar la carga del diseño Box-Behnken.
818
  """
819
+ # Extraer los valores de los inputs
820
+ factor_details = []
821
+ for i in range(6):
822
+ name = factor_inputs[i]['name'].value
823
+ min_val = factor_inputs[i]['min'].value
824
+ max_val = factor_inputs[i]['max'].value
825
+ factor_details.append({'name': name, 'min': min_val, 'max': max_val})
826
+ # Generar el diseño
827
+ return handle_load_design(n_factors, factor_details, y_name, load_example)
828
+
829
+ def handle_submit_response(rsm, design_df):
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
830
  """
831
+ Obtiene las respuestas ingresadas por el usuario y realiza el análisis.
832
  """
833
+ try:
834
+ # Obtener las respuestas de la última columna (AIA_ppm)
835
+ response_values = design_df[rsm.y_name].tolist()
836
+ return fit_and_optimize(rsm, response_values)
837
+ except Exception as e:
838
+ print(f"Error al procesar las respuestas: {str(e)}")
839
+ return [None]*12
840
 
841
  # Cargar Diseño
842
  load_button.click(
843
+ fn=handle_load_design_wrapper,
844
+ inputs=[n_factors_input, factor_inputs, y_name_input, load_example_checkbox],
 
 
 
845
  outputs=[gr.State(), design_output]
846
  )
847
+
848
+ # Enviar Respuestas y Realizar Análisis
849
  submit_response_button.click(
850
+ fn=handle_submit_response,
851
  inputs=[gr.State(), design_output],
852
  outputs=[
853
  model_completo_output,
 
861
  anova_table_output,
862
  download_all_plots_button,
863
  download_excel_button,
864
+ gr.State() # Para tables_dict
865
  ]
866
  )
867
 
868
+ # Navegación de Gráficos (Simplificada)
869
+ current_plot_state = gr.State(0)
870
+
871
+ def get_current_plot(rsm, current_index):
872
+ if not rsm.all_figures:
873
+ return None, "No hay gráficos disponibles.", current_index
874
+ selected_fig = rsm.all_figures[current_index]
875
+ plot_info_text = f"Gráfico {current_index + 1} de {len(rsm.all_figures)}"
876
+ return selected_fig, plot_info_text, current_index
877
+
878
+ def navigate_plot(direction, current_index, rsm):
879
+ if not rsm.all_figures:
880
+ return None, "No hay gráficos disponibles.", current_index
881
+ if direction == 'left':
882
+ new_index = (current_index - 1) % len(rsm.all_figures)
883
+ elif direction == 'right':
884
+ new_index = (current_index + 1) % len(rsm.all_figures)
885
+ else:
886
+ new_index = current_index
887
+ selected_fig = rsm.all_figures[new_index]
888
+ plot_info_text = f"Gráfico {new_index + 1} de {len(rsm.all_figures)}"
889
+ return selected_fig, plot_info_text, new_index
890
+
891
+ # Navegación de Gráficos
892
+ left_button.click(
893
+ fn=navigate_plot,
894
+ inputs=[gr.ButtonValue(left_button), current_plot_state, gr.State()],
895
+ outputs=[rsm_plot_output, plot_info, current_plot_state]
896
+ )
897
+ right_button.click(
898
+ fn=navigate_plot,
899
+ inputs=[gr.ButtonValue(right_button), current_plot_state, gr.State()],
900
+ outputs=[rsm_plot_output, plot_info, current_plot_state]
901
+ )
902
+
903
+ # Descargar Gráfico Actual
904
+ download_plot_button.click(
905
+ fn=lambda rsm, current_index: rsm.save_fig_to_bytes(rsm.all_figures[current_index]) if rsm and rsm.all_figures else None,
906
+ inputs=[gr.State(), current_plot_state],
907
+ outputs=download_plot_button
908
  )
909
 
910
  # Descargar Todos los Gráficos en ZIP
911
  download_all_plots_button.click(
912
+ fn=lambda rsm: rsm.save_figures_to_zip() if rsm else None,
913
+ inputs=[gr.State()],
914
+ outputs=download_all_plots_button
915
+ )
916
+
917
+ # Descargar Tablas en Excel
918
+ download_excel_button.click(
919
+ fn=lambda excel_path: (excel_path, None) if excel_path else (None, None),
920
+ inputs=[gr.State()],
921
+ outputs=[download_excel_button, None]
922
  )
923
 
924
  # Descargar Tablas en Word
925
  download_word_button.click(
926
+ fn=lambda rsm_instance, tables_dict: export_word(rsm_instance, tables_dict) if rsm_instance and tables_dict else None,
927
  inputs=[gr.State(), gr.State()],
928
  outputs=[download_word_button]
929
  )
930
+
931
  return demo
932
 
933
  # --- Función Principal ---