-
DoC
+
+
+
+
+
+
+
+ DoC
+
+
+ Deck of Cards
+
+
+
+
+ {isAuthenticated ? (
+
+
{
+ if(window.confirm('¿Deseas cerrar sesión?')) {
+ logout();
+ }
+ }}
+ >
+ {user?.username?.charAt(0).toUpperCase() || 'U'}
+
+
+ ) : (
+
+ Iniciar Sesión
+
+ )}
-
- Deck of Cards
-
+
- {/* Contenido principal */}
-
+
-
);
}
\ No newline at end of file
diff --git a/frontend/src/lib/api.js b/frontend/src/lib/api.js
index dae5d65..787616f 100644
--- a/frontend/src/lib/api.js
+++ b/frontend/src/lib/api.js
@@ -9,5 +9,12 @@ const api = Axios.create({
}
});
+api.interceptors.request.use((config) => {
+ const token = localStorage.getItem('token');
+ if (token) {
+ config.headers.Authorization = `Bearer ${token}`;
+ }
+ return config;
+});
export default api;
\ No newline at end of file
diff --git a/frontend/src/pages/AdvancedMode.jsx b/frontend/src/pages/AdvancedMode.jsx
deleted file mode 100644
index 88a5a83..0000000
--- a/frontend/src/pages/AdvancedMode.jsx
+++ /dev/null
@@ -1,236 +0,0 @@
-import React, { useState, useEffect, useRef } from 'react';
-import CriterionInput from '../components/CriterionInput';
-import CardEditor from '../components/CardEditor';
-import BlankCardsCounter from '../components/BlankCardsCounter';
-import AddLevelButton from '../components/AddLevelButton';
-import Chart from '../components/membershipFunction/Chart';
-import Controls from '../components/membershipFunction/Controls';
-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 [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, step]);
-
- // Estados Fase 2 (Franjas)
- const [baseScale, setBaseScale] = useState({});
- const [selectedTerm, setSelectedTerm] = useState(null);
- const [mfDefinitions, setMfDefinitions] = useState({});
-
- // Manejadores de Escala
- 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); }
- };
-
- 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:", { base_scale: baseScale, membership_functions: mfDefinitions });
- alert("¡Mira la consola! JSON preparado.");
- };
-
- const scaleKeys = Object.keys(baseScale);
- const selectedColor = COLORS[scaleKeys.indexOf(selectedTerm) % COLORS.length] || '#2563eb';
-
- 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 */}
- {step === 1 && (
-
-
-
-
- Paso 1: Establecer escala
-
- {needsZoom && (
-
- )}
-
-
-
-
-
-
-
-
-
- {levels.map((level, index) => (
-
-
- 3} />
-
- {index < levels.length - 1 && (
-
- )}
-
- ))}
-
-
-
-
-
-
-
-
-
-
-
-
-
- )}
-
- {/* PASO 2 */}
- {step === 2 && (
-
-
-
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/BasicMode.jsx b/frontend/src/pages/BasicMode.jsx
deleted file mode 100644
index 3d913e6..0000000
--- a/frontend/src/pages/BasicMode.jsx
+++ /dev/null
@@ -1,156 +0,0 @@
-import { useState } from 'react';
-import CriterionInput from '../components/CriterionInput';
-import CardEditor from '../components/CardEditor';
-import BlankCardsCounter from '../components/BlankCardsCounter';
-import AddLevelButton from '../components/AddLevelButton';
-import ValueFunctionChart from '../components/ValueFunctionChart';
-import { calculateValueFunction } from '../services/docService';
-
-export default function BasicMode() {
- const [criterionName, setCriterionName] = useState('');
- const [levels, setLevels] = useState(['', '', '']);
- const [blankCards, setBlankCards] = useState([0, 0]);
-
- const [isLoading, setIsLoading] = useState(false);
- const [result, setResult] = useState(null);
-
- const [errors, setErrors] = useState({ criterion: false, levels: [] });
-
- const handleCalculate = async () => {
-
- let hasError = false;
- const newErrors = { criterion: false, levels: Array(levels.length).fill(false) };
-
- if (!criterionName.trim()) {
- newErrors.criterion = true;
- hasError = true;
- }
-
- levels.forEach((level, idx) => {
- if (!level.trim()) {
- newErrors.levels[idx] = true;
- hasError = true;
- }
- });
-
- setErrors(newErrors);
-
- if (hasError) return;
-
- setIsLoading(true);
- setResult(null);
-
- const payload = {
- criterion_name: criterionName.trim(),
- levels: levels.map(l => l.trim()),
- blank_cards: blankCards,
- references: { "0": 0, [(levels.length - 1).toString()]: 1 }
- };
-
- try {
- const data = await calculateValueFunction(payload);
- setResult(data);
- } catch (error) {
- alert("No se ha podido conectar con el backend: " + error);
- } finally {
- setIsLoading(false);
- }
- };
-
- 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]) {
- const newErrLevels = [...errors.levels];
- newErrLevels[index] = false;
- setErrors({ ...errors, levels: newErrLevels });
- }
- };
-
- 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);
-
- const newErrLevels = errors.levels.filter((_, index) => index !== indexToRemove);
-
- setLevels(newLevels);
- setBlankCards(newBlankCards);
- setErrors({ ...errors, levels: newErrLevels });
- };
-
- const handleBlankCardChange = (index, delta) => {
- const newBlankCards = [...blankCards];
- const newValue = newBlankCards[index] + delta;
- if (newValue >= 0) {
- newBlankCards[index] = newValue;
- setBlankCards(newBlankCards);
- }
- };
-
- return (
-
-
-
-
-
- {levels.map((level, index) => (
-
-
-
-
- {index < levels.length - 1 && (
-
- )}
-
- ))}
-
-
-
-
-
-
-
-
-
-
-
- );
-}
\ No newline at end of file
diff --git a/frontend/src/pages/Login.jsx b/frontend/src/pages/Login.jsx
new file mode 100644
index 0000000..e6a48ff
--- /dev/null
+++ b/frontend/src/pages/Login.jsx
@@ -0,0 +1,88 @@
+import { useState } from 'react';
+import { useNavigate, Link } from 'react-router-dom';
+import { useAuth } from '../context/AuthContext';
+import { authService } from '../services/authService';
+
+export default function Login() {
+ const [email, setEmail] = useState('');
+ const [password, setPassword] = useState('');
+ const [error, setError] = useState('');
+ const navigate = useNavigate();
+ const { login } = useAuth();
+
+ const handleSubmit = async (e) => {
+ e.preventDefault();
+ setError('');
+
+ try {
+ const data = await authService.login(email, password);
+
+ const userData = {
+ id: data.user_id,
+ username: data.username,
+ email: email
+ };
+
+ login(userData, data.token);
+ navigate('/');
+ } catch (err) {
+ setError(err.response?.data?.detail || 'Error al iniciar sesión. Revisa tus credenciales.');
+ }
+ };
+
+ return (
+
+
+
+
Bienvenido
+
Inicia sesión para guardar tus espectros difusos
+
+
+ {error && (
+
+ {error}
+
+ )}
+
+
+
+
+ ¿No tienes cuenta? Regístrate aquí
+
+
+
+ );
+}
\ No newline at end of file
diff --git a/frontend/src/pages/Register.jsx b/frontend/src/pages/Register.jsx
new file mode 100644
index 0000000..3055c9d
--- /dev/null
+++ b/frontend/src/pages/Register.jsx
@@ -0,0 +1,102 @@
+import { useState } from 'react';
+import { useNavigate, Link } from 'react-router-dom';
+import { useAuth } from '../context/AuthContext';
+import { authService } from '../services/authService';
+
+export default function Register() {
+ const [username, setUsername] = useState('');
+ const [email, setEmail] = useState('');
+ const [password, setPassword] = useState('');
+ const [error, setError] = useState('');
+ const navigate = useNavigate();
+ const { login } = useAuth();
+
+ const handleSubmit = async (e) => {
+ e.preventDefault();
+ setError('');
+
+ try {
+ const data = await authService.register(username, email, password);
+
+ const userData = {
+ id: data.user_id,
+ username: username,
+ email: email
+ };
+
+ login(userData, data.token);
+ navigate('/');
+ } catch (err) {
+ setError(err.response?.data?.detail || 'Error al registrar el usuario.');
+ }
+ };
+
+ return (
+
+
+
+
Crear Cuenta
+
Únete para guardar tu progreso
+
+
+ {error && (
+
+ {error}
+
+ )}
+
+
+
+
+ ¿Ya tienes cuenta? Inicia sesión aquí
+
+
+
+ );
+}
\ No newline at end of file
diff --git a/frontend/src/routers/AppRouter.jsx b/frontend/src/routers/AppRouter.jsx
index 5cc0534..679ca28 100644
--- a/frontend/src/routers/AppRouter.jsx
+++ b/frontend/src/routers/AppRouter.jsx
@@ -1,6 +1,8 @@
import { BrowserRouter, Routes, Route, Navigate } from 'react-router-dom';
import MainLayout from '../components/layout/MainLayout';
import DocEditor from '../pages/DocEditor';
+import Login from '../pages/Login';
+import Register from '../pages/Register';
export function AppRouter() {
return (
@@ -8,6 +10,8 @@ export function AppRouter() {
}>
} />
+ } />
+ } />
} />
diff --git a/frontend/src/services/authService.js b/frontend/src/services/authService.js
new file mode 100644
index 0000000..be72b64
--- /dev/null
+++ b/frontend/src/services/authService.js
@@ -0,0 +1,13 @@
+import api from '../lib/api';
+
+export const authService = {
+ login: async (email, password) => {
+ const response = await api.post('/auth/login', { email, password });
+ return response.data;
+ },
+
+ register: async (username, email, password) => {
+ const response = await api.post('/auth/register', { username, email, password });
+ return response.data;
+ }
+};
\ No newline at end of file