From 00aad899cdbe1e7afc9411702e194c318723511b Mon Sep 17 00:00:00 2001 From: Alexis Date: Fri, 27 Mar 2026 09:32:18 +0100 Subject: [PATCH] =?UTF-8?q?refactor:=20reorganizada=20toda=20la=20l=C3=B3g?= =?UTF-8?q?ica=20pesada=20de=20"advancedMode"=20al=20nuevo=20componente=20?= =?UTF-8?q?orquestador=20"DocEditor",=20y=20componentizaci=C3=B3n=20del=20?= =?UTF-8?q?step1=20y=20step2.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../src/components/editor/Step1BaseScale.jsx | 92 +++++++++++++ .../components/editor/Step2FuzzyModeling.jsx | 43 ++++++ frontend/src/pages/DocEditor.jsx | 129 ++++++++++++++++++ frontend/src/routers/AppRouter.jsx | 4 +- 4 files changed, 266 insertions(+), 2 deletions(-) create mode 100644 frontend/src/components/editor/Step1BaseScale.jsx create mode 100644 frontend/src/components/editor/Step2FuzzyModeling.jsx create mode 100644 frontend/src/pages/DocEditor.jsx diff --git a/frontend/src/components/editor/Step1BaseScale.jsx b/frontend/src/components/editor/Step1BaseScale.jsx new file mode 100644 index 0000000..115b450 --- /dev/null +++ b/frontend/src/components/editor/Step1BaseScale.jsx @@ -0,0 +1,92 @@ +import React, { useState, useEffect, useRef } from 'react'; +import CriterionInput from '../CriterionInput'; +import CardEditor from '../CardEditor'; +import BlankCardsCounter from '../BlankCardsCounter'; +import AddLevelButton from '../AddLevelButton'; + +export default function Step1BaseScale({ + criterionName, handleCriterionChange, + levels, handleLevelChange, handleAddLevel, handleRemoveLevel, + blankCards, handleBlankCardChange, + errors, handleGenerateBaseScale, isLoading +}) { + const [isZoomActive, setIsZoomActive] = useState(true); + const containerRef = useRef(null); + const tableRef = useRef(null); + const [dimensions, setDimensions] = useState({ container: 1000, table: 0 }); + + useEffect(() => { + const updateMeasurements = () => { + if (containerRef.current && tableRef.current) { + setDimensions({ + container: containerRef.current.offsetWidth, + table: tableRef.current.scrollWidth + }); + } + }; + const timeoutId = setTimeout(updateMeasurements, 50); + window.addEventListener('resize', updateMeasurements); + return () => { + clearTimeout(timeoutId); + window.removeEventListener('resize', updateMeasurements); + }; + }, [levels, blankCards]); + + const needsZoom = dimensions.table > dimensions.container; + const dynamicScale = needsZoom ? (dimensions.container / dimensions.table) * 0.95 : 1; + const currentScale = isZoomActive && needsZoom ? dynamicScale : 1; + + return ( +
+ +
+

+ Paso 1: Escala de Referencia (Mesa) +

+ {needsZoom && ( + + )} +
+ + + +
+
+ +
+ {levels.map((level, index) => ( + +
+ 3} /> +
+ {index < levels.length - 1 && ( + + )} +
+ ))} +
+
+
+ +
+ +
+
+ +
+ +
+
+ ); +} \ No newline at end of file diff --git a/frontend/src/components/editor/Step2FuzzyModeling.jsx b/frontend/src/components/editor/Step2FuzzyModeling.jsx new file mode 100644 index 0000000..8412d36 --- /dev/null +++ b/frontend/src/components/editor/Step2FuzzyModeling.jsx @@ -0,0 +1,43 @@ +import Chart from '../membershipFunction/Chart'; +import Controls from '../membershipFunction/Controls'; + +const COLORS = ['#ef4444', '#f59e0b', '#10b981', '#3b82f6', '#d946ef', '#06b6d4', '#8b5cf6', '#f43f5e', '#6366f1']; + +export default function Step2FuzzyModeling({ + baseScale, mfDefinitions, selectedTerm, setSelectedTerm, updateCurrentMf, handleFinalSubmit, onBack +}) { + const scaleKeys = Object.keys(baseScale); + const selectedColor = COLORS[scaleKeys.indexOf(selectedTerm) % COLORS.length] || '#2563eb'; + + return ( +
+
+

Paso 2: Modelar Conceptos Difusos

+ +
+ +
+ {scaleKeys.map((name, index) => { + const color = COLORS[index % COLORS.length]; + const isSelected = selectedTerm === name; + return ( + + ); + })} +
+ + + + + +
+ +
+ +
+ ); +} \ No newline at end of file diff --git a/frontend/src/pages/DocEditor.jsx b/frontend/src/pages/DocEditor.jsx new file mode 100644 index 0000000..3baa1f2 --- /dev/null +++ b/frontend/src/pages/DocEditor.jsx @@ -0,0 +1,129 @@ +import { useState } from 'react'; +import Step1BaseScale from '../components/editor/Step1BaseScale'; +import Step2FuzzyModeling from '../components/editor/Step2FuzzyModeling'; +import { calculateValueFunction } from '../services/docService'; + +export default function DocEditor() { + const [step, setStep] = useState(1); + const [isLoading, setIsLoading] = useState(false); + + // ESTADOS: FASE 1 + const [criterionName, setCriterionName] = useState(''); + const [levels, setLevels] = useState(['', '', '']); + const [blankCards, setBlankCards] = useState([0, 0]); + const [errors, setErrors] = useState({ criterion: false, levels: [] }); + + // ESTADOS: FASE 2 + const [baseScale, setBaseScale] = useState({}); + const [selectedTerm, setSelectedTerm] = useState(null); + const [mfDefinitions, setMfDefinitions] = useState({}); + + // MANEJADORES: FASE 1 + const handleCriterionChange = (val) => { setCriterionName(val); if (errors.criterion) setErrors({ ...errors, criterion: false }); }; + const handleLevelChange = (index, newValue) => { const newLevels = [...levels]; newLevels[index] = newValue; setLevels(newLevels); if (errors.levels[index]) setErrors({ ...errors, levels: errors.levels.map((e, i) => i === index ? false : e) }); }; + const handleAddLevel = () => { setLevels([...levels, '']); setBlankCards([...blankCards, 0]); setErrors({ ...errors, levels: [...errors.levels, false] }); }; + const handleRemoveLevel = (indexToRemove) => { if (levels.length <= 3) return; setLevels(levels.filter((_, i) => i !== indexToRemove)); setBlankCards(blankCards.filter((_, i) => i !== (indexToRemove === 0 ? 0 : indexToRemove - 1))); setErrors({ ...errors, levels: errors.levels.filter((_, i) => i !== indexToRemove) }); }; + const handleBlankCardChange = (index, delta) => { const newCards = [...blankCards]; if (newCards[index] + delta >= 0) { newCards[index] += delta; setBlankCards(newCards); } }; + + const handleGenerateBaseScale = async () => { + const newErrors = { criterion: !criterionName.trim(), levels: levels.map(l => !l.trim()) }; + if (newErrors.criterion || newErrors.levels.includes(true)) { + setErrors(newErrors); + return alert("Por favor, rellena todos los campos."); + } + + setIsLoading(true); + try { + const payloadBase = { criterion_name: criterionName.trim(), levels: levels.map(l => l.trim()), blank_cards: blankCards, references: { "0": 0, [(levels.length - 1).toString()]: 1 } }; + const baseResult = await calculateValueFunction(payloadBase); + + setBaseScale(baseResult.values); + const initialMfs = {}; + Object.entries(baseResult.values).forEach(([name, value]) => { initialMfs[name] = { supportStart: value, coreStart: value, coreEnd: value, supportEnd: value }; }); + + setMfDefinitions(initialMfs); + setSelectedTerm(Object.keys(baseResult.values)[0]); + setStep(2); + } catch (error) { alert("Error: " + error); } finally { setIsLoading(false); } + }; + + // MANEJADORES: FASE 2 + const updateCurrentMf = (field, value) => { + if (!selectedTerm) return; + let numValue = parseFloat(value); + + setMfDefinitions(prev => { + const scaleKeys = Object.keys(baseScale); + const selectedIndex = scaleKeys.indexOf(selectedTerm); + let prevCoreEnd = 0, prevSupportEnd = 0, nextCoreStart = 1, nextSupportStart = 1; + + if (selectedIndex > 0) { + prevCoreEnd = prev[scaleKeys[selectedIndex - 1]].coreEnd; + prevSupportEnd = prev[scaleKeys[selectedIndex - 1]].supportEnd; + } + if (selectedIndex < scaleKeys.length - 1) { + nextCoreStart = prev[scaleKeys[selectedIndex + 1]].coreStart; + nextSupportStart = prev[scaleKeys[selectedIndex + 1]].supportStart; + } + + const anchor = baseScale[selectedTerm]; + + if (field === 'supportStart' && numValue < prevCoreEnd) numValue = prevCoreEnd; + if (field === 'coreStart' && numValue < prevSupportEnd) numValue = prevSupportEnd; + if (field === 'coreEnd' && numValue > nextSupportStart) numValue = nextSupportStart; + if (field === 'supportEnd' && numValue > nextCoreStart) numValue = nextCoreStart; + + if ((field === 'supportStart' || field === 'coreStart') && numValue > anchor) numValue = anchor; + if ((field === 'supportEnd' || field === 'coreEnd') && numValue < anchor) numValue = anchor; + + const current = { ...prev[selectedTerm], [field]: numValue }; + + if (field === 'supportStart') { + if (current.supportStart > current.coreStart) current.coreStart = current.supportStart; + if (current.coreStart > current.coreEnd) current.coreEnd = current.coreStart; + if (current.coreEnd > current.supportEnd) current.supportEnd = current.coreEnd; + } else if (field === 'coreStart') { + if (current.coreStart < current.supportStart) current.supportStart = current.coreStart; + if (current.coreStart > current.coreEnd) current.coreEnd = current.coreStart; + if (current.coreEnd > current.supportEnd) current.supportEnd = current.coreEnd; + } else if (field === 'coreEnd') { + if (current.coreEnd > current.supportEnd) current.supportEnd = current.coreEnd; + if (current.coreEnd < current.coreStart) current.coreStart = current.coreEnd; + if (current.coreStart < current.supportStart) current.supportStart = current.coreStart; + } else if (field === 'supportEnd') { + if (current.supportEnd < current.coreEnd) current.coreEnd = current.supportEnd; + if (current.coreEnd < current.coreStart) current.coreStart = current.coreEnd; + if (current.coreStart < current.supportStart) current.supportStart = current.coreStart; + } + + return { ...prev, [selectedTerm]: current }; + }); + }; + + const handleFinalSubmit = () => { + console.log("PAYLOAD DOC-MF:", { baseScale, mfDefinitions }); + alert("¡Mira la consola! JSON preparado."); + }; + + return ( +
+ {step === 1 && ( + + )} + {step === 2 && ( + setStep(1)} + /> + )} +
+ ); +} \ No newline at end of file diff --git a/frontend/src/routers/AppRouter.jsx b/frontend/src/routers/AppRouter.jsx index 3259cfb..5cc0534 100644 --- a/frontend/src/routers/AppRouter.jsx +++ b/frontend/src/routers/AppRouter.jsx @@ -1,13 +1,13 @@ import { BrowserRouter, Routes, Route, Navigate } from 'react-router-dom'; import MainLayout from '../components/layout/MainLayout'; -import AdvancedMode from '../pages/AdvancedMode'; +import DocEditor from '../pages/DocEditor'; export function AppRouter() { return ( }> - } /> + } /> } />