diff --git a/frontend/src/pages/AdvancedMode.jsx b/frontend/src/pages/AdvancedMode.jsx index 3a96f2d..2a3b9a9 100644 --- a/frontend/src/pages/AdvancedMode.jsx +++ b/frontend/src/pages/AdvancedMode.jsx @@ -1,7 +1,319 @@ +import React, { useState } from 'react'; +import { ComposedChart, Line, XAxis, YAxis, CartesianGrid, ReferenceArea, ReferenceLine, ResponsiveContainer, Tooltip } from 'recharts'; +import CriterionInput from '../components/CriterionInput'; +import CardEditor from '../components/CardEditor'; +import BlankCardsCounter from '../components/BlankCardsCounter'; +import AddLevelButton from '../components/AddLevelButton'; +import { calculateValueFunction } from '../services/docService'; + +const COLORS = [ + '#ef4444', + '#f59e0b', + '#10b981', + '#3b82f6', + '#d946ef', + '#06b6d4', + '#8b5cf6', + '#f43f5e', + '#6366f1' +]; + export default function AdvancedMode() { + + const [step, setStep] = useState(1); + const [isLoading, setIsLoading] = useState(false); + + const [criterionName, setCriterionName] = useState(''); + const [levels, setLevels] = useState(['', '', '']); + const [blankCards, setBlankCards] = useState([0, 0]); + const [errors, setErrors] = useState({ criterion: false, levels: [] }); + + const [baseScale, setBaseScale] = useState({}); + const [selectedTerm, setSelectedTerm] = useState(null); + const [mfDefinitions, setMfDefinitions] = useState({}); + + 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; + const newLevels = levels.filter((_, index) => index !== indexToRemove); + const blankIndexToRemove = indexToRemove === 0 ? 0 : indexToRemove - 1; + const newBlankCards = blankCards.filter((_, index) => index !== blankIndexToRemove); + setLevels(newLevels); + setBlankCards(newBlankCards); + setErrors({ ...errors, levels: errors.levels.filter((_, index) => index !== indexToRemove) }); + }; + + const handleBlankCardChange = (index, delta) => { + const newBlankCards = [...blankCards]; + const newValue = newBlankCards[index] + delta; + if (newValue >= 0) { + newBlankCards[index] = newValue; + setBlankCards(newBlankCards); + } + }; + + const handleGenerateBaseScale = async () => { + let hasError = false; + const newErrors = { criterion: false, levels: Array(levels.length).fill(false) }; + if (!criterionName.trim()) { newErrors.criterion = true; hasError = true; } + levels.forEach((lvl, idx) => { if (!lvl.trim()) { newErrors.levels[idx] = true; hasError = true; }}); + setErrors(newErrors); + + if (hasError) return alert("Por favor, rellena todos los campos de la escala base."); + + 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); + const calculatedValues = baseResult.values; + setBaseScale(calculatedValues); + + const initialMfs = {}; + Object.entries(calculatedValues).forEach(([name, value]) => { + initialMfs[name] = { + supportStart: value, + coreStart: value, + coreEnd: value, + supportEnd: value + }; + }); + setMfDefinitions(initialMfs); + setSelectedTerm(Object.keys(calculatedValues)[0]); + setStep(2); + + } catch (error) { + alert("Error al conectar con el backend: " + error); + } finally { + setIsLoading(false); + } + }; + + const updateCurrentMf = (field, value) => { + if (!selectedTerm) return; + const numValue = parseFloat(value); + + setMfDefinitions(prev => { + const current = { ...prev[selectedTerm], [field]: numValue }; + + // Reglas de colisión internas + if (field === 'supportStart' && current.supportStart > current.coreStart) current.coreStart = current.supportStart; + if (field === 'coreStart') { + if (current.coreStart < current.supportStart) current.supportStart = current.coreStart; + if (current.coreStart > current.coreEnd) current.coreEnd = current.coreStart; + } + if (field === 'coreEnd') { + if (current.coreEnd < current.coreStart) current.coreStart = current.coreEnd; + if (current.coreEnd > current.supportEnd) current.supportEnd = current.coreEnd; + } + if (field === 'supportEnd' && current.supportEnd < current.coreEnd) current.coreEnd = current.supportEnd; + + return { ...prev, [selectedTerm]: current }; + }); + }; + + const handleFinalSubmit = () => { + console.log("DATOS FINALES LISTOS PARA EL BACKEND:", { + base_scale: baseScale, + membership_functions: mfDefinitions + }); + alert("¡Mira la consola! El JSON está preparado."); + }; + + // Cálculo de límites verticales + const scaleKeys = Object.keys(baseScale); + const selectedIndex = scaleKeys.indexOf(selectedTerm); + const selectedColor = COLORS[selectedIndex % COLORS.length] || '#2563eb'; + const currentMf = selectedTerm ? mfDefinitions[selectedTerm] : null; + + let minBound = 0; + let maxBound = 1; + + if (selectedIndex > 0) { + minBound = baseScale[scaleKeys[selectedIndex - 1]]; + } + if (selectedIndex >= 0 && selectedIndex < scaleKeys.length - 1) { + maxBound = baseScale[scaleKeys[selectedIndex + 1]]; + } + + return ( -