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
+
+
+
+
+ );
+}
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`