Refactorizacion de nombres de scripts

This commit is contained in:
Miguel 2025-06-02 17:04:34 +02:00
parent e44cc3af8f
commit bc768e9ca7
17 changed files with 504 additions and 710 deletions

View File

@ -12,7 +12,7 @@ Este documento describe el estado actual y los objetivos de desarrollo para la *
- La interfaz de usuario se divide en 2 columnas, a la izquierda el area de ingreso de datos y equaciones a ser evaluadas, a la derecha el area de resultados que se colorean segun cada tipo de respuesta.
### 2. **Sintaxis Simplificada con Corchetes (Única)**
- Usar **exclusivamente** `Class[args]` en lugar de `Class("args")`
- Usar **exclusivamente** `Class[args]` en lugar de `Class("args")` . Cuando se usan los corchetes los parametros se separan por `;`
- Los corchetes indican parsing especial (agregar comillas automáticamente)
- Mantiene sintaxis limpia y reduce errores de tipeo
@ -91,6 +91,7 @@ x=? → solve(x)
### 1. **Bracket Parser y Detección Contextual de Ecuaciones**
- Preprocesador que convierte `Class[args]``Class("args")`
- La separacion de argumentos se hace con el ";"
- Soporte para clases: `IP4`, `Hex`, `Bin`, `Date`, `Dec`, `Chr`
- **Detección contextual inteligente de ecuaciones**:
- Ecuaciones standalone: `3+b=5+c` (detectar como ecuación)
@ -314,6 +315,7 @@ Cada tipo de objeto (por ejemplo, `IP4`, `Hex`, etc.) debe definir una función
- Recibe el string de entrada del usuario.
- Decide, usando su propia lógica (por ejemplo, expresiones regulares), si puede ofrecer una ayuda relevante.
- Devuelve un string de ayuda (ejemplo de uso, sintaxis, sugerencia) o `None` si no aplica.
- Las respuestas no pueden tener mas de una linea nunca.
**Ejemplo:**
```python

4
.vscode/settings.json vendored Normal file
View File

@ -0,0 +1,4 @@
{
"python.analysis.autoImportCompletions": true,
"python.analysis.typeCheckingMode": "off"
}

Binary file not shown.

View File

@ -1,16 +1,16 @@
"""
Clase híbrida para números binarios
"""
from hybrid_base import HybridCalcType
from sympy_Base import SympyClassBase
import re
class HybridBin(HybridCalcType):
class Class_Bin(SympyClassBase):
"""Clase híbrida para números binarios"""
def __new__(cls, value_input):
"""Crear objeto SymPy válido"""
obj = HybridCalcType.__new__(cls)
obj = SympyClassBase.__new__(cls)
return obj
def __init__(self, value_input):

255
calc.py Normal file
View File

@ -0,0 +1,255 @@
#!/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__)
return logger, log_file
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 launch_application():
try:
# Importar y ejecutar la aplicación
from main_calc_app import HybridCalculatorApp
root = tk.Tk()
app = HybridCalculatorApp(root)
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()
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:
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)
if __name__ == "__main__":
# Configurar logging básico para manejo de argumentos
temp_logger = None
temp_log_file = None
# 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)

View File

@ -1,16 +1,16 @@
"""
Clase híbrida para caracteres
"""
from hybrid_base import HybridCalcType
from sympy_Base import SympyClassBase
import re
class HybridChr(HybridCalcType):
class Class_Chr(SympyClassBase):
"""Clase híbrida para caracteres"""
def __new__(cls, str_input):
"""Crear objeto SymPy válido"""
obj = HybridCalcType.__new__(cls)
obj = SympyClassBase.__new__(cls)
return obj
def __init__(self, str_input: str):

View File

@ -1,16 +1,16 @@
"""
Clase híbrida para números decimales
"""
from hybrid_base import HybridCalcType
from sympy_Base import SympyClassBase
import re
class HybridDec(HybridCalcType):
class Class_Dec(SympyClassBase):
"""Clase híbrida para números decimales"""
def __new__(cls, value_input):
"""Crear objeto SymPy válido"""
obj = HybridCalcType.__new__(cls)
obj = SympyClassBase.__new__(cls)
return obj
def __init__(self, value_input):

View File

@ -1,16 +1,16 @@
"""
Clase híbrida para números hexadecimales
"""
from hybrid_base import HybridCalcType
from sympy_Base import SympyClassBase
import re
class HybridHex(HybridCalcType):
class Class_Hex(SympyClassBase):
"""Clase híbrida para números hexadecimales"""
def __new__(cls, value_input):
"""Crear objeto SymPy válido"""
obj = HybridCalcType.__new__(cls)
obj = SympyClassBase.__new__(cls)
return obj
def __init__(self, value_input):

View File

@ -1,9 +1,17 @@
Hex[FF]
ip=IP4[192.168.1.100/24]
ip=IP4[110.1.30.70/24]
ip.NetworkAddress()
500/25
Dec[Hex[ff]]
Hex[ff]
Hex[ff]
Hex[ff].toDecimal()
IP4[110.1.30.70,255.255.255.0]
IP4[110.1.30.70,255.255.255.0]

View File

@ -1,4 +1,4 @@
{
"window_geometry": "1000x700+327+101",
"sash_pos_x": 339
"window_geometry": "1020x700+2638+160",
"sash_pos_x": 303
}

BIN
icon.png Normal file

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

View File

@ -1,17 +1,17 @@
"""
Clase híbrida para direcciones IPv4
"""
from hybrid_base import HybridCalcType
from sympy_Base import SympyClassBase
from typing import Optional, Tuple
import re
class HybridIP4(HybridCalcType):
class Class_IP4(SympyClassBase):
"""Clase híbrida para direcciones IPv4"""
def __new__(cls, *args):
"""Crear objeto SymPy válido"""
obj = HybridCalcType.__new__(cls)
obj = SympyClassBase.__new__(cls)
return obj
def __init__(self, *args):
@ -148,7 +148,7 @@ class HybridIP4(HybridCalcType):
]
network_str = '.'.join(str(x) for x in parts)
return HybridIP4(f"{network_str}/{self._prefix}")
return Class_IP4(f"{network_str}/{self._prefix}")
def BroadcastAddress(self):
"""Obtiene la dirección de broadcast"""
@ -170,7 +170,7 @@ class HybridIP4(HybridCalcType):
]
broadcast_str = '.'.join(str(x) for x in parts)
return HybridIP4(f"{broadcast_str}/{self._prefix}")
return Class_IP4(f"{broadcast_str}/{self._prefix}")
def Nodes(self):
"""Obtiene el número de nodos disponibles"""

View File

@ -1,624 +0,0 @@
#!/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)

View File

@ -6,6 +6,7 @@ from tkinter import scrolledtext, messagebox, Menu, filedialog
import tkinter.font as tkFont
import json
import os
from pathlib import Path # Added for robust path handling
import threading
from typing import List, Dict, Any, Optional
import re
@ -13,13 +14,13 @@ import re
# Importar componentes del CAS híbrido
from main_evaluation import HybridEvaluationEngine, EvaluationResult
from tl_popup import InteractiveResultManager
from ip4_type import HybridIP4 as IP4
from hex_type import HybridHex as Hex
from bin_type import HybridBin as Bin
from dec_type import HybridDec as Dec
from chr_type import HybridChr as Chr
from ip4_type import Class_IP4
from hex_type import Class_Hex
from bin_type import Class_Bin
from dec_type import Class_Dec
from chr_type import Class_Chr
import sympy
from sympy_helper import Helper as SympyHelper
from sympy_helper import SympyTools as SympyHelper
class HybridCalculatorApp:
@ -29,12 +30,12 @@ class HybridCalculatorApp:
HISTORY_FILE = "hybrid_calc_history.txt"
HELPERS = [
IP4.Helper,
Hex.Helper,
Bin.Helper,
Dec.Helper,
Chr.Helper,
SympyHelper,
Class_IP4.Helper,
Class_Hex.Helper,
Class_Bin.Helper,
Class_Dec.Helper,
Class_Chr.Helper,
SympyHelper.Helper,
]
def __init__(self, root: tk.Tk):
@ -70,10 +71,23 @@ class HybridCalculatorApp:
def _setup_icon(self):
"""Configura el ícono de la aplicación"""
try:
self.app_icon = tk.PhotoImage(file="icon.png")
# Construct path relative to this script file (main_calc_app.py)
script_dir = Path(__file__).resolve().parent
icon_path = script_dir / "icon.png"
if not icon_path.is_file():
print(f"Advertencia: Archivo de ícono no encontrado en '{icon_path}'.")
# Optionally, set a default Tk icon or simply return
return
self.app_icon = tk.PhotoImage(file=str(icon_path))
self.root.iconphoto(True, self.app_icon)
except tk.TclError:
print("Advertencia: No se pudo cargar 'icon.png' como ícono.")
except tk.TclError as e:
# Provide more specific error, including the path and Tkinter's error message
print(f"Advertencia: No se pudo cargar el ícono desde '{icon_path}'. Error de Tkinter: {e}")
except Exception as e:
# Catch other potential errors during icon loading
print(f"Advertencia: Ocurrió un error inesperado al cargar el ícono desde '{icon_path}': {e}")
def _load_settings(self) -> Dict[str, Any]:
"""Carga configuración de la aplicación"""
@ -261,6 +275,8 @@ class HybridCalculatorApp:
self.output_text.tag_configure("ip", foreground="#fff176")
self.output_text.tag_configure("date", foreground="#ff8a80")
self.output_text.tag_configure("chr_type", foreground="#80cbc4")
# Agregar tag para ayuda contextual
self.output_text.tag_configure("helper", foreground="#ffd700", font=("Consolas", 11, "italic"))
def on_key_release(self, event=None):
"""Maneja eventos de teclado"""
@ -279,9 +295,58 @@ class HybridCalculatorApp:
cursor_index_str = self.input_text.index(tk.INSERT)
line_num_str, char_num_str = cursor_index_str.split('.')
current_line_num = int(line_num_str)
char_idx_of_dot = int(char_num_str)
obj_expr_str = self.input_text.get(f"{current_line_num}.0", f"{current_line_num}.{char_idx_of_dot -1}").strip()
char_idx_after_dot = int(char_num_str)
if char_idx_after_dot == 0: # Should not happen if a dot was typed
print("DEBUG: _handle_dot_autocomplete called with cursor at beginning of line somehow.")
return
text_before_dot = self.input_text.get(f"{current_line_num}.0", f"{current_line_num}.{char_idx_after_dot - 1}")
# Si no hay nada o solo espacios antes del punto, ofrecer sugerencias globales
if not text_before_dot.strip():
print("DEBUG: Dot on empty line or after spaces. Offering global suggestions.")
suggestions = []
custom_classes_suggestions = [
("Hex", "Tipo Hexadecimal. Ej: Hex[FF]"),
("Bin", "Tipo Binario. Ej: Bin[1010]"),
("Dec", "Tipo Decimal. Ej: Dec[42]"),
("IP4", "Tipo Dirección IPv4. Ej: IP4[1.2.3.4/24]"),
("Chr", "Tipo Carácter. Ej: Chr[A]"),
]
suggestions.extend(custom_classes_suggestions)
try:
sympy_functions = SympyHelper.PopupFunctionList()
if sympy_functions:
suggestions.extend(sympy_functions)
except Exception as e:
print(f"DEBUG: Error calling SympyHelper.PopupFunctionList() for global: {e}")
if suggestions:
self._show_autocomplete_popup(suggestions, is_global_popup=True)
return
# Si hay texto antes del punto, es para autocompletado de métodos de un objeto
obj_expr_str = text_before_dot.strip()
print(f"DEBUG: Autocomplete triggered for object. Expression: '{obj_expr_str}'")
if not obj_expr_str:
# Esto no debería ocurrir si la lógica anterior para popup global es correcta
print("DEBUG: Object expression is empty. No autocomplete.")
return
# Caso especial para el módulo sympy
if obj_expr_str == "sympy":
print(f"DEBUG: Detected 'sympy.', using SympyHelper for suggestions.")
try:
methods = SympyHelper.PopupFunctionList()
if methods:
self._show_autocomplete_popup(methods, is_global_popup=False)
else:
print(f"DEBUG: SympyHelper.PopupFunctionList returned no methods.")
except Exception as e:
print(f"DEBUG: Error calling SympyHelper.PopupFunctionList(): {e}")
return
# Preprocesar para convertir sintaxis de corchetes a llamada de clase
@ -289,24 +354,28 @@ class HybridCalculatorApp:
bracket_match = re.match(r"([A-Za-z_][A-Za-z0-9_]*)\[(.*)\]$", obj_expr_str)
if bracket_match:
class_name, arg = bracket_match.groups()
# Si el argumento es un número, no poner comillas
if arg.isdigit():
obj_expr_str = f"{class_name}({arg})"
else:
obj_expr_str = f"{class_name}('{arg}')"
print(f"DEBUG: Preprocessed bracket syntax to: '{obj_expr_str}'")
eval_context = self.engine._get_full_context() if hasattr(self.engine, '_get_full_context') else {}
obj = None
try:
print(f"DEBUG: Attempting to eval: '{obj_expr_str}'")
obj = eval(obj_expr_str, eval_context)
except Exception:
print(f"DEBUG: Eval successful. Object: {type(obj)}, Value: {obj}")
except Exception as e:
print(f"DEBUG: Error evaluating object expression '{obj_expr_str}': {e}")
return
if obj is not None and hasattr(obj, 'PopupFunctionList'):
methods = obj.PopupFunctionList()
if methods:
self._show_autocomplete_popup(methods)
self._show_autocomplete_popup(methods, is_global_popup=False)
def _show_autocomplete_popup(self, suggestions):
def _show_autocomplete_popup(self, suggestions, is_global_popup=False):
# suggestions: lista de tuplas (nombre, hint)
cursor_bbox = self.input_text.bbox(tk.INSERT)
if not cursor_bbox:
@ -330,18 +399,29 @@ class HybridCalculatorApp:
self._autocomplete_listbox.select_set(0)
self._autocomplete_listbox.pack(expand=True, fill=tk.BOTH)
self._autocomplete_listbox.bind("<Return>", self._on_autocomplete_select)
self._autocomplete_listbox.bind("<Tab>", self._on_autocomplete_select)
self._autocomplete_listbox.bind("<Escape>", lambda e: self._close_autocomplete_popup())
self._autocomplete_listbox.bind("<Double-Button-1>", self._on_autocomplete_select)
self._autocomplete_listbox.bind("<Double-Button-1>", lambda e, g=is_global_popup: self._on_autocomplete_select(e, is_global=g))
self._autocomplete_listbox.focus_set()
self._autocomplete_listbox.bind("<Up>", lambda e: self._navigate_autocomplete(e, -1))
self._autocomplete_listbox.bind("<Down>", lambda e: self._navigate_autocomplete(e, 1))
self.input_text.bind("<FocusOut>", lambda e: self._close_autocomplete_popup(), add=True)
# self.input_text.bind("<FocusOut>", lambda e: self._close_autocomplete_popup(), add=True) # Removed: Caused popup to close immediately
self.input_text.bind("<Button-1>", lambda e: self._close_autocomplete_popup(), add=True)
self.root.bind("<Button-1>", lambda e: self._close_autocomplete_popup(), add=True)
self.input_text.bind("<Key>", lambda e: self._close_autocomplete_popup(), add=True)
# self.input_text.bind("<Key>", lambda e: self._close_autocomplete_popup(), add=True) # Removed: Too aggressive
# Pasar el flag is_global_popup a los bindings que llaman a _on_autocomplete_select
self._autocomplete_listbox.bind("<Return>", lambda e, g=is_global_popup: self._on_autocomplete_select(e, is_global=g))
self._autocomplete_listbox.bind("<Tab>", lambda e, g=is_global_popup: self._on_autocomplete_select(e, is_global=g))
max_len = max(len(name) for name, _ in suggestions) if suggestions else 10
width = max(15, min(max_len + 10, 50))
height = min(len(suggestions), 10)
# Calcular el ancho basado en el texto completo que se muestra en el listbox
full_text_suggestions = [f"{name}{hint}" for name, hint in suggestions]
max_full_len = max(len(text) for text in full_text_suggestions) if full_text_suggestions else 20
width = max(20, min(max_full_len + 5, 80)) # Ajustar el +5 y el límite 80 según sea necesario
self._autocomplete_listbox.config(width=width, height=height)
else:
self._close_autocomplete_popup()
@ -363,17 +443,39 @@ class HybridCalculatorApp:
self._autocomplete_listbox.see(new_idx)
return "break"
def _on_autocomplete_select(self, event):
def _on_autocomplete_select(self, event, is_global=False):
if not hasattr(self, '_autocomplete_listbox') or not self._autocomplete_listbox:
return "break"
selection = self._autocomplete_listbox.curselection()
if not selection:
self._close_autocomplete_popup()
return "break"
selected = self._autocomplete_listbox.get(selection[0])
method_name = selected.split()[0]
self.input_text.insert(tk.INSERT, method_name + "()")
self.input_text.mark_set(tk.INSERT, f"{tk.INSERT}-1c") # Cursor dentro de los paréntesis
selected_text_with_hint = self._autocomplete_listbox.get(selection[0])
# Extraer solo el nombre del ítem, antes de " —"
item_name = selected_text_with_hint.split("")[0].strip()
if is_global:
# Eliminar el punto que activó el popup y luego insertar el nombre
cursor_pos_str = self.input_text.index(tk.INSERT) # Posición actual (después del punto)
line_num, char_num = map(int, cursor_pos_str.split('.'))
# El punto está en char_num - 1 en la línea actual
dot_pos_on_line = char_num - 1
dot_index_str = f"{line_num}.{dot_pos_on_line}"
self.input_text.delete(dot_index_str)
# Insertar el nombre de la función/clase seguido de "()"
insert_text = item_name + "()"
self.input_text.insert(dot_index_str, insert_text)
# Colocar cursor dentro de los paréntesis: después del nombre y el '('
self.input_text.mark_set(tk.INSERT, f"{dot_index_str}+{len(item_name)+1}c")
else:
# Comportamiento existente para métodos de objeto
self.input_text.insert(tk.INSERT, item_name + "()")
self.input_text.mark_set(tk.INSERT, f"{tk.INSERT}-1c") # Cursor dentro de los paréntesis
self._close_autocomplete_popup()
self.input_text.focus_set()
self.on_key_release() # Trigger re-evaluation
@ -383,6 +485,17 @@ class HybridCalculatorApp:
if hasattr(self, '_autocomplete_popup') and self._autocomplete_popup:
self._autocomplete_popup.destroy()
self._autocomplete_popup = None
# Consider unbinding the Button-1 events if they were stored with IDs,
# for now, their guard condition `if self._autocomplete_popup:` handles multiple calls.
# Example of how to unbind if IDs were stored:
# if hasattr(self, '_input_text_b1_bind_id'):
# self.input_text.unbind("<Button-1>", self._input_text_b1_bind_id)
# del self._input_text_b1_bind_id
# if hasattr(self, '_root_b1_bind_id'):
# self.root.unbind("<Button-1>", self._root_b1_bind_id)
# del self._root_b1_bind_id
if hasattr(self, '_autocomplete_listbox') and self._autocomplete_listbox:
self._autocomplete_listbox = None
def _evaluate_and_update(self):
@ -428,7 +541,11 @@ class HybridCalculatorApp:
if result.is_error:
ayuda = self.obtener_ayuda(result.original_line)
if ayuda:
output_parts.append(("helper", ayuda))
# Mostrar ayuda en un solo renglón, truncando si es necesario
ayuda_linea = ayuda.replace("\n", " ").replace("\r", " ")
if len(ayuda_linea) > 120:
ayuda_linea = ayuda_linea[:117] + "..."
output_parts.append(("helper", ayuda_linea))
else:
output_parts.append(("error", f"Error: {result.error}"))
elif result.result_type == "comment":
@ -445,9 +562,7 @@ class HybridCalculatorApp:
# Verificar si es resultado interactivo
if self.interactive_manager and result.is_interactive:
interactive_tag, display_text = self.interactive_manager.create_interactive_tag(
result.result, self.output_text, "1.0"
)
interactive_tag, display_text = self.interactive_manager.create_interactive_tag(result.result, self.output_text, "1.0")
if interactive_tag:
output_parts.append((interactive_tag, display_text))
else:
@ -467,13 +582,13 @@ class HybridCalculatorApp:
def _get_result_tag(self, result: Any) -> str:
"""Determina el tag de color para un resultado"""
if isinstance(result, Hex):
if isinstance(result, Class_Hex):
return "hex"
elif isinstance(result, Bin):
elif isinstance(result, Class_Bin):
return "bin"
elif isinstance(result, IP4):
elif isinstance(result, Class_IP4):
return "ip"
elif isinstance(result, Chr):
elif isinstance(result, Class_Chr):
return "chr_type"
elif isinstance(result, sympy.Basic):
return "symbolic"

View File

@ -10,12 +10,12 @@ from contextlib import contextmanager
from tl_bracket_parser import BracketParser
from tl_popup import PlotResult
from hybrid_base import HybridCalcType
from ip4_type import HybridIP4 as IP4
from hex_type import HybridHex as Hex
from bin_type import HybridBin as Bin
from dec_type import HybridDec as Dec
from chr_type import HybridChr as Chr
from sympy_Base import SympyClassBase
from ip4_type import Class_IP4 as Class_IP4
from hex_type import Class_Hex as Class_Hex
from bin_type import Class_Bin as Class_Bin
from dec_type import Class_Dec as Class_Dec
from chr_type import Class_Chr as Class_Chr
class HybridEvaluationEngine:
@ -85,17 +85,17 @@ class HybridEvaluationEngine:
# Clases especializadas
specialized_classes = {
'Hex': Hex,
'Bin': Bin,
'Dec': Dec,
'IP4': IP4,
'Chr': Chr,
'Hex': Class_Hex,
'Bin': Class_Bin,
'Dec': Class_Dec,
'IP4': Class_IP4,
'Chr': Class_Chr,
# Alias en minúsculas
'hex': Hex,
'bin': Bin,
'dec': Dec,
'ip4': IP4,
'chr': Chr,
'hex': Class_Hex,
'bin': Class_Bin,
'dec': Class_Dec,
'ip4': Class_IP4,
'chr': Class_Chr,
}
# Funciones de utilidad
@ -314,7 +314,7 @@ class HybridEvaluationEngine:
result = eval(expression, {"__builtins__": {}}, context)
# Si el resultado es un objeto híbrido, integrarlo con SymPy si es necesario
if isinstance(result, HybridCalcType):
if isinstance(result, SympyClassBase):
return result
elif isinstance(result, PlotResult):
if self.debug:

View File

@ -7,7 +7,7 @@ from typing import Any, Optional, Dict
import re
class HybridCalcType(Basic):
class SympyClassBase(Basic):
"""
Clase base híbrida que combina SymPy Basic con funcionalidad de calculadora
Todas las clases especializadas deben heredar de esta

View File

@ -1,25 +1,59 @@
import re
def Helper(input_str):
"""Ayuda contextual para funciones SymPy comunes"""
sympy_funcs = {
class SympyTools:
"""
Utilidades para la integración con SymPy, incluyendo ayuda contextual
y listas de funciones para autocompletado.
"""
_sympy_func_details = {
"diff": "Derivada: diff(expr, var). Ej: diff(sin(x), x)",
"integrate": "Integral: integrate(expr, var). Ej: integrate(x**2, x)",
"solve": "Resolver ecuaciones: solve(expr, var). Ej: solve(x**2-1, x)",
"limit": "Límite: limit(expr, var, valor). Ej: limit(sin(x)/x, x, 0)",
"series": "Serie de Taylor: series(expr, var, punto, n). Ej: series(exp(x), x, 0, 5)",
"Matrix": "Matrices: Matrix([[a, b], [c, d]]). Ej: Matrix([[1,2],[3,4]])",
"plot": "Gráfica: plot(expr, (var, a, b)). Ej: plot(sin(x), (x, 0, 2*pi))",
"plot": "Gráfica: plot(expr, (var, a, b)). Ej: plot(sin(x), (x, -2*pi, 2*pi))", # Corregido ejemplo de plot
"plot3d": "Gráfica 3D: plot3d(expr, (x, a, b), (y, c, d))",
"simplify": "Simplificar: simplify(expr). Ej: simplify((x**2 + 2*x + 1))",
"expand": "Expandir: expand(expr). Ej: expand((x+1)**2)",
"factor": "Factorizar: factor(expr). Ej: factor(x**2 + 2*x + 1)",
"collect": "Agrupar: collect(expr, var). Ej: collect(x*y + x, x)",
"cancel": "Cancelar: cancel(expr). Ej: cancel((x**2 + 2*x + 1)/(x+1))",
"apart": "Fracciones parciales: apart(expr, var). Ej: apart(1/(x*(x+1)), x)",
"apart": "Fracciones parciales: apart(expr). Ej: apart(1/(x*(x+1)))", # Var opcional en apart
"together": "Unir fracciones: together(expr). Ej: together(1/x + 1/y)",
}
for func, ayuda in sympy_funcs.items():
if input_str.strip().startswith(func):
return ayuda
return None
@staticmethod
def Helper(input_str: str) -> str | None:
"""
Ayuda contextual para funciones SymPy comunes.
Se activa si alguna palabra en input_str es un prefijo de un nombre de función conocido.
"""
cleaned_input = input_str.strip()
if not cleaned_input:
return None
# Extraer palabras (posibles identificadores de función) de la entrada
words_in_input = re.findall(r'[a-zA-Z_][a-zA-Z0-9_]*', cleaned_input)
if not words_in_input:
return None
# Iterar sobre las funciones de SymPy definidas
for func_name, help_text in SympyTools._sympy_func_details.items():
# Comprobar si alguna palabra de la entrada es un prefijo del nombre de la función
for word in words_in_input:
# len(word) > 0 para cumplir "no hay minimo" (una palabra no vacía)
if func_name.startswith(word) and len(word) > 0:
# Ej: func_name="solve", word="solv" -> True. Devuelve ayuda para "solve".
# Ej: func_name="solve", word="s" -> True. Devuelve ayuda para "solve"
# (o la primera función en el diccionario que empiece con "s").
return help_text
return None
@staticmethod
def PopupFunctionList():
"""Lista de métodos y sus descripciones breves para el popup de autocompletado."""
return [
(name, desc.split(". Ej:")[0] if ". Ej:" in desc else desc)
for name, desc in SympyTools._sympy_func_details.items()
]