Skip to content

Release: Promoción inicial a Producción (v1.0.0) - #78

Merged
Ochoa-Stack merged 351 commits into
mainfrom
develop
Sep 1, 2026
Merged

Release: Promoción inicial a Producción (v1.0.0)#78
Ochoa-Stack merged 351 commits into
mainfrom
develop

Conversation

@Ochoa-Stack

Copy link
Copy Markdown
Owner

develop

Descripción

Primera promoción oficial de la rama develop hacia main. Este Pull Request consolida el ciclo de desarrollo fundacional de la plataforma SkillStat, integrando un histórico acumulado de 77 PRs iterativos. Con esta fusión, se establece la primera versión estable de la plataforma orientada al entorno productivo.

Componentes Core Integrados

  • Autenticación Completa: Flujos de acceso mediante credenciales nativas y OAuth (Google), incluyendo vinculación de cuentas con confirmación explícita.
  • Módulos de Negocio: Dashboard analítico (Panorama) y motor orquestador de Alertas.
  • Administración y Resiliencia: Panel de control de administradores y sistema de respaldo/restauración de base de datos con reseteo de esquemas.
  • Hardening de Seguridad: Prevención de vulnerabilidades transaccionales, blindaje de cookies CSRF en dominios cross-origin (PSL) y control de retención de intenciones para sesiones de invitados.

Estado y Deuda Técnica

La deuda técnica conocida y los hallazgos operativos identificados durante las pruebas de integración han sido documentados formalmente en el archivo README.md (sección "Estado del Proyecto"). Estos elementos están acotados y se consideran no bloqueantes para esta promoción.

Revisión y Gobernanza

  • Proceso: Autorrevisión documentada.
  • Contexto Operativo: El equipo de desarrollo original fue disuelto tras la entrega de la fase académica del proyecto.
  • Responsabilidad de Merge: Esta integración se ejecuta bajo responsabilidad individual del autor, manteniendo el mismo rigor técnico, estructural y de checkpoint que se aplicó estrictamente durante el desarrollo colaborativo.

Tipo de cambio

  • feat
  • fix
  • refactor
  • chore
  • docs
  • release (Promoción a Producción)

Checklist de Promoción

  • El código consolidado proviene de develop actualizado y aprueba la suite de pruebas
  • Los componentes están preparados para operar bajo los límites de la infraestructura (Render/Neon)
  • La deuda técnica conocida está registrada y no compromete el arranque productivo
  • El repositorio cuenta con su documentación base (README) y registros arquitectónicos (ADRs) actualizados

Ochoa-Stack and others added 30 commits June 22, 2026 22:15
…tection, register autologin, logout endpoint, and consistent JWT error handling
feat(frontend): implementación del dashboard Panorama, flujo comparativo y navegación global
feat(auth): implementación de sesiones seguras (httpOnly/CSRF) y flujos de autenticación completos
… fix auth screen heading order and checkbox text wrapping
feat(auth): integración de Google Sign-In, soporte OAuth y correcciones UI en flujos de acceso
Co-authored-by: Oscar Soriano <neko.dev@outlook.com>
Co-authored-by: Aylin Chavira <aylinchavirachv@gmail.com>
Co-authored-by: Alejandro Balderrama <alejandro64.bp@gmail.com>
Co-authored-by: Alejandro Balderrama <alejandro64.bp@gmail.com>
Co-authored-by: Oscar Soriano <neko.dev@outlook.com>
Co-authored-by: Aylin Chavira <aylinchavirachv@gmail.com>
Co-authored-by: Alejandro Balderrama <alejandro64.bp@gmail.com>
Co-authored-by: Aylin Chavira <aylinchavirachv@gmail.com>
feat(auth): flujos de recuperación de contraseña y gestión estricta de sesiones
Co-authored-by: Alejandro Balderrama <alejandro64.bp@gmail.com>
Co-authored-by: Aylin Chavira <aylinchavirachv@gmail.com>
Co-authored-by: Aylin Chavira <aylinchavirachv@gmail.com>
…cleanup

feat(profile): gestión de habilidades de usuario, limpieza de roles y control de acceso
Co-authored-by: Alejandro Balderrama <alejandro64.bp@gmail.com>
Co-authored-by: Oscar Soriano <neko.dev@outlook.com>
Co-authored-by: Aylin Chavira <aylinchavirachv@gmail.com>
Ochoa-Stack and others added 29 commits July 31, 2026 17:12
Extrae la logica de get_compare a PanoramaService.get_compare_data,
usando los metodos batch de la ronda anterior. Reduce el patron N+1
de 15 queries a 3 para 5 habilidades, verificado con test de
caracterizacion. Comportamiento observable del endpoint sin cambios.

Co-authored-by: Oscar Soriano <neko.dev@outlook.com>
Co-authored-by: Aylin Chavira <aylinchavirachv@gmail.com>
Co-authored-by: Alejandro Balderrama <alejandro64.bp@gmail.com>
Habilita la extension unaccent de PostgreSQL (via wrapper immutable_unaccent
para permitir indexacion, dado que unaccent nativo es STABLE no IMMUTABLE)
y agrega CityRepository.find_by_normalized_name, verificado en 1 query
indexada. Prepara la base para DT-27: get_or_create_city y _normalize
siguen intactos, se reconectan en la siguiente ronda.

Co-authored-by: Oscar Soriano <neko.dev@outlook.com>
Co-authored-by: Aylin Chavira <aylinchavirachv@gmail.com>
Co-authored-by: Alejandro Balderrama <alejandro64.bp@gmail.com>
Resuelve DT-27. CityRepository queda reducido a persistencia pura
(find_by_normalized_name, get_by_name, create heredado). CityService
nuevo orquesta busqueda indexada + Nominatim + persistencia, capturando
ConflictError para condiciones de carrera bajo la constraint unique de
City.name. ingestion_service.py actualizado para consumir CityService.

11 tests fallan intencionalmente en este punto (esperado, corregidos en
la siguiente ronda): 6 en test_city_repository.py probaban el metodo
eliminado, 5 en test_ingestion_service.py mockeaban la ruta antigua.

Co-authored-by: Oscar Soriano <neko.dev@outlook.com>
Co-authored-by: Aylin Chavira <aylinchavirachv@gmail.com>
Co-authored-by: Alejandro Balderrama <alejandro64.bp@gmail.com>
test_city_repository.py reducido a persistencia pura (get_by_name).
test_city_service.py nuevo: migra los 6 tests de orquestacion con mocks
corregidos al namespace correcto, mas un test nuevo de recuperacion
ante ConflictError por condicion de carrera concurrente.
test_ingestion_service.py: 5 mocks corregidos de CityRepository a
CityService. Suite completa: 140 tests, cero regresiones.

Co-authored-by: Oscar Soriano <neko.dev@outlook.com>
Co-authored-by: Aylin Chavira <aylinchavirachv@gmail.com>
Co-authored-by: Alejandro Balderrama <alejandro64.bp@gmail.com>
…ints

Extrae get_skills, get_catalogs, get_summary, get_top_skills, get_trends,
get_geo y get_salaries a PanoramaService. panorama_bp.py ya no llama a
ningun repositorio directamente en ningun endpoint. SkillRepository.get_by_id
y TrendSnapshotRepository.get_top_skills permanecen sin modificar dado que
tienen consumidores externos (profile_bp.py, alerts_service.py,
profile_service.py), confirmado sin cambios via git status.

Cierra el alcance completo de esta rama: DT-26, DT-27, dos hallazgos sin
numerar (transaccion movida fuera de CityRepository, busqueda O(n)
reemplazada por indice unaccent). Suite completa: 140 tests, cero
regresiones acumuladas en las 7 rondas.

Co-authored-by: Oscar Soriano <neko.dev@outlook.com>
Co-authored-by: Aylin Chavira <aylinchavirachv@gmail.com>
Co-authored-by: Alejandro Balderrama <alejandro64.bp@gmail.com>
…-city-decoupling

refactor(panorama/cities): resolución N+1, indexación unaccent y eliminación de patrón Fat Controller
…tore --clean

DT-35: pg_restore --clean --if-exists solo emite DROPs para los objetos
presentes en el propio dump, lo que falla cuando la base de datos actual
tiene tablas con FK hacia objetos que el dump desconoce (confirmado al
agregar google_link_tokens con FK hacia users). Se reemplaza por un reset
explicito del schema public (DROP SCHEMA CASCADE + CREATE SCHEMA) antes de
pg_restore, eliminando la dependencia de que pg_restore infiera
correctamente el orden de eliminacion entre esquemas divergentes.

Las extensiones (unaccent) se recuperan automaticamente via el upgrade()
que ya se ejecuta despues del restore, verificado explicitamente con un
assert nuevo en test_backup_restore_schema_sync.py.

El GRANT del schema recreado se limita a CURRENT_USER, retirando la
concesion original a PUBLIC por principio de minimo privilegio (no hay
un segundo rol de Postgres que la necesite en la topologia actual).

Hallazgo adicional durante esta rama: el DDL en modo AUTOCOMMIT del reset
de schema genera deadlock si se ejecuta dentro de una transaccion de
prueba tipo savepoint. test_backup_service.py mockea puntualmente esa
conexion en el unico test que la disparaba, sin afectar la aserción real
que el test verifica (fallo de pg_restore -> AppError RESTORE_ERROR).

Co-authored-by: Oscar Soriano <neko.dev@outlook.com>
Co-authored-by: Aylin Chavira <aylinchavirachv@gmail.com>
Co-authored-by: Alejandro Balderrama <alejandro64.bp@gmail.com>
fix(backup): reseteo explícito de esquema previo a restauración y blindaje de privilegios
…unt linking confirmation

Cambio fusionado que cubre tres rondas de trabajo sobre el mismo conjunto
de archivos, declaradas explicitamente porque no admiten division limpia
por hunks sin riesgo de dejar un estado intermedio no verificado:

- Se extrae AuthService desde auth_bp.py, moviendo toda la logica de
  negocio de register, login, verificacion de correo y reset de password
  a la capa de servicio, alineando el archivo con el patron ya usado en
  ProfileService y PanoramaService.
- Se elimina el endpoint GET /me duplicado en auth_bp.py; /profile/me
  queda como unica fuente de verdad. navbar-role.js migrado en consecuencia.
- Se descompone google_login en AuthService.authenticate_with_google,
  separando verificacion de token, regla de email verificado y resolucion
  de cuenta. Se introduce GoogleLinkToken y el endpoint
  POST /google/confirm-link para exigir confirmacion explicita del usuario
  antes de vincular una cuenta de Google a una cuenta existente por
  coincidencia de email, en vez de vincular de forma silenciosa.
- AppError extendido con el atributo detail para transportar el
  link_token en la respuesta de vinculacion pendiente.

Co-authored-by: Oscar Soriano <neko.dev@outlook.com>
Co-authored-by: Aylin Chavira <aylinchavirachv@gmail.com>
Co-authored-by: Alejandro Balderrama <alejandro64.bp@gmail.com>
…orts

Ronda 4 de esta rama: SECRET_KEY y JWT_SECRET_KEY ahora tienen guard
condicionado a FLASK_ENV=production, siguiendo el mismo patron ya usado
para RESEND_API_KEY y PIPELINE_TRIGGER_SECRET. Antes de este cambio,
ambas claves podian arrancar la aplicacion en produccion usando su
fallback inseguro hardcodeado sin ninguna validacion.

Se eliminan ademas tres imports locales sin uso dentro de
authenticate_with_google(), residuo de una version anterior del metodo
antes de consolidar los imports de google.oauth2/google.auth a nivel de
modulo (hallazgo pendiente de la Ronda 3, corregido aqui).

Co-authored-by: Oscar Soriano <neko.dev@outlook.com>
Co-authored-by: Aylin Chavira <aylinchavirachv@gmail.com>
Co-authored-by: Alejandro Balderrama <alejandro64.bp@gmail.com>
…dation

refactor(auth): consolidación de AuthService, seguridad en vinculación OAuth y blindaje de entorno
Corrige flujo roto: handleGoogleCredential() redirigia ciegamente a
panorama.html en cualquier respuesta 200 de POST /auth/google, sin
inspeccionar el codigo de la respuesta. Cuando el backend responde con
ACCOUNT_LINK_PENDING (cuenta existente sin OAuthAccount vinculado), no
se emite cookie de sesion, causando que el usuario terminara en un
bucle de redireccion sin ningun mensaje explicativo.

Se agrega modal de confirmacion que muestra el email en cuestion y
requiere accion explicita del usuario antes de completar la
vinculacion via POST /auth/google/confirm-link. Cancelar no invoca
ningun endpoint; el token de 15 minutos expira solo si no se confirma
(opcion simple, decision de producto ya registrada en M2/DT-23).

Co-authored-by: Oscar Soriano <neko.dev@outlook.com>
Co-authored-by: Aylin Chavira <aylinchavirachv@gmail.com>
Co-authored-by: Alejandro Balderrama <alejandro64.bp@gmail.com>
…dd-skill

Cuando un usuario sin sesion activa intenta agregar una habilidad desde
el catalogo, handleAddSkill() ya redirigia correctamente a
register.html, pero lo hacia de inmediato y en silencio. Se agrega un
mensaje breve en el propio boton (Inicia sesion para guardar) con un
setTimeout de 1 segundo antes de la redireccion, dando contexto al
usuario de por que fue enviado a otra pantalla.

Co-authored-by: Aylin Chavira <aylinchavirachv@gmail.com>
…alog-fixes

feat(frontend): modal de confirmación para vinculación OAuth y mejoras de UX en catálogo
…ions

Problema:
El navegador bloquea la lectura de csrf_access_token a través de document.cookie
en producción porque backend y frontend operan bajo distintos subdominios de
onrender.com (dominio incluido en la Public Suffix List). Esto causaba que
client.js enviara un token nulo, provocando errores 401 UNAUTHORIZED en todas
las peticiones autenticadas que mutan estado (POST/PATCH/DELETE).

Solución:
- Backend: Se agrega el endpoint GET /csrf-token que expone bajo demanda el
  claim csrf contenido en el JWT actual.
- Frontend: Se reemplaza la lectura síncrona de cookies por ensureCsrfToken(),
  una función asíncrona que solicita el token a la API y lo cachea en
  sessionStorage para las peticiones subsecuentes.
- Navbar: Se agrega limpieza explícita de sessionStorage en handleLogout.

Este enfoque resuelve el problema de CORS/PSL sin necesidad de alterar los flujos
de inicio de sesión (Google o tradicional) existentes.

Co-authored-by: Oscar Soriano <neko.dev@outlook.com>
Co-authored-by: Aylin Chavira <aylinchavirachv@gmail.com>
Co-authored-by: Alejandro Balderrama <alejandro64.bp@gmail.com>
fix(auth): resolución de bloqueo cross-origin (PSL) para tokens CSRF mediante obtención asíncrona
El commit anterior (fix/csrf-cross-origin-cookie) introdujo un candado
circular: ensureCsrfToken() se ejecutaba incondicionalmente antes de
toda peticion via apiPost, incluyendo las peticiones de login mismas,
que por definicion ocurren sin sesion activa todavia. El endpoint
GET /auth/csrf-token respondia 401 sin sesion, y ese error se propagaba
como excepcion, bloqueando la peticion de login original antes de que
se ejecutara. Resultado: ningun flujo de autenticacion (login
tradicional, Google, confirmacion de vinculacion) funcionaba en
produccion.

Se corrige ensureCsrfToken() para que, ante un 401 por falta de sesion,
retorne null en vez de lanzar excepcion, dejando que la peticion
original continue sin el header CSRF — comportamiento correcto para
los endpoints de autenticacion, que nunca requieren CSRF por no tener
@jwt_required(). Las peticiones mutables ya autenticadas siguen
obteniendo el token correctamente en su primer intento posterior al
login, cuando la cookie de sesion ya existe.
hotfix(auth): resolución de interbloqueo en obtención de token CSRF durante flujos de inicio de sesión
… login

Co-authored-by: Oscar Soriano <neko.dev@outlook.com>
Co-authored-by: Aylin Chavira <aylinchavirachv@gmail.com>
Co-authored-by: Alejandro Balderrama <alejandro64.bp@gmail.com>
Co-authored-by: Oscar Soriano <neko.dev@outlook.com>
Co-authored-by: Aylin Chavira <aylinchavirachv@gmail.com>
Co-authored-by: Alejandro Balderrama <alejandro64.bp@gmail.com>
…odas las vistas

Co-authored-by: Oscar Soriano <neko.dev@outlook.com>
Co-authored-by: Aylin Chavira <aylinchavirachv@gmail.com>
Co-authored-by: Alejandro Balderrama <alejandro64.bp@gmail.com>
…stency

fix(frontend): consolidación de estado de invitados, retención de intenciones y refinamiento visual
…stency

style(ui): actualización de activos gráficos y favicons de la marca
chore(repo): documentación técnica, cumplimiento legal y reglas de formato (pre-main)
@Ochoa-Stack
Ochoa-Stack merged commit 918fc61 into main Sep 1, 2026
1 check passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant