import gradio as gr import pandas as pd import numpy as np from sklearn.ensemble import RandomForestRegressor from sklearn.model_selection import train_test_split import matplotlib.pyplot as plt import matplotlib.gridspec as gridspec import os import traceback estado = { "modelo_tiempo": None, "modelo_distancia": None, "df": None, "X_test": None, "yT_test": None, "yD_test": None, "listo": False, "mensaje": " Cargando modelo... (puede tardar unos segundos)" } def cargar_y_entrenar(): file_path = "RegistroLanzamientos.xlsx" if not os.path.exists(file_path): estado["mensaje"] = " No se encontró 'RegistroLanzamientos.xlsx' en la raíz. Súbelo en la pestaña Files." return try: df = pd.read_excel(file_path) df.columns = ['Lanzamiento', 'Radio_cm', 'Peso_g', 'Pos_X', 'Pos_Y', 'Tiempo_s'] df['Distancia_cm'] = np.sqrt(df['Pos_X']**2 + df['Pos_Y']**2) X = df[['Radio_cm', 'Peso_g']] y_tiempo, y_distancia = df['Tiempo_s'], df['Distancia_cm'] idx_train, idx_test = train_test_split(df.index, test_size=0.2, random_state=42) X_train, X_test = X.loc[idx_train], X.loc[idx_test] yT_train, yT_test = y_tiempo.loc[idx_train], y_tiempo.loc[idx_test] yD_train, yD_test = y_distancia.loc[idx_train], y_distancia.loc[idx_test] modelo_tiempo = RandomForestRegressor(n_estimators=100, random_state=42) modelo_distancia = RandomForestRegressor(n_estimators=100, random_state=42) modelo_tiempo.fit(X_train, yT_train) modelo_distancia.fit(X_train, yD_train) estado.update({ "modelo_tiempo": modelo_tiempo, "modelo_distancia": modelo_distancia, "df": df, "X_test": X_test, "yT_test": yT_test, "yD_test": yD_test, "listo": True, "mensaje": " Modelo cargado y listo." }) except Exception as e: estado["mensaje"] = f" Error al entrenar:\n{traceback.format_exc()}" cargar_y_entrenar() def predecir(radio, peso): if not estado["listo"]: return estado["mensaje"], "", "" if radio <= 0 or peso <= 0: return " Valores deben ser > 0.", "", "" input_data = pd.DataFrame({'Radio_cm': [radio], 'Peso_g': [peso]}) pred_t = estado["modelo_tiempo"].predict(input_data)[0] pred_d = estado["modelo_distancia"].predict(input_data)[0] return " Predicción exitosa", f"{pred_t:.2f}", f"{pred_d:.1f}" def generar_grafico(): if not estado["listo"]: return None, estado["mensaje"] mT, mD = estado["modelo_tiempo"], estado["modelo_distancia"] X_test, yT_test, yD_test = estado["X_test"], estado["yT_test"], estado["yD_test"] df = estado["df"] pred_t = mT.predict(X_test) pred_d = mD.predict(X_test) r2_T = sum((yT_test - pred_t)**2) / sum((yT_test - yT_test.mean())**2) r2_D = sum((yD_test - pred_d)**2) / sum((yD_test - yD_test.mean())**2) fig = plt.figure(figsize=(16, 12)) gs = gridspec.GridSpec(2, 2, figure=fig, hspace=0.4, wspace=0.3) ax0 = fig.add_subplot(gs[0, 0]) ax0.scatter(yT_test, pred_t, color='#e74c3c', alpha=0.8) lim = [min(yT_test.min(), pred_t.min()), max(yT_test.max(), pred_t.max())] ax0.plot(lim, lim, '--', color='gray') ax0.set_xlabel('Tiempo real (s)'); ax0.set_ylabel('Tiempo predicho (s)') ax0.set_title('Tiempo de caída'); ax0.text(0.05, 0.95, f'R²={r2_T:.3f}', transform=ax0.transAxes, va='top') ax1 = fig.add_subplot(gs[0, 1]) ax1.scatter(yD_test, pred_d, color='#2980b9', alpha=0.8) lim2 = [min(yD_test.min(), pred_d.min()), max(yD_test.max(), pred_d.max())] ax1.plot(lim2, lim2, '--', color='gray') ax1.set_xlabel('Distancia real (cm)'); ax1.set_ylabel('Distancia predicha (cm)') ax1.set_title('Distancia al origen'); ax1.text(0.05, 0.95, f'R²={r2_D:.3f}', transform=ax1.transAxes, va='top') ax2 = fig.add_subplot(gs[1, 0]) imp = [mT.feature_importances_, mD.feature_importances_] x = np.arange(2); w = 0.35 ax2.bar(x-w/2, imp[0], w, label='Tiempo', color='#e74c3c') ax2.bar(x+w/2, imp[1], w, label='Distancia', color='#2980b9') ax2.set_xticks(x); ax2.set_xticklabels(['Radio (cm)', 'Peso (g)']) ax2.set_ylabel('Importancia'); ax2.legend(); ax2.set_title('Variables clave') ax3 = fig.add_subplot(gs[1, 1]) r_range = np.linspace(df['Radio_cm'].min(), df['Radio_cm'].max(), 50) p_fijo = df['Peso_g'].median() ax3.plot(r_range, mT.predict(pd.DataFrame({'Radio_cm': r_range, 'Peso_g': p_fijo})), label='Tiempo', color='#e74c3c') ax3b = ax3.twinx() ax3b.plot(r_range, mD.predict(pd.DataFrame({'Radio_cm': r_range, 'Peso_g': p_fijo})), label='Distancia', color='#2980b9', ls='--') ax3.set_xlabel('Radio (cm)'); ax3.set_ylabel('Tiempo (s)', color='#e74c3c') ax3b.set_ylabel('Distancia (cm)', color='#2980b9') ax3.set_title(f'Efecto del radio (peso fijo={p_fijo:.0f}g)') ax3.legend(loc='upper left'); ax3b.legend(loc='upper right') plt.tight_layout() return fig, f" Gráfico generado. R² Tiempo: {r2_T:.3f} | R² Distancia: {r2_D:.3f}" with gr.Blocks(title=" Simulador Paracaídas") as app: gr.Markdown(" Predicción de Caída de Paracaídas") gr.Markdown(f"**Estado:** `{estado['mensaje']}`") with gr.Row(): radio_inp = gr.Number(label="Radio del paracaídas (cm)", value=24.0, minimum=0.1) peso_inp = gr.Number(label="Peso del muñeco (g)", value=55.0, minimum=0.1) btn_pred = gr.Button(" Calcular", variant="primary") with gr.Row(): msg_out = gr.Textbox(label="Estado", interactive=False) t_out = gr.Number(label="Tiempo (s)") d_out = gr.Number(label="Distancia (cm)") btn_pred.click(predecir, inputs=[radio_inp, peso_inp], outputs=[msg_out, t_out, d_out]) gr.Markdown("---") btn_plot = gr.Button(" Ver Gráfico Completo", variant="secondary") plot_out = gr.Plot(label="Análisis del Modelo") plot_msg = gr.Textbox(label="Métricas", interactive=False) gr.Markdown("*Basado en: Ruiz Cruz, EH (2026). Pensamiento cientifico autentico mediante STEAM, u-learning y modelos predictivos en educacion tecnica.*") btn_plot.click(generar_grafico, inputs=[], outputs=[plot_out, plot_msg]) app.launch(server_name="0.0.0.0", server_port=7860, share=False)