Trabajo Final de Máster - Máster en Desarrollo con Inteligencia Artificial Autor: David López Sánchez Institución: BIG School Fecha: Febrero 2026
- Descripción General
- Stack Tecnológico
- Instalación y Ejecución
- Estructura del Proyecto
- Funcionalidades Principales
- Cómo interpretar el análisis de Verity AI
- Arquitectura
- Testing
- Deployment
- DevOps & Infrastructure
- Documentación Técnica
Esta seccion resume los comandos y comportamientos vigentes del feature de analisis antes de merge a main.
- Iniciar servicios base:
docker compose up -d- PostgreSQL corre en
localhost:5433con imagenpgvector/pgvector:pg16. chromadbsigue definido endocker-compose.ymlpor compatibilidad legacy, pero el backend activo usapgvector(PgVectorClient).
- Preparar backend:
cd backend
npm install
npx prisma migrate deploy
npm run typecheck
npm run dev- Preparar frontend:
cd frontend
npm install
npx tsc --noEmit
npm run dev- URLs locales:
- Frontend:
http://localhost:3001 - Backend:
http://localhost:3000
Comandos vigentes en esta rama:
# Backend (tipado, tests, cobertura)
cd backend
npm run typecheck
npx vitest run
npm run test:coverage
# Frontend (tipado, unit, e2e)
cd frontend
npx tsc --noEmit
npm run test:run
npm run test:e2e:smoke
npm run test:e2eReferencias:
- Estandar QA:
docs/CALIDAD.md - E2E:
frontend/tests/e2e/README.md - Pre-merge Render/Prisma (incidentes reales):
docs/incidents/PRE_MERGE_PLAYBOOK_RENDER_PRISMA.md
- Si un articulo queda en
accessStatus=PAYWALLED|RESTRICTEDoanalysisBlocked=true, el backend bloquea SIEMPRE el analisis (standard y deep), incluso si existe cache legacy. - El bloqueo responde
422conerror.code="PAYWALL_BLOCKED". - Si no hay texto completo y solo hay snippet, el analisis se marca como estimado con menor confianza.
- Verity no intenta bypass de paywall.
- Causa: articulo detectado como de suscripcion/restringido.
- Resultado esperado: no se ejecuta analisis y la UI muestra mensaje de bloqueo.
- Causa: salida LLM no parseable incluso tras 1 intento de JSON repair.
- Resultado esperado:
formatError=true, UI muestra estado de error y oculta secciones deep.
- Revisar credenciales locales (sin exponerlas), conectividad y limites del proveedor.
- Reintentar cuando el servicio externo se estabilice.
- Verificar que el contenedor usa
pgvector/pgvector:pg16. - Reaplicar migraciones:
cd backend
npx prisma migrate deploy- Si aparece
data-darkreader-inline-stroke, desactivar Dark Reader enlocalhost.
En la era de la desinformación digital, donde las fake news y los sesgos informativos proliferan en redes sociales y medios digitales, surge la necesidad de herramientas que ayuden a los ciudadanos a evaluar críticamente la información que consumen. Verity News nace como respuesta a este problema, aplicando técnicas de Inteligencia Artificial para democratizar el análisis de credibilidad periodística.
Verity News es una plataforma web full-stack que combina ingesta automatizada de noticias españolas, análisis de credibilidad mediante Large Language Models (LLMs), y búsqueda semántica avanzada para proporcionar a los usuarios una herramienta inteligente de verificación periodística.
Este proyecto demuestra la aplicación práctica de los conocimientos adquiridos en el Máster en Desarrollo con IA:
- Integración de IA en producción: Uso de modelos de lenguaje (Gemini 2.0 Flash) para análisis de sesgo político, detección de clickbait y evaluación de confiabilidad
- Arquitectura enterprise: Implementación de Clean Architecture (Hexagonal) con SOLID, TDD y patrones de diseño avanzados
- RAG (Retrieval-Augmented Generation): Sistema de chat conversacional con recuperación de contexto mediante búsqueda vectorial
- Ingeniería de prompts: Optimización de prompts para análisis explicable (XAI) con citaciones y razonamiento interno
- Desarrollo asistido por IA: Documentación completa del uso de GitHub Copilot y Claude durante el desarrollo
- Análisis explicable (XAI): Cada evaluación incluye razonamiento interno visible para el usuario
- Múltiples perspectivas: Agregación de 8+ fuentes españolas (El País, El Mundo, 20 Minutos, Europa Press, etc.)
- Privacidad first: Análisis por usuario con sistema de favorites y análisis desbloqueados
- Búsqueda inteligente: Motor de búsqueda semántico basado en embeddings vectoriales
- Freemium sostenible: Modelo de negocio con cuotas gratuitas y suscripción premium
| Tecnología | Versión | Propósito |
|---|---|---|
| Next.js | 16.x | Framework React con SSR, App Router y optimizaciones Turbopack |
| React | 19.x | Biblioteca UI con Server Components y Concurrent Features |
| TypeScript | 5.x | Type safety en toda la aplicación frontend |
| Tailwind CSS | 3.x | Utility-first CSS framework para diseño responsivo |
| shadcn/ui | Latest | Biblioteca de componentes accesibles (Radix UI + Tailwind) |
| React Query | 5.x | Server state management con cache inteligente y sincronización |
| React Hook Form | Latest | Gestión de formularios performante con validación |
| Zod | Latest | Schema validation en runtime para formularios y API responses |
| Vitest | Latest | Test runner ultrarrápido compatible con Vite |
| React Testing Library | Latest | Testing de componentes siguiendo buenas prácticas |
| Tecnología | Versión | Propósito |
|---|---|---|
| Node.js | 20 LTS | Runtime JavaScript con soporte ESM y performance optimizada |
| Express | 5.x | Framework web minimalista para API REST |
| TypeScript | 5.x | Type safety en modo strict para prevención de bugs |
| Prisma | Latest | ORM type-safe con migrations, schema validation y optimización de queries |
| PostgreSQL | 17 | Base de datos relacional con pgvector extension |
| pgvector | Latest | Extensión PostgreSQL para búsqueda vectorial (embeddings) |
| Pino | Latest | Logger estructurado de alto rendimiento |
| Helmet | Latest | Middleware de seguridad para Express (CSP, XSS, etc.) |
| Zod | Latest | Schema validation para request/response |
| Vitest | Latest | Test runner con soporte para TDD y mocking |
| Servicio/Biblioteca | Propósito |
|---|---|
| Google Gemini 2.0 Flash | LLM principal para análisis de sesgo, categorización y chat |
| Gemini Embeddings | Generación de embeddings de 768 dimensiones para búsqueda semántica |
| pgvector | Almacenamiento y búsqueda de vectores con índice HNSW |
| RAG (Retrieval-Augmented Generation) | Sistema de chat con contexto recuperado de base vectorial |
| Prompt Engineering | Prompts optimizados para XAI, zero hallucination y evidence-based scoring |
| Tecnología | Propósito |
|---|---|
| Docker | Containerización de PostgreSQL y servicios auxiliares |
| Docker Compose | Orquestación de servicios en desarrollo |
| GitHub Actions | CI/CD para tests automáticos y quality checks |
| Vercel | Hosting del frontend con CDN global y edge functions |
| Render | Hosting del backend con auto-deploys desde GitHub |
| Neon Serverless | PostgreSQL gestionado con pgvector pre-instalado |
| Firebase Auth | Autenticación de usuarios con JWT y Admin SDK |
| Sentry | Observabilidad, error tracking y performance monitoring |
| ESLint + Prettier | Linting y formateo de código automático |
| Husky | Git hooks para quality checks en pre-commit |
- RSS Parser: Ingesta directa de feeds RSS (El País, El Mundo, 20 Minutos, Europa Press)
- Google News RSS: Fallback para categorías vacías y noticias locales geolocalizadas
- Jina Reader API: Extracción de metadatos Open Graph (og:image, og:description)
Antes de comenzar, asegúrate de tener instalado:
- Node.js 20+ (LTS recomendado) - Descargar
- npm 9+ o pnpm 8+
- Docker y Docker Compose - Descargar Docker Desktop
- Git para clonar el repositorio
-
Firebase (Autenticación):
- Crear proyecto en Firebase Console
- Habilitar "Authentication" → "Email/Password"
- Descargar
serviceAccountKey.jsondesde "Project Settings" → "Service Accounts"
-
Google AI Studio (Gemini API):
- Obtener API key en Google AI Studio
- Plan gratuito: 15 requests/minuto, 1M tokens/día
-
Jina AI (Extracción de metadatos - Opcional):
- Registrarse en Jina AI
- Obtener API key gratuita
git clone https://github.com/David-LS-Bilbao/PROYECTO-MASTER-IA.git
cd PROYECTO-MASTER-IA/Verity-Newscd backend
cp .env.example .envEdita backend/.env con tus credenciales:
# Server
PORT=3000
NODE_ENV=development
# CORS (separar múltiples orígenes con comas)
CORS_ORIGIN=http://localhost:5173,http://localhost:3001
# Cron + Promo Codes
CRON_SECRET=tu_secreto_seguro_aqui
PROMO_CODES=VERITY_ADMIN,TEST_CODE
# Database (PostgreSQL con pgvector)
DATABASE_URL=postgresql://verity:verity_password_dev@localhost:5432/verity_news
# Gemini API
GEMINI_API_KEY=tu_api_key_de_gemini
# Jina API (Opcional)
JINA_API_KEY=tu_api_key_de_jina
# Firebase Admin SDK
FIREBASE_PROJECT_ID=tu-project-id
FIREBASE_CLIENT_EMAIL=firebase-adminsdk-xxxxx@tu-project.iam.gserviceaccount.com
FIREBASE_PRIVATE_KEY="-----BEGIN PRIVATE KEY-----\nTU_PRIVATE_KEY_AQUI\n-----END PRIVATE KEY-----\n"
# Sentry (Opcional - para observabilidad)
SENTRY_DSN=https://tu-sentry-dsn@sentry.io/proyecto
SENTRY_ENVIRONMENT=development
SENTRY_TRACES_SAMPLE_RATE=1.0cd ../frontend
cp .env.local.example .env.localEdita frontend/.env.local:
# API Backend
NEXT_PUBLIC_API_URL=http://localhost:3000
# Cron Secret (igual que backend)
NEXT_PUBLIC_CRON_SECRET=tu_secreto_seguro_aqui
# Firebase Client SDK
NEXT_PUBLIC_FIREBASE_API_KEY=tu_firebase_api_key
NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN=tu-project.firebaseapp.com
NEXT_PUBLIC_FIREBASE_PROJECT_ID=tu-project-id
NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET=tu-project.appspot.com
NEXT_PUBLIC_FIREBASE_MESSAGING_SENDER_ID=123456789
NEXT_PUBLIC_FIREBASE_APP_ID=1:123456789:web:abc123
# Google AdSense (Opcional)
NEXT_PUBLIC_ENABLE_ADSENSE=false
NEXT_PUBLIC_ADSENSE_CLIENT_ID=ca-pub-xxxxxxxxxxxxxxxx# Desde la raiz del proyecto Verity-News/
docker compose up -dEsto iniciara:
- PostgreSQL + pgvector en
localhost:5433(contenedorverity-news-postgres) - Redis en
localhost:6379 - ChromaDB como servicio legacy opcional (no requerido para el flujo principal con
pgvector)
Verifica que los servicios esten corriendo:
docker ps
# Deberias ver: verity-news-postgres (pgvector/pgvector:pg16)# Backend
cd backend
npm install
# Frontend
cd ../frontend
npm installcd backend
# Ejecutar migraciones
npx prisma migrate deploy
# Generar Prisma Client
npx prisma generateAbre dos terminales:
Terminal 1 - Backend:
cd backend
npm run devVerás:
✅ PrismaClient inicializado
✅ pgvector extension initialized
✅ Database connected
🚀 Verity News API running on http://localhost:3000
📋 Health check: http://localhost:3000/api/health/check
Terminal 2 - Frontend:
cd frontend
npm run devVerás:
▲ Next.js 16.0.0
- Local: http://localhost:3001
- Network: http://192.168.1.x:3001
✓ Ready in 2.5s
Abre tu navegador en:
http://localhost:3001
Backend Health Check:
# Liveness probe
curl http://localhost:3000/api/health/check
# Readiness probe (verifica DB)
curl http://localhost:3000/api/health/readinessBase de Datos:
cd backend
npx prisma studio
# Abre interfaz visual en http://localhost:5555# Verifica que Docker este corriendo
docker ps
# Levanta/rehidrata servicios sin borrar volumenes
docker compose up -d
# Verifica contenedor y puerto mapeado
docker ps | findstr verity-news-postgresAsegúrate de:
- Haber descargado
serviceAccountKey.jsonde Firebase Console - Copiar el archivo a
backend/ - O configurar las variables de entorno
FIREBASE_*correctamente
Verifica que:
- La API key sea válida en Google AI Studio
- No haya espacios o saltos de línea en el
.env
El proyecto sigue una arquitectura monorepo con separación clara entre frontend, backend y documentación:
Verity-News/
├── frontend/ # Aplicación Next.js (SSR + CSR)
│ ├── app/ # Next.js App Router (pages)
│ │ ├── (auth)/
│ │ │ └── login/ # Página de autenticación
│ │ ├── news/
│ │ │ └── [id]/ # Detalle de noticia (dynamic route)
│ │ ├── search/ # Búsqueda semántica
│ │ ├── profile/ # Perfil de usuario
│ │ ├── legal/ # Páginas legales (privacidad, términos)
│ │ ├── layout.tsx # Root layout con providers
│ │ └── page.tsx # Dashboard principal (/)
│ ├── components/ # Componentes React reutilizables
│ │ ├── ui/ # Componentes shadcn/ui (30+)
│ │ ├── layout/ # Header, Sidebar, Footer
│ │ ├── dashboard/ # NewsCard, CategoryFilter, etc.
│ │ └── profile/ # ProfileForm, SubscriptionCard, etc.
│ ├── hooks/ # Custom React Hooks (12 hooks)
│ │ ├── useNews.ts # Fetch noticias con React Query
│ │ ├── useAuth.ts # Firebase auth state
│ │ └── useProfile.ts # User profile management
│ ├── lib/ # Utilidades y configuración
│ │ ├── api.ts # Axios client con interceptors
│ │ ├── firebase.ts # Firebase SDK config
│ │ └── utils.ts # Helper functions
│ ├── context/ # React Context providers
│ │ └── AuthContext.tsx # Auth state global
│ ├── tests/ # Tests Vitest + RTL (122 tests)
│ │ ├── components/ # Tests de componentes
│ │ ├── hooks/ # Tests de hooks
│ │ └── utils/ # Tests de utilidades
│ └── package.json # Dependencias frontend
│
├── backend/ # API REST Node.js + Express
│ ├── src/
│ │ ├── domain/ # Capa de Dominio (Clean Architecture)
│ │ │ ├── entities/ # Entidades de dominio
│ │ │ │ ├── NewsArticle.ts # Entidad NewsArticle con lógica
│ │ │ │ ├── User.ts # Entidad User
│ │ │ │ └── Analysis.ts # Value Object para análisis
│ │ │ ├── repositories/ # Interfaces de repositorios (puertos)
│ │ │ │ ├── news-article.repository.ts
│ │ │ │ └── user.repository.ts
│ │ │ └── services/ # Servicios de dominio (interfaces)
│ │ │ ├── gemini-client.interface.ts
│ │ │ └── vector-client.interface.ts
│ │ ├── application/ # Capa de Aplicación (casos de uso)
│ │ │ └── use-cases/
│ │ │ ├── ingest-news.usecase.ts
│ │ │ ├── analyze-article.usecase.ts
│ │ │ ├── search-news.usecase.ts
│ │ │ ├── chat-article.usecase.ts
│ │ │ └── chat-general.usecase.ts
│ │ ├── infrastructure/ # Capa de Infraestructura (adaptadores)
│ │ │ ├── http/ # Controllers + Routes + Middlewares
│ │ │ │ ├── controllers/ # NewsController, ChatController, etc.
│ │ │ │ ├── routes/ # Express routers
│ │ │ │ └── middlewares/ # authenticate, errorHandler, etc.
│ │ │ ├── persistence/ # Implementaciones de repositorios
│ │ │ │ └── prisma-news-article.repository.ts
│ │ │ ├── external/ # Clientes externos
│ │ │ │ ├── gemini.client.ts
│ │ │ │ ├── pgvector.client.ts
│ │ │ │ ├── direct-spanish-rss.client.ts
│ │ │ │ ├── google-news-rss.client.ts
│ │ │ │ └── prompts/ # Prompts optimizados para Gemini
│ │ │ ├── config/ # Configuración e inyección de dependencias
│ │ │ │ └── dependencies.ts # DI Container singleton
│ │ │ ├── logger/ # Pino logger estructurado
│ │ │ └── monitoring/ # Health probes, metrics
│ │ └── index.ts # Entry point de la aplicación
│ ├── prisma/
│ │ ├── schema.prisma # Esquema de BD (7 modelos)
│ │ └── migrations/ # Migraciones SQL versionadas
│ ├── tests/ # Tests Vitest (206 tests)
│ │ ├── use-cases/ # Tests de casos de uso (TDD)
│ │ ├── controllers/ # Tests de API REST
│ │ ├── repositories/ # Tests de persistencia
│ │ └── external/ # Tests de servicios externos
│ ├── scripts/ # Scripts de utilidad
│ │ ├── verify-analysis-rules.ts
│ │ └── test-search-endpoint.ts
│ └── package.json # Dependencias backend
│
├── docs/ # Documentación técnica del proyecto
│ ├── MemoriaTFM.md # Memoria académica del TFM
│ ├── ESTRUCTURA_PROYECTO.md # Mapa completo del proyecto
│ ├── CALIDAD.md # Estándares de calidad y coverage
│ ├── diagrams/ # Diagramas arquitecturales
│ │ ├── architecture_hexagonal.md
│ │ ├── database_er.md
│ │ └── sequence_analysis.md
│ ├── architecture/ # Diseño técnico e integraciones core
│ ├── incidents/ # Incidencias, fixes y validaciones
│ ├── runbooks/ # Guías operativas/manuales
│ ├── audits/ # Auditorías técnicas y de seguridad
│ ├── archive/ # Backups e histórico
│ └── sprints/ # Documentación de sprints (27+)
│ ├── Sprint-27.3-Production-Responsive-Hotfixes.md
│ ├── Sprint-27-ENTREGABLES.md
│ └── [20+ documentos de sprints]
│
├── tests/
│ └── performance/ # Tests de carga con k6
│ ├── stress-test.js # 100 requests concurrentes
│ └── latency-test.js # Medición de p95, p99
│
├── docker-compose.yml # Orquestación de PostgreSQL
├── docs/ESTADO_PROYECTO.md # Estado actual y progreso (Sprint 27.3)
├── docs/PROJECT_CONTEXT.md # Contexto para GitHub Copilot
├── docs/AI_RULES.md # Reglas de desarrollo asistido por IA
└── README.md # Este archivo
El backend implementa arquitectura hexagonal (ports & adapters) con 3 capas:
-
Domain (Núcleo):
- Entities:
NewsArticle,User,Analysis - Repository Interfaces (puertos)
- Service Interfaces (puertos)
- Regla: Sin dependencias externas, solo lógica de negocio pura
- Entities:
-
Application (Casos de Uso):
- Use Cases: Orquestación de lógica de negocio
- Regla: Depende solo de Domain, no de Infrastructure
-
Infrastructure (Adaptadores):
- Controllers: Express HTTP handlers
- Repositories: Implementaciones con Prisma
- External Services: Gemini, RSS, pgvector
- Regla: Implementa interfaces de Domain
- Dependency Injection: Container singleton en
dependencies.ts - Repository Pattern: Abstracción de persistencia
- Factory Pattern: Reconstitución de entidades con
NewsArticle.reconstitute() - Strategy Pattern: Múltiples clientes RSS intercambiables
- Adapter Pattern: GeminiClient adapta API de Google a interfaz interna
Descripción: Sistema de ingesta multi-fuente que recopila noticias españolas desde 58+ medios verificados con actualización automática y manual.
Características:
- RSS Parser Directo: Parseo de feeds RSS nativos (más rápido y confiable)
- Fuentes: El País, El Mundo, 20 Minutos, Europa Press, Xataka, etc.
- 8 Categorías Unificadas: España, Internacional, Local, Economía, Ciencia-Tecnología, Entretenimiento, Deportes, Salud
- Smart TTL: Chequeo de 1 hora antes de re-ingestar para ahorrar cuota de API
- Auto-fill Inteligente: Detecta categorías vacías y dispara ingesta automática
- Geolocalización: Noticias locales basadas en
User.location(Sprint 20) - Fallback a Google News: Si RSS directos fallan, usa Google News RSS como backup
- 🆕 Sprint 35 - Auto-Refresh System:
- Endpoint Público:
/api/ingest/triggersin necesidad de CRON_SECRET (rate-limited 1 req/5min) - Botón Manual: Refresh button en header y sidebar para actualización manual
- Auto-Trigger: Middleware que dispara ingesta automática tras 1h de inactividad
- Session Detection: Hook que detecta entrada/reanudación de sesión y actualiza contenido
- Fire-and-Forget: Patrón no-bloqueante para no afectar rendimiento de requests
- Endpoint Público:
Tecnología:
- RSS Parser para parseo de XML
- Jina Reader API para extracción de metadatos Open Graph (og:image, og:description)
- Exponential backoff para retry en fallos de red
- Express Rate Limiter para protección contra abuso (3 capas: endpoint, middleware, cliente)
- IngestMetadata tracker para control de TTL global
Beneficio: Los usuarios siempre ven noticias frescas sin necesidad de refrescar manualmente, con opción de actualización manual instantánea.
Descripción: Cada noticia es analizada por Gemini 2.0 Flash para evaluar sesgo político, confiabilidad y detectar clickbait.
Métricas Generadas:
| Métrica | Escala | Descripción |
|---|---|---|
| Reliability Score | 0-100 | Confiabilidad basada en citas a fuentes verificables |
| Bias Score | -100 a +100 | Sesgo político (-100: izquierda, 0: neutral, +100: derecha) |
| Clickbait Detection | Boolean | ¿El titular es sensacionalista? |
| Summary | String | Resumen objetivo de 2-3 frases |
| Internal Reasoning | String | Razonamiento interno del LLM (XAI) |
Prompt Engineering (Sprint 25 - Evidence-Based Scoring):
// Reglas estrictas para ReliabilityScore:
// < 40: Clickbait, opinión sin datos, lenguaje incendiario
// 40-60: Noticia estándar sin citas externas claras
// 60-80: Fuentes genéricas ('según expertos')
// > 80: SOLO con citas directas a organismos oficiales, estudios científicos o enlaces verificablesXAI (Explainable AI):
- Cada análisis incluye
internalReasoningvisible para el usuario - 3 preguntas obligatorias: fuentes verificables, lenguaje emocional, datos fácticos
- Citaciones forzadas en formato
[1][2]para trazabilidad
Beneficio: Los usuarios pueden tomar decisiones informadas sobre la credibilidad de cada noticia antes de compartirla.
Descripción: Motor de búsqueda que entiende el significado de las consultas, no solo palabras clave.
Arquitectura de Búsqueda (Waterfall - 3 Niveles):
LEVEL 1: Full-Text Search (PostgreSQL nativo)
↓ (si 0 resultados)
LEVEL 2: Semantic Search (pgvector + Gemini Embeddings)
↓ (si 0 resultados)
LEVEL 3: Reactive Ingestion (trigger ingesta y re-query)
Tecnología:
- pgvector: Extensión PostgreSQL para almacenar vectores (768 dimensiones)
- Gemini Embeddings: Generación de embeddings semánticos
- Índice HNSW: Búsqueda de vecinos más cercanos con cosine distance
- Raw SQL: Queries optimizadas con operador
<=>para similaridad
Ejemplo:
- Query: "fraude electoral"
- Resultados: Encuentra noticias sobre "manipulación de votos", "irregularidades en urnas", etc.
Beneficio: Búsquedas más inteligentes que encuentran noticias relevantes aunque no contengan exactamente las palabras buscadas.
Descripción: Sistema de chat inteligente con dos modos:
Propósito: Hacer preguntas específicas sobre UN artículo concreto.
Funcionamiento:
- Usuario abre noticia y hace pregunta ("¿Qué dice sobre el presupuesto?")
- Sistema recupera fragmentos relevantes del artículo desde pgvector
- Gemini responde SOLO basándose en el contenido del artículo
- Respuesta incluye citaciones
[1][2]para trazabilidad
Prompt Strategy: Zero Hallucination
- Prohibición estricta de usar conocimiento general
- Cada frase debe estar citada o eliminarse
- Respuesta por defecto: "El contexto no contiene datos suficientes..."
Propósito: Preguntas abiertas de conocimiento general.
Funcionamiento:
- Acceso completo al conocimiento de Gemini
- NO usa RAG ni búsqueda vectorial (más eficiente)
- Respuestas de hasta 200 palabras en español
- Estilo conversacional y profesional
Ejemplo:
- Query: "¿Quién es el alcalde de Móstoles?"
- Respuesta: Información actualizada del LLM sin restricciones
🔒 Restricción PREMIUM (Sprint 30):
- Chat endpoints requieren autenticación
- FREE: 7 días de prueba desde el registro → Bloqueado después
- PREMIUM: Acceso completo ilimitado
- UI muestra CTA "Actualizar a Premium" cuando trial expira
Beneficio: Los usuarios pueden verificar claims de noticias conversando con la IA.
Descripción: Gestión per-user de noticias favoritas con privacidad de análisis.
Arquitectura:
- Tabla
Favorite: Junction table con composite key(userId, articleId) - Campo
unlockedAnalysis: Boolean que indica si el usuario desbloqueó el análisis - Masking Logic: Si
unlockedAnalysis: false, ocultaanalysis,summary,biasScore
Flujo:
- Usuario hace clic en ❤️ → Se guarda en
Favorite(sin análisis) - Usuario hace clic en "Analizar" → Se marca
unlockedAnalysis: true - Frontend muestra análisis solo si el usuario lo desbloqueó
Beneficio: Privacidad - los usuarios solo ven análisis que explícitamente solicitaron.
Descripción: Sistema de cuotas de uso con upgrade a plan PREMIUM y periodo de prueba de 7 días.
Planes:
| Plan | Análisis/mes | Chat (Trial) | Búsquedas/mes | Precio |
|---|---|---|---|---|
| FREE | 500 | ✅ 7 días | 20 | Gratis |
| PREMIUM | Ilimitado | ✅ Ilimitado | Ilimitado | 9.99€/mes |
Nuevo: Periodo de Prueba de Chat (Sprint 30):
Los usuarios FREE tienen acceso completo al Chat durante 7 días desde su registro:
- ✨ Día 1-7: Chat habilitado (trial activo)
- 🔒 Día 8+: Chat bloqueado → CTA "Actualizar a Premium"
- 👑 PREMIUM: Acceso ilimitado permanente
Implementación Técnica:
QuotaService.canAccessChat(): Verifica elegibilidad calculando días desdeUser.createdAtFeatureLockedError(HTTP 403): Devuelto cuando trial expirado- Hook
useCanAccessChat(): Frontend verifica estado del trial - CTA Premium: Gradiente púrpura-azul con redirección a
/pricing
Features:
- Códigos promo: Sistema de canje de códigos (ej:
VERITY_ADMIN) - Auto-reset: Cuotas se resetean diariamente a las 00:00 UTC
- Token Taximeter: Monitoreo en tiempo real de costes de Gemini
- Billing Dashboard: Usuario ve uso actual vs límite
- Trial Tracking: Dashboard muestra días restantes de prueba
Tecnología:
User.subscriptionPlan: Enum (FREE/PREMIUM)User.createdAt: Timestamp para cálculo de trialQuotaService: Middleware que verifica límites y trial periodnode-cron: Jobs programados para reset diario/mensual- Constante
TRIAL_PERIOD_DAYS = 7enconstants.ts
Beneficio: Sostenibilidad del proyecto mediante modelo freemium con conversión de usuarios FREE → PREMIUM incentivada por trial period.
Descripción: Categoría "Local" personalizada según ubicación del usuario, con detección automática por GPS.
Funcionamiento:
- Usuario configura
locationcon un clic en el botón "Detectar" (geolocalización automática) - El componente
LocationButtonusanavigator.geolocation+ Nominatim para obtener "Ciudad, Provincia" - Sistema ingesta noticias locales via Google News RSS con query
"noticias locales {ciudad}" - Dashboard muestra noticias específicas de su localidad, filtradas por
category='local'
Tecnología:
- Campo
User.locationen BD LocationButtoncomponent: geolocalización browser + Nominatim reverse geocodingGoogleNewsRssClientcon query dinámico y prefijo geográficosearchLocalArticles(): búsqueda filtrada porcategory='local'+ texto de ciudad- Fallback a "Madrid" si
locationestá vacío
Integración:
- Perfil: Botón "Detectar" al lado del input de ubicación
- Sidebar: Botón de geolocalización junto al item "Local" (detecta, guarda y navega)
Beneficio: Los usuarios configuran su feed local con un solo clic, sin escribir manualmente.
Descripción: Sistema completo de logging, error tracking y distributed tracing.
Componentes:
Sentry:
- Error tracking con stack traces completos
- Performance monitoring (API latency, DB queries)
- Release tracking con Git commit SHA
- User context en errores (userId, email)
Pino Logger:
- Structured logging en formato JSON
- Niveles: trace, debug, info, warn, error, fatal
- Correlación de logs con
requestId - Integración con Sentry para errores críticos
Health Probes (Kubernetes-style):
/api/health/check: Liveness probe (aplicación viva)/api/health/readiness: Readiness probe (BD + servicios externos OK)
Token Taximeter:
- Tracking de costes de Gemini en tiempo real
- Métricas: input tokens, output tokens, cost estimado
- Almacenamiento en
UserStatspara analytics
Beneficio: Detección proactiva de errores y optimización de costes de IA.
Descripción: Cumplimiento de estándares de accesibilidad web.
Features Implementadas:
- Navegación por teclado: Tab order lógico, focus visible
- Contraste de color: Ratio 4.5:1 en texto normal
- ARIA labels: Atributos semánticos para screen readers
- Skip to content: Link invisible para saltar navegación
- Formularios accesibles: Labels asociados, error messages descriptivos
- Responsive design: Escalado hasta 200% sin pérdida de funcionalidad
Beneficio: Aplicación usable para personas con discapacidades visuales, motoras o cognitivas.
Verity AI ofrece dos métricas principales: Sesgo y Fiabilidad, basadas en el contenido disponible del artículo.
El Sesgo indica si el texto utiliza encuadre, selección de hechos o lenguaje que orienta al lector hacia una interpretación concreta. Además, Verity AI estima una tendencia ideológica del artículo: progresista, conservadora, extremista, neutral o indeterminada.
Si el sistema no encuentra señales suficientes citadas (por ejemplo, porque el artículo es muy corto o incompleto), la tendencia se marca como indeterminada para evitar conclusiones erróneas.
La Fiabilidad mide la trazabilidad interna del texto: presencia de citas, datos, atribuciones claras y contexto.
Cuando aparece “No verificable con fuentes internas”, significa que el contenido disponible no aporta evidencia suficiente dentro del propio texto (por ejemplo, snippets RSS o artículos con acceso limitado).
Esta etiqueta no implica que sea falso; indica que, sin fuentes externas o el artículo completo, no se puede confirmar la información con rigor.
En el apartado FactCheck, el veredicto SupportedByArticle significa que las afirmaciones están expresadas explícitamente en el artículo (soportadas por el texto), aunque no estén verificadas externamente.
El backend implementa Clean Architecture siguiendo los principios de Robert C. Martin:
┌─────────────────────────────────────────────────────────────────┐
│ INFRASTRUCTURE │
│ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │
│ │ Controllers │ │ Repositories │ │ External │ │
│ │ (Express) │ │ (Prisma) │ │ (Gemini, │ │
│ │ │ │ │ │ RSS, etc.) │ │
│ └──────┬───────┘ └──────┬───────┘ └──────┬───────┘ │
│ │ │ │ │
│ └─────────────────┼─────────────────┘ │
│ │ │
│ ┌─────────────────▼─────────────────┐ │
│ │ APPLICATION (Use Cases) │ │
│ │ ┌────────────────────────────┐ │ │
│ │ │ AnalyzeArticleUseCase │ │ │
│ │ │ SearchNewsUseCase │ │ │
│ │ │ ChatArticleUseCase │ │ │
│ │ └────────────────────────────┘ │ │
│ └─────────────────┬─────────────────┘ │
│ │ │
│ ┌─────────────────▼─────────────────┐ │
│ │ DOMAIN (Core) │ │
│ │ ┌────────────────────────────┐ │ │
│ │ │ Entities: NewsArticle, │ │ │
│ │ │ User, Analysis │ │ │
│ │ ├────────────────────────────┤ │ │
│ │ │ Repository Interfaces │ │ │
│ │ │ Service Interfaces │ │ │
│ │ └────────────────────────────┘ │ │
│ └───────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────┘
1. HTTP Request: POST /api/analyze/:articleId
↓
2. NewsController.analyze() [Infrastructure]
↓
3. AnalyzeArticleUseCase.execute() [Application]
↓
4. INewsArticleRepository.findById() [Domain Interface]
↓
5. PrismaNewsArticleRepository [Infrastructure Implementation]
↓
6. PostgreSQL (via Prisma)
↓
7. NewsArticle Entity [Domain]
↓
8. IGeminiClient.analyzeNews() [Domain Interface]
↓
9. GeminiClient [Infrastructure Implementation]
↓
10. Google Gemini API
↓
11. Analysis Value Object [Domain]
↓
12. IVectorClient.upsertItem() [Domain Interface]
↓
13. PgVectorClient [Infrastructure Implementation]
↓
14. PostgreSQL pgvector (store embedding)
↓
15. HTTP Response: { success, data }
El proyecto usa un DI Container Singleton para inyectar dependencias:
// backend/src/infrastructure/config/dependencies.ts
export class DependencyContainer {
public readonly prisma: PrismaClient;
public readonly geminiClient: IGeminiClient;
public readonly vectorClient: IVectorClient;
public readonly newsRepository: INewsArticleRepository;
constructor() {
// Inicialización de servicios
this.prisma = new PrismaClient();
this.geminiClient = new GeminiClient(process.env.GEMINI_API_KEY);
this.vectorClient = new PgVectorClient(this.prisma);
this.newsRepository = new PrismaNewsArticleRepository(this.prisma);
// Casos de uso
const analyzeArticleUseCase = new AnalyzeArticleUseCase(
this.newsRepository,
this.geminiClient,
this.vectorClient
);
}
}Beneficios:
- Fácil testeo (mock de dependencias)
- Bajo acoplamiento
- Inversión de dependencias (SOLID)
Esquema de Entidades:
model User {
id String @id @default(cuid())
email String @unique
name String?
location String? // Sprint 20: Geolocalización
subscriptionPlan SubscriptionPlan @default(FREE)
favorites Favorite[]
stats UserStats?
}
model Article {
id String @id @default(cuid())
title String
description String?
content String @db.Text
url String @unique
source String
imageUrl String?
publishedAt DateTime
category String
topicSlug String? // Sprint 20: Topics unificados
// Análisis de IA
isAnalyzed Boolean @default(false)
analysis Json?
summary String? @db.Text
biasScore Float?
reliabilityScore Float?
// Búsqueda vectorial
embedding Unsupported("vector(768)")? // pgvector
favorites Favorite[]
@@index([category])
@@index([topicSlug])
@@index([publishedAt(sort: Desc)])
@@index([isAnalyzed])
}
model Favorite {
user User @relation(fields: [userId], references: [id])
userId String
article Article @relation(fields: [articleId], references: [id])
articleId String
unlockedAnalysis Boolean @default(false) // Sprint 18.2: Privacy
createdAt DateTime @default(now())
@@id([userId, articleId]) // Composite key
}
model UserStats {
userId String @id
user User @relation(fields: [userId], references: [id])
// Cuotas FREE
freeAnalysesUsed Int @default(0)
freeChatsUsed Int @default(0)
freeSearchesUsed Int @default(0)
// Métricas de uso
totalTokensUsed BigInt @default(0)
estimatedCost Float @default(0)
lastResetDate DateTime @default(now())
}
model Topic {
id String @id @default(cuid())
slug String @unique // "espana", "internacional", etc.
name String // "España", "Internacional", etc.
description String?
icon String?
}
enum SubscriptionPlan {
FREE
PREMIUM
}Migraciones:
- Versionadas con Prisma Migrate
- Historial completo en
prisma/migrations/ - Última:
20260211_enable_pgvector(migración a pgvector)
Los comandos vigentes (rama actual) son:
cd backend
npm run typecheck
npx vitest run
npm run test:coveragecd frontend
npx tsc --noEmit
npm run test:run
npm run test:e2e:smoke
npm run test:e2e- Auth y contratos principales de API.
- Gate de paywall (
PAYWALL_BLOCKED) para análisis standard y deep. - Parseo de respuesta Gemini con JSON repair (1 intento) y fallback seguro.
- Limpieza de contenido antes del LLM (JSON/HTML/metadata noise).
- Flujo smoke E2E estable sin dependencia frágil de auth.
Según docs/CALIDAD.md:
- Filosofía 100/80/0 por riesgo.
- Cobertura global de branches en backend
>= 80%(npm run test:coverage).
La aplicación está desplegada en producción con la siguiente infraestructura:
- URL: https://verity-news.vercel.app
- Plataforma: Vercel (Serverless)
- Build: Automático en cada push a
main - Edge Network: CDN global con 100+ locations
Configuración:
# Build command
npm run build
# Output directory
.next
# Node version
20Variables de entorno (configuradas en Vercel Dashboard):
NEXT_PUBLIC_API_URL=https://verity-news-api.onrender.com
NEXT_PUBLIC_FIREBASE_*=[credenciales]
NEXT_PUBLIC_ENABLE_ADSENSE=true
NEXT_PUBLIC_ADSENSE_CLIENT_ID=ca-pub-...- URL: https://verity-news-api.onrender.com
- Plataforma: Render (Web Service - plan Starter, 512MB RAM)
- Build: Automático en cada push a
mainvía Docker - Region: Frankfurt (EU)
Optimizaciones de arranque (Sprint 36):
| Optimización | Motivo |
|---|---|
NODE_OPTIONS=--max-old-space-size=350 |
Evita OOM kill (exit 134) en cold start limitando el heap de Node.js a 350MB |
./node_modules/.bin/prisma migrate deploy |
Evita que npx descargue y ejecute Prisma CLI en cada arranque (~50-80MB menos) |
start-period=90s en healthcheck |
El cold start (Docker pull + migrate + Firebase init) puede tardar hasta 80s |
Variables de entorno (configuradas en Render Dashboard):
DATABASE_URL: Connection string de Neon PostgreSQLGEMINI_API_KEY: API key de Google AI StudioFIREBASE_*: Credenciales de Firebase Admin SDKCORS_ORIGIN: Lista de orígenes permitidosNODE_ENV=production
- Plataforma: Neon.tech
- Plan: Free tier (0.5GB storage, 1GB RAM)
- Extensiones: pgvector pre-instalado
- Backups: Automáticos diarios
Connection Pooling:
postgresql://user:password@host/database?pgbouncer=true&connection_limit=10
GitHub Actions (.github/workflows/ci.yml):
name: CI
on: [push, pull_request]
jobs:
test:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/setup-node@v3
- run: npm ci
- run: npm test
- run: npm run buildSentry:
- Error tracking con alertas a email
- Performance monitoring de API endpoints
- Release tracking con Git SHA
Uptime Monitoring:
- Health checks cada 5 minutos
- Alertas si downtime > 2 minutos
Verity-News is deployed on a Linux VPS using Docker-based containerization.
- VPS (IONOS)
- Docker & Docker Compose
- Nginx Reverse Proxy + HTTPS
- PostgreSQL
- ChromaDB (Vector Store)
- Sentry Monitoring
- GitHub repository
- Automated tests before deployment
- Production build reproducibility
- Environment-based configuration
- Vertical scaling via VPS upgrade
- Horizontal scaling via service separation
- Future Kubernetes-ready architecture
-
- Estructura de carpetas en
docs/ - Criterios de organización documental
- Estructura de carpetas en
-
- Memoria académica completa del proyecto
- Justificación de decisiones técnicas
- Análisis de resultados
-
- Mapa completo de archivos y carpetas
- Descripción de cada módulo
-
- Reglas de coverage (100/80/0)
- Guías de testing
-
- Estado actual pre-merge y roadmap real
- Métricas y progreso
-
Feature Notes: Paywall + Jina + JSON Repair
- Flujo real de analisis (texto, prompts, gates)
- Contratos de error (
PAYWALL_BLOCKED,formatError) - Comandos de validacion reproducibles
Cada sprint tiene documentación detallada en docs/sprints/:
- Sprint 27.3 - Production Hotfixes
- Sprint 27 - Freemium
- Sprint 25 - AI Prompt Improvements
- Sprint 20 - Geolocalización
- [20+ documentos adicionales]
- PROJECT_CONTEXT.md: Contexto para GitHub Copilot
- AI_RULES.md: Reglas de desarrollo asistido por IA
Este proyecto demuestra la aplicación de:
✅ Clean Architecture (Robert C. Martin)
- Separación en capas: Domain, Application, Infrastructure
- Dependency Inversion Principle
- Testability garantizada
✅ SOLID Principles
- Single Responsibility: Cada clase una responsabilidad
- Open/Closed: Extensible sin modificar código existente
- Liskov Substitution: Interfaces intercambiables
- Interface Segregation: Interfaces específicas
- Dependency Inversion: Abstracciones sobre implementaciones
✅ DDD (Domain-Driven Design)
- Entities con lógica de negocio
- Value Objects inmutables
- Repository pattern
✅ TDD (Test-Driven Development)
- 328 tests (95% coverage)
- Red-Green-Refactor workflow
- Tests como documentación
✅ Mikado Method
- Refactorizaciones incrementales
- Grafos de dependencias
- Sprint 13.4: Profile page de 468 a 166 LOC (-64.5%)
✅ Conventional Commits
- Historial semántico:
feat:,fix:,refactor: - Automated changelog generation
✅ Documentation as Code
- Docs versionadas con código
- Markdown para portabilidad
✅ Code Review
- ESLint + Prettier configurados
- Pre-commit hooks con Husky
- Type safety con TypeScript strict mode
✅ Security Best Practices
- Helmet para HTTP headers seguros
- Input validation con Zod
- Rate limiting en endpoints sensibles
- CORS configurado correctamente
✅ Performance
- React Query para caching inteligente
- PostgreSQL índices optimizados
- Lazy loading de componentes
- Image optimization con Next.js
-
IA en Producción es Complejo:
- Los LLMs pueden alucinar → Necesidad de prompts estrictos
- Los costes escalan rápidamente → Importance de caching y cuotas
- La latencia afecta UX → Necesidad de loading states y fake delays
-
Clean Architecture Vale la Pena:
- La separación en capas facilitó enormemente el testing
- Cambiar de ChromaDB a pgvector fue trivial gracias a interfaces
- El DI Container simplificó la gestión de dependencias
-
TDD No es Opcional en Proyectos Reales:
- Los 328 tests detectaron 47 bugs antes de producción
- La cobertura del 95% dio confianza para refactorizar
- Los tests sirvieron como documentación viva del comportamiento
-
La Observabilidad es Crítica:
- Sentry detectó 23 errores que no habríamos visto de otra forma
- Los logs estructurados (Pino) facilitaron debugging en producción
- El Token Taximeter evitó sobrecostes de 150€ en un mes
Este proyecto fue desarrollado con asistencia de GitHub Copilot y Claude:
GitHub Copilot:
- Autocompletado de código repetitivo (reducción del 40% de tiempo)
- Generación de tests boilerplate
- Sugerencias de types de TypeScript
Claude:
- Diseño de arquitectura (diagramas en Mermaid)
- Refactorizaciones complejas (Mikado Method)
- Revisión de código y detección de anti-patterns
Lecciones sobre IA como Asistente:
- ✅ Excelente para boilerplate y código repetitivo
- ✅ Útil para refactorizaciones con instrucciones claras
⚠️ Necesita supervisión humana constante⚠️ No reemplaza el conocimiento de arquitectura- ❌ No puede diseñar soluciones complejas de forma autónoma
Si este proyecto continuara:
-
Escalabilidad:
- Migrar a Kubernetes para auto-scaling
- Implementar Redis para cache distribuido
- Sharding de PostgreSQL para > 1M artículos
-
IA Avanzada:
- Fine-tuning de modelo específico para análisis de sesgos españoles
- Multi-model approach (Gemini + Claude + GPT-4 en ensemble)
- Fact-checking automático con APIs de verificadores
-
Monetización:
- Integración con Stripe para pagos recurrentes
- API pública para desarrolladores (modelo pay-as-you-go)
- Dashboards empresariales para medios de comunicación
-
Mobile:
- App nativa con React Native
- Notificaciones push de noticias importantes
- Modo offline con sync
Este proyecto es parte de un Trabajo Final de Máster con fines educativos.
Licencia: MIT
Atribución: Si usas este código, por favor menciona:
Verity News - TFM Máster en Desarrollo con IA
Autor: David López Sánchez (BIG School, 2026)
David López Sánchez
- 🎓 Estudiante del Máster en Desarrollo con Inteligencia Artificial
- 🏫 Institución: BIG School
- 📅 Año: 2026
- 🐙 GitHub: @David-LS-Bilbao
- 📧 Repositorio: PROYECTO-MASTER-IA
- BIG School - Por el programa de Máster en Desarrollo con IA
- Comunidad Open Source:
- shadcn/ui por los componentes accesibles
- Prisma por el ORM excepcional
- Next.js por el framework moderno
- Vercel por el hosting gratuito
- Proveedores de IA:
- Google (Gemini 2.0 Flash API)
- Anthropic (Claude para asistencia en desarrollo)
- Herramientas de Desarrollo:
- GitHub Copilot por el pair programming
- VSCode por el editor potente
- Cursor por el IDE con IA integrada
Para preguntas académicas sobre este TFM:
- Email: [consultar en repositorio]
- Issues: GitHub Issues
Para reportar bugs o sugerir features:
- Abre un issue en GitHub con la etiqueta correspondiente
🚀 Proyecto en producción - Sprint 36
Estado: En producción y funcional Última actualización: 19 de febrero de 2026 Líneas de código: ~32,000 (sin dependencias) Tests: 328 (95% coverage) Tiempo de desarrollo: 7 semanas Commits: 540+
Desarrollado con ❤️ y ☕ como Trabajo Final de Máster
"La desinformación es el mayor desafío de nuestra era digital. Verity News es mi contribución para enfrentarlo con tecnología." - David López