#!/usr/bin/env python3 """ Launcher principal para Calculadora MAV - CAS Híbrido Este script maneja la inicialización y ejecución de la aplicación """ import sys import os import subprocess import tkinter as tk from tkinter import messagebox from pathlib import Path import importlib.util import logging import datetime import traceback import platform import platform def setup_logging(): """Configura el sistema de logging completo""" log_dir = Path("logs") log_dir.mkdir(exist_ok=True) # Archivo de log con timestamp timestamp = datetime.datetime.now().strftime("%Y%m%d_%H%M%S") log_file = log_dir / f"mav_calc_{timestamp}.log" # Configurar logging logging.basicConfig( level=logging.DEBUG, format='%(asctime)s - %(levelname)s - %(funcName)s:%(lineno)d - %(message)s', handlers=[ logging.FileHandler(log_file, encoding='utf-8'), logging.StreamHandler(sys.stdout) ] ) logger = logging.getLogger(__name__) # Log información del sistema logger.info("=" * 60) logger.info("CALCULADORA MAV - CAS HÍBRIDO - INICIO DE SESIÓN") logger.info("=" * 60) logger.info(f"Timestamp: {datetime.datetime.now()}") logger.info(f"Python: {sys.version}") logger.info(f"Plataforma: {platform.platform()}") logger.info(f"Directorio de trabajo: {Path.cwd()}") logger.info(f"Argumentos: {sys.argv}") logger.info(f"Archivo de log: {log_file}") logger.info("-" * 60) return logger, log_file def log_system_info(logger): """Registra información detallada del sistema""" try: logger.info("INFORMACIÓN DEL SISTEMA:") logger.info(f" OS: {platform.system()} {platform.release()}") logger.info(f" Arquitectura: {platform.machine()}") logger.info(f" Procesador: {platform.processor()}") logger.info(f" Python executable: {sys.executable}") logger.info(f" Python path: {sys.path[:3]}...") # Solo primeros 3 elementos # Información de memoria si está disponible try: import psutil memory = psutil.virtual_memory() logger.info(f" RAM total: {memory.total // (1024**3)} GB") logger.info(f" RAM disponible: {memory.available // (1024**3)} GB") except ImportError: logger.info(" Información de memoria: No disponible (psutil no instalado)") except Exception as e: logger.error(f"Error obteniendo información del sistema: {e}") def log_error_with_context(logger, error, context=""): """Registra un error con contexto completo""" logger.error("=" * 50) logger.error("ERROR DETECTADO") logger.error("=" * 50) if context: logger.error(f"Contexto: {context}") logger.error(f"Tipo de error: {type(error).__name__}") logger.error(f"Mensaje: {str(error)}") logger.error("Traceback completo:") logger.error(traceback.format_exc()) logger.error("Variables locales en el momento del error:") # Intentar capturar variables locales del frame donde ocurrió el error try: tb = traceback.extract_tb(error.__traceback__) if tb: last_frame = tb[-1] logger.error(f" Archivo: {last_frame.filename}") logger.error(f" Línea: {last_frame.lineno}") logger.error(f" Función: {last_frame.name}") logger.error(f" Código: {last_frame.line}") except Exception as frame_error: logger.error(f"No se pudieron obtener detalles del frame: {frame_error}") logger.error("=" * 50) def show_error_with_log_info(error, log_file, context=""): """Muestra error al usuario con información del log""" error_msg = f"""Error en Calculadora MAV: {context} Error: {type(error).__name__}: {str(error)} INFORMACIÓN DE DEBUGGING: • Log completo guardado en: {log_file} • Para soporte, enviar el archivo de log • Timestamp: {datetime.datetime.now()} ¿Qué hacer ahora? 1. Revisar el archivo de log para más detalles 2. Intentar reiniciar la aplicación 3. Verificar dependencias con: python launcher.py --setup 4. Ejecutar tests con: python launcher.py --test """ try: root = tk.Tk() root.withdraw() # Crear ventana de error personalizada error_window = tk.Toplevel(root) error_window.title("Error - Calculadora MAV") error_window.geometry("600x400") error_window.configure(bg="#2b2b2b") # Hacer la ventana modal error_window.transient(root) error_window.grab_set() # Centrar ventana error_window.update_idletasks() x = (error_window.winfo_screenwidth() // 2) - (error_window.winfo_width() // 2) y = (error_window.winfo_screenheight() // 2) - (error_window.winfo_height() // 2) error_window.geometry(f"+{x}+{y}") # Contenido from tkinter import scrolledtext text_widget = scrolledtext.ScrolledText( error_window, font=("Consolas", 10), bg="#1e1e1e", fg="#ff6b6b", wrap=tk.WORD ) text_widget.pack(fill=tk.BOTH, expand=True, padx=10, pady=10) text_widget.insert("1.0", error_msg) text_widget.config(state="disabled") # Botones button_frame = tk.Frame(error_window, bg="#2b2b2b") button_frame.pack(fill=tk.X, padx=10, pady=5) def open_log_folder(): try: if platform.system() == "Windows": os.startfile(log_file.parent) elif platform.system() == "Darwin": # macOS subprocess.run(["open", str(log_file.parent)]) else: # Linux subprocess.run(["xdg-open", str(log_file.parent)]) except Exception: pass def copy_log_path(): error_window.clipboard_clear() error_window.clipboard_append(str(log_file)) tk.Button( button_frame, text="Abrir Carpeta de Logs", command=open_log_folder, bg="#4fc3f7", fg="white" ).pack(side=tk.LEFT, padx=5) tk.Button( button_frame, text="Copiar Ruta del Log", command=copy_log_path, bg="#82aaff", fg="white" ).pack(side=tk.LEFT, padx=5) tk.Button( button_frame, text="Cerrar", command=error_window.destroy, bg="#ff6b6b", fg="white" ).pack(side=tk.RIGHT, padx=5) # Esperar a que se cierre la ventana error_window.wait_window() root.destroy() except Exception as gui_error: # Si falla la GUI, mostrar en consola print("ERROR: No se pudo mostrar ventana de error") print(error_msg) print(f"Error adicional: {gui_error}") # Variable global para el logger logger = None log_file = None def check_dependencies(): """Verifica que todas las dependencias estén disponibles""" logger.info("Verificando dependencias...") required_modules = { 'sympy': 'SymPy (motor algebraico)', 'matplotlib': 'Matplotlib (plotting)', 'numpy': 'NumPy (cálculos numéricos)', 'tkinter': 'Tkinter (interfaz gráfica)' } missing_modules = [] for module, description in required_modules.items(): try: if module == 'tkinter': import tkinter logger.info(f"✅ {module} - {description}") else: importlib.import_module(module) logger.info(f"✅ {module} - {description}") except ImportError as e: missing_modules.append((module, description)) logger.error(f"❌ {module} - {description} - Error: {e}") if missing_modules: logger.warning(f"Módulos faltantes: {[m[0] for m in missing_modules]}") else: logger.info("Todas las dependencias están disponibles") return missing_modules def install_missing_dependencies(missing_modules): """Intenta instalar dependencias faltantes""" logger.info("Intentando instalar dependencias faltantes...") installable_modules = [ ('sympy', 'sympy>=1.12'), ('matplotlib', 'matplotlib>=3.7.0'), ('numpy', 'numpy>=1.24.0') ] modules_to_install = [] for module_name, _ in missing_modules: for inst_name, inst_package in installable_modules: if module_name == inst_name: modules_to_install.append(inst_package) break if not modules_to_install: logger.info("No hay módulos para instalar automáticamente") return True logger.info(f"Instalando: {', '.join(modules_to_install)}") try: cmd = [sys.executable, "-m", "pip", "install"] + modules_to_install logger.info(f"Ejecutando comando: {' '.join(cmd)}") result = subprocess.run( cmd, capture_output=True, text=True, timeout=300 # 5 minutos timeout ) logger.info(f"Código de salida pip: {result.returncode}") logger.info(f"Stdout pip: {result.stdout}") if result.stderr: logger.warning(f"Stderr pip: {result.stderr}") return result.returncode == 0 except subprocess.TimeoutExpired: logger.error("Timeout durante instalación de dependencias") return False except Exception as e: logger.error(f"Error durante instalación: {e}") log_error_with_context(logger, e, "Instalación de dependencias") return False def show_dependency_error(missing_modules): """Muestra error de dependencias faltantes""" error_msg = "Dependencias faltantes:\n\n" for module, description in missing_modules: error_msg += f"• {module}: {description}\n" error_msg += "\nPara instalar dependencias:\n" error_msg += "pip install sympy matplotlib numpy\n\n" if any(module == 'tkinter' for module, _ in missing_modules): error_msg += "Para tkinter en Linux:\n" error_msg += "sudo apt-get install python3-tk\n" error_msg += "\nO ejecute: python launcher.py --setup" logger.error("DEPENDENCIAS FALTANTES:") for module, description in missing_modules: logger.error(f" • {module}: {description}") show_error_with_log_info( Exception("Dependencias faltantes"), log_file, "Faltan dependencias requeridas para ejecutar la aplicación" ) def check_app_files(): """Verifica que todos los archivos de la aplicación estén presentes""" logger.info("Verificando archivos de la aplicación...") required_files = [ 'bracket_parser.py', 'hybrid_base_types.py', 'hybrid_evaluation_engine.py', 'interactive_results.py', 'hybrid_calc_app.py' ] current_dir = Path(__file__).parent missing_files = [] for filename in required_files: file_path = current_dir / filename if file_path.exists(): logger.info(f"✅ {filename} - Tamaño: {file_path.stat().st_size} bytes") else: missing_files.append(filename) logger.error(f"❌ {filename} - No encontrado") if missing_files: logger.error(f"Archivos faltantes: {missing_files}") else: logger.info("Todos los archivos de la aplicación están presentes") return missing_files def show_file_error(missing_files): """Muestra error de archivos faltantes""" error_msg = "Archivos de aplicación faltantes:\n\n" for filename in missing_files: error_msg += f"• {filename}\n" error_msg += "\nAsegúrese de tener todos los archivos del proyecto." logger.error("ARCHIVOS FALTANTES:") for filename in missing_files: logger.error(f" • {filename}") show_error_with_log_info( Exception("Archivos faltantes"), log_file, "Faltan archivos necesarios para la aplicación" ) def launch_application(): """Lanza la aplicación principal""" logger.info("Iniciando aplicación principal...") try: # Importar y ejecutar la aplicación logger.info("Importando módulo principal...") from hybrid_calc_app import HybridCalculatorApp logger.info("Creando ventana principal...") root = tk.Tk() logger.info("Inicializando aplicación...") app = HybridCalculatorApp(root) logger.info("✅ Calculadora MAV - CAS Híbrido iniciada correctamente") logger.info("Iniciando loop principal de tkinter...") root.mainloop() logger.info("Aplicación cerrada normalmente") except ImportError as e: logger.error(f"Error de importación: {e}") log_error_with_context(logger, e, "Importación de módulos de la aplicación") show_error_with_log_info(e, log_file, "Error importando módulos de la aplicación") except Exception as e: logger.error(f"Error durante ejecución de la aplicación: {e}") log_error_with_context(logger, e, "Ejecución de la aplicación principal") show_error_with_log_info(e, log_file, "Error durante la ejecución de la aplicación") def main(): """Función principal del launcher""" global logger, log_file # Configurar logging al inicio try: logger, log_file = setup_logging() log_system_info(logger) except Exception as e: print(f"ERROR: No se pudo configurar logging: {e}") # Continuar sin logging si falla logger = None log_file = None logger.info("Iniciando verificaciones del sistema...") try: # Verificar archivos de aplicación logger.info("Verificando archivos de aplicación...") missing_files = check_app_files() if missing_files: logger.error("❌ Archivos faltantes detectados") show_file_error(missing_files) logger.info("Cerrando debido a archivos faltantes") sys.exit(1) logger.info("✅ Todos los archivos están presentes") # Verificar dependencias logger.info("Verificando dependencias...") missing_deps = check_dependencies() if missing_deps: logger.warning("❌ Dependencias faltantes detectadas") # Intentar instalación automática para módulos pip installable_deps = [ (module, desc) for module, desc in missing_deps if module in ['sympy', 'matplotlib', 'numpy'] ] if installable_deps: logger.info("Preguntando al usuario sobre instalación automática...") response = input("¿Instalar dependencias automáticamente? (s/n): ").lower().strip() logger.info(f"Respuesta del usuario: {response}") if response in ['s', 'si', 'y', 'yes']: logger.info("Iniciando instalación automática...") if install_missing_dependencies(installable_deps): logger.info("✅ Dependencias instaladas correctamente") # Re-verificar después de instalación missing_deps = check_dependencies() else: logger.error("❌ Error durante instalación automática") else: logger.info("Usuario declinó instalación automática") if missing_deps: logger.error("Dependencias aún faltantes, mostrando error al usuario") show_dependency_error(missing_deps) logger.info("Cerrando debido a dependencias faltantes") sys.exit(1) logger.info("✅ Todas las dependencias disponibles") # Splash screen eliminado logger.info("Splash screen ha sido eliminado.") # Lanzar aplicación logger.info("Lanzando aplicación principal...") launch_application() logger.info("Aplicación cerrada - fin de sesión") logger.info("=" * 60) except KeyboardInterrupt: logger.info("Aplicación interrumpida por el usuario (Ctrl+C)") sys.exit(0) except Exception as e: logger.error("Error crítico en main():") log_error_with_context(logger, e, "Función principal del launcher") show_error_with_log_info(e, log_file, "Error crítico durante el inicio") sys.exit(1) def show_help(): """Muestra ayuda del launcher""" help_text = """ Calculadora MAV - CAS Híbrido Launcher Uso: python launcher.py [opciones] Opciones: --help Muestra esta ayuda --no-splash Inicia sin splash screen --test Ejecuta tests antes de iniciar --setup Ejecuta setup de dependencias --debug Activa logging detallado Descripción: Sistema de álgebra computacional híbrido que combina SymPy con clases especializadas para networking, programación y cálculos numéricos. Dependencias: - Python 3.8+ - SymPy (motor algebraico) - Matplotlib (plotting) - NumPy (cálculos numéricos) - Tkinter (interfaz gráfica) Logging: - Los logs se guardan automáticamente en: ./logs/ - Cada ejecución genera un archivo con timestamp - En caso de error, se muestra la ubicación del log - Los logs incluyen información del sistema y debugging Resolución de problemas: 1. Revisar logs en ./logs/ para errores detallados 2. Ejecutar: python launcher.py --test 3. Verificar dependencias: python launcher.py --setup 4. Modo debug: python launcher.py --debug Para más información, consulte la documentación. """ print(help_text) if __name__ == "__main__": # Configurar logging básico para manejo de argumentos temp_logger = None temp_log_file = None try: temp_logger, temp_log_file = setup_logging() temp_logger.info("Procesando argumentos de línea de comandos...") except: pass # Si falla el logging, continuar sin él # Manejar argumentos de línea de comandos if "--help" in sys.argv or "-h" in sys.argv: if temp_logger: temp_logger.info("Mostrando ayuda") show_help() sys.exit(0) if "--test" in sys.argv: if temp_logger: temp_logger.info("Ejecutando tests...") try: from test_suite import run_all_tests if not run_all_tests(): error_msg = "❌ Tests fallaron - no se iniciará la aplicación" print(error_msg) if temp_logger: temp_logger.error(error_msg) sys.exit(1) success_msg = "✅ Tests pasaron - iniciando aplicación" print(success_msg) if temp_logger: temp_logger.info(success_msg) except ImportError as e: warning_msg = "⚠️ Tests no disponibles - continuando con inicio" print(warning_msg) if temp_logger: temp_logger.warning(f"{warning_msg} - Error: {e}") except Exception as e: if temp_logger: log_error_with_context(temp_logger, e, "Ejecución de tests") print(f"❌ Error ejecutando tests: {e}") sys.exit(1) if "--setup" in sys.argv: if temp_logger: temp_logger.info("Ejecutando setup...") try: import setup_script setup_script.main() sys.exit(0) except ImportError: error_msg = "❌ Setup script no disponible" print(error_msg) if temp_logger: temp_logger.error(error_msg) sys.exit(1) except Exception as e: if temp_logger: log_error_with_context(temp_logger, e, "Ejecución de setup") print(f"❌ Error en setup: {e}") sys.exit(1) # Logging adicional para debug if "--debug" in sys.argv: if temp_logger: temp_logger.setLevel(logging.DEBUG) temp_logger.info("Modo debug activado") # Inicio normal try: main() except Exception as e: if temp_logger: log_error_with_context(temp_logger, e, "Error crítico en __main__") print(f"ERROR CRÍTICO: {e}") if temp_log_file: print(f"Ver log completo en: {temp_log_file}") sys.exit(1)