+ {/* Navbar fija en la parte superior */}
+
+
+ {/* Contenedor principal con sidebar y contenido */}
+
+ {/* Sidebar lateral como overlay */}
+
+
+ {/* Área de contenido principal - siempre ocupa todo el ancho */}
+
+ {children}
+
+
+
+ {/* Overlay para cuando el sidebar está abierto */}
+ {ui.sidebarVisible && (
+
actions.toggleSidebar()}
+ />
+ )}
+
+ );
+};
+
+export default MainLayout;
diff --git a/Aplicativo web Caja de polinomios/src/contexts/AppContext.js b/Aplicativo web Caja de polinomios/src/contexts/AppContext.js
new file mode 100644
index 0000000..8811961
--- /dev/null
+++ b/Aplicativo web Caja de polinomios/src/contexts/AppContext.js
@@ -0,0 +1,234 @@
+import React, { createContext, useContext, useReducer } from 'react';
+
+// Tipos de acciones
+const ActionTypes = {
+ SET_POLYNOMIAL: 'SET_POLYNOMIAL',
+ SET_OPERATION: 'SET_OPERATION',
+ SET_RESULT: 'SET_RESULT',
+ SET_STEP_BY_STEP: 'SET_STEP_BY_STEP',
+ SET_SIDEBAR_VISIBLE: 'SET_SIDEBAR_VISIBLE',
+ SET_HELP_VISIBLE: 'SET_HELP_VISIBLE',
+ SET_ZOOM: 'SET_ZOOM',
+ SET_PAN: 'SET_PAN',
+ ADD_POLYNOMIAL_CHIP: 'ADD_POLYNOMIAL_CHIP',
+ REMOVE_POLYNOMIAL_CHIP: 'REMOVE_POLYNOMIAL_CHIP',
+ CLEAR_PLANE: 'CLEAR_PLANE',
+ SET_CURRENT_STEP: 'SET_CURRENT_STEP'
+};
+
+// Estado inicial
+const initialState = {
+ // Polinomios
+ polynomials: {
+ first: '',
+ second: ''
+ },
+
+ // Operación actual
+ currentOperation: null, // 'addition', 'subtraction', 'multiplication', 'division'
+
+ // Resultado
+ result: null,
+
+ // Pasos de resolución
+ stepByStep: [],
+ currentStep: 0,
+
+ // UI State
+ ui: {
+ sidebarVisible: true,
+ helpVisible: false,
+ currentView: 'input' // 'input', 'steps', 'result'
+ },
+
+ // Plano cartesiano
+ plane: {
+ zoom: 1,
+ panX: 0,
+ panY: 0,
+ chips: []
+ },
+
+ // Configuraciones
+ settings: {
+ showGrid: true,
+ showAxes: true,
+ animationSpeed: 1
+ }
+};
+
+// Reducer
+function appReducer(state, action) {
+ switch (action.type) {
+ case ActionTypes.SET_POLYNOMIAL:
+ return {
+ ...state,
+ polynomials: {
+ ...state.polynomials,
+ [action.payload.type]: action.payload.value
+ }
+ };
+
+ case ActionTypes.SET_OPERATION:
+ return {
+ ...state,
+ currentOperation: action.payload,
+ result: null,
+ stepByStep: [],
+ currentStep: 0
+ };
+
+ case ActionTypes.SET_RESULT:
+ return {
+ ...state,
+ result: action.payload
+ };
+
+ case ActionTypes.SET_STEP_BY_STEP:
+ return {
+ ...state,
+ stepByStep: action.payload,
+ currentStep: 0
+ };
+
+ case ActionTypes.SET_CURRENT_STEP:
+ return {
+ ...state,
+ currentStep: action.payload
+ };
+
+ case ActionTypes.SET_SIDEBAR_VISIBLE:
+ return {
+ ...state,
+ ui: {
+ ...state.ui,
+ sidebarVisible: action.payload
+ }
+ };
+
+ case ActionTypes.SET_HELP_VISIBLE:
+ return {
+ ...state,
+ ui: {
+ ...state.ui,
+ helpVisible: action.payload
+ }
+ };
+
+ case ActionTypes.SET_ZOOM:
+ return {
+ ...state,
+ plane: {
+ ...state.plane,
+ zoom: action.payload
+ }
+ };
+
+ case ActionTypes.SET_PAN:
+ return {
+ ...state,
+ plane: {
+ ...state.plane,
+ panX: action.payload.x,
+ panY: action.payload.y
+ }
+ };
+
+ case ActionTypes.ADD_POLYNOMIAL_CHIP:
+ return {
+ ...state,
+ plane: {
+ ...state.plane,
+ chips: [...state.plane.chips, action.payload]
+ }
+ };
+
+ case ActionTypes.REMOVE_POLYNOMIAL_CHIP:
+ return {
+ ...state,
+ plane: {
+ ...state.plane,
+ chips: state.plane.chips.filter(chip => chip.id !== action.payload)
+ }
+ };
+
+ case ActionTypes.CLEAR_PLANE:
+ return {
+ ...state,
+ plane: {
+ ...state.plane,
+ chips: []
+ }
+ };
+
+ default:
+ return state;
+ }
+}
+
+// Context
+const AppContext = createContext();
+
+// Provider
+export function AppProvider({ children }) {
+ const [state, dispatch] = useReducer(appReducer, initialState);
+
+ // Action creators
+ const actions = {
+ setPolynomial: (type, value) =>
+ dispatch({ type: ActionTypes.SET_POLYNOMIAL, payload: { type, value } }),
+
+ setOperation: (operation) =>
+ dispatch({ type: ActionTypes.SET_OPERATION, payload: operation }),
+
+ setResult: (result) =>
+ dispatch({ type: ActionTypes.SET_RESULT, payload: result }),
+
+ setStepByStep: (steps) =>
+ dispatch({ type: ActionTypes.SET_STEP_BY_STEP, payload: steps }),
+
+ setCurrentStep: (step) =>
+ dispatch({ type: ActionTypes.SET_CURRENT_STEP, payload: step }),
+
+ toggleSidebar: () =>
+ dispatch({ type: ActionTypes.SET_SIDEBAR_VISIBLE, payload: !state.ui.sidebarVisible }),
+
+ setSidebarVisible: (visible) =>
+ dispatch({ type: ActionTypes.SET_SIDEBAR_VISIBLE, payload: visible }),
+
+ toggleHelp: () =>
+ dispatch({ type: ActionTypes.SET_HELP_VISIBLE, payload: !state.ui.helpVisible }),
+
+ setZoom: (zoom) =>
+ dispatch({ type: ActionTypes.SET_ZOOM, payload: zoom }),
+
+ setPan: (x, y) =>
+ dispatch({ type: ActionTypes.SET_PAN, payload: { x, y } }),
+
+ addChip: (chip) =>
+ dispatch({ type: ActionTypes.ADD_POLYNOMIAL_CHIP, payload: chip }),
+
+ removeChip: (chipId) =>
+ dispatch({ type: ActionTypes.REMOVE_POLYNOMIAL_CHIP, payload: chipId }),
+
+ clearPlane: () =>
+ dispatch({ type: ActionTypes.CLEAR_PLANE })
+ };
+
+ return (
+
+ {children}
+
+ );
+}
+
+// Hook personalizado
+export function useApp() {
+ const context = useContext(AppContext);
+ if (!context) {
+ throw new Error('useApp debe ser usado dentro de AppProvider');
+ }
+ return context;
+}
+
+export default AppContext;
diff --git a/Aplicativo web Caja de polinomios/src/index.js b/Aplicativo web Caja de polinomios/src/index.js
new file mode 100644
index 0000000..c85c8ff
--- /dev/null
+++ b/Aplicativo web Caja de polinomios/src/index.js
@@ -0,0 +1,11 @@
+import React from 'react';
+import ReactDOM from 'react-dom/client';
+import './styles/globals.css';
+import App from './App';
+
+const root = ReactDOM.createRoot(document.getElementById('root'));
+root.render(
+
+
+
+);
diff --git a/Aplicativo web Caja de polinomios/src/styles/globals.css b/Aplicativo web Caja de polinomios/src/styles/globals.css
new file mode 100644
index 0000000..607f327
--- /dev/null
+++ b/Aplicativo web Caja de polinomios/src/styles/globals.css
@@ -0,0 +1,304 @@
+@import url('https://fonts.googleapis.com/css2?family=Inter:wght@100;200;300;400;500;600;700;800;900&display=swap');
+@import url('https://fonts.googleapis.com/css2?family=Fira+Code:wght@300;400;500;600;700&display=swap');
+@import './variables.css';
+
+/* Reset y configuración base */
+* {
+ margin: 0;
+ padding: 0;
+ box-sizing: border-box;
+}
+
+*,
+*::before,
+*::after {
+ box-sizing: inherit;
+}
+
+/* Prevenir scroll en toda la aplicación */
+html, body, #root {
+ height: 100%;
+ overflow: hidden;
+}
+
+html {
+ font-size: 16px;
+ line-height: 1.5;
+ -webkit-text-size-adjust: 100%;
+ -ms-text-size-adjust: 100%;
+ height: 100%;
+ overflow: hidden;
+}
+
+body {
+ font-family: var(--font-family-primary);
+ font-size: var(--font-size-base);
+ font-weight: 400;
+ line-height: 1.6;
+ color: var(--gray-900);
+ background-color: var(--white);
+ -webkit-font-smoothing: antialiased;
+ -moz-osx-font-smoothing: grayscale;
+ overflow: hidden;
+ height: 100vh;
+ margin: 0;
+ padding: 0;
+}
+
+/* Elementos básicos */
+h1, h2, h3, h4, h5, h6 {
+ margin: 0;
+ font-weight: 600;
+ line-height: 1.25;
+ color: var(--gray-900);
+}
+
+h1 { font-size: var(--font-size-3xl); }
+h2 { font-size: var(--font-size-2xl); }
+h3 { font-size: var(--font-size-xl); }
+h4 { font-size: var(--font-size-lg); }
+h5 { font-size: var(--font-size-base); }
+h6 { font-size: var(--font-size-sm); }
+
+p {
+ margin: 0;
+ color: var(--gray-700);
+}
+
+a {
+ color: var(--primary-color);
+ text-decoration: none;
+ transition: color var(--transition-fast);
+}
+
+a:hover {
+ color: var(--primary-dark);
+ text-decoration: underline;
+}
+
+ul, ol {
+ list-style: none;
+}
+
+img {
+ max-width: 100%;
+ height: auto;
+ display: block;
+}
+
+button {
+ cursor: pointer;
+ border: none;
+ background: none;
+ font-family: inherit;
+ font-size: inherit;
+}
+
+input, textarea, select {
+ font-family: inherit;
+ font-size: inherit;
+ border: none;
+ outline: none;
+}
+
+/* Utilidades de layout */
+.container {
+ width: 100%;
+ max-width: 1200px;
+ margin: 0 auto;
+ padding: 0 var(--spacing-4);
+}
+
+.flex {
+ display: flex;
+}
+
+.flex-col {
+ flex-direction: column;
+}
+
+.items-center {
+ align-items: center;
+}
+
+.justify-center {
+ justify-content: center;
+}
+
+.justify-between {
+ justify-content: space-between;
+}
+
+.gap-2 { gap: var(--spacing-2); }
+.gap-3 { gap: var(--spacing-3); }
+.gap-4 { gap: var(--spacing-4); }
+.gap-6 { gap: var(--spacing-6); }
+.gap-8 { gap: var(--spacing-8); }
+
+/* Utilidades de espaciado */
+.p-2 { padding: var(--spacing-2); }
+.p-3 { padding: var(--spacing-3); }
+.p-4 { padding: var(--spacing-4); }
+.p-6 { padding: var(--spacing-6); }
+.p-8 { padding: var(--spacing-8); }
+
+.m-2 { margin: var(--spacing-2); }
+.m-3 { margin: var(--spacing-3); }
+.m-4 { margin: var(--spacing-4); }
+.m-6 { margin: var(--spacing-6); }
+.m-8 { margin: var(--spacing-8); }
+
+.mb-2 { margin-bottom: var(--spacing-2); }
+.mb-3 { margin-bottom: var(--spacing-3); }
+.mb-4 { margin-bottom: var(--spacing-4); }
+.mb-6 { margin-bottom: var(--spacing-6); }
+
+.mt-2 { margin-top: var(--spacing-2); }
+.mt-3 { margin-top: var(--spacing-3); }
+.mt-4 { margin-top: var(--spacing-4); }
+.mt-6 { margin-top: var(--spacing-6); }
+
+/* Utilidades de texto */
+.text-center { text-align: center; }
+.text-left { text-align: left; }
+.text-right { text-align: right; }
+
+.font-bold { font-weight: 700; }
+.font-semibold { font-weight: 600; }
+.font-medium { font-weight: 500; }
+.font-normal { font-weight: 400; }
+
+.text-xs { font-size: var(--font-size-xs); }
+.text-sm { font-size: var(--font-size-sm); }
+.text-base { font-size: var(--font-size-base); }
+.text-lg { font-size: var(--font-size-lg); }
+.text-xl { font-size: var(--font-size-xl); }
+
+/* Colores de texto */
+.text-gray-500 { color: var(--gray-500); }
+.text-gray-600 { color: var(--gray-600); }
+.text-gray-700 { color: var(--gray-700); }
+.text-gray-900 { color: var(--gray-900); }
+.text-primary { color: var(--primary-color); }
+.text-secondary { color: var(--secondary-color); }
+.text-success { color: var(--success-color); }
+.text-warning { color: var(--warning-color); }
+.text-error { color: var(--error-color); }
+
+/* Utilidades de visibilidad */
+.hidden { display: none; }
+.visible { display: block; }
+
+.sr-only {
+ position: absolute;
+ width: 1px;
+ height: 1px;
+ padding: 0;
+ margin: -1px;
+ overflow: hidden;
+ clip: rect(0, 0, 0, 0);
+ white-space: nowrap;
+ border: 0;
+}
+
+/* Animaciones y transiciones */
+.transition-all {
+ transition: all var(--transition-normal);
+}
+
+.transition-colors {
+ transition: color var(--transition-fast), background-color var(--transition-fast), border-color var(--transition-fast);
+}
+
+.transition-transform {
+ transition: transform var(--transition-normal);
+}
+
+/* Estados interactivos */
+.hover\:scale-105:hover {
+ transform: scale(1.05);
+}
+
+.hover\:shadow-md:hover {
+ box-shadow: var(--shadow-md);
+}
+
+.focus\:outline-none:focus {
+ outline: 2px solid transparent;
+ outline-offset: 2px;
+}
+
+.focus\:ring-2:focus {
+ --tw-ring-offset-shadow: var(--tw-ring-inset) 0 0 0 var(--tw-ring-offset-width) var(--tw-ring-offset-color);
+ --tw-ring-shadow: var(--tw-ring-inset) 0 0 0 calc(2px + var(--tw-ring-offset-width)) var(--tw-ring-color);
+ box-shadow: var(--tw-ring-offset-shadow), var(--tw-ring-shadow), var(--tw-shadow, 0 0 #0000);
+}
+
+.focus\:ring-primary:focus {
+ --tw-ring-color: var(--primary-color);
+}
+
+/* Responsive utilities */
+@media (max-width: 768px) {
+ .md\:hidden {
+ display: none;
+ }
+
+ .md\:block {
+ display: block;
+ }
+
+ .md\:flex {
+ display: flex;
+ }
+}
+
+@media (min-width: 769px) {
+ .md\:block {
+ display: block;
+ }
+
+ .md\:hidden {
+ display: none;
+ }
+}
+
+/* Estilos específicos para matemáticas */
+.math-expression {
+ font-family: var(--font-family-mono);
+ font-size: var(--font-size-lg);
+ font-weight: 500;
+ color: var(--gray-800);
+ background-color: var(--gray-50);
+ padding: var(--spacing-2) var(--spacing-3);
+ border-radius: var(--border-radius-md);
+ border: 1px solid var(--gray-200);
+}
+
+.quadrant-positive {
+ background-color: rgba(16, 185, 129, 0.1);
+ border-color: var(--positive-quadrant);
+}
+
+.quadrant-negative {
+ background-color: rgba(239, 68, 68, 0.1);
+ border-color: var(--negative-quadrant);
+}
+
+/* Scrollbar personalizado */
+::-webkit-scrollbar {
+ width: 8px;
+}
+
+::-webkit-scrollbar-track {
+ background: var(--gray-100);
+}
+
+::-webkit-scrollbar-thumb {
+ background: var(--gray-300);
+ border-radius: var(--border-radius-full);
+}
+
+::-webkit-scrollbar-thumb:hover {
+ background: var(--gray-400);
+}
diff --git a/Aplicativo web Caja de polinomios/src/styles/variables.css b/Aplicativo web Caja de polinomios/src/styles/variables.css
new file mode 100644
index 0000000..ecaac95
--- /dev/null
+++ b/Aplicativo web Caja de polinomios/src/styles/variables.css
@@ -0,0 +1,110 @@
+/* Variables CSS Globales */
+:root {
+ /* Colores principales */
+ --primary-color: #2563eb;
+ --primary-dark: #1d4ed8;
+ --primary-light: #60a5fa;
+
+ /* Colores secundarios */
+ --secondary-color: #10b981;
+ --secondary-dark: #059669;
+ --secondary-light: #34d399;
+
+ /* Colores de estado */
+ --success-color: #10b981;
+ --warning-color: #f59e0b;
+ --error-color: #ef4444;
+ --info-color: #3b82f6;
+
+ /* Colores neutros */
+ --white: #ffffff;
+ --gray-50: #f9fafb;
+ --gray-100: #f3f4f6;
+ --gray-200: #e5e7eb;
+ --gray-300: #d1d5db;
+ --gray-400: #9ca3af;
+ --gray-500: #6b7280;
+ --gray-600: #4b5563;
+ --gray-700: #374151;
+ --gray-800: #1f2937;
+ --gray-900: #111827;
+
+ /* Colores de fondo y texto */
+ --bg-color: #ffffff;
+ --text-color: #111827;
+ --text-muted: #6b7280;
+
+ /* Colores matemáticos específicos */
+ --positive-quadrant: #10b981;
+ --negative-quadrant: #ef4444;
+ --axis-color: #374151;
+ --grid-color: #e5e7eb;
+
+ /* Tipografía */
+ --font-family-base: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', sans-serif;
+ --font-family-primary: 'Inter', -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', sans-serif;
+ --font-family-mono: 'Fira Code', 'Monaco', 'Consolas', monospace;
+
+ /* Line heights */
+ --line-height-tight: 1.25;
+ --line-height-base: 1.5;
+ --line-height-relaxed: 1.75;
+
+ /* Tamaños de fuente */
+ --font-size-xs: 0.75rem;
+ --font-size-sm: 0.875rem;
+ --font-size-base: 1rem;
+ --font-size-lg: 1.125rem;
+ --font-size-xl: 1.25rem;
+ --font-size-2xl: 1.5rem;
+ --font-size-3xl: 1.875rem;
+ --font-size-4xl: 2.25rem;
+
+ /* Espaciado */
+ --spacing-1: 0.25rem;
+ --spacing-2: 0.5rem;
+ --spacing-3: 0.75rem;
+ --spacing-4: 1rem;
+ --spacing-5: 1.25rem;
+ --spacing-6: 1.5rem;
+ --spacing-8: 2rem;
+ --spacing-10: 2.5rem;
+ --spacing-12: 3rem;
+ --spacing-16: 4rem;
+ --spacing-20: 5rem;
+
+ /* Bordes y radius */
+ --border-radius-sm: 0.125rem;
+ --border-radius: 0.25rem;
+ --border-radius-md: 0.375rem;
+ --border-radius-lg: 0.5rem;
+ --border-radius-xl: 0.75rem;
+ --border-radius-2xl: 1rem;
+ --border-radius-full: 9999px;
+
+ /* Sombras */
+ --shadow-sm: 0 1px 2px 0 rgb(0 0 0 / 0.05);
+ --shadow: 0 1px 3px 0 rgb(0 0 0 / 0.1), 0 1px 2px -1px rgb(0 0 0 / 0.1);
+ --shadow-md: 0 4px 6px -1px rgb(0 0 0 / 0.1), 0 2px 4px -2px rgb(0 0 0 / 0.1);
+ --shadow-lg: 0 10px 15px -3px rgb(0 0 0 / 0.1), 0 4px 6px -4px rgb(0 0 0 / 0.1);
+ --shadow-xl: 0 20px 25px -5px rgb(0 0 0 / 0.1), 0 8px 10px -6px rgb(0 0 0 / 0.1);
+
+ /* Transiciones */
+ --transition-fast: 150ms ease-in-out;
+ --transition-normal: 250ms ease-in-out;
+ --transition-slow: 350ms ease-in-out;
+
+ /* Z-index */
+ --z-dropdown: 1000;
+ --z-sticky: 1020;
+ --z-fixed: 1030;
+ --z-modal-backdrop: 1040;
+ --z-modal: 1050;
+ --z-popover: 1060;
+ --z-tooltip: 1070;
+
+ /* Layout específico */
+ --navbar-height: 60px;
+ --sidebar-width: 320px;
+ --sidebar-collapsed-width: 60px;
+}
diff --git a/Aplicativo web Caja de polinomios/src/utils/operations.js b/Aplicativo web Caja de polinomios/src/utils/operations.js
new file mode 100644
index 0000000..19090d7
--- /dev/null
+++ b/Aplicativo web Caja de polinomios/src/utils/operations.js
@@ -0,0 +1,400 @@
+import { parsePolynomial, normalizePolynomial } from './polynomialParser';
+
+// Realizar suma de polinomios
+export const addPolynomials = (poly1Str, poly2Str) => {
+ try {
+ const poly1 = parsePolynomial(poly1Str);
+ const poly2 = parsePolynomial(poly2Str);
+
+ // Combinar todos los términos
+ const allTerms = [...poly1.terms, ...poly2.terms];
+
+ // Crear polinomio temporal para normalizar
+ const tempPolyStr = allTerms
+ .map(term => term.toString())
+ .join(' + ')
+ .replace(/\+ -/g, '- ');
+
+ const result = normalizePolynomial(tempPolyStr);
+
+ return {
+ result: result.toString(),
+ steps: generateAdditionSteps(poly1, poly2, result)
+ };
+ } catch (error) {
+ throw new Error('Error en la suma de polinomios: ' + error.message);
+ }
+};
+
+// Realizar resta de polinomios
+export const subtractPolynomials = (poly1Str, poly2Str) => {
+ try {
+ const poly1 = parsePolynomial(poly1Str);
+ const poly2 = parsePolynomial(poly2Str);
+
+ // Cambiar signos del segundo polinomio
+ const negatedPoly2Terms = poly2.terms.map(term => ({
+ ...term,
+ coefficient: -term.coefficient
+ }));
+
+ // Combinar términos
+ const allTerms = [...poly1.terms, ...negatedPoly2Terms];
+
+ const tempPolyStr = allTerms
+ .map(term => term.toString())
+ .join(' + ')
+ .replace(/\+ -/g, '- ');
+
+ const result = normalizePolynomial(tempPolyStr);
+
+ return {
+ result: result.toString(),
+ steps: generateSubtractionSteps(poly1, poly2, result)
+ };
+ } catch (error) {
+ throw new Error('Error en la resta de polinomios: ' + error.message);
+ }
+};
+
+// Realizar multiplicación de polinomios
+export const multiplyPolynomials = (poly1Str, poly2Str) => {
+ try {
+ const poly1 = parsePolynomial(poly1Str);
+ const poly2 = parsePolynomial(poly2Str);
+
+ const resultTerms = [];
+
+ // Multiplicar cada término de poly1 con cada término de poly2
+ for (const term1 of poly1.terms) {
+ for (const term2 of poly2.terms) {
+ const newCoeff = term1.coefficient * term2.coefficient;
+ const newVar = term1.variable || term2.variable;
+ const newExp = term1.exponent + term2.exponent;
+
+ resultTerms.push({
+ coefficient: newCoeff,
+ variable: newVar,
+ exponent: newExp,
+ toString() {
+ if (this.exponent === 0) return this.coefficient.toString();
+ const coeff = this.coefficient === 1 ? '' :
+ this.coefficient === -1 ? '-' :
+ this.coefficient.toString();
+ const exp = this.exponent === 1 ? '' : `^${this.exponent}`;
+ return `${coeff}${this.variable}${exp}`;
+ }
+ });
+ }
+ }
+
+ const tempPolyStr = resultTerms
+ .map(term => term.toString())
+ .join(' + ')
+ .replace(/\+ -/g, '- ');
+
+ const result = normalizePolynomial(tempPolyStr);
+
+ return {
+ result: result.toString(),
+ steps: generateMultiplicationSteps(poly1, poly2, result, resultTerms)
+ };
+ } catch (error) {
+ throw new Error('Error en la multiplicación de polinomios: ' + error.message);
+ }
+};
+
+// Realizar división de polinomios (división sintética básica)
+export const dividePolynomials = (dividendStr, divisorStr) => {
+ try {
+ const dividend = parsePolynomial(dividendStr);
+ const divisor = parsePolynomial(divisorStr);
+
+ if (divisor.getDegree() > dividend.getDegree()) {
+ return {
+ quotient: '0',
+ remainder: dividendStr,
+ steps: [{
+ title: 'División no posible',
+ description: 'El grado del divisor es mayor que el del dividendo',
+ action: 'El cociente es 0 y el residuo es el dividendo original'
+ }]
+ };
+ }
+
+ // Implementación básica de división polinomial
+ // Por simplicidad, implementamos casos básicos
+
+ return {
+ quotient: 'x + 1', // Placeholder
+ remainder: '0',
+ steps: generateDivisionSteps(dividend, divisor)
+ };
+ } catch (error) {
+ throw new Error('Error en la división de polinomios: ' + error.message);
+ }
+};
+
+// Generar pasos para suma
+const generateAdditionSteps = (poly1, poly2, result) => [
+ {
+ id: 1,
+ title: 'Paso 1: Preparación del Plano',
+ description: 'Dividimos los cuadrantes del plano cartesiano por signos. Los cuadrantes I y IV representan términos positivos, mientras que los II y III representan términos negativos.',
+ action: 'Configurar cuadrantes según signos',
+ visualization: {
+ type: 'quadrant_setup',
+ data: { positive: ['I', 'IV'], negative: ['II', 'III'] }
+ }
+ },
+ {
+ id: 2,
+ title: 'Paso 2: Ubicar Primer Polinomio',
+ description: `Colocamos las fichas del primer polinomio P(x) = ${poly1.toString()} en los cuadrantes superiores (I y II) según el signo de cada término.`,
+ action: 'Posicionar fichas del primer polinomio',
+ visualization: {
+ type: 'place_polynomial',
+ data: { polynomial: poly1, quadrants: ['I', 'II'] }
+ }
+ },
+ {
+ id: 3,
+ title: 'Paso 3: Ubicar Segundo Polinomio',
+ description: `Colocamos las fichas del segundo polinomio Q(x) = ${poly2.toString()} en los cuadrantes inferiores (III y IV) según el signo de cada término.`,
+ action: 'Posicionar fichas del segundo polinomio',
+ visualization: {
+ type: 'place_polynomial',
+ data: { polynomial: poly2, quadrants: ['III', 'IV'] }
+ }
+ },
+ {
+ id: 4,
+ title: 'Paso 4: Traslado Diagonal',
+ description: 'Movemos las fichas del polinomio inferior hacia arriba, cruzándolas en diagonal para combinar términos similares.',
+ action: 'Mover fichas diagonalmente',
+ visualization: {
+ type: 'diagonal_move',
+ data: { direction: 'up' }
+ }
+ },
+ {
+ id: 5,
+ title: 'Paso 5: Eliminación de Opuestos',
+ description: 'Eliminamos del juego las fichas similares que se encuentren en lados opuestos, ya que se cancelan mutuamente.',
+ action: 'Cancelar términos opuestos',
+ visualization: {
+ type: 'cancel_opposites',
+ data: {}
+ }
+ },
+ {
+ id: 6,
+ title: 'Paso 6: Lectura del Resultado',
+ description: `El resultado final es: ${result.toString()}. Leemos el polinomio resultante teniendo en cuenta los signos de los cuadrantes donde quedaron las fichas.`,
+ action: 'Interpretar resultado final',
+ visualization: {
+ type: 'final_result',
+ data: { result: result.toString() }
+ }
+ }
+];
+
+// Generar pasos para resta
+const generateSubtractionSteps = (poly1, poly2, result) => [
+ {
+ id: 1,
+ title: 'Pasos Iniciales',
+ description: 'Seguimos los primeros tres pasos de la adición para ubicar ambos polinomios en el plano.',
+ action: 'Aplicar pasos 1-3 de suma',
+ visualization: {
+ type: 'setup_subtraction',
+ data: { poly1, poly2 }
+ }
+ },
+ {
+ id: 2,
+ title: 'Cambio de Lado',
+ description: `Cambiamos de lado las fichas del polinomio Q(x) = ${poly2.toString()} que deseamos restar. Las fichas de la izquierda las pasamos a la derecha y viceversa.`,
+ action: 'Invertir posición de fichas',
+ visualization: {
+ type: 'flip_sides',
+ data: { polynomial: poly2 }
+ }
+ },
+ {
+ id: 3,
+ title: 'Finalización',
+ description: `Repetimos los pasos 4, 5 y 6 de la adición. El resultado final es: ${result.toString()}.`,
+ action: 'Completar como en suma',
+ visualization: {
+ type: 'complete_subtraction',
+ data: { result: result.toString() }
+ }
+ }
+];
+
+// Generar pasos para multiplicación
+const generateMultiplicationSteps = (poly1, poly2, result, intermediateTerms) => [
+ {
+ id: 1,
+ title: 'Preparación de Fichas',
+ description: `Tomamos las fichas necesarias para formar P(x) = ${poly1.toString()} usando sólo uno de los lados de cada ficha. El otro lado debe coincidir con términos de Q(x) = ${poly2.toString()}.`,
+ action: 'Preparar fichas base',
+ visualization: {
+ type: 'prepare_multiplication',
+ data: { base: poly1, height: poly2 }
+ }
+ },
+ {
+ id: 2,
+ title: 'Disposición Horizontal',
+ description: 'Ubicamos las fichas de P(x) horizontalmente, considerando los signos y que la altura coincida con términos de Q(x).',
+ action: 'Formar base horizontal',
+ visualization: {
+ type: 'horizontal_layout',
+ data: { polynomial: poly1 }
+ }
+ },
+ {
+ id: 3,
+ title: 'Formación de Altura',
+ description: 'Agregamos fichas para formar un rectángulo de altura Q(x), manteniendo la base P(x).',
+ action: 'Construir altura',
+ visualization: {
+ type: 'build_height',
+ data: { height: poly2 }
+ }
+ },
+ {
+ id: 4,
+ title: 'Completar Rectángulo',
+ description: 'Rellenamos los espacios vacíos con fichas hasta formar un rectángulo completo de base P(x) y altura Q(x).',
+ action: 'Rellenar espacios',
+ visualization: {
+ type: 'fill_rectangle',
+ data: { terms: intermediateTerms }
+ }
+ },
+ {
+ id: 5,
+ title: 'Análisis de Área',
+ description: 'Analizamos el área de cada ficha y su signo según el cuadrante donde se encuentre.',
+ action: 'Calcular áreas',
+ visualization: {
+ type: 'analyze_areas',
+ data: { terms: intermediateTerms }
+ }
+ },
+ {
+ id: 6,
+ title: 'Simplificación',
+ description: `Eliminamos términos semejantes y leemos el resultado: ${result.toString()}.`,
+ action: 'Simplificar resultado',
+ visualization: {
+ type: 'simplify_result',
+ data: { result: result.toString() }
+ }
+ }
+];
+
+// Generar pasos para división
+const generateDivisionSteps = (dividend, divisor) => [
+ {
+ id: 1,
+ title: 'Preparación del Dividendo',
+ description: `Tomamos las fichas necesarias para formar el dividendo P(x) = ${dividend.toString()}.`,
+ action: 'Preparar fichas del dividendo',
+ visualization: {
+ type: 'prepare_dividend',
+ data: { dividend }
+ }
+ },
+ {
+ id: 2,
+ title: 'Ubicación Inicial',
+ description: 'Inicialmente, ubicamos las fichas en los dos cuadrantes superiores, teniendo en cuenta el signo de cada término.',
+ action: 'Posicionar en cuadrantes superiores',
+ visualization: {
+ type: 'initial_placement',
+ data: { dividend }
+ }
+ },
+ {
+ id: 3,
+ title: 'Base del Rectángulo',
+ description: `Sobre el eje x, formamos un rectángulo de base el divisor Q(x) = ${divisor.toString()} usando fichas de mayor grado.`,
+ action: 'Formar base con divisor',
+ visualization: {
+ type: 'form_base',
+ data: { divisor }
+ }
+ },
+ {
+ id: 4,
+ title: 'Construcción Completa',
+ description: 'Sin cambiar la base, construimos un rectángulo con las fichas sobrantes, agregando ceros si es necesario.',
+ action: 'Completar rectángulo',
+ visualization: {
+ type: 'complete_division',
+ data: { dividend, divisor }
+ }
+ }
+];
+
+// Validar operación antes de ejecutar
+export const validateOperation = (poly1Str, poly2Str, operation) => {
+ try {
+ if (!poly1Str.trim() || !poly2Str.trim()) {
+ return {
+ valid: false,
+ error: 'Ambos polinomios deben estar completos'
+ };
+ }
+
+ const poly1 = parsePolynomial(poly1Str);
+ const poly2 = parsePolynomial(poly2Str);
+
+ if (poly1.terms.length === 0 || poly2.terms.length === 0) {
+ return {
+ valid: false,
+ error: 'Los polinomios deben tener términos válidos'
+ };
+ }
+
+ switch (operation) {
+ case 'division':
+ if (poly2.getDegree() === 0 && poly2.terms[0]?.coefficient === 0) {
+ return {
+ valid: false,
+ error: 'No se puede dividir por cero'
+ };
+ }
+ break;
+
+ case 'multiplication':
+ const totalDegree = poly1.getDegree() + poly2.getDegree();
+ if (totalDegree > 6) {
+ return {
+ valid: false,
+ error: 'El resultado sería demasiado complejo para visualizar'
+ };
+ }
+ break;
+ }
+
+ return { valid: true };
+ } catch (error) {
+ return {
+ valid: false,
+ error: 'Error al validar la operación: ' + error.message
+ };
+ }
+};
+
+export default {
+ addPolynomials,
+ subtractPolynomials,
+ multiplyPolynomials,
+ dividePolynomials,
+ validateOperation
+};
diff --git a/Aplicativo web Caja de polinomios/src/utils/p5Utils.js b/Aplicativo web Caja de polinomios/src/utils/p5Utils.js
new file mode 100644
index 0000000..194b8b4
--- /dev/null
+++ b/Aplicativo web Caja de polinomios/src/utils/p5Utils.js
@@ -0,0 +1,337 @@
+// Utilidades específicas para p5.js
+
+// Función para convertir coordenadas del canvas a coordenadas matemáticas
+export const canvasToMath = (canvasX, canvasY, canvasWidth, canvasHeight, zoom = 1, panX = 0, panY = 0) => {
+ const centerX = canvasWidth / 2;
+ const centerY = canvasHeight / 2;
+
+ const mathX = ((canvasX - centerX - panX) / zoom) / 20; // 20 pixels por unidad
+ const mathY = -((canvasY - centerY - panY) / zoom) / 20; // Invertir Y para matemáticas
+
+ return { x: mathX, y: mathY };
+};
+
+// Función para convertir coordenadas matemáticas a coordenadas del canvas
+export const mathToCanvas = (mathX, mathY, canvasWidth, canvasHeight, zoom = 1, panX = 0, panY = 0) => {
+ const centerX = canvasWidth / 2;
+ const centerY = canvasHeight / 2;
+
+ const canvasX = centerX + (mathX * 20 * zoom) + panX;
+ const canvasY = centerY - (mathY * 20 * zoom) + panY; // Invertir Y
+
+ return { x: canvasX, y: canvasY };
+};
+
+// Crear ficha de polinomio
+export const createPolynomialChip = (term, position = { x: 0, y: 0 }) => {
+ const chip = {
+ id: Date.now() + Math.random(),
+ type: getChipType(term),
+ label: formatTermLabel(term),
+ coefficient: term.coefficient,
+ variable: term.variable,
+ exponent: term.exponent,
+ x: position.x,
+ y: position.y,
+ size: getChipSize(term),
+ color: getChipColor(term),
+ isSelected: false,
+ isDragging: false,
+ createdAt: Date.now()
+ };
+
+ return chip;
+};
+
+// Determinar tipo de ficha basado en el término
+const getChipType = (term) => {
+ if (term.exponent === 0) return 'constant';
+ if (term.exponent === 1) return 'x';
+ if (term.exponent === 2) return 'x2';
+ return 'polynomial';
+};
+
+// Formatear etiqueta de la ficha
+const formatTermLabel = (term) => {
+ if (term.exponent === 0) {
+ return term.coefficient.toString();
+ }
+
+ const coeff = Math.abs(term.coefficient) === 1 ? '' : Math.abs(term.coefficient);
+ const sign = term.coefficient < 0 ? '-' : '';
+
+ if (term.exponent === 1) {
+ return `${sign}${coeff}${term.variable}`;
+ }
+
+ return `${sign}${coeff}${term.variable}²`;
+};
+
+// Obtener tamaño de ficha
+const getChipSize = (term) => {
+ if (term.exponent === 0) return 30; // Constantes más pequeñas
+ if (term.exponent === 1) return 40; // Lineales medianas
+ if (term.exponent === 2) return 50; // Cuadráticas más grandes
+ return 35;
+};
+
+// Obtener color de ficha
+const getChipColor = (term) => {
+ const colors = {
+ constant: '#f59e0b', // Orange
+ x: '#10b981', // Green
+ x2: '#2563eb', // Blue
+ polynomial: '#8b5cf6' // Purple
+ };
+
+ return colors[getChipType(term)] || '#6b7280';
+};
+
+// Determinar cuadrante basado en coordenadas
+export const getQuadrant = (x, y) => {
+ if (x >= 0 && y >= 0) return 1; // I
+ if (x < 0 && y >= 0) return 2; // II
+ if (x < 0 && y < 0) return 3; // III
+ if (x >= 0 && y < 0) return 4; // IV
+ return 0; // En los ejes
+};
+
+// Verificar si un punto está dentro de una ficha
+export const isPointInChip = (pointX, pointY, chip) => {
+ const distance = Math.sqrt(
+ Math.pow(pointX - chip.x, 2) + Math.pow(pointY - chip.y, 2)
+ );
+ return distance <= chip.size / 2;
+};
+
+// Calcular posición inicial para fichas según la operación
+export const calculateInitialPositions = (polynomial, operation, isFirst = true) => {
+ const positions = [];
+ const terms = polynomial.terms;
+
+ switch (operation) {
+ case 'addition':
+ case 'subtraction':
+ // Para suma/resta: primer polinomio arriba, segundo abajo
+ const yOffset = isFirst ? -100 : 100;
+ terms.forEach((term, index) => {
+ const x = (index - terms.length / 2) * 80;
+ const y = yOffset;
+ positions.push({ x, y });
+ });
+ break;
+
+ case 'multiplication':
+ // Para multiplicación: formar base y altura
+ if (isFirst) {
+ // Base horizontal
+ terms.forEach((term, index) => {
+ const x = (index - terms.length / 2) * 60;
+ const y = 0;
+ positions.push({ x, y });
+ });
+ } else {
+ // Altura vertical
+ terms.forEach((term, index) => {
+ const x = -120;
+ const y = (index - terms.length / 2) * 60;
+ positions.push({ x, y });
+ });
+ }
+ break;
+
+ case 'division':
+ // Para división: dividendo distribuido, divisor como base
+ if (isFirst) {
+ // Dividendo distribuido en cuadrantes superiores
+ terms.forEach((term, index) => {
+ const x = (index - terms.length / 2) * 70;
+ const y = -80;
+ positions.push({ x, y });
+ });
+ } else {
+ // Divisor como base
+ terms.forEach((term, index) => {
+ const x = (index - terms.length / 2) * 50;
+ const y = 80;
+ positions.push({ x, y });
+ });
+ }
+ break;
+
+ default:
+ // Posición por defecto
+ terms.forEach((term, index) => {
+ const x = (index - terms.length / 2) * 60;
+ const y = isFirst ? -50 : 50;
+ positions.push({ x, y });
+ });
+ }
+
+ return positions;
+};
+
+// Animar movimiento de ficha
+export const animateChipMovement = (chip, targetX, targetY, duration = 1000) => {
+ const startX = chip.x;
+ const startY = chip.y;
+ const startTime = Date.now();
+
+ return new Promise((resolve) => {
+ const animate = () => {
+ const elapsed = Date.now() - startTime;
+ const progress = Math.min(elapsed / duration, 1);
+
+ // Función de easing (ease-out)
+ const easeOut = 1 - Math.pow(1 - progress, 3);
+
+ chip.x = startX + (targetX - startX) * easeOut;
+ chip.y = startY + (targetY - startY) * easeOut;
+
+ if (progress < 1) {
+ requestAnimationFrame(animate);
+ } else {
+ chip.x = targetX;
+ chip.y = targetY;
+ resolve();
+ }
+ };
+
+ animate();
+ });
+};
+
+// Detectar colisiones entre fichas
+export const detectCollisions = (chips) => {
+ const collisions = [];
+
+ for (let i = 0; i < chips.length; i++) {
+ for (let j = i + 1; j < chips.length; j++) {
+ const chip1 = chips[i];
+ const chip2 = chips[j];
+
+ const distance = Math.sqrt(
+ Math.pow(chip1.x - chip2.x, 2) + Math.pow(chip1.y - chip2.y, 2)
+ );
+
+ const minDistance = (chip1.size + chip2.size) / 2;
+
+ if (distance < minDistance) {
+ collisions.push({ chip1, chip2, distance });
+ }
+ }
+ }
+
+ return collisions;
+};
+
+// Resolver colisiones moviendo fichas
+export const resolveCollisions = (collisions) => {
+ collisions.forEach(({ chip1, chip2, distance }) => {
+ const minDistance = (chip1.size + chip2.size) / 2 + 5; // +5 para espacio
+ const overlap = minDistance - distance;
+
+ if (overlap > 0) {
+ const angle = Math.atan2(chip2.y - chip1.y, chip2.x - chip1.x);
+ const moveDistance = overlap / 2;
+
+ chip1.x -= Math.cos(angle) * moveDistance;
+ chip1.y -= Math.sin(angle) * moveDistance;
+ chip2.x += Math.cos(angle) * moveDistance;
+ chip2.y += Math.sin(angle) * moveDistance;
+ }
+ });
+};
+
+// Agrupar fichas similares
+export const groupSimilarChips = (chips) => {
+ const groups = {};
+
+ chips.forEach(chip => {
+ const key = `${chip.variable}_${chip.exponent}`;
+ if (!groups[key]) {
+ groups[key] = [];
+ }
+ groups[key].push(chip);
+ });
+
+ return groups;
+};
+
+// Combinar fichas similares en una sola
+export const combineSimilarChips = (chipGroup) => {
+ if (chipGroup.length <= 1) return chipGroup;
+
+ const combined = { ...chipGroup[0] };
+ combined.coefficient = chipGroup.reduce((sum, chip) => sum + chip.coefficient, 0);
+ combined.label = formatTermLabel(combined);
+ combined.id = Date.now() + Math.random();
+
+ // Posición promedio
+ combined.x = chipGroup.reduce((sum, chip) => sum + chip.x, 0) / chipGroup.length;
+ combined.y = chipGroup.reduce((sum, chip) => sum + chip.y, 0) / chipGroup.length;
+
+ return combined.coefficient === 0 ? null : combined;
+};
+
+// Aplicar efecto visual a ficha
+export const applyChipEffect = (p5, chip, effect) => {
+ p5.push();
+ p5.translate(chip.x, chip.y);
+
+ switch (effect) {
+ case 'highlight':
+ p5.stroke(255, 255, 0);
+ p5.strokeWeight(3);
+ p5.noFill();
+ p5.circle(0, 0, chip.size + 10);
+ break;
+
+ case 'pulse':
+ const pulse = Math.sin(Date.now() * 0.01) * 5;
+ p5.scale(1 + pulse * 0.1);
+ break;
+
+ case 'fade':
+ p5.tint(255, 128);
+ break;
+
+ case 'error':
+ p5.stroke(255, 0, 0);
+ p5.strokeWeight(2);
+ p5.noFill();
+ for (let i = 0; i < 3; i++) {
+ p5.circle(0, 0, chip.size + i * 5);
+ }
+ break;
+ }
+
+ p5.pop();
+};
+
+// Utilidades de dibujo matemático
+export const drawMathSymbol = (p5, symbol, x, y, size = 16) => {
+ p5.push();
+ p5.translate(x, y);
+ p5.textAlign(p5.CENTER, p5.CENTER);
+ p5.textSize(size);
+ p5.fill(0);
+ p5.text(symbol, 0, 0);
+ p5.pop();
+};
+
+export default {
+ canvasToMath,
+ mathToCanvas,
+ createPolynomialChip,
+ getQuadrant,
+ isPointInChip,
+ calculateInitialPositions,
+ animateChipMovement,
+ detectCollisions,
+ resolveCollisions,
+ groupSimilarChips,
+ combineSimilarChips,
+ applyChipEffect,
+ drawMathSymbol
+};
diff --git a/Aplicativo web Caja de polinomios/src/utils/polynomialParser.js b/Aplicativo web Caja de polinomios/src/utils/polynomialParser.js
new file mode 100644
index 0000000..42ecae9
--- /dev/null
+++ b/Aplicativo web Caja de polinomios/src/utils/polynomialParser.js
@@ -0,0 +1,326 @@
+import {
+ polynomialPatterns,
+ termPatterns,
+ extractTerms,
+ validateTerm
+} from './regexPatterns';
+
+// Clase para representar un término de polinomio
+export class PolynomialTerm {
+ constructor(coefficient = 1, variable = '', exponent = 0) {
+ this.coefficient = coefficient;
+ this.variable = variable;
+ this.exponent = exponent;
+ }
+
+ toString() {
+ if (this.exponent === 0) {
+ return this.coefficient.toString();
+ }
+
+ const coeff = this.coefficient === 1 ? '' :
+ this.coefficient === -1 ? '-' :
+ this.coefficient.toString();
+
+ const exp = this.exponent === 1 ? '' : `^${this.exponent}`;
+
+ return `${coeff}${this.variable}${exp}`;
+ }
+
+ equals(other) {
+ return this.variable === other.variable && this.exponent === other.exponent;
+ }
+
+ getDegree() {
+ return this.variable ? this.exponent : 0;
+ }
+
+ getType() {
+ if (this.exponent === 0) return 'constant';
+ if (this.exponent === 1) return 'linear';
+ if (this.exponent === 2) return 'quadratic';
+ return 'polynomial';
+ }
+}
+
+// Clase para representar un polinomio completo
+export class Polynomial {
+ constructor(terms = []) {
+ this.terms = terms;
+ }
+
+ getDegree() {
+ return Math.max(...this.terms.map(term => term.getDegree()), 0);
+ }
+
+ getTermCount() {
+ return this.terms.length;
+ }
+
+ toString() {
+ if (this.terms.length === 0) return '0';
+
+ return this.terms
+ .map((term, index) => {
+ const termStr = term.toString();
+ if (index === 0) return termStr;
+
+ const coeff = term.coefficient;
+ if (coeff >= 0) {
+ return `+ ${termStr}`;
+ } else {
+ return `- ${termStr.substring(1)}`;
+ }
+ })
+ .join(' ');
+ }
+
+ getTermsByType() {
+ return {
+ quadratic: this.terms.filter(t => t.getType() === 'quadratic'),
+ linear: this.terms.filter(t => t.getType() === 'linear'),
+ constant: this.terms.filter(t => t.getType() === 'constant')
+ };
+ }
+}
+
+// Validar un polinomio
+export const validatePolynomial = (polynomialStr) => {
+ try {
+ if (!polynomialStr || typeof polynomialStr !== 'string') {
+ return {
+ isValid: false,
+ error: 'Debe ingresar un polinomio válido'
+ };
+ }
+
+ const cleaned = polynomialStr.trim().replace(/\s+/g, '');
+
+ if (!cleaned) {
+ return {
+ isValid: false,
+ error: 'El polinomio no puede estar vacío'
+ };
+ }
+
+ // Verificar formato general
+ if (!polynomialPatterns.polynomial.test(cleaned)) {
+ return {
+ isValid: false,
+ error: 'Formato de polinomio inválido. Use formato: ax² + bx + c'
+ };
+ }
+
+ // Verificar cada término
+ const terms = extractTerms(cleaned);
+ for (const term of terms) {
+ if (!validateTerm(term)) {
+ return {
+ isValid: false,
+ error: `Término inválido: ${term}`
+ };
+ }
+ }
+
+ // Verificar que tenga al menos un término válido
+ if (terms.length === 0) {
+ return {
+ isValid: false,
+ error: 'No se encontraron términos válidos'
+ };
+ }
+
+ return {
+ isValid: true,
+ terms: terms.length,
+ degree: getDegree(polynomialStr)
+ };
+
+ } catch (error) {
+ return {
+ isValid: false,
+ error: 'Error al validar el polinomio'
+ };
+ }
+};
+
+// Parsear un string de polinomio a objeto Polynomial
+export const parsePolynomial = (polynomialStr) => {
+ try {
+ const cleaned = polynomialStr.trim().replace(/\s+/g, '');
+ const termStrings = extractTerms(cleaned);
+ const terms = [];
+
+ for (const termStr of termStrings) {
+ const term = parseTerm(termStr);
+ if (term) {
+ terms.push(term);
+ }
+ }
+
+ return new Polynomial(terms);
+ } catch (error) {
+ console.error('Error parsing polynomial:', error);
+ return new Polynomial([]);
+ }
+};
+
+// Parsear un término individual
+export const parseTerm = (termStr) => {
+ try {
+ const cleaned = termStr.trim().replace(/\s+/g, '');
+
+ // Término cuadrático
+ let match = termPatterns.quadratic.exec(cleaned);
+ if (match) {
+ const coeff = parseCoefficient(match[1]);
+ const variable = match[2];
+ return new PolynomialTerm(coeff, variable, 2);
+ }
+
+ // Término lineal
+ match = termPatterns.linear.exec(cleaned);
+ if (match) {
+ const coeff = parseCoefficient(match[1]);
+ const variable = match[2];
+ return new PolynomialTerm(coeff, variable, 1);
+ }
+
+ // Término constante
+ match = termPatterns.constant.exec(cleaned);
+ if (match) {
+ const coeff = parseFloat(match[1]);
+ return new PolynomialTerm(coeff, '', 0);
+ }
+
+ return null;
+ } catch (error) {
+ console.error('Error parsing term:', error);
+ return null;
+ }
+};
+
+// Parsear coeficiente
+const parseCoefficient = (coeffStr) => {
+ if (!coeffStr || coeffStr === '+') return 1;
+ if (coeffStr === '-') return -1;
+
+ const cleaned = coeffStr.replace(/\s+/g, '');
+ const num = parseFloat(cleaned);
+ return isNaN(num) ? 1 : num;
+};
+
+// Obtener el grado de un polinomio
+export const getDegree = (polynomialStr) => {
+ try {
+ const polynomial = parsePolynomial(polynomialStr);
+ return polynomial.getDegree();
+ } catch (error) {
+ return 0;
+ }
+};
+
+// Obtener términos agrupados por tipo
+export const getTermsByType = (polynomialStr) => {
+ try {
+ const polynomial = parsePolynomial(polynomialStr);
+ return polynomial.getTermsByType();
+ } catch (error) {
+ return { quadratic: [], linear: [], constant: [] };
+ }
+};
+
+// Normalizar polinomio (combinar términos similares)
+export const normalizePolynomial = (polynomialStr) => {
+ try {
+ const polynomial = parsePolynomial(polynomialStr);
+ const termGroups = {};
+
+ // Agrupar términos similares
+ for (const term of polynomial.terms) {
+ const key = `${term.variable}_${term.exponent}`;
+ if (termGroups[key]) {
+ termGroups[key].coefficient += term.coefficient;
+ } else {
+ termGroups[key] = new PolynomialTerm(
+ term.coefficient,
+ term.variable,
+ term.exponent
+ );
+ }
+ }
+
+ // Filtrar términos con coeficiente cero y ordenar por grado
+ const normalizedTerms = Object.values(termGroups)
+ .filter(term => term.coefficient !== 0)
+ .sort((a, b) => b.exponent - a.exponent);
+
+ return new Polynomial(normalizedTerms);
+ } catch (error) {
+ console.error('Error normalizing polynomial:', error);
+ return new Polynomial([]);
+ }
+};
+
+// Convertir polinomio a formato LaTeX
+export const toLatex = (polynomialStr) => {
+ try {
+ const polynomial = parsePolynomial(polynomialStr);
+ return polynomial.terms
+ .map(term => {
+ let result = '';
+
+ if (term.coefficient !== 1 || term.exponent === 0) {
+ result += term.coefficient;
+ }
+
+ if (term.variable) {
+ result += term.variable;
+
+ if (term.exponent > 1) {
+ result += `^{${term.exponent}}`;
+ }
+ }
+
+ return result;
+ })
+ .join(' + ')
+ .replace(/\+ -/g, '- ');
+ } catch (error) {
+ return polynomialStr;
+ }
+};
+
+// Generar sugerencias de autocompletado
+export const generateSuggestions = (input) => {
+ const suggestions = [];
+ const inputLower = input.toLowerCase();
+
+ // Sugerencias basadas en patrones comunes
+ const commonPatterns = [
+ 'x²', 'x^2', '2x²', '3x²', '-x²',
+ 'x', '2x', '3x', '-x', '-2x',
+ '+', '-', '+ x', '- x', '+ 1', '- 1'
+ ];
+
+ for (const pattern of commonPatterns) {
+ if (pattern.toLowerCase().startsWith(inputLower)) {
+ suggestions.push(pattern);
+ }
+ }
+
+ return suggestions.slice(0, 5); // Limitar a 5 sugerencias
+};
+
+export default {
+ PolynomialTerm,
+ Polynomial,
+ validatePolynomial,
+ parsePolynomial,
+ parseTerm,
+ getDegree,
+ getTermsByType,
+ normalizePolynomial,
+ toLatex,
+ generateSuggestions
+};
diff --git a/Aplicativo web Caja de polinomios/src/utils/regexPatterns.js b/Aplicativo web Caja de polinomios/src/utils/regexPatterns.js
new file mode 100644
index 0000000..6663de2
--- /dev/null
+++ b/Aplicativo web Caja de polinomios/src/utils/regexPatterns.js
@@ -0,0 +1,180 @@
+// Expresiones regulares para validar polinomios
+export const polynomialPatterns = {
+ // Término completo: coeficiente opcional + variable + exponente opcional
+ term: /([+-]?\s*\d*\.?\d*)\s*([a-zA-Z])?\s*(\^?\s*\d+)?/g,
+
+ // Validación general de polinomio
+ polynomial: /^[+-]?\s*(\d*\.?\d*\s*[a-zA-Z]?\s*(\^?\s*\d+)?|[a-zA-Z]\s*(\^?\s*\d+)?|\d+\.?\d*)\s*([+-]\s*(\d*\.?\d*\s*[a-zA-Z]?\s*(\^?\s*\d+)?|[a-zA-Z]\s*(\^?\s*\d+)?|\d+\.?\d*))*$/,
+
+ // Coeficiente
+ coefficient: /^[+-]?\d*\.?\d*$/,
+
+ // Variable con exponente
+ variable: /^[a-zA-Z](\^?\d+)?$/,
+
+ // Número constante
+ constant: /^[+-]?\d+\.?\d*$/,
+
+ // Operadores
+ operator: /^[+-]$/
+};
+
+// Patrones específicos para diferentes tipos de términos
+export const termPatterns = {
+ // x^2, 2x^2, -3x^2, etc.
+ quadratic: /([+-]?\s*\d*\.?\d*)\s*([a-zA-Z])\s*\^?\s*2/,
+
+ // x, 2x, -3x, etc.
+ linear: /([+-]?\s*\d*\.?\d*)\s*([a-zA-Z])(?!\s*\^)/,
+
+ // 5, -3, 2.5, etc.
+ constant: /([+-]?\s*\d+\.?\d*)(?!\s*[a-zA-Z])/
+};
+
+// Validar formato de polinomio completo
+export const validatePolynomialFormat = (polynomial) => {
+ if (!polynomial || typeof polynomial !== 'string') {
+ return false;
+ }
+
+ const cleaned = polynomial.replace(/\s/g, '');
+ return polynomialPatterns.polynomial.test(cleaned);
+};
+
+// Extraer términos individuales
+export const extractTerms = (polynomial) => {
+ const terms = [];
+ const cleaned = polynomial.replace(/\s/g, '');
+
+ // Dividir por operadores manteniendo el signo
+ const parts = cleaned.split(/([+-])/).filter(part => part !== '');
+
+ let currentTerm = '';
+ for (let i = 0; i < parts.length; i++) {
+ if (polynomialPatterns.operator.test(parts[i]) && i > 0) {
+ if (currentTerm) {
+ terms.push(currentTerm);
+ }
+ currentTerm = parts[i];
+ } else {
+ currentTerm += parts[i];
+ }
+ }
+
+ if (currentTerm) {
+ terms.push(currentTerm);
+ }
+
+ return terms;
+};
+
+// Validar un término individual
+export const validateTerm = (term) => {
+ const cleaned = term.replace(/\s/g, '');
+
+ return (
+ termPatterns.quadratic.test(cleaned) ||
+ termPatterns.linear.test(cleaned) ||
+ termPatterns.constant.test(cleaned)
+ );
+};
+
+// Patrones para operaciones específicas
+export const operationPatterns = {
+ addition: {
+ step1: /preparación.*plano/i,
+ step2: /primer.*polinomio.*superior/i,
+ step3: /segundo.*polinomio.*inferior/i,
+ step4: /diagonal.*arriba/i,
+ step5: /eliminar.*opuestos/i,
+ step6: /leer.*resultado/i
+ },
+
+ subtraction: {
+ step1: /tres.*primeros.*pasos/i,
+ step2: /cambiar.*lado.*fichas/i,
+ step3: /repetir.*pasos/i
+ },
+
+ multiplication: {
+ step1: /fichas.*formar.*lado/i,
+ step2: /horizontal.*signos/i,
+ step3: /altura.*rectángulo/i,
+ step4: /rellenar.*espacios/i,
+ step5: /área.*signo/i,
+ step6: /eliminar.*términos/i
+ },
+
+ division: {
+ step1: /fichas.*dividendo/i,
+ step2: /cuadrantes.*superiores/i,
+ step3: /base.*mayor.*grado/i,
+ step4: /rectángulo.*fichas.*sobrantes/i
+ }
+};
+
+// Validar sintaxis específica para cada operación
+export const validateOperationSyntax = (polynomial, operation) => {
+ if (!validatePolynomialFormat(polynomial)) {
+ return { valid: false, error: 'Formato de polinomio inválido' };
+ }
+
+ const terms = extractTerms(polynomial);
+
+ switch (operation) {
+ case 'addition':
+ case 'subtraction':
+ // Para suma y resta, cualquier polinomio válido es aceptable
+ return { valid: true };
+
+ case 'multiplication':
+ // Para multiplicación, verificar que sea factorizable
+ if (terms.length < 2) {
+ return {
+ valid: false,
+ error: 'Para multiplicación se necesitan al menos 2 términos'
+ };
+ }
+ return { valid: true };
+
+ case 'division':
+ // Para división, verificar estructura apropiada
+ if (terms.length < 2) {
+ return {
+ valid: false,
+ error: 'Para división se necesita un polinomio divisible'
+ };
+ }
+ return { valid: true };
+
+ default:
+ return { valid: true };
+ }
+};
+
+// Generar regex dinámico para autocompletado
+export const generateAutocompletePattern = (input) => {
+ const escaped = input.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
+ return new RegExp(`^${escaped}`, 'i');
+};
+
+// Sugerencias de autocompletado
+export const autocompleteSuggestions = [
+ 'x²', 'x^2', '2x²', '3x²', '-x²', '-2x²',
+ 'x', '2x', '3x', '-x', '-2x', '-3x',
+ '1', '2', '3', '-1', '-2', '-3',
+ 'x² + x + 1', 'x² - 1', '2x² + 3x - 5',
+ 'x² + 2x + 1', 'x² - 4x + 4', 'x² - 2x - 3'
+];
+
+export default {
+ polynomialPatterns,
+ termPatterns,
+ validatePolynomialFormat,
+ extractTerms,
+ validateTerm,
+ operationPatterns,
+ validateOperationSyntax,
+ generateAutocompletePattern,
+ autocompleteSuggestions
+};