import tkinter as tk
from tkinter import ttk, messagebox, simpledialog, filedialog
import json
import numpy as np
import math
import matplotlib.pyplot as plt
from matplotlib.backends.backend_tkagg import FigureCanvasTkAgg
import pandas as pd
import os
from parametric_sweep import ParametricSweep

# ==================== CONSTANTES DE MATERIAL ====================
DENS = 7850  # kg/m3
FY = 355  # MPa
GAMMAS = 1.05  # Factor seguridad acero
EYOUNG = 210000  # MPa
NU = 0.3  # Coef Poisson

FYD = FY * 10**6 / GAMMAS  # Pa
EYOUNG_PA = EYOUNG * 10**6  # Pa
G = EYOUNG_PA / (2 * (1 + NU))  # Pa

# ==================== FUNCIONES DE CÁLCULO ====================

def generate_ipe_points(H, b, tf, tw):
    """Genera los puntos de una sección IPE a partir de parámetros normalizados"""
    # H: altura total, b: ancho flange, tf: espesor flange, tw: espesor alma
    h_alma = H - 2*tf
    x_alma_ini = (b - tw) / 2
    x_alma_fin = (b + tw) / 2

    # Puntos de la sección (sentido horario)
    points = np.array([
        [0, 0],                           # Esquina inferior izquierda flange
        [b, 0],                           # Esquina inferior derecha flange
        [b, tf],                          # Unión alma-flange derecha
        [x_alma_fin, tf],                 # Esquina superior alma derecha
        [x_alma_fin, H - tf],             # Esquina inferior alma derecha (arriba)
        [b, H - tf],                      # Unión alma-flange arriba
        [b, H],                           # Esquina superior derecha flange
        [0, H],                           # Esquina superior izquierda flange
        [0, H - tf],                      # Unión alma-flange arriba izquierda
        [x_alma_ini, H - tf],             # Esquina inferior alma izquierda (arriba)
        [x_alma_ini, tf],                 # Esquina superior alma izquierda
        [0, tf],                          # Unión alma-flange izquierda
        [0, 0],                           # Cerrar la sección
    ])

    return points

def generate_puente_nuevo_points(h, b, tf, tw, ha, ta, tr, theta):
    """Genera los puntos de la sección tipo puente nuevo a partir de parámetros normalizados"""
    # h: altura exterior, b: ancho total, tf: espesor ala, tw: espesor alma,

    hr = (b-tw) / 2 * math.tan(math.radians(theta))
    hl = tr / math.cos(math.radians(theta))
    hr_ = (b/2 - ta - tw/2)* math.tan(math.radians(theta))

    # Puntos de la sección (sentido horario)
    points = np.array([
        [0, 0],                           
        [b, 0],                           
        [b, tf + ha],                          
        [(b + tw)/2, tf + ha + hr],                
        [(b + tw)/2, tf + ha + hr - hl],           
        [b - ta, tf + ha + hr - hl - hr_],         
        [b - ta, tf],                           
        [(b + tw)/2, tf],                           
        [(b + tw)/2, h - tf],                      
        [b - ta, h - tf],             
        [b - ta, h - tf - ha - hr + hl + hr_],                 
        [(b + tw)/2, h - tf - ha - hr + hl],                          
        [(b + tw)/2, h - tf - ha - hr],
        [b, h - tf - ha],
        [b, h],
        [0, h],                           
        [0, h - tf - ha],                      
        [(b - tw)/2, h - tf - ha - hr],                          
        [(b - tw)/2, h - tf - ha - hr + hl],
        [ta, h - tf - ha - hr + hl + hr_],
        [ta, h - tf],
        [(b - tw)/2, h - tf],
        [(b - tw)/2, tf],
        [ta, tf],
        [ta, tf + ha + hr - hl - hr_],
        [(b - tw)/2, tf + ha + hr - hl],                
        [(b - tw)/2, tf + ha + hr],                          
        [0, tf + ha],                          
        [0, 0],                           
    ])

    return points

def calculate_section_properties(points):
    """Calcula todas las propiedades de la sección - CÓDIGO ORIGINAL SIN MODIFICAR"""
    npuntos = len(points)
    puntos = points

    px = puntos[:, 0]
    py = puntos[:, 1]

    # ANCHO Y CANTO MÁXIMO
    bmax = np.amax(px) - np.amin(px)
    hmax = np.amax(py) - np.amin(py)

    # PERÍMETRO
    long_i = np.zeros(npuntos - 1)
    for i in range(npuntos - 1):
        long_i[i] = ((puntos[i+1, 0] - puntos[i, 0])**2 + (puntos[i+1, 1] - puntos[i, 1])**2)**(1/2)
    perimetro = abs(sum(long_i))

    # ÁREA
    area_i = np.zeros(npuntos - 1)
    for i in range(npuntos - 1):
        area_i[i] = (puntos[i+1, 0] - puntos[i, 0]) * (puntos[i+1, 1] + puntos[i, 1]) / 2
    area = abs(sum(area_i))

    # PESO POR METRO
    peso = area * DENS

    # CENTRO DE GRAVEDAD
    cdg_i = np.zeros([npuntos, 2])
    for i in range(npuntos - 1):
        h1 = puntos[i, 1]
        h2 = puntos[i+1, 1]
        b = puntos[i+1, 0] - puntos[i, 0]
        d = puntos[i, 0]

        if h1 + h2 == 0:
            cdg_i[i, 1] = 0
        else:
            cdg_i[i, 1] = 1/3 * (h1*h1 + h1*h2 + h2*h2) / (h1 + h2)

        if h1 + h2 == 0:
            cdg_i[i, 0] = d + b/2
        else:
            cdg_i[i, 0] = d + b/3 * (h1 + 2*h2) / (h1 + h2)

    # MOMENTO ESTÁTICO
    statico_i = np.zeros([npuntos, 2])
    for i in range(npuntos - 1):
        statico_i[i, 1] = area_i[i] * cdg_i[i, 1]
        statico_i[i, 0] = area_i[i] * cdg_i[i, 0]

    cdg = sum(statico_i) / sum(area_i)
    xg = cdg[0]
    yg = cdg[1]

    # FIBRAS MÁS ALEJADAS
    v1y = np.amax(py) - yg
    v2y = np.amin(py) - yg
    v1x = np.amax(px) - xg
    v2x = np.amin(px) - xg

    # MOMENTOS DE INERCIA
    inercia_i = np.zeros([npuntos, 3])
    for i in range(npuntos - 1):
        h1 = puntos[i, 1]
        h2 = puntos[i+1, 1]
        b = puntos[i+1, 0] - puntos[i, 0]
        d = puntos[i, 0]
        xgi = cdg_i[i, 0]
        ygi = cdg_i[i, 1]
        ai = area_i[i]

        # Ixg
        if h2 >= h1:
            ixcuad_G_local = 1/12 * b * (h1**3) + b * h1 * (h1/2 - ygi)**2
            ixtriang_G_loc = 1/36 * b * (h2-h1)**3 + 1/2 * b * (h2-h1) * ((2*h1+h2)/3 - ygi)**2
        else:
            ixcuad_G_local = 1/12 * b * (h2**3) + b * h2 * (h2/2 - ygi)**2
            ixtriang_G_loc = 1/36 * b * (h1-h2)**3 + 1/2 * b * (h1-h2) * ((2*h2+h1)/3 - ygi)**2
        inercia_i[i, 0] = ixcuad_G_local + ixtriang_G_loc + ai * (yg - ygi)**2

        # Iyg
        if h2 >= h1:
            iycuad = 1/12 * h1 * b**3 + h1 * b * (b/2 + d - xgi)**2
            iytrian = 1/36 * (h2-h1) * b**3 + 1/2 * b * (h2-h1) * (2/3*b + d - xgi)**2
        else:
            iycuad = 1/12 * h2 * b**3 + h2 * b * (b/2 + d - xgi)**2
            iytrian = 1/36 * (h1-h2) * b**3 + 1/2 * b * (h1-h2) * (1/3*b + d - xgi)**2
        inercia_i[i, 1] = iycuad + iytrian + ai * (xg - xgi)**2

        # Pxyg
        if h2 >= h1:
            pxygcuadrado = b * h1 * (-h1/2 + ygi) * (-d - b/2 + xgi)
            pxytriangulo = b*b * (h2-h1)**2 / 72 + b * (h2-h1) / 2 * (-(h2-h1)/3 - h1 + ygi) * (-d - 2/3*b + xgi)
        else:
            pxygcuadrado = b * h2 * (-h2/2 + ygi) * (-d - b/2 + xgi)
            pxytriangulo = -b*b * (h1-h2)**2 / 72 + b * (h1-h2) / 2 * (-(h1-h2)/3 - h2 + ygi) * (-d - 1/3*b + xgi)
        inercia_i[i, 2] = pxygcuadrado + pxytriangulo + ai * (-xg + xgi) * (-yg + ygi)

    ig = sum(inercia_i)
    ixg = abs(ig[0])
    iyg = abs(ig[1])
    pxyg = ig[2]
    if sum(area_i) >= 0:
        pxyg = pxyg
    else:
        pxyg = -pxyg

    # RADIOS DE GIRO
    rx = (ixg / area)**0.5
    ry = (iyg / area)**0.5

    # EJES PRINCIPALES DE INERCIA
    ic = (ixg + iyg) / 2
    ir = (((ixg - iyg)/2)**2 + pxyg**2)**0.5

    imax = ic + ir
    imin = ic - ir
    rmax = (imax / area)**0.5
    rmin = (imin / area)**0.5

    # ORIENTACIÓN DE EJES PRINCIPALES
    rest = ixg - iyg
    if abs(rest) < 10**-12:
        tetha = 45
    else:
        tetha = 0.5 * math.atan((pxyg*2) / (ixg - iyg))
        tetha = abs(tetha) * 180 / math.pi

    if pxyg > 0:
        if ixg > iyg:
            tetha = -tetha
        else:
            tetha = tetha
    else:
        if ixg > iyg:
            tetha = tetha
        else:
            tetha = -tetha

    # MÓDULO RESISTENTE ELÁSTICO
    wel1x = abs(ixg / v1y)
    wel2x = abs(ixg / v2y)
    wel1y = abs(iyg / v1x)
    wel2y = abs(iyg / v2x)

    # AXIL Y MOMENTO ELÁSTICO
    nel = area * FYD
    melx = min(wel1x, wel2x) * FYD
    mely = min(wel1y, wel2y) * FYD

    # CONVERSIÓN A UNIDADES PRÁCTICAS (cm, cm2, cm3, cm4, kN)
    pot = 2
    bmax_cm = bmax * 10**pot
    hmax_cm = hmax * 10**pot
    perimetro_cm = perimetro * 10**pot
    xg_cm = xg * 10**pot
    yg_cm = yg * 10**pot
    v1y_cm = v1y * 10**pot
    v2y_cm = v2y * 10**pot
    v1x_cm = v1x * 10**pot
    v2x_cm = v2x * 10**pot
    rx_cm = rx * 10**pot
    ry_cm = ry * 10**pot
    rmax_cm = rmax * 10**pot
    rmin_cm = rmin * 10**pot

    area_cm2 = area * 10**(pot*2)

    wel1x_cm3 = wel1x * 10**(pot*3)
    wel2x_cm3 = wel2x * 10**(pot*3)
    wel1y_cm3 = wel1y * 10**(pot*3)
    wel2y_cm3 = wel2y * 10**(pot*3)

    ixg_cm4 = ixg * 10**(pot*4)
    iyg_cm4 = iyg * 10**(pot*4)
    pxyg_cm4 = pxyg * 10**(pot*4)
    imax_cm4 = imax * 10**(pot*4)
    imin_cm4 = imin * 10**(pot*4)

    nel_kn = nel / 1000
    melx_kn = melx / 1000
    mely_kn = mely / 1000

    return {
        'area': area_cm2,
        'perimetro': perimetro_cm,
        'xg': xg_cm,
        'yg': yg_cm,
        'v1y': v1y_cm,
        'v2y': v2y_cm,
        'v1x': v1x_cm,
        'v2x': v2x_cm,
        'ixg': ixg_cm4,
        'iyg': iyg_cm4,
        'pxyg': pxyg_cm4,
        'imax': imax_cm4,
        'imin': imin_cm4,
        'rx': rx_cm,
        'ry': ry_cm,
        'rmax': rmax_cm,
        'rmin': rmin_cm,
        'wel1x': wel1x_cm3,
        'wel2x': wel2x_cm3,
        'wel1y': wel1y_cm3,
        'wel2y': wel2y_cm3,
        'tetha': tetha,
        'peso': peso,
        'nel': nel_kn,
        'melx': melx_kn,
        'mely': mely_kn,
        'xg_orig': xg,
        'yg_orig': yg,
        'bmax': bmax,
        'hmax': hmax,
        'points': points
    }


# ==================== APLICACIÓN TKINTER ====================

class SectionDesignerApp(tk.Tk):
    def __init__(self):
        super().__init__()
        self.title("Diseñador de Secciones de Acero")
        self.geometry("1200x800")

        self.json_path = r"c:\Users\Daniel.p\Documents\Automatizaciones\Propiedades seccion\secciones_config.json"
        self.load_json()
        self.current_section_name = None
        self.current_points = None
        self.current_properties = None

        # Variables para barrido paramétrico
        self.sweep_result = None  # DataFrame de resultados
        self.sweep_pareto = None  # DataFrame con Pareto front
        self.sweep_figure = None  # Figura con gráficas
        self.sweep_canvas = None  # Canvas para mostrar gráficas

        self.create_widgets()

    def load_json(self):
        """Carga el archivo JSON de configuración"""
        if os.path.exists(self.json_path):
            with open(self.json_path, 'r') as f:
                self.secciones_data = json.load(f)
        else:
            self.secciones_data = {"secciones": []}

    def save_json(self):
        """Guarda los cambios al JSON"""
        with open(self.json_path, 'w') as f:
            json.dump(self.secciones_data, f, indent=2)

    def create_widgets(self):
        """Crea la interfaz gráfica"""

        # ========== PANEL IZQUIERDO (CONTROLES) ==========
        left_panel = ttk.Frame(self, width=250)
        left_panel.pack(side=tk.LEFT, fill=tk.BOTH, padx=10, pady=10)

        # Selector de sección
        ttk.Label(left_panel, text="Sección:", font=("Arial", 10, "bold")).pack(anchor=tk.W, pady=(10, 5))
        self.combo_seccion = ttk.Combobox(
            left_panel,
            values=[s['nombre'] for s in self.secciones_data['secciones']],
            state='readonly',
            width=20
        )
        self.combo_seccion.pack(anchor=tk.W)
        self.combo_seccion.bind('<<ComboboxSelected>>', self.on_section_changed)

        # Separador
        ttk.Separator(left_panel, orient=tk.HORIZONTAL).pack(fill=tk.X, pady=10)

        # Selector de tipo de sección
        ttk.Label(left_panel, text="Tipo de sección:", font=("Arial", 10, "bold")).pack(anchor=tk.W, pady=(10, 5))
        self.combo_tipo = ttk.Combobox(
            left_panel,
            values=["IPE", "Personalizada", "Puente nuevo", "Barrido Paramétrico"],
            state='readonly',
            width=20
        )
        self.combo_tipo.pack(anchor=tk.W, pady=(0, 10))
        self.combo_tipo.set("IPE")
        self.combo_tipo.bind('<<ComboboxSelected>>', self.on_section_type_changed)

        # Frame para parámetros IPE
        self.frame_ipe = ttk.Frame(left_panel)
        self.frame_ipe.pack(fill=tk.BOTH, expand=False, pady=(0, 10))

        ttk.Label(self.frame_ipe, text="Parámetros IPE:", font=("Arial", 10, "bold")).pack(anchor=tk.W, pady=(10, 5))

        # H (Altura)
        ttk.Label(self.frame_ipe, text="H (altura, m):").pack(anchor=tk.W)
        self.entry_H = ttk.Spinbox(self.frame_ipe, from_=0.0, to=10000.0, increment=0.01, width=15, command=self.on_parameter_change_ipe)
        self.entry_H.pack(anchor=tk.W, fill=tk.X)
        self.entry_H.set("0.300")
        self.entry_H.bind('<FocusOut>', self.on_parameter_change_ipe)
        self.entry_H.bind('<Return>', self.on_parameter_change_ipe)

        # b (Ancho)
        ttk.Label(self.frame_ipe, text="b (ancho flange, m):").pack(anchor=tk.W, pady=(10, 0))
        self.entry_b = ttk.Spinbox(self.frame_ipe, from_=0.0, to=10000.0, increment=0.01, width=15, command=self.on_parameter_change_ipe)
        self.entry_b.pack(anchor=tk.W, fill=tk.X)
        self.entry_b.set("0.150")
        self.entry_b.bind('<FocusOut>', self.on_parameter_change_ipe)
        self.entry_b.bind('<Return>', self.on_parameter_change_ipe)

        # tf (Espesor flange)
        ttk.Label(self.frame_ipe, text="tf (espesor flange, m):").pack(anchor=tk.W, pady=(10, 0))
        self.entry_tf = ttk.Spinbox(self.frame_ipe, from_=0.0, to=10000.0, increment=0.005, width=15, command=self.on_parameter_change_ipe)
        self.entry_tf.pack(anchor=tk.W, fill=tk.X)
        self.entry_tf.set("0.0107")
        self.entry_tf.bind('<FocusOut>', self.on_parameter_change_ipe)
        self.entry_tf.bind('<Return>', self.on_parameter_change_ipe)

        # tw (Espesor alma)
        ttk.Label(self.frame_ipe, text="tw (espesor alma, m):").pack(anchor=tk.W, pady=(10, 0))
        self.entry_tw = ttk.Spinbox(self.frame_ipe, from_=0.0, to=10000.0, increment=0.005, width=15, command=self.on_parameter_change_ipe)
        self.entry_tw.pack(anchor=tk.W, fill=tk.X)
        self.entry_tw.set("0.0063")
        self.entry_tw.bind('<FocusOut>', self.on_parameter_change_ipe)
        self.entry_tw.bind('<Return>', self.on_parameter_change_ipe)

        # Frame para sección personalizada
        self.frame_personalizada = ttk.Frame(left_panel)

        ttk.Label(self.frame_personalizada, text="Puntos (JSON):", font=("Arial", 10, "bold")).pack(anchor=tk.W, pady=(10, 5))
        ttk.Label(self.frame_personalizada, text="Formato: [[x1,y1], [x2,y2], ...]", font=("Arial", 8)).pack(anchor=tk.W)

        self.text_puntos = tk.Text(self.frame_personalizada, height=8, width=30, font=("Courier", 8))
        self.text_puntos.pack(fill=tk.BOTH, expand=True, pady=(0, 5))

        ttk.Button(self.frame_personalizada, text="Cargar puntos", command=self.load_custom_points).pack(fill=tk.X)

        # Frame para puente nuevo
        self.frame_puente_nuevo = ttk.Frame(left_panel)
        self.frame_puente_nuevo.pack(fill=tk.BOTH, expand=True, pady=(0, 10))

        ttk.Label(self.frame_puente_nuevo, text="Parámetros puente nuevo:", font=("Arial", 10, "bold")).pack(anchor=tk.W, pady=(10, 5))

        # H (Altura exterior)
        ttk.Label(self.frame_puente_nuevo, text = "h (altura exterior, m):").pack(anchor=tk.W, pady=(10, 0))
        self.entry_h_puente = ttk.Spinbox(self.frame_puente_nuevo, from_=0.0, to=10000.0, increment=0.01, width=15, command=self.on_parameter_change_puente_nuevo)
        self.entry_h_puente.pack(anchor=tk.W, fill=tk.X)
        self.entry_h_puente.set("0.300")
        self.entry_h_puente.bind('<FocusOut>', self.on_parameter_change_puente_nuevo)
        self.entry_h_puente.bind('<Return>', self.on_parameter_change_puente_nuevo)

        # b (Ancho)
        ttk.Label(self.frame_puente_nuevo, text="b (ancho, m):").pack(anchor=tk.W, pady=(10, 0))
        self.entry_b_puente = ttk.Spinbox(self.frame_puente_nuevo, from_=0.0, to=10000.0, increment=0.005, width=15, command=self.on_parameter_change_puente_nuevo)
        self.entry_b_puente.pack(anchor=tk.W, fill=tk.X)
        self.entry_b_puente.set("0.150")
        self.entry_b_puente.bind('<FocusOut>', self.on_parameter_change_puente_nuevo)
        self.entry_b_puente.bind('<Return>', self.on_parameter_change_puente_nuevo)

        # tf (espesor ala)
        ttk.Label(self.frame_puente_nuevo, text="tf (espesor ala, m):").pack(anchor=tk.W, pady=(10, 0))
        self.entry_tf_puente = ttk.Spinbox(self.frame_puente_nuevo, from_=0.0, to=10000.0, increment=0.0005, width=15, command=self.on_parameter_change_puente_nuevo)
        self.entry_tf_puente.pack(anchor=tk.W, fill=tk.X)
        self.entry_tf_puente.set("0.0107")
        self.entry_tf_puente.bind('<FocusOut>', self.on_parameter_change_puente_nuevo)
        self.entry_tf_puente.bind('<Return>', self.on_parameter_change_puente_nuevo)

        # tw (espesor alma)
        ttk.Label(self.frame_puente_nuevo, text="tw (espesor alma, m):").pack(anchor=tk.W, pady=(10, 0))
        self.entry_tw_puente = ttk.Spinbox(self.frame_puente_nuevo, from_=0.0, to=10000.0, increment=0.0005, width=15, command=self.on_parameter_change_puente_nuevo)
        self.entry_tw_puente.pack(anchor=tk.W, fill=tk.X)
        self.entry_tw_puente.set("0.0063")
        self.entry_tw_puente.bind('<FocusOut>', self.on_parameter_change_puente_nuevo)
        self.entry_tw_puente.bind('<Return>', self.on_parameter_change_puente_nuevo)

        # ha (altura refuerzo ala)
        ttk.Label(self.frame_puente_nuevo, text="ha (altura refuerzo ala, m):").pack(anchor=tk.W, pady=(10, 0))
        self.entry_ha_puente = ttk.Spinbox(self.frame_puente_nuevo, from_=0.0, to=10000.0, increment=0.0005, width=15, command=self.on_parameter_change_puente_nuevo)
        self.entry_ha_puente.pack(anchor=tk.W, fill=tk.X)
        self.entry_ha_puente.set("0.02")
        self.entry_ha_puente.bind('<FocusOut>', self.on_parameter_change_puente_nuevo)
        self.entry_ha_puente.bind('<Return>', self.on_parameter_change_puente_nuevo)

        # ta (espesor refuerzo ala)
        ttk.Label(self.frame_puente_nuevo, text="ta (espesor refuerzo ala, m):").pack(anchor=tk.W, pady=(10, 0))
        self.entry_ta_puente = ttk.Spinbox(self.frame_puente_nuevo, from_=0.0, to=10000.0, increment=0.0005, width=15, command=self.on_parameter_change_puente_nuevo)
        self.entry_ta_puente.pack(anchor=tk.W, fill=tk.X)
        self.entry_ta_puente.set("0.01")
        self.entry_ta_puente.bind('<FocusOut>', self.on_parameter_change_puente_nuevo)
        self.entry_ta_puente.bind('<Return>', self.on_parameter_change_puente_nuevo)

        # tr (espesor refuerzo alma)
        ttk.Label(self.frame_puente_nuevo, text="tr (espesor refuerzo alma, m):").pack(anchor=tk.W, pady=(10, 0))
        self.entry_tr_puente = ttk.Spinbox(self.frame_puente_nuevo, from_=0.0, to=10000.0, increment=0.0005, width=15, command=self.on_parameter_change_puente_nuevo)
        self.entry_tr_puente.pack(anchor=tk.W, fill=tk.X)
        self.entry_tr_puente.set("0.01")
        self.entry_tr_puente.bind('<FocusOut>', self.on_parameter_change_puente_nuevo)
        self.entry_tr_puente.bind('<Return>', self.on_parameter_change_puente_nuevo)

        # theta (ángulo refuerzo)
        ttk.Label(self.frame_puente_nuevo, text="theta (ángulo refuerzo, grados):").pack(anchor=tk.W, pady=(10, 0))
        self.entry_theta_puente = ttk.Spinbox(self.frame_puente_nuevo, from_=0.0, to=360.0, increment=1.0, width=15, command=self.on_parameter_change_puente_nuevo)
        self.entry_theta_puente.pack(anchor=tk.W, fill=tk.X)
        self.entry_theta_puente.set("45")
        self.entry_theta_puente.bind('<FocusOut>', self.on_parameter_change_puente_nuevo)
        self.entry_theta_puente.bind('<Return>', self.on_parameter_change_puente_nuevo)

        # Frame para barrido paramétrico
        self.frame_sweep_tab = ttk.Frame(left_panel)
        self.create_sweep_tab_widgets()

        # Separador
        ttk.Separator(left_panel, orient=tk.HORIZONTAL).pack(fill=tk.X, pady=10)

        # Panel de resultados
        ttk.Label(left_panel, text="Propiedades:", font=("Arial", 10, "bold")).pack(anchor=tk.W, pady=(10, 5))

        self.text_results = tk.Text(left_panel, height=20, width=30, state=tk.DISABLED, font=("Courier", 8))
        self.text_results.pack(fill=tk.BOTH, expand=True, pady=(0, 10))

        # Botón guardar
        ttk.Button(left_panel, text="Guardar nueva sección", command=self.save_new_section).pack(fill=tk.X)

        # ========== PANEL DERECHO (GRÁFICO) ==========
        right_panel = ttk.Frame(self)
        right_panel.pack(side=tk.RIGHT, fill=tk.BOTH, expand=True, padx=10, pady=10)

        self.figure = plt.Figure(figsize=(6, 8), dpi=100)
        self.ax = self.figure.add_subplot(111)

        self.canvas = FigureCanvasTkAgg(self.figure, master=right_panel)
        self.canvas.get_tk_widget().pack(fill=tk.BOTH, expand=True)

        # Cargar la primera sección después de que todos los widgets estén creados
        if self.secciones_data['secciones']:
            self.combo_seccion.current(0)
            self.on_section_changed(None)

        self.frame_puente_nuevo.pack_forget()

    def create_sweep_tab_widgets(self):
        """Crea los widgets del panel de barrido paramétrico"""
        self.frame_sweep_tab.pack(fill=tk.BOTH, expand=True, pady=(0, 10))

        # Título
        ttk.Label(self.frame_sweep_tab, text="Barrido Paramétrico", font=("Arial", 10, "bold")).pack(anchor=tk.W, pady=(10, 5))

        # Sección actual
        ttk.Label(self.frame_sweep_tab, text="Sección: IPE", font=("Arial", 9)).pack(anchor=tk.W, pady=(5, 10))

        # Frame para parámetros fijos
        frame_fixed = ttk.LabelFrame(self.frame_sweep_tab, text="Parámetros Fijos", padding=5)
        frame_fixed.pack(fill=tk.X, pady=(0, 10))

        # Variables para toggle fijo/barrido
        self.sweep_toggle_vars = {}
        self.sweep_fixed_entries = {}
        self.sweep_range_entries = {}

        params_ipe = ['H', 'b', 'tf', 'tw']
        for param in params_ipe:
            container = ttk.Frame(frame_fixed)
            container.pack(fill=tk.X, pady=2)

            var = tk.BooleanVar(value=True)
            self.sweep_toggle_vars[param] = var
            chk = ttk.Checkbutton(container, text=f"Fijo: {param}", variable=var,
                                 command=lambda p=param: self.on_sweep_toggle(p))
            chk.pack(side=tk.LEFT, fill=tk.X, expand=True)

            entry = ttk.Spinbox(container, from_=0.0, to=1000.0, increment=0.001, width=10)
            entry.pack(side=tk.LEFT, padx=5)
            self.sweep_fixed_entries[param] = entry

        # Cargar valores actuales
        self.sweep_fixed_entries['H'].set("0.300")
        self.sweep_fixed_entries['b'].set("0.150")
        self.sweep_fixed_entries['tf'].set("0.0107")
        self.sweep_fixed_entries['tw'].set("0.0063")

        # Frame para parámetros a barrer
        frame_sweep = ttk.LabelFrame(self.frame_sweep_tab, text="Parámetros a Barrer", padding=5)
        frame_sweep.pack(fill=tk.X, pady=(0, 10))

        self.sweep_range_labels = {}
        self.sweep_range_entries = {}

        for param in params_ipe:
            container = ttk.Frame(frame_sweep)
            container.pack(fill=tk.X, pady=2)

            lbl = ttk.Label(container, text=f"{param}:", width=5)
            lbl.pack(side=tk.LEFT)
            self.sweep_range_labels[param] = lbl

            ttk.Label(container, text="min:").pack(side=tk.LEFT, padx=(10, 2))
            min_entry = ttk.Spinbox(container, from_=0.0, to=1000.0, increment=0.001, width=8)
            min_entry.pack(side=tk.LEFT, padx=2)

            ttk.Label(container, text="max:").pack(side=tk.LEFT, padx=(10, 2))
            max_entry = ttk.Spinbox(container, from_=0.0, to=1000.0, increment=0.001, width=8)
            max_entry.pack(side=tk.LEFT, padx=2)

            ttk.Label(container, text="pasos:").pack(side=tk.LEFT, padx=(10, 2))
            steps_entry = ttk.Spinbox(container, from_=2, to=20, increment=1, width=8)
            steps_entry.pack(side=tk.LEFT, padx=2)
            steps_entry.set(5)

            self.sweep_range_entries[param] = {
                'min': min_entry,
                'max': max_entry,
                'steps': steps_entry
            }

        # Botones de control
        button_frame = ttk.Frame(self.frame_sweep_tab)
        button_frame.pack(fill=tk.X, pady=(10, 0))

        ttk.Button(button_frame, text="Ejecutar Barrido", command=self.execute_parametric_sweep).pack(side=tk.LEFT, padx=2)
        ttk.Button(button_frame, text="Exportar Excel", command=self.export_sweep_results).pack(side=tk.LEFT, padx=2)
        ttk.Button(button_frame, text="Limpiar", command=self.clear_sweep_results).pack(side=tk.LEFT, padx=2)

        # Ocultar frame de barrido inicialmente
        self.frame_sweep_tab.pack_forget()

    def on_sweep_toggle(self, param):
        """Alterna parámetro entre fijo y barrido"""
        is_fixed = self.sweep_toggle_vars[param].get()
        # Los rangos se mostrarán solo si no está fijo
        # Esta lógica se puede mejorar si es necesario

    def on_section_type_changed(self, event):
        """Cambia entre IPE, Personalizada, Puente nuevo y Barrido"""
        section_type = self.combo_tipo.get()

        if section_type == "IPE":
            self.frame_personalizada.pack_forget()
            self.frame_puente_nuevo.pack_forget()
            self.frame_sweep_tab.pack_forget()
            self.frame_ipe.pack(fill=tk.BOTH, expand=False, pady=(0, 10))
            self.on_parameter_change_ipe(None)
        elif section_type == "Barrido Paramétrico":
            self.frame_ipe.pack_forget()
            self.frame_personalizada.pack_forget()
            self.frame_puente_nuevo.pack_forget()
            self.frame_sweep_tab.pack(fill=tk.BOTH, expand=True, pady=(0, 10))
            # Precargar valores actuales como fijos
            if self.current_properties:
                try:
                    self.sweep_fixed_entries['H'].delete(0, tk.END)
                    self.sweep_fixed_entries['H'].insert(0, f"{float(self.entry_H.get()):.6f}")
                    self.sweep_fixed_entries['b'].delete(0, tk.END)
                    self.sweep_fixed_entries['b'].insert(0, f"{float(self.entry_b.get()):.6f}")
                    self.sweep_fixed_entries['tf'].delete(0, tk.END)
                    self.sweep_fixed_entries['tf'].insert(0, f"{float(self.entry_tf.get()):.6f}")
                    self.sweep_fixed_entries['tw'].delete(0, tk.END)
                    self.sweep_fixed_entries['tw'].insert(0, f"{float(self.entry_tw.get()):.6f}")
                except:
                    pass
        elif section_type == "Puente nuevo":
            self.frame_ipe.pack_forget()
            self.frame_personalizada.pack_forget()
            self.frame_sweep_tab.pack_forget()
            self.frame_puente_nuevo.pack(fill=tk.BOTH, expand=True, pady=(0, 10))
            self.on_parameter_change_puente_nuevo(None)
        else:  # Personalizada
            self.frame_ipe.pack_forget()
            self.frame_puente_nuevo.pack_forget()
            self.frame_sweep_tab.pack_forget()
            self.frame_personalizada.pack(fill=tk.BOTH, expand=True)
            if self.current_points is not None:
                import json
                puntos_json = json.dumps(self.current_points.tolist(), indent=2)
                self.text_puntos.delete(1.0, tk.END)
                self.text_puntos.insert(1.0, puntos_json)

    def load_custom_points(self):
        """Carga puntos personalizados desde el texto"""
        try:
            import json
            text = self.text_puntos.get(1.0, tk.END).strip()
            puntos = json.loads(text)
            self.current_points = np.array(puntos)
            self.current_properties = calculate_section_properties(self.current_points)
            self.update_plot()
            self.update_results()
            messagebox.showinfo("Éxito", "Puntos cargados correctamente")
        except json.JSONDecodeError:
            messagebox.showerror("Error", "Formato JSON inválido")
        except Exception as e:
            messagebox.showerror("Error", f"Error al cargar puntos: {str(e)}")

    def on_section_changed(self, event):
        """Se ejecuta cuando cambia la sección seleccionada"""
        idx = self.combo_seccion.current()
        if idx >= 0:
            seccion = self.secciones_data['secciones'][idx]
            self.current_section_name = seccion['nombre']
            section_type = seccion.get('tipo', 'otro')

            # Actualizar el combo de tipo
            if section_type == 'ipe':
                self.combo_tipo.set("IPE")
            elif section_type == 'puente_nuevo':
                self.combo_tipo.set("Puente nuevo")
            else:
                self.combo_tipo.set("Personalizada")

            # Si tiene puntos directos, cargarlos
            if 'puntos' in seccion:
                self.current_points = np.array(seccion['puntos'])
                if section_type == 'ipe':
                    self.current_properties = calculate_section_properties(self.current_points)
                if section_type == 'puente_nuevo':
                    self.current_properties = calculate_section_properties(self.current_points)
                self.update_plot()
                self.update_results()

                # Si además es IPE, establecer los valores
                if section_type == 'ipe' and 'parametros' in seccion:
                    params = seccion['parametros']
                    self.entry_H.delete(0, tk.END)
                    self.entry_H.insert(0, f"{params['H']:.6f}")
                    self.entry_b.delete(0, tk.END)
                    self.entry_b.insert(0, f"{params['b']:.6f}")
                    self.entry_tf.delete(0, tk.END)
                    self.entry_tf.insert(0, f"{params['tf']:.6f}")
                    self.entry_tw.delete(0, tk.END)
                    self.entry_tw.insert(0, f"{params['tw']:.6f}")
                    # Mostrar frame IPE
                    self.frame_personalizada.pack_forget()
                    self.frame_ipe.pack(fill=tk.BOTH, expand=False, pady=(0, 10))
                elif section_type == 'puente_nuevo' and 'parametros' in seccion:
                    params = seccion['parametros']
                    self.entry_h_puente.delete(0, tk.END)
                    self.entry_h_puente.insert(0, f"{params['h']:.6f}")
                    self.entry_b_puente.delete(0, tk.END)
                    self.entry_b_puente.insert(0, f"{params['b']:.6f}")
                    self.entry_tf_puente.delete(0, tk.END)
                    self.entry_tf_puente.insert(0, f"{params['tf']:.6f}")
                    self.entry_tw_puente.delete(0, tk.END)
                    self.entry_tw_puente.insert(0, f"{params['tw']:.6f}")
                    self.entry_ha_puente.delete(0, tk.END)
                    self.entry_ha_puente.insert(0, f"{params['ha']:.6f}")
                    self.entry_ta_puente.delete(0, tk.END)
                    self.entry_ta_puente.insert(0, f"{params['ta']:.6f}")
                    self.entry_tr_puente.delete(0, tk.END)
                    self.entry_tr_puente.insert(0, f"{params['tr']:.6f}")
                    self.entry_theta_puente.delete(0, tk.END)
                    self.entry_theta_puente.insert(0, f"{params['theta']:.6f}")
                    # Mostrar frame puente nuevo
                    self.frame_ipe.pack_forget()
                    self.frame_personalizada.pack_forget()
                    self.frame_puente_nuevo.pack(fill=tk.BOTH, expand=True, pady=(0, 10))
                else:
                    # Mostrar frame personalizado
                    self.frame_ipe.pack_forget()
                    self.frame_puente_nuevo.pack_forget()
                    self.frame_personalizada.pack(fill=tk.BOTH, expand=True)
                    import json
                    puntos_json = json.dumps(self.current_points.tolist(), indent=2)
                    self.text_puntos.delete(1.0, tk.END)
                    self.text_puntos.insert(1.0, puntos_json)
            # Si solo tiene parámetros IPE, generar puntos
            elif section_type == 'ipe' and 'parametros' in seccion:
                params = seccion['parametros']
                self.combo_tipo.set("IPE")
                self.entry_H.delete(0, tk.END)
                self.entry_H.insert(0, f"{params['H']:.6f}")
                self.entry_b.delete(0, tk.END)
                self.entry_b.insert(0, f"{params['b']:.6f}")
                self.entry_tf.delete(0, tk.END)
                self.entry_tf.insert(0, f"{params['tf']:.6f}")
                self.entry_tw.delete(0, tk.END)
                self.entry_tw.insert(0, f"{params['tw']:.6f}")
                self.on_parameter_change_ipe(None)
            elif section_type == 'puente_nuevo' and 'parametros' in seccion:
                params = seccion['parametros']
                self.combo_tipo.set("Puente nuevo")
                self.entry_h_puente.delete(0, tk.END)
                self.entry_h_puente.insert(0, f"{params['h']:.6f}")
                self.entry_b_puente.delete(0, tk.END)
                self.entry_b_puente.insert(0, f"{params['b']:.6f}")
                self.entry_tf_puente.delete(0, tk.END)
                self.entry_tf_puente.insert(0, f"{params['tf']:.6f}")
                self.entry_tw_puente.delete(0, tk.END)
                self.entry_tw_puente.insert(0, f"{params['tw']:.6f}")
                self.entry_ha_puente.delete(0, tk.END)
                self.entry_ha_puente.insert(0, f"{params['ha']:.6f}")
                self.entry_ta_puente.delete(0, tk.END)
                self.entry_ta_puente.insert(0, f"{params['ta']:.6f}")
                self.entry_tr_puente.delete(0, tk.END)
                self.entry_tr_puente.insert(0, f"{params['tr']:.6f}")
                self.entry_theta_puente.delete(0, tk.END)
                self.entry_theta_puente.insert(0, f"{params['theta']:.6f}")
                self.on_parameter_change_puente_nuevo(None)


    def on_parameter_change_ipe(self, event=None):
        """Se ejecuta cuando cambien los valores de entrada"""
        try:
            H = float(self.entry_H.get())
            b = float(self.entry_b.get())
            tf = float(self.entry_tf.get())
            tw = float(self.entry_tw.get())

            # Validar valores básicos
            if H <= 0 or b <= 0 or tf <= 0 or tw <= 0:
                return
            
            if tf > H/2 or tw > b:
                tf = min(tf, H/2)
                tw = min(tw, b)
                self.entry_tf.set(f"{tf:.6f}")
                self.entry_tw.set(f"{tw:.6f}")

            # Generar puntos y calcular
            self.current_points = generate_ipe_points(H, b, tf, tw)
            self.current_properties = calculate_section_properties(self.current_points)

            # Actualizar visualización
            self.update_plot()
            self.update_results()
        except ValueError:
            # Ignorar si los valores no son números válidos
            pass

    def on_parameter_change_puente_nuevo(self, event=None):
        """Se ejecuta cuando cambien los valores de entrada"""
        try:
            h = float(self.entry_h_puente.get())
            b = float(self.entry_b_puente.get())
            tf = float(self.entry_tf_puente.get())
            tw = float(self.entry_tw_puente.get())
            ha = float(self.entry_ha_puente.get())
            ta = float(self.entry_ta_puente.get())
            tr = float(self.entry_tr_puente.get())
            theta = float(self.entry_theta_puente.get())

            # Validar valores básicos
            if h <= 0 or b <= 0 or tf <= 0 or tw <= 0 or ha <= 0 or ta <= 0 or tr <= 0:
                return

            # Generar puntos y calcular
            self.current_points = generate_puente_nuevo_points(h, b, tf, tw, ha, ta, tr, theta)
            self.current_properties = calculate_section_properties(self.current_points)

            # Actualizar visualización
            self.update_plot()
            self.update_results()
        except ValueError:
            # Ignorar si los valores no son números válidos
            pass

    def update_plot(self):
        """Actualiza el gráfico de la sección"""
        self.ax.clear()

        points = self.current_points
        props = self.current_properties

        px = points[:, 0]
        py = points[:, 1]
        xg = props['xg_orig']
        yg = props['yg_orig']
        bmax = props['bmax']
        hmax = props['hmax']
        tetha = props['tetha']

        # Dibujar sección
        self.ax.plot(px, py, 'b-', linewidth=2)
        self.ax.fill(px, py, facecolor="lightblue", alpha=0.5)

        # Centro de gravedad
        self.ax.plot(xg, yg, 'ro', markersize=8, label='CDG')

        # Ejes principales
        tt = min(bmax, hmax)
        self.ax.arrow(xg, yg, tt/3, 0, head_width=tt/30, head_length=tt/30, fc='green', ec='green')
        self.ax.arrow(xg, yg, 0, tt/3, head_width=tt/30, head_length=tt/30, fc='green', ec='green')

        tet = tetha * (math.pi) / 180
        self.ax.arrow(xg, yg, tt/3*math.cos(tet), tt/3*math.sin(tet), head_width=tt/30, head_length=tt/30, fc='red', ec='red')
        self.ax.arrow(xg, yg, -tt/3*math.sin(tet), tt/3*math.cos(tet), head_width=tt/30, head_length=tt/30, fc='red', ec='red')

        self.ax.set_aspect('equal')
        self.ax.grid(True, alpha=0.3)
        self.ax.set_xlabel('X (m)', fontweight='bold')
        self.ax.set_ylabel('Y (m)', fontweight='bold')
        self.ax.set_title(self.current_section_name or 'Sección IPE', fontweight='bold')
        self.ax.legend()

        self.figure.tight_layout()
        self.canvas.draw()

    def update_results(self):
        """Actualiza el panel de resultados"""
        props = self.current_properties

        results_text = f"""PROPIEDADES CALCULADAS

GEOMÉTRICAS:
 Área: {props['area']:.2f} cm²
 Perímetro: {props['perimetro']:.2f} cm
 b_max: {props['bmax']*100:.2f} cm
 h_max: {props['hmax']*100:.2f} cm

CDG (ref. origen):
 X: {props['xg']:.2f} cm
 Y: {props['yg']:.2f} cm

INERCIA (eje Xg-Yg):
 Ixg: {props['ixg']:.2f} cm⁴
 Iyg: {props['iyg']:.2f} cm⁴
 Pxyg: {props['pxyg']:.2f} cm⁴
 rx: {props['rx']:.2f} cm
 ry: {props['ry']:.2f} cm

INERCIA (ejes principales):
 Imax: {props['imax']:.2f} cm⁴
 Imin: {props['imin']:.2f} cm⁴
 θ: {props['tetha']:.2f}°
 rmax: {props['rmax']:.2f} cm
 rmin: {props['rmin']:.2f} cm

MÓDULO RESISTENTE:
 Wel1x: {props['wel1x']:.2f} cm³
 Wel2x: {props['wel2x']:.2f} cm³
 Wel1y: {props['wel1y']:.2f} cm³
 Wel2y: {props['wel2y']:.2f} cm³

MECÁNICAS:
 Peso: {props['peso']:.2f} kg/m
 Nel: {props['nel']:.2f} kN
 Melx: {props['melx']:.2f} kN·m
 Mely: {props['mely']:.2f} kN·m
"""

        self.text_results.config(state=tk.NORMAL)
        self.text_results.delete(1.0, tk.END)
        self.text_results.insert(1.0, results_text)
        self.text_results.config(state=tk.DISABLED)

    def save_new_section(self):
        """Guarda la sección actual en el JSON"""
        if not self.current_section_name or self.current_points is None:
            messagebox.showerror("Error", "Selecciona una sección primero")
            return

        # Preguntar nombre de la nueva sección
        new_name = tk.simpledialog.askstring("Guardar sección", "Nombre de la nueva sección:")
        if not new_name:
            return

        # Verificar si ya existe
        exists = any(s['nombre'] == new_name for s in self.secciones_data['secciones'])
        if exists:
            should_overwrite = messagebox.askyesno("Ya existe", f"La sección '{new_name}' ya existe. ¿Sobreescribir?")
            if not should_overwrite:
                return

        # Detectar tipo de sección
        section_type = self.combo_tipo.get().lower()
        if section_type == "ipe":
            section_type = "ipe"
        elif section_type == "puente nuevo":
            section_type = "puente_nuevo"
        else:
            section_type = "otro"

        # Crear nueva sección con puntos
        new_section = {
            'nombre': new_name,
            'tipo': section_type,
            'puntos': self.current_points.tolist()  # Guardar los puntos actuales
        }

        # Si es IPE, guardar también los parámetros
        if section_type == "ipe":
            try:
                H = float(self.entry_H.get())
                b = float(self.entry_b.get())
                tf = float(self.entry_tf.get())
                tw = float(self.entry_tw.get())

                if H > 0 and b > 0 and tf > 0 and tw > 0:  # Validar que sean mayores a 0
                    new_section['parametros'] = {
                        'H': H,
                        'b': b,
                        'tf': tf,
                        'tw': tw
                    }
            except:
                pass  # Si no se pueden leer, solo guardamos puntos
        elif section_type == "puente_nuevo":
            try:
                h = float(self.entry_h_puente.get())
                b = float(self.entry_b_puente.get())
                tf = float(self.entry_tf_puente.get())
                tw = float(self.entry_tw_puente.get())
                ha = float(self.entry_ha_puente.get())
                ta = float(self.entry_ta_puente.get())
                tr = float(self.entry_tr_puente.get())
                theta = float(self.entry_theta_puente.get())

                if all(v > 0 for v in [h, b, tf, tw, ha, ta, tr]) and 0 <= theta <= 360:
                    new_section['parametros'] = {
                        'h': h,
                        'b': b,
                        'tf': tf,
                        'tw': tw,
                        'ha': ha,
                        'ta': ta,
                        'tr': tr,
                        'theta': theta
                    }
            except:
                pass  # Si no se pueden leer, solo guardamos puntos

        # Eliminar si existe
        self.secciones_data['secciones'] = [s for s in self.secciones_data['secciones'] if s['nombre'] != new_name]
        self.secciones_data['secciones'].append(new_section)

        self.save_json()

        # Actualizar combo
        self.combo_seccion['values'] = [s['nombre'] for s in self.secciones_data['secciones']]
        self.combo_seccion.set(new_name)

        messagebox.showinfo("Éxito", f"Sección '{new_name}' guardada correctamente")


    # ==================== MÉTODOS DE BARRIDO PARAMÉTRICO ====================

    def execute_parametric_sweep(self):
        """Ejecuta el barrido paramétrico"""
        try:
            # Recopilar configuración
            fixed_params = {}
            sweep_configs = {}

            for param in ['H', 'b', 'tf', 'tw']:
                is_fixed = self.sweep_toggle_vars[param].get()

                if is_fixed:
                    value = float(self.sweep_fixed_entries[param].get())
                    fixed_params[param] = value
                else:
                    min_val = float(self.sweep_range_entries[param]['min'].get())
                    max_val = float(self.sweep_range_entries[param]['max'].get())
                    steps = int(self.sweep_range_entries[param]['steps'].get())

                    if min_val >= max_val:
                        messagebox.showerror("Error", f"Rango inválido para {param}: min >= max")
                        return

                    sweep_configs[param] = {
                        'min': min_val,
                        'max': max_val,
                        'steps': steps
                    }

            if not sweep_configs:
                messagebox.showerror("Error", "Debe barrer al menos un parámetro")
                return

            if not fixed_params:
                messagebox.showwarning("Advertencia", "No hay parámetros fijos")

            # Ejecutar barrido
            messagebox.showinfo("Info", "Ejecutando barrido... Esto puede tomar unos segundos")

            sweep = ParametricSweep("ipe", fixed_params, sweep_configs)
            self.sweep_result = sweep.execute_sweep()
            self.sweep_pareto = sweep.find_pareto_front()

            # Calcular combinaciones teóricas vs válidas
            total_combos = 1
            for config in sweep_configs.values():
                total_combos *= config['steps']

            valid_combos = len(self.sweep_result)
            invalid_combos = total_combos - valid_combos

            msg = f"✓ Barrido completado:\n"
            msg += f"  Combinaciones teóricas: {total_combos}\n"
            msg += f"  Combinaciones válidas: {valid_combos}\n"
            if invalid_combos > 0:
                msg += f"  Rechazadas (geometría): {invalid_combos}\n"
            msg += f"\n✓ Soluciones ordenadas: {len(self.sweep_pareto)}"

            messagebox.showinfo("Éxito", msg)

            # Mostrar resultados
            self.show_sweep_results()

        except Exception as e:
            messagebox.showerror("Error", f"Error en barrido: {str(e)}")

    def show_sweep_results(self):
        """Muestra los resultados del barrido ordenados por eficiencia"""
        if self.sweep_result is None or self.sweep_pareto is None:
            messagebox.showerror("Error", "Ejecute primero el barrido")
            return

        try:
            # Crear figura con dos subplots
            if self.sweep_figure is not None:
                plt.close(self.sweep_figure)

            self.sweep_figure = plt.Figure(figsize=(14, 6), dpi=100)

            # Subplot 1: Visualización de soluciones
            ax1 = self.sweep_figure.add_subplot(121)

            # Obtener parámetros barridos
            sweep_params = [p for p in ['H', 'b', 'tf', 'tw'] if p in self.sweep_result.columns and self.sweep_result[p].nunique() > 1]

            scatter = None  # Variable para almacenar scatter si existe
            if len(sweep_params) >= 2:
                # Scatter plot: Peso vs Eficiencia
                scatter = ax1.scatter(self.sweep_result['peso'],
                                     self.sweep_result['eficiencia'],
                                     c=self.sweep_result['ixg'],
                                     cmap='viridis', s=100, alpha=0.6, edgecolors='black')

                # Marcar el más eficiente con un símbolo especial (diamante dorado)
                most_efficient = self.sweep_pareto.iloc[0]
                ax1.scatter(most_efficient['peso'], most_efficient['eficiencia'],
                           marker='D', s=400, c='gold', edgecolors='orange',
                           linewidth=2.5, label='Óptimo', zorder=6)

                self.sweep_figure.colorbar(scatter, ax=ax1, label='Ix (cm⁴)')
                ax1.set_xlabel('Peso (kg/m)', fontweight='bold', fontsize=11)
                ax1.set_ylabel('Eficiencia = (Ix+Iy)/Peso*1000', fontweight='bold', fontsize=11)
                ax1.set_title('Soluciones: Peso vs Eficiencia\n(Dorado = Óptimo)', fontweight='bold')
                ax1.grid(True, alpha=0.3)
                ax1.legend(loc='best')

            elif len(sweep_params) == 1:
                # Plot simple si solo 1 parámetro
                ax1.plot(self.sweep_result[sweep_params[0]], self.sweep_result['eficiencia'], 'b.-', label='Todas', linewidth=2, markersize=8)
                top5 = self.sweep_pareto.head(5)
                ax1.plot(top5[sweep_params[0]], top5['eficiencia'], 'r*', markersize=20, label='Top 5', zorder=5)

                # Marcar el más eficiente
                most_efficient = self.sweep_pareto.iloc[0]
                ax1.plot(most_efficient[sweep_params[0]], most_efficient['eficiencia'], 'D',
                        color='gold', markersize=5, markeredgecolor='orange', markeredgewidth=2,
                        label='Más Eficiente', zorder=6)

                ax1.set_xlabel(f'{sweep_params[0]} (m)', fontweight='bold', fontsize=11)
                ax1.set_ylabel('Eficiencia', fontweight='bold', fontsize=11)
                ax1.set_title(f'Eficiencia vs {sweep_params[0]}', fontweight='bold')
                ax1.legend()
                ax1.grid(True, alpha=0.3)

            # Subplot 2: Tabla top 10
            ax2 = self.sweep_figure.add_subplot(122)
            ax2.axis('tight')
            ax2.axis('off')

            # Preparar datos para tabla
            cols_to_show = [c for c in ['H', 'b', 'tf', 'tw', 'peso', 'ixg', 'iyg', 'eficiencia'] if c in self.sweep_pareto.columns]
            top_pareto = self.sweep_pareto.head(10)[cols_to_show].copy()

            # Formatear números
            table_data = []
            for rank, (_, row) in enumerate(top_pareto.iterrows(), 1):
                formatted_row = [str(rank)]  # Ranking
                for col in cols_to_show:
                    val = row[col]
                    if col in ['H', 'b', 'tf', 'tw']:
                        formatted_row.append(f"{val:.4f}")
                    else:
                        formatted_row.append(f"{val:.2f}")
                table_data.append(formatted_row)

            col_labels = ['#'] + list(cols_to_show)
            table = ax2.table(cellText=table_data, colLabels=col_labels,
                            cellLoc='center', loc='center', bbox=[0, 0, 1, 1])
            table.auto_set_font_size(False)
            table.set_fontsize(8)
            table.scale(1, 1.8)

            # Colorear header
            for i in range(len(col_labels)):
                table[(0, i)].set_facecolor('#40466e')
                table[(0, i)].set_text_props(weight='bold', color='white')

            # Colorear filas alternadas
            for i in range(1, len(table_data) + 1):
                color = '#f0f0f0' if i % 2 == 0 else 'white'
                for j in range(len(col_labels)):
                    table[(i, j)].set_facecolor(color)

            ax2.set_title('Top 10 Soluciones\n(Ordenadas por Eficiencia)', fontweight='bold', fontsize=11, pad=20)

            self.sweep_figure.tight_layout()

            # Mostrar en canvas
            if self.sweep_canvas is not None:
                self.sweep_canvas.get_tk_widget().destroy()

            right_panel = self.canvas.get_tk_widget().master
            self.sweep_canvas = FigureCanvasTkAgg(self.sweep_figure, master=right_panel)
            self.sweep_canvas.get_tk_widget().pack(fill=tk.BOTH, expand=True)
            self.sweep_canvas.draw()

            # Agregar interactividad con hover DESPUÉS de crear el canvas
            if scatter is not None:
                self._setup_scatter_hover(ax1, scatter)

        except Exception as e:
            messagebox.showerror("Error", f"Error mostrando resultados: {str(e)}")
            import traceback
            traceback.print_exc()

    def _setup_scatter_hover(self, ax, scatter):
        """Configura interactividad con hover para mostrar valores de puntos"""
        # Almacenar referencias a los datos
        self.scatter_ax = ax
        self.scatter_data = self.sweep_result.copy()
        self.hover_annotation = None

        def on_hover(event):
            """Muestra información cuando el mouse pasa sobre puntos del scatter"""
            if event.inaxes != self.scatter_ax or len(self.scatter_data) == 0:
                if self.hover_annotation is not None:
                    self.hover_annotation.remove()
                    self.hover_annotation = None
                self.sweep_canvas.draw_idle()
                return

            # Calcular distancias a todos los puntos
            x, y = event.xdata, event.ydata
            if x is None or y is None:
                return

            # Distancias en coordenadas de display para mejor detección
            try:
                peso_data = self.scatter_data['peso'].values
                eficiencia_data = self.scatter_data['eficiencia'].values

                # Convertir coordenadas de datos a display
                x_display, y_display = self.scatter_ax.transData.transform((x, y))
                puntos_display = self.scatter_ax.transData.transform(
                    np.c_[peso_data, eficiencia_data]
                )

                # Calcular distancia en píxeles
                dist = np.sqrt((puntos_display[:, 0] - x_display)**2 +
                             (puntos_display[:, 1] - y_display)**2)

                # Umbral de 20 píxeles para detectar hover
                idx_cercano = np.argmin(dist)
                if dist[idx_cercano] < 20:
                    # Obtener datos del punto
                    row = self.scatter_data.iloc[idx_cercano]

                    # Construir texto con información
                    texto = f"Peso: {row['peso']:.2f} kg/m\n"
                    texto += f"Eficiencia: {row['eficiencia']:.2f}\n"

                    # Agregar parámetros que varían
                    for param in ['H', 'b', 'tf', 'tw']:
                        if param in self.scatter_data.columns and self.scatter_data[param].nunique() > 1:
                            texto += f"{param}: {row[param]:.4f}\n"

                    texto += f"Ix: {row['ixg']:.0f} cm⁴\n"
                    texto += f"Iy: {row['iyg']:.0f} cm⁴"

                    # Crear o actualizar anotación
                    if self.hover_annotation is not None:
                        self.hover_annotation.remove()

                    self.hover_annotation = self.scatter_ax.annotate(
                        texto,
                        xy=(row['peso'], row['eficiencia']),
                        xytext=(10, 10),
                        textcoords='offset points',
                        bbox=dict(boxstyle='round,pad=0.5', facecolor='yellow', alpha=0.7),
                        fontsize=8,
                        arrowprops=dict(arrowstyle='->', connectionstyle='arc3,rad=0', color='black')
                    )
                    self.sweep_canvas.draw_idle()
                else:
                    # Mover el mouse lejos de los puntos
                    if self.hover_annotation is not None:
                        self.hover_annotation.remove()
                        self.hover_annotation = None
                        self.sweep_canvas.draw_idle()
            except Exception:
                pass

        # Conectar evento de movimiento del mouse
        self.sweep_canvas.mpl_connect('motion_notify_event', on_hover)

    def export_sweep_results(self):
        """Exporta resultados del barrido a Excel"""
        if self.sweep_result is None:
            messagebox.showerror("Error", "No hay resultados para exportar. Ejecute primero el barrido")
            return

        try:
            # Pedir ruta de guardado
            filename = filedialog.asksaveasfilename(
                defaultextension=".xlsx",
                filetypes=[("Excel files", "*.xlsx"), ("All files", "*.*")]
            )

            if not filename:
                return

            # Crear archivo Excel con pandas
            with pd.ExcelWriter(filename, engine='openpyxl') as writer:
                # Hoja 1: Todos los resultados
                self.sweep_result.to_excel(writer, sheet_name='Resultados', index=False)

                # Hoja 2: Pareto front
                if self.sweep_pareto is not None:
                    self.sweep_pareto.to_excel(writer, sheet_name='Pareto Front', index=False)

            messagebox.showinfo("Éxito", f"Resultados exportados a:\n{filename}")

        except Exception as e:
            messagebox.showerror("Error", f"Error exportando: {str(e)}")

    def clear_sweep_results(self):
        """Limpia los resultados del barrido"""
        self.sweep_result = None
        self.sweep_pareto = None

        if self.sweep_canvas is not None:
            self.sweep_canvas.get_tk_widget().destroy()
            self.sweep_canvas = None

        # Volver a gráfico normal
        self.ax.clear()
        self.canvas.draw()

        messagebox.showinfo("Info", "Resultados de barrido limpiados")


if __name__ == "__main__":
    app = SectionDesignerApp()
    app.mainloop()
