625 lines
21 KiB
Python
625 lines
21 KiB
Python
#!/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 = [
|
|
'tl_bracket_parser.py',
|
|
'hybrid_base.py',
|
|
'ip4_type.py',
|
|
'hex_type.py',
|
|
'bin_type.py',
|
|
'dec_type.py',
|
|
'chr_type.py',
|
|
'main_evaluation.py',
|
|
'tl_popup.py',
|
|
'main_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 main_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)
|