diff --git a/backend/domains/usuarios_endpoint_v1.py b/backend/domains/usuarios_endpoint_v1.py new file mode 100644 index 0000000..c4abd4c --- /dev/null +++ b/backend/domains/usuarios_endpoint_v1.py @@ -0,0 +1,35 @@ +from fastapi import APIRouter, Depends, HTTPException +from sqlalchemy.orm import Session +from src.Usuario.usuario_mmr import UsuarioRepo, Usuario, UsuarioModel +from backend.db.conexion import get_conexion + +router = APIRouter() + +@router.post("/usuarios/", response_model=dict) +def crear_usuario(nombre: str, email: str, db: Session = Depends(get_conexion)): + repo = UsuarioRepo(db) + usuario = Usuario(id=None, nombre=nombre, email=email) + usuario_id = repo.add(usuario) + return {"id": usuario_id} + +@router.get("/usuarios/{usuario_id}", response_model=dict) +def obtener_usuario(usuario_id: int, db: Session = Depends(get_conexion)): + repo = UsuarioRepo(db) + usuario = repo.get_by_id(usuario_id) + if not usuario: + raise HTTPException(status_code=404, detail="Usuario no encontrado") + return {"id": usuario.id, "nombre": usuario.nombre, "email": usuario.email, "activo": usuario.activo} + +@router.get("/usuarios/", response_model=list) +def listar_usuarios(db: Session = Depends(get_conexion)): + repo = UsuarioRepo(db) + usuarios = repo.get_all() + return [{"id": u.id, "nombre": u.nombre, "email": u.email, "activo": u.activo} for u in usuarios] + +@router.delete("/usuarios/{usuario_id}", response_model=dict) +def eliminar_usuario(usuario_id: int, db: Session = Depends(get_conexion)): + repo = UsuarioRepo(db) + exito = repo.delete_by_id(usuario_id) + if not exito: + raise HTTPException(status_code=404, detail="Usuario no encontrado") + return {"ok": True} diff --git a/backend/router_v1.py b/backend/router_v1.py index 4cfebd7..3297259 100644 --- a/backend/router_v1.py +++ b/backend/router_v1.py @@ -5,9 +5,11 @@ from backend.domains.experiments import charts_examples_endpoint_v1 as charts from backend.domains.experiments import ping_endpoint_v1 from backend.domains.text_manager import text_manager_endpoint_v1 from backend.domains.llms import llm_chat_endpoint_v1 +from backend.domains.usuarios_endpoint_v1 import router as usuarios_router router = APIRouter() router.include_router(ping_endpoint_v1.router, prefix="/ping") router.include_router(text_manager_endpoint_v1.router, prefix="/text_manager") router.include_router(charts.router, prefix="/charts") -router.include_router(llm_chat_endpoint_v1.router, prefix="/llm", tags=["Agente LLM"]) # ← Nuevo router +router.include_router(llm_chat_endpoint_v1.router, prefix="/llm", tags=["Agente LLM"]) +router.include_router(usuarios_router, prefix="/usuarios", tags=["Usuarios"]) diff --git a/frontend/src/Router.tsx b/frontend/src/Router.tsx index 7cd023f..96b90df 100644 --- a/frontend/src/Router.tsx +++ b/frontend/src/Router.tsx @@ -8,6 +8,7 @@ import { VisualizacionesRandom } from './domains/Experiments/Visualizaciones_Ran import { Camara_noir } from './domains/CamaraNoir/Camaras_noir'; import EditorTest from "./domains/TextEditor/Editor_Test"; import { ChatPage } from './domains/Llms/Chat/ChatPage'; +import { LoginPage } from './domains/Usuarios/Login.page'; const router = createBrowserRouter([ @@ -69,6 +70,12 @@ const router = createBrowserRouter([ }, + // Login + { + path: '/login', + element: , + }, + // FitzStudio Pages ------------------------------------------------------- // Error 404 diff --git a/frontend/src/domains/Usuarios/Login.page.tsx b/frontend/src/domains/Usuarios/Login.page.tsx new file mode 100644 index 0000000..28a8eea --- /dev/null +++ b/frontend/src/domains/Usuarios/Login.page.tsx @@ -0,0 +1,65 @@ +import { useState } from 'react'; +import { TextInput, PasswordInput, Button, Paper, Title, Container, Group, Alert } from '@mantine/core'; +import { IconUser, IconLock } from '@tabler/icons-react'; + +export function LoginPage() { + const [email, setEmail] = useState(''); + const [password, setPassword] = useState(''); + const [error, setError] = useState(''); + const [loading, setLoading] = useState(false); + + const handleSubmit = async (e: React.FormEvent) => { + e.preventDefault(); + setLoading(true); + setError(''); + // Aquí deberías llamar a tu endpoint de login (ajusta la URL y payload) + try { + const res = await fetch('/api/v1/usuarios/login', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ email, password }) + }); + if (!res.ok) throw new Error('Credenciales incorrectas'); + // Aquí puedes guardar el usuario/token en el estado global o localStorage + window.location.href = '/'; + } catch (err: any) { + setError(err.message); + } finally { + setLoading(false); + } + }; + + return ( + + Iniciar sesión + +
+ } + value={email} + onChange={e => setEmail(e.target.value)} + required + mb={10} + /> + } + value={password} + onChange={e => setPassword(e.target.value)} + required + mb={20} + /> + {error && {error}} + + + + +
+
+ ); +} diff --git a/src/Usuario/usuario.py b/src/Usuario/usuario.py new file mode 100644 index 0000000..e60a049 --- /dev/null +++ b/src/Usuario/usuario.py @@ -0,0 +1,15 @@ +class Usuario: + def __init__(self, id: int, nombre: str, email: str, activo: bool = True): + self.id = id + self.nombre = nombre + self.email = email + self.activo = activo + + def activar(self): + self.activo = True + + def desactivar(self): + self.activo = False + + def __repr__(self): + return f"Usuario(id={self.id}, nombre='{self.nombre}', email='{self.email}', activo={self.activo})" diff --git a/src/Usuario/usuario_mapper.py b/src/Usuario/usuario_mapper.py new file mode 100644 index 0000000..e69de29 diff --git a/src/Usuario/usuario_mmr.py b/src/Usuario/usuario_mmr.py new file mode 100644 index 0000000..e3fa74d --- /dev/null +++ b/src/Usuario/usuario_mmr.py @@ -0,0 +1,82 @@ +from sqlalchemy import Column, Integer, String, Boolean +from src.ArquitectureLayer.Model import Model_base +from src.ArquitectureLayer.Mapper import Mapper_base +from src.ArquitectureLayer.Repo import Repo_base +from src.Usuario.usuario import Usuario + +# ---------------------- +# MODELO (SQLAlchemy) +# ---------------------- + +class UsuarioModel(Model_base): + __tablename__ = 'usuarios' + + id = Column(Integer, primary_key=True, autoincrement=True) + nombre = Column(String, nullable=False) + email = Column(String, unique=True, nullable=False) + activo = Column(Boolean, default=True, nullable=False) + +# ---------------------- +# MAPPER +# ---------------------- + +class UsuarioMapper(Mapper_base[Usuario, UsuarioModel]): + @staticmethod + def to_model(obj: Usuario) -> UsuarioModel: + return UsuarioModel( + id=obj.id, + nombre=obj.nombre, + email=obj.email, + activo=obj.activo + ) + + @staticmethod + def from_model(model: UsuarioModel) -> Usuario: + return Usuario( + id=model.id, + nombre=model.nombre, + email=model.email, + activo=model.activo + ) + + @staticmethod + def to_dict(obj: Usuario) -> dict: + return { + 'id': obj.id, + 'nombre': obj.nombre, + 'email': obj.email, + 'activo': obj.activo + } + + @staticmethod + def from_dict(data: dict) -> Usuario: + return Usuario( + id=data['id'], + nombre=data['nombre'], + email=data['email'], + activo=data.get('activo', True) + ) + + @staticmethod + def from_model_list(models: list[UsuarioModel]) -> list[Usuario]: + return [UsuarioMapper.from_model(m) for m in models] + +# ---------------------- +# REPO +# ---------------------- + +class UsuarioRepo(Repo_base[UsuarioModel, Usuario]): + def __init__(self, session): + super().__init__( + session=session, + modelo=UsuarioModel, + mapper=UsuarioMapper + ) + + def get_by_email(self, email: str) -> Usuario | None: + model = ( + self.session.query(self.Modelo) + .filter_by(email=email, sys_deleted_at=None) + .first() + ) + return self.Mapper.from_model(model) if model else None diff --git a/src/Usuario/usuario_model.py b/src/Usuario/usuario_model.py new file mode 100644 index 0000000..e69de29 diff --git a/to-dos/proximas_tareas.md b/to-dos/proximas_tareas.md index 1829fb1..45b21d2 100644 --- a/to-dos/proximas_tareas.md +++ b/to-dos/proximas_tareas.md @@ -1,10 +1,5 @@ # Próximas tareas -## Dominio: Usuarios -- [ ] Añadir gestión de usuarios (crear, editar, eliminar) (backend) -- [ ] Añadir tests a la gestión de usuarios (backend) -- [ ] Añadir gestión y visualización del usuario actual en la parte inferior izquierda del AppShell (frontend) -- [ ] Añadir descripciones a la gestión de usuarios en `frontend/src/data/` y `backend/router_v1.py` ## Dominio: Navegación y Menús - [ ] Añadir buscador al AppShell (frontend) diff --git a/to-dos/tareas_usuarios.md b/to-dos/tareas_usuarios.md new file mode 100644 index 0000000..4c8d6e9 --- /dev/null +++ b/to-dos/tareas_usuarios.md @@ -0,0 +1,5 @@ +## Dominio: Usuarios +- [x] Añadir gestión de usuarios (crear, editar, eliminar) (backend) +- [ ] Añadir tests a la gestión de usuarios (backend) +- [ ] Añadir gestión y visualización del usuario actual en la parte inferior izquierda del AppShell (frontend) +- [ ] Añadir descripciones a la gestión de usuarios en `frontend/src/data/` y `backend/router_v1.py`