Plataforma de orientación vocacional — proyecto integrador CodeUp Riwi: Beyond Limits.
Un usuario se registra, responde un test vocacional de 20 preguntas, recibe un perfil vocacional calculado y 3 carreras recomendadas, puede consultar el catálogo completo de carreras y revisar su historial de resultados. Un administrador puede gestionar preguntas y carreras, y consultar analítica básica de uso — todo desde la propia interfaz, sin tocar la base de datos ni Swagger.
- Características
- Stack tecnológico
- Arquitectura
- Requisitos previos
- Instalación y ejecución
- Variables de entorno
- Referencia de la API
- Modelo de datos
- Testing
- Crear un usuario administrador
- Estructura del proyecto
- Historias de usuario
- Decisiones técnicas destacadas
- Documentación adicional
- Autenticación JWT con registro (nombre, apellido, país, género, correo y contraseña) e inicio de sesión.
- Test vocacional de 20 preguntas (escala Likert 1–5), presentadas de a una, con navegación y validación de que todas fueron respondidas.
- Cálculo automático del perfil vocacional (5 perfiles posibles) a partir de las respuestas, con regla de desempate determinista.
- Recomendación de 3 carreras coherentes con el perfil obtenido, mostradas junto al resultado.
- Catálogo completo de carreras (20 carreras), con detalle individual y perfil asociado.
- Historial de resultados del usuario, ordenado del más reciente al más antiguo.
- Panel de administración (solo rol
admin): CRUD de preguntas y carreras, y analítica básica (tests completados, perfil más frecuente, carrera más recomendada). - Autorización por rol aplicada tanto en la API (
require_role) como reflejada en la interfaz (la navegación de administración solo aparece para admins). - Interfaz sin build tool: SPA en JavaScript vanilla con sistema de diseño propio basado en design tokens (paleta oscura, componentes
.card/.btnreutilizables, estados de foco/hover/activo/deshabilitado).
| Capa | Tecnología |
|---|---|
| Backend | FastAPI + SQLAlchemy 2.0 + Alembic |
| Base de datos | PostgreSQL 16 |
| Autenticación | JWT (PyJWT, HS256) + bcrypt |
| Frontend | JavaScript vanilla (SPA sin framework ni build tool) |
| Infraestructura | Docker Compose |
| Testing | pytest + httpx |
Ver Plan_Ruta_CareerPath_Individual.md para el plan de trabajo completo.
┌─────────────────┐ HTTP / JSON ┌──────────────────────┐ SQL ┌──────────────┐
│ Frontend (SPA) │ ───────────────────────▶ │ Backend (FastAPI) │ ───────────────▶ │ PostgreSQL │
│ JS vanilla │ ◀─────────────────────── │ SQLAlchemy + JWT │ ◀─────────────── │ │
└─────────────────┘ └──────────────────────┘ └──────────────┘
Live Server / uvicorn :8000 Docker volume
http.server :5500 (Docker o venv) persistente
El frontend nunca accede a la base de datos directamente: toda la comunicación pasa por la API REST del backend, autenticada con un token JWT guardado en localStorage tras el login.
- Docker Desktop en ejecución (para PostgreSQL y, opcionalmente, el backend).
- Algo para servir el frontend estático: extensión Live Server de VS Code, o
python -m http.server. - Solo si vas a correr el backend sin Docker: Python 3.12 (no una versión más nueva:
pydantic-coreybcryptno publican wheels precompilados para 3.13/3.14 y compilarlos desde código falla en Windows).
Desde la raíz del repositorio:
docker compose up -d --buildEsto levanta dos contenedores:
db: PostgreSQL 16 con usuario/clave/basecareerpath, con un volumen persistente.backend: construye la imagen a partir debackend/Dockerfile, espera a que la base de datos esté lista, correalembic upgrade headautomáticamente (crea las tablas y carga los datos semilla: perfiles vocacionales, 20 preguntas, 20 carreras) y levantauvicorncon recarga automática enhttp://localhost:8000.
No hace falta crear .env, instalar Python 3.12 ni un entorno virtual para este camino — las variables (DATABASE_URL, JWT_SECRET_KEY) ya están definidas en docker-compose.yml para desarrollo local. Confirma que quedó arriba en http://127.0.0.1:8000/docs.
Para ver los logs o confirmar que las migraciones corrieron: docker compose logs backend. Para apagar todo (conservando los datos): docker compose down. Para borrar también los datos: docker compose down -v.
Solo falta servir el frontend — ver Servir el frontend.
Útil si prefieres depurar con tu IDE en vez de dentro del contenedor. Requiere Python 3.12.
Primero levanta solo la base de datos: docker compose up -d db.
1. Variables de entorno — backend/.env no está versionado en git (contiene secretos). Crea el tuyo:
cd backend
cp .env.example .envY genera tu propio JWT_SECRET_KEY (no reutilices el de otra persona):
python -c "import secrets; print(secrets.token_hex(32))"Pega el resultado en .env, reemplazando el placeholder. DATABASE_URL ya apunta a localhost:5432, coincidente con el docker-compose.yml.
2. Entorno virtual e instalación de dependencias
cd backend
py -3.12 -m venv venvWindows:
./venv/Scripts/python.exe -m pip install -r requirements.txtMac/Linux:
source venv/bin/activate
pip install -r requirements.txt3. Migraciones
./venv/Scripts/python.exe -m alembic upgrade head4. Levantar el backend
./venv/Scripts/python.exe -m uvicorn app.main:app --reload --port 8000Con Live Server, o:
python -m http.server 5500 --directory frontendAbrir http://127.0.0.1:5500.
CORS: el backend (
app/main.py) solo acepta peticiones desdehttp://localhost:5500yhttp://127.0.0.1:5500. Si el frontend se sirve en otro puerto, hay que agregarlo aallow_originsenmain.py.
Definidas en backend/.env (flujo local) o directamente en docker-compose.yml (flujo Docker):
| Variable | Descripción | Valor de ejemplo |
|---|---|---|
DATABASE_URL |
Cadena de conexión SQLAlchemy a PostgreSQL | postgresql+psycopg://careerpath:careerpath@localhost:5432/careerpath |
JWT_SECRET_KEY |
Clave simétrica para firmar/verificar los JWT (HS256) | Generar con secrets.token_hex(32) |
Documentación interactiva completa (Swagger UI) disponible en http://localhost:8000/docs una vez el backend está arriba.
| Método | Ruta | Auth | Descripción |
|---|---|---|---|
POST |
/auth/register |
— | Crea un usuario (rol usuario por defecto). |
POST |
/auth/login |
— | Autentica y devuelve un JWT. |
GET |
/auth/me |
Usuario | Datos del usuario autenticado. |
| Método | Ruta | Auth | Descripción |
|---|---|---|---|
GET |
/test/questions |
Usuario | Las 20 preguntas, ordenadas (sin exponer el perfil que miden). |
POST |
/test/submit |
Usuario | Envía las 20 respuestas; calcula perfil + 3 carreras recomendadas. |
GET |
/test/history |
Usuario | Historial de resultados del usuario, del más reciente al más antiguo. |
| Método | Ruta | Auth | Descripción |
|---|---|---|---|
GET |
/careers |
Usuario | Catálogo completo (20 carreras), orden alfabético. |
GET |
/careers/{id} |
Usuario | Detalle de una carrera junto con su perfil vocacional. |
| Método | Ruta | Descripción |
|---|---|---|
GET |
/admin/profiles |
Lista los 5 perfiles vocacionales (para poblar formularios). |
GET / POST |
/admin/questions |
Listar / crear preguntas. |
PUT / DELETE |
/admin/questions/{id} |
Editar / eliminar una pregunta. |
GET / POST |
/admin/careers |
Listar / crear carreras. |
PUT / DELETE |
/admin/careers/{id} |
Editar / eliminar una carrera (protegido: no permite dejar un perfil con menos de 3 carreras). |
GET |
/admin/analytics |
Tests completados, perfil más frecuente, carrera más recomendada. |
| Método | Ruta | Descripción |
|---|---|---|
GET |
/health |
Healthcheck simple. |
Tablas principales (ver migraciones en backend/alembic/versions/):
users— cuenta, credenciales (hash bcrypt), rol (usuario/admin), datos de perfil (nombre, apellido, país, género).vocational_profiles— los 5 perfiles vocacionales posibles.questions— las 20 preguntas del test, cada una asociada a un perfil.test_attempts/answers— cada intento de test y las respuestas crudas asociadas (separado del resultado calculado).results— perfil calculado por intento, asociado al usuario (1 resultado por intento).careers— catálogo de carreras (20), cada una asociada a un perfil.
Los tests corren contra el entorno local de Python (no dentro del contenedor) y cubren la lógica de negocio pura (cálculo de perfil, validación de envíos, reglas de administración) sin necesidad de base de datos:
cd backend
./venv/Scripts/python.exe -m pytest -q| Archivo | Cubre |
|---|---|
tests/test_auth.py |
JWT (creación/decodificación), get_current_user, require_role. |
tests/test_test_submission.py |
Validación de envío del test, cálculo de perfil, selección de carreras recomendadas. |
tests/test_admin.py |
Reglas de borrado seguro de carreras, cálculo de analítica (moda de perfil, carrera más recomendada). |
No existe un flujo de auto-registro como admin (decisión intencional). Hay que registrarse normalmente desde la app y luego promover el rol directamente en la base de datos:
docker exec -it proyecto_integradorriwi-db-1 psql -U careerpath -d careerpath -c "UPDATE users SET role='admin' WHERE email='tu_correo@ejemplo.com';"Con ese usuario, la opción "Administración" aparece en la barra de navegación al iniciar sesión.
backend/
app/
main.py # instancia de FastAPI, CORS, routers
config.py # variables de entorno
database.py # engine SQLAlchemy, sesión
models.py # modelos ORM
schemas.py # esquemas Pydantic
security.py # hashing de contraseñas, JWT
dependencies.py # autenticación/autorización por rol
routers/ # auth, test, careers, admin
alembic/versions/ # migraciones de base de datos
tests/ # pruebas con pytest
Dockerfile # imagen del backend (migra y levanta uvicorn)
.dockerignore
frontend/
index.html
css/styles.css
js/
api.js # wrapper fetch hacia la API
app.js # punto de entrada de la SPA, navegación
views/ # register, login, test, results, careers, history, admin
docker-compose.yml # servicios db (PostgreSQL) y backend
| Historia | Prioridad | Descripción |
|---|---|---|
| HU-01 | Must | Registro de usuario |
| HU-02 | Must | Inicio de sesión |
| HU-03 | Must | Realizar el test vocacional |
| HU-04 | Must | Obtener el perfil vocacional calculado |
| HU-05 | Must | Recibir recomendación de 3 carreras |
| HU-06 | Must | Consultar catálogo de carreras |
| HU-07 | Should | Consultar historial de resultados |
| HU-08 / HU-08b | Should | Administrar preguntas y carreras (API + interfaz) |
| HU-09 | Should | Consultar analítica básica |
| HU-10 | Should | Sistema de diseño y tema visual |
- Doble validación (Pydantic +
CHECKen base de datos) para reglas de negocio críticas (rol, contraseña, rango de respuestas, género), de forma que la integridad no dependa únicamente de la API. - JWT stateless con HS256: sin refresh tokens ni lista de revocación, dado el alcance del proyecto; el logout es solo del lado del cliente.
- Cálculo del perfil oculto al frontend:
GET /test/questionsnunca expone a qué perfil pertenece cada pregunta, para que el resultado no pueda manipularse. - Autorización por rol reforzada a nivel de router (
require_role("admin")aplicado a todo/admin), no endpoint por endpoint. - Sin frameworks de frontend ni build tool: SPA en JS vanilla con un sistema de diseño propio basado en variables CSS (design tokens), suficiente para el alcance visual del proyecto.