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:
+
+ - Panel de entrada (izquierda)
+ - Panel de resultados 1:1 (centro)
+ - Panel LaTeX/MathJax (derecha)
+
+ 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