Release: Promoción inicial a Producción (v1.0.0) - #78
Merged
Conversation
…Comparar nav link
…ctor, and results section
…version, and inline error handling
…Comparar nav link
…tection, register autologin, logout endpoint, and consistent JWT error handling
… and structured error parsing
… validation and terms acceptance
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
…le for social login
…nt linking by verified email
… fix auth screen heading order and checkbox text wrapping
…gle headings independently of forms
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>
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)
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
develop
Descripción
Primera promoción oficial de la rama
develophaciamain. 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
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
Tipo de cambio
Checklist de Promoción
developactualizado y aprueba la suite de pruebas