Update app.py
Browse files
app.py
CHANGED
|
@@ -37,7 +37,6 @@ def ajustar_decimales_evento(df, decimales):
|
|
| 37 |
|
| 38 |
def calcular_promedio_desviacion(df, n_replicas, unidad_predicha, unidad_replicas, decimales):
|
| 39 |
df = df.copy()
|
| 40 |
-
# Ahora las columnas de las réplicas se llaman "Absorbancia Real X (unidad_replicas)"
|
| 41 |
col_replicas = [c for c in df.columns if c.startswith("Absorbancia Real") and f"({unidad_replicas})" in c and "Promedio" not in c and "Desviación" not in c]
|
| 42 |
|
| 43 |
for col in col_replicas:
|
|
@@ -397,8 +396,6 @@ def exportar_latex(df, informe_md, filas_seleccionadas):
|
|
| 397 |
return filename
|
| 398 |
|
| 399 |
def limpiar_datos(n_replicas):
|
| 400 |
-
# Por defecto asignamos una unidad de predicha y replicas.
|
| 401 |
-
# Aquí el ejemplo quedará con mg/L para predicha y Absorbancia para réplicas
|
| 402 |
unidad_predicha = "mg/L"
|
| 403 |
unidad_replicas = "Abs"
|
| 404 |
|
|
@@ -408,7 +405,6 @@ def limpiar_datos(n_replicas):
|
|
| 408 |
"Factor de Dilución": [(1/(1/(2**i))) for i in range(7)],
|
| 409 |
f"Concentración Predicha ({unidad_predicha})": [2000000/(1/(1/(2**i))) for i in range(7)]
|
| 410 |
})
|
| 411 |
-
# Creamos columnas de absorbancia real vacías
|
| 412 |
for i in range(1, n_replicas+1):
|
| 413 |
df[f"Absorbancia Real {i} ({unidad_replicas})"] = np.nan
|
| 414 |
|
|
@@ -466,28 +462,17 @@ def cargar_excel(file):
|
|
| 466 |
|
| 467 |
df_base = df_sheet1.iloc[:, :4].copy()
|
| 468 |
|
| 469 |
-
# Intentar extraer la unidad de predicha del nombre de la columna de Concentración Predicha
|
| 470 |
-
# Suponemos que la cuarta columna es "Concentración Predicha (...)"
|
| 471 |
pred_col = df_base.columns[-1]
|
| 472 |
unidad_predicha = "mg/L"
|
| 473 |
try:
|
| 474 |
-
# intentar extraer unidad de predicha
|
| 475 |
if "(" in pred_col and ")" in pred_col:
|
| 476 |
unidad_predicha = pred_col.split("(")[1].split(")")[0].strip()
|
| 477 |
except:
|
| 478 |
unidad_predicha = "mg/L"
|
| 479 |
|
| 480 |
-
# Por defecto, la unidad de las réplicas (absorbancias)
|
| 481 |
unidad_replicas = "Abs"
|
| 482 |
|
| 483 |
-
|
| 484 |
-
# Si no se logra, usamos por defecto
|
| 485 |
-
try:
|
| 486 |
-
primera_col = df_base.columns[0]
|
| 487 |
-
# Antes se intentaba extraer la unidad de aquí, ahora sólo la concentración inicial
|
| 488 |
-
concentracion_inicial = 2000000.0
|
| 489 |
-
except:
|
| 490 |
-
concentracion_inicial = 2000000.0
|
| 491 |
|
| 492 |
n_filas = len(df_base)
|
| 493 |
n_replicas = 2
|
|
@@ -665,64 +650,81 @@ def iniciar_con_ejemplo():
|
|
| 665 |
3
|
| 666 |
)
|
| 667 |
|
|
|
|
|
|
|
| 668 |
with gr.Blocks(theme=gr.themes.Soft()) as interfaz:
|
| 669 |
gr.Markdown("""
|
| 670 |
# 📊 Sistema Avanzado de Calibración con Análisis Estadístico
|
| 671 |
-
|
| 672 |
-
|
| 673 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
| 674 |
""")
|
| 675 |
|
| 676 |
with gr.Tab("📝 Datos de Calibración"):
|
|
|
|
|
|
|
|
|
|
|
|
|
| 677 |
with gr.Row():
|
| 678 |
-
|
| 679 |
-
|
| 680 |
-
|
| 681 |
-
|
| 682 |
-
|
| 683 |
-
|
| 684 |
-
|
| 685 |
-
|
| 686 |
-
|
| 687 |
-
|
| 688 |
-
|
| 689 |
-
|
| 690 |
-
|
| 691 |
-
|
| 692 |
-
|
| 693 |
-
|
| 694 |
-
|
| 695 |
-
|
| 696 |
-
|
| 697 |
-
|
| 698 |
-
|
| 699 |
-
|
| 700 |
-
|
| 701 |
-
|
| 702 |
-
|
| 703 |
-
|
| 704 |
-
|
| 705 |
-
|
| 706 |
-
|
| 707 |
-
|
| 708 |
-
|
| 709 |
-
|
| 710 |
-
|
| 711 |
-
|
| 712 |
-
|
| 713 |
-
|
| 714 |
-
|
| 715 |
-
|
| 716 |
-
|
| 717 |
-
|
| 718 |
-
ajustar_decimales_btn = gr.Button("🛠 Ajustar Decimales", variant="secondary")
|
| 719 |
-
|
| 720 |
with gr.Row():
|
| 721 |
-
|
| 722 |
-
|
| 723 |
-
|
| 724 |
-
|
| 725 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 726 |
tabla_output = gr.DataFrame(
|
| 727 |
wrap=True,
|
| 728 |
label="Tabla de Datos",
|
|
@@ -731,38 +733,48 @@ with gr.Blocks(theme=gr.themes.Soft()) as interfaz:
|
|
| 731 |
)
|
| 732 |
|
| 733 |
with gr.Tab("📊 Análisis y Reporte"):
|
| 734 |
-
|
| 735 |
-
|
| 736 |
-
|
| 737 |
-
|
| 738 |
-
label="Seleccione las filas a incluir en el análisis",
|
| 739 |
-
choices=[],
|
| 740 |
-
value=[],
|
| 741 |
-
)
|
| 742 |
-
|
| 743 |
with gr.Row():
|
| 744 |
-
|
| 745 |
-
|
| 746 |
-
choices=[
|
| 747 |
-
value=
|
| 748 |
-
label="Estilo de Punto"
|
| 749 |
-
)
|
| 750 |
-
color_linea_ajuste_picker = gr.ColorPicker(label="Color de la Línea de Ajuste", value="#00FF00")
|
| 751 |
-
estilo_linea_ajuste_dropdown = gr.Dropdown(
|
| 752 |
-
choices=["-", "--", "-.", ":"],
|
| 753 |
-
value="-",
|
| 754 |
-
label="Estilo Línea de Ajuste"
|
| 755 |
)
|
| 756 |
|
| 757 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 758 |
|
| 759 |
with gr.Row():
|
| 760 |
-
mostrar_linea_ajuste = gr.Checkbox(value=True, label="Mostrar Línea de Ajuste")
|
| 761 |
-
mostrar_puntos = gr.Checkbox(value=True, label="Mostrar Puntos")
|
| 762 |
graficar_btn = gr.Button("📊 Graficar", variant="primary")
|
|
|
|
| 763 |
|
| 764 |
-
|
|
|
|
|
|
|
| 765 |
|
|
|
|
| 766 |
with gr.Row():
|
| 767 |
copiar_btn = gr.Button("📋 Copiar Informe", variant="secondary")
|
| 768 |
exportar_word_btn = gr.Button("💾 Exportar Informe Word", variant="primary")
|
|
@@ -772,19 +784,20 @@ with gr.Blocks(theme=gr.themes.Soft()) as interfaz:
|
|
| 772 |
exportar_word_file = gr.File(label="Informe en Word")
|
| 773 |
exportar_latex_file = gr.File(label="Informe en LaTeX")
|
| 774 |
|
| 775 |
-
informe_output = gr.Markdown(elem_id="informe_output")
|
| 776 |
-
|
| 777 |
with gr.Tab("📈 Regresión Absorbancia vs Concentración"):
|
| 778 |
-
gr.Markdown("
|
|
|
|
|
|
|
|
|
|
| 779 |
|
| 780 |
filas_seleccionadas_regresion = gr.CheckboxGroup(
|
| 781 |
-
label="Seleccione
|
| 782 |
choices=[],
|
| 783 |
value=[],
|
| 784 |
)
|
| 785 |
|
| 786 |
with gr.Row():
|
| 787 |
-
color_puntos_regresion_picker = gr.ColorPicker(label="Color Puntos Regresión", value="#0000FF")
|
| 788 |
estilo_puntos_regresion = gr.Dropdown(
|
| 789 |
choices=["o", "s", "^", "D", "v", "<", ">", "h", "H", "p", "*", "X", "d"],
|
| 790 |
value="o",
|
|
@@ -799,44 +812,43 @@ with gr.Blocks(theme=gr.themes.Soft()) as interfaz:
|
|
| 799 |
mostrar_linea_ajuste_regresion = gr.Checkbox(value=True, label="Mostrar Línea de Ajuste (Regresión)")
|
| 800 |
mostrar_puntos_regresion = gr.Checkbox(value=True, label="Mostrar Puntos (Regresión)")
|
| 801 |
|
| 802 |
-
|
| 803 |
-
|
| 804 |
-
|
| 805 |
-
|
| 806 |
-
|
| 807 |
-
|
| 808 |
-
|
| 809 |
-
|
| 810 |
-
|
| 811 |
-
)
|
| 812 |
|
| 813 |
with gr.Row():
|
| 814 |
titulo_grafico_original = gr.Textbox(
|
| 815 |
label="Título del Gráfico Original",
|
| 816 |
-
placeholder="Regresión Lineal:
|
| 817 |
)
|
| 818 |
titulo_grafico_personalizado = gr.Textbox(
|
| 819 |
label="Título del Gráfico Personalizado",
|
| 820 |
-
placeholder="Regresión Lineal Personalizada"
|
| 821 |
)
|
| 822 |
|
| 823 |
with gr.Row():
|
| 824 |
eje_x_original = gr.Textbox(
|
| 825 |
-
label="Etiqueta
|
| 826 |
placeholder="Concentración Predicha (mg/L)"
|
| 827 |
)
|
| 828 |
eje_y_original = gr.Textbox(
|
| 829 |
-
label="Etiqueta
|
| 830 |
placeholder="Absorbancia Real Promedio (UFC)"
|
| 831 |
)
|
| 832 |
|
| 833 |
with gr.Row():
|
| 834 |
eje_x_personalizado = gr.Textbox(
|
| 835 |
-
label="Etiqueta
|
| 836 |
placeholder="Concentración Predicha (mg/L)"
|
| 837 |
)
|
| 838 |
eje_y_personalizado = gr.Textbox(
|
| 839 |
-
label="Etiqueta
|
| 840 |
placeholder="Absorbancia Real Promedio (UFC)"
|
| 841 |
)
|
| 842 |
|
|
|
|
| 37 |
|
| 38 |
def calcular_promedio_desviacion(df, n_replicas, unidad_predicha, unidad_replicas, decimales):
|
| 39 |
df = df.copy()
|
|
|
|
| 40 |
col_replicas = [c for c in df.columns if c.startswith("Absorbancia Real") and f"({unidad_replicas})" in c and "Promedio" not in c and "Desviación" not in c]
|
| 41 |
|
| 42 |
for col in col_replicas:
|
|
|
|
| 396 |
return filename
|
| 397 |
|
| 398 |
def limpiar_datos(n_replicas):
|
|
|
|
|
|
|
| 399 |
unidad_predicha = "mg/L"
|
| 400 |
unidad_replicas = "Abs"
|
| 401 |
|
|
|
|
| 405 |
"Factor de Dilución": [(1/(1/(2**i))) for i in range(7)],
|
| 406 |
f"Concentración Predicha ({unidad_predicha})": [2000000/(1/(1/(2**i))) for i in range(7)]
|
| 407 |
})
|
|
|
|
| 408 |
for i in range(1, n_replicas+1):
|
| 409 |
df[f"Absorbancia Real {i} ({unidad_replicas})"] = np.nan
|
| 410 |
|
|
|
|
| 462 |
|
| 463 |
df_base = df_sheet1.iloc[:, :4].copy()
|
| 464 |
|
|
|
|
|
|
|
| 465 |
pred_col = df_base.columns[-1]
|
| 466 |
unidad_predicha = "mg/L"
|
| 467 |
try:
|
|
|
|
| 468 |
if "(" in pred_col and ")" in pred_col:
|
| 469 |
unidad_predicha = pred_col.split("(")[1].split(")")[0].strip()
|
| 470 |
except:
|
| 471 |
unidad_predicha = "mg/L"
|
| 472 |
|
|
|
|
| 473 |
unidad_replicas = "Abs"
|
| 474 |
|
| 475 |
+
concentracion_inicial = 2000000.0
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 476 |
|
| 477 |
n_filas = len(df_base)
|
| 478 |
n_replicas = 2
|
|
|
|
| 650 |
3
|
| 651 |
)
|
| 652 |
|
| 653 |
+
import gradio as gr
|
| 654 |
+
|
| 655 |
with gr.Blocks(theme=gr.themes.Soft()) as interfaz:
|
| 656 |
gr.Markdown("""
|
| 657 |
# 📊 Sistema Avanzado de Calibración con Análisis Estadístico
|
| 658 |
+
|
| 659 |
+
En esta interfaz puede:
|
| 660 |
+
- Ingresar y/o cargar datos de calibración.
|
| 661 |
+
- Ajustar unidades, número de decimales, réplicas, etc.
|
| 662 |
+
- Calcular la regresión, visualizar gráficos y generar informes.
|
| 663 |
+
|
| 664 |
+
**Sugerencia:** Cargue sus datos o use un ejemplo, luego presione "Calcular" para obtener el análisis.
|
| 665 |
""")
|
| 666 |
|
| 667 |
with gr.Tab("📝 Datos de Calibración"):
|
| 668 |
+
gr.Markdown("""
|
| 669 |
+
### Ingrese Parámetros Iniciales
|
| 670 |
+
Ajuste las unidades y la concentración inicial, así como el número de filas y réplicas para la tabla.
|
| 671 |
+
""")
|
| 672 |
with gr.Row():
|
| 673 |
+
with gr.Column():
|
| 674 |
+
concentracion_input = gr.Number(
|
| 675 |
+
value=2000000,
|
| 676 |
+
label="Concentración Inicial",
|
| 677 |
+
precision=0
|
| 678 |
+
)
|
| 679 |
+
unidad_predicha_input = gr.Textbox(
|
| 680 |
+
value="mg/L",
|
| 681 |
+
label="Unidad de Medida (Predicha)",
|
| 682 |
+
placeholder="Ejemplo: mg/L"
|
| 683 |
+
)
|
| 684 |
+
unidad_replicas_input = gr.Textbox(
|
| 685 |
+
value="UFC",
|
| 686 |
+
label="Unidad de Medida (Absorbancias)",
|
| 687 |
+
placeholder="Ejemplo: Abs, OD, UFC, etc."
|
| 688 |
+
)
|
| 689 |
+
|
| 690 |
+
with gr.Column():
|
| 691 |
+
filas_slider = gr.Slider(
|
| 692 |
+
minimum=1,
|
| 693 |
+
maximum=20,
|
| 694 |
+
value=7,
|
| 695 |
+
step=1,
|
| 696 |
+
label="Número de Filas"
|
| 697 |
+
)
|
| 698 |
+
decimales_slider = gr.Slider(
|
| 699 |
+
minimum=0,
|
| 700 |
+
maximum=5,
|
| 701 |
+
value=3,
|
| 702 |
+
step=1,
|
| 703 |
+
label="Número de Decimales"
|
| 704 |
+
)
|
| 705 |
+
replicas_slider = gr.Slider(
|
| 706 |
+
minimum=1,
|
| 707 |
+
maximum=10,
|
| 708 |
+
value=1,
|
| 709 |
+
step=1,
|
| 710 |
+
label="Número de Réplicas"
|
| 711 |
+
)
|
| 712 |
+
|
|
|
|
|
|
|
| 713 |
with gr.Row():
|
| 714 |
+
with gr.Column():
|
| 715 |
+
gr.Markdown("### Acciones sobre la Tabla")
|
| 716 |
+
calcular_btn = gr.Button("🔄 Calcular", variant="primary")
|
| 717 |
+
ajustar_decimales_btn = gr.Button("🛠 Ajustar Decimales", variant="secondary")
|
| 718 |
+
limpiar_btn = gr.Button("🗑 Limpiar Datos", variant="secondary")
|
| 719 |
+
|
| 720 |
+
with gr.Column():
|
| 721 |
+
gr.Markdown("### Ejemplos y Carga de Datos")
|
| 722 |
+
ejemplo_ufc_btn = gr.Button("📋 Cargar Ejemplo UFC", variant="secondary")
|
| 723 |
+
ejemplo_od_btn = gr.Button("📋 Cargar Ejemplo OD", variant="secondary")
|
| 724 |
+
sinteticos_btn = gr.Button("🧪 Generar Datos Sintéticos", variant="secondary")
|
| 725 |
+
cargar_excel_btn = gr.UploadButton("📂 Cargar Excel", file_types=[".xlsx"], variant="secondary")
|
| 726 |
+
|
| 727 |
+
gr.Markdown("### Tabla de Datos")
|
| 728 |
tabla_output = gr.DataFrame(
|
| 729 |
wrap=True,
|
| 730 |
label="Tabla de Datos",
|
|
|
|
| 733 |
)
|
| 734 |
|
| 735 |
with gr.Tab("📊 Análisis y Reporte"):
|
| 736 |
+
gr.Markdown("""
|
| 737 |
+
### Análisis y Resultados
|
| 738 |
+
Seleccione las filas a utilizar en el análisis y presione Calcular. Ajuste colores y estilo según su preferencia.
|
| 739 |
+
""")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 740 |
with gr.Row():
|
| 741 |
+
filas_seleccionadas = gr.CheckboxGroup(
|
| 742 |
+
label="Seleccione filas para el análisis",
|
| 743 |
+
choices=[],
|
| 744 |
+
value=[],
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 745 |
)
|
| 746 |
|
| 747 |
+
with gr.Row():
|
| 748 |
+
with gr.Column():
|
| 749 |
+
color_puntos_picker = gr.ColorPicker(label="Color de Puntos", value="#0000FF")
|
| 750 |
+
estilo_puntos_dropdown = gr.Dropdown(
|
| 751 |
+
choices=["o", "s", "^", "D", "v", "<", ">", "h", "H", "p", "*", "X", "d"],
|
| 752 |
+
value="o",
|
| 753 |
+
label="Estilo de Punto"
|
| 754 |
+
)
|
| 755 |
+
|
| 756 |
+
with gr.Column():
|
| 757 |
+
color_linea_ajuste_picker = gr.ColorPicker(label="Color de la Línea de Ajuste", value="#00FF00")
|
| 758 |
+
estilo_linea_ajuste_dropdown = gr.Dropdown(
|
| 759 |
+
choices=["-", "--", "-.", ":"],
|
| 760 |
+
value="-",
|
| 761 |
+
label="Estilo Línea de Ajuste"
|
| 762 |
+
)
|
| 763 |
+
|
| 764 |
+
with gr.Column():
|
| 765 |
+
color_barras_error_picker = gr.ColorPicker(label="Color Barras de Error", value="#FFA500")
|
| 766 |
+
mostrar_linea_ajuste = gr.Checkbox(value=True, label="Mostrar Línea de Ajuste")
|
| 767 |
+
mostrar_puntos = gr.Checkbox(value=True, label="Mostrar Puntos")
|
| 768 |
|
| 769 |
with gr.Row():
|
|
|
|
|
|
|
| 770 |
graficar_btn = gr.Button("📊 Graficar", variant="primary")
|
| 771 |
+
recalcular_btn = gr.Button("🔄 Recalcular", variant="secondary")
|
| 772 |
|
| 773 |
+
estado_output = gr.Textbox(label="Estado del Análisis", interactive=False)
|
| 774 |
+
graficos_output = gr.Plot(label="Gráficos de Análisis")
|
| 775 |
+
informe_output = gr.Markdown(elem_id="informe_output")
|
| 776 |
|
| 777 |
+
gr.Markdown("### Exportar Informe")
|
| 778 |
with gr.Row():
|
| 779 |
copiar_btn = gr.Button("📋 Copiar Informe", variant="secondary")
|
| 780 |
exportar_word_btn = gr.Button("💾 Exportar Informe Word", variant="primary")
|
|
|
|
| 784 |
exportar_word_file = gr.File(label="Informe en Word")
|
| 785 |
exportar_latex_file = gr.File(label="Informe en LaTeX")
|
| 786 |
|
|
|
|
|
|
|
| 787 |
with gr.Tab("📈 Regresión Absorbancia vs Concentración"):
|
| 788 |
+
gr.Markdown("""
|
| 789 |
+
### Análisis de Regresión Adicional
|
| 790 |
+
Seleccione nuevamente las filas para la regresión y personalice los gráficos.
|
| 791 |
+
""")
|
| 792 |
|
| 793 |
filas_seleccionadas_regresion = gr.CheckboxGroup(
|
| 794 |
+
label="Seleccione filas para la regresión",
|
| 795 |
choices=[],
|
| 796 |
value=[],
|
| 797 |
)
|
| 798 |
|
| 799 |
with gr.Row():
|
| 800 |
+
color_puntos_regresion_picker = gr.ColorPicker(label="Color Puntos (Regresión)", value="#0000FF")
|
| 801 |
estilo_puntos_regresion = gr.Dropdown(
|
| 802 |
choices=["o", "s", "^", "D", "v", "<", ">", "h", "H", "p", "*", "X", "d"],
|
| 803 |
value="o",
|
|
|
|
| 812 |
mostrar_linea_ajuste_regresion = gr.Checkbox(value=True, label="Mostrar Línea de Ajuste (Regresión)")
|
| 813 |
mostrar_puntos_regresion = gr.Checkbox(value=True, label="Mostrar Puntos (Regresión)")
|
| 814 |
|
| 815 |
+
legend_location_dropdown = gr.Dropdown(
|
| 816 |
+
choices=[
|
| 817 |
+
'best', 'upper right', 'upper left', 'lower left', 'lower right',
|
| 818 |
+
'right', 'center left', 'center right', 'lower center',
|
| 819 |
+
'upper center', 'center'
|
| 820 |
+
],
|
| 821 |
+
value='lower right',
|
| 822 |
+
label='Ubicación de la Leyenda'
|
| 823 |
+
)
|
|
|
|
| 824 |
|
| 825 |
with gr.Row():
|
| 826 |
titulo_grafico_original = gr.Textbox(
|
| 827 |
label="Título del Gráfico Original",
|
| 828 |
+
placeholder="Ej: Regresión Lineal: Abs vs Conc (Original)"
|
| 829 |
)
|
| 830 |
titulo_grafico_personalizado = gr.Textbox(
|
| 831 |
label="Título del Gráfico Personalizado",
|
| 832 |
+
placeholder="Ej: Regresión Lineal Personalizada"
|
| 833 |
)
|
| 834 |
|
| 835 |
with gr.Row():
|
| 836 |
eje_x_original = gr.Textbox(
|
| 837 |
+
label="Etiqueta Eje X (Original)",
|
| 838 |
placeholder="Concentración Predicha (mg/L)"
|
| 839 |
)
|
| 840 |
eje_y_original = gr.Textbox(
|
| 841 |
+
label="Etiqueta Eje Y (Original)",
|
| 842 |
placeholder="Absorbancia Real Promedio (UFC)"
|
| 843 |
)
|
| 844 |
|
| 845 |
with gr.Row():
|
| 846 |
eje_x_personalizado = gr.Textbox(
|
| 847 |
+
label="Etiqueta Eje X (Personalizado)",
|
| 848 |
placeholder="Concentración Predicha (mg/L)"
|
| 849 |
)
|
| 850 |
eje_y_personalizado = gr.Textbox(
|
| 851 |
+
label="Etiqueta Eje Y (Personalizado)",
|
| 852 |
placeholder="Absorbancia Real Promedio (UFC)"
|
| 853 |
)
|
| 854 |
|