Skip to content

Folders and files

NameName
Last commit message
Last commit date

Latest commit

 

History

14 Commits
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 

Repository files navigation

OposApp — seguimiento del BOPA y tests con IA local

OposApp

Seguimiento automático del BOPA y tests de práctica con IA local — con privacidad total.

Flutter Spring Boot Java PostgreSQL Ollama License: MIT Portfolio


¿Qué es OposApp?

Aplicación móvil para opositores que resuelve dos problemas reales del opositor medio en Asturias: los 15-30 min diarios buscando convocatorias a mano y los 50-150 €/mes de las academias.

Automatiza el scraping del BOPA cada 24 h y usa un modelo de IA local (Ollama, familia Qwen) para generar tests personalizados en ~11 s — sin enviar ningún dato a servicios externos. Todo corre en infraestructura propia, cumpliendo el RGPD por diseño.

Un único APK para dos entornos. Al arrancar, la app detecta si el NAS es alcanzable en la red local (192.168.0.200:8083) y usa la IP directa; si no, cae automáticamente al dominio HTTPS externo. El mismo binario funciona dentro y fuera de la LAN sin recompilar.

Es mi Trabajo de Fin de Grado (DAM). Ficha completa en el portfolio.

Características

  • Convocatorias BOPA — scraping diario automatizado (n8n · 07:00), filtros y búsqueda full-text tolerante a erratas
  • Tests con IA local — generación asíncrona (solicitudId + polling) y corrección con explicación por pregunta
  • Dashboard de progreso — KPIs, gráficos fl_chart, racha e historial · modo offline con caché Hive
  • Seguridad y RGPD — JWT HS512, BCrypt, rate limiting · exportación y borrado de cuenta (Art. 17/20)
  • Panel de administración — usuarios, estado de servicios (PostgreSQL, Ollama) y registro de auditoría

Capturas

Convocatorias BOPA
BOPA
Progreso
Dashboard
Tests con IA
Tests

Arquitectura

graph TD
    subgraph NAS["NAS Synology DS224+"]
        N8N["n8n<br/>Scraping BOPA · 07:00"]
        PG["PostgreSQL 15<br/>schema tfg"]
        OL["Ollama<br/>Qwen (local)"]
        SB["Spring Boot API<br/>JWT · BCrypt · Bucket4j"]
        CD["Caddy<br/>HTTPS reverse proxy"]

        N8N -->|convocatorias| PG
        OL -->|preguntas JSON| SB
        PG <-->|JPA / Hibernate| SB
        SB --> CD
    end

    FL["Flutter App<br/>Android 14+ · Dio · GoRouter · Hive"]

    FL -->|LAN: IP directa| SB
    CD <-->|HTTPS + JWT| FL
Loading

Stack tecnológico

Frontend Flutter 3.24 · Dio + Retrofit · GoRouter · provider · Hive (offline) · fl_chart · Material 3 (#FF6B00) Backend Spring Boot 3 · Java 21 · PostgreSQL 15 · Spring Security (JWT HS512 + BCrypt) · Bucket4j · OpenAPI Automatización e IA n8n (scraping) · Ollama (Qwen2.5-coder free / Qwen3 premium) · Caddy (HTTPS)

Ver detalle del stack

Backend

Componente Detalle
Spring Boot 3.x API RESTful stateless, arquitectura en capas estricta (Controller → Service → Repository → Model)
Java 21 Compilación y ejecución, gestionado con Gradle
PostgreSQL 15 Base de datos relacional, schema tfg, ddl-auto=none para migraciones manuales
Spring Security + JWT HS512 Autenticación stateless, tokens de 7 días con claims usuarioId, email y rol
BCryptPasswordEncoder Hashing de contraseñas con factor de coste 12
Bucket4j Rate limiting por Token Bucket — 30 req/min por IP, responde HTTP 429 si se supera
SpringDoc OpenAPI Swagger UI autogenerado en /swagger-ui.html
JavaMailSender + Thymeleaf Emails transaccionales (confirmación de registro, recuperación de contraseña)

Frontend

Componente Detalle
Flutter 3.24 Android 14.0+, un único código base (compila también para web)
Dio + Retrofit Cliente HTTP con interceptor JWT global y cliente tipado
GoRouter Enrutado declarativo con redirecciones según el estado de autenticación
provider Gestión de estado reactivo siguiendo el patrón ViewModel
flutter_secure_storage Almacenamiento seguro del token en el Keystore nativo del dispositivo
Hive Caché local clave-valor para modo offline (últimas convocatorias)
connectivity_plus / network_info_plus Detección de red y de IP local — base de la autodetección LAN/HTTPS
fl_chart + percent_indicator Gráficos de progreso (líneas, barras, anillos) con acento naranja
confetti / shimmer Animación de resultados y microinteracciones de carga
Material Design 3 useMaterial3: true, color semilla #FF6B00, tema claro

Automatización e IA

Componente Detalle
n8n Workflow de scraping del BOPA, se dispara a las 07:00 AM (CET) todos los días
Ollama (/api/generate) Generación de tests en local, sin API de pago ni salida de datos. Modelos configurables: qwen2.5-coder (free) y un modelo mayor / qwen3 (premium), con detección automática de familia
Caddy Server Reverse proxy con certificados HTTPS automáticos (Let's Encrypt)

Configuración de la IA (Ollama)

La generación de tests corre sobre Ollama (servidor de IA local). El backend lo llama en POST /api/generate (stream=false, num_predict=3500) y detecta la familia del modelo por su nombre para ajustar los parámetros automáticamente:

  • qwen2.5-coder (plan gratuito) — temperature 0.3
  • qwen3 (plan premium) — temperature 0.2, think=false (modelos thinking)

La URL y los modelos se configuran por variables de entorno (.env), sin tocar código:

OLLAMA_URL=http://<IP_OLLAMA>:11434
OLLAMA_MODEL_FREE=qwen2.5-coder:7b-instruct
OLLAMA_MODEL_PREMIUM=qwen2.5-coder:14b-instruct-q3_K_M   # o un modelo qwen3 si lo prefieres

Puesta en marcha de Ollama (una sola vez, en el host de IA):

curl -fsSL https://ollama.com/install.sh | sh          # instalar Ollama (Linux)
ollama pull qwen2.5-coder:7b-instruct                  # modelo del plan gratuito
ollama pull qwen2.5-coder:14b-instruct-q3_K_M          # modelo premium (o tu qwen3)
curl http://<IP_OLLAMA>:11434/api/tags                 # comprobar que responde

Ollama corre como servicio aparte (no está en el docker-compose.yml, que solo levanta el backend). El backend expone su estado en GET /admin/ollama/status, que por dentro consulta /api/tags.

Seguridad

  • JWT HS512 stateless (7 días), validado en cada petición por JwtAuthenticationFilter
  • BCrypt (coste 12) — contraseñas nunca en texto plano
  • Rate limiting (Bucket4j) — 30 req/min por IP, HTTP 429 si se supera
  • Bean Validation en todos los DTOs · JPA/Hibernate (imposible inyección SQL)
  • Panel de administración restringido a red local del NAS (LAN o WireGuard) + rol ROLE_ADMIN
  • RGPD — consentimiento opt-in con timestamp, borrado (DELETE /api/user/delete) y exportación (GET /api/user/export)

Métricas de rendimiento (medidas en pruebas)

Métrica Objetivo Resultado
Listado de convocatorias (200 registros) < 500 ms 187 ms
Generación de test con IA (10 preguntas) < 15 s 11,3 s
Carga de pantalla inicial (4G) < 3 s 1,8 s
Login con verificación BCrypt < 500 ms 312 ms
Estructura del proyecto
OposApp/
├── api-backend/                      # Spring Boot API (Java 21 + Gradle)
│   └── src/main/java/es/ivanesco/oposapp/api/
│       ├── controllers/              # Un controlador por dominio (Auth, Test, Bopa, Admin, Ads...)
│       ├── services/                 # Lógica de negocio (TestService, OllamaService, AuthService...)
│       ├── repositories/             # JpaRepository por entidad
│       ├── models/                   # Entidades JPA (@Table(schema="tfg"))
│       ├── dtos/                     # Request / Response DTOs desacoplados
│       ├── config/                   # SecurityConfig, RateLimitFilter, JwtAuthenticationFilter
│       └── exceptions/               # GlobalExceptionHandler con @RestControllerAdvice
│
├── oposapp/                          # App Flutter (Android 14+)
│   └── lib/
│       ├── main.dart                 # MaterialApp.router + init (red, Hive) antes de runApp
│       ├── core/                     # constants (URLs/endpoints), routing (GoRouter), theme, errors
│       ├── screens/                  # splash, login, home, bopa, generate, tests, test, resultado, progreso, perfil, admin
│       ├── services/                 # api_service (Dio+JWT), auth_service, admin_service, network_service
│       ├── models/                   # usuario, convocatoria, pregunta, estadisticas, audit_log...
│       ├── widgets/                  # ad_banner_widget, app_toast, reporte_dialog
│       └── cache/                    # hive_cache.dart (modo offline)
│
├── base_datos/                       # Scripts SQL / schema tfg
├── assets/                           # Capturas y recursos
└── docker-compose.yml                # PostgreSQL 15 + n8n + Caddy
Cómo ejecutar el proyecto

Requisitos: Docker + Compose · Java 21 + Gradle · Flutter SDK 3.24 · Ollama con qwen2.5-coder:7b-instruct (y el modelo premium). Ver Configuración de la IA.

# Backend
docker-compose up -d              # PostgreSQL + n8n + Caddy
cd api-backend && ./gradlew bootRun   # API en el puerto 8081
# Swagger: http://localhost:8081/swagger-ui.html

# Frontend
cd oposapp
flutter pub get
flutter run                       # emulador o dispositivo físico

La URL del backend se resuelve en runtime en ApiService.initialize(): LAN → 192.168.0.200:8083, fuera de LAN → dominio HTTPS externo. Para desarrollo en el propio PC usa baseUrlLocal (http://localhost:8081/api) en api_constants.dart.

Solución de problemas comunes
Error Causa probable Solución
App no recibe token tras login Interceptor Dio no adjunta Authorization: Bearer Revisar ApiService y los logs del backend
App apunta al entorno equivocado El NAS no responde en 192.168.0.200:8083 (autodetección LAN/HTTPS) Verificar que el NAS es alcanzable en la LAN
Timeout en generación de tests Ollama apagado o saturado curl http://<IP_OLLAMA>:11434/api/tags desde el NAS
Error Hibernate 6 al arrancar Entidades sin @Table(schema="tfg") o scale/precision en campos no BigDecimal Eliminar scale/precision de esos campos
Scraping del BOPA sin resultados Fallo en algún nodo del workflow n8n Consultar la tabla de historial de scraping

Roadmap

  • Expansión del scraping al BOE y otras Comunidades Autónomas
  • Fine-tuning del modelo Qwen con datos reales de uso
  • Simulacros cronometrados y modo competitivo
  • Notificaciones push (FCM) por categoría
  • Expansión a iOS y web
  • Modelo B2B: licencias white-label para academias

Autor

Iván Jonás Fernández Correa — TFG · Desarrollo de Aplicaciones Multiplataforma (DAM) · 2025-2026 Tutores: Delio Tolivia Cadrecha (PIDAM) · Mario Álvarez Fernández (PDAW)

Portfolio · LinkedIn

Licencia

Distribuido bajo licencia MIT.

About

OposApp es una aplicación móvil para Android, desarrollada con Flutter, que automatiza el seguimiento de convocatorias del Boletín Oficial del Principado de Asturias (BOPA) y genera tests de práctica personalizados mediante inteligencia artificial local. Todo ello garantizando la privacidad total de los datos.

Resources

Stars

Watchers

Forks

Releases

Packages

Contributors

Languages