diff --git a/.gitignore b/.gitignore
index a8ebeb5..cbfb8f8 100644
--- a/.gitignore
+++ b/.gitignore
@@ -3,11 +3,15 @@ __pycache__/
*.py[cod]
*$py.class
-# Variables de entorno
-.env
-.env*
+# Variables de entorno (solo ignoramos las locales/sobre-escrituras)
+# Mantenemos .env y .env.example versionados para compartir la configuración
+# de producción y el esqueleto de variables. Nunca subimos .env.local.
+.env.local
+.env.*.local
+# Override de Docker Compose para desarrollo local (no debe llegar a producción)
+docker-compose.override.yml
# Configuraciones del editor
.vscode/
-.idea/
\ No newline at end of file
+.idea/
diff --git a/README.md b/README.md
index 2f10aad..60714e5 100644
--- a/README.md
+++ b/README.md
@@ -1,6 +1,4 @@
-# Deck of Cards
-
-
+# Deck of Cards
@@ -28,11 +26,76 @@ Incluye:
---
+# 📸 Capturas de Pantalla
+
+## 🛠️ Proceso de Modelado (3 Pasos)
+El núcleo de la aplicación guía al experto a través de tres fases intuitivas para transformar cartas físicas en modelos matemáticos.
+
+
+
+
+
Paso 1: Definición de la Escala Base
+
+
+
+
+
+
+
+
+
Paso 2: Modelado Difuso
+
+
+
Paso 2: Definición de Subescalas e Intervalos
+
+
+
+
+
+
+
+
+
Paso 3: Visualización de la Función Final
+
+
+
+
+
+## 📂 Historial
+
+
+
+
+
Listado del Historial de Modelos
+
+
+
Vista de Detalle de un Modelo Guardado
+
+
+
+
+
+## 🔐 Acceso a la Plataforma
+
+
+
+
+
Login
+
+
+
+
Registro
+
+
+
+
+
+---
+
# ⚡ 0. ¿En qué consiste?
-**Deck of Cards – TFG** es una herramienta completa diseñada para construir, validar y evaluar **funciones de pertenencia difusas** mediante el método **Deck of Cards (DoC)**, tanto en su versión **T1MF** (tipo-1) como **IT2MF** (tipo-2 intervalar).
+**Deck of Cards** es una herramienta completa diseñada para construir, validar y evaluar **funciones de pertenencia difusas** mediante el método **Deck of Cards (DoC)**, tanto en su versión **T1MF** (tipo-1) como **IT2MF** (tipo-2 intervalar).
-> [!INFO]
> El sistema combina un backend robusto en **FastAPI + MongoDB**, un frontend moderno en **React + Vite**, autenticación por email y **Google OAuth 2.0**, y un sistema de historial por usuario para guardar y recuperar trabajos anteriores.
---
@@ -111,6 +174,13 @@ docker compose up --build
>- Frontend (react) → http://localhost:5173
>- Base de Datos (mongodb) → puerto 27017
+> [!NOTE]
+> **Nota para desarrolladores:** Gracias a Docker, no necesitas instalar nada localmente para que la app funcione. Sin embargo, para que tu editor de código reconozca las librerías, tenga autocompletado y no muestre errores de importación, te recomendamos entrar a las carpetas y descargar las dependencias en tu máquina local:
+> ```bash
+> cd frontend && npm install
+> cd ../backend && pip install -r requirements.txt
+> ```
+
---
# 🔌 3. Endpoints principales del proyecto
@@ -148,11 +218,12 @@ deck-of-cards/
│ ├── routers/
│ ├── models/
│ ├── utils/
-│ ├── .env
+│ ├── .env → !!!
│ └── main.py
│
├── frontend/ → Vite + React
│ ├── src/
+│ ├── .env → !!!
│ └── ...
│
├── docker-compose.yaml
@@ -161,7 +232,28 @@ deck-of-cards/
---
-# 🔐 5. Configuración del archivo .env
+# 🔐 5. Configuración de los archivos .env
+
+El frontend necesita un archivo `.env` para especificar el puerto del backend
+
+📍 **Este archivo debe estar dentro de la carpeta `frontend/`**, así:
+
+```
+deck-of-cards/
+│
+├── frontend/
+│ ├── src/
+│ ├── .env ← AQUÍ
+│ └── ...
+```
+
+Contenido obligatorio del `.env`:
+
+```
+VITE_API_URL=http://localhost:8000/api
+```
+
+Por otro lado,
El backend necesita un archivo `.env` para funcionar correctamente, especialmente para el login con Google.
@@ -177,7 +269,7 @@ deck-of-cards/
│ └── ...
```
-Contenido mínimo del `.env`:
+Contenido obligatorio del `.env`:
```
GOOGLE_CLIENT_ID=tu_client_id_de_google //id del cliente, debe ser el mismo del proyecto de google cloud.
@@ -200,37 +292,21 @@ Un ejemplo de página de donde sacar las tres primeras claves:
Para activar el login con Google, debes crear credenciales OAuth 2.0.
Para ello sigue estos pasos:
-### 🟦 Paso 1 — Entra en Google Cloud Console
+### 🟦 Paso 1 — Entra en Google Cloud Console y haz login
https://console.cloud.google.com
### 🟩 Paso 2 — Crea un proyecto
-Menú superior → “Seleccionar proyecto” → “Nuevo proyecto”.
+Menú superior → “Seleccionar proyecto” → “Nuevo proyecto” → Elige un nombre para tu proyecto.
-### 🟧 Paso 3 — Configura la pantalla de consentimiento OAuth
-
-
-
-
-En el buscador escribe: **OAuth consent screen** ó ve a la página que se muestra en la captura superior.
-
-Selecciona **Información de la página**:
-
-→ Rellena los datos necesarios (no hace falta poner un dominio si lo tienes en local) → Guardar
-
-
-
-
-
-### 🟨 Paso 4 — Crea las credenciales OAuth
-Menú lateral:
-**APIs & Services → Credentials → Create Credentials → OAuth Client ID**
+### 🟨 Paso 3 — Crea las credenciales OAuth
+En el menú lateral:
+**APIs y Servicios → Credenciales → Crear Credenciales → ID de Cliente OAuth**
-
-
+
Tipo de aplicación:
-✔ **Web application**
+✔ **Aplicación Web**
Añade en `Orígenes autorizados de javascript`:
http://localhost:8000
@@ -238,16 +314,37 @@ http://localhost:8000
Añade este Redirect URI obligatorio:
http://localhost:8000/api/auth/google/callback
+Después te saldrá una ventana para descargarte las claves en formato json (hazlo para que no se pierdan)
+
+Finalmente podrás ver la pantalla final (desde aquí no se puede acceder al secreto del cliente, solo puedes generar uno nuevo):
+
-
-### 🟥 Paso 5 — Copia tus claves
+### 🟧 Paso 4 — Copia tus claves
Google te mostrará:
- **Client ID**
- **Client Secret**
-Pégalos en tu `.env` dentro de `backend/`. Asegúrate de que las copias correctamente.
+Pégalos en tu `.env` dentro de `backend/`. Asegúrate de que las copias correctamente cada una en su lugar.
+
+### 🟥 Paso 5 — Configura la pantalla de consentimiento OAuth
+
+En el buscador escribe: **OAuth consent screen** o busca en el menu lateral la siguiente opción:
+
+
+
+Configura los datos de tu proyecto seleccionando **Información de la página**:
+
+→ Rellena los datos necesarios (no hace falta poner un dominio si lo tienes en local) → Guardar
+
+
+
+### 🟪 Paso 6 — Publicar la app
+
+Publica tu app accediendo a: (Este paso es muy importante para permitir acceder a cualquier correo)
+
+
---
@@ -261,8 +358,21 @@ Con esto, ya puedes:
- Levantar el sistema con Docker
- Usar login normal y login con Google
-Y ...
+Y... ¡Proyecto listo para ejecutarse en local!
-¡Proyecto listo para ejecutarse en local!
+## 👥 Autores y Equipo
-Gracias por llegar hasta aquí ;)
+Este proyecto es fruto de la colaboración académica con la **Universidad de Jaén**.
+
+| Rol | Desarrollador | GitHub |
+| :--- | :--- | :--- |
+| **Frontend** | Alexis López Moral | [@AlexisLopez-Dev](https://github.com/AlexisLopez-Dev) |
+| **Backend** | Mireya Cueto Garrido | [@MireyaCueto](https://github.com/MireyaCueto) |
+
+### 🎓 Dirección y Tutorización
+* **Director del proyecto:** Luis Martínez López
+
+---
+
+ Realizado con ❤️ en la Universidad de Jaén
+
diff --git a/backend/.env.example b/backend/.env.example
new file mode 100644
index 0000000..ec7a048
--- /dev/null
+++ b/backend/.env.example
@@ -0,0 +1,27 @@
+# Backend environment variables (FastAPI)
+# ----------------------------------------
+# Copia este archivo como `.env` para desarrollo local y rellena los valores.
+#
+# docker-compose levanta el backend con `env_file: backend/.env`.
+
+# Google OAuth (https://console.cloud.google.com/apis/credentials)
+# IMPORTANTE: la REDIRECT_URI es la URL a la que Google devuelve al usuario,
+# por tanto debe coincidir con la URL pública del backend tal como la ve el
+# navegador. Usando docker-compose, el backend está expuesto en el host en
+# el puerto 8070, así que:
+# http://localhost:8070/api/auth/google/callback
+# Si ejecutas el backend fuera de Docker en el puerto 8000, usa:
+# http://localhost:8000/api/auth/google/callback
+# Esta URI debe estar registrada también en la consola de Google Cloud.
+GOOGLE_CLIENT_ID=tu-client-id.apps.googleusercontent.com
+GOOGLE_CLIENT_SECRET=tu-client-secret
+GOOGLE_REDIRECT_URI=http://localhost:8070/api/auth/google/callback
+
+# Clave para firmar los JWT (usa algo largo y aleatorio en producción)
+SECRET_KEY=cambia-esta-clave-en-produccion
+
+# URL del frontend a la que se redirige tras el login con Google
+# Con docker-compose en local: http://localhost:8071
+# Con Vite directo en local: http://localhost:5173
+# En producción: https://tu-dominio.com
+FRONTEND_URL=http://localhost:5173
diff --git a/backend/api/routers/google_auth.py b/backend/api/routers/google_auth.py
index 2769868..1c787b8 100644
--- a/backend/api/routers/google_auth.py
+++ b/backend/api/routers/google_auth.py
@@ -13,6 +13,7 @@ GOOGLE_CLIENT_ID = os.getenv("GOOGLE_CLIENT_ID")
GOOGLE_CLIENT_SECRET = os.getenv("GOOGLE_CLIENT_SECRET")
REDIRECT_URI = os.getenv("GOOGLE_REDIRECT_URI")
SECRET_KEY = os.getenv("SECRET_KEY")
+FRONTEND_URL = os.getenv("FRONTEND_URL", "http://localhost:5173")
@router.get("/login")
async def google_login():
@@ -97,4 +98,4 @@ async def google_callback(request: Request):
{"$set": {"token": token}}
)
- return RedirectResponse(f"http://localhost:5173/login?token={token}")
\ No newline at end of file
+ return RedirectResponse(f"{FRONTEND_URL}/login?token={token}")
\ No newline at end of file
diff --git a/docker-compose.yaml b/docker-compose.yaml
index aead62e..48211f1 100644
--- a/docker-compose.yaml
+++ b/docker-compose.yaml
@@ -4,26 +4,30 @@ services:
context: ./backend
container_name: backend
ports:
- - "8000:8000"
+ - "8070:8000"
volumes:
- ./backend:/app
depends_on:
- db
env_file:
- - backend\.env
+ - backend/.env
frontend:
build:
context: ./frontend
container_name: frontend
ports:
- - "5173:5173"
+ - "8071:5173"
volumes:
- ./frontend:/app
- /app/node_modules
+ environment:
+ - VITE_API_URL=http://localhost:8070/api
+ depends_on:
+ - backend
db:
- image: mongo:6
+ image: mongo:4.4
container_name: mongo
restart: always
ports:
diff --git a/frontend/.env.example b/frontend/.env.example
new file mode 100644
index 0000000..3c23460
--- /dev/null
+++ b/frontend/.env.example
@@ -0,0 +1,22 @@
+# Frontend environment variables (Vite)
+# --------------------------------------
+# Copia este archivo como `.env.local` para desarrollo local.
+# Vite carga automáticamente `.env.local` encima de `.env`, así que
+# los valores aquí definidos sobrescribirán los de producción en dev.
+#
+# cp .env.example .env.local
+#
+# IMPORTANTE: Sólo las variables con prefijo VITE_ se exponen al cliente.
+
+# URL base de la API del backend (la ve el NAVEGADOR, no el contenedor).
+#
+# - Dev con docker-compose (recomendado): el backend está expuesto en el
+# puerto 8070 del host (mapea 8070 -> 8000 del contenedor).
+# VITE_API_URL=http://localhost:8070/api
+#
+# - Backend ejecutado fuera de Docker (uvicorn --port 8000):
+# VITE_API_URL=http://localhost:8000/api
+#
+# - Producción (ya definido en .env):
+# VITE_API_URL=http://sinbad2.ujaen.es:8070/api
+VITE_API_URL=http://localhost:8070/api
diff --git a/frontend/.gitignore b/frontend/.gitignore
index a547bf3..efe0071 100644
--- a/frontend/.gitignore
+++ b/frontend/.gitignore
@@ -12,6 +12,11 @@ dist
dist-ssr
*.local
+# Variables de entorno locales de Vite
+# (.env y .env.example sí se versionan)
+.env.local
+.env.*.local
+
# Editor directories and files
.vscode/*
!.vscode/extensions.json
diff --git a/frontend/src/components/membershipFunction/Controls.jsx b/frontend/src/components/membershipFunction/Controls.jsx
index 565cc84..252a6fb 100644
--- a/frontend/src/components/membershipFunction/Controls.jsx
+++ b/frontend/src/components/membershipFunction/Controls.jsx
@@ -1,4 +1,105 @@
-export default function Controls({
+import { useState } from 'react';
+
+function SliderInput({ label, value, min, max, step, color, onChange, decimals = 3 }) {
+ const [draft, setDraft] = useState(null);
+
+ const numericValue = Number(value);
+ const displayValue = draft !== null ? draft : numericValue.toFixed(decimals);
+
+ const commitDraft = () => {
+ if (draft === null) return;
+
+ const trimmed = draft.trim();
+ if (trimmed === '' || trimmed === '-' || trimmed === '.' || trimmed === '-.') {
+ setDraft(null);
+ return;
+ }
+
+ const parsed = parseFloat(trimmed);
+ if (Number.isNaN(parsed)) {
+ setDraft(null);
+ return;
+ }
+
+ const clamped = Math.min(max, Math.max(min, parsed));
+ onChange(clamped);
+ setDraft(null);
+ };
+
+ const handleNumberChange = (e) => {
+ const raw = e.target.value;
+
+ if (raw === '') {
+ setDraft('');
+ return;
+ }
+
+ const num = e.target.valueAsNumber;
+
+ if (Number.isNaN(num)) {
+ setDraft(raw);
+ return;
+ }
+
+ if (num > max) {
+ setDraft(max.toFixed(decimals));
+ onChange(max);
+ return;
+ }
+
+ if (num < min) {
+ setDraft(raw);
+ return;
+ }
+
+ setDraft(raw);
+ onChange(num);
+ };
+
+ const handleKeyDown = (e) => {
+ if (e.key === 'Enter') {
+ e.currentTarget.blur();
+ } else if (e.key === 'Escape') {
+ setDraft(null);
+ e.currentTarget.blur();
+ }
+ };
+
+ return (
+