C2MV commited on
Commit
017603a
·
verified ·
1 Parent(s): 02b562d

Create app.py

Browse files
Files changed (1) hide show
  1. app.py +553 -0
app.py ADDED
@@ -0,0 +1,553 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ import pandas as pd
3
+ import numpy as np
4
+ import matplotlib.pyplot as plt
5
+ import seaborn as sns
6
+ from scipy import stats
7
+ from datetime import datetime
8
+ import docx
9
+ from docx.shared import Inches, Pt
10
+ from docx.enum.text import WD_PARAGRAPH_ALIGNMENT
11
+ import os
12
+
13
+ def generar_tabla(n_filas, concentracion_inicial, unidad_medida):
14
+ valores_base = [1.00, 0.80, 0.60, 0.40, 0.20, 0.10, 0.05]
15
+
16
+ if n_filas <= 7:
17
+ solucion_inoculo = valores_base[:n_filas]
18
+ agua = [round(1 - x, 2) for x in solucion_inoculo]
19
+ else:
20
+ solucion_inoculo = valores_base.copy()
21
+ ultimo_valor = valores_base[-1]
22
+ for _ in range(n_filas - 7):
23
+ nuevo_valor = round(ultimo_valor / 2, 3)
24
+ solucion_inoculo.append(nuevo_valor)
25
+ ultimo_valor = nuevo_valor
26
+ agua = [round(1 - x, 3) for x in solucion_inoculo]
27
+
28
+ data = {
29
+ f"Solución de inóculo ({concentracion_inicial} {unidad_medida})": solucion_inoculo,
30
+ "H2O": agua
31
+ }
32
+ df = pd.DataFrame(data)
33
+
34
+ nombre_columna = f"Solución de inóculo ({concentracion_inicial} {unidad_medida})"
35
+ df["Factor de Dilución"] = df[nombre_columna].apply(lambda x: round(1 / x, 2))
36
+ df[f"Concentración Predicha ({unidad_medida})"] = df["Factor de Dilución"].apply(
37
+ lambda x: round(concentracion_inicial / x, 0)
38
+ )
39
+
40
+ df[f"Concentración Real ({unidad_medida})"] = None
41
+
42
+ return df
43
+
44
+ def generar_datos_sinteticos(df, desviacion_std):
45
+ col_predicha = [col for col in df.columns if 'Predicha' in col][0]
46
+ col_real = [col for col in df.columns if 'Real' in col][0]
47
+
48
+ # Generar datos sintéticos
49
+ valores_predichos = df[col_predicha].values
50
+ datos_sinteticos = valores_predichos + np.random.normal(0, desviacion_std, size=len(valores_predichos))
51
+ datos_sinteticos = np.maximum(0, datos_sinteticos) # Asegurar que no haya valores negativos
52
+ datos_sinteticos = np.round(datos_sinteticos, 2)
53
+
54
+ df[col_real] = datos_sinteticos
55
+
56
+ return df
57
+
58
+ def generar_graficos(df_valid):
59
+ col_predicha = [col for col in df_valid.columns if 'Predicha' in col][0]
60
+ col_real = [col for col in df_valid.columns if 'Real' in col][0]
61
+
62
+ # Calcular regresión lineal
63
+ slope, intercept, r_value, p_value, std_err = stats.linregress(df_valid[col_predicha], df_valid[col_real])
64
+ df_valid['Ajuste Lineal'] = intercept + slope * df_valid[col_predicha]
65
+
66
+ # Configurar estilos
67
+ sns.set(style="whitegrid")
68
+ plt.rcParams.update({'figure.autolayout': True})
69
+
70
+ fig, (ax1, ax2) = plt.subplots(1, 2, figsize=(14, 6))
71
+
72
+ # Gráfico de dispersión con línea de regresión
73
+ sns.scatterplot(
74
+ data=df_valid,
75
+ x=col_predicha,
76
+ y=col_real,
77
+ ax=ax1,
78
+ color='blue',
79
+ s=100,
80
+ label='Datos Reales',
81
+ marker='o'
82
+ )
83
+
84
+ # Línea de ajuste
85
+ sns.lineplot(
86
+ x=df_valid[col_predicha],
87
+ y=df_valid['Ajuste Lineal'],
88
+ ax=ax1,
89
+ color='green',
90
+ label='Ajuste Lineal',
91
+ linewidth=2
92
+ )
93
+
94
+ # Línea ideal
95
+ ax1.plot(
96
+ [df_valid[col_predicha].min(), df_valid[col_predicha].max()],
97
+ [df_valid[col_predicha].min(), df_valid[col_predicha].max()],
98
+ color='red',
99
+ linestyle='--',
100
+ label='Ideal'
101
+ )
102
+
103
+ ax1.set_title('Correlación entre Concentración Predicha y Real', fontsize=14)
104
+ ax1.set_xlabel('Concentración Predicha', fontsize=12)
105
+ ax1.set_ylabel('Concentración Real', fontsize=12)
106
+
107
+ # Añadir ecuación y R² en el gráfico
108
+ ax1.annotate(
109
+ f'y = {intercept:.2f} + {slope:.2f}x\n$R^2$ = {r_value**2:.4f}',
110
+ xy=(0.05, 0.95),
111
+ xycoords='axes fraction',
112
+ fontsize=12,
113
+ backgroundcolor='white',
114
+ verticalalignment='top'
115
+ )
116
+
117
+ # Posicionar la leyenda
118
+ ax1.legend(loc='lower right', fontsize=10)
119
+
120
+ # Gráfico de residuos
121
+ residuos = df_valid[col_real] - df_valid['Ajuste Lineal']
122
+ sns.scatterplot(
123
+ data=df_valid,
124
+ x=col_predicha,
125
+ y=residuos,
126
+ ax=ax2,
127
+ color='purple',
128
+ s=100,
129
+ marker='D',
130
+ label='Residuos'
131
+ )
132
+
133
+ ax2.axhline(y=0, color='black', linestyle='--', linewidth=1)
134
+ ax2.set_title('Gráfico de Residuos', fontsize=14)
135
+ ax2.set_xlabel('Concentración Predicha', fontsize=12)
136
+ ax2.set_ylabel('Residuo', fontsize=12)
137
+ ax2.legend(loc='upper right', fontsize=10)
138
+
139
+ plt.tight_layout()
140
+ plt.savefig('grafico.png') # Guardar el gráfico para incluirlo en el informe
141
+ return fig
142
+
143
+ def evaluar_calidad_calibracion(df_valid, r_squared, rmse, cv_percent):
144
+ """Evaluar la calidad de la calibración y proporcionar recomendaciones"""
145
+ evaluacion = {
146
+ "calidad": "",
147
+ "recomendaciones": [],
148
+ "estado": "✅" if r_squared >= 0.95 and cv_percent <= 15 else "⚠️"
149
+ }
150
+
151
+ if r_squared >= 0.95:
152
+ evaluacion["calidad"] = "Excelente"
153
+ elif r_squared >= 0.90:
154
+ evaluacion["calidad"] = "Buena"
155
+ elif r_squared >= 0.85:
156
+ evaluacion["calidad"] = "Regular"
157
+ else:
158
+ evaluacion["calidad"] = "Deficiente"
159
+
160
+ if r_squared < 0.95:
161
+ evaluacion["recomendaciones"].append("- Considere repetir algunas mediciones para mejorar la correlación")
162
+
163
+ if cv_percent > 15:
164
+ evaluacion["recomendaciones"].append("- La variabilidad es alta. Revise el procedimiento de dilución")
165
+
166
+ if rmse > 0.1 * df_valid[df_valid.columns[-1]].mean():
167
+ evaluacion["recomendaciones"].append("- El error de predicción es significativo. Verifique la técnica de medición")
168
+
169
+ return evaluacion
170
+
171
+ def generar_informe_completo(df_valid):
172
+ """Generar un informe completo en formato markdown"""
173
+ col_predicha = [col for col in df_valid.columns if 'Predicha' in col][0]
174
+ col_real = [col for col in df_valid.columns if 'Real' in col][0]
175
+
176
+ # Calcular estadísticas
177
+ slope, intercept, r_value, p_value, std_err = stats.linregress(df_valid[col_predicha], df_valid[col_real])
178
+ r_squared = r_value ** 2
179
+ rmse = np.sqrt(((df_valid[col_real] - df_valid['Ajuste Lineal']) ** 2).mean())
180
+ cv = (df_valid['Ajuste Lineal'].std() / df_valid['Ajuste Lineal'].mean()) * 100 # CV de los ajustes
181
+
182
+ # Evaluar calidad
183
+ evaluacion = evaluar_calidad_calibracion(df_valid, r_squared, rmse, cv)
184
+
185
+ informe = f"""# Informe de Calibración {evaluacion['estado']}
186
+ Fecha: {datetime.now().strftime('%d/%m/%Y %H:%M')}
187
+
188
+ ## Resumen Estadístico
189
+ - **Ecuación de Regresión**: y = {intercept:.4f} + {slope:.4f}x
190
+ - **Coeficiente de correlación (r)**: {r_value:.4f}
191
+ - **Coeficiente de determinación ($R^2$)**: {r_squared:.4f}
192
+ - **Valor p**: {p_value:.4e}
193
+ - **Error estándar de la pendiente**: {std_err:.4f}
194
+ - **Error cuadrático medio (RMSE)**: {rmse:.4f}
195
+ - **Coeficiente de variación (CV)**: {cv:.2f}%
196
+
197
+ ## Evaluación de Calidad
198
+ - **Calidad de la calibración**: {evaluacion['calidad']}
199
+
200
+ ## Recomendaciones
201
+ {chr(10).join(evaluacion['recomendaciones']) if evaluacion['recomendaciones'] else "No hay recomendaciones específicas. La calibración cumple con los criterios de calidad."}
202
+
203
+ ## Decisión
204
+ {("✅ APROBADO - La calibración cumple con los criterios de calidad establecidos" if evaluacion['estado'] == "✅" else "⚠️ REQUIERE REVISIÓN - La calibración necesita ajustes según las recomendaciones anteriores")}
205
+
206
+ ---
207
+ *Nota: Este informe fue generado automáticamente. Por favor, revise los resultados y valide según sus criterios específicos.*
208
+ """
209
+ return informe, evaluacion['estado']
210
+
211
+ def actualizar_analisis(df):
212
+ if df is None or df.empty:
213
+ return "Error en los datos", None, "No se pueden generar análisis"
214
+
215
+ col_predicha = [col for col in df.columns if 'Predicha' in col][0]
216
+ col_real = [col for col in df.columns if 'Real' in col][0]
217
+
218
+ # Convertir columnas a numérico
219
+ df[col_predicha] = pd.to_numeric(df[col_predicha], errors='coerce')
220
+ df[col_real] = pd.to_numeric(df[col_real], errors='coerce')
221
+
222
+ df_valid = df.dropna(subset=[col_predicha, col_real])
223
+
224
+ if len(df_valid) < 2:
225
+ return "Se necesitan más datos", None, "Se requieren al menos dos valores reales para el análisis"
226
+
227
+ # Calcular la regresión y agregar 'Ajuste Lineal'
228
+ slope, intercept, r_value, p_value, std_err = stats.linregress(df_valid[col_predicha], df_valid[col_real])
229
+ df_valid['Ajuste Lineal'] = intercept + slope * df_valid[col_predicha]
230
+
231
+ fig = generar_graficos(df_valid)
232
+ informe, estado = generar_informe_completo(df_valid)
233
+
234
+ return estado, fig, informe
235
+
236
+ def exportar_informe_word(df_valid, informe_md):
237
+ # Crear documento Word
238
+ doc = docx.Document()
239
+
240
+ # Estilos APA 7
241
+ style = doc.styles['Normal']
242
+ font = style.font
243
+ font.name = 'Times New Roman'
244
+ font.size = Pt(12)
245
+
246
+ # Título centrado
247
+ titulo = doc.add_heading('Informe de Calibración', 0)
248
+ titulo.alignment = WD_PARAGRAPH_ALIGNMENT.CENTER
249
+
250
+ # Fecha
251
+ fecha = doc.add_paragraph(f"Fecha: {datetime.now().strftime('%d/%m/%Y %H:%M')}")
252
+ fecha.alignment = WD_PARAGRAPH_ALIGNMENT.CENTER
253
+
254
+ # Insertar gráfico
255
+ doc.add_picture('grafico.png', width=Inches(6))
256
+ ultimo_parrafo = doc.paragraphs[-1]
257
+ ultimo_parrafo.alignment = WD_PARAGRAPH_ALIGNMENT.CENTER
258
+
259
+ # Leyenda del gráfico en estilo APA 7
260
+ leyenda = doc.add_paragraph('Figura 1. Gráfico de calibración.')
261
+ leyenda_format = leyenda.paragraph_format
262
+ leyenda_format.alignment = WD_PARAGRAPH_ALIGNMENT.CENTER
263
+ leyenda.style = doc.styles['Caption']
264
+
265
+ # Agregar contenido del informe
266
+ doc.add_heading('Resumen Estadístico', level=1)
267
+ for linea in informe_md.split('\n'):
268
+ if linea.startswith('##'):
269
+ doc.add_heading(linea.replace('##', '').strip(), level=2)
270
+ else:
271
+ doc.add_paragraph(linea)
272
+
273
+ # Añadir tabla de datos
274
+ doc.add_heading('Tabla de Datos de Calibración', level=1)
275
+
276
+ # Convertir DataFrame a lista de listas
277
+ tabla_datos = df_valid.reset_index(drop=True)
278
+ tabla_datos = tabla_datos.round(4) # Redondear a 4 decimales si es necesario
279
+ columnas = tabla_datos.columns.tolist()
280
+ registros = tabla_datos.values.tolist()
281
+
282
+ # Crear tabla en Word
283
+ tabla = doc.add_table(rows=1 + len(registros), cols=len(columnas))
284
+ tabla.style = 'Table Grid'
285
+
286
+ # Añadir los encabezados
287
+ hdr_cells = tabla.rows[0].cells
288
+ for idx, col_name in enumerate(columnas):
289
+ hdr_cells[idx].text = col_name
290
+
291
+ # Añadir los registros
292
+ for i, registro in enumerate(registros):
293
+ row_cells = tabla.rows[i + 1].cells
294
+ for j, valor in enumerate(registro):
295
+ row_cells[j].text = str(valor)
296
+
297
+ # Formatear fuente de la tabla
298
+ for row in tabla.rows:
299
+ for cell in row.cells:
300
+ for paragraph in cell.paragraphs:
301
+ paragraph.style = doc.styles['Normal']
302
+
303
+ # Guardar documento
304
+ doc.save('informe_calibracion.docx')
305
+ return 'informe_calibracion.docx'
306
+
307
+ def exportar_informe_latex(df_valid, informe_md):
308
+ # Generar código LaTeX
309
+ informe_tex = r"""\documentclass{article}
310
+ \usepackage[spanish]{babel}
311
+ \usepackage{amsmath}
312
+ \usepackage{graphicx}
313
+ \usepackage{booktabs}
314
+ \begin{document}
315
+ """
316
+ informe_tex += informe_md.replace('#', '').replace('**', '\\textbf{').replace('*', '\\textit{')
317
+ informe_tex += r"""
318
+ \end{document}
319
+ """
320
+ with open('informe_calibracion.tex', 'w') as f:
321
+ f.write(informe_tex)
322
+ return 'informe_calibracion.tex'
323
+
324
+ # Funciones de ejemplo
325
+ def cargar_ejemplo_ufc():
326
+ df = generar_tabla(7, 2000000, "UFC")
327
+ valores_reales = [2000000, 1600000, 1200000, 800000, 400000, 200000, 100000]
328
+ df[f"Concentración Real (UFC)"] = valores_reales
329
+ return 2000000, "UFC", 7, df
330
+
331
+ def cargar_ejemplo_od():
332
+ df = generar_tabla(7, 1.0, "OD")
333
+ valores_reales = [1.000, 0.800, 0.600, 0.400, 0.200, 0.100, 0.050]
334
+ df[f"Concentración Real (OD)"] = valores_reales
335
+ return 1.0, "OD", 7, df
336
+
337
+ # Función para actualizar la tabla
338
+ def actualizar_tabla_evento(n_filas, concentracion, unidad):
339
+ df = generar_tabla(n_filas, concentracion, unidad)
340
+ return df
341
+
342
+ # Función para limpiar datos
343
+ def limpiar_datos():
344
+ df = generar_tabla(7, 2000000, "UFC")
345
+ return (
346
+ 2000000, # Concentración Inicial
347
+ "UFC", # Unidad de Medida
348
+ 7, # Número de filas
349
+ df, # Tabla Output
350
+ "", # Estado Output
351
+ None, # Gráficos Output
352
+ "" # Informe Output
353
+ )
354
+
355
+ # Función para generar datos sintéticos
356
+ def generar_datos_sinteticos_evento(df):
357
+ df = df.copy()
358
+ desviacion_std = 0.05 * df[df.columns[-2]].mean() # 5% de la media como desviación estándar
359
+ df = generar_datos_sinteticos(df, desviacion_std)
360
+ return df
361
+
362
+ # Eventos de botones y actualización
363
+ def copiar_informe(informe):
364
+ return informe
365
+
366
+ def exportar_word(df, informe_md):
367
+ df_valid = df.copy()
368
+ col_predicha = [col for col in df_valid.columns if 'Predicha' in col][0]
369
+ col_real = [col for col in df_valid.columns if 'Real' in col][0]
370
+
371
+ # Convertir columnas a numérico
372
+ df_valid[col_predicha] = pd.to_numeric(df_valid[col_predicha], errors='coerce')
373
+ df_valid[col_real] = pd.to_numeric(df_valid[col_real], errors='coerce')
374
+
375
+ df_valid = df_valid.dropna(subset=[col_predicha, col_real])
376
+
377
+ if df_valid.empty:
378
+ return None
379
+
380
+ filename = exportar_informe_word(df_valid, informe_md)
381
+ return gr.File.update(value=filename, visible=True)
382
+
383
+ def exportar_latex(df, informe_md):
384
+ df_valid = df.copy()
385
+ col_predicha = [col for col in df_valid.columns if 'Predicha' in col][0]
386
+ col_real = [col for col in df_valid.columns if 'Real' in col][0]
387
+
388
+ # Convertir columnas a numérico
389
+ df_valid[col_predicha] = pd.to_numeric(df_valid[col_predicha], errors='coerce')
390
+ df_valid[col_real] = pd.to_numeric(df_valid[col_real], errors='coerce')
391
+
392
+ df_valid = df_valid.dropna(subset=[col_predicha, col_real])
393
+
394
+ if df_valid.empty:
395
+ return None
396
+
397
+ filename = exportar_informe_latex(df_valid, informe_md)
398
+ return gr.File.update(value=filename, visible=True)
399
+
400
+ # Interfaz Gradio
401
+ with gr.Blocks(theme=gr.themes.Soft()) as interfaz:
402
+ gr.Markdown("""
403
+ # 📊 Sistema Avanzado de Calibración con Análisis Estadístico
404
+ Configure los parámetros, edite los valores en la tabla y luego presione "Calcular" para obtener el análisis.
405
+ """)
406
+
407
+ with gr.Tab("📝 Datos de Calibración"):
408
+ with gr.Row():
409
+ concentracion_input = gr.Number(
410
+ value=2000000,
411
+ label="Concentración Inicial",
412
+ precision=0
413
+ )
414
+ unidad_input = gr.Textbox(
415
+ value="UFC",
416
+ label="Unidad de Medida",
417
+ placeholder="UFC, OD, etc..."
418
+ )
419
+ filas_slider = gr.Slider(
420
+ minimum=1,
421
+ maximum=20,
422
+ value=7,
423
+ step=1,
424
+ label="Número de filas"
425
+ )
426
+
427
+ with gr.Row():
428
+ calcular_btn = gr.Button("🔄 Calcular", variant="primary")
429
+ limpiar_btn = gr.Button("🗑 Limpiar Datos", variant="secondary")
430
+
431
+ with gr.Row():
432
+ ejemplo_ufc_btn = gr.Button("📋 Cargar Ejemplo UFC", variant="secondary")
433
+ ejemplo_od_btn = gr.Button("📋 Cargar Ejemplo OD", variant="secondary")
434
+ sinteticos_btn = gr.Button("🧪 Generar Datos Sintéticos", variant="secondary") # Nuevo botón
435
+
436
+ tabla_output = gr.DataFrame(
437
+ row_count=(1, "dynamic"),
438
+ col_count=(5, "fixed"),
439
+ wrap=True,
440
+ label="Tabla de Datos",
441
+ interactive=True,
442
+ datatype=["number", "number", "number", "number", "number"],
443
+ type="pandas",
444
+ )
445
+
446
+ with gr.Tab("📊 Análisis y Reporte"):
447
+ estado_output = gr.Textbox(label="Estado", interactive=False)
448
+ graficos_output = gr.Plot(label="Gráficos de Análisis")
449
+ informe_output = gr.Markdown()
450
+
451
+ with gr.Row():
452
+ copiar_btn = gr.Button("📋 Copiar Informe", variant="secondary")
453
+ exportar_word_btn = gr.Button("💾 Exportar Informe Word", variant="primary")
454
+ exportar_latex_btn = gr.Button("💾 Exportar Informe LaTeX", variant="primary")
455
+
456
+ # Eventos
457
+ input_components = [tabla_output]
458
+ output_components = [estado_output, graficos_output, informe_output]
459
+
460
+ # Evento al presionar el botón Calcular
461
+ calcular_btn.click(
462
+ fn=actualizar_analisis,
463
+ inputs=tabla_output,
464
+ outputs=output_components
465
+ )
466
+
467
+ # Evento para limpiar datos
468
+ limpiar_btn.click(
469
+ fn=limpiar_datos,
470
+ inputs=[],
471
+ outputs=[concentracion_input, unidad_input, filas_slider, tabla_output, estado_output, graficos_output, informe_output]
472
+ )
473
+
474
+ # Eventos de los botones de ejemplo
475
+ ejemplo_ufc_btn.click(
476
+ fn=cargar_ejemplo_ufc,
477
+ outputs=[concentracion_input, unidad_input, filas_slider, tabla_output]
478
+ )
479
+
480
+ ejemplo_od_btn.click(
481
+ fn=cargar_ejemplo_od,
482
+ outputs=[concentracion_input, unidad_input, filas_slider, tabla_output]
483
+ )
484
+
485
+ # Evento para generar datos sintéticos
486
+ sinteticos_btn.click(
487
+ fn=generar_datos_sinteticos_evento,
488
+ inputs=tabla_output,
489
+ outputs=tabla_output
490
+ )
491
+
492
+ # Actualizar tabla al cambiar los parámetros
493
+ concentracion_input.change(
494
+ fn=actualizar_tabla_evento,
495
+ inputs=[filas_slider, concentracion_input, unidad_input],
496
+ outputs=tabla_output
497
+ )
498
+
499
+ unidad_input.change(
500
+ fn=actualizar_tabla_evento,
501
+ inputs=[filas_slider, concentracion_input, unidad_input],
502
+ outputs=tabla_output
503
+ )
504
+
505
+ filas_slider.change(
506
+ fn=actualizar_tabla_evento,
507
+ inputs=[filas_slider, concentracion_input, unidad_input],
508
+ outputs=tabla_output
509
+ )
510
+
511
+ # Evento de copiar informe
512
+ copiar_btn.click(
513
+ fn=copiar_informe,
514
+ inputs=[informe_output],
515
+ outputs=[]
516
+ )
517
+
518
+ # Eventos de exportar informes
519
+ exportar_word_btn.click(
520
+ fn=exportar_word,
521
+ inputs=[tabla_output, informe_output],
522
+ outputs=exportar_word_btn
523
+ )
524
+
525
+ exportar_latex_btn.click(
526
+ fn=exportar_latex,
527
+ inputs=[tabla_output, informe_output],
528
+ outputs=exportar_latex_btn
529
+ )
530
+
531
+ # Inicializar la interfaz con el ejemplo base
532
+ def iniciar_con_ejemplo():
533
+ df = generar_tabla(7, 2000000, "UFC")
534
+ valores_reales = [2000000, 1600000, 1200000, 800000, 400000, 200000, 100000]
535
+ df[f"Concentración Real (UFC)"] = valores_reales
536
+ estado, fig, informe = actualizar_analisis(df)
537
+ return (
538
+ 2000000,
539
+ "UFC",
540
+ 7,
541
+ df,
542
+ estado,
543
+ fig,
544
+ informe
545
+ )
546
+
547
+ interfaz.load(
548
+ fn=iniciar_con_ejemplo,
549
+ outputs=[concentracion_input, unidad_input, filas_slider, tabla_output, estado_output, graficos_output, informe_output]
550
+ )
551
+
552
+ # Lanzar la interfaz
553
+ interfaz.launch()