C2MV commited on
Commit
dfc0706
1 Parent(s): b7401b3

Update app.py

Browse files
Files changed (1) hide show
  1. app.py +636 -60
app.py CHANGED
@@ -192,7 +192,7 @@ def generar_graficos(df_valid, n_replicas, unidad_medida, palette_puntos, estilo
192
  ax2.legend(loc='upper right', fontsize=10)
193
 
194
  plt.tight_layout()
195
- # plt.savefig('grafico.png') # Si deseas guardar el gráfico
196
  return fig
197
 
198
  def evaluar_calidad_calibracion(df_valid, r_squared, rmse, cv_percent):
@@ -315,82 +315,658 @@ def actualizar_analisis(df, n_replicas, unidad_medida, filas_seleccionadas):
315
 
316
  return estado, fig, informe, df
317
 
318
- # Definir la interfaz de Gradio
319
- import gradio as gr
 
 
 
 
 
 
 
320
 
321
- with gr.Blocks() as interfaz:
322
- gr.Markdown("# Generador de Tabla de Diluciones y Análisis de Calibración")
323
 
324
- with gr.Tab("Generar Tabla"):
325
- gr.Markdown("## Parámetros de la Tabla")
326
- with gr.Row():
327
- n_filas = gr.Number(label="Número de filas", value=7, precision=0)
328
- concentracion_inicial = gr.Number(label="Concentración Inicial", value=100)
329
- unidad_medida = gr.Textbox(label="Unidad de Medida", value="mg/L")
330
- n_replicas = gr.Number(label="Número de réplicas", value=3, precision=0)
331
- btn_generar_tabla = gr.Button("Generar Tabla")
332
- tabla = gr.DataFrame(label="Tabla Generada", editable=True)
333
-
334
- btn_generar_tabla.click(
335
- generar_tabla,
336
- inputs=[n_filas, concentracion_inicial, unidad_medida, n_replicas],
337
- outputs=tabla
338
- )
339
 
340
- with gr.Tab("Ajustar Decimales"):
341
- gr.Markdown("## Ajustar Decimales")
342
- decimales = gr.Number(label="Número de decimales", value=2, precision=0)
343
- btn_ajustar_decimales = gr.Button("Ajustar Decimales")
344
- tabla_ajustada = gr.DataFrame(label="Tabla Ajustada", editable=True)
345
 
346
- btn_ajustar_decimales.click(
347
- ajustar_decimales_evento,
348
- inputs=[tabla, decimales],
349
- outputs=tabla_ajustada
350
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
351
 
352
- with gr.Tab("Ingresar Concentraciones Reales"):
353
- gr.Markdown("## Ingresar Concentraciones Reales")
354
- gr.Markdown("Ingrese los valores de concentración real en las columnas correspondientes de la tabla.")
355
- tabla_input = gr.DataFrame(label="Tabla con Concentraciones Reales", editable=True)
356
 
357
- # Sincronizar tabla_ajustada con tabla_input
358
- tabla_ajustada.change(
359
- lambda df: df,
360
- inputs=tabla_ajustada,
361
- outputs=tabla_input
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
362
  )
363
 
364
- with gr.Tab("Análisis"):
365
- gr.Markdown("## Seleccionar Filas para Análisis")
 
 
 
366
  filas_seleccionadas = gr.CheckboxGroup(
367
- label="Seleccione las filas a incluir en el análisis"
 
 
368
  )
369
- btn_actualizar_filas = gr.Button("Actualizar Opciones de Filas")
370
 
371
- # Actualizar las opciones de filas basadas en el número de filas de la tabla
372
- def actualizar_opciones_filas(df):
373
- opciones = [f"Fila {i+1}" for i in range(len(df))]
374
- return gr.CheckboxGroup.update(choices=opciones)
375
 
376
- btn_actualizar_filas.click(
377
- actualizar_opciones_filas,
378
- inputs=tabla_input,
379
- outputs=filas_seleccionadas
380
- )
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
381
 
382
- btn_analizar = gr.Button("Realizar Análisis")
 
383
 
384
- estado = gr.Textbox(label="Estado del Análisis")
385
- grafico = gr.Plot(label="Gráfico")
386
- informe = gr.Markdown(label="Informe")
387
 
388
- btn_analizar.click(
389
- actualizar_analisis,
390
- inputs=[tabla_input, n_replicas, unidad_medida, filas_seleccionadas],
391
- outputs=[estado, grafico, informe, tabla_input]
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
392
  )
393
 
 
 
 
 
 
 
 
 
 
 
 
 
394
  # Lanzar la interfaz
395
  if __name__ == "__main__":
396
  interfaz.launch()
 
192
  ax2.legend(loc='upper right', fontsize=10)
193
 
194
  plt.tight_layout()
195
+ plt.savefig('grafico.png') # Guardar el gráfico para incluirlo en el informe
196
  return fig
197
 
198
  def evaluar_calidad_calibracion(df_valid, r_squared, rmse, cv_percent):
 
315
 
316
  return estado, fig, informe, df
317
 
318
+ def actualizar_graficos(df, n_replicas, unidad_medida,
319
+ palette_puntos, estilo_puntos,
320
+ palette_linea_ajuste, estilo_linea_ajuste,
321
+ palette_linea_ideal, estilo_linea_ideal,
322
+ palette_barras_error,
323
+ mostrar_linea_ajuste, mostrar_linea_ideal, mostrar_puntos,
324
+ filas_seleccionadas):
325
+ if df is None or df.empty:
326
+ return None
327
 
328
+ # Asegurarse de que los cálculos estén actualizados
329
+ df = calcular_promedio_desviacion(df, n_replicas, unidad_medida)
330
 
331
+ col_predicha_num = "Concentración Predicha Numérica"
332
+ col_real_promedio = f"Concentración Real Promedio ({unidad_medida})"
 
 
 
 
 
 
 
 
 
 
 
 
 
333
 
334
+ # Convertir columnas a numérico
335
+ df[col_predicha_num] = pd.to_numeric(df[col_predicha_num], errors='coerce')
336
+ df[col_real_promedio] = pd.to_numeric(df[col_real_promedio], errors='coerce')
 
 
337
 
338
+ df_valid = df.dropna(subset=[col_predicha_num, col_real_promedio])
339
+
340
+ # Resetear el índice para asegurar que sea secuencial
341
+ df_valid.reset_index(drop=True, inplace=True)
342
+
343
+ # Convertir filas_seleccionadas a índices
344
+ if not filas_seleccionadas:
345
+ return None
346
+
347
+ indices_seleccionados = [int(s.split(' ')[1]) - 1 for s in filas_seleccionadas]
348
+
349
+ # Filtrar filas según las seleccionadas
350
+ df_valid = df_valid.loc[indices_seleccionados]
351
+
352
+ if len(df_valid) < 2:
353
+ return None
354
+
355
+ # Generar gráfico con opciones seleccionadas
356
+ fig = generar_graficos(
357
+ df_valid, n_replicas, unidad_medida,
358
+ palette_puntos, estilo_puntos,
359
+ palette_linea_ajuste, estilo_linea_ajuste,
360
+ palette_linea_ideal, estilo_linea_ideal,
361
+ palette_barras_error,
362
+ mostrar_linea_ajuste, mostrar_linea_ideal, mostrar_puntos
363
+ )
364
+
365
+ return fig
366
+
367
+ def exportar_informe_word(df_valid, informe_md, unidad_medida):
368
+ # Crear documento Word
369
+ doc = docx.Document()
370
+
371
+ # Estilos APA 7
372
+ style = doc.styles['Normal']
373
+ font = style.font
374
+ font.name = 'Times New Roman'
375
+ font.size = Pt(12)
376
+
377
+ # Título centrado
378
+ titulo = doc.add_heading('Informe de Calibración', 0)
379
+ titulo.alignment = WD_PARAGRAPH_ALIGNMENT.CENTER
380
+
381
+ # Fecha
382
+ fecha = doc.add_paragraph(f"Fecha: {datetime.now().strftime('%d/%m/%Y %H:%M')}")
383
+ fecha.alignment = WD_PARAGRAPH_ALIGNMENT.CENTER
384
+
385
+ # Insertar gráfico
386
+ if os.path.exists('grafico.png'):
387
+ doc.add_picture('grafico.png', width=Inches(6))
388
+ ultimo_parrafo = doc.paragraphs[-1]
389
+ ultimo_parrafo.alignment = WD_PARAGRAPH_ALIGNMENT.CENTER
390
+
391
+ # Leyenda del gráfico en estilo APA 7
392
+ leyenda = doc.add_paragraph('Figura 1. Gráfico de calibración.')
393
+ leyenda_format = leyenda.paragraph_format
394
+ leyenda_format.alignment = WD_PARAGRAPH_ALIGNMENT.CENTER
395
+ leyenda.style = doc.styles['Caption']
396
+
397
+ # Agregar contenido del informe
398
+ doc.add_heading('Resumen Estadístico', level=1)
399
+ for linea in informe_md.split('\n'):
400
+ if linea.startswith('##'):
401
+ doc.add_heading(linea.replace('##', '').strip(), level=2)
402
+ else:
403
+ doc.add_paragraph(linea)
404
+
405
+ # Añadir tabla de datos
406
+ doc.add_heading('Tabla de Datos de Calibración', level=1)
407
+
408
+ # Convertir DataFrame a lista de listas
409
+ tabla_datos = df_valid.reset_index(drop=True)
410
+ tabla_datos = tabla_datos.round(4) # Redondear a 4 decimales si es necesario
411
+ columnas = tabla_datos.columns.tolist()
412
+ registros = tabla_datos.values.tolist()
413
+
414
+ # Crear tabla en Word
415
+ tabla = doc.add_table(rows=1 + len(registros), cols=len(columnas))
416
+ tabla.style = 'Table Grid'
417
+
418
+ # Añadir los encabezados
419
+ hdr_cells = tabla.rows[0].cells
420
+ for idx, col_name in enumerate(columnas):
421
+ hdr_cells[idx].text = col_name
422
+
423
+ # Añadir los registros
424
+ for i, registro in enumerate(registros):
425
+ row_cells = tabla.rows[i + 1].cells
426
+ for j, valor in enumerate(registro):
427
+ row_cells[j].text = str(valor)
428
+
429
+ # Formatear fuente de la tabla
430
+ for row in tabla.rows:
431
+ for cell in row.cells:
432
+ for paragraph in cell.paragraphs:
433
+ paragraph.style = doc.styles['Normal']
434
+
435
+ # Guardar documento
436
+ filename = 'informe_calibracion.docx'
437
+ doc.save(filename)
438
+ return filename
439
+
440
+ def exportar_informe_latex(df_valid, informe_md):
441
+ # Generar código LaTeX
442
+ informe_tex = r"""\documentclass{article}
443
+ \usepackage[spanish]{babel}
444
+ \usepackage{amsmath}
445
+ \usepackage{graphicx}
446
+ \usepackage{booktabs}
447
+ \begin{document}
448
+ """
449
+ informe_tex += informe_md.replace('#', '').replace('**', '\\textbf{').replace('*', '\\textit{')
450
+ informe_tex += r"""
451
+ \end{document}
452
+ """
453
+ filename = 'informe_calibracion.tex'
454
+ with open(filename, 'w') as f:
455
+ f.write(informe_tex)
456
+ return filename
457
+
458
+ def exportar_word(df, informe_md, unidad_medida, filas_seleccionadas):
459
+ df_valid = df.copy()
460
+ col_predicha_num = "Concentración Predicha Numérica"
461
+ col_real_promedio = f"Concentración Real Promedio ({unidad_medida})"
462
+
463
+ # Convertir columnas a numérico
464
+ df_valid[col_predicha_num] = pd.to_numeric(df_valid[col_predicha_num], errors='coerce')
465
+ df_valid[col_real_promedio] = pd.to_numeric(df_valid[col_real_promedio], errors='coerce')
466
+
467
+ df_valid = df_valid.dropna(subset=[col_predicha_num, col_real_promedio])
468
+
469
+ # Resetear el índice
470
+ df_valid.reset_index(drop=True, inplace=True)
471
+
472
+ # Convertir filas_seleccionadas a índices
473
+ if not filas_seleccionadas:
474
+ return None
475
+
476
+ indices_seleccionados = [int(s.split(' ')[1]) - 1 for s in filas_seleccionadas]
477
+
478
+ # Filtrar filas según las seleccionadas
479
+ df_valid = df_valid.loc[indices_seleccionados]
480
+
481
+ if df_valid.empty:
482
+ return None
483
+
484
+ filename = exportar_informe_word(df_valid, informe_md, unidad_medida)
485
+
486
+ return filename # Retornamos el nombre del archivo
487
+
488
+ def exportar_latex(df, informe_md, filas_seleccionadas):
489
+ df_valid = df.copy()
490
+ col_predicha_num = "Concentración Predicha Numérica"
491
+ col_real_promedio = [col for col in df_valid.columns if 'Real Promedio' in col][0]
492
+
493
+ # Convertir columnas a numérico
494
+ df_valid[col_predicha_num] = pd.to_numeric(df_valid[col_predicha_num], errors='coerce')
495
+ df_valid[col_real_promedio] = pd.to_numeric(df_valid[col_real_promedio], errors='coerce')
496
+
497
+ df_valid = df_valid.dropna(subset=[col_predicha_num, col_real_promedio])
498
+
499
+ # Resetear el índice
500
+ df_valid.reset_index(drop=True, inplace=True)
501
+
502
+ # Convertir filas_seleccionadas a índices
503
+ if not filas_seleccionadas:
504
+ return None
505
+
506
+ indices_seleccionados = [int(s.split(' ')[1]) - 1 for s in filas_seleccionadas]
507
+
508
+ # Filtrar filas según las seleccionadas
509
+ df_valid = df_valid.loc[indices_seleccionados]
510
+
511
+ if df_valid.empty:
512
+ return None
513
+
514
+ filename = exportar_informe_latex(df_valid, informe_md)
515
+
516
+ return filename # Retornamos el nombre del archivo
517
+
518
+ def cargar_ejemplo_ufc(n_replicas):
519
+ df = generar_tabla(7, 2000000, "UFC", n_replicas)
520
+ # Valores reales de ejemplo
521
+ for i in range(1, n_replicas + 1):
522
+ valores_reales = [2000000 - (i - 1) * 10000, 1600000 - (i - 1) * 8000, 1200000 - (i - 1) * 6000,
523
+ 800000 - (i - 1) * 4000, 400000 - (i - 1) * 2000, 200000 - (i - 1) * 1000,
524
+ 100000 - (i - 1) * 500]
525
+ df[f"Concentración Real {i} (UFC)"] = valores_reales
526
+ return 2000000, "UFC", 7, df
527
+
528
+ def cargar_ejemplo_od(n_replicas):
529
+ df = generar_tabla(7, 1.0, "OD", n_replicas)
530
+ # Valores reales de ejemplo
531
+ for i in range(1, n_replicas + 1):
532
+ valores_reales = [1.00 - (i - 1) * 0.05, 0.80 - (i - 1) * 0.04, 0.60 - (i - 1) * 0.03,
533
+ 0.40 - (i - 1) * 0.02, 0.20 - (i - 1) * 0.01, 0.10 - (i - 1) * 0.005,
534
+ 0.05 - (i - 1) * 0.002]
535
+ df[f"Concentración Real {i} (OD)"] = valores_reales
536
+ return 1.0, "OD", 7, df
537
+
538
+ def limpiar_datos(n_replicas):
539
+ df = generar_tabla(7, 2000000, "UFC", n_replicas)
540
+ return (
541
+ 2000000, # Concentración Inicial
542
+ "UFC", # Unidad de Medida
543
+ 7, # Número de filas
544
+ df, # Tabla Output
545
+ "", # Estado Output
546
+ None, # Gráficos Output
547
+ "" # Informe Output
548
+ )
549
+
550
+ def generar_datos_sinteticos_evento(df, n_replicas, unidad_medida):
551
+ df = df.copy()
552
+ col_predicha_num = "Concentración Predicha Numérica"
553
+
554
+ # Generar datos sintéticos para cada réplica
555
+ for i in range(1, n_replicas + 1):
556
+ col_real = f"Concentración Real {i} ({unidad_medida})"
557
+ df[col_predicha_num] = pd.to_numeric(df[col_predicha_num], errors='coerce')
558
+ desviacion_std = 0.05 * df[col_predicha_num].mean() # 5% de la media como desviación estándar
559
+ valores_predichos = df[col_predicha_num].astype(float).values
560
+ datos_sinteticos = valores_predichos + np.random.normal(0, desviacion_std, size=len(valores_predichos))
561
+ datos_sinteticos = np.maximum(0, datos_sinteticos) # Asegurar que no haya valores negativos
562
+ datos_sinteticos = np.round(datos_sinteticos, 2)
563
+ df[col_real] = datos_sinteticos
564
+
565
+ return df
566
+
567
+ def actualizar_tabla_evento(df, n_filas, concentracion, unidad, n_replicas):
568
+ # Actualizar tabla sin borrar "Concentración Real"
569
+ df_new = generar_tabla(n_filas, concentracion, unidad, n_replicas)
570
+
571
+ # Mapear columnas
572
+ col_real_new = [col for col in df_new.columns if 'Concentración Real' in col and 'Promedio' not in col and 'Desviación' not in col]
573
+ col_real_old = [col for col in df.columns if 'Concentración Real' in col and 'Promedio' not in col and 'Desviación' not in col]
574
+
575
+ # Reemplazar valores existentes en "Concentración Real"
576
+ for col_new, col_old in zip(col_real_new, col_real_old):
577
+ df_new[col_new] = None
578
+ for idx in df_new.index:
579
+ if idx in df.index:
580
+ df_new.at[idx, col_new] = df.at[idx, col_old]
581
+
582
+ return df_new
583
+
584
+ def cargar_excel(file):
585
+ # Leer el archivo Excel
586
+ df = pd.read_excel(file.name, sheet_name=None)
587
+
588
+ # Verificar que el archivo tenga al menos dos pestañas
589
+ if len(df) < 2:
590
+ return "El archivo debe tener al menos dos pestañas.", None, None, None, None, None, None
591
+
592
+ # Obtener la primera pestaña como referencia
593
+ primera_pestaña = next(iter(df.values()))
594
+ concentracion_inicial = primera_pestaña.iloc[0, 0]
595
+ unidad_medida = primera_pestaña.columns[0].split('(')[-1].split(')')[0]
596
+ n_filas = len(primera_pestaña)
597
+ n_replicas = len(df)
598
+
599
+ # Generar la tabla base
600
+ df_base = generar_tabla(n_filas, concentracion_inicial, unidad_medida, n_replicas)
601
 
602
+ # Llenar la tabla con los datos de cada pestaña
603
+ for i, (sheet_name, sheet_df) in enumerate(df.items(), start=1):
604
+ col_real = f"Concentración Real {i} ({unidad_medida})"
605
+ df_base[col_real] = sheet_df.iloc[:, 1].values
606
 
607
+ return concentracion_inicial, unidad_medida, n_filas, n_replicas, df_base, "", None, ""
608
+
609
+ # Función para calcular la regresión y generar el gráfico
610
+ def calcular_regresion(conc_data, abs_data):
611
+ if conc_data is None or abs_data is None:
612
+ return "Datos insuficientes", None
613
+
614
+ df_conc = pd.DataFrame(conc_data, columns=["Concentración"])
615
+ df_abs = pd.DataFrame(abs_data, columns=["Absorbancia"])
616
+
617
+ if df_conc.empty or df_abs.empty or len(df_conc) != len(df_abs):
618
+ return "Los datos de concentración y absorbancia deben tener el mismo número de puntos", None
619
+
620
+ df = pd.concat([df_conc, df_abs], axis=1)
621
+ df = df.dropna()
622
+
623
+ if len(df) < 2:
624
+ return "Se requieren al menos dos puntos para calcular la regresión", None
625
+
626
+ # Calcular regresión lineal
627
+ slope, intercept, r_value, p_value, std_err = stats.linregress(df["Concentración"], df["Absorbancia"])
628
+
629
+ # Generar gráfico
630
+ fig, ax = plt.subplots(figsize=(8, 6))
631
+ ax.scatter(df["Concentración"], df["Absorbancia"], color='blue', label='Datos')
632
+ ax.plot(df["Concentración"], intercept + slope * df["Concentración"], 'r', label='Ajuste Lineal')
633
+ ax.set_xlabel('Concentración')
634
+ ax.set_ylabel('Absorbancia')
635
+ ax.set_title('Regresión Lineal: Absorbancia vs Concentración')
636
+ ax.legend()
637
+
638
+ # Añadir ecuación y R² en el gráfico
639
+ ax.annotate(
640
+ f'y = {intercept:.4f} + {slope:.4f}x\n$R^2$ = {r_value**2:.4f}',
641
+ xy=(0.05, 0.95),
642
+ xycoords='axes fraction',
643
+ fontsize=12,
644
+ backgroundcolor='white',
645
+ verticalalignment='top'
646
+ )
647
+
648
+ return "Regresión calculada exitosamente", fig
649
+
650
+ # Interfaz Gradio
651
+ with gr.Blocks(theme=gr.themes.Soft()) as interfaz:
652
+ gr.Markdown("""
653
+ # 📊 Sistema Avanzado de Calibración con Análisis Estadístico
654
+ Configure los parámetros, edite los valores en la tabla y luego presione "Calcular" para obtener el análisis.
655
+ """)
656
+
657
+ with gr.Tab("📝 Datos de Calibración"):
658
+ with gr.Row():
659
+ concentracion_input = gr.Number(
660
+ value=2000000,
661
+ label="Concentración Inicial",
662
+ precision=0
663
+ )
664
+ unidad_input = gr.Textbox(
665
+ value="UFC",
666
+ label="Unidad de Medida",
667
+ placeholder="UFC, OD, etc..."
668
+ )
669
+ filas_slider = gr.Slider(
670
+ minimum=1,
671
+ maximum=20,
672
+ value=7,
673
+ step=1,
674
+ label="Número de filas"
675
+ )
676
+ decimales_slider = gr.Slider(
677
+ minimum=0,
678
+ maximum=5,
679
+ value=0,
680
+ step=1,
681
+ label="Número de Decimales"
682
+ )
683
+ replicas_slider = gr.Slider(
684
+ minimum=1,
685
+ maximum=10,
686
+ value=1,
687
+ step=1,
688
+ label="Número de Réplicas"
689
+ )
690
+
691
+ with gr.Row():
692
+ calcular_btn = gr.Button("🔄 Calcular", variant="primary")
693
+ limpiar_btn = gr.Button("🗑 Limpiar Datos", variant="secondary")
694
+ ajustar_decimales_btn = gr.Button("🛠 Ajustar Decimales", variant="secondary")
695
+
696
+ with gr.Row():
697
+ ejemplo_ufc_btn = gr.Button("📋 Cargar Ejemplo UFC", variant="secondary")
698
+ ejemplo_od_btn = gr.Button("📋 Cargar Ejemplo OD", variant="secondary")
699
+ sinteticos_btn = gr.Button("🧪 Generar Datos Sintéticos", variant="secondary")
700
+ cargar_excel_btn = gr.UploadButton("📂 Cargar Excel", file_types=[".xlsx"], variant="secondary")
701
+
702
+ tabla_output = gr.DataFrame(
703
+ wrap=True,
704
+ label="Tabla de Datos",
705
+ interactive=True,
706
+ type="pandas",
707
  )
708
 
709
+ with gr.Tab("📊 Análisis y Reporte"):
710
+ estado_output = gr.Textbox(label="Estado", interactive=False)
711
+ graficos_output = gr.Plot(label="Gráficos de Análisis")
712
+
713
+ # Reemplazar Multiselect por CheckboxGroup
714
  filas_seleccionadas = gr.CheckboxGroup(
715
+ label="Seleccione las filas a incluir en el análisis",
716
+ choices=[],
717
+ value=[],
718
  )
 
719
 
720
+ # Opciones y botones debajo del gráfico
721
+ with gr.Row():
722
+ # Paletas de colores disponibles en Seaborn
723
+ paletas_colores = ["deep", "muted", "pastel", "bright", "dark", "colorblind", "Set1", "Set2", "Set3"]
724
 
725
+ palette_puntos_dropdown = gr.Dropdown(
726
+ choices=paletas_colores,
727
+ value="deep",
728
+ label="Paleta para Puntos"
729
+ )
730
+ estilo_puntos_dropdown = gr.Dropdown(
731
+ choices=["o", "s", "^", "D", "v", "<", ">", "h", "H", "p", "*", "X", "d"],
732
+ value="o",
733
+ label="Estilo de Puntos"
734
+ )
735
+ palette_linea_ajuste_dropdown = gr.Dropdown(
736
+ choices=paletas_colores,
737
+ value="muted",
738
+ label="Paleta Línea de Ajuste"
739
+ )
740
+ estilo_linea_ajuste_dropdown = gr.Dropdown(
741
+ choices=["-", "--", "-.", ":"],
742
+ value="-",
743
+ label="Estilo Línea de Ajuste"
744
+ )
745
+
746
+ with gr.Row():
747
+ palette_linea_ideal_dropdown = gr.Dropdown(
748
+ choices=paletas_colores,
749
+ value="bright",
750
+ label="Paleta Línea Ideal"
751
+ )
752
+ estilo_linea_ideal_dropdown = gr.Dropdown(
753
+ choices=["--", "-", "-.", ":"],
754
+ value="--",
755
+ label="Estilo Línea Ideal"
756
+ )
757
+ palette_barras_error_dropdown = gr.Dropdown(
758
+ choices=paletas_colores,
759
+ value="pastel",
760
+ label="Paleta Barras de Error"
761
+ )
762
+ mostrar_linea_ajuste = gr.Checkbox(value=True, label="Mostrar Línea de Ajuste")
763
+ mostrar_linea_ideal = gr.Checkbox(value=True, label="Mostrar Línea Ideal")
764
+ mostrar_puntos = gr.Checkbox(value=True, label="Mostrar Puntos")
765
+ graficar_btn = gr.Button("📊 Graficar", variant="primary")
766
+
767
+ with gr.Row():
768
+ copiar_btn = gr.Button("📋 Copiar Informe", variant="secondary")
769
+ exportar_word_btn = gr.Button("💾 Exportar Informe Word", variant="primary")
770
+ exportar_latex_btn = gr.Button("💾 Exportar Informe LaTeX", variant="primary")
771
+
772
+ with gr.Row():
773
+ exportar_word_file = gr.File(label="Informe en Word")
774
+ exportar_latex_file = gr.File(label="Informe en LaTeX")
775
 
776
+ # Informe al final
777
+ informe_output = gr.Markdown(elem_id="informe_output")
778
 
779
+ with gr.Tab("📈 Regresión Absorbancia vs Concentración"):
780
+ gr.Markdown("## Ajuste de Regresión entre Absorbancia y Concentración")
 
781
 
782
+ # Crear componentes para ingresar datos
783
+ with gr.Row():
784
+ concentracion_abs_input = gr.Dataframe(
785
+ headers=["Concentración"],
786
+ label="Datos de Concentración",
787
+ interactive=True
788
+ )
789
+ absorbancia_input = gr.Dataframe(
790
+ headers=["Absorbancia"],
791
+ label="Datos de Absorbancia",
792
+ interactive=True
793
+ )
794
+
795
+ calcular_regresion_btn = gr.Button("Calcular Regresión")
796
+
797
+ # Salidas
798
+ estado_regresion_output = gr.Textbox(label="Estado de la Regresión", interactive=False)
799
+ grafico_regresion_output = gr.Plot(label="Gráfico de Regresión")
800
+
801
+ # Eventos para actualizar las opciones de filas
802
+ def actualizar_opciones_filas(df):
803
+ if df is None or df.empty:
804
+ return gr.update(choices=[], value=[])
805
+ else:
806
+ opciones = [f"Fila {i+1}" for i in df.index]
807
+ return gr.update(choices=opciones, value=opciones)
808
+
809
+ tabla_output.change(
810
+ fn=actualizar_opciones_filas,
811
+ inputs=[tabla_output],
812
+ outputs=filas_seleccionadas
813
+ )
814
+
815
+ # Evento al presionar el botón Calcular
816
+ calcular_btn.click(
817
+ fn=actualizar_analisis,
818
+ inputs=[tabla_output, replicas_slider, unidad_input, filas_seleccionadas],
819
+ outputs=[estado_output, graficos_output, informe_output, tabla_output]
820
+ )
821
+
822
+ # Evento para graficar con opciones seleccionadas
823
+ graficar_btn.click(
824
+ fn=actualizar_graficos,
825
+ inputs=[
826
+ tabla_output, replicas_slider, unidad_input,
827
+ palette_puntos_dropdown, estilo_puntos_dropdown,
828
+ palette_linea_ajuste_dropdown, estilo_linea_ajuste_dropdown,
829
+ palette_linea_ideal_dropdown, estilo_linea_ideal_dropdown,
830
+ palette_barras_error_dropdown,
831
+ mostrar_linea_ajuste, mostrar_linea_ideal, mostrar_puntos,
832
+ filas_seleccionadas
833
+ ],
834
+ outputs=graficos_output
835
+ )
836
+
837
+ # Eventos de los botones adicionales, como limpiar, cargar ejemplos, ajustar decimales, etc.
838
+ # Evento para limpiar datos
839
+ limpiar_btn.click(
840
+ fn=limpiar_datos,
841
+ inputs=[replicas_slider],
842
+ outputs=[concentracion_input, unidad_input, filas_slider, tabla_output, estado_output, graficos_output, informe_output]
843
+ )
844
+
845
+ # Eventos de los botones de ejemplo
846
+ ejemplo_ufc_btn.click(
847
+ fn=cargar_ejemplo_ufc,
848
+ inputs=[replicas_slider],
849
+ outputs=[concentracion_input, unidad_input, filas_slider, tabla_output]
850
+ )
851
+
852
+ ejemplo_od_btn.click(
853
+ fn=cargar_ejemplo_od,
854
+ inputs=[replicas_slider],
855
+ outputs=[concentracion_input, unidad_input, filas_slider, tabla_output]
856
+ )
857
+
858
+ # Evento para generar datos sintéticos
859
+ sinteticos_btn.click(
860
+ fn=generar_datos_sinteticos_evento,
861
+ inputs=[tabla_output, replicas_slider, unidad_input],
862
+ outputs=tabla_output
863
+ )
864
+
865
+ # Evento para cargar archivo Excel
866
+ cargar_excel_btn.upload(
867
+ fn=cargar_excel,
868
+ inputs=[cargar_excel_btn],
869
+ outputs=[concentracion_input, unidad_input, filas_slider, replicas_slider, tabla_output, estado_output, graficos_output, informe_output]
870
+ )
871
+
872
+ # Evento al presionar el botón Ajustar Decimales
873
+ ajustar_decimales_btn.click(
874
+ fn=ajustar_decimales_evento,
875
+ inputs=[tabla_output, decimales_slider],
876
+ outputs=tabla_output
877
+ )
878
+
879
+ # Actualizar tabla al cambiar los parámetros (sin borrar "Concentración Real")
880
+ def actualizar_tabla_wrapper(df, filas, conc, unidad, replicas):
881
+ return actualizar_tabla_evento(df, filas, conc, unidad, replicas)
882
+
883
+ concentracion_input.change(
884
+ fn=actualizar_tabla_wrapper,
885
+ inputs=[tabla_output, filas_slider, concentracion_input, unidad_input, replicas_slider],
886
+ outputs=tabla_output
887
+ )
888
+
889
+ unidad_input.change(
890
+ fn=actualizar_tabla_wrapper,
891
+ inputs=[tabla_output, filas_slider, concentracion_input, unidad_input, replicas_slider],
892
+ outputs=tabla_output
893
+ )
894
+
895
+ filas_slider.change(
896
+ fn=actualizar_tabla_wrapper,
897
+ inputs=[tabla_output, filas_slider, concentracion_input, unidad_input, replicas_slider],
898
+ outputs=tabla_output
899
+ )
900
+
901
+ replicas_slider.change(
902
+ fn=actualizar_tabla_wrapper,
903
+ inputs=[tabla_output, filas_slider, concentracion_input, unidad_input, replicas_slider],
904
+ outputs=tabla_output
905
+ )
906
+
907
+ # Evento de copiar informe utilizando JavaScript
908
+ copiar_btn.click(
909
+ None,
910
+ [],
911
+ [],
912
+ js="""
913
+ function() {
914
+ const informeElement = document.querySelector('#informe_output');
915
+ const range = document.createRange();
916
+ range.selectNode(informeElement);
917
+ window.getSelection().removeAllRanges();
918
+ window.getSelection().addRange(range);
919
+ document.execCommand('copy');
920
+ window.getSelection().removeAllRanges();
921
+ alert('Informe copiado al portapapeles');
922
+ }
923
+ """
924
+ )
925
+
926
+ # Eventos de exportar informes
927
+ exportar_word_btn.click(
928
+ fn=exportar_word,
929
+ inputs=[tabla_output, informe_output, unidad_input, filas_seleccionadas],
930
+ outputs=exportar_word_file
931
+ )
932
+
933
+ exportar_latex_btn.click(
934
+ fn=exportar_latex,
935
+ inputs=[tabla_output, informe_output, filas_seleccionadas],
936
+ outputs=exportar_latex_file
937
+ )
938
+
939
+ # Inicializar la interfaz con el ejemplo base
940
+ def iniciar_con_ejemplo():
941
+ n_replicas = 1
942
+ df = generar_tabla(7, 2000000, "UFC", n_replicas)
943
+ # Valores reales de ejemplo
944
+ df[f"Concentración Real 1 (UFC)"] = [2000000, 1600000, 1200000, 800000, 400000, 200000, 100000]
945
+ filas_seleccionadas_inicial = [f"Fila {i+1}" for i in df.index]
946
+ estado, fig, informe, df = actualizar_analisis(df, n_replicas, "UFC", filas_seleccionadas_inicial)
947
+ return (
948
+ 2000000,
949
+ "UFC",
950
+ 7,
951
+ df,
952
+ estado,
953
+ fig,
954
+ informe,
955
+ filas_seleccionadas_inicial
956
  )
957
 
958
+ interfaz.load(
959
+ fn=iniciar_con_ejemplo,
960
+ outputs=[concentracion_input, unidad_input, filas_slider, tabla_output, estado_output, graficos_output, informe_output, filas_seleccionadas]
961
+ )
962
+
963
+ # Evento al presionar el botón de calcular regresión
964
+ calcular_regresion_btn.click(
965
+ fn=calcular_regresion,
966
+ inputs=[concentracion_abs_input, absorbancia_input],
967
+ outputs=[estado_regresion_output, grafico_regresion_output]
968
+ )
969
+
970
  # Lanzar la interfaz
971
  if __name__ == "__main__":
972
  interfaz.launch()