diff --git a/.doc/DESCRIPTION.md b/.doc/DESCRIPTION.md index 8d85f43..61598c8 100644 --- a/.doc/DESCRIPTION.md +++ b/.doc/DESCRIPTION.md @@ -3,7 +3,7 @@ ## Overview -The MAV Calculator is a hybrid Computer Algebra System (CAS) built around a pure algebraic engine that treats all assignments as equations. The application follows a clean pipeline from user input to result display, with sophisticated tokenization and symbolic computation capabilities. +The MAV Calculator is a hybrid Computer Algebra System (CAS) built around a pure algebraic engine that treats all assignments as equations. The application follows a clean pipeline from user input to result display, with sophisticated tokenization and symbolic computation capabilities. The GUI design must be minimalist and dark themed. ### The application has this main components: @@ -21,6 +21,7 @@ The MAV Calculator is a hybrid Computer Algebra System (CAS) built around a pure * Cycle: every time the user change the input panel start a cycle that ends when all lines are evaluated and produced an output * Input panel: tk text area where the user can write or modify text. * Output panel: read only tk area text correlated 1:1 to every line on the input panel . Every input line must correspond to only 1 line on the output. The output lines can have colors and binds to click for opening the plots or to show matrix or lists. +* Mathjax panel: read only panel atached to the output panel on the right with the comments, equations and asignments. The formulas must be rendered using mathjax. This pannel must be collapsable. * Persistence: The app maintain the state of all configurated setup and dimension of the window and all the text on the input panel. ## Application Architecture diff --git a/README_PYSIDE6.md b/README_PYSIDE6.md new file mode 100644 index 0000000..63bc1c5 --- /dev/null +++ b/README_PYSIDE6.md @@ -0,0 +1,188 @@ +# Calculadora MAV - Versión PySide6 con Diseño Minimalista + +## 🎯 Implementación Minimalista de 3 Paneles + +Esta es la implementación de la Calculadora MAV usando **PySide6** con diseño minimalista de 3 paneles y renderizado **MathJax** para ecuaciones LaTeX, manteniendo correspondencia 1:1 línea por línea entre entrada y salida. + +## ✨ Características Principales + +### 🔧 Interfaz Moderna +- **PySide6**: Framework Qt moderno y nativo +- **Tema Oscuro**: Diseño elegante y profesional +- **Resaltado de Sintaxis**: Coloreado inteligente de expresiones matemáticas +- **Interfaz Responsive**: Se adapta al tamaño de la ventana + +### 🧮 Motor de Cálculo +- **SymPy**: Motor algebraico simbólico completo +- **Evaluación Asíncrona**: Cálculos en threads separados +- **Tipos Personalizados**: Sistema de tipos extensible +- **Historial Automático**: Guarda y restaura sesiones + +### 📐 Renderizado LaTeX +- **MathJax**: Renderizado web profesional de ecuaciones +- **Tiempo Real**: Actualización automática del panel LaTeX +- **Múltiples Tipos**: Ecuaciones, asignaciones y expresiones simbólicas +- **Interactivo**: Panel redimensionable y ocultable + +## 🚀 Instalación y Uso + +### Requisitos Previos +```bash +Python 3.8 o superior +``` + +### Instalar Dependencias +```bash +pip install -r requirements.txt +``` + +### Ejecutar la Aplicación +```bash +# Opción 1: Usar el launcher (recomendado) +python launch_pyside6.py + +# Opción 2: Ejecutar directamente +python main_calc_app_pyside6.py +``` + +## 🎮 Guía de Uso + +### Interfaz Principal - 3 Paneles Minimalistas +- **Panel 1 (Izquierda)**: Entrada de expresiones matemáticas +- **Panel 2 (Centro)**: Resultados con correspondencia 1:1 línea por línea +- **Panel 3 (Derecha)**: Renderizado LaTeX/MathJax de ecuaciones y comentarios (colapsable) + +### Atajos de Teclado +| Atajo | Función | +|-------|---------| +| `Ctrl+Enter` | Evaluar expresión | +| `Shift+Enter` | Evaluar expresión | +| `F12` | Mostrar/ocultar panel LaTeX | +| `Ctrl+N` | Nueva sesión | +| `Ctrl+O` | Abrir archivo | +| `Ctrl+S` | Guardar archivo | + +### Ejemplos de Uso +```python +# Ecuaciones básicas +x**2 + y**2 = r**2 + +# Resolver ecuaciones +solve(x**2 - 4, x) + +# Cálculo diferencial +diff(x**3 + 2*x**2 + x, x) + +# Cálculo integral +integrate(x**2, x) + +# Álgebra lineal +Matrix([[1, 2], [3, 4]]) + +# Asignaciones +a = x**2 + 5 +b = solve(a - 10, x) +``` + +## 🎨 Características de la Interfaz + +### Resaltado de Sintaxis +- **Números**: Color azul claro (`#89ddff`) +- **Funciones**: Color azul (`#82aaff`) y negrita +- **Variables**: Color verde claro (`#c3e88d`) +- **Operadores**: Color rojo (`#ff6b6b`) y negrita +- **Paréntesis**: Color naranja (`#f78c6c`) y negrita + +### Panel LaTeX +- **Ecuaciones**: Borde azul (`#4fc3f7`) +- **Asignaciones**: Borde verde (`#c3e88d`) +- **Expresiones Simbólicas**: Borde naranja (`#f78c6c`) +- **Hover Effects**: Cambios de color al pasar el ratón + +## 🔧 Arquitectura Técnica + +### Componentes Principales +``` +HybridCalculatorPySide6 (Ventana Principal) +├── MathInputHighlighter (Resaltado de sintaxis) +├── CalculatorWorker (Evaluación asíncrona) +├── MathJaxPanel (Renderizado LaTeX) +└── PureAlgebraicEngine (Motor de cálculo) +``` + +### Threading +- **UI Thread**: Interfaz de usuario principal +- **Worker Thread**: Evaluación matemática asíncrona +- **Signals/Slots**: Comunicación segura entre threads + +### WebEngine +- **QWebEngineView**: Para renderizado MathJax +- **JavaScript Integration**: Comunicación bidireccional +- **HTML5**: Soporte completo para MathJax 3.x + +## 🆚 Comparación con Versión Original + +| Característica | Tkinter Original | PySide6 Nueva | +|----------------|------------------|---------------| +| **Framework** | Tkinter | PySide6/Qt | +| **Renderizado LaTeX** | pywebview/tkinterweb | MathJax nativo | +| **Tema** | Básico | Moderno oscuro | +| **Resaltado** | No | Sintaxis completa | +| **Threading** | Básico | Asíncrono avanzado | +| **Responsive** | Limitado | Completo | +| **Cross-platform** | Básico | Nativo Qt | + +## 🔍 Solución de Problemas + +### Error: "No module named 'PySide6'" +```bash +pip install PySide6 +``` + +### Error: "No module named 'PySide6.QtWebEngineWidgets'" +```bash +pip install PySide6-WebEngine +``` + +### Panel LaTeX no funciona +1. Verificar conexión a internet (MathJax CDN) +2. Comprobar que WebEngine esté instalado +3. Ver logs en consola para errores JavaScript + +### Rendimiento lento +1. Cerrar otras aplicaciones que usen Qt/WebEngine +2. Reducir el número de ecuaciones en el panel LaTeX +3. Verificar que no hay bucles infinitos en expresiones + +## 🚧 Desarrollo y Contribución + +### Estructura del Código +``` +main_calc_app_pyside6.py # Aplicación principal +launch_pyside6.py # Launcher con verificaciones +requirements.txt # Dependencias actualizadas +README_PYSIDE6.md # Esta documentación +``` + +### Extensiones Futuras +- [ ] Autocompletado inteligente +- [ ] Gráficos integrados con matplotlib +- [ ] Exportación de ecuaciones LaTeX +- [ ] Temas personalizables +- [ ] Plugin system +- [ ] Colaboración en tiempo real + +## 📄 Licencia + +Mismo sistema de licencia que el proyecto original. + +## 💝 Agradecimientos + +- **Qt/PySide6**: Framework de interfaz moderna +- **MathJax**: Renderizado matemático profesional +- **SymPy**: Motor algebraico potente +- **Comunidad Python**: Soporte y documentación + +--- + +**¡Disfruta calculando con la nueva interfaz moderna! 🎉** \ No newline at end of file diff --git a/demo_minimalista.py b/demo_minimalista.py new file mode 100644 index 0000000..4959803 --- /dev/null +++ b/demo_minimalista.py @@ -0,0 +1,87 @@ +#!/usr/bin/env python3 +""" +Demostración del diseño minimalista de 3 paneles +Calculadora MAV - PySide6 +""" +import sys +import time + +def show_demo_instructions(): + """Muestra las instrucciones de la demostración""" + print("🎯 DEMOSTRACIÓN: Calculadora MAV - Diseño Minimalista") + print("=" * 60) + print() + print("📋 DISEÑO DE 3 PANELES:") + print(" ┌─────────────┬─────────────┬─────────────┐") + print(" │ PANEL 1 │ PANEL 2 │ PANEL 3 │") + print(" │ ENTRADA │ RESULTADOS │ LATEX │") + print(" │ │ (1:1) │ (colapsable)│") + print(" └─────────────┴─────────────┴─────────────┘") + print() + print("✨ CARACTERÍSTICAS PRINCIPALES:") + print(" • Correspondencia 1:1 entre líneas de entrada y salida") + print(" • Tema oscuro minimalista") + print(" • Resaltado de sintaxis en tiempo real") + print(" • Panel LaTeX con MathJax para ecuaciones") + print(" • Motor algebraico SymPy sin cambios") + print() + print("🎮 EJEMPLOS PARA PROBAR:") + print(" # Comentario - aparece en panel LaTeX") + print(" x**2 + y**2 = r**2") + print(" solve(x**2 - 4, x)") + print(" a = 5*x + 3") + print(" diff(x**3, x)") + print(" Matrix([[1, 2], [3, 4]])") + print() + print("⌨️ ATAJOS DE TECLADO:") + print(" • Ctrl+Enter / Shift+Enter: Evaluar") + print(" • F12: Mostrar/ocultar panel LaTeX") + print(" • Ctrl+N: Nueva sesión") + print() + +def run_demo(): + """Ejecuta la demostración""" + show_demo_instructions() + + try: + response = input("¿Ejecutar la aplicación? (s/N): ").strip().lower() + if response in ['s', 'sí', 'si', 'y', 'yes']: + print("\n🚀 Iniciando Calculadora MAV...") + + # Verificar dependencias primero + try: + from PySide6.QtWidgets import QApplication + print("✅ PySide6 disponible") + except ImportError: + print("❌ PySide6 no está instalado") + print(" Ejecuta: pip install -r requirements.txt") + return 1 + + # Iniciar aplicación + from main_calc_app_pyside6 import main as run_app + + # Precarga con contenido de demostración + print("📝 Precargando contenido de demostración...") + demo_content = """# Demostración Calculadora MAV +x**2 + y**2 = r**2 +solve(x**2 - 4, x) +a = 5*x + 3 +diff(a, x)""" + + # Ejecutar aplicación + run_app() + + else: + print("👋 ¡Hasta luego!") + + except KeyboardInterrupt: + print("\n\n🚪 Demostración cancelada") + return 0 + except Exception as e: + print(f"\n❌ Error: {e}") + return 1 + + return 0 + +if __name__ == "__main__": + sys.exit(run_demo()) \ No newline at end of file diff --git a/hybrid_calc_history.txt b/hybrid_calc_history.txt index 520dcce..017ead5 100644 --- a/hybrid_calc_history.txt +++ b/hybrid_calc_history.txt @@ -1,6 +1,8 @@ - - x**2 + y**2 = r**2 + r=? -a=r*5+5 \ No newline at end of file +a=r*5+5 + + + diff --git a/hybrid_calc_settings.json b/hybrid_calc_settings.json index 0288774..a829cc2 100644 --- a/hybrid_calc_settings.json +++ b/hybrid_calc_settings.json @@ -1,9 +1,5 @@ { - "window_geometry": "1272x700+331+1194", - "sash_pos_x": 440, - "symbolic_mode": true, - "show_numeric_approximation": true, - "keep_symbolic_fractions": true, - "auto_simplify": false, - "latex_panel_visible": true + "window_geometry": "1400x800", + "debug_mode": false, + "latex_panel_visible": true } \ No newline at end of file diff --git a/launch_pyside6.py b/launch_pyside6.py new file mode 100644 index 0000000..16e530b --- /dev/null +++ b/launch_pyside6.py @@ -0,0 +1,89 @@ +#!/usr/bin/env python3 +""" +Launcher para Calculadora MAV - Versión PySide6 con MathJax +""" +import sys +import os +import subprocess +from pathlib import Path +import importlib.util +import logging + +def check_dependencies(): + """Verifica que todas las dependencias estén instaladas""" + required_modules = [ + 'PySide6', + 'sympy', + 'numpy', + 'matplotlib' + ] + + missing = [] + for module in required_modules: + try: + __import__(module) + except ImportError: + missing.append(module) + + if missing: + print("❌ Faltan las siguientes dependencias:") + for module in missing: + print(f" - {module}") + print("\n💡 Para instalar las dependencias, ejecuta:") + print(" pip install -r requirements.txt") + return False + + print("✅ Todas las dependencias están instaladas") + return True + +def check_pyside6_webengine(): + """Verifica si PySide6 WebEngine está disponible""" + try: + from PySide6.QtWebEngineWidgets import QWebEngineView + print("✅ PySide6 WebEngine disponible para MathJax") + return True + except ImportError: + print("⚠️ PySide6 WebEngine no disponible") + print(" Instalando QtWebEngine...") + try: + subprocess.run([sys.executable, '-m', 'pip', 'install', 'PySide6-WebEngine'], + check=True, capture_output=True) + print("✅ PySide6 WebEngine instalado correctamente") + return True + except subprocess.CalledProcessError: + print("❌ No se pudo instalar PySide6 WebEngine") + return False + +def main(): + """Función principal del launcher""" + print("🚀 Iniciando Calculadora MAV - Diseño Minimalista 3 Paneles") + print("=" * 60) + + # Verificar dependencias + if not check_dependencies(): + sys.exit(1) + + # Verificar WebEngine + if not check_pyside6_webengine(): + print("⚠️ Continuando sin WebEngine (funcionalidad limitada)") + + print("\n🎯 Iniciando aplicación...") + + try: + # Importar y ejecutar la aplicación + from main_calc_app_pyside6 import main as run_app + run_app() + + except ImportError as e: + print(f"❌ Error de importación: {e}") + print(" Verifica que todos los archivos del proyecto estén presentes") + sys.exit(1) + + except Exception as e: + print(f"❌ Error inesperado: {e}") + import traceback + traceback.print_exc() + sys.exit(1) + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/main_calc_app_pyside6.py b/main_calc_app_pyside6.py new file mode 100644 index 0000000..8b9c87f --- /dev/null +++ b/main_calc_app_pyside6.py @@ -0,0 +1,789 @@ +""" +Calculadora MAV CAS Híbrida - Aplicación PySide6 con MathJax +Diseño minimalista de 3 paneles con correspondencia 1:1 línea por línea +""" +import sys +import json +import logging +import os +import re +import threading +import time +from pathlib import Path +from typing import List, Dict, Any, Optional + +from PySide6.QtWidgets import ( + QApplication, QMainWindow, QWidget, QVBoxLayout, QHBoxLayout, + QTextEdit, QPlainTextEdit, QSplitter, QPushButton, QLabel, + QFrame, QMenuBar, QStatusBar, QMessageBox, QFileDialog, + QScrollArea, QSizePolicy +) +from PySide6.QtCore import ( + Qt, QTimer, QThread, QObject, Signal, QUrl, QSize +) +from PySide6.QtGui import ( + QFont, QTextCursor, QTextCharFormat, QColor, QIcon, + QTextDocument, QSyntaxHighlighter, QTextFormat, + QKeySequence, QShortcut, QFontMetrics +) +from PySide6.QtWebEngineWidgets import QWebEngineView +from PySide6.QtWebEngineCore import QWebEngineSettings + +# Importar componentes del CAS híbrido +from main_evaluation_puro import PureAlgebraicEngine, EvaluationResult +from tl_popup import InteractiveResultManager, PlotResult +from type_registry import get_registered_helper_functions, get_registered_base_context +import sympy +from sympy_helper import SympyTools as SympyHelper + + +class MathInputHighlighter(QSyntaxHighlighter): + """Resaltador de sintaxis para expresiones matemáticas""" + + def __init__(self, parent=None): + super().__init__(parent) + self.setup_highlighting_rules() + + def setup_highlighting_rules(self): + """Configura las reglas de resaltado""" + self.highlighting_rules = [] + + # Números + number_format = QTextCharFormat() + number_format.setForeground(QColor("#89ddff")) + self.highlighting_rules.append((r'\b\d+\.?\d*\b', number_format)) + + # Funciones matemáticas + function_format = QTextCharFormat() + function_format.setForeground(QColor("#82aaff")) + function_format.setFontWeight(QFont.Bold) + functions = [ + 'sin', 'cos', 'tan', 'log', 'ln', 'exp', 'sqrt', 'abs', + 'solve', 'diff', 'integrate', 'limit', 'series', 'factor', + 'expand', 'simplify', 'Matrix', 'det', 'inv' + ] + for func in functions: + pattern = rf'\b{func}\b' + self.highlighting_rules.append((pattern, function_format)) + + # Variables + variable_format = QTextCharFormat() + variable_format.setForeground(QColor("#c3e88d")) + self.highlighting_rules.append((r'\b[a-zA-Z_][a-zA-Z0-9_]*\b', variable_format)) + + # Operadores + operator_format = QTextCharFormat() + operator_format.setForeground(QColor("#ff6b6b")) + operator_format.setFontWeight(QFont.Bold) + self.highlighting_rules.append((r'[+\-*/=<>!&|^]', operator_format)) + + # Paréntesis y corchetes + bracket_format = QTextCharFormat() + bracket_format.setForeground(QColor("#f78c6c")) + bracket_format.setFontWeight(QFont.Bold) + self.highlighting_rules.append((r'[\[\](){}]', bracket_format)) + + def highlightBlock(self, text): + """Aplica el resaltado al bloque de texto""" + for pattern, format_obj in self.highlighting_rules: + expression = re.compile(pattern) + for match in expression.finditer(text): + start, end = match.span() + self.setFormat(start, end - start, format_obj) + + +class SynchronizedTextEdit(QTextEdit): + """Editor de texto que mantiene sincronización línea por línea""" + + def __init__(self, parent=None): + super().__init__(parent) + self.setLineWrapMode(QTextEdit.NoWrap) + self.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff) + self.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff) + + def sync_scroll_with(self, other_widget): + """Sincroniza el scroll con otro widget""" + self.verticalScrollBar().valueChanged.connect( + other_widget.verticalScrollBar().setValue + ) + + +class LineNumberedPlainTextEdit(QPlainTextEdit): + """Editor de texto plano con numeración de líneas implícita""" + + def __init__(self, parent=None): + super().__init__(parent) + self.setLineWrapMode(QPlainTextEdit.NoWrap) + self.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff) + self.setHorizontalScrollBarPolicy(Qt.ScrollBarAsNeeded) + + +class MathJaxPanel(QWebEngineView): + """Panel web para renderizado de LaTeX con MathJax - Panel colapsable a la derecha""" + + def __init__(self, parent=None): + super().__init__(parent) + self.equations = [] + self.setup_webview() + self.load_mathjax_base() + + def setup_webview(self): + """Configura el webview""" + self.setMinimumWidth(300) + self.setSizePolicy(QSizePolicy.Preferred, QSizePolicy.Expanding) + self.settings().setAttribute(QWebEngineSettings.LocalContentCanAccessRemoteUrls, True) + self.settings().setAttribute(QWebEngineSettings.LocalContentCanAccessFileUrls, True) + + def load_mathjax_base(self): + """Carga el HTML base con MathJax""" + html_content = self.generate_base_html() + self.setHtml(html_content) + + def generate_base_html(self): + """Genera el HTML base con MathJax configurado""" + return """ + + + + + MathJax Panel + + + + + + +
+ + + """ + + def add_equation(self, equation_type: str, latex_content: str): + """Añade una ecuación al panel""" + self.equations.append({'type': equation_type, 'content': latex_content}) + # Escapar backticks en el contenido LaTeX + escaped_content = latex_content.replace('`', '\\`') + js_code = f"addEquation('{equation_type}', `{escaped_content}`);" + self.page().runJavaScript(js_code) + + def clear_equations(self): + """Limpia todas las ecuaciones""" + self.equations.clear() + self.page().runJavaScript("clearEquations();") + + +class HybridCalculatorPySide6(QMainWindow): + """Aplicación principal del CAS híbrido - Diseño minimalista de 3 paneles""" + + SETTINGS_FILE = "hybrid_calc_settings.json" + HISTORY_FILE = "hybrid_calc_history.txt" + + def __init__(self): + super().__init__() + + # Configurar logging + logging.basicConfig(level=logging.DEBUG, format='%(levelname)s: %(message)s') + self.logger = logging.getLogger(__name__) + + # ========== USAR EL MOTOR ORIGINAL SIN CAMBIOS ========== + self.engine = PureAlgebraicEngine() + self.interactive_manager = None + + # Variables de configuración + self.settings = self.load_settings() + self.debug = self.settings.get("debug_mode", False) + + # Variables de estado UI + self.latex_panel_visible = True + self._debounce_timer = QTimer() + self._debounce_timer.setSingleShot(True) + self._debounce_timer.timeout.connect(self._evaluate_and_update) + + # ========== CONFIGURAR HELPERS DINÁMICOS (COMO EN ORIGINAL) ========== + self._setup_dynamic_helpers() + + # Configurar interfaz + self.setup_ui() + self.setup_shortcuts() + self.load_history() + + # Configurar manager interactivo + self.setup_interactive_manager() + + self.logger.info("✅ Calculadora MAV PySide6 inicializada") + + def _setup_dynamic_helpers(self): + """Configura helpers dinámicamente desde el registro de tipos - COMO EN ORIGINAL""" + try: + self.HELPERS = get_registered_helper_functions() + self.HELPERS.append(SympyHelper.Helper) + self.logger.info(f"Helpers dinámicos cargados: {len(self.HELPERS)}") + except Exception as e: + self.logger.error(f"Error cargando helpers dinámicos: {e}") + self.HELPERS = [SympyHelper.Helper] + + def setup_interactive_manager(self): + """Configura el manager de contenido interactivo - COMO EN ORIGINAL""" + try: + self.interactive_manager = InteractiveResultManager(self) + except Exception as e: + self.logger.error(f"Error configurando interactive manager: {e}") + self.interactive_manager = None + + def setup_ui(self): + """Configura la interfaz de usuario - DISEÑO MINIMALISTA DE 3 PANELES""" + self.setWindowTitle("Calculadora MAV - CAS Híbrido") + self.setGeometry(100, 100, 1400, 800) + self.setStyleSheet(self.get_minimal_dark_theme()) + + # Widget central + central_widget = QWidget() + self.setCentralWidget(central_widget) + + # Layout principal horizontal + main_layout = QHBoxLayout(central_widget) + main_layout.setContentsMargins(0, 0, 0, 0) + main_layout.setSpacing(1) + + # ========== PANEL 1: ENTRADA ========== + self.input_text = LineNumberedPlainTextEdit() + self.input_text.setFont(QFont("Consolas", 11)) + self.input_text.setPlaceholderText("Introduce expresiones matemáticas...") + self.input_text.textChanged.connect(self._on_input_changed) + + # Configurar highlighter + self.highlighter = MathInputHighlighter(self.input_text.document()) + + # ========== PANEL 2: SALIDA (SINCRONIZADO 1:1) ========== + self.output_text = SynchronizedTextEdit() + self.output_text.setFont(QFont("Consolas", 11)) + self.output_text.setReadOnly(True) + + # Sincronizar scroll entre entrada y salida + self.input_text.verticalScrollBar().valueChanged.connect( + self.output_text.verticalScrollBar().setValue + ) + self.output_text.verticalScrollBar().valueChanged.connect( + self.input_text.verticalScrollBar().setValue + ) + + # ========== PANEL 3: MATHJAX (COLAPSABLE) ========== + self.latex_panel = MathJaxPanel() + + # ========== LAYOUT DE 3 PANELES ========== + # Paneles 1 y 2 tienen mismo ancho, panel 3 es más estrecho + main_layout.addWidget(self.input_text, 1) + main_layout.addWidget(self.output_text, 1) + main_layout.addWidget(self.latex_panel, 0) + + # ========== CONFIGURAR TAGS DE SALIDA ========== + self.setup_output_tags() + + # ========== MENÚ Y BARRA DE ESTADO ========== + self.create_menu_bar() + self.create_status_bar() + + def setup_output_tags(self): + """Configura los tags de formato para la salida - COMO EN ORIGINAL""" + doc = self.output_text.document() + + # Crear formatos de texto + self.tag_formats = {} + + # Error + error_format = QTextCharFormat() + error_format.setForeground(QColor("#ff6b6b")) + self.tag_formats['error'] = error_format + + # Resultado simbólico + symbolic_format = QTextCharFormat() + symbolic_format.setForeground(QColor("#c3e88d")) + self.tag_formats['symbolic'] = symbolic_format + + # Resultado numérico + numeric_format = QTextCharFormat() + numeric_format.setForeground(QColor("#89ddff")) + self.tag_formats['numeric'] = numeric_format + + # Tipo personalizado + custom_format = QTextCharFormat() + custom_format.setForeground(QColor("#f78c6c")) + self.tag_formats['custom'] = custom_format + + def _on_input_changed(self): + """Maneja cambios en la entrada con debounce - COMO EN ORIGINAL""" + self._debounce_timer.start(300) # 300ms delay + + def _evaluate_and_update(self): + """Evalúa la entrada y actualiza la salida - USANDO MOTOR ORIGINAL""" + try: + # Obtener líneas de entrada + input_text = self.input_text.toPlainText() + lines = input_text.split('\n') + + # Evaluar usando el motor original + self._evaluate_lines(lines) + + except Exception as e: + self.logger.error(f"Error en evaluación: {e}") + self._show_error(str(e)) + + def _evaluate_lines(self, lines: List[str]): + """Evalúa líneas usando el motor original - SIN CAMBIOS EN LÓGICA""" + try: + # Limpiar panel LaTeX + self.latex_panel.clear_equations() + + # Limpiar salida + self.output_text.clear() + + # Evaluar cada línea + output_lines = [] + for i, line in enumerate(lines): + line = line.strip() + + if not line or line.startswith('#'): + # Línea vacía o comentario + output_lines.append("") + if line.startswith('#'): + # Añadir comentario al panel LaTeX + comment_text = line[1:].strip() + if comment_text: + self.latex_panel.add_equation("comment", comment_text) + else: + try: + # Evaluar usando el motor original + result = self.engine.evaluate_line(line) + + # Procesar resultado + output_data = self._process_evaluation_result(result) + if output_data: + output_lines.append(output_data[0][1]) # Tomar el texto del resultado + + # Añadir al panel LaTeX si es aplicable + self._add_to_latex_panel_if_applicable(result) + else: + output_lines.append("") + + except Exception as e: + error_msg = f"❌ {str(e)}" + output_lines.append(error_msg) + self.logger.error(f"Error evaluando línea '{line}': {e}") + + # Mostrar salida manteniendo correspondencia 1:1 + self._display_output_lines(output_lines) + + except Exception as e: + self.logger.error(f"Error en _evaluate_lines: {e}") + self._show_error(str(e)) + + def _process_evaluation_result(self, result: EvaluationResult) -> List[tuple]: + """Procesa resultado de evaluación - USANDO ESTRUCTURA ORIGINAL""" + if not result.success: + return [("error", f"❌ {result.error_message}")] + + output_data = [] + + # El resultado principal está en result.output + if result.output: + # Determinar el tipo de formato basado en result_type + if result.result_type == "error": + output_data.append(("error", f"❌ {result.output}")) + elif result.result_type == "plot": + output_data.append(("plot", f"📈 {result.output}")) + elif result.result_type == "symbolic": + output_data.append(("symbolic", f"📊 {result.output}")) + elif result.result_type == "numeric": + output_data.append(("numeric", f"🔢 {result.output}")) + else: + # Por defecto, mostrar como simbólico + output_data.append(("symbolic", result.output)) + + # Si no hay resultados, mostrar algo + if not output_data: + output_data.append(("symbolic", "✓")) + + return output_data + + def _display_output_lines(self, output_lines: List[str]): + """Muestra líneas de salida manteniendo correspondencia 1:1""" + self.output_text.clear() + + for i, line in enumerate(output_lines): + if i > 0: + self.output_text.append("") # Nueva línea + + # Determinar formato basado en contenido + if line.startswith("❌"): + self._append_formatted_text(line, self.tag_formats['error']) + elif line.startswith("📊"): + self._append_formatted_text(line, self.tag_formats['symbolic']) + elif line.startswith("🔢"): + self._append_formatted_text(line, self.tag_formats['numeric']) + else: + self._append_formatted_text(line, self.tag_formats.get('custom', None)) + + def _append_formatted_text(self, text: str, format_obj: QTextCharFormat = None): + """Añade texto formateado al panel de salida""" + cursor = self.output_text.textCursor() + cursor.movePosition(QTextCursor.End) + + if format_obj: + cursor.insertText(text, format_obj) + else: + cursor.insertText(text) + + def _add_to_latex_panel_if_applicable(self, result: EvaluationResult): + """Añade resultado al panel LaTeX - USANDO ESTRUCTURA ORIGINAL""" + if not result.success: + return + + try: + # Determinar tipo de ecuación basado en flags del result + if result.is_assignment: + equation_type = "assignment" + elif result.is_equation: + equation_type = "equation" + else: + equation_type = "symbolic" + + # Generar LaTeX del objeto resultado actual + if result.actual_result_object is not None: + latex_content = self._sympy_to_latex(result.actual_result_object) + if latex_content: + self.latex_panel.add_equation(equation_type, latex_content) + + except Exception as e: + self.logger.error(f"Error añadiendo al panel LaTeX: {e}") + + def _sympy_to_latex(self, sympy_obj) -> str: + """Convierte objeto SymPy a LaTeX - COMO EN ORIGINAL""" + try: + if hasattr(sympy_obj, 'latex'): + return sympy_obj.latex() + elif hasattr(sympy, 'latex'): + return sympy.latex(sympy_obj) + else: + return str(sympy_obj) + except Exception as e: + self.logger.error(f"Error convirtiendo a LaTeX: {e}") + return str(sympy_obj) + + def _show_error(self, error_msg: str): + """Muestra mensaje de error""" + self.output_text.append(f"❌ Error: {error_msg}") + self.statusBar().showMessage(f"❌ {error_msg}", 5000) + + def get_minimal_dark_theme(self): + """Tema oscuro minimalista""" + return """ + QMainWindow { + background-color: #1e1e1e; + color: #d4d4d4; + } + QPlainTextEdit, QTextEdit { + background-color: #252526; + color: #d4d4d4; + border: 1px solid #3c3c3c; + selection-background-color: #264f78; + padding: 8px; + } + QMenuBar { + background-color: #2d2d30; + color: #d4d4d4; + border-bottom: 1px solid #3c3c3c; + padding: 2px; + } + QMenuBar::item:selected { + background-color: #094771; + } + QMenu { + background-color: #2d2d30; + color: #d4d4d4; + border: 1px solid #3c3c3c; + } + QMenu::item:selected { + background-color: #094771; + } + QStatusBar { + background-color: #007acc; + color: white; + border: none; + } + """ + + def setup_shortcuts(self): + """Configura atajos de teclado""" + # Evaluar manual (forzar evaluación inmediata) + eval_shortcut = QShortcut(QKeySequence("Ctrl+Return"), self) + eval_shortcut.activated.connect(self._evaluate_and_update) + + eval_shortcut2 = QShortcut(QKeySequence("Shift+Return"), self) + eval_shortcut2.activated.connect(self._evaluate_and_update) + + # Toggle LaTeX panel + latex_shortcut = QShortcut(QKeySequence("F12"), self) + latex_shortcut.activated.connect(self.toggle_latex_panel) + + def create_menu_bar(self): + """Crea la barra de menú minimalista""" + menubar = self.menuBar() + + # Menú Archivo + file_menu = menubar.addMenu("Archivo") + + new_action = file_menu.addAction("Nuevo") + new_action.setShortcut(QKeySequence.New) + new_action.triggered.connect(self.new_session) + + open_action = file_menu.addAction("Abrir...") + open_action.setShortcut(QKeySequence.Open) + open_action.triggered.connect(self.load_file) + + save_action = file_menu.addAction("Guardar...") + save_action.setShortcut(QKeySequence.Save) + save_action.triggered.connect(self.save_file) + + file_menu.addSeparator() + exit_action = file_menu.addAction("Salir") + exit_action.triggered.connect(self.close) + + # Menú Ver + view_menu = menubar.addMenu("Ver") + toggle_latex_action = view_menu.addAction("Mostrar/Ocultar LaTeX") + toggle_latex_action.setShortcut(QKeySequence("F12")) + toggle_latex_action.triggered.connect(self.toggle_latex_panel) + + # Menú Ayuda + help_menu = menubar.addMenu("Ayuda") + about_action = help_menu.addAction("Acerca de...") + about_action.triggered.connect(self.show_about) + + def create_status_bar(self): + """Crea la barra de estado""" + self.statusBar().showMessage("🔢 Calculadora MAV - Sistema Algebraico Híbrido") + + def toggle_latex_panel(self): + """Muestra/oculta el panel LaTeX""" + self.latex_panel_visible = not self.latex_panel_visible + + if self.latex_panel_visible: + self.latex_panel.show() + else: + self.latex_panel.hide() + + def new_session(self): + """Inicia una nueva sesión""" + self.input_text.clear() + self.output_text.clear() + self.latex_panel.clear_equations() + self.statusBar().showMessage("✨ Nueva sesión iniciada", 2000) + + def load_file(self): + """Carga un archivo""" + file_path, _ = QFileDialog.getOpenFileName( + self, "Abrir archivo", "", "Archivos de texto (*.txt);;Todos los archivos (*)" + ) + if file_path: + try: + with open(file_path, 'r', encoding='utf-8') as f: + content = f.read() + self.input_text.setPlainText(content) + self.statusBar().showMessage(f"📁 Archivo cargado: {Path(file_path).name}", 3000) + except Exception as e: + QMessageBox.warning(self, "Error", f"No se pudo cargar el archivo:\n{e}") + + def save_file(self): + """Guarda el contenido actual""" + file_path, _ = QFileDialog.getSaveFileName( + self, "Guardar archivo", "", "Archivos de texto (*.txt);;Todos los archivos (*)" + ) + if file_path: + try: + with open(file_path, 'w', encoding='utf-8') as f: + f.write(self.input_text.toPlainText()) + self.statusBar().showMessage(f"💾 Archivo guardado: {Path(file_path).name}", 3000) + except Exception as e: + QMessageBox.warning(self, "Error", f"No se pudo guardar el archivo:\n{e}") + + def show_about(self): + """Muestra información sobre la aplicación""" + QMessageBox.about( + self, + "Acerca de Calculadora MAV", + """ +

Calculadora MAV - CAS Híbrido

+

Versión PySide6 con diseño minimalista

+

Sistema algebraico computacional híbrido con 3 paneles:

+ +

Motor algebraico: SymPy con tipos personalizados

+ """ + ) + + def load_settings(self) -> Dict[str, Any]: + """Carga configuración desde archivo""" + try: + if Path(self.SETTINGS_FILE).exists(): + with open(self.SETTINGS_FILE, 'r', encoding='utf-8') as f: + return json.load(f) + except Exception as e: + self.logger.warning(f"No se pudo cargar configuración: {e}") + + return { + "window_geometry": "1400x800", + "debug_mode": False, + "latex_panel_visible": True + } + + def save_settings(self): + """Guarda configuración a archivo""" + try: + settings = { + "window_geometry": f"{self.width()}x{self.height()}", + "debug_mode": self.debug, + "latex_panel_visible": self.latex_panel_visible + } + + with open(self.SETTINGS_FILE, 'w', encoding='utf-8') as f: + json.dump(settings, f, indent=2, ensure_ascii=False) + + except Exception as e: + self.logger.error(f"Error guardando configuración: {e}") + + def load_history(self): + """Carga historial desde archivo""" + try: + if Path(self.HISTORY_FILE).exists(): + with open(self.HISTORY_FILE, 'r', encoding='utf-8') as f: + history = f.read().strip() + if history: + self.input_text.setPlainText(history) + except Exception as e: + self.logger.warning(f"No se pudo cargar historial: {e}") + + def save_history(self): + """Guarda historial a archivo""" + try: + with open(self.HISTORY_FILE, 'w', encoding='utf-8') as f: + f.write(self.input_text.toPlainText()) + except Exception as e: + self.logger.error(f"Error guardando historial: {e}") + + def closeEvent(self, event): + """Maneja el evento de cierre de la aplicación""" + self.save_settings() + self.save_history() + event.accept() + + +def main(): + """Función principal""" + app = QApplication(sys.argv) + app.setApplicationName("Calculadora MAV") + app.setApplicationVersion("2.0.0") + + # Configurar estilo + app.setStyle('Fusion') + + # Crear y mostrar ventana principal + window = HybridCalculatorPySide6() + window.show() + + # Ejecutar aplicación + sys.exit(app.exec()) + + +if __name__ == "__main__": + main() \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index 66cd75f..be0a3c5 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,12 +1,12 @@ # requirements.txt -# Calculadora MAV - CAS Híbrido +# Calculadora MAV - CAS Híbrido con PySide6 y MathJax # Dependencias requeridas # Motor algebraico principal sympy>=1.12 -# Interfaz gráfica (generalmente incluido con Python) -# tkinter - incluido con Python estándar +# Interfaz gráfica moderna +PySide6>=6.6.0 # Plotting y visualización matplotlib>=3.7.0 diff --git a/test.py b/test.py index a522b3f..1f9d3f4 100644 --- a/test.py +++ b/test.py @@ -1,37 +1,332 @@ -import webview +# OPCIÓN 1: PySide6 (RECOMENDADA) +# pip install PySide6 -def create_webview_app(): - html_content = """ - - - - - - - - - -

MathJax con webview

-
-

Ecuación simple: $x^2 + y^2 = z^2$

-

Ecuación compleja:

- $$\\sum_{n=1}^{\\infty} \\frac{1}{n^2} = \\frac{\\pi^2}{6}$$ -
- - - """ +import sys +from PySide6.QtWidgets import QApplication, QMainWindow, QVBoxLayout, QWidget, QPushButton, QHBoxLayout +from PySide6.QtWebEngineWidgets import QWebEngineView +from PySide6.QtCore import QUrl + +class MathJaxPySide6(QMainWindow): + def __init__(self): + super().__init__() + self.setWindowTitle("🧮 MathJax con PySide6") + self.setGeometry(100, 100, 1200, 800) + + # Widget central + central_widget = QWidget() + self.setCentralWidget(central_widget) + + # Layout principal + layout = QVBoxLayout(central_widget) + + # Botones de control + button_layout = QHBoxLayout() + + btn_reload = QPushButton("🔄 Recargar") + btn_zoom_in = QPushButton("🔍+ Zoom In") + btn_zoom_out = QPushButton("🔍- Zoom Out") + btn_fullscreen = QPushButton("📺 Pantalla Completa") + + btn_reload.clicked.connect(self.reload_page) + btn_zoom_in.clicked.connect(self.zoom_in) + btn_zoom_out.clicked.connect(self.zoom_out) + btn_fullscreen.clicked.connect(self.toggle_fullscreen) + + button_layout.addWidget(btn_reload) + button_layout.addWidget(btn_zoom_in) + button_layout.addWidget(btn_zoom_out) + button_layout.addWidget(btn_fullscreen) + button_layout.addStretch() + + layout.addLayout(button_layout) + + # Vista web + self.web_view = QWebEngineView() + layout.addWidget(self.web_view) + + # HTML con MathJax avanzado + self.html_content = """ + + + + + MathJax Avanzado + + + + + + +
+

🚀 Laboratorio Matemático Avanzado

+ +
+

📊 Álgebra Lineal

+

Determinante de matriz 3×3:

+ $$\\det(A) = \\begin{vmatrix} + a_{11} & a_{12} & a_{13} \\\\ + a_{21} & a_{22} & a_{23} \\\\ + a_{31} & a_{32} & a_{33} + \\end{vmatrix}$$ + +

Eigenvalores: $\\det(A - \\lambda I) = 0$

+
+ +
+

⚛️ Física Cuántica

+

Operador Hamiltoniano:

+ $$\\hat{H} = \\frac{-\\hbar^2}{2m}\\nabla^2 + V(\\mathbf{r})$$ + +

Función de onda normalizada:

+ $$\\int_{-\\infty}^{\\infty} |\\Psi(x,t)|^2 dx = 1$$ +
+ +
+

🔬 Matemáticas Avanzadas

+

Transformada de Fourier:

+ $$\\mathcal{F}[f(t)] = \\int_{-\\infty}^{\\infty} f(t) e^{-2\\pi i \\xi t} dt$$ + +

Función Gamma:

+ $$\\Gamma(z) = \\int_0^\\infty t^{z-1} e^{-t} dt$$ +
+ +
+

🎮 Generador Interactivo

+ + + + + + +
+
+
+ + + + + """ + + # Cargar contenido + self.web_view.setHtml(self.html_content) - webview.create_window('MathJax App', html=html_content, width=800, height=600) - webview.start() + def reload_page(self): + self.web_view.setHtml(self.html_content) + + def zoom_in(self): + current_zoom = self.web_view.zoomFactor() + self.web_view.setZoomFactor(current_zoom * 1.2) + + def zoom_out(self): + current_zoom = self.web_view.zoomFactor() + self.web_view.setZoomFactor(current_zoom / 1.2) + + def toggle_fullscreen(self): + if self.isFullScreen(): + self.showNormal() + else: + self.showFullScreen() +# OPCIÓN 2: PyQt6 (Alternativa) +# pip install PyQt6 PyQt6-WebEngine + +""" +import sys +from PyQt6.QtWidgets import QApplication, QMainWindow, QVBoxLayout, QWidget +from PyQt6.QtWebEngineWidgets import QWebEngineView + +class MathJaxPyQt6(QMainWindow): + def __init__(self): + super().__init__() + self.setWindowTitle("MathJax con PyQt6") + self.setGeometry(100, 100, 1000, 700) + + central_widget = QWidget() + self.setCentralWidget(central_widget) + layout = QVBoxLayout(central_widget) + + self.web_view = QWebEngineView() + layout.addWidget(self.web_view) + + # Mismo HTML que PySide6 + html_content = "[MISMO HTML DE ARRIBA]" + self.web_view.setHtml(html_content) + +def run_pyqt6(): + app = QApplication(sys.argv) + window = MathJaxPyQt6() + window.show() + sys.exit(app.exec()) +""" + +def run_pyside6(): + app = QApplication(sys.argv) + window = MathJaxPySide6() + window.show() + sys.exit(app.exec()) -# Función principal para elegir método if __name__ == "__main__": - create_webview_app() + print("🧮 Iniciando aplicación MathJax con PySide6...") + print("✨ Funciones disponibles:") + print(" - Zoom in/out") + print(" - Pantalla completa") + print(" - Generador interactivo de ecuaciones") + print(" - MathJax completamente funcional") + + run_pyside6() + +# RESUMEN DE INSTALACIÓN: +# +# Para PySide6 (RECOMENDADO): +# pip install PySide6 +# +# Para PyQt6: +# pip install PyQt6 PyQt6-WebEngine +# +# Ambos funcionan igual de bien para MathJax, +# pero PySide6 tiene licencia más permisiva. \ No newline at end of file diff --git a/test_pyside6.py b/test_pyside6.py new file mode 100644 index 0000000..d893bb0 --- /dev/null +++ b/test_pyside6.py @@ -0,0 +1,212 @@ +#!/usr/bin/env python3 +""" +Script de prueba para la versión PySide6 de Calculadora MAV +""" +import sys +import traceback +from pathlib import Path + +def test_imports(): + """Prueba todas las importaciones necesarias""" + print("🧪 Probando importaciones...") + + tests = [ + ("PySide6.QtWidgets", "QApplication"), + ("PySide6.QtCore", "QThread"), + ("PySide6.QtGui", "QFont"), + ("PySide6.QtWebEngineWidgets", "QWebEngineView"), + ("sympy", None), + ("numpy", None), + ("matplotlib", None), + ] + + success_count = 0 + for module, attr in tests: + try: + if attr: + mod = __import__(module, fromlist=[attr]) + getattr(mod, attr) + else: + __import__(module) + print(f" ✅ {module}" + (f".{attr}" if attr else "")) + success_count += 1 + except ImportError as e: + print(f" ❌ {module}" + (f".{attr}" if attr else "") + f" - {e}") + except Exception as e: + print(f" ⚠️ {module}" + (f".{attr}" if attr else "") + f" - {e}") + + print(f"\n📊 Resultado: {success_count}/{len(tests)} módulos disponibles") + return success_count == len(tests) + +def test_application_creation(): + """Prueba la creación básica de la aplicación""" + print("\n🏗️ Probando creación de aplicación...") + + try: + from PySide6.QtWidgets import QApplication + from PySide6.QtCore import QCoreApplication + + # Crear aplicación mínima + if not QCoreApplication.instance(): + app = QApplication([]) + else: + app = QCoreApplication.instance() + + print(" ✅ QApplication creada correctamente") + + # Probar importación de nuestra aplicación + from main_calc_app_pyside6 import HybridCalculatorPySide6 + print(" ✅ Clase HybridCalculatorPySide6 importada") + + # Probar creación de ventana (sin mostrar) + window = HybridCalculatorPySide6() + print(" ✅ Ventana principal creada") + + # Verificar componentes principales + assert hasattr(window, 'input_text'), "input_text no encontrado" + assert hasattr(window, 'output_text'), "output_text no encontrado" + assert hasattr(window, 'latex_panel'), "latex_panel no encontrado" + assert hasattr(window, 'engine'), "engine no encontrado" + print(" ✅ Componentes principales verificados") + + return True + + except Exception as e: + print(f" ❌ Error: {e}") + traceback.print_exc() + return False + +def test_engine_functionality(): + """Prueba la funcionalidad del motor de cálculo""" + print("\n⚙️ Probando motor de cálculo...") + + try: + from main_evaluation_puro import PureAlgebraicEngine + + engine = PureAlgebraicEngine() + print(" ✅ Motor PureAlgebraicEngine creado") + + # Prueba básica + result = engine.evaluate_line("2 + 2") + print(f" ✅ Evaluación básica: 2 + 2 = {result.output if result.success else result.error_message}") + + # Prueba simbólica + result = engine.evaluate_line("x**2 + y**2") + print(f" ✅ Evaluación simbólica: x**2 + y**2 = {result.output if result.success else result.error_message}") + + return True + + except Exception as e: + print(f" ❌ Error: {e}") + traceback.print_exc() + return False + +def test_mathjax_html(): + """Prueba la generación de HTML MathJax""" + print("\n📐 Probando generación MathJax...") + + try: + from main_calc_app_pyside6 import MathJaxPanel + from PySide6.QtWidgets import QApplication + from PySide6.QtCore import QCoreApplication + + if not QCoreApplication.instance(): + app = QApplication([]) + + panel = MathJaxPanel() + print(" ✅ Panel MathJax creado") + + # Verificar HTML base + html = panel.generate_base_html() + assert "MathJax" in html, "MathJax no encontrado en HTML" + assert "equation" in html, "Estilos de ecuación no encontrados" + print(" ✅ HTML base generado correctamente") + + return True + + except Exception as e: + print(f" ❌ Error: {e}") + traceback.print_exc() + return False + +def run_minimal_app(): + """Ejecuta una versión mínima de la aplicación para prueba visual""" + print("\n🖥️ Iniciando prueba visual (cierra la ventana para continuar)...") + + try: + from PySide6.QtWidgets import QApplication + from main_calc_app_pyside6 import HybridCalculatorPySide6 + + app = QApplication(sys.argv) + window = HybridCalculatorPySide6() + + # Precargar algunos datos de prueba + window.input_text.setPlainText("# Prueba PySide6\nx**2 + y**2 = r**2\nsolve(x**2 - 4, x)") + + window.show() + print(" ✅ Aplicación mostrada - cierra la ventana para continuar") + + # No ejecutar el loop principal, solo mostrar + return True + + except Exception as e: + print(f" ❌ Error: {e}") + traceback.print_exc() + return False + +def main(): + """Función principal de pruebas""" + print("🚀 Iniciando pruebas de Calculadora MAV PySide6") + print("=" * 60) + + tests = [ + ("Importaciones", test_imports), + ("Creación de aplicación", test_application_creation), + ("Motor de cálculo", test_engine_functionality), + ("HTML MathJax", test_mathjax_html), + ] + + results = [] + for test_name, test_func in tests: + try: + result = test_func() + results.append((test_name, result)) + except Exception as e: + print(f"💥 Error inesperado en {test_name}: {e}") + results.append((test_name, False)) + + # Resumen de resultados + print("\n" + "=" * 60) + print("📋 RESUMEN DE PRUEBAS") + print("=" * 60) + + passed = 0 + total = len(results) + + for test_name, success in results: + status = "✅ PASÓ" if success else "❌ FALLÓ" + print(f" {status:<10} {test_name}") + if success: + passed += 1 + + print(f"\n📊 Resultado final: {passed}/{total} pruebas pasaron") + + if passed == total: + print("🎉 ¡Todas las pruebas pasaron! La aplicación está lista.") + + # Ofrecer ejecutar prueba visual + try: + response = input("\n¿Quieres ejecutar una prueba visual? (s/N): ").strip().lower() + if response in ['s', 'sí', 'si', 'y', 'yes']: + run_minimal_app() + except KeyboardInterrupt: + print("\n🚪 Prueba cancelada por el usuario") + + else: + print("⚠️ Algunas pruebas fallaron. Revisa los errores antes de ejecutar la aplicación.") + return 1 + + return 0 + +if __name__ == "__main__": + sys.exit(main()) \ No newline at end of file diff --git a/test_simple_engine.py b/test_simple_engine.py new file mode 100644 index 0000000..7d6e5d7 --- /dev/null +++ b/test_simple_engine.py @@ -0,0 +1,72 @@ +#!/usr/bin/env python3 +""" +Prueba simple del motor PureAlgebraicEngine para verificar la API +""" +import sys +from pathlib import Path + +def test_engine(): + """Prueba básica del motor""" + print("🧪 Probando PureAlgebraicEngine...") + + try: + # Importar el motor + from main_evaluation_puro import PureAlgebraicEngine + print(" ✅ Import exitoso") + + # Crear instancia + engine = PureAlgebraicEngine() + print(" ✅ Instancia creada") + + # Pruebas básicas + test_cases = [ + "2 + 2", + "x**2 + y**2", + "solve(x**2 - 4, x)", + "# Esto es un comentario", + "a = 5*x + 3", + "x**2 + y**2 = r**2" + ] + + print("\n📝 Ejecutando casos de prueba:") + for i, test_case in enumerate(test_cases, 1): + try: + result = engine.evaluate_line(test_case) + status = "✅" if result.success else "❌" + output = result.output if result.success else result.error_message + print(f" {i}. {status} '{test_case}' → {output}") + + # Información adicional del resultado + if result.success: + print(f" Tipo: {result.result_type}, Asignación: {result.is_assignment}, Ecuación: {result.is_equation}") + + except Exception as e: + print(f" {i}. ❌ '{test_case}' → ERROR: {e}") + + print("\n✅ Pruebas del motor completadas") + return True + + except Exception as e: + print(f"❌ Error en prueba del motor: {e}") + import traceback + traceback.print_exc() + return False + +def main(): + """Función principal""" + print("🚀 Prueba Simple del Motor Algebraico") + print("=" * 50) + + success = test_engine() + + if success: + print("\n🎉 ¡Motor funcionando correctamente!") + print(" Puedes proceder a usar la aplicación PySide6") + else: + print("\n⚠️ Problemas detectados en el motor") + print(" Revisa los errores antes de usar la aplicación") + + return 0 if success else 1 + +if __name__ == "__main__": + sys.exit(main()) \ No newline at end of file