Calc/main_calc.py

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)