Tabla de Contenidos

Parte de Ecentia Ecentia

Esta sección reúne toda la documentación técnica y de gestión desarrollada para el Proyecto Final del Ciclo Formativo de Grado Superior en Desarrollo de Aplicaciones Multiplataforma (DAM), realizado para el instituto IES Francisco Rodríguez Marín.

Documento General

Introducción y Contexto

Nexus es una plataforma digital multiplataforma que unifica tres grandes módulos de comercio y comunidad en un único ecosistema: un marketplace de segunda mano, un chollometro comunitario y un sistema de publicidad B2B para empresas, con el objetivo de convertirse en la herramienta de ahorro de referencia para el usuario español.

Descripción del Proyecto

El proyecto Nexus nace de la necesidad de centralizar en una sola aplicación todas las herramientas que un usuario necesita para ahorrar dinero en sus compras cotidianas. Actualmente, los usuarios deben consultar múltiples plataformas para comparar precios de productos nuevos (en tiendas físicas y online), encontrar ofertas publicadas por la comunidad y acceder al mercado de segunda mano. Nexus elimina esa fragmentación ofreciendo una experiencia unificada y coherente.

La plataforma se desarrolla como Proyecto Final de Ciclo Formativo de Grado Superior en Desarrollo de Aplicaciones Multiplataforma (DAM), en el IES Francisco Rodríguez Marín, y forma parte del ecosistema de la empresa ficticia Ecentia, utilizada como entidad organizativa del proyecto. El equipo de desarrollo está formado por tres integrantes que asumen conjuntamente las responsabilidades de diseño, arquitectura y programación.

El nombre Nexus (del latín nexus: conexión, vínculo) refleja la propuesta central de la plataforma: conectar compradores y vendedores particulares, conectar usuarios con las mejores ofertas del momento y conectar empresas con su audiencia objetivo, todo en un único punto de acceso.

Motivación

El mercado de aplicaciones de ahorro y segunda mano en España está dominado por plataformas especializadas en nichos concretos: Wallapop y Vinted para segunda mano, Chollometro y Letsbonus para ofertas, Autocasion y Coches.net para vehículos. Ninguna de estas plataformas ofrece una experiencia integrada que permita al usuario buscar simultáneamente entre productos nuevos con descuento, artículos de segunda mano entre particulares y vehículos de ocasión.

Nexus propone una solución diferente: un buscador unificado con expansión de sinónimos que indexa en tiempo real productos, ofertas y vehículos, combinado con una capa social basada en el sistema de votación Spark/Drip (similar al upvote/downvote de Reddit pero aplicado al ámbito del ahorro), que permite a la comunidad destacar las mejores oportunidades de forma orgánica.

El sistema de publicidad integrado para empresas (contratos de banner y patrocinio de productos pagados con Stripe) añade una vía de monetización sostenible que no depende exclusivamente de publicidad invasiva, sino de visibilidad genuina dentro del catálogo donde el usuario ya está buscando.

Objetivos Generales

  • Desarrollar una plataforma web y móvil multiplataforma con un único backend compartido que sirva a todas las aplicaciones cliente.
  • Implementar un marketplace de compraventa entre particulares con pagos seguros mediante Stripe (modelo escrow) y gestión de envíos con generación de QR y seguimiento.
  • Crear un sistema comunitario de publicación y votación de ofertas/chollos con métricas de popularidad (sistema Spark/Drip) y secciones especializadas (flash, gratis, viajes).
  • Ofrecer un canal de publicidad y patrocinio para empresas, con contratos gestionados y pagados mediante Stripe Checkout.
  • Garantizar la seguridad de los usuarios mediante autenticación JWT con invalidación por versión, 2FA (TOTP y Email OTP), Google OAuth y protección reCAPTCHA v3 en formularios.
  • Cumplir con el Reglamento General de Protección de Datos (RGPD) mediante borrado suave, consentimiento explícito de términos, gestión de newsletter con double opt-in y tokens de baja únicos.
  • Diseñar una arquitectura escalable y mantenible, desplegada íntegramente en servicios cloud (Render, Vercel, Cloudinary, PostgreSQL gestionado).
  • Habilitar la distribución como aplicación nativa Android (y potencialmente iOS) mediante Ionic + Capacitor sin duplicar el código fuente.

Tecnologías Utilizadas y Justificación

Backend — Spring Boot 3.5.13 (Java 17): Framework maduro con ecosistema robusto para seguridad (Spring Security), persistencia (Spring Data JPA + Hibernate), comunicación en tiempo real (Spring WebSocket STOMP), correo (Spring Mail), caché y validación. Java 17 (LTS) garantiza compatibilidad y soporte a largo plazo. La elección frente a Node.js o Django se justifica por la tipificación fuerte, el rendimiento en JVM y la cobertura nativa de todos los casos de uso requeridos (seguridad, pagos, WebSocket, email) con un modelo de inyección de dependencias maduro.

Base de datos — PostgreSQL: Sistema de gestión de bases de datos relacional de código abierto con soporte avanzado para índices compuestos, consultas con JOIN complejas y tipos TEXT ilimitados. Se eligió frente a MySQL por su mayor conformidad con el estándar SQL, mejor soporte para operaciones concurrentes y disponibilidad en el plan gratuito de Render. La gestión del esquema se delega a Hibernate con ddl-auto=create durante el desarrollo.

Frontend de usuario — Angular 21 + Ionic 8 + Capacitor 8 (TypeScript 5.9): Angular 21 con Standalone Components y Signals reactivos ofrece una arquitectura sin módulos NgModule que reduce la complejidad y mejora el tree-shaking. Ionic 8 proporciona componentes UI nativos adaptados a dispositivos móviles. Capacitor 8 compila la app web como aplicación nativa Android e iOS con acceso a APIs del dispositivo (cámara, GPS). Esta combinación fue elegida frente a React Native o Flutter porque maximiza la reutilización de código entre web y móvil con una única base de código Angular.

Panel de administración — Angular 21: Aplicación Angular independiente desplegada en un subdominio separado por seguridad, con acceso exclusivo para administradores (ROLE_ADMIN). Comparte el mismo backend pero utiliza tokens JWT con rol diferente, evitando que cualquier vulnerabilidad en la app pública afecte al panel de administración.

Web informativa — Astro: Framework de generación de sitios con renderizado en servidor (SSR) y cero JavaScript por defecto en el cliente. Ideal para la web de presentación del proyecto por su velocidad de carga, excelente SEO y sintaxis de componentes familiar para desarrolladores Angular/React.

Almacenamiento de medios — Cloudinary 1.36.0: Servicio cloud de gestión y transformación de imágenes. Todas las imágenes de productos, vehículos, ofertas y avatares se suben a Cloudinary vía la API HTTP (con el cliente Java), liberando al servidor Spring de almacenamiento en disco. Cloudinary optimiza y sirve las imágenes desde su CDN global.

Pasarela de pagos — Stripe Java SDK 24.1.0 + Stripe.js: Stripe Payment Intents para el flujo de compra con modelo escrow; Stripe Checkout Sessions para contratos de publicidad de empresas; Webhooks con verificación de firma HMAC para confirmación asíncrona de pagos. Stripe ofrece el mejor equilibrio entre facilidad de integración, seguridad PCI DSS y exhaustividad de la documentación.

Autenticación — JJWT 0.11.5 + Spring Security + BCrypt: Los tokens JWT son firmados con HMAC-SHA256. El campo jwtVersion en la entidad Actor permite invalidar globalmente todos los tokens de un usuario sin necesidad de una blacklist en servidor. Las contraseñas siempre se almacenan hasheadas con BCrypt.

Autenticación en dos factores — dev.samstevens.totp 1.7.1 + ZXing 3.5.3: La librería TOTP genera secretos compatibles con Google Authenticator y Authy. ZXing genera los códigos QR para el proceso de vinculación. El método por email usa códigos OTP de 6 dígitos con caducidad de 10 minutos.

Chat en tiempo real — Spring WebSocket + STOMP + SockJS + @stomp/stompjs 7.3.0: El protocolo STOMP sobre WebSocket gestiona la mensajería privada entre usuarios. El interceptor WebSocket autentica las conexiones usando el mismo JWT del usuario. SockJS proporciona fallback HTTP para navegadores o redes sin soporte nativo de WebSocket.

Asistente IA de soporte — Google Gemini 1.5-flash / Groq (LLaMA 3.3-70b): El chatbot de soporte integra dos proveedores de IA intercambiables mediante configuración. Cuando ningún agente puede resolver la consulta, el sistema escala automáticamente por email al equipo de soporte.

Protección anti-bots — Google reCAPTCHA v3: Integrado en el registro y login. El token se genera en el cliente y se verifica en el backend contra la API de Google (recaptcha.net/recaptcha/api/siteverify) con un umbral mínimo de puntuación de 0.5, rechazando peticiones automatizadas sin interrumpir al usuario legítimo.

Alcance del Sistema

El sistema Nexus abarca cuatro submódulos desplegados de forma independiente pero interconectados:

  • Aplicación de usuario (nexus-angular-app): Aplicación web/móvil para usuarios finales. Incluye el marketplace completo (productos, vehículos, ofertas), sistema de pagos con Stripe, chat en tiempo real, notificaciones in-app, gestión del perfil, secciones de publicidad y asistente IA de soporte. Desplegada en Vercel, compilable para Android vía Capacitor.
  • Panel de administración (nexus-admin-web-app): Aplicación web exclusiva para administradores, con 19 módulos de gestión: dashboard, estadísticas, usuarios, moderación (productos, ofertas, vehículos, reportes), devoluciones, compras, contratos, cupones, patrocinios, newsletter, notificaciones masivas, soporte de chat, audit log y configuración global. Desplegada en Vercel en un dominio separado.
  • Web informativa (nexus-web-about): Sitio estático con documentación técnica, blog de actualizaciones, catálogo de tecnologías y páginas legales. Generada con Astro y desplegada en Vercel.
  • Backend unificado (nexus-backend): API REST (48 controladores) + WebSocket STOMP que sirve a todas las aplicaciones cliente. Desplegado en Render con Docker. Puerto 8080 con compresión HTTP y configuración optimizada para entornos con recursos limitados.

Exclusiones deliberadas en v1.0: Los pagos están limitados al entorno de pruebas de Stripe (sin cargos reales). La aplicación iOS está prevista pero aún no publicada en App Store. La integración con transportistas externos (Correos, MRW) está implementada como servicio stub con estructura preparada para integración real.

Análisis y Requisitos

Esta sección presenta los actores del sistema, los requisitos funcionales y no funcionales más representativos, y los principales casos de uso identificados. Para la especificación completa y detallada, consulta la sección Requisitos del Sistema, extraída del análisis exhaustivo del código fuente real.

Actores del Sistema

Nexus define cuatro tipos de actores representados mediante una jerarquía de herencia en la capa de persistencia, con la clase abstracta Actor como base común (estrategia JPA InheritanceType.JOINED, tabla compartida actor):

Visitante

  • No registrado, sin token JWT
  • Acceso de solo lectura al catálogo
  • Puede buscar, navegar perfiles públicos y ver ofertas
  • Puede registrarse o iniciar sesión

Usuario (ROLE_USER)

  • Registro con email o Google OAuth
  • Puede publicar productos, vehículos y ofertas
  • Compra y venta con pagos seguros (Stripe escrow)
  • Chat en tiempo real, favoritos, valoraciones, bloqueos
  • Acceso a su perfil, configuración, notificaciones y compras
  • Puede convertirse en Empresa completando el onboarding empresarial

Empresa (ROLE_EMPRESA)

  • Perfil empresarial con CIF y nombre comercial
  • Acceso a contratos publicitarios (banners y patrocinios)
  • Pago de contratos mediante Stripe Checkout
  • Publicación de contenido como cuenta verificada
  • Gestión de sus patrocinios activos

Administrador (ROLE_ADMIN)

  • Panel de gestión exclusivo en dominio separado
  • Moderación de usuarios, contenido y reportes
  • Gestión de contratos, cupones, publicidad y newsletter
  • Estadísticas, auditoría de acciones y configuración global
  • Tres niveles de acceso: 1 (básico), 2 (moderador), 3 (superadmin)

Resumen de Requisitos

A continuación se recogen los módulos funcionales principales y las reglas de negocio más relevantes del sistema. La especificación completa y detallada —con los 63 requisitos funcionales (RF-01 a RF-63), 26 requisitos no funcionales (RNF-01 a RNF-26) y 17 requisitos de información (RI-01 a RI-17)— está disponible en la sección Requisitos del Sistema de este mismo documento.

RF — Autenticación

Sistema de autenticación multicapa

El sistema soporta registro con email y contraseña (con medidor de fortaleza y reCAPTCHA v3 verificado en backend), Google OAuth 2.0 (ID token verificado en el backend con la librería de Google API Client), verificación de email con código OTP de 6 dígitos (válido 30 minutos), autenticación de dos factores (TOTP con código QR vía ZXing o Email OTP válido 10 minutos), recuperación de contraseña con token UUID de 15 minutos de vigencia almacenado directamente en la entidad Actor (sin tabla adicional), y un wizard de onboarding inicial para nuevos usuarios que guia por aceptación de términos, configuración de seguridad y elección del tipo de cuenta.

RF — Marketplace

Publicación y gestión del catálogo

Los usuarios pueden publicar productos de segunda mano (con hasta 1 imagen principal + 5 en galería almacenadas en Cloudinary, tipo de oferta VENTA/DONACION/INTERCAMBIO, condición, peso para cálculo de envío, geolocalización y vigencia automática de 180 días), vehículos (con ficha técnica completa: marca, modelo, año, kilometraje, combustible, cambio, potencia, cilindrada, color, puertas, plazas, ITV, garantía, extras) y ofertas/chollos (con precio original, precio de oferta, código de descuento, URL externa, y datos de flash sale con fecha de fin y límite de unidades). Los anuncios expirados permanecen visibles 14 días adicionales antes de ocultarse.

RF — Búsqueda

Motor de búsqueda unificado con filtros avanzados

El MarketplaceSearchController indexa simultáneamente productos, vehículos y ofertas con expansión de sinónimos gestionada por SynonymService (p. ej. “ps5” encuentra “PlayStation 5”). Los filtros incluyen: categoría, rango de precio (mínimo y máximo), condición del artículo, antigüedad de la publicación, disponibilidad de envío, ordenación (novedades, precio asc/desc), parámetros específicos de vehículos y filtro geográfico por ubicación (manual o GPS) con radio ajustable en kilómetros. El radio geográfico se convierte en un bounding box lat/lon calculado en el backend con trigonometría esférica.

RF — Pagos

Sistema de pagos seguro con Stripe

Las compras de productos usan Stripe Payment Intents con modelo escrow (el dinero queda retenido hasta que el comprador confirma la recepción). Los contratos publicitarios de empresas usan Stripe Checkout Sessions con redirección al portal de Stripe. El StripeWebhookController recibe confirmaciones asíncronas vía POST en /stripe/webhook, verificando la firma HMAC del evento con el secreto del endpoint. El sistema calcula automáticamente la comisión de la plataforma (comisionNexus) en cada transacción. Los cupones de descuento se validan y aplican en el proceso de checkout mediante CuponValidacionService.

RF — Mensajería

Chat en tiempo real con WebSocket STOMP

El sistema de mensajería permite conversaciones privadas entre comprador y vendedor, asociadas opcionalmente a un producto concreto mediante un roomId determinista (calculado a partir de los IDs de ambos participantes). Soporta múltiples tipos de mensaje: TEXTO, IMAGEN, VIDEO, AUDIO, GIF y OFERTA_PRECIO (propuesta de precio con aceptación/rechazo en tiempo real). El WebSocketAuthInterceptor valida el token JWT en el handshake de la conexión STOMP. El sistema de bloqueo (BloqueoService) impide la comunicación entre usuarios que se han bloqueado mutuamente.

RF — Moderación

Sistema de reportes y sanciones

Los usuarios pueden reportar productos, ofertas, vehículos, usuarios, mensajes y comentarios seleccionando un motivo predefinido. Los administradores gestionan los reportes desde el panel, pudiendo aplicar sanciones: ban permanente (baneado = true), suspensión temporal (suspendidoHasta) o marcado de fraude (flagFraude). El campo jwtVersion del actor sancionado se incrementa para invalidar todos sus tokens activos. Todas las acciones del administrador se registran en la entidad AuditLog con actor, tipo, descripción y timestamp.

RF — Newsletter

Newsletter con double opt-in y cumplimiento RGPD

El sistema de newsletter implementa el flujo de double opt-in requerido por el artículo 7 del RGPD: el visitante proporciona su email, el sistema envía un correo de confirmación (Gmail SMTP con Spring Mail y STARTTLS), y solo activa la suscripción tras la confirmación. Cada suscriptor tiene un token de baja único incluido en cada email. El sistema registra la IP del consentimiento, la fecha y la versión de la política de privacidad aceptada, permitiendo acreditar el consentimiento explícito ante cualquier auditoría. El administrador puede gestionar suscriptores y enviar campañas desde el panel de administración.

Requisitos No Funcionales

Categoría Requisito Implementación técnica
Seguridad Contraseñas nunca en texto plano BCrypt con configuración de coste vía PasswordConfig de Spring Security. El getter de password en Actor está anotado con @JsonIgnore.
Seguridad Protección contra bots en formularios Google reCAPTCHA v3 con umbral mínimo de puntuación 0.5, verificado sincrónicamente en el backend antes de procesar el registro o login
Seguridad Tokens JWT con invalidación global Campo jwtVersion (entero) en Actor; al incrementarse, todos los JWT emitidos previamente son rechazados por el JWTAuthenticationFilter
Privacidad (RGPD) Derecho al olvido Borrado suave (cuentaEliminada = true) sin eliminar registros históricos de compras; anonimización de datos personales en la práctica
Privacidad (RGPD) Consentimiento verificable de newsletter Double opt-in con registro de IP del consentimiento, fecha exacta, versión de la política aceptada y token de baja único por suscriptor
Rendimiento Optimización para entorno con recursos limitados Tomcat máximo 15 hilos, HikariCP máximo 3 conexiones, pool async máximo 5 hilos, scheduler 1 hilo, compresión HTTP habilitada desde 1 KB
Escalabilidad Carga progresiva del frontend Lazy loading total de rutas en Angular con loadComponent(() => import(...)), sin módulos NgModule, reduciendo el bundle inicial
Disponibilidad Despliegue continuo y tolerancia a fallos Docker en Render (backend), Vercel CDN global (frontends). HikariCP configurado con initialization-fail-timeout=60s y keepalive-time=60s para tolerar reinicios de la BD
Mantenibilidad Trazabilidad de acciones administrativas Entidad AuditLog inmutable que registra cada acción administrativa (actor, tipo, descripción, timestamp), consultable desde el panel de administración
Usabilidad Soporte multidispositivo nativo Angular + Ionic como PWA y app nativa Android vía Capacitor 8; diseño adaptativo (responsive) para todos los tamaños de pantalla
Interoperabilidad API documentada automáticamente SpringDoc OpenAPI 2.6.0 con anotaciones @Tag, @Operation y @Schema; Swagger UI accesible en /swagger-ui.html

Casos de Uso Representativos

Los siguientes casos de uso cubren los flujos más representativos del sistema. Están implementados al completo en el backend y en las dos aplicaciones Angular.

CU-01

Compra de un producto de segunda mano

Actor principal: Usuario autenticado (comprador)

Flujo: El usuario navega el marketplace → accede al detalle del producto → pulsa “Comprar” → selecciona método de entrega (envío o recogida en mano) → introduce la dirección de entrega → aplica cupón de descuento (opcional, validado en el backend) → el sistema crea un Stripe Payment Intent y devuelve el clientSecret → el usuario completa el pago en el formulario de Stripe.js → Stripe envía el webhook payment_intent.succeeded → el backend crea la entidad Compra (estado PAGADO) y Envio (con código QR base64) → el vendedor y el comprador reciben notificaciones in-app → el producto pasa a estado RESERVADO → el vendedor confirma el envío con número de seguimiento → el comprador confirma la recepción → el pago se libera (modelo escrow) → ambas partes pueden valorarse.

CU-02

Publicación y votación de una oferta/chollo

Actor principal: Usuario o Empresa autenticados

Flujo: El usuario accede a “Publicar oferta” → completa el formulario (título, descripción, tienda, precio original, precio oferta, categoría, imagen subida a Cloudinary, URL externa, código de descuento, tipo flash/normal con fecha de fin) → el sistema calcula automáticamente el badge (NUEVA, CHOLLAZO, PORCENTAJE, GRATUITA, EXPIRA_HOY) en @PreUpdate → la oferta se publica y es visible en el chollometro → los usuarios votan con Spark (positivo) o Drip (negativo) usando SparkVotoController → un scheduler recalcula el sparkScore cada 5 minutos → las ofertas más valoradas ascienden en el ranking principal.

CU-03

Contratación de publicidad por una empresa

Actor principal: Empresa autenticada

Flujo: La empresa accede a “Publicidad / Mis contratos” → solicita un patrocinio (tipo BANNER o PUBLICACION) indicando el ítem a patrocinar (producto, oferta o vehículo) y los días de duración → el administrador recibe la solicitud en estado DRAFT → el administrador la aprueba y establece el precio → el sistema genera una Stripe Checkout Session vía PatrocinioController → la empresa paga mediante Stripe Checkout → el webhook checkout.session.completed confirma el pago → el contrato pasa a estado ACTIVE y el ítem recibe el flag patrocinado = true con la patrocinioHasta calculada → el ítem aparece con la etiqueta “Patrocinado” en el catálogo.

CU-04

Registro y autenticación con 2FA TOTP

Actor principal: Visitante

Flujo: El visitante rellena el formulario de registro con username único, email, contraseña (con validación de fortaleza en tiempo real) y token reCAPTCHA → el backend valida el captcha y crea el Usuario con contraseña hasheada con BCrypt → envía un OTP de 6 dígitos al email → el usuario verifica su cuenta → accede al wizard de onboarding → activa 2FA TOTP: el backend genera un secreto TOTP con TwoFactorAuthService, crea el QR con ZXing y lo devuelve en base64 → el usuario escanea el QR con Google Authenticator → en el próximo login, tras verificar credenciales, el backend responde con twoFactorRequired: true → el usuario introduce el código TOTP de 6 dígitos → el backend lo valida con la librería dev.samstevens.totp → si es válido, genera y devuelve el JWT con el rol del actor.

CU-05

Solicitud y gestión de una devolución

Actor principal: Usuario comprador

Flujo: El comprador accede al detalle de una compra en estado ENTREGADA → solicita una devolución seleccionando un motivo (PRODUCTO_DEFECTUOSO, NO_CORRESPONDE, DAÑO_TRANSPORTE, CAMBIO_DE_OPINIÓN, TALLA_INCORRECTA, OTRO) y adjuntando fotos de evidencia (hasta 4, subidas a Cloudinary) → el sistema crea la entidad Devolucion en estado SOLICITADA → el vendedor recibe una notificación y puede aceptar o rechazar la devolución con una nota → si es aceptada, el sistema inicia el reembolso en Stripe (vía DevolucionService.procesarReembolso()) → el estado de la compra pasa a REEMBOLSADA → el comprador recibe el importe devuelto.

Diseño

Esta sección describe la arquitectura general del sistema, el modelo de datos persistente, el diseño de la API REST y la organización de componentes en las aplicaciones Angular.

Arquitectura del Sistema

El sistema se organiza en tres capas bien diferenciadas: los tres clientes en Vercel, el backend central en Render y los servicios externos de terceros. El backend es el único punto de contacto entre todos ellos; ningún cliente habla directamente con PostgreSQL, Cloudinary ni Stripe.

Capa de cliente — Vercel CDN

nexus-angular-app

Angular 21 + Ionic 8
App de usuario · Web + Android
Puerto 4200 (dev)

nexus-admin-web-app

Angular 21
Panel de administración
Puerto 4201 (dev)

nexus-web-about

Astro (estático)
Web informativa y docs
Puerto 4321 (dev)

HTTP REST + JWT Bearer  ·  WebSocket STOMP (solo app de usuario)

Backend — Render.com · Docker · Puerto 8080

nexus-backend — Spring Boot 3.5.13 (Java 17)

Controllers
48 REST + 1 WS
Services
44
Security
JWT + 2FA + reCAPTCHA
Schedulers
7 tareas auto
Spring Data JPA / Hibernate

Servicios externos

PostgreSQL Base de datos
(Render DB)
Cloudinary Imágenes y vídeos
Stripe Pagos y webhooks
Gmail SMTP Correo transaccional

Capas internas del backend

El backend sigue el patrón arquitectónico en capas clásico de Spring:

  • Capa de presentación (Controllers): 48 controladores REST + 1 controlador WebSocket, uno por dominio funcional. Anotados con @RestController y documentados con OpenAPI/Swagger (@Tag, @Operation). Gestionan la serialización/deserialización JSON con Jackson.
  • Capa de servicio (Services): 44 servicios que encapsulan toda la lógica de negocio. Anotados con @Service y gestionados por el contenedor IoC de Spring. Las operaciones que requieren atomicidad se anotan con @Transactional.
  • Capa de persistencia (Repositories): Interfaces Spring Data JPA que extienden JpaRepository con métodos derivados del nombre y consultas JPQL personalizadas con @Query.
  • Capa de seguridad: El JWTAuthenticationFilter intercepta todas las peticiones HTTP, extrae el token Bearer, valida la firma y la jwtVersion, y establece el contexto de autenticación en el SecurityContextHolder. El WebSocketAuthInterceptor aplica el mismo proceso para las conexiones STOMP.
  • Capa de tareas programadas (Schedulers): Dos schedulers: AnuncioCaducidadService (gestiona la caducidad de anuncios y envía notificaciones proactivas) y NewsletterScheduler (gestiona envíos de campañas). El recalculo del Spark Score se integra en el servicio de votos con el intervalo configurado.

Modelo de Base de Datos

La base de datos PostgreSQL se gestiona con Hibernate ORM a través de Spring Data JPA. La jerarquía de actores se implementa con la estrategia JOINED: la tabla actor almacena los campos comunes y las tablas usuario, empresa y admin solo los campos específicos de cada subtipo, unidos mediante PrimaryKeyJoinColumn. Los índices de base de datos más críticos se definen con la anotación @Table(indexes = {...}) directamente en las entidades Java.

Jerarquía de actores

Entidad / Tabla Relaciones Campos principales
Actor
actor
Clase base de Usuario, Empresa y Admin (estrategia JOINED) username (unique), email (unique), password (bcrypt), jwtVersion, twoFactorEnabled, baneado, suspendidoHasta, stripeCustomerId, cuentaEliminada, googleId
Usuario
usuario
Extiende Actor (PK compartida) reputacion (0.0–5.0), totalVentas, ubicacion, cuentaPrivada, newsletterSuscrito, terminosAceptados, direccionPorDefecto (embebida), 6 flags de notificación
Empresa
empresa
Extiende Actor (PK compartida) cif (unique), nombreComercial, descripcion, web, logo (Cloudinary URL), verificada
Admin
admin
Extiende Actor (PK compartida) nivelAcceso (1 básico / 2 moderador / 3 superadmin)

Catálogo

Entidad / Tabla Relaciones e índices Campos principales
Producto
producto
FK Actor (vendedor), FK Categoria
idx_producto_vendedor, idx_producto_estado, idx_producto_categoria
titulo, precio, tipoOferta (VENTA/DONACION/INTERCAMBIO), estado, condicion, admiteEnvio, latitude/longitude, imagenPrincipal, galería (hasta 5), fechaCaducidad (180 días), patrocinado
Oferta
oferta
FK Actor, FK Categoria
idx_oferta_activa, idx_oferta_spark, idx_oferta_categoria
titulo, precioOferta, precioOriginal, tienda, badge (auto), sparkCount, dripCount, sparkScore, esFlash, flashFin, codigoDescuento, patrocinada
Vehiculo
vehiculo
FK Actor (publicador), FK Categoria
idx_vehiculo_publicador, idx_vehiculo_tipo, idx_vehiculo_estado
tipoVehiculo, marca, modelo, anio, kilometros, combustible, cambio, potencia, itv, garantia, extras (lista), galería de imágenes
Categoria
categoria
Self-join ManyToOne (parent → hijos)
slug (unique), parent_id
nombre, slug (único), icono, color, orden, activa, parent (FK opcional)

Comercio y logística

Entidad / Tabla Relaciones Campos principales
Compra
compra
FK Actor (comprador), FK Producto
idx_compra_comprador, idx_compra_estado
estado (PENDIENTE/PAGADO/ENVIADO/ENTREGADO/COMPLETADA…), stripePaymentIntentId, precioFinal, comisionNexus, metodoEntrega, dirección completa de entrega, fechas de cada hito
Envio
envio
OneToOne con Compra codigoEnvio (SHIP-XXXXXXXX), codigoQR (base64), transportista, numeroSeguimiento, urlTracking, fechaLimiteEnvio, estadoEnvio
Devolucion
devolucion
FK Compra; tabla devolucion_fotos motivo (enum), descripcion, fotos de evidencia (hasta 4 URLs), notaVendedor, importeDevuelto, estado (SOLICITADA/ACEPTADA/RECHAZADA/COMPLETADA)
Contrato
contrato
FK Empresa, FK Actor tipoContrato (BANNER/PUBLICACION), estado (DRAFT→ACTIVE→EXPIRED), monto, itemId (ítem patrocinado), diasPatrocinio, stripeCheckoutSessionId
Cupon
cupon
Tabla cupon_uso (historial); tabla cupon_categorias codigo (unique, máx 20 chars), tipoDescuento (PORCENTAJE/FIJO), valor, importeMinimo, alcance, limiteTotalUsos, limiteUsosPorUsuario, fechas de vigencia

Social y sistema

Entidad / Tabla Relaciones Campos principales
ChatMensaje
chat_mensaje
FK Actor ×2 (remitente, receptor), FK Producto (opcional)
idx: roomId, remitente, receptor, fechaEnvio
roomId, texto, mediaUrl, tipo (TEXTO/IMAGEN/VIDEO/AUDIO/GIF/OFERTA_PRECIO), leido, recibido, precioPropuesto, estadoPropuesta
Valoracion
valoracion
FK Actor ×2 (comprador, vendedor), FK Compra (unique — una por compra) puntuacion (1–5 estrellas), comentario, respuestaVendedor, fechaRespuesta
SparkVoto
spark_voto
FK Actor, FK Oferta
unique (usuario_id + oferta_id)
tipoVoto (SPARK/DRIP), fecha
Favorito
favorito
FK Actor, FK Producto/Oferta/Vehiculo (opcional) fecha. Restricción unique por usuario + ítem para evitar duplicados.
NewsletterSuscripcion
newsletter_suscripcion
Sin FK externas
email (unique), tokenBaja (unique)
email, activa, ipConsentimiento, fechaConsentimiento, versionPolitica, tokenBaja (único por suscriptor), fechaBaja
AuditLog
audit_log
FK Actor (admin que actuó)
idx: actor_id, fechaAccion
tipoAccion, descripcion, fechaAccion. Registro inmutable de cada acción administrativa sensible.
NotificacionInApp
notificacion_in_app
FK Actor (destinatario)
idx: actor_id, leida, fecha
titulo, mensaje, tipo (NUEVA_COMPRA/MENSAJE/ALERTA/CADUCIDAD_ANUNCIO/…), urlDestino, leida, destacada

Diseño de la API REST

La API REST está documentada automáticamente con SpringDoc OpenAPI 2.6.0 (Swagger UI en /swagger-ui.html, especificación JSON en /v3/api-docs). Las rutas siguen el patrón REST estándar con sustantivos en plural para colecciones:

Base de ruta Controlador Métodos principales Autenticación requerida
/api/auth  ·  /auth AuthController POST /login, /register, /google, /verify-2fa, /forgot-password, /reset-password, /verify-email Pública
/producto ProductoController GET /, /disponibles, /{id}, /filtrar; POST /publicar/{usuarioId}; PUT /{id}; PATCH /{id}/estado; DELETE /{id}; POST /{id}/votar, /{id}/renovar Lectura pública; escritura autenticada
/oferta OfertaController GET / (paginado con filtros), /{id}; POST /publicar; PUT /{id}; DELETE /{id}; POST /{id}/spark, /{id}/drip Lectura pública; escritura autenticada
/vehiculo VehiculoController GET /, /filtrar, /{id}; POST /publicar/{usuarioId}; PUT /{id}; PATCH /{id}/estado; DELETE /{id} Lectura pública; escritura autenticada
/compra CompraController POST /; GET /{id}, /mis-compras, /mis-ventas; PATCH /{id}/confirmar-entrega, /{id}/cancelar ROLE_USER
/pago PagoController POST /crear-intent, /confirmar; GET /estado/{compraId} ROLE_USER
/envio EnvioController POST /; GET /{id}/qr; PATCH /{id}/tracking, /{id}/confirmar ROLE_USER
/devolucion DevolucionController POST /; GET /{id}; PATCH /{id}/aceptar, /{id}/rechazar, /{id}/completar ROLE_USER
/chat  ·  /ws ChatController, ChatWebSocketController GET /historial/{roomId}; POST /enviar (fallback REST); WS /app/chat (STOMP) ROLE_USER
/notificacion NotificacionController GET /mis-notificaciones; PATCH /{id}/leer; DELETE /{id} ROLE_USER
/favorito FavoritoController POST /; DELETE /{id}; GET /mis-favoritos ROLE_USER
/valoracion ValoracionController POST /; GET /vendedor/{id}; PATCH /{id}/responder ROLE_USER
/reporte ReporteController POST /; GET /mis-reportes ROLE_USER
/contrato ContratoController POST /solicitar; GET /mis-contratos; POST /pagar/{id} ROLE_USER / ROLE_EMPRESA
/newsletter NewsletterController POST /suscribir; GET /confirmar?token=...; GET /baja?token=... Pública
/soporte-chat SoporteChatController POST /mensaje; GET /historial/{sessionId} Pública
/api/admin/** 13 controladores Admin CRUD completo sobre usuarios, productos, ofertas, vehículos, compras, devoluciones, contratos, cupones, newsletter, reportes, sanciones, estadísticas, audit-log, configuración ROLE_ADMIN
/stripe/webhook StripeWebhookController POST / (receptor de eventos Stripe con verificación de firma HMAC-SHA256) Pública (verificada por firma)

Diseño de la Interfaz de Usuario

La aplicación Angular (nexus-angular-app) sigue la arquitectura de Standalone Components sin módulos NgModule. Las rutas se definen en app.routes.ts con lazy loading total mediante loadComponent(() => import(...)). Los guards authGuard y guestGuard protegen las rutas privadas y públicas respectivamente:

Módulo / carpeta Componentes principales Guard
/ (home)HomeComponent — dashboard con secciones destacadas, ofertas flash y productos recientesNinguno
/login, /registerLoginComponent, RegisterComponent — formularios con reCAPTCHA y validación reactivaguestGuard
/forgot-password, /reset-passwordForgotPasswordComponent, ResetPasswordComponent — flujo de recuperación de contraseña por emailNinguno
/verify-emailVerifyEmailComponent — verificación del OTP recibido por emailNinguno
/searchSearchComponent — búsqueda unificada con panel de filtros y resultados paginadosNinguno
/productos/:slugProductoDetailComponent — detalle del producto con galería, info del vendedor y botón de compraNinguno
/ofertas/:slugOfertaDetailComponent — detalle de oferta con votos Spark/Drip y comentariosNinguno
/vehiculos, /vehiculos/:slugVehiculosComponent, VehiculoDetailComponent — catálogo y ficha técnica de vehículosNinguno
/publicar, /publicar/oferta, /publicar/vehiculoPublishProductoComponent, PublishOfertaComponent, PublishVehiculoComponent — formularios de publicación multistepauthGuard
/checkout/:slugCheckoutComponent — flujo de compra con Stripe.js integradoauthGuard
/compras/mis-compras, /compras/:idMisComprasComponent, CompraDetailComponent — historial y detalle de comprasauthGuard
/mensajesMensajesContainerComponent — chat en tiempo real con lista de conversaciones activasauthGuard
/perfil, /perfil/:usernameMiCuentaComponent, PerfilPublicoComponent — perfil privado y públicoauthGuard / Ninguno
/configuracionConfiguracionComponent — seguridad, privacidad, notificaciones, 2FAauthGuard
/notificacionesNotificacionesComponent — centro de notificaciones in-appauthGuard
/publicidad/contratos, /publicidad/patrociniosEmpresaContratosPageComponent, MisPatrociniosPageComponent — gestión de publicidad para empresasauthGuard
/devoluciones/*NuevaDevolucionComponent, DevolucionDetailComponent — solicitud y seguimiento de devolucionesauthGuard

Los servicios Angular del directorio core/services/ encapsulan todas las llamadas HTTP al backend mediante HttpClient. El JwtInterceptor inyecta automáticamente el token Bearer en todas las peticiones internas (excluye URLs externas como las de Stripe o Google). El ErrorInterceptor gestiona de forma centralizada las respuestas de error 401 (sesión expirada), 403 (acceso denegado) y 500 (error interno).

Las dos aplicaciones Angular: diferencias y relación con el backend

Nexus tiene dos frontends Angular completamente separados que se conectan al mismo backend. No comparten código entre sí, tienen dominios distintos y sus tokens JWT sirven para cosas diferentes. Esto es deliberado: si alguien encuentra un fallo en la app pública, no puede saltarse al panel de admin.

nexus-angular-app — App de usuario

  • Para quién: cualquier persona, sin necesidad de cuenta para navegar
  • Rutas públicas: catálogo, buscador, detalle de productos, perfiles públicos
  • Rutas privadas: publicar anuncios, comprar, mensajes, devoluciones, configuración
  • Tecnología extra: Ionic 8 para UI móvil + Capacitor 8 para compilar como APK Android
  • WebSocket: sí — chat en tiempo real y notificaciones in-app
  • Roles JWT: ROLE_USER y ROLE_EMPRESA
  • Despliegue: Vercel, dominio principal de la plataforma

nexus-admin-web-app — Panel de administración

  • Para quién: solo administradores con ROLE_ADMIN
  • Rutas: todas privadas, redirige a login si no hay token admin válido
  • Módulos: dashboard, estadísticas, usuarios, moderación, reportes, devoluciones, compras, contratos, cupones, patrocinios, newsletter, notificaciones masivas, soporte de chat, audit log, categorías, seguridad y configuración global
  • Tecnología extra: solo Angular puro, sin Ionic ni Capacitor
  • WebSocket: no (el soporte de chat usa polling o conexión puntual)
  • Roles JWT: ROLE_ADMIN exclusivamente
  • Despliegue: Vercel, subdominio separado

Cómo se comunican con el backend

Las dos apps siguen exactamente el mismo patrón de comunicación: todas las peticiones van con el header Authorization: Bearer <token>, el JwtInterceptor lo inyecta automáticamente y el backend valida el token en cada petición antes de ejecutar nada. La diferencia está en qué endpoints acepta cada token:

  • Un token de ROLE_USER puede llamar a rutas como /producto, /compra, /chat. Si intenta acceder a /api/admin/**, recibe un 403.
  • Un token de ROLE_ADMIN puede acceder a todo lo anterior más /api/admin/**. En la práctica, los admins solo usan su panel y nunca interactúan con la app pública.
  • Las rutas públicas (catálogo, búsqueda, newsletter) no necesitan token. El backend las sirve sin autenticación.

El admin.service.ts del panel de administración encapsula todas las llamadas HTTP bajo el prefijo /api/admin/ en un único servicio centralizado. Esto hace que añadir nuevas funciones al panel sea mucho más simple: el componente llama al servicio, el servicio llama al backend, el backend ejecuta y devuelve. Sin lógica de negocio en el frontend.

Nota de seguridad

Por qué el admin está en un dominio separado

Tener el panel de administración en un subdominio diferente no es solo una decisión de organización: es una medida de seguridad. Cualquier política de Content-Security-Policy o configuración de CORS del panel no afecta a la app pública, y viceversa. Si la app pública sufre un ataque XSS, el atacante no puede acceder al almacenamiento del panel porque están en orígenes distintos. El backend también rechaza cualquier petición a /api/admin/** que no lleve un JWT con ROLE_ADMIN, independientemente del origen.

Implementación

Esta sección detalla la organización del código fuente, las decisiones técnicas tomadas durante el desarrollo y los flujos de implementación más representativos del sistema.

Estructura de Carpetas del Backend

nexus-backend/src/main/java/com/nexus/
  • config/ — Configuración de Spring: CORS (origenes dinámicos desde application.properties), WebSocket STOMP (broker de mensajes), caché (@EnableCaching), Cloudinary, OpenAPI/Swagger
  • controller/ — 49 controladores (48 REST + 1 WebSocket), uno por dominio funcional. Incluye AuthController, ProductoController, CompraController, ChatWebSocketController, 13 controladores Admin* y StripeWebhookController
  • dto/ — Data Transfer Objects para respuestas específicas que no coinciden directamente con las entidades (resumen de vendedor, estado de compra, etc.)
  • entity/ — 56 archivos Java: entidades JPA (Actor, Usuario, Empresa, Admin, Producto, Oferta, Vehiculo, Compra, Envio, Devolucion, ChatMensaje, Valoracion, Contrato, Cupon, Categoria, AuditLog, SparkVoto, Bloqueo, etc.) y enumeraciones (EstadoCompra, TipoOferta, CondicionProducto, TipoCombustible, TipoVehiculo, MotivoDevolucion, MotivoReporte, TipoMensaje, TipoNotificacion, etc.)
  • repository/ — Interfaces Spring Data JPA con métodos derivados del nombre y consultas JPQL con @Query para filtros complejos
  • scheduler/ — Tareas programadas con @Scheduled: gestión de caducidad de anuncios (avisos a 30, 14, 7 y 1 días) y gestión del scheduler de newsletter
  • security/JWTUtils (generación/validación de tokens, HMAC-SHA256), JWTAuthenticationFilter (filtro HTTP que intercepta todas las peticiones), WebSocketAuthInterceptor (validación del JWT en el handshake STOMP), SecurityConfiguration (reglas de autorización por ruta y rol), PasswordConfig (bean BCrypt)
  • service/ — 44 servicios de negocio: UsuarioService, ProductoService, CompraService, StripeService, StorageService (Cloudinary), EmailService (Spring Mail + Gmail SMTP), TwoFactorService/TwoFactorAuthService, SoporteAiService (Gemini/Groq), CuponValidacionService, SynonymService, etc.
  • soporte/ — Lógica del chatbot de soporte IA y escalación a agente humano

Estructura de Carpetas del Frontend de Usuario

nexus-angular-app/src/app/
  • components/ — Componentes organizados por dominio: auth/ (login, register, forgot/reset-password, verify-email), home/, marketplace/ (publish-producto, publish-oferta, publish-vehiculo, cerca-de-ti, gratis, viajes), search/, compras/ (checkout, confirmacion, mis-compras, enviar-pedido), mensajes/, notificaciones/, perfil/ (mi-cuenta, perfil-publico, configuracion), vehiculos/, ofertas/, devoluciones/, publicidad/, newsletter/, errors/
  • core/auth/JwtService (almacenamiento y validación del token en localStorage), AuthService (estado de sesión reactivo con Signals)
  • core/guards/auth-guard.ts (redirige a registro si no hay sesión activa), guest-guard.ts (redirige a home si ya hay sesión)
  • core/interceptors/jwt-interceptor.ts (inyecta el Bearer token en todas las peticiones internas; detecta si la petición va al panel admin y usa el token admin correspondiente), error-interceptor.ts (gestión centralizada de errores HTTP)
  • core/services/ — 15 servicios Angular: chat.service, compra.service, favorito.service, pago.service, notification.service, search.service, websocket.service (gestión de la conexión STOMP), toast.service, ui.service, devolucion.service, bloqueo.service, support-chat.service, cookie.service, scroll.service, guest-popup.service
  • models/ — 25 interfaces TypeScript que reflejan las entidades del backend: Usuario, Producto, Oferta, Vehiculo, Compra, Envio, Devolucion, ChatMensaje, Valoracion, Contrato, Cupon, Categoria, Notificacion, etc.
  • shared/ — Componentes reutilizables entre módulos (ProductoDetailComponent)
  • mobile/ — Componentes específicos para la app móvil Capacitor (MobileFavoritosComponent)

Estructura del Panel de Administración

nexus-admin-web-app/src/app/admin/
  • 19 módulos lazy-loaded: dashboard/, estadisticas/, usuarios/, fraude/, sanciones/, reportes/, devoluciones/, productos/, ofertas/, compras/, cupones/, vehiculos/, soporte/, notificaciones/, newsletter/, contratos/, patrocinios/, configuracion/, audit-log/, seguridad/
  • admin.routes.ts — Todas las rutas del panel envueltas en el AdminLayoutComponent como componente padre con sidebar lateral
  • admin.service.ts — Servicio central que encapsula todas las llamadas HTTP al backend bajo el prefijo /api/admin/
  • admin.models.ts — Interfaces TypeScript del dominio administrativo

Decisiones Técnicas Relevantes

DT-01

Jerarquía de herencia JOINED en JPA para los actores

La entidad abstracta Actor es la clase base para Usuario, Empresa y Admin usando la estrategia InheritanceType.JOINED. Los campos comunes (email, password, avatar, 2FA, jwtVersion, estado de cuenta, ban, stripe…) se almacenan en la tabla actor; los específicos de cada tipo, en sus propias tablas. Esta decisión simplifica la autenticación (siempre se busca en actor) y la generación de JWT (siempre hay un único punto de entrada). El método genérico JWTUtils.userLogin() detecta el tipo del actor (instanceof Admin/Empresa/Usuario) y carga la subclase correspondiente con todos sus campos.

DT-02

Invalidación global de tokens JWT mediante jwtVersion

Los JWT son stateless: una vez emitidos, el servidor no puede invalidarlos individualmente sin mantener una blacklist. Nexus resuelve esto con el campo jwtVersion (entero) en la entidad Actor. Cada JWT lleva en su payload la versión vigente en el momento de emisión. El JWTAuthenticationFilter compara esa versión con la actual en la BD. Al cambiar la contraseña, aplicar un ban o ejecutar un logout global, el backend incrementa jwtVersion y todos los tokens anteriores son rechazados automáticamente, efectuando un logout global sin blacklist.

DT-03

Optimización para el plan gratuito de Render (0.1 CPU / 512 MB RAM)

El backend está desplegado en el plan gratuito de Render. Para operar establemente con estos recursos, se aplicaron las siguientes optimizaciones en application.properties: Tomcat con máximo 15 hilos activos y 2 mínimos; pool HikariCP con máximo 3 conexiones, initialization-fail-timeout=60s para tolerar arranques lentos de la BD y keepalive-time=60s; pool de tareas async con máximo 5 hilos y cola de 25; scheduler de 1 hilo; compresión HTTP de respuestas JSON habilitada desde 1 KB; batching de inserts JPA con tamaño de lote 15; caché de segundo nivel de Hibernate desactivada. La configuración resultante permite operar con consumo de memoria controlado bajo carga moderada.

DT-04

Standalone Components y lazy loading total en Angular 21

La aplicación prescinde completamente de los NgModule tradicionales. Cada componente, directiva y pipe se declara como standalone: true e importa directamente sus dependencias. El enrutador carga cada ruta con loadComponent(() => import(...)), de modo que el bundle inicial solo contiene el código del componente de inicio; el resto se descarga bajo demanda cuando el usuario navega a esa ruta. Esto reduce drásticamente el tiempo de carga inicial (FCP) y mejora el rendimiento en dispositivos móviles.

DT-05

Modelo escrow en el sistema de pagos con Stripe

Las compras no transfieren el dinero al vendedor inmediatamente. Se usa el modelo de Payment Intent de Stripe: el comprador paga y el dinero queda pendiente de liberación. Solo cuando el comprador confirma la recepción del producto (o transcurre el plazo máximo configurado), el sistema ejecuta la transferencia definitiva al vendedor descontando la comisionNexus calculada. Este modelo protege al comprador de estafas y es la base del sistema de devoluciones: si el comprador abre una disputa, el DevolucionService puede iniciar un reembolso parcial o total directamente en Stripe.

DT-06

Sistema Spark/Drip con recalculo periódico de puntuación

Las ofertas tienen dos contadores: sparkCount (votos positivos) y dripCount (negativos). El sparkScore = sparkCount - dripCount determina el orden de aparición en el feed principal. El campo transitorio miVoto (anotado con @Transient) indica el voto del usuario actual sin persistirlo. Para evitar escrituras masivas en la BD durante picos de votación, el score se recalcula mediante un scheduler configurado en nexus.upvotes.recalculo-minutos=5. El método recalcularScore() de la entidad Oferta se invoca también en @PrePersist y @PreUpdate.

DT-07

Prevención de bucles JSON en relaciones JPA bidireccionales

Las relaciones bidireccionales entre entidades JPA (p. ej. Producto ↔ Actor, Oferta ↔ Categoria) causan bucles infinitos en la serialización Jackson. La solución implementada combina: @JsonIgnoreProperties en las anotaciones de relación (definiendo explícitamente los campos a ignorar en cada dirección), @JsonIgnore en el getter de la contraseña hasheada y el secreto TOTP, y la dependencia jackson-datatype-hibernate6 para manejar correctamente las entidades lazy de Hibernate sin deserialización forzada.

DT-08

Generación automática de códigos de envío y QR

Cuando se confirma un pago mediante el webhook de Stripe, el EnvioService genera automáticamente un código de envío único con el formato SHIP-XXXXXXXX (UUID truncado en mayúsculas). Usando la librería ZXing (com.google.zxing), se genera un código QR que codifica ese código y se convierte a imagen PNG codificada en Base64. Este QR se muestra al comprador en la confirmación de compra y al vendedor en su panel de envíos para el proceso de entrega logística.

Flujos Principales Implementados

Flujo completo de autenticación JWT con 2FA

  1. El usuario envía sus credenciales a POST /api/auth/login con token reCAPTCHA v3 generado en el cliente.
  2. El CaptchaService verifica el token contra la API de Google con un umbral mínimo de 0.5. Si falla, devuelve 400.
  3. Spring Security autentica al actor mediante AuthenticationManager con BCrypt. Si falla, devuelve 401.
  4. Si el actor tiene twoFactorEnabled = true, el backend devuelve {twoFactorRequired: true} sin emitir JWT todavía.
  5. El usuario envía el código 2FA a POST /api/auth/verify-2fa. Si es TOTP, se valida con la librería de samstevens.totp. Si es Email OTP, se compara con el código almacenado (válido 10 minutos).
  6. JWTUtils.generateTokenForUser(actor) genera un JWT firmado con HMAC-SHA256, con claims: subject (username), rol (ROLE_USER/EMPRESA/ADMIN), iat (fecha emisión), exp (86400000 ms = 24h).
  7. El frontend almacena el JWT en localStorage y el JwtInterceptor lo incluye en Authorization: Bearer <token> en todas las peticiones HTTP internas.
  8. El JWTAuthenticationFilter del backend extrae el token, valida la firma y la jwtVersion, y establece el SecurityContextHolder para cada petición.

Flujo de compra con Stripe Payment Intent

  1. El usuario accede a /checkout/:slug y selecciona método de entrega y dirección.
  2. El frontend llama a POST /pago/crear-intent con el ID del producto y datos del pedido.
  3. El backend calcula el precio final (producto + envío - descuento del cupón si aplica) y crea un PaymentIntent en Stripe con el importe en céntimos de euro.
  4. El backend devuelve el clientSecret del PaymentIntent. El frontend inicializa el formulario de Stripe.js (@stripe/stripe-js) con ese secreto.
  5. El usuario introduce sus datos de pago en el formulario seguro de Stripe y confirma el pago.
  6. Stripe procesa el pago y envía el evento payment_intent.succeeded al webhook POST /stripe/webhook. La firma del evento se verifica con stripe.webhook.secret.
  7. El StripeWebhookController llama a CompraService.crearCompra() que crea la entidad Compra en estado PAGADO y la entidad Envio con el código QR.
  8. El producto pasa a estado RESERVADO. Se envían notificaciones in-app y por email al comprador y al vendedor.
  9. El vendedor sube el número de seguimiento en PATCH /envio/{id}/tracking. El estado del envío pasa a EN_TRANSITO.
  10. El comprador confirma la recepción en PATCH /compra/{id}/confirmar-entrega. La Compra pasa a COMPLETADA y el pago se libera. El producto pasa a VENDIDO.

Flujo de mensajería en tiempo real con WebSocket STOMP

  1. El frontend crea una conexión WebSocket en el endpoint /ws usando SockJS como transporte. Se pasa el JWT en la query string o cabecera de la sesión.
  2. El WebSocketAuthInterceptor de Spring intercepta el mensaje CONNECT del protocolo STOMP, extrae y valida el JWT, y registra el usuario autenticado en el contexto de la sesión WebSocket.
  3. El cliente se suscribe al canal personal /user/{username}/queue/messages para recibir sus mensajes entrantes.
  4. Para enviar un mensaje, el cliente publica en /app/chat con el payload: {roomId, texto, tipo, receptorId, productoId (opcional)}.
  5. El ChatWebSocketController persiste el ChatMensaje en la BD, detecta si el receptor está conectado y envía el mensaje a su canal personal mediante SimpMessagingTemplate.
  6. Si el receptor no está conectado, el mensaje queda en BD con recibido = false. Cuando se conecte, el cliente solicita el historial de mensajes no recibidos vía GET /chat/historial/{roomId} y los marca como recibidos.

Ciclo de vida de anuncios con caducidad automática

  1. Al publicar un producto, oferta o vehículo, el hook @PrePersist calcula automáticamente la fechaCaducidad = fechaPublicacion + 180 días.
  2. El scheduler AnuncioCaducidadService comprueba periódicamente qué anuncios están próximos a caducar.
  3. En los hitos de 30, 14, 7 y 1 días restantes, envía una notificación in-app al vendedor y registra el hito en ultimoAvisoCaducidadDias para no duplicar el aviso.
  4. Cuando la fechaCaducidad es alcanzada, el estado del anuncio cambia automáticamente a EXPIRADO.
  5. El anuncio expirado permanece visible durante 14 días adicionales (mostrándose como expirado), tras los cuales se oculta del catálogo público. El vendedor puede reactivar el anuncio (POST /producto/{id}/renovar) para reiniciar el contador de 180 días.

Pruebas y Evaluación

La estrategia de pruebas del proyecto combina pruebas funcionales manuales sobre el sistema desplegado, pruebas de integración de los servicios externos (Stripe en modo test, Cloudinary, WebSocket) y pruebas exploratorias por parte del equipo. Dado que el proyecto es un Trabajo de Fin de Grado con un equipo de tres personas, el foco se puso en cobertura funcional real frente a cobertura de código automatizada.

Pruebas funcionales

Se realizaron pruebas manuales sobre los flujos principales del sistema en el entorno de producción real (Render + Vercel), validando el comportamiento de cada módulo funcional de extremo a extremo. A continuación se documenta visualmente cada flujo probado, cubriendo la aplicación web de usuario, la aplicación móvil Android (compilada con Capacitor 8) y el panel de administración.

Autenticación

Registro, Verificación e Inicio de Sesión — Web

Formulario de registro con email y contraseña

El visitante accede al formulario de registro e introduce username, email y contraseña. El sistema valida la fortaleza de la contraseña en tiempo real y protege el envío con reCAPTCHA v3. Al completar el formulario, el backend crea el usuario con la contraseña hasheada en BCrypt y envía un código OTP de 6 dígitos al email.

Registro normal con email y contraseña

Verificación del email mediante código OTP

Tras el registro, el usuario recibe un código OTP de 6 dígitos con una validez de 30 minutos. El sistema valida el código en el backend y activa la cuenta para continuar con el wizard de onboarding. Se muestran ambas pantallas: la de solicitud de código y la de introducción del OTP recibido.

Pantalla de verificación de registro

Pantalla de verificación

Código OTP de verificación introducido

Introducción del código OTP

Wizard de onboarding — Elección de identidad, seguridad y estilo

Una vez verificada la cuenta, el sistema guía al usuario por un wizard de configuración inicial: elección de identidad personal (foto de perfil y datos), configuración de seguridad (activación opcional de 2FA) y selección de estilo visual de la plataforma. Estos pasos son obligatorios antes del primer acceso completo.

Onboarding: identidad personal

Identidad personal

Onboarding: seguridad y 2FA

Configuración de seguridad

Onboarding: elección de estilo

Estilo predeterminado

Inicio de sesión con email y contraseña (con validación 2FA)

El usuario introduce sus credenciales en el formulario de login. El backend valida el token reCAPTCHA v3 y las credenciales con BCrypt. Si el actor tiene 2FA activado, el sistema devuelve twoFactorRequired: true y solicita el código TOTP antes de emitir el JWT de acceso.

Formulario de inicio de sesión

Formulario de login

Login con validación 2FA

Validación 2FA TOTP

Inicio de sesión con Google OAuth 2.0

El usuario puede autenticarse directamente con su cuenta de Google. El backend verifica el ID token de Google con la librería de Google API Client, crea o recupera el actor asociado al googleId y emite un JWT propio de Nexus. El proceso no requiere contraseña ni verificación OTP adicional.

Login con Google OAuth

Inicio con Google

Selección de cuenta Google

Selección de cuenta Google

Recuperación de contraseña mediante email

El usuario solicita la recuperación de contraseña introduciendo su email. El backend genera un token UUID con caducidad de 15 minutos almacenado en la entidad Actor y envía un enlace al email. El usuario accede al enlace e introduce su nueva contraseña, que se hashea con BCrypt y se actualiza. El campo jwtVersion se incrementa para invalidar todos los tokens activos.

Solicitud de recuperación de contraseña

Solicitud de recuperación

Formulario de nueva contraseña

Introducción de nueva contraseña

Navegación

Pantalla Principal y Navegación General — Web

Pantalla de inicio en modo visitante (sin sesión)

El visitante no registrado accede a la pantalla de inicio de Nexus. Puede navegar el catálogo, explorar categorías y ver ofertas públicas sin necesidad de autenticación. El sistema muestra un popup sugiriendo el registro al intentar interactuar con funcionalidades que requieren cuenta.

Pantalla de inicio modo visitante

Secciones de la pantalla principal

La pantalla principal carga con múltiples secciones: chollos del día (ofertas más votadas), sección "Explora por categoría" con las categorías activas, "Lo último en Nexus" con los anuncios más recientes y el ranking de top chollos flash con ofertas con fecha límite. Cada sección se renderiza de forma independiente y lazy-loaded desde el backend.

Sección chollos del día

Chollos del día

Explorar por categoría

Explorar por categoría

Lo último en Nexus

Lo último en Nexus

Top chollos flash

Top chollos flash

Menús desplegables: categorías y perfil de usuario

El header de la aplicación incluye dos desplegables principales: el menú de categorías (accesible desde el icono de navegación lateral), que lista todas las categorías activas con sus subcategorías y colores; y el desplegable del perfil de usuario, que muestra accesos rápidos a Mi Cuenta, Mis Compras, Mis Ventas, configuración y cierre de sesión.

Desplegable de categorías

Menú de categorías

Desplegable de perfil de usuario

Menú del perfil

Centro de notificaciones in-app

El usuario accede al centro de notificaciones desde el icono de la campana en el header. El sistema muestra las notificaciones in-app ordenadas por fecha, diferenciando las leídas de las no leídas. Cada notificación incluye un enlace directo al recurso relacionado (compra, mensaje, anuncio, etc.).

Centro de notificaciones in-app
Catálogo

Exploración de Categorías, Filtros y Secciones Especiales — Web

Categoría de coches y vista de vehículos

El usuario navega a la categoría de coches desde el menú principal. El listado muestra los vehículos disponibles con sus datos principales (marca, modelo, año, kilómetros, precio y ubicación). Los filtros de búsqueda permiten acotar por tipo de vehículo, combustible, precio y otros parámetros técnicos específicos del catálogo de vehículos.

Categoría de coches

Categoría coches

Vista general de vehículos

Vista general de vehículos

Sección "Cerca de ti" — Filtro geográfico por radar

La sección "Cerca de ti" usa la geolocalización del dispositivo para mostrar productos y vehículos en un radio configurable. Se prueba con dos radios distintos: 50km (muestra 2 coches encontrados) y 10km (muestra 1 coche). El backend convierte las coordenadas GPS en un bounding box lat/lon con trigonometría esférica para filtrar los anuncios geolocalizados.

Cerca de ti: 50km con 2 coches

Radio 50km — 2 resultados

Cerca de ti: 10km con 1 coche

Radio 10km — 1 resultado

Secciones especiales: Ofertas Flash, Gratis, Viajes y Favoritos

La plataforma incluye secciones especializadas: "Ofertas Flash" (chollos con fecha de caducidad próxima y unidades limitadas), "Gratis" (productos con tipo de oferta DONACION), "Viajes" (categoría específica de viajes y experiencias) y "Favoritos" (anuncios marcados por el usuario autenticado). Cada sección se alimenta de endpoints específicos del backend con filtros precalculados.

Pantalla de ofertas flash

Ofertas flash

Pantalla de productos gratis

Productos gratuitos

Pantalla de viajes

Viajes

Pantalla de favoritos

Favoritos

Catálogo

Detalle de Producto, Vehículo y Oferta — Web

Página de detalle de producto de segunda mano

El usuario accede al detalle de un producto publicado por otro usuario. La página muestra la galería de imágenes, el precio, la condición, la descripción completa, la ubicación, los datos del vendedor con su puntuación de reputación y los botones de acción: "Comprar", "Contactar" y "Añadir a favoritos". El botón de compra está deshabilitado para el propio publicador.

Detalle de producto - parte superior

Detalle del producto (cabecera)

Detalle de producto - parte inferior

Descripción y vendedor

Página de detalle de vehículo

El detalle de vehículo presenta la ficha técnica completa: marca, modelo, año de fabricación, kilometraje, tipo de combustible, caja de cambios, potencia, cilindrada, número de puertas y plazas, color, estado de ITV, garantía y lista de extras. La galería soporta múltiples imágenes. El sistema muestra el precio y la ubicación con mapa embebido.

Detalle de vehículo - ficha técnica

Ficha técnica del vehículo

Detalle de vehículo - galería y precio

Galería y datos del vendedor

Página de detalle de oferta/chollo

El detalle de oferta muestra el precio original y el precio de oferta con el porcentaje de descuento calculado, el badge automático asignado (NUEVA, CHOLLAZO, PORCENTAJE, GRATUITA o EXPIRA_HOY), el código de descuento si lo tiene, el enlace a la tienda externa, los contadores Spark/Drip con el botón de votación activo para usuarios autenticados y la sección de comentarios de la comunidad.

Detalle de oferta - parte superior

Detalle de oferta (cabecera)

Detalle de oferta - votos y comentarios

Votos Spark/Drip y comunidad

Publicación

Publicación de Productos, Vehículos y Ofertas — Web

Pantalla de selección de tipo de publicación

El usuario autenticado accede a la pantalla de publicación desde el botón "Publicar" del header. Se le presentan tres opciones: publicar un producto de segunda mano, publicar un vehículo o publicar una oferta/chollo. Cada opción redirige al formulario de publicación multistep correspondiente.

Pantalla de selección de tipo de publicación

Flujo completo de publicación de producto de segunda mano

El formulario multistep guía al usuario por cuatro pasos: (1) detalles básicos (título, categoría, condición, tipo de oferta), (2) fotos y descripción (subida de hasta 6 imágenes a Cloudinary con previalización en tiempo real), (3) precio y ubicación (precio, si admite envío, peso y dirección de recogida), (4) revisión final antes de publicar. Al confirmar, el backend crea el producto con fechaCaducidad a 180 días y lo muestra en el catálogo.

Detalles básicos del producto

Paso 1: Detalles básicos

Fotos y descripción del producto

Paso 2: Fotos y descripción

Precio y ubicación del producto

Paso 3: Precio y ubicación

Revisión final antes de publicar

Paso 4: Revisión final

Revisar y publicar producto

Confirmación de publicación

Producto publicado correctamente

Producto publicado

Flujo completo de publicación de vehículo

El formulario de vehículo incluye pasos específicos para el catálogo de automoción: tipo de vehículo y datos básicos (marca, modelo, año), detalles técnicos (combustible, cambio, potencia, cilindrada, puertas, plazas, color, ITV, garantía y extras), estado y precio de venta, y subida de fotos con descripción. El vehículo publicado aparece en el catálogo de vehículos.

Inicio de publicación de vehículo

Inicio: tipo de vehículo

Datos básicos del vehículo

Datos básicos

Detalles técnicos del vehículo

Detalles técnicos

Estado y precio del vehículo

Estado y precio

Fotos y descripción del vehículo

Fotos y descripción

Flujo completo de publicación de oferta/chollo

El formulario de oferta permite publicar chollos con precios de tiendas externas. El usuario completa: título, tienda, precio original y precio de oferta (el sistema calcula el porcentaje de descuento automáticamente), categoría, código de descuento, URL de la oferta externa, imagen subida a Cloudinary y opcionalmente marcarlo como flash sale con fecha de fin y número de unidades limitadas.

Selección de tipo de oferta

Selección de tipo

Formulario de oferta - paso 1

Datos de la oferta

Formulario de oferta - paso 2

Detalles adicionales

Oferta publicada correctamente

Oferta publicada

Perfil

Gestión del Perfil y Configuración de Cuenta — Web

Resumen del perfil y estadísticas de usuario

La página de perfil privado muestra el resumen de la actividad del usuario: reputación (puntuación 0-5 estrellas calculada a partir de las valoraciones de compradores), total de ventas, productos activos, favoritos guardados y las valoraciones recibidas. La sección de estadísticas proporciona métricas de rendimiento de los anuncios publicados.

Resumen del perfil de usuario

Resumen del perfil

Estadísticas del perfil

Estadísticas

Secciones del perfil: productos, vehículos y ofertas publicadas

El perfil agrupa en secciones diferenciadas todos los anuncios del usuario: "Mis productos" (con estado vacío y con artículos publicados), "Mis vehículos" y "Mis ofertas". Cada sección muestra el estado actual de cada anuncio y permite acceder al detalle o editar directamente desde el listado.

Mis productos sin anuncios

Mis productos (vacío)

Mis productos con anuncios

Mis productos (con anuncios)

Mis vehículos

Mis vehículos

Mis ofertas sin anuncios

Mis ofertas (vacío)

Mis ofertas con anuncios

Mis ofertas (con anuncios)

Historial de compras, ventas, buzón y ayuda

El perfil incluye secciones para "Mis compras" (historial de compras realizadas como comprador), "Mis ventas" (historial como vendedor), "Buzón" (mensajes recibidos y conversaciones activas) y "Ayuda" (acceso al centro de soporte con el asistente IA). Estas secciones están disponibles exclusivamente para usuarios autenticados.

Mis compras

Mis compras

Mis ventas

Mis ventas

Buzón de mensajes

Buzón

Ayuda - parte 1

Ayuda (1)

Ayuda - parte 2

Ayuda (2)

Sección de publicidad y métodos de pago en el perfil

El perfil incluye acceso a "Publicidad" (para usuarios con cuenta de empresa, donde se gestionan los contratos activos y patrocinios), y "Métodos de pago" (gestión de las tarjetas y métodos de pago registrados en Stripe para acelerar futuras compras).

Sección de publicidad en el perfil

Publicidad

Métodos de pago

Métodos de pago

Configuración de Mi Cuenta: datos, privacidad, seguridad y notificaciones

La sección "Mi Cuenta" agrupa toda la configuración del usuario en pestañas: datos del perfil (edición de nombre, avatar, ubicación y datos personales), datos y privacidad (configuración de visibilidad de la cuenta, RGPD y cookies), notificaciones (preferencias de qué eventos generan notificación), privacidad (gestión del consentimiento), seguridad (cambio de contraseña y gestión de 2FA) y tipo de cuenta (conversión a cuenta empresa).

Mi cuenta: datos del perfil

Datos del perfil

Mi cuenta: datos y privacidad

Datos y privacidad

Mi cuenta: notificaciones

Notificaciones

Mi cuenta: privacidad

Privacidad

Mi cuenta: seguridad y 2FA

Seguridad y 2FA

Mi cuenta: tipo de cuenta

Tipo de cuenta

Compra

Flujo Completo de Compra con Stripe — PC (Web)

Búsqueda y vista de producto a comprar

El comprador localiza el producto mediante el buscador unificado o navegando por el catálogo. Al encontrar el producto deseado, accede a su página de detalle donde puede consultar la descripción, estado, fotos, precio y datos del vendedor antes de iniciar el proceso de compra.

Búsqueda del producto en PC

Búsqueda del producto

Vista del producto en PC

Vista de detalle del producto

Formulario de datos del pedido y selección de envío

El comprador introduce sus datos personales para el pedido (nombre, dirección de entrega, teléfono de contacto) y selecciona el método de entrega: envío postal con cálculo automático del coste según el peso del producto, o recogida en mano en la ubicación del vendedor. El sistema calcula el precio total incluyendo los gastos de envío antes de proceder al pago.

Datos personales del pedido

Datos del pedido

Método de envío paso 1

Método de envío (1)

Método de envío paso 2

Método de envío (2)

Pago con Stripe y confirmación de compra

El sistema crea un Stripe Payment Intent y devuelve el clientSecret. El formulario de pago de Stripe.js se inicializa con ese secreto y el comprador introduce sus datos de tarjeta en el elemento seguro de Stripe. Una vez procesado el pago, el webhook payment_intent.succeeded confirma la transacción y el sistema crea la compra en estado PAGADO, generando el código QR de envío.

Formulario de pago Stripe en PC

Formulario de pago Stripe

Confirmación de pago exitoso

Confirmación de pago

Seguimiento del pedido y notificaciones

Tras la confirmación del pago, el comprador puede consultar el estado de su pedido en "Mis compras". El detalle del pedido muestra el código de envío, el estado actual (PAGADO → ENVIADO → ENTREGADO → COMPLETADA), el número de seguimiento del transportista cuando el vendedor lo registra, y las fechas de cada hito. El comprador y el vendedor reciben notificaciones in-app y por correo electrónico en cada cambio de estado. El campo "movimientos" registra el historial de estados del pedido.

Detalles del pedido

Detalle del pedido

Detalles del pedido - información adicional

Información adicional del pedido

Historial de movimientos del pedido

Historial de movimientos

Notificación in-app de compra

Notificación in-app

Correos transaccionales de confirmación y detalle de pago

El sistema envía automáticamente correos transaccionales vía Gmail SMTP para las compras completadas: un email de confirmación de compra con el resumen del pedido y un email con el detalle del cargo de Stripe. En caso de cancelación o reembolso, se envía un email de notificación de la devolución.

Gmail: confirmación de compra

Email confirmación de compra

Gmail: detalle de pago

Email detalle del pago

Gmail: cancelación y reembolso

Email cancelación/reembolso

Sincronización entre dispositivos: compra desde móvil visible en PC

Se valida la sincronización entre dispositivos: una compra realizada desde la app móvil aparece correctamente en el historial de pedidos de la aplicación web de PC, confirmando que el backend gestiona el estado de forma centralizada y consistente independientemente del cliente que inició la transacción.

Compra Móvil

Flujo Completo de Compra con Stripe — Aplicación Móvil Android

Búsqueda y compra de producto en la app móvil

Se prueba el flujo completo de compra en la app Android compilada con Capacitor 8. El comprador busca el producto, accede al detalle, inicia el proceso de compra, introduce sus datos de entrega, selecciona el método de envío y completa el pago con el formulario de Stripe.js adaptado a pantalla móvil. Las 14 capturas documentan cada paso del flujo desde la búsqueda hasta el seguimiento del pedido.

Búsqueda de producto en móvil

Búsqueda

Vista del producto en móvil

Vista del producto

Página de compra en móvil

Iniciar compra

Datos del pedido en móvil

Datos del pedido

Selección de envío en móvil (1)

Envío (1)

Selección de envío en móvil (2)

Envío (2)

Datos de pago Stripe en móvil

Pago Stripe

Confirmación de pago en móvil

Confirmación

Detalle confirmación de pago en móvil

Detalle confirmación

Seguimiento del pedido en móvil (1)

Seguimiento (1)

Seguimiento del pedido en móvil (2)

Seguimiento (2)

Notificación in-app de compra en móvil

Notificación

Gmail: confirmación de compra en móvil

Email confirmación

Gmail: detalle de pago en móvil

Email detalle pago

Admin

Gestión de Compras y Reembolsos — Panel de Administración

Listado de compras, filtrado y proceso de reembolso desde el panel

El administrador accede al módulo de compras del panel, donde puede consultar todas las transacciones del sistema filtradas por estado (pendiente, pagado, enviado, entregado, cancelado). Desde el detalle de una compra, puede iniciar un reembolso directamente en Stripe mediante el modal de reembolso, que procesa la devolución del importe al comprador y actualiza el estado de la compra a REEMBOLSADA.

Pantalla de compras en el panel admin

Listado de compras

Filtro: compras pagadas

Filtro por estado PAGADO

Modal de cancelación y reembolso

Modal de reembolso/cancelación

Mensajería

Mensajería en Tiempo Real con WebSocket STOMP — PC (Web)

Pantalla de mensajes e inicio de conversación desde el detalle del producto

El comprador interesado en un producto puede contactar al vendedor directamente desde el botón "Contactar" en la página de detalle del producto. Esto abre una conversación en el sistema de mensajería asociada al roomId calculado de forma determinista a partir de los IDs de ambos participantes y el ID del producto. La pantalla de mensajes lista todas las conversaciones activas del usuario autenticado.

Botón de contactar en el producto

Contactar desde el producto

Pantalla de mensajes en PC

Pantalla de mensajes

Envío y recepción de mensajes de texto e imágenes

La conversación soporta múltiples tipos de mensaje: texto plano, imágenes (subidas a Cloudinary), vídeo, audio y GIFs. Los mensajes se entregan en tiempo real mediante el protocolo WebSocket STOMP. El sistema marca los mensajes como leídos/recibidos y mantiene el historial completo en la base de datos. Se valida la entrega bidireccional entre el comprador y el vendedor.

Envío de mensaje de texto

Envío de mensaje

Recepción de mensaje de texto

Recepción de mensaje

Envío de imágenes en el chat

Envío de imágenes

Recepción de imágenes en el chat

Recepción de imágenes

Propuesta de precio y negociación en el chat

El sistema de mensajería incluye el tipo de mensaje OFERTA_PRECIO, que permite al comprador proponer un precio personalizado al vendedor directamente en la conversación. El mensaje de propuesta muestra el precio propuesto y dos botones de acción (aceptar/rechazar) para el vendedor. Al aceptar, el sistema actualiza el estado de la propuesta y notifica al comprador para que proceda a la compra al precio negociado.

Envío de propuesta de precio

Propuesta de precio enviada

Recepción y respuesta a propuesta de precio

Recepción de propuesta

Reservas

Reservas de Productos y Edición de Anuncios — PC (Web)

Flujo de reserva de un producto

El vendedor puede marcar un producto como reservado para un comprador específico. Desde la lista de productos disponibles, el vendedor accede al popup de reserva, selecciona al comprador y confirma. El producto pasa a estado RESERVADO y aparece marcado con la etiqueta correspondiente en el catálogo. El filtro de la vista de admin de productos también puede filtrar por estado reservado.

Listado de productos con opción de reservar

Productos disponibles

Popup de confirmación de reserva

Popup de reserva

Producto marcado como reservado

Producto reservado

Editor de anuncios publicados — PC

El vendedor puede editar sus anuncios publicados en cualquier momento desde la página de detalle del producto. El editor permite modificar el título, descripción, precio, categoría, condición, imágenes y ubicación. Los cambios se sincronizan inmediatamente con el catálogo. Se prueba el flujo completo de edición con múltiples pasos.

Botones de acción y acceso al editor

Acceso al editor de anuncio

Editor de anuncio - paso 1

Edición paso 1

Editor de anuncio - paso 2

Edición paso 2

Editor de anuncio - paso 3

Edición paso 3

Editor de anuncio - paso 4

Edición paso 4

Mensajería Móvil

Mensajería y Edición de Anuncios — Aplicación Móvil Android

Chat y edición de anuncios en la app móvil

Se validan en la app móvil Android las funcionalidades de mensajería (contactar desde el producto, pantalla de mensajes, recepción de mensajes en tiempo real vía WebSocket) y el editor de anuncios (acceso al editor desde el listado de publicaciones propias, modificación de datos en múltiples pasos y guardado de cambios). Las 8 capturas documentan el flujo completo en pantalla móvil.

Contactar desde producto en móvil

Contactar (móvil)

Pantalla de mensajes en móvil

Mensajes

Recepción de mensaje en móvil

Recepción

Editar anuncio en móvil

Editar anuncio

Edición anuncio paso 1 en móvil

Edición (1)

Edición anuncio paso 2 en móvil

Edición (2)

Edición anuncio paso 3 en móvil

Edición (3)

Edición anuncio paso 4 en móvil

Edición (4)

Publicidad

Sistema de Publicidad y Patrocinios con Stripe Checkout — Web

Solicitud de patrocinio y gestión de contratos

Una empresa autenticada accede al módulo de publicidad para solicitar un patrocinio (BANNER o PUBLICACION). El proceso consta de tres pasos: selección del tipo de contrato y el ítem a patrocinar, especificación de los días de duración y revisión de la solicitud antes de enviarla. La solicitud queda en estado DRAFT hasta que un administrador la revise y establezca el precio.

Publicidad: selección de tipo

Paso 1: Tipo de contrato

Publicidad: duración y detalles

Paso 2: Duración

Publicidad: revisión y envío

Paso 3: Revisión

Mis solicitudes de publicidad

Mis solicitudes

Pago de patrocinio con Stripe Checkout

Una vez que el administrador aprueba la solicitud y establece el precio, la empresa recibe una notificación y puede proceder al pago. El sistema genera una Stripe Checkout Session y redirige al portal de Stripe para el pago. Tras el pago exitoso, el webhook checkout.session.completed activa el contrato y el ítem aparece con la etiqueta "Patrocinado" en el catálogo.

Pagar patrocinio - paso 1

Pago patrocinio (1)

Pagar patrocinio - paso 2

Pago patrocinio (2)

Pagar patrocinio - paso 3: Stripe Checkout

Stripe Checkout

Soporte IA

Asistente de Soporte con IA y Escalación a Agente Humano — Web

Chat de soporte con IA, escalación a humano y cierre de sesión

El sistema de soporte integra un chatbot basado en Google Gemini 1.5-flash (con Groq como alternativa). El usuario inicia una sesión de soporte, formula su consulta y el asistente responde de forma contextualizada. Si la consulta supera las capacidades del bot, el sistema escala automáticamente a un agente humano, que continúa la conversación desde el panel de administración. Al resolver la incidencia, el agente puede cerrar la sesión y el sistema solicita una valoración de la atención recibida.

Chat de soporte con IA

Soporte con IA

Escalación a agente humano

Escalación a agente humano

Chat de soporte finalizado

Sesión de soporte finalizada

Móvil Android

Pruebas Generales — Aplicación Android (Ionic + Capacitor 8)

Registro y verificación de cuenta en app móvil

Se valida el flujo completo de registro en la versión móvil Android: formulario de registro (dos páginas de datos), verificación OTP por email, y wizard de onboarding (identidad personal, configuración de seguridad y selección de estilo). La experiencia es idéntica a la web pero adaptada a pantalla móvil con los componentes de Ionic 8.

Registro móvil paso 1

Registro (1)

Registro móvil paso 2

Registro (2)

Verificación OTP móvil

Verificación OTP

Onboarding: identidad personal en móvil

Identidad personal

Onboarding: seguridad en móvil

Seguridad

Onboarding: estilo en móvil

Estilo

Inicio de sesión normal y con Google en app móvil

Se prueba el inicio de sesión con email/contraseña (incluyendo el paso de 2FA si está activado) y con Google OAuth en la versión móvil. El flujo de recuperación de contraseña también se valida en dispositivo móvil.

Login normal en móvil

Login normal

Login normal móvil - validación 2FA

2FA móvil

Login con Google en móvil

Login Google

Selección cuenta Google en móvil

Cuenta Google

Recuperar contraseña en móvil

Recuperar contraseña

Nueva contraseña en móvil

Nueva contraseña

Navegación principal, categorías y notificaciones en app móvil

Se valida la navegación principal de la app móvil: pantalla de inicio en modo visitante, menú lateral de categorías (sidebar desplegable), acceso a la categoría de coches, sección de favoritos y centro de notificaciones. Los componentes de Ionic 8 adaptan la navegación a los patrones nativos de Android.

Inicio móvil visitante

Inicio (visitante)

Menú categorías en móvil

Categorías

Categoría coches en móvil

Cat. coches

Favoritos en móvil

Favoritos

Notificaciones en móvil

Notificaciones

Detalle de vehículo en app móvil

El detalle de vehículo en la app móvil muestra la galería de imágenes con swipe horizontal, la ficha técnica completa en un layout adaptado a pantalla pequeña, y los botones de acción de contactar con el vendedor o añadir a favoritos.

Detalle coche en móvil

Detalle coche

Detalle coche móvil - ficha técnica

Ficha técnica

Publicación de productos, vehículos y ofertas en app móvil

Se valida el flujo multistep de publicación en la versión móvil para los tres tipos de anuncio: artículo de segunda mano (con subida de fotos desde la cámara o galería mediante la API de Capacitor), vehículo (con todos los campos técnicos) y oferta/chollo. Se documentan todos los pasos de cada formulario hasta la confirmación de publicación exitosa.

Selección tipo publicación en móvil

Publicar

Publicar artículo en móvil

Artículo

Fotos y descripción artículo móvil (1)

Fotos/desc. (1)

Fotos y descripción artículo móvil (2)

Fotos/desc. (2)

Precio y ubicación anuncio móvil (1)

Precio/ubic. (1)

Precio y ubicación anuncio móvil (2)

Precio/ubic. (2)

Revisión final anuncio móvil

Revisión final

Revisión final anuncio móvil (2)

Revisión (2)

Publicar coche: revisar en móvil

Revisar coche

Producto publicado en móvil

Publicado

Publicar vehículo en móvil

Vehículo

Publicar vehículo móvil paso 1

Vehículo (1)

Publicar vehículo móvil paso 2

Vehículo (2)

Fotos vehículo móvil

Fotos vehículo

Estado y precio vehículo móvil

Estado/precio

Publicar oferta móvil paso 1

Oferta (1)

Publicar oferta móvil paso 2

Oferta (2)

Publicar oferta móvil paso 3

Oferta (3)

Oferta publicada en móvil

Oferta publicada

Perfil de usuario y secciones de gestión en app móvil

Se validan todas las secciones del perfil en la versión móvil: información del perfil, edición de datos personales, "Mis cosas" (productos, vehículos y ofertas propias con y sin anuncios), historial de compras, envíos, valoraciones recibidas y acceso a los ajustes de cuenta (datos y privacidad, tipo de cuenta, cookies, notificaciones, privacidad y seguridad).

Perfil en móvil

Perfil

Información del perfil en móvil

Info perfil

Edición del perfil en móvil

Editar perfil

Mis cosas en móvil

Mis cosas

Mis cosas con producto en móvil

Mis cosas (con producto)

Mis productos móvil

Mis productos

Mis vehículos en móvil

Mis vehículos

Mis ofertas en móvil

Mis ofertas

Mis compras en móvil

Mis compras

Mis envíos en móvil

Mis envíos

Valoraciones en móvil

Valoraciones

Ajustes: datos y privacidad en móvil

Datos y privacidad

Ajustes: tipo de cuenta en móvil

Tipo de cuenta

Ajustes: cookies en móvil

Cookies

Ajustes: notificaciones en móvil

Notificaciones

Ajustes: privacidad en móvil

Privacidad

Ajustes: seguridad en móvil

Seguridad

Admin

Panel de Administración — Autenticación y Configuración de Seguridad 2FA

Login del panel de administración con 2FA

El administrador accede al panel de administración en su dominio separado mediante un formulario de login exclusivo. Al introducir las credenciales correctas, si el administrador tiene 2FA activado, el sistema solicita el código TOTP antes de emitir el JWT con ROLE_ADMIN. El backend rechaza cualquier token sin este rol en todos los endpoints bajo /api/admin/**.

Login del panel de administración

Formulario de login admin

Login admin con 2FA activado

Login con 2FA TOTP

Configuración del 2FA en el panel de administración

El administrador puede activar o desactivar la autenticación de dos factores TOTP desde la configuración de seguridad de su cuenta. Al activarlo, el backend genera un secreto TOTP, crea el código QR con ZXing en base64 y lo muestra para escanearlo con una app autenticadora (Google Authenticator, Authy). Una vez escaneado y verificado el primer código, el 2FA queda activado y se requiere en cada login.

Código QR para configurar 2FA admin

QR para app autenticadora

2FA activado en admin

2FA activado

2FA desactivado en admin

2FA desactivado

Admin

Panel de Administración — Dashboard y Estadísticas en Tiempo Real

Dashboard principal del panel de administración

El dashboard del panel muestra un resumen en tiempo real del estado de la plataforma: número de usuarios registrados, productos activos, transacciones del día, reportes pendientes de revisión, patrocinios en revisión y últimas actividades. Los datos se actualizan automáticamente y permiten al administrador detectar incidencias rápidamente sin tener que navegar por cada módulo.

Dashboard del panel de administración

Estadísticas en tiempo real — Tres vistas de métricas

El módulo de estadísticas del panel de administración proporciona métricas detalladas en tiempo real sobre el uso de la plataforma: usuarios activos, transacciones por período, evolución de publicaciones, distribución por categorías, métricas de la comunidad (votos Spark/Drip) y rendimiento del sistema. Las tres vistas documentan diferentes aspectos del dashboard de estadísticas.

Estadísticas en tiempo real - vista 1

Estadísticas (1)

Estadísticas en tiempo real - vista 2

Estadísticas (2)

Estadísticas en tiempo real - vista 3

Estadísticas (3)

Admin

Panel de Administración — Gestión de Usuarios, Sanciones y Fraude

Listado y detalle de usuarios registrados

El módulo de usuarios muestra todos los actores registrados en el sistema con sus datos principales: username, email, rol, estado de la cuenta (activo, baneado, suspendido, eliminado), fecha de registro y reputación. El administrador puede buscar y filtrar usuarios, acceder al detalle completo de cada uno y ver su historial de actividad, anuncios publicados, compras y reportes asociados.

Lista de usuarios en el panel admin

Listado de usuarios

Detalle de usuario en el panel admin

Detalle de usuario

Sistema de sanciones, exportación a CSV y gestión de fraude

El módulo de sanciones permite aplicar bans permanentes (baneado = true), suspensiones temporales (suspendidoHasta) o marcado de fraude (flagFraude) a usuarios que infrinjan las normas. Cada sanción incrementa el campo jwtVersion del actor, invalidando todos sus tokens activos de forma inmediata. El historial de sanciones es exportable a CSV, compatible con Google Sheets para análisis externo. El módulo de fraude permite gestionar las alertas de fraude detectadas.

Módulo de sanciones

Sanciones

CSV de sanciones importado en Google Sheets

CSV exportado a Sheets

Módulo de fraude

Fraude

Admin

Panel de Administración — Moderación de Productos, Vehículos, Ofertas y Reportes

Gestión de productos en el panel de administración

El módulo de productos del panel muestra todos los productos publicados en la plataforma con filtros por estado, categoría, precio y usuario. El administrador puede acceder al detalle completo de cada producto (incluyendo las imágenes de Cloudinary), cambiar su estado, eliminar contenido inapropiado y filtrar por estados específicos como RESERVADO para gestionar el inventario activo.

Listado de productos en admin

Listado de productos

Productos filtrados por estado reservado

Filtro: estado RESERVADO

Detalle de producto en admin (1)

Detalle del producto (1)

Detalle de producto en admin (2)

Detalle del producto (2)

Gestión de ofertas y creación directa desde el panel

El módulo de ofertas permite al administrador revisar todas las ofertas publicadas, acceder a su detalle con los contadores Spark/Drip y el historial de votos, y crear nuevas ofertas directamente desde el panel mediante el modal de creación de oferta, sin necesidad de acceder a la app de usuario.

Listado de ofertas en admin

Listado de ofertas

Detalle de oferta en admin

Detalle de oferta

Modal para crear oferta desde admin

Modal crear oferta

Gestión de vehículos con filtros

El módulo de vehículos del panel de administración lista todos los vehículos publicados con sus datos básicos y permite filtrar por tipo de vehículo (coches, motos, furgonetas, etc.), estado del anuncio y otros parámetros. El detalle de cada vehículo muestra la ficha técnica completa y permite al administrador gestionar el estado del anuncio.

Listado de vehículos en admin

Listado de vehículos

Vehículos filtrados por coches

Filtro: coches

Detalle de vehículo en admin

Detalle del vehículo

Gestión de reportes — Listado y detalle de incidencias

El módulo de reportes centraliza todas las denuncias realizadas por usuarios sobre productos, ofertas, vehículos, mensajes y otros usuarios. Cada reporte incluye el motivo seleccionado, una descripción, el elemento denunciado y el usuario denunciante. El administrador puede revisar el detalle completo, consultar el histórico de reportes del mismo usuario y decidir la acción a tomar (sanción, eliminación de contenido, desestimación).

Lista de reportes en admin

Listado de reportes

Detalle de reporte en admin

Detalle del reporte

Admin

Panel de Administración — Compras, Devoluciones, Contratos y Cupones

Gestión de compras y procesado de reembolsos

El módulo de compras del panel permite al administrador supervisar todas las transacciones del sistema, filtrar por estado y gestionar incidencias. Desde el modal de reembolso, el administrador puede procesar directamente la devolución del importe al comprador en Stripe, registrar el motivo y actualizar el estado de la compra a REEMBOLSADA. Toda acción queda registrada en el AuditLog.

Módulo de compras en admin

Gestión de compras

Modal de reembolso en admin

Modal de reembolso

Gestión de devoluciones y contratos publicitarios

El módulo de devoluciones muestra todas las solicitudes de devolución activas con su estado (SOLICITADA/ACEPTADA/RECHAZADA/COMPLETADA), las fotos de evidencia adjuntas y la nota del vendedor. El módulo de contratos gestiona los contratos publicitarios de las empresas con estados DRAFT, ACTIVE y EXPIRED.

Módulo de devoluciones en admin

Devoluciones

Módulo de contratos en admin

Contratos publicitarios

Creación y gestión de cupones de descuento

El administrador puede crear cupones de descuento mediante el modal de creación, configurando el código único (máximo 20 caracteres), el tipo de descuento (PORCENTAJE o FIJO), el valor, el importe mínimo de compra, el alcance (global o por categorías específicas), el límite total de usos y la vigencia del cupón. El listado de cupones muestra el estado de uso de cada uno.

Listado de cupones en admin

Cupones

Modal crear cupón - paso 1

Crear cupón (1)

Modal crear cupón - paso 2

Crear cupón (2)

Admin

Panel de Administración — Gestión de Publicidad y Patrocinios

Revisión, aprobación y rechazo de solicitudes de patrocinio

El administrador gestiona el ciclo de vida completo de los patrocinios: revisa las solicitudes en estado DRAFT enviadas por las empresas, las mueve a "en revisión", establece el precio del patrocinio y aprueba o rechaza la solicitud. Las tres pestañas documentan los patrocinios en cada estado: en revisión, aprobados (pendientes de pago por la empresa) y cancelados.

Patrocinios en revisión

En revisión

Patrocinios aprobados

Aprobados

Patrocinios cancelados

Cancelados

Modal de propuesta de publicidad al establecer el precio

Al revisar una solicitud de patrocinio, el administrador completa el modal de propuesta publicitaria especificando el precio final del contrato y las condiciones. El modal muestra los detalles de la solicitud de la empresa (tipo de contrato, ítem a patrocinar, duración solicitada) y permite al administrador establecer el importe antes de aprobarla.

Modal propuesta publicidad - paso 1

Propuesta publicidad (1)

Modal propuesta publicidad - paso 2

Propuesta publicidad (2)

Admin

Panel de Administración — Newsletter, Notificaciones y Correo Automático

Gestión del newsletter y correo de automatización semanal

El módulo de newsletter permite al administrador gestionar la lista de suscriptores (con filtros por estado de opt-in), configurar el envío automático semanal mediante el NewsletterScheduler, y lanzar emisiones manuales con contenido personalizado. Se valida el recibo del correo automático generado por el scheduler en una cuenta de Gmail real, confirmando la entrega correcta del newsletter vía Gmail SMTP con Spring Mail.

Configuración automatización newsletter

Automatización semanal

Emisión manual de newsletter

Emisión manual

Correo semanal recibido en Gmail

Email enviado (visto desde Gmail)

Gestión de notificaciones masivas con plantillas

El módulo de notificaciones del panel permite al administrador enviar notificaciones in-app masivas a todos los usuarios o a segmentos específicos. Las plantillas de notificación se pueden activar y desactivar según la temporada o campaña, permitiendo envíos programados de notificaciones con contenido personalizable.

Módulo de notificaciones en admin

Notificaciones masivas

Plantilla de notificación activada

Plantilla activada

Admin

Panel de Administración — Soporte al Usuario y Configuración Global

Panel de soporte — Gestión de conversaciones y encuesta de satisfacción

Cuando el chatbot de IA no puede resolver una consulta, la sesión de soporte se escala a un agente humano. El administrador accede al panel de soporte del panel de administración, donde visualiza todas las sesiones activas y escaladas, puede responder a los usuarios en tiempo real, y solicitar al usuario una encuesta de satisfacción al finalizar la atención, con el objetivo de medir la calidad del soporte.

Panel de soporte en admin

Panel de soporte

Solicitar encuesta de satisfacción al usuario

Solicitar encuesta

Configuración global del sistema desde el panel de administración

El módulo de configuración del panel permite al administrador gestionar parámetros globales del sistema: activación/desactivación de funcionalidades, configuración de límites operativos, gestión de categorías activas, ajuste de los parámetros del sistema de votación Spark/Drip (intervalo de recalculo), y otras opciones de configuración que afectan al comportamiento global de la plataforma sin requerir un redespliegue del backend.

Configuración global admin - parte 1

Configuración (1)

Configuración global admin - parte 2

Configuración (2)

Configuración global admin - parte 3

Configuración (3)

Pruebas unitarias

A continuación se documentan las pruebas unitarias diseñadas para validar las funcionalidades más críticas del sistema Nexus. Cada prueba está asociada a una captura funcional real del sistema, analiza el código implicado en el frontend y el backend, y especifica la lógica que se valida, el comportamiento esperado y los errores que previene. La selección prioriza las funcionalidades con mayor complejidad lógica, mayor impacto en la seguridad del sistema o mayor integración entre capas.

UT
01

Validación unitaria del proceso de autenticación y generación de JWT con invalidación por versión

Cubre el flujo completo de login con credenciales locales, generación del token JWT firmado con HMAC-SHA256 y la lógica de invalidación global mediante el campo jwtVersion.

Angular — AuthService Spring — AuthController · JWTUtils Seguridad crítica
Frontend — Angular

El componente de login invoca AuthService.login(), que construye el payload con el campo user (acepta username o email indistintamente) más password y captchaToken generado por reCAPTCHA v3. La petición se hace a POST /auth/login.

Si el backend responde con requires2FA: true, el flujo se detiene y se muestra el segundo paso. En caso contrario, AuthService llama a JwtService.saveToken() para persistir el JWT en localStorage y después a loadCurrentUser() para cargar el perfil completo en el AuthStore mediante Signals de Angular. El AuthStore expone isLoggedIn como computed(), actualizando la interfaz de forma reactiva sin emisor manual.

El interceptor HTTP de Angular adjunta automáticamente el JWT en el header Authorization: Bearer <token> en todas las peticiones autenticadas posteriores.

Backend — Spring Boot

AuthController.login() recibe el LoginRequest. Antes de procesar el login, invoca CaptchaService.verify(captchaToken); si la puntuación es inferior a 0.5, rechaza la petición con HTTP 400. Tras validar el captcha, delega en AuthenticationManager.authenticate() de Spring Security, que llama a UsuarioService.loadUserByUsername() para buscar el actor por username o email.

Si las credenciales son válidas, JWTUtils.generateToken(authentication) genera el JWT: firma HMAC-SHA256, subject = username, claim rol = ROLE_USER / ROLE_ADMIN / ROLE_EMPRESA, y expiración a 24 horas. El JWTAuthenticationFilter valida en cada petición que el token tenga firma correcta y que la jwtVersion coincida con la del Actor en base de datos. Incrementar jwtVersion (por baneo, cambio de contraseña o logout forzado) invalida todos los tokens emitidos anteriormente.

Especificación de la prueba unitaria — JWTUtils.generateToken() · validateToken()
  • Caso 1 — Token válido: dado un Authentication con rol ROLE_USER, se genera un token, se llama a validateToken() y se espera true. Se extrae el subject con getUsernameOfToken() y se verifica que coincide con el username de entrada.
  • Caso 2 — Token con firma manipulada: se modifica el payload del JWT antes de validarlo. Se espera que validateToken() devuelva false sin lanzar excepción.
  • Caso 3 — Invalidación por versión: se mockea el ActorRepository para devolver un Actor con jwtVersion = 2; el token fue emitido con versión 1. Se espera que el filtro rechace la petición con HTTP 401.
  • Caso 4 — reCAPTCHA rechazado: se mockea CaptchaService para devolver puntuación 0.3. Se espera respuesta HTTP 400 con mensaje de error antes de cualquier consulta a la base de datos.

Resultado esperado: El sistema genera tokens firmados correctamente, los invalida de forma fiable cuando jwtVersion cambia, y rechaza tokens manipulados o captchas inválidos en todos los casos de prueba.

Capturas de referencia

Formulario de inicio de sesión

Formulario de login con email/password

Validación 2FA tras login

Paso de verificación 2FA

Onboarding seguridad

Configuración de seguridad en onboarding

UT
02

Prueba unitaria del servicio de autenticación de dos factores TOTP con generación de QR

Valida la generación del secreto TOTP, la construcción del código QR en base64 mediante ZXing, y la verificación del código de 6 dígitos con la librería dev.samstevens.totp.

Angular — flujo 2FA Spring — TwoFactorAuthService · AdminSeguridadController Seguridad crítica
Frontend — Angular

El flujo de activación del 2FA se gestiona desde el módulo de seguridad de la cuenta. El componente realiza GET /auth/2fa/setup que devuelve { secret, qrCodeImage }; el qrCodeImage es un Data URI base64 de la imagen PNG generada por ZXing, mostrado directamente con <img [src]="qrCodeImage">. El usuario escanea el QR con Google Authenticator o Authy e introduce el código de 6 dígitos para confirmar la activación. El frontend lo envía a POST /auth/2fa/verify-setup.

En el flujo de login, si el backend responde con requires2FA: true, el componente muestra el campo de código TOTP. El código se envía a POST /auth/2fa/verify y, si es válido, el backend responde con el JWT definitivo. Si es incorrecto, se muestra un error sin consumir el código (el código TOTP cambia cada 30 segundos).

Backend — Spring Boot

TwoFactorAuthService.generateNewSecret() utiliza DefaultSecretGenerator de la librería dev.samstevens.totp para generar un secreto Base32 aleatorio de 32 caracteres. getQrCodeImage(secret, label) construye el URI otpauth con QrData.Builder (SHA-1, 6 dígitos, período 30 s) y genera la imagen PNG con ZxingPngQrGenerator, devolviendo el Data URI en base64.

isCodeValid(secret, code) delega en DefaultCodeVerifier combinado con SystemTimeProvider; tolera una ventana de ±1 período (30 s) para compensar desfases de reloj entre dispositivo y servidor. Los secretos TOTP se persisten encriptados en el campo totpSecret de la entidad Actor y nunca se exponen en el API de lectura.

Especificación de la prueba unitaria — TwoFactorAuthService.generateNewSecret() · isCodeValid()
  • Caso 1 — Secreto único: se llama a generateNewSecret() dos veces y se verifica que los secretos generados son diferentes (aleatoriedad suficiente).
  • Caso 2 — QR bien formado: se verifica que el Data URI devuelto por getQrCodeImage() comienza por data:image/png;base64, y que el payload Base64 es decodificable sin errores.
  • Caso 3 — Código correcto: se genera un secreto TOTP real, se calcula el código TOTP actual usando la misma librería y se verifica que isCodeValid(secret, code) devuelve true.
  • Caso 4 — Código incorrecto: se pasa un código arbitrario ("000000") y se verifica que isCodeValid() devuelve false.
  • Caso 5 — Código expirado: se genera un código con un timestamp 90 s en el pasado (fuera de la ventana de tolerancia) y se verifica que es rechazado.

Resultado esperado: Cada secreto generado es único, el QR es válido y decodificable, los códigos correctos son aceptados, y los incorrectos o expirados son rechazados de forma determinista.

Capturas de referencia

QR de activación 2FA TOTP

QR de vinculación con Google Authenticator

2FA activado

Estado 2FA activado correctamente

Login con 2FA obligatorio

Login con validación TOTP obligatoria

2FA desactivado

Estado 2FA desactivado

UT
03

Prueba unitaria del servicio de validación y aplicación de cupones de descuento

Valida las reglas de negocio del CuponValidacionService: vigencia temporal, límites de uso global y por usuario, importe mínimo, alcance por categoría, y cálculo del descuento para tipos FIJO, PORCENTAJE, ENVIO_GRATIS y COMBINADO.

Angular — checkout flow Spring — CuponValidacionService · PublicCuponesController Lógica de negocio
Frontend — Angular

Durante el proceso de checkout, el usuario introduce el código de cupón en el campo correspondiente. El componente llama al endpoint POST /public/cupones/validar con el body { codigo, usuarioId, importeTotal, costeEnvio, categoriaId }. El servicio devuelve un ValidacionResult con los campos valido, mensaje, tipo, descuento e importeFinal. Si es válido, el precio total mostrado al usuario se actualiza de forma reactiva y el código se almacena para incluirse en el PaymentIntent.

El frontend aplica validación de formato básica antes de enviar la petición (el campo no puede estar vacío y tiene un máximo de 20 caracteres), reduciendo peticiones innecesarias al backend.

Backend — Spring Boot

CuponValidacionService.validar() recibe el record AplicarCuponRequest y ejecuta una cadena de validaciones en orden determinista: (1) normalización del código a mayúsculas; (2) existencia en BD mediante CuponRepository.findByCodigoIgnoreCase(); (3) estado activo; (4) ventana temporal (fechaInicio / fechaFin); (5) límite de uso total; (6) alcance (GLOBAL, USUARIO, GRUPO); (7) límite por usuario con CuponUsoRepository; (8) importe mínimo; (9) restricción por categoría.

El cálculo del descuento en calcularDescuento() usa BigDecimal para evitar errores de punto flotante, y aplica el tope máximo (topeMaximo) en los tipos PORCENTAJE y COMBINADO.

Especificación de la prueba unitaria — CuponValidacionService.validar() · calcularDescuento()
  • Caso 1 — Cupón expirado: fechaFin en el pasado → ValidacionResult.valido = false con mensaje "El cupón ha caducado".
  • Caso 2 — Límite global agotado: totalUsos == limiteUsoTotal → rechazado con mensaje de agotamiento.
  • Caso 3 — Cupón personal ajeno: alcance = USUARIO con usuario.id ≠ req.usuarioId → rechazado con "no te pertenece".
  • Caso 4 — Importe insuficiente: importeTotal < importeMinimo → rechazado con importe mínimo en el mensaje.
  • Caso 5 — Descuento PORCENTAJE con tope: cupón de 20% con topeMaximo = 10 € sobre un total de 100 € → descuento = 10 € (no 20 €).
  • Caso 6 — Descuento ENVIO_GRATIS: el descuento calculado debe ser igual al valor del campo costeEnvio del request.
  • Caso 7 — Cupón válido completo: todas las condiciones superadas → valido = true e importeFinal = importeTotal - descuento con BigDecimal exacto.

Resultado esperado: Cada regla de negocio se evalúa en el orden correcto, el cálculo del descuento es exacto con BigDecimal, y no puede aplicarse ningún cupón inválido, expirado, ajeno o agotado bajo ninguna circunstancia.

Capturas de referencia

Datos de pago con cupón

Aplicación de cupón en el checkout

Modal crear cupón admin paso 1

Creación del cupón — paso 1

Modal crear cupón admin paso 2

Creación del cupón — paso 2

Listado de cupones

Listado de cupones en admin

UT
04

Prueba unitaria del flujo de compra segura con Stripe — modelo escrow y máquina de estados

Valida la creación del PaymentIntent en Stripe, las guardas de estado de la compra (PENDIENTE → PAGADO), el cambio de estado del producto (DISPONIBLE → VENDIDO) y las comprobaciones de negocio que previenen compras inválidas.

Angular — CompraService Spring — CompraController · CompraService · StripeService Pagos críticos
Frontend — Angular

CompraService.iniciarPago() realiza POST /compra/intent con parámetros productoId, compradorId, tipoEnvio y esRecogida. El backend responde con { clientSecret, compraId, precioProducto, costoEnvio, comisionNexus, total }. El frontend usa el clientSecret para inicializar Stripe.js y mostrar el formulario de tarjeta seguro (los datos de tarjeta nunca pasan por Angular, siempre por el iframe de Stripe).

Tras la confirmación en Stripe.js, el componente llama a CompraService.confirmarPago(compraId, body) enviando el paymentIntentId, los datos de entrega y el método seleccionado. Solo en ese momento el backend reserva el producto y crea el envío con código QR. Esta secuencia de dos pasos garantiza que ningún producto queda bloqueado si el usuario abandona el checkout antes de pagar.

Backend — Spring Boot

CompraController.crearIntentoPago() ejecuta las siguientes guardas antes de llamar a Stripe: (1) existencia del producto y comprador; (2) estado DISPONIBLE del producto; (3) el comprador no es el mismo que el vendedor; (4) ninguno de los dos tiene bloqueado al otro mediante BloqueoService. Si hay precio negociado por chat, se usa ese valor en lugar del precio del anuncio.

CompraService.confirmarPago() es transaccional (@Transactional) y actúa como máquina de estados: verifica que la compra esté en PENDIENTE antes de avanzar, pone el producto en VENDIDO, crea el Envio con QR base64, y dispara notificaciones in-app y emails a comprador y vendedor. Si el producto ya no está disponible (condición de carrera), lanza IllegalStateException.

Especificación de la prueba unitaria — CompraService.confirmarPago() · guardas del CompraController
  • Caso 1 — Compra de producto propio: comprador.id == vendedor.id → HTTP 400, sin llamar a Stripe.
  • Caso 2 — Producto no disponible: se mockea el producto con estado RESERVADO → HTTP 400 "El producto ya no está disponible".
  • Caso 3 — Usuarios bloqueados: se mockea BloqueoService.estaBloqueado() devolviendo true → HTTP 403 sin llamar a Stripe.
  • Caso 4 — Flujo correcto: se mockean StripeService.crearIntentoPago() y el repositorio. Se verifica que la entidad Compra se guarda con estado PAGADO, que el producto pasa a VENDIDO y que se invocan notificacionService.notificarNuevaCompraVendedor() y emailService.enviarConfirmacionCompra().
  • Caso 5 — Condición de carrera: el producto cambia de DISPONIBLE a VENDIDO entre el crearIntento y el confirmarPagoIllegalStateException propagada como HTTP 400.

Resultado esperado: La máquina de estados de la compra es idempotente y libre de condiciones de carrera. Ninguna compra inválida llega a Stripe. El producto siempre refleja su estado real en la base de datos.

Capturas de referencia

Datos del pedido

Paso 1 — Datos personales del pedido

Selección método de envío

Paso 2 — Selección del método de envío

Confirmación de pago

Paso 3 — Confirmación de pago exitosa

Email de confirmación

Email de confirmación enviado al comprador

UT
05

Prueba unitaria del sistema de mensajería en tiempo real — WebSocket STOMP y propuesta de precio

Valida el guardado de mensajes de tipo TEXTO y OFERTA_PRECIO, la lógica de aceptación/rechazo de propuestas de precio negociado, la distribución en tiempo real por WebSocket y el filtrado de contenido mediante el servicio de moderación.

Angular — WebSocketService · ChatService Spring — ChatWebSocketController · ChatService · ModerationService WebSocket en tiempo real
Frontend — Angular

WebSocketService gestiona la conexión STOMP sobre SockJS con reconexión automática cada 3 segundos. Al conectar, el cliente envía el JWT en los connectHeaders: { Authorization: 'Bearer ...' }. Se suscribe al topic /topic/chat/{roomId} para recibir los mensajes de la sala activa y al canal privado /user/queue/notificaciones para notificaciones de conversaciones no abiertas.

Para enviar texto, el componente publica en /app/chat.enviar con el payload { productoId, remitenteId, receptorId, texto, tipo: 'TEXTO' }. Para propuestas de precio, usa tipo: 'OFERTA_PRECIO' con el campo precioPropuesto. El multimedia pasa por el endpoint REST POST /chat/media. El unreadConvCount se actualiza mediante un Signal de Angular sin necesidad de polling.

Backend — Spring Boot

ChatWebSocketController está anotado con @MessageMapping("/chat.enviar") y recibe el payload deserializado automáticamente por STOMP. Para mensajes de tipo TEXTO, delega en ChatService.guardarMensajeTexto(), que aplica ModerationService.censurarTexto() antes de persistir, sustituyendo palabras de una lista negra por asteriscos. Para OFERTA_PRECIO, persiste el precioPropuesto con estadoPropuesta = PENDIENTE.

Tras persistir, SimpMessagingTemplate.convertAndSend("/topic/chat/" + roomId, msg) distribuye el mensaje a todos los suscriptores. Si el receptor no está suscrito, se le envía una notificación in-app. La aceptación de una propuesta queda disponible para CompraService mediante ChatService.getPrecioNegociado(productoId, compradorId).

Especificación de la prueba unitaria — ChatService.guardarMensajeTexto() · lógica de propuesta de precio
  • Caso 1 — Mensaje vacío rechazado: texto null o en blanco → el controlador REST MensajeController.enviar() devuelve HTTP 400 sin persistir nada.
  • Caso 2 — Moderación de contenido: se mockea ModerationService para censurar "palabrota" → el texto almacenado en BD contiene "********" en lugar del original.
  • Caso 3 — Distribución STOMP: se verifica que SimpMessagingTemplate.convertAndSend() es invocado con el topic correcto (/topic/chat/{roomId}) y con el objeto ChatMensaje persistido.
  • Caso 4 — Propuesta de precio PENDIENTE: mensaje con tipo = OFERTA_PRECIO y precioPropuesto = 250.0 → se persiste con estadoPropuesta = PENDIENTE.
  • Caso 5 — Precio negociado disponible para compra: tras aceptar la propuesta, ChatService.getPrecioNegociado(productoId, compradorId) devuelve el precio pactado, que CompraService usará en lugar del precio original del anuncio.

Resultado esperado: Los mensajes vacíos son rechazados, el contenido inapropiado se censura antes de persistirse, los mensajes llegan en tiempo real al topic correcto y el precio negociado queda disponible para el flujo de compra de forma consistente.

Capturas de referencia

Pantalla de mensajes PC

Pantalla de conversaciones en escritorio

Envío de mensaje

Envío de mensaje de texto

Propuesta de precio

Envío de propuesta de precio

Recepción de propuesta

Recepción y aceptación de propuesta

UT
06

Prueba unitaria del motor de búsqueda unificado — expansión de sinónimos y filtro geográfico por bounding box

Valida la expansión de términos sinónimos con SynonymService, el cálculo trigonométrico del bounding box geográfico para búsquedas por proximidad y la integración de filtros combinados en ProductoService.buscarConFiltrosPaginado().

Angular — SearchService Spring — MarketplaceSearchService · SynonymService · ProductoService
Frontend — Angular

SearchService del frontend construye los parámetros de búsqueda y realiza GET /search?q=ps5&tipo=PRODUCTO&lat=37.38&lng=-5.99&radius=50. Cuando el usuario activa la búsqueda por ubicación, el navegador solicita los permisos de geolocalización mediante la API nativa navigator.geolocation.getCurrentPosition(); las coordenadas se adjuntan como parámetros lat, lng y radius. La respuesta paginada incluye los campos contenido, totalElementos, totalPaginas y paginaActual, que el componente de resultados renderiza con scroll infinito.

Backend — Spring Boot

SynonymService mantiene un mapa estático de más de 200 entradas. El método expand(busqueda) normaliza el término a minúsculas y busca su entrada en el mapa; si existe, devuelve la lista original más todos los sinónimos declarados. Por ejemplo, expand("ps5") devuelve ["ps5", "playstation 5", "play station 5", "ps five", ...]. Si no existe entrada, devuelve una lista con el término original.

El bounding box geográfico se calcula en MarketplaceSearchService con la fórmula: deltaLat = radius / 111.1 y deltaLng = radius / (111.1 × cos(lat)). Esta corrección por latitud evita que el radio en longitud se distorsione en latitudes alejadas del ecuador. Los límites minLat, maxLat, minLng, maxLng se pasan al repositorio para filtrar por las columnas lat / lng del anuncio.

Especificación de la prueba unitaria — SynonymService.expand() · cálculo del bounding box
  • Caso 1 — Expansión de sinónimo conocido: expand("ps5") → la lista devuelta contiene "playstation 5" y "sony playstation 5". El término original "ps5" también está incluido.
  • Caso 2 — Término sin sinónimos: expand("silla") → devuelve una lista con un único elemento: ["silla"].
  • Caso 3 — Insensibilidad a mayúsculas: expand("PS5") y expand("ps5") deben devolver la misma lista de sinónimos.
  • Caso 4 — Bounding box simétrico: dado el punto (lat=40.4168, lng=-3.7038, radius=50), se verifica que maxLat - minLat ≈ 0.9 (50 km / 111.1) y que maxLng - minLng < maxLat - minLat por la corrección del coseno (en Madrid, la longitud se contrae).
  • Caso 5 — Reducción del radio filtra resultados: con un conjunto de productos mockeados, al reducir el radio de 50 km a 10 km el número de resultados devueltos disminuye, validando que el bounding box se aplica correctamente.

Resultado esperado: La expansión de sinónimos es determinista, insensible a mayúsculas y no modifica términos desconocidos. El bounding box geográfico aplica la corrección trigonométrica por latitud y filtra con precisión los resultados según el radio configurado por el usuario.

Capturas de referencia

Búsqueda 50km 2 coches

Radio 50 km — 2 resultados

Búsqueda 10km 1 coche

Radio 10 km — 1 resultado

UT
07

Prueba unitaria del sistema de votación Spark/Drip — toggle, contadores y difusión en tiempo real

Valida la lógica de toggle de votos (volver a votar lo mismo elimina el voto), el incremento y decremento de contadores sparkCount / dripCount, el cambio de voto (de positivo a negativo y viceversa) y la difusión del resultado actualizado por WebSocket al topic de la oferta.

Angular — detalle de oferta Spring — SparkVotoService · SparkVotoController WebSocket en tiempo real
Frontend — Angular

El componente de detalle de oferta muestra los contadores sparkCount y dripCount actualizados en tiempo real. Se suscribe al topic STOMP /topic/votos/oferta/{ofertaId}, que el backend usa para difundir el resultado tras cada voto. Al recibir el evento, el componente actualiza los valores directamente en la vista sin recargar la página ni volver a consultar el API REST.

Al pulsar el botón de Spark o Drip, el componente llama a POST /spark/oferta/{ofertaId}?actorId=X&valor=1 (o valor=-1 para Drip). El estado visual del botón (activo/inactivo) se gestiona con el campo votoActual consultado al cargar el detalle mediante GET /spark/oferta/{ofertaId}/voto?actorId=X.

Backend — Spring Boot

SparkVotoService.votarOferta(actorId, ofertaId, valor) implementa una lógica de tres ramas: (1) si no existe voto previo, se crea y se incrementa el contador correspondiente (sparkCount o dripCount); (2) si ya existe voto con el mismo valor, se elimina (toggle off) y se decrementa el contador; (3) si ya existe voto con valor distinto, se actualiza el voto y se ajustan ambos contadores. Los contadores nunca caen por debajo de cero gracias a Math.max(0, ...).

Tras actualizar la entidad Oferta, el servicio llama a SimpMessagingTemplate.convertAndSend("/topic/votos/oferta/" + ofertaId, Map.of(...)) con los nuevos valores de sparkCount, dripCount y el score neto calculado. Esta llamada es sincrónica y forma parte de la misma transacción, garantizando coherencia entre el dato persistido y el dato difundido.

Especificación de la prueba unitaria — SparkVotoService.votarOferta() · tres ramas del toggle
  • Caso 1 — Primer voto positivo (Spark): no hay voto previo → se crea SparkVoto con valor = 1, oferta.sparkCount incrementa en 1. Se verifica invocación de ofertaRepository.save().
  • Caso 2 — Toggle off (mismo voto): ya existe un SparkVoto con valor = 1 → se elimina y sparkCount decrece en 1. No debe quedar registro en SparkVotoRepository.
  • Caso 3 — Cambio de voto (Spark → Drip): existe voto con valor = 1, se llama con valor = -1sparkCount baja 1, dripCount sube 1. El SparkVoto existente se actualiza, no se crea uno nuevo.
  • Caso 4 — Contador nunca negativo: oferta.sparkCount = 0, se hace toggle off de un voto Spark → se verifica que sparkCount permanece en 0 (no baja a -1).
  • Caso 5 — Difusión WebSocket: se mockea SimpMessagingTemplate y se verifica que convertAndSend() es invocado exactamente una vez con el topic /topic/votos/oferta/{ofertaId} y el mapa correcto de sparkCount, dripCount y score.

Resultado esperado: La lógica de toggle es determinista en las tres ramas, los contadores son siempre no negativos, y cada voto dispara exactamente una difusión WebSocket con los valores actualizados correctos, garantizando coherencia entre el dato en base de datos y la actualización en tiempo real en el cliente.

Capturas de referencia

Detalle de oferta cabecera

Detalle de oferta — cabecera

Detalle de oferta votos Spark/Drip

Botones Spark y Drip con contadores

Despliegue

El sistema completo está desplegado en producción usando exclusivamente servicios cloud. No hay servidores propios: todo corre en Render, Vercel y servicios gestionados de terceros.

Backend — Render.com con Docker

El backend (nexus-backend) se despliega en Render como un Web Service Docker. El Dockerfile del repositorio usa una imagen base eclipse-temurin:17-jdk-alpine, compila el JAR con Maven y arranca la aplicación en el puerto 8080. El despliegue es continuo: cada push a la rama principal del repositorio dispara automáticamente un nuevo build y despliegue en Render.

La configuración de producción se gestiona íntegramente mediante variables de entorno en el panel de Render (nunca en el código fuente): cadena de conexión PostgreSQL, secreto JWT, claves de Stripe, API keys de Cloudinary, credenciales Gmail SMTP, secreto de webhook de Stripe, claves de Google OAuth y reCAPTCHA, y API keys de Gemini/Groq.

La base de datos es un PostgreSQL gestionado por Render (plan gratuito). Hibernate arranca con ddl-auto=create en el primer despliegue para generar el esquema, y luego se cambia a validate para no recrear tablas en reinicios. El pool de conexiones HikariCP está ajustado a máximo 3 conexiones para respetar el límite del plan gratuito.

Frontends — Vercel

Los tres frontends (nexus-angular-app, nexus-admin-web-app, nexus-web-about) se despliegan en Vercel con despliegue continuo por push. Cada proyecto tiene su propio vercel.json con la configuración de build y las reglas de reescritura de rutas necesarias para el enrutamiento SPA de Angular.

Las variables de entorno del frontend (URL del backend, claves públicas de Stripe, site key de reCAPTCHA, Google Client ID) se definen en el panel de Vercel y se inyectan en tiempo de build como variables de entorno de Angular (environments/environment.prod.ts).

App Android — Capacitor

La app móvil Android se genera a partir del mismo código de nexus-angular-app. El proceso de compilación es: ng build --configuration productionnpx cap copy android → compilación del proyecto Android con Android Studio. El APK resultante funciona conectado al mismo backend en producción. La distribución en Google Play está pendiente de completar el proceso de firma y publicación.

Limitación del plan gratuito de Render: El servicio entra en hibernación después de 15 minutos de inactividad. La primera petición tras la hibernación puede tardar entre 30 y 60 segundos en responder mientras el contenedor Docker arranca. Esta limitación desaparece al migrar a un plan de pago con instancias persistentes.

Conclusiones y Posibles Mejoras

Esta sección recoge una valoración objetiva del trabajo realizado, analizando qué objetivos se han cumplido respecto a los planificados, las dificultades encontradas y las líneas de mejora concretas identificadas para versiones futuras.

Objetivos Cumplidos

El proyecto Nexus ha alcanzado en gran medida los objetivos fijados en la fase de planificación, logrando una plataforma funcional y desplegada en producción con las siguientes capacidades operativas:

  • Marketplace completo: Sistema de publicación, búsqueda y compraventa de productos de segunda mano, vehículos y ofertas comunitarias, con ciclo de vida completo de los anuncios (publicación, caducidad a 180 días, renovación, avisos proactivos) y gestión de hasta 6 imágenes por anuncio en Cloudinary.
  • Sistema de pagos funcional: Integración completa con Stripe en modo test, incluyendo Payment Intents con modelo escrow, Stripe Checkout Sessions para contratos de publicidad empresarial y recepción de webhooks con verificación de firma HMAC.
  • Autenticación multicapa robusta: Sistema completo con JWT (con invalidación global via jwtVersion), BCrypt, Google OAuth 2.0, reCAPTCHA v3, verificación de email por OTP, 2FA TOTP (con código QR generado por ZXing) y 2FA por email, sin dependencias de sesiones en servidor.
  • Chat en tiempo real: Mensajería instantanea mediante WebSocket STOMP con soporte para texto, imágenes, audio, vídeo, GIFs y propuestas de precio. Autenticación del canal WebSocket con el mismo JWT de usuario.
  • Panel de administración completo: Aplicación Angular separada con 19 módulos de gestión, cubriendo moderación, estadísticas, audit log, contratos, cupones, newsletter, gestión de fraude y sanciones.
  • Multiplataforma real: La aplicación web funciona como Progressive Web App y ha sido compilada como app nativa Android mediante Capacitor 8 con acceso a GPS y cámara.
  • Cumplimiento RGPD: Borrado suave (soft-delete), double opt-in en newsletter con registro de IP y versión de política, consentimiento explícito de términos y tokens de baja únicos por suscriptor.
  • Asistente IA de soporte: Chatbot de soporte integrado con Google Gemini 1.5-flash y Groq (LLaMA 3.3-70b) como alternativa, con escalación automática por email cuando el agente no puede resolver la consulta.
  • Infraestructura cloud completa: Backend en Render (Docker), tres frontends en Vercel CDN, base de datos PostgreSQL gestionada, medios en Cloudinary. Despliegue continuo por push a rama principal.

Dificultades Encontradas

D-01

Gestión de herencia JPA con Spring Security y dependencias circulares

La estrategia InheritanceType.JOINED requirió ajustes finos en el UserDetailsService para cargar el actor correcto según su subtipo, y en la generación de JWT para asignar el rol adecuado. La resolución de dependencias circulares entre JWTUtils y los servicios de usuarios/empresas/admins requirió el uso generalizado de la anotación @Lazy en las inyecciones de dependencias para romper los ciclos detectados por el contenedor IoC de Spring en el arranque.

D-02

Configuración de CORS y WebSocket en despliegue multidominios

La separación de las aplicaciones en dominios diferentes (API en Render, frontends en Vercel) generó problemas complejos de CORS, especialmente para las conexiones WebSocket STOMP cuyo handshake inicial es HTTP. Fue necesario configurar los orígenes permitidos dinámicamente desde application.properties y validar cuidadosamente el interceptor WebSocket, que tiene un proceso de autenticación diferente al filtro JWT estándar de HTTP.

D-03

Optimización de memoria para el plan gratuito de Render

El plan gratuito de Render impone 0.1 CPU y 512 MB de RAM. Las primeras versiones del backend con configuración por defecto de Tomcat y HikariCP causaban errores OutOfMemoryError bajo carga. Se realizó un proceso iterativo de ajuste de todos los parámetros de pools, threads, caché JVM y serialización Jackson hasta alcanzar una configuración estable. La principal limitación detectada es que el plan gratuito provoca que el servicio entre en hibernación tras períodos de inactividad, causando tiempos de arranque de hasta 30-60 segundos en la primera petición.

D-04

Integración de Stripe Webhooks en desarrollo local

Los webhooks de Stripe requieren una URL pública accesible. Durante el desarrollo local fue necesario usar Stripe CLI (stripe listen --forward-to localhost:8080/stripe/webhook) para reenviar los eventos al servidor local. La gestión del secreto de webhook diferenciado entre entornos de desarrollo y producción requirió cuidado especial para no exponer credenciales en el repositorio Git.

D-05

Serialización JSON con entidades JPA y bucles infinitos

Las relaciones bidireccionales entre entidades JPA (Producto ↔ Actor, Oferta ↔ Categoria, Compra ↔ Producto ↔ Actor) causaban bucles infinitos de serialización JSON que resultaban en StackOverflowError. La solución requirió el uso estratégico de @JsonIgnoreProperties con listas explícitas de campos a ignorar en cada dirección de la relación, @JsonIgnore en campos sensibles, y la inclusión de jackson-datatype-hibernate6 para manejar correctamente los proxies de Hibernate.

D-06

Compatibilidad de versiones en el ecosistema Angular moderno

Angular 21 con Standalone Components es una arquitectura relativamente nueva que presenta incompatibilidades con algunas librerías de terceros diseñadas para NgModules. La librería ng-recaptcha requirió overrides en package.json para forzar la compatibilidad con Angular 21. La integración de Ionic 8 con Capacitor 8 también requirió configuración adicional del capacitor.config.ts y del servidor de desarrollo para el flujo de compilación nativa Android.

Líneas de Mejora Futuras

Mejora Justificación técnica Prioridad
Publicación en App Store (iOS) y Google Play La app está compilada con Capacitor para Android pero no publicada en las tiendas. Completar el proceso de firma de APK/IPA y subir a Google Play y App Store ampliaría significativamente el alcance de usuarios. Alta
Motor de búsqueda avanzado (Elasticsearch) La búsqueda actual usa consultas JPA con filtros dinámicos. Integrar Elasticsearch permitiría búsqueda de texto completo con relevancia ponderada, autocompletado en tiempo real, facetas dinámicas y tolerancia a errores tipográficos (fuzzy search), mejorando radicalmente la experiencia de búsqueda. Alta
Activación de pagos reales con Stripe Live Actualmente los pagos están en modo Test. Activar el modo Live requiere completar el proceso de verificación KYC de Stripe y configurar transferencias al vendedor vía Stripe Connect para el modelo escrow real. Alta
Notificaciones push nativas (FCM) Las notificaciones actuales son solo in-app. Integrar Firebase Cloud Messaging (FCM) con el plugin @capacitor/push-notifications permitiría notificaciones push nativas en Android e iOS aunque el usuario no tenga la app abierta. Media
Integración real con transportistas (Correos, MRW) El módulo de envíos está implementado como stub con estructura preparada (CarrierApiService). Integrar APIs reales de Correos o MRW permitiría generar etiquetas de envío automáticas y hacer seguimiento en tiempo real del paquete. Media
Sistema de recomendaciones personalizado Implementar un motor de recomendaciones basado en el historial de búsquedas, favoritos y compras del usuario (collaborative filtering o content-based filtering) mejoraría la tasa de descubrimiento de productos relevantes y la tasa de conversión. Media
Migración a infraestructura escalable El plan gratuito de Render tiene limitaciones críticas (hibernación por inactividad, 0.1 CPU). Migrar a un plan de pago con instancias persistentes o a una infraestructura Kubernetes permitiría escalar horizontalmente y eliminar los tiempos de arranque en frío. Media
Moderación automática con IA (visión y NLP) Implementar detección automática de contenido inapropiado en imágenes (Google Vision API o similar) y de spam/fraude en descripciones de productos (NLP), reduciendo la carga del equipo de moderación y mejorando la velocidad de detección de contenido dañino. Baja
Sistema de pujas y subastas La entidad Producto ya define el tipo de oferta SUBASTA en el enum TipoOferta, pero el flujo de subastas con pujas temporizadas no ha sido implementado. Implementarlo añadiría un mecanismo de precio competitivo para artículos de alto valor. Baja
Programa de afiliados y códigos de referido Implementar un sistema de códigos de referido que recompense a usuarios que inviten a nuevos registros con créditos o descuentos, acelerando el crecimiento orgánico de la plataforma mediante incentivos económicos. Baja

Reflexión Final

Nexus ha demostrado ser técnicamente viable construir en el marco de un proyecto de formación (noviembre 2025 – mayo 2026) una plataforma de comercio digital con un nivel de complejidad técnica comparable a productos comerciales reales: autenticación multicapa, pagos con Stripe, WebSocket en tiempo real, integración con IAs generativas, RGPD, despliegue cloud y app móvil nativa.

La elección de un stack tecnológico maduro y bien documentado (Spring Boot + Angular + PostgreSQL) facilitó la resolución de problemas complejos gracias a la amplia documentación oficial, comunidad activa y ecosistema de librerías probadas. La arquitectura desacoplada (frontend/backend/admin en repositorios y dominios separados) demostró ser la decisión correcta, aunque añadió complejidad de configuración en CORS y WebSocket.

El mayor valor técnico del proyecto reside en la integración coherente de múltiples servicios externos (Stripe, Cloudinary, Google OAuth, reCAPTCHA, IA generativa, Gmail SMTP) en un único sistema cohesionado con una arquitectura extensible que permite sustituir o actualizar cada componente de forma independiente sin afectar al resto del sistema.

Bibliografía

Documentación oficial, especificaciones y recursos técnicos consultados durante el desarrollo del proyecto Nexus, ordenados por categoría tecnológica.

Backend — Java y Spring Ecosystem

Seguridad y Autenticación

Base de Datos

Frontend — Angular y Ecosistema

Pagos e Infraestructura de Medios

Inteligencia Artificial y APIs Externas

Infraestructura y Despliegue

Documentación API y estándares

Normativa y Cumplimiento

Anexos

Documentación complementaria que incluye el glosario de términos técnicos, la tabla de endpoints de la API y el resumen del modelo de base de datos.

Anexo A — Glosario de Términos Técnicos

Término Definición en el contexto de Nexus
ActorClase base abstracta JPA que representa a cualquier entidad con cuenta en el sistema. Todos los tipos de usuario (Usuario, Empresa, Admin) heredan de Actor.
API RESTInterfaz de programación de aplicaciones basada en el protocolo HTTP con arquitectura REST. El backend expone su funcionalidad mediante API REST JSON.
Audit LogRegistro inmutable de acciones administrativas en el sistema. Cada entrada almacena qué administrador realizó qué acción y cuándo.
BadgeEtiqueta visual calculada automáticamente para las ofertas del chollometro: NUEVA, CHOLLAZO, PORCENTAJE, GRATUITA o EXPIRA_HOY.
BCryptAlgoritmo de hashing adaptativo usado para almacenar las contraseñas de los usuarios. Nunca se almacena la contraseña en texto plano.
Bearer TokenToken JWT enviado en la cabecera HTTP Authorization: Bearer <token> para autenticar peticiones al backend.
CapacitorLibrería de Ionic que compila una aplicación web Angular como app nativa para Android e iOS, con acceso a APIs del dispositivo (cámara, GPS, etc.).
ChollometroSección de Nexus donde los usuarios publican y votan ofertas/chollos de productos nuevos en tiendas online y físicas.
CloudinaryServicio cloud de almacenamiento, gestión y transformación de imágenes y vídeos. Todas las imágenes de Nexus se almacenan y sirven desde Cloudinary.
CORSCross-Origin Resource Sharing. Mecanismo HTTP que controla qué dominios externos pueden realizar peticiones al backend de Nexus.
CupónCódigo de descuento creado por el administrador y aplicable en el proceso de checkout. Puede ser de tipo PORCENTAJE o FIJO con restricciones de uso.
DDL-autoPropiedad de Hibernate que controla cómo se gestiona el esquema de BD. En Nexus usa create en desarrollo (recrea el esquema en cada arranque).
Double opt-inFlujo de suscripción al newsletter que requiere confirmación por email. Requerido por el RGPD para acreditar el consentimiento explícito.
DripVoto negativo en el sistema de votación de Nexus. Un Drip indica que la oferta no parece válida o interesante.
EscrowModelo de pago donde el dinero del comprador queda retenido por un tercero (Stripe) hasta que se confirma la correcta recepción del producto.
FCMFirebase Cloud Messaging. Servicio de notificaciones push de Google. Aún no implementado en Nexus (línea de mejora futura).
Guard (Angular)Servicio Angular que controla el acceso a rutas. authGuard protege rutas privadas; guestGuard protege rutas de no-autenticados.
HikariCPPool de conexiones JDBC de alto rendimiento usado por Spring Boot para gestionar el pool de conexiones a PostgreSQL.
Interceptor (Angular)Servicio Angular que intercepta y modifica peticiones/respuestas HTTP globalmente. Nexus usa JwtInterceptor y ErrorInterceptor.
IoC / DIInversión de Control / Inyección de Dependencias. Patrón central de Spring que gestiona la creación y ciclo de vida de los beans.
IonicFramework UI para aplicaciones híbridas móviles. Proporciona componentes nativos (botones, listas, tarjetas) adaptados para dispositivos iOS y Android.
JPAJakarta Persistence API. Estándar Java para mapeo objeto-relacional (ORM). Nexus usa la implementación de Hibernate.
JWTJSON Web Token. Estándar (RFC 7519) para tokens de autenticación compactos y firmados. En Nexus se firman con HMAC-SHA256.
jwtVersionCampo entero en la entidad Actor que permite invalidar globalmente todos los JWT de un usuario al incrementarse.
Lazy Loading (Angular)Técnica de carga bajo demanda de módulos/componentes Angular. Nexus usa lazy loading total en todas las rutas para reducir el bundle inicial.
Lazy Loading (JPA)Carga diferida de relaciones JPA. Las entidades con FetchType.LAZY solo se cargan de la BD cuando se accede explícitamente a ellas.
MarketplaceSección de Nexus donde los usuarios pueden publicar y comprar productos de segunda mano entre particulares.
MVPMinimum Viable Product (Producto Mínimo Viable). Primera versión funcional del producto con las características esenciales para validar el mercado.
OAuth 2.0Estándar de autorización que permite a usuarios autenticarse con cuentas de terceros (en Nexus, Google) sin compartir su contraseña.
OpenAPI / SwaggerEspecificación estándar para documentar APIs REST. Nexus genera automáticamente la documentación con SpringDoc OpenAPI 2.6.0.
OTPOne-Time Password. Código de un solo uso de 6 dígitos. Nexus lo usa para verificación de email (30 min) y 2FA por email (10 min).
Payment IntentObjeto de Stripe que representa una intención de cobro. Permite el modelo escrow y el flujo de confirmación en dos pasos.
RGPDReglamento General de Protección de Datos (EU 2016/679). Nexus implementa borrado suave, double opt-in y registro de consentimientos para cumplirlo.
ROLE_ADMINRol de Spring Security asignado a los administradores. Da acceso exclusivo al panel de administración (/api/admin/**).
ROLE_EMPRESARol de Spring Security asignado a las empresas. Extiende ROLE_USER con acceso a contratos publicitarios y Stripe Checkout.
ROLE_USERRol de Spring Security asignado a los usuarios particulares registrados. Es el rol base del sistema.
roomIdIdentificador único de una conversación de chat entre dos usuarios. Se calcula de forma determinista a partir de los IDs de los participantes.
SchedulerTarea programada de Spring (@Scheduled) que se ejecuta automáticamente en intervalos configurados. En Nexus gestiona caducidad de anuncios y newsletter.
Soft DeleteBorrado lógico. En lugar de eliminar físicamente un registro de la BD, se marca como eliminado (cuentaEliminada = true). Cumple el RGPD.
SparkVoto positivo en el sistema de votación de Nexus. Un Spark indica que la oferta es interesante y válida.
sparkScorePuntuación neta de una oferta = sparkCount - dripCount. Determina el orden de aparición en el feed del chollometro.
Standalone ComponentComponente Angular que se declara con standalone: true e importa sus dependencias directamente, sin necesidad de NgModule.
STOMPSimple Text Orientated Messaging Protocol. Protocolo de mensajería sobre WebSocket usado en Nexus para el chat en tiempo real.
StripePlataforma de pagos online. Nexus la usa para Payment Intents (compras) y Stripe Checkout (contratos de publicidad).
TOTPTime-based One-Time Password (RFC 6238). Código de 6 dígitos generado por una app autenticadora (Google Authenticator, Authy) que cambia cada 30 segundos.
WebhookMecanismo de notificación HTTP en el que Stripe llama al endpoint de Nexus (/stripe/webhook) para confirmar eventos de pago asíncronos.
WebSocketProtocolo de comunicación bidireccional y persistente sobre TCP. Nexus lo usa con STOMP para el chat en tiempo real y las notificaciones instantáneas.
ZXingLibrería Java para leer y generar códigos QR y de barras. Nexus la usa para generar los códigos QR de los envíos y para el proceso de vinculación del 2FA TOTP.

Anexo B — Tabla de Endpoints Principales de la API

Todos los endpoints están documentados con Swagger UI en /swagger-ui.html. A continuación se listan los más representativos por dominio funcional:

Método Endpoint Descripción Autenticación
Autenticación
POST/api/auth/registerRegistro de nuevo usuario con email, username, contraseña y reCAPTCHAPública
POST/api/auth/loginLogin con credenciales y reCAPTCHA. Devuelve JWT o flag twoFactorRequiredPública
POST/api/auth/googleLogin/registro con ID token de Google OAuth 2.0Pública
POST/api/auth/verify-2faValidación del código 2FA (TOTP o Email OTP). Devuelve JWT si es válidoPública
POST/api/auth/verify-emailVerificación del email con el OTP de 6 dígitos enviado al correoPública
POST/api/auth/forgot-passwordSolicitar enlace de recuperación de contraseña (válido 15 minutos)Pública
POST/api/auth/reset-passwordRestablecer contraseña con el token recibido por emailPública
Productos
GET/producto/filtrarBúsqueda paginada con filtros (categoría, precio, condición, ubicación, radio)Pública
GET/producto/{id}Detalle de producto. Comprueba bloqueos si se pasa ?usuarioId=Pública
POST/producto/publicar/{usuarioId}Publicar nuevo producto con imagen principal y galería (multipart)JWT
PUT/producto/{id}Actualizar datos del producto (multipart con campos opcionales)JWT
PATCH/producto/{id}/estadoCambiar estado: DISPONIBLE, RESERVADO, VENDIDOJWT
DELETE/producto/{id}Eliminar producto y sus imágenes de CloudinaryJWT
POST/producto/{id}/renovarReactivar anuncio expirado con nueva vigencia de 180 díasJWT
Ofertas (Chollometro)
GET/ofertaListado de ofertas activas paginado, ordenado por sparkScore descendentePública
POST/oferta/publicarPublicar nueva oferta/chollo con imagen (multipart)JWT
POST/spark-voto/{ofertaId}/votarVotar Spark (positivo) o Drip (negativo) en una ofertaJWT
Vehículos
GET/vehiculo/filtrarBúsqueda de vehículos con filtros de ficha técnica (combustible, km, año, etc.)Pública
POST/vehiculo/publicar/{usuarioId}Publicar vehículo con ficha técnica e imágenes (multipart)JWT
Compras y Pagos
POST/pago/crear-intentCrear Stripe Payment Intent y devolver clientSecret para el frontendJWT
POST/stripe/webhookReceptor de eventos Stripe con verificación de firma HMAC del payloadPública (firma)
GET/compra/mis-comprasHistorial de compras del usuario autenticado como compradorJWT
PATCH/compra/{id}/confirmar-entregaEl comprador confirma la recepción del producto. Libera el pago escrow.JWT
Envíos y Devoluciones
GET/envio/{id}/qrObtener el código QR del envío en formato base64JWT
POST/devolucionSolicitar devolución de una compra con motivo y fotos de evidenciaJWT
Chat y Mensajería
GET/chat/historial/{roomId}Obtener historial de mensajes de una conversaciónJWT
WS/ws (STOMP /app/chat)Enviar mensaje en tiempo real por WebSocket STOMPJWT (handshake)
Perfiles y Social
GET/usuario/perfil/{username}Perfil público del usuario (si no está privado)Pública
POST/favoritoAñadir producto/oferta a favoritosJWT
POST/valoracionCrear valoración post-compra (una por compra)JWT
POST/reporteReportar un usuario, producto, oferta u otro contenidoJWT
Newsletter y Soporte
POST/newsletter/suscribirSuscribirse al newsletter (inicia el double opt-in)Pública
GET/newsletter/confirmarConfirmar la suscripción con el token del email de confirmaciónPública
POST/soporte-chat/mensajeEnviar mensaje al chatbot de soporte IAPública
Administración
GET/api/admin/usuariosListado completo de usuarios con filtros y búsquedaROLE_ADMIN
POST/api/admin/sanciones/banAplicar ban permanente a un usuarioROLE_ADMIN
GET/api/admin/audit-logConsultar el registro de auditoría de acciones adminROLE_ADMIN
GET/api/admin/estadisticasMétricas del sistema: usuarios, ventas, ingresos, productos activosROLE_ADMIN
POST/api/admin/cuponesCrear cupón de descuentoROLE_ADMIN
GET/api/admin/reportesListado de reportes pendientes de resoluciónROLE_ADMIN

Anexo C — Resumen del Modelo de Base de Datos

Tablas principales del esquema PostgreSQL generado por Hibernate. Todas incluyen id (SERIAL PRIMARY KEY) heredado de DomainEntity. Para el detalle completo de campos consulta la sección Diseño → Modelo de Base de Datos.

Tabla Columnas clave Relaciones e índices
actorusername, email, password (bcrypt), jwt_version, two_factor_enabled, baneado, suspendido_hasta, stripe_customer_id, google_id, reset_tokenPadre de usuario, empresa, admin · Idx: username, email, google_id, reset_token
usuarioreputacion, total_ventas, ubicacion, cuenta_privada, newsletter_suscrito, terminos_aceptados, dir_* (dirección embebida)FK → actor (PK compartida) · Tabla: usuario_bloqueados
empresacif (unique), nombre_comercial, descripcion, web, logo, verificadaFK → actor (PK compartida) · Idx: cif
adminnivel_acceso (1/2/3)FK → actor (PK compartida)
productotitulo, precio, tipo_oferta, estado, condicion, admite_envio, latitude, longitude, imagen_principal, fecha_caducidad, patrocinadoFK → actor (vendedor), categoria · Tabla: producto_imagenes · Idx: vendedor, estado, categoria
ofertatitulo, precio_oferta, precio_original, tienda, badge, spark_count, drip_count, spark_score, es_flash, flash_fin, codigo_descuento, patrocinadaFK → actor, categoria · Tabla: oferta_imagenes · Idx: activa, spark, categoria, publicacion
vehiculotipo_vehiculo, marca, modelo, anio, kilometros, combustible, cambio, potencia, itv, garantia, latitude, longitudeFK → actor (publicador), categoria · Tablas: vehiculo_imagenes, vehiculo_extras · Idx: publicador, tipo, estado
compraestado, stripe_payment_intent_id, precio_final, comision_nexus, metodo_entrega, dir_* (dirección), fechas de hitosFK → actor (comprador), producto · Idx: comprador, estado
enviocodigo_envio (SHIP-XXXXXXXX), codigo_qr (base64), transportista, numero_seguimiento, url_tracking, fecha_limite_envio, estado_envioFK → compra (OneToOne)
devolucionmotivo, descripcion, nota_vendedor, importe_devuelto, estadoFK → compra · Tabla: devolucion_fotos
chat_mensajeroom_id, texto, media_url, tipo, leido, recibido, precio_propuesto, estado_propuesta, fecha_envioFK → actor x2 (remitente, receptor), producto (opc.) · Idx: room_id, fechas
valoracionpuntuacion (1-5), comentario, respuesta_vendedorFK → actor x2, compra (unique — una por compra)
contratotipo_contrato, estado, monto, dias_patrocinio, stripe_checkout_session_idFK → empresa, actor
cuponcodigo (unique), tipo_descuento, valor, alcance, limite_total_usos, fechas vigenciaTablas: cupon_categorias, cupon_uso · Idx: codigo
categorianombre, slug (unique), icono, color, orden, activa, parent_idSelf-join (parent → hijos) · Idx: slug, parent_id
newsletter_suscripcionemail (unique), activa, ip_consentimiento, fecha_consentimiento, version_politica, token_baja (unique)Sin FK externas · Idx: email, token_baja
audit_logtipo_accion, descripcion, fecha_accionFK → actor (admin responsable) · Idx: actor_id, fecha_accion
spark_vototipo_voto (SPARK/DRIP), fechaFK → actor, oferta · Unique: usuario_id + oferta_id
favoritofechaFK → actor y producto/oferta/vehiculo (opc.) · Unique: usuario + item
notificacion_in_apptitulo, mensaje, tipo, url_destino, leida, destacada, fechaFK → actor · Idx: actor_id, leida, fecha

Documentos Adicionales

Plan de Proyecto

Cronograma, gestión de recursos y fases del desarrollo.

1. Introducción y contexto

Nexus es una idea muy simple: hacer más fácil la vida a la gente que quiere ahorrar. Hoy en día, si buscas un buen precio, tienes que abrir mil apps distintas, comparar en webs, revisar descuentos y mirar si alguien vende lo mismo más barato de segunda mano. Es un lío.

Por eso hemos pensado en Nexus, una aplicación gratuita que reúne todo eso en un solo lugar. Aquí puedes encontrar tanto productos nuevos con ofertas y descuentos, como artículos de segunda mano entre particulares. Todo junto, claro y sin perder tiempo. Las empresas también podrán colgar promociones de sus productos y hacer promoción de ellos.

La idea es que ahorrar deje de ser un esfuerzo y se convierta en algo natural mediante nuestra aplicación. Que puedas encontrar lo que buscas sin complicaciones, con una experiencia sencilla y agradable. Al final, Nexus no quiere ser solo otra app más, sino ese compañero del día a día que te echa una mano para cuidar tu dinero y aprovechar mejor tu tiempo.

2. Alcance del proyecto

Vamos a centrarnos en lo que es verdaderamente esencial para asegurarnos de que la primera versión de Nexus sea sólida y fácil de usar. No queremos lanzar una aplicación que haga mil cosas a medias, sino que haga pocas cosas a la perfección.

También nos gustaría que estén disponibles en varios sistemas operativos, pero debemos empezar por una aplicación web y después mediante Ionic + Capacitor compilar las aplicaciones móviles para Android e iOS (las cuáles compartirán un backend unificado).

Esta primera versión es nuestra carta de presentación al mundo. Por eso, planteamos con mucho cuidado qué podrás hacer en la app desde el primer día y qué mejoras y funciones increíbles iremos añadiendo después, escuchando siempre lo que nos digan nuestros usuarios.

La idea es construir una base fuerte y luego levantar el resto del edificio sobre ella. Cuando busques algo, no tendrás que preocuparte por si es nuevo o usado; nuestro buscador unificado encuentra todo a la vez y te lo muestra de forma ordenada.

Por supuesto, podrás crear tu propio perfil, con tu foto y una pequeña descripción, para que otros usuarios vean tu valoración, ya que la confianza es la base.

Y si algo te interesa, podrás ponerte en contacto de inmediato con el vendedor a través de un chat privado dentro de la app, como cualquier otra red social.

3. Cronograma del proyecto

Fase Actividades Principales Duración / Fechas Responsable
Fase 1: Hacer el plano Poner las ideas en papel, realizando bocetos y diseños de la interfaz. Decisión de colores, botones, categorías y elementos visuales. Noviembre 2025 - Diciembre 2025 Los tres integrantes
Fase 2: Construir el proyecto Desarrollo técnico de la aplicación. Implementación del backend con Spring (Java) y PostgreSQL, y del frontend con Angular. Diciembre 2025 - Marzo 2026 Los tres integrantes
Fase 3: Pulir y abrillantar Control de calidad, búsqueda y corrección de errores (bugs). Pruebas beta con usuarios reales para obtener feedback antes del lanzamiento. Abril 2026 - Mayo 2026 Los tres integrantes

4. Recursos

Para que Nexus sea una realidad, no nos queremos centrar en el dinero, sino el talento y la dedicación de un pequeño pero increíblemente enfocado equipo de tres desarrolladores.

Funcionaremos como una unidad compacta y multifuncional, donde los tres seremos los artesanos digitales responsables de dar vida a cada aspecto del proyecto. Esta estructura nos obliga a ser extremadamente ágiles, creativos y colaborativos, ya que las responsabilidades de gestión, diseño de la experiencia de usuario, arquitectura técnica y programación recaerá sobre nosotros. Por supuesto, además del equipo, necesitaremos los recursos financieros para poder dedicar el 100% de nuestro tiempo y energía a Nexus.

Además de programar Nexus (el servidor y la base de datos), alguno de nosotros asumirá de forma natural un rol de coordinación, organizando las tareas semanales y asegurándose de que el equipo siempre tenga claro el siguiente paso para cumplir con el ajustado calendario. Como Desarrolladores y Diseñadores de Experiencia (UX), nos encargaremos de construir todo lo que el usuario ve y toca en la pantalla del móvil. Nuestra misión es doble: no sólo escribiremos el código para que la aplicación funcione en iPhone y Android, sino que también nos encargaremos del diseño y la usabilidad.

5. Organización y gestión

Como somos solo tres programadores, no seremos como una empresa enorme. Aquí no hay jefes estrictos, papeleo innecesario ni juntas que se resolverán con un correo. Confiamos mucho el uno en el otro, hablamos directo y cada quien se hace responsable. Seremos como un equipo especial: rápidos, coordinados y con la meta bien clara. Para mantener algo de orden en todo esto, usaremos una versión sencilla de Scrum, que le viene bien a equipos chicos que necesitan ser rápidos y reaccionar ante cambios.

Cada dos semanas haremos un Sprint. Al comenzar, revisaremos la lista de cosas que queremos para la app y elegiremos las tareas más importantes. En eso nos enfocaremos las siguientes dos semanas. Así, dividimos el proyecto grande en partes pequeñas, y vemos el progreso todo el tiempo. Cada dos semanas tendremos algo nuevo de la aplicación que funciona, que podemos ver y probar.

Lo bueno de trabajar así es que somos un equipo muy eficiente y nos adaptamos fácil. No gastamos tiempo en planes que nunca se cumplen. Podemos probar ideas rápido, y si algo falla o se nos ocurre algo mejor, cambiamos sin problemas. Esto ayuda a que cada quien se sienta dueño de una parte del proyecto y nos mantiene motivados porque festejamos pequeñas victorias cada dos semanas. Con solo 6 meses, esta forma de trabajar es clave para lograrlo.

6. Gestión de riesgos

Hay tres cosas que nos preocupan mucho. Primero, el tiempo: seis meses es poco y no hay margen de error. Segundo, la competencia: hay empresas muy grandes en este mercado. Y tercero, el problema de quién va primero: necesitamos vendedores para que haya compradores, y al revés. Conocer estos problemas es clave para solucionarlos, y tenemos un plan para cada uno.

Para defendernos de estos problemas, vamos a usar nuestras ventajas como equipo pequeño. Para el problema del tiempo, seremos ágiles y nos centraremos en lo importante. Para la competencia, no competiremos en tamaño, sino creando una experiencia increíble para el usuario. Y para empezar con pocos usuarios, comenzaremos poco a poco: nos centraremos en algunas ciudades y categorías para crear una comunidad activa antes de expandirnos por todo el país. Usaremos los riesgos para impulsar nuestra estrategia.

Riesgo Probabilidad Impacto Plan de Mitigación
No cumplir con el plazo de 6 meses Media-baja Crítico Centrar el desarrollo en las funcionalidades esenciales (MVP) y escalar progresivamente según disponibilidad.
Mercado fantasma (baja tracción inicial) Alta Alto Marketing local enfocado, pruebas internas intensivas y captación de usuarios en círculos cercanos (amigos/familia).
Invisibilidad frente a competidores Media-alta Medio Posicionamiento basado en la propuesta de valor única: una solución "todo en uno" para ofertas y segunda mano.
Estafas o ambiente tóxico Media Crítico Implementación de sistemas de valoración de perfiles y funciones robustas de reporte y moderación comunitaria.

7. Aseguramiento de la calidad

Nuestro plan de calidad tiene tres bases. Primero, cada uno es responsable de su trabajo. Cada persona probará lo que hace para asegurarse de que todo esté bien hecho. Segundo, trabajamos en equipo. Revisamos el trabajo de los demás antes de añadirlo al proyecto. Así, tenemos otra opinión y encontramos errores que podríamos haber pasado por alto. Tercero, probamos las apps en situaciones reales. Antes de lanzarlas, les damos una versión de prueba a amigos y familiares para que la usen y nos den su opinión sincera.

Puede que este plan parezca que nos hace ir más lento, pero en realidad nos ayuda a avanzar más rápido. Es más fácil arreglar un error al principio que cuando ya lo está usando un cliente. Si un usuario encuentra un error, puede ser un problema grave y perder la confianza en la app. Queremos que la gente sienta que Nexus es una app profesional, sólida y confiable desde el primer momento.

Plan de Aseguramiento de la Calidad

8. Plan de comunicación

Tipo de Comunicación Frecuencia Medio Participantes
Comunicación presencial 2 horas semanales Sesiones presenciales en el centro educativo Los tres integrantes
Sincronización online Frecuente / Diaria Canales de Discord y herramientas de mensajería Los tres integrantes
Reunión de seguimiento Mensual Videollamada vía Discord Los tres integrantes
Informes y boletines Mensual Email y publicación de novedades en plataforma web Los tres integrantes
Exposiciones técnicas Hitos del proyecto Presentaciones presenciales en clase Los tres integrantes

9. Plan de cierre

Lanzar la app en mayo de 2026 no es el final, solo el fin del primer paso y el comienzo de algo mejor. El plan para terminar el proyecto es pasar de crear a crecer y mantener. Es como cuando un hijo empieza a caminar solo, nosotros lo guiaremos.

Para cerrar bien, haremos una revisión final para revisar que todo esté como queríamos desde el inicio. Luego, ordenaremos y explicaremos todo nuestro trabajo. Esto es importante para que después podamos mejorar la app sin problemas.

Al final, tendremos una reunión en equipo para hablar de cómo nos sentimos. No hablaremos de código, sino de cómo trabajamos, qué salió bien, qué hicimos mal y qué aprendimos.

Conclusión

Para mayo de 2026, esperamos tener la primera versión de Nexus lista y funcionando bien. Esto preparará la aplicación para crecer y mejorar fácilmente en el futuro. Durante este tiempo, revisaremos todo con cuidado, documentamos bien el proceso y analizaremos qué funcionó y qué no para aprender juntos. Así, nos aseguraremos de que la app ayude a la gente a ahorrar de manera práctica y con buena calidad.

Estudio de Viabilidad

Análisis económico, técnico y operativo del proyecto.

1. Introducción y objetivos

Nexus es una solución integral diseñada tanto para entornos web como móviles, que nace de la convergencia entre los principales portales de ofertas comunitarias y los marketplaces de compraventa entre particulares. El proyecto ofrece una plataforma unificada donde los usuarios pueden centralizar el descubrimiento de promociones destacadas, la gestión de productos de segunda mano y el acceso a anuncios corporativos en un ecosistema único.

El objetivo primordial es consolidar un espacio digital interactivo, con una estética cuidada y moderna, que permita a los usuarios:

  • Localizar y filtrar productos tanto nuevos como usados con facilidad.
  • Gestionar y publicar anuncios propios de forma intuitiva.
  • Participar activamente en la comunidad mediante valoraciones y votos ("likes"), definiendo la relevancia de las ofertas.
  • Acceder a un sistema logístico integrado con pasarela de pagos segura y gestión de comisiones.

2. Alcance preliminar

El alcance de este proyecto intermodular se centra en el desarrollo de una aplicación funcional multiplataforma (inicialmente Android con proyección a iOS) que integre las siguientes capacidades principales:

  • Dashboard Dinámico: Pantalla de inicio con visualización de ofertas destacadas y categorización inteligente de productos.
  • Marketplace de Segunda Mano: Espacio dedicado para que los usuarios publiquen y gestionen sus propios artículos.
  • Ranking de Popularidad: Sección de contenidos destacados, impulsada por la interacción social y el volumen de valoraciones positivas.
  • Búsqueda Unificada: Motor de búsqueda global capaz de indexar simultáneamente artículos nuevos, ofertas externas y productos usados.
  • Gestión de Identidad: Sistema de perfiles de usuario, historial de actividad y centro de control de publicaciones.
  • Sistema de Gestión de Envíos: Módulo logístico experimental para la gestión de envíos bajo un modelo de comisión controlado por la plataforma.

Exclusiones del Proyecto

Para asegurar la viabilidad técnica en los plazos establecidos, se han definido las siguientes exclusiones:

  • Transacciones Monetarias Reales: Aunque el sistema integra la infraestructura de Stripe, su funcionamiento está limitado al entorno de pruebas (Test Mode). No se realizarán cargos ni transferencias reales.

4. Viabilidad económica

El modelo financiero de Nexus se basa en una estructura de costes optimizada para el lanzamiento de una plataforma tecnológica en fase inicial, con un enfoque claro en la escalabilidad y la eficiencia operativa.

Estimación de Costes Iniciales

  • Desarrollo y Diseño UI/UX: Se estima una inversión de entre 6.000 € y 8.000 € para cubrir las fases de prototipado, desarrollo del MVP (Minimum Viable Product) y auditoría de seguridad inicial.
  • Infraestructura y Servicios: El mantenimiento de servidores, bases de datos y almacenamiento de medios se presupuesta en aproximadamente 500 € anuales mediante servicios cloud de pago por uso.
  • Marketing y Captación: Una partida inicial de 1.000 € destinada a campañas en redes sociales y posicionamiento SEO para captar los primeros usuarios activos.

Fuentes de Financiación y Monetización

El proyecto se financiará inicialmente mediante fondos propios o aportaciones de microinversores. La sostenibilidad financiera a largo plazo se apoyará en las siguientes vías de ingreso:

  • Comisiones de Gestión: Aplicación de un margen sobre las transacciones realizadas a través del sistema de envíos y pagos seguros.
  • Publicidad Destacada (B2B): Venta de espacios publicitarios premium y visibilidad mejorada para empresas y vendedores profesionales.

Análisis Costo-Beneficio

La inversión inicial es moderada y está dimensionada para minimizar el riesgo financiero. Nexus presenta un alto potencial de retorno (ROI) gracias a un modelo de negocio donde los ingresos crecen proporcionalmente al volumen de la comunidad, manteniendo unos costes operativos fijos muy reducidos.

5. Viabilidad operativa

La operatividad de Nexus se ha diseñado para maximizar la satisfacción del usuario final y ofrecer herramientas de gestión robustas para administradores y partners comerciales.

Impacto en la Experiencia de Usuario

La plataforma ofrece una interfaz minimalista y moderna que reduce la fricción en el proceso de compraventa. La integración de incentivos sociales (votos Spark/Drip y destacados dinámicos) fomenta una participación comunitaria activa y orgánica.

Impacto en el Ecosistema Empresarial

Nexus se posiciona como un canal publicitario eficiente para empresas, permitiéndoles promocionar su stock mediante anuncios patrocinados y productos con visibilidad preferente en el buscador global y en la pantalla de inicio.

Procesos de Gestión Interna

Para garantizar el correcto funcionamiento del sistema, se dispone de un Panel de Administración centralizado que permite:

  • Moderación y control de publicaciones y reportes de usuarios.
  • Seguimiento en tiempo real de estadísticas de uso y métricas de negocio.
  • Gestión de contratos publicitarios y estados de transacciones.

6. Viabilidad legal y normativa

Para garantizar un entorno seguro y de confianza, Nexus se desarrolla bajo el cumplimiento estricto del marco legal vigente en materia de comercio electrónico y protección de datos.

  • Protección de Datos (RGPD): Implementación de políticas de privacidad rigurosas conforme al Reglamento General de Protección de Datos (UE 2016/679). Esto incluye la gestión de derechos de acceso, rectificación, supresión y el consentimiento explícito para el tratamiento de información personal.
  • Seguridad Jurídica: Definición de Términos y Condiciones de Uso detallados que regulan las responsabilidades de compradores, vendedores y de la propia plataforma, asegurando la transparencia en cada operación.
  • Propiedad Intelectual: Protección de los activos digitales de la marca y establecimiento de normativas claras sobre los derechos de uso y copyright de los contenidos subidos por los usuarios a la plataforma.

7. Riesgos iniciales

Como todo proyecto tecnológico, Nexus se enfrenta a una serie de desafíos críticos. A continuación, se detallan los riesgos identificados y las estrategias diseñadas para mitigar su impacto:

Riesgo Descripción Medida de Mitigación
Competencia consolidada Existencia de plataformas líderes especializadas en nichos específicos (ofertas o compraventa). Diferenciación mediante la integración de ambos servicios en una única UX y un sistema social de valoración único.
Baja adopción inicial Dificultad para atraer a la masa crítica de usuarios necesaria para que el sistema de votos sea efectivo. Implementación de campañas de lanzamiento dirigidas y programas de bonificación por registro y actividad.
Contenido fraudulento Riesgo de publicación de artículos falsos, duplicados o intentos de estafa entre particulares. Uso de filtros automáticos basados en IA para moderación preventiva y un sistema robusto de reportes comunitarios.
Sostenibilidad de costes Incremento imprevisto en los costes de mantenimiento de servidores y APIs de terceros. Estructura de escalado progresivo y optimización de recursos según el volumen real de tráfico y usuarios.

8. Conclusión y recomendación

Tras el análisis exhaustivo de los factores técnicos, operativos y financieros, se concluye que el proyecto Nexus es altamente viable y presenta una oportunidad estratégica sólida en el mercado actual de aplicaciones multiplataforma.

Su enfoque innovador, que logra converger con éxito el dinamismo de las ofertas virales comunitarias con la robustez de un marketplace de compraventa entre particulares, le otorga una ventaja competitiva diferencial. La estructura de costes contenida y la escalabilidad de su arquitectura tecnológica aseguran que el proyecto pueda crecer de forma sostenible.

Recomendación final: Se recomienda proceder con la fase de implementación del MVP, priorizando la consolidación de la comunidad de usuarios inicial y la optimización de los algoritmos de relevancia social, elementos críticos para asegurar el éxito y la tracción del ecosistema Nexus.

Requisitos del Sistema

Proyecto: Nexus  ·  Versión: 2.0 (Producción)  ·  Fuente: Análisis del código fuente real (backend + frontend)

Nexus es una plataforma web de comercio y comunidad que unifica tres grandes módulos en un único ecosistema digital:

Marketplace de segunda mano — compraventa de productos y vehículos entre particulares con pagos seguros mediante escrow.

Chollometro comunitario — publicación y votación de ofertas externas de tiendas online y físicas con sistema de puntuación Spark/Drip.

Sistema de publicidad para empresas — contratos de patrocinio (banners y productos destacados) gestionados y pagados con Stripe.

El sistema está compuesto por un backend Spring Boot 3 (Java 17) con API REST y WebSocket STOMP, y un frontend Angular 17+ con Standalone Components, Signals reactivos y lazy loading total. La base de datos es PostgreSQL, el almacenamiento de medios se externaliza a Cloudinary y los pagos a Stripe.

Actores del Sistema

El sistema define cuatro tipos de actores representados mediante una jerarquía de herencia con una entidad base común Actor:

Visitante

  • No registrado
  • Acceso de solo lectura al catálogo
  • Puede registrarse o iniciar sesión
  • Interacción limitada (no puede publicar ni comprar)

Usuario

  • Registro con email o redes sociales
  • Puede publicar productos, vehículos y ofertas
  • Compra y venta con pagos seguros
  • Chat en tiempo real, favoritos, valoraciones
  • Puede convertirse en Empresa

Empresa

  • Perfil empresarial con CIF y nombre comercial
  • Acceso a contratos publicitarios (banners, patrocinios)
  • Pago de contratos mediante Stripe Checkout
  • Publicación de contenido como empresa verificada

Administrador

  • Panel de gestión exclusivo (dominio separado)
  • Moderación de usuarios, contenido y reportes
  • Gestión de contratos y publicidad
  • Estadísticas, auditoría y configuración global
  • Niveles de acceso (1, 2 o 3)

Requisitos de Información

Actores y Perfiles

RI-01

Información Común a Todos los Actores

De todos los actores del sistema se almacenará:

  • Identificación: nombre de usuario (único), email (único), contraseña (hash bcrypt), nombre, apellidos, teléfono.
  • Avatar: URL del avatar actual, fuente del avatar (GOOGLE, INITIALS, CUSTOM), URL del avatar de Google, URL del avatar personalizado.
  • Estado de cuenta: cuentaVerificada, cuentaEliminada (soft-delete RGPD), baneado, motivoBan, suspendidoHasta, motivoSuspension.
  • OAuth: googleId para autenticación con Google OAuth.
  • Seguridad: twoFactorEnabled, twoFactorMethod (TOTP o EMAIL), twoFactorSecret (cifrado), jwtVersion (invalidación de sesiones).
  • Sesión: ultimoIp, ultimoDispositivo, ultimaUbicacion, ultimoLogin.
  • Stripe: stripeCustomerId para pagos guardados.
  • Onboarding: onboardingCompletado para el wizard de bienvenida.

RI-02

Información Adicional del Usuario (Particular)

Adicionalmente a RI-01, del actor de tipo Usuario se almacenará:

  • biografía, ubicación, tipoCuenta (PERSONAL), fechaRegistro.
  • Reputación: reputación (media 0.0–5.0), totalVentas, esVerificado.
  • Privacidad: cuentaPrivada, perfilPublico, mostrarTelefono, mostrarUbicacion, permitirMensajesDesconocidos.
  • Preferencias de notificación: notifNuevosMensajes, notifNuevaCompra, notifValoracion, notifOfertas, notifEnvios, notifNovedades.
  • Dirección de envío por defecto (embebida): nombre completo, dirección, ciudad, código postal, país, teléfono.
  • Términos: terminosAceptados, versionTerminosAceptados, fechaAceptacionTerminos, newsletterSuscrito.

RI-03

Información Adicional de la Empresa

Adicionalmente a RI-01, de la Empresa se almacenará:

  • CIF (único), nombreComercial, descripción, web, teléfono, logo (URL), verificada (por administrador).

RI-04

Información Adicional del Administrador

Adicionalmente a RI-01, del Administrador se almacenará:

  • nivelAcceso (entero: 1 básico, 2 moderador, 3 superadmin).

Catálogo, Comercio y Logística

RI-05

Productos de Segunda Mano

De cada producto publicado en el marketplace se almacenará:

  • título, descripción, precio (EUR), precioNegociable, tipoOferta (VENTA, DONACION, INTERCAMBIO, SUBASTA, ALQUILER).
  • estado (DISPONIBLE, RESERVADO, VENDIDO, PAUSADO, ELIMINADO, EXPIRADO), condición (NUEVO, COMO_NUEVO, MUY_BUEN_ESTADO, BUEN_ESTADO, ACEPTABLE, PARA_PIEZAS).
  • categoría (FK), vendedor (FK Actor), marca, modelo.
  • Envío: admiteEnvio, precioEnvio, peso (kg).
  • Ubicación: ubicación (texto), latitud, longitud.
  • Imágenes: imagenPrincipal (URL Cloudinary), galeriaImagenes (hasta 5 URLs).
  • Métricas: numeroVistas, numeroFavoritos.
  • Ciclo de vida: fechaPublicacion, fechaCaducidad (publicación + 180 días), ultimoAvisoCaducidadDias.
  • Moderación admin: pausadoHasta, motivoPausa, destacado, patrocinado.

RI-06

Vehículos

Los vehículos extienden los campos de producto con información técnica específica:

  • tipoVehiculo (COCHE, MOTO, FURGONETA, SCOOTER, OTRO), marca, modelo, año, kilómetros.
  • combustible (GASOLINA, DIESEL, HÍBRIDO, ELÉCTRICO, GLP, GNC), cambio (MANUAL, AUTOMÁTICO), potencia (CV), cilindrada (cc).
  • color, numeroPuertas, plazas, matrícula (parcialmente oculta en público).
  • itv (booleano), fechaITV, garantía (booleano), extras (lista de características adicionales).

RI-07

Ofertas (Chollometro)

De cada oferta/chollo publicado se almacenará:

  • título, descripción, precioOferta, precioOriginal, tienda, urlOferta.
  • categoría (FK), publicador (FK Actor), estado (ACTIVA, PAUSADA, AGOTADA), esActiva.
  • badge automático (NUEVA, CHOLLAZO, PORCENTAJE, GRATUITA, EXPIRA_HOY), codigoDescuento.
  • Votos: sparkCount (positivos), dripCount (negativos), sparkScore (diferencia).
  • Métricas: numeroVistas, numeroCompartidos, numeroComentarios.
  • Flash: esFlash, flashFin, limiteUnidades, fechaExpiracion.
  • Localización: esOnline, ciudadOferta, gastosEnvio.
  • destacada (admin), imagenPrincipal (URL).

RI-08

Compras y Transacciones

De cada transacción de compra se almacenará:

  • comprador (FK), vendedor (FK), producto (FK), estado (PENDIENTE, PAGADO, ENVIADO, ENTREGADO, EN_DISPUTA, REEMBOLSADA, CANCELADA, COMPLETADA).
  • Importes: precioFinal, precioEnvio, costoEnvio, comisionNexus.
  • Stripe: stripePaymentIntentId, stripeChargeId.
  • Entrega: metodoEntrega, tipoEnvio, transportista, dirección completa de entrega.
  • Fechas: fechaCompra, fechaPago, fechaEnvio, fechaEntrega, fechaCompletada.

RI-09

Envíos y Devoluciones

De cada envío se almacenará: código único SHIP-XXXXXXXX, código QR (base64), transportista, número de seguimiento, URL de tracking, fechaLimiteEnvio.

De cada devolución se almacenará: motivo (PRODUCTO_DEFECTUOSO, NO_CORRESPONDE, DAÑO_TRANSPORTE, CAMBIO_DE_OPINIÓN, TALLA_INCORRECTA, OTRO), descripción, fotos de evidencia (hasta 4), dirección de devolución, nota del vendedor, importe devuelto, estado (SOLICITADA, ACEPTADA, RECHAZADA, COMPLETADA).

Social y Comunicación

RI-10

Mensajes de Chat

De cada mensaje de chat se almacenará: remitente (FK), receptor (FK), producto asociado (FK, opcional), texto, URL de media (imagen/vídeo/audio), duración de audio, tipo de mensaje (TEXTO, IMAGEN, VIDEO, AUDIO, GIF, OFERTA_PRECIO, SISTEMA), fecha de envío, leído, recibido, precio propuesto (en caso de propuesta de precio), estado de la propuesta (PENDIENTE, ACEPTADA, RECHAZADA), roomId (identificador único de la conversación).

RI-11

Valoraciones

De cada valoración se almacenará: comprador (FK), vendedor (FK), compra (FK, único — una valoración por compra), puntuación (1–5 estrellas), comentario opcional, respuesta del vendedor, fecha de respuesta.

RI-12

Categorías

Las categorías forman un árbol jerárquico (padre–hijos). De cada categoría: nombre, slug (único), icono, color, orden (para posición en menú), activa, referencia al padre (FK opcional).

RI-13

Notificaciones

De cada notificación in-app se almacenará: destinatario (FK Actor), título, mensaje, tipo (NUEVA_COMPRA, MENSAJE, ALERTA, ADVERTENCIA, FAVORITO_PRODUCTO, CADUCIDAD_ANUNCIO, DEVOLUCION, ENVIO_PLAZO, etc.), URL de destino, estado (leída, destacada), fecha.

RI-14

Reportes

De cada denuncia se almacenará: denunciante (FK), tipo de elemento denunciado (USUARIO, PRODUCTO, OFERTA, VEHICULO, MENSAJE, COMENTARIO), motivo (SPAM, FRAUDE, CONTENIDO_INAPROPIADO, PRODUCTO_FALSO, ACOSO, PRECIO_INCORRECTO, DUPLICADO, OTRO), descripción, estado (PENDIENTE, RESUELTO, DESESTIMADO).

Negocio y Publicidad

RI-15

Contratos Publicitarios

De cada contrato se almacenará: empresa (FK), tipo (BANNER, PUBLICACION), estado (DRAFT, PROPUESTA_ADMIN, PENDIENTE_PAGO, ACTIVE, RECHAZADO, EXPIRED, CANCELLED), fechas de inicio y fin, monto, producto patrocinado asociado (FK, opcional), stripeSessionId.

RI-16

Cupones de Descuento

De cada cupón se almacenará: código (único, máx. 20 caracteres), tipo de descuento (PORCENTAJE, FIJO), valor, importe mínimo de compra, tope máximo de descuento, alcance (TODOS, USUARIO_ESPECIFICO, GRUPO), límite de uso total, límite de uso por usuario, fechas de vigencia, categorías aplicables.

RI-17

Newsletter y Consentimiento RGPD

De cada suscripción al newsletter se almacenará: email, estado (activa/inactiva), IP del consentimiento, fecha del consentimiento, versión de la política de privacidad aceptada, token de baja (único), fecha de baja. Permite acreditar el consentimiento explícito conforme al artículo 7 del RGPD.

Requisitos Funcionales

Los requisitos funcionales describen las capacidades concretas que el sistema debe ofrecer a cada tipo de actor.

1

Actor No Registrado (Visitante) Público

Un visitante sin cuenta podrá realizar las siguientes acciones sin necesidad de autenticación:

RF-01

Exploración del Catálogo de Productos

El visitante podrá consultar el listado completo de productos de segunda mano disponibles, con acceso al detalle de cada uno: imágenes, descripción, precio, condición, ubicación del vendedor e información pública del vendedor (reputación, total de ventas, valoraciones).

RF-02

Exploración del Catálogo de Vehículos

El visitante podrá consultar el catálogo de vehículos con su ficha técnica completa (marca, modelo, año, kilómetros, combustible, equipamiento, galería de imágenes).

RF-03

Exploración de Ofertas y Chollos

El visitante podrá ver las ofertas publicadas por la comunidad, incluyendo precio original, precio de oferta, porcentaje de descuento, tienda, badge de categoría y puntuaciones Spark/Drip. Podrá acceder también a las secciones especializadas: Ofertas Flash (con cuenta atrás), Viajes y Gratis.

RF-04

Búsqueda y Filtrado Avanzado

El visitante podrá realizar búsquedas globales que abarquen simultáneamente productos, vehículos y ofertas. Dispondrá de los siguientes filtros:

  • Texto libre con expansión de sinónimos (ej.: "ps5" encuentra "PlayStation 5").
  • Categoría, rango de precio (mínimo y máximo), condición del artículo.
  • Antigüedad de la publicación (hoy, últimos 3 días, última semana, último mes).
  • Disponibilidad de envío, ordenación (novedades, precio asc/desc).
  • Filtros específicos de vehículos: marca, modelo, año, kilómetros, combustible, cambio, potencia, color, puertas, plazas, ITV y garantía.
  • Filtro geográfico por ubicación (manual o GPS) con radio ajustable en kilómetros y mapa interactivo.

RF-05

Consulta de Perfiles Públicos

El visitante podrá ver el perfil público de cualquier usuario (si este no lo tiene privado), incluyendo sus publicaciones activas, valoraciones recibidas, estadísticas de vendedor y descripción.

RF-06

Consulta de Categorías

El visitante podrá navegar el árbol jerárquico de categorías para filtrar el contenido disponible.

RF-07

Suscripción al Newsletter

El visitante podrá suscribirse al newsletter proporcionando su email. El sistema implementará double opt-in: enviará un correo de confirmación y solo activará la suscripción tras la confirmación. El visitante podrá darse de baja en cualquier momento mediante un token único incluido en cada email.

RF-08

Iniciación de Chat de Soporte

El visitante podrá iniciar una sesión de chat de soporte con el asistente virtual (IA) sin necesidad de estar registrado.

2

Registro y Autenticación Público

RF-09

Registro por Correo Electrónico

El visitante podrá crear una cuenta proporcionando: nombre de usuario (único), email (único, validado en tiempo real), contraseña (con medidor de fortaleza: longitud, mayúsculas, números y caracteres especiales), nombre, apellidos, aceptación de términos y condiciones, y opción de suscripción al newsletter. El sistema validará la disponibilidad del email y del nombre de usuario de forma asíncrona antes de confirmar el registro.

RF-10

Verificación de Email

Tras el registro, el sistema enviará un código de 6 dígitos al correo del usuario. La cuenta deberá ser verificada antes de poder acceder a todas las funcionalidades.

RF-11

Registro e Inicio de Sesión con Google

El visitante podrá registrarse e iniciar sesión mediante Google OAuth (Google One Tap y botón clásico), importando automáticamente nombre, email y avatar del perfil de Google.

RF-12

Inicio de Sesión con Email y Contraseña

El usuario registrado podrá autenticarse con email o nombre de usuario y contraseña. El sistema incluirá protección ante ataques de fuerza bruta mediante reCAPTCHA y limitación de intentos (rate limiting con retroalimentación visual del tiempo de espera).

RF-13

Autenticación en Dos Factores (2FA)

El usuario podrá configurar un segundo factor de autenticación eligiendo entre:

  • TOTP: código de 6 dígitos generado por una app autenticadora (Google Authenticator, Authy). El sistema generará un código QR para el proceso de vinculación.
  • Email OTP: código de un solo uso enviado al correo del usuario en cada inicio de sesión.

RF-14

Recuperación de Contraseña

El usuario podrá solicitar el restablecimiento de su contraseña introduciendo su email. El sistema enviará un enlace único con token (válido 15 minutos) para establecer una nueva contraseña.

RF-15

Onboarding Inicial

Tras el primer registro o acceso OAuth, el sistema guiará al nuevo usuario por un asistente de configuración inicial con los pasos: aceptación de términos, configuración de seguridad (2FA opcional), selección del tipo de cuenta (Personal o Empresa) y, para usuarios de Google, selección del avatar (foto de Google, iniciales o imagen propia).

3

Gestión de Cuenta y Perfil Autenticado

RF-17

Edición del Perfil Personal

El usuario podrá actualizar su nombre, apellidos, biografía, ubicación (con buscador y detección automática por GPS), teléfono y avatar (subir imagen propia, usar iniciales generadas automáticamente o recuperar la foto de Google si se registró con OAuth).

RF-18

Configuración de Privacidad

El usuario podrá controlar la visibilidad de su ubicación y teléfono en el perfil público, así como si desea recibir mensajes de usuarios desconocidos.

RF-19

Configuración de Notificaciones

El usuario podrá activar o desactivar individualmente las notificaciones para: nuevos mensajes, nueva compra, valoraciones recibidas, alertas de ofertas, actualizaciones de envíos y novedades de la plataforma.

RF-20

Cambio de Contraseña

El usuario podrá cambiar su contraseña desde la configuración de seguridad, previa verificación de la contraseña actual.

RF-21

Gestión de Sesiones Activas

El usuario podrá ver las sesiones activas registradas en su cuenta (dispositivo, IP, fecha) y cerrar todas las sesiones remotas simultáneamente, lo que invalidará todos los tokens JWT en circulación.

RF-22

Conversión de Cuenta Personal a Empresa

El usuario podrá convertir su cuenta de tipo Personal a Empresa proporcionando CIF (validado), nombre comercial, descripción, web y teléfono de empresa.

RF-23

Gestión de Métodos de Pago

El usuario podrá añadir tarjetas de crédito/débito mediante Stripe (con SetupIntent), ver las tarjetas guardadas (mostrando marca, últimos 4 dígitos y fecha de vencimiento) y eliminarlas.

RF-24

Exportación de Datos Personales

El usuario podrá descargar un fichero JSON con todos sus datos almacenados en el sistema (derecho de acceso, art. 15 RGPD).

RF-25

Eliminación de Cuenta

El usuario podrá solicitar la eliminación de su cuenta confirmando la acción (requiere escribir "ELIMINAR" y verificar contraseña o confirmar si es usuario OAuth). La cuenta se marcará como eliminada (soft-delete) y el email se anonimizará, preservando la integridad referencial de la base de datos (derecho al olvido, art. 17 RGPD).

4

Marketplace y Publicaciones Autenticado

RF-26

Publicación de Productos de Segunda Mano

El usuario podrá publicar anuncios de segunda mano mediante un asistente en tres pasos:

  • Paso 1: Selección de categoría y condición del artículo.
  • Paso 2: Título, descripción (con editor de texto enriquecido y plantillas predefinidas), imágenes (múltiples, con arrastre para reordenar y soporte de pegado desde portapapeles), marca y modelo.
  • Paso 3: Precio (con opción de marcar como negociable), tipo de oferta (VENTA, INTERCAMBIO, DONACIÓN), configuración de envío (peso y precio) y ubicación geográfica.

El sistema validará el contenido del título y la descripción contra la lista de palabras prohibidas antes de aceptar la publicación.

RF-27

Publicación de Vehículos

El usuario podrá publicar anuncios de vehículos mediante un asistente en cuatro pasos: selección del tipo de vehículo, detalles técnicos (marca desde buscador externo, modelo, año, kilómetros, combustible, cambio, potencia, color, puertas, plazas, ITV, garantía), condición y precio, y descripción con imágenes.

RF-28

Publicación de Ofertas / Chollos

El usuario podrá compartir ofertas de tiendas externas proporcionando la URL del producto (el sistema extraerá automáticamente el favicon y nombre de la tienda), título, descripción, precio original, precio de oferta, tienda, código de descuento, categoría, tipo de oferta (online o presencial), gastos de envío e imágenes. El sistema calculará y mostrará en tiempo real el badge correspondiente.

RF-29

Gestión de Publicaciones Propias

El usuario podrá, desde su panel de perfil, gestionar sus publicaciones activas:

  • Editar cualquier campo del anuncio, incluyendo imágenes.
  • Cambiar el estado del producto (DISPONIBLE, RESERVADO, VENDIDO).
  • Pausar y reactivar anuncios.
  • Eliminar el anuncio (soft-delete).
  • Renovar un anuncio expirado, reiniciando su ciclo de vida a 180 días.

RF-30

Sistema de Votación Spark / Drip

Los usuarios registrados podrán votar las ofertas y productos de la comunidad con Spark (voto positivo) o Drip (voto negativo). El sistema admite cambiar el voto y cancelarlo (toggle). La puntuación resultante determina el ranking de chollos en tiempo real.

RF-31

Favoritos

El usuario podrá guardar y eliminar productos, vehículos y ofertas en su lista de favoritos. Los cambios se reflejarán inmediatamente en la interfaz (optimistic UI) y serán revertidos si la operación falla en el servidor.

RF-32

Bloqueo de Usuarios

El usuario podrá bloquear a otros usuarios desde su perfil público. Los usuarios bloqueados no verán sus anuncios mutuamente en búsquedas, y no podrán enviarse mensajes entre sí.

5

Proceso de Compra y Pagos Autenticado

RF-33

Proceso de Checkout

El usuario comprador podrá adquirir un producto a través de un proceso guiado que incluye:

  • Selección del método de entrega: envío a domicilio (con selección de transportista: Correos, SEUR, MRW), recogida en punto de conveniencia o entrega en mano.
  • Introducción o selección de dirección de entrega con opción de guardarla como predeterminada.
  • Aplicación de cupones de descuento (validados en tiempo real).
  • Pago con tarjeta nueva (Stripe Card Element) o con tarjeta guardada previamente.
  • Visualización del desglose de costes: precio del producto, envío y comisión de Nexus.

RF-34

Negociación de Precio por Chat

El usuario comprador podrá proponer un precio alternativo al vendedor directamente desde el chat de la conversación del producto. El vendedor podrá aceptar o rechazar la propuesta. Si es aceptada, el precio negociado se usará automáticamente como base del pago en el checkout.

RF-35

Seguimiento del Estado de la Compra

El comprador y el vendedor podrán consultar el estado de cada transacción mediante un timeline visual que refleja los hitos: Pago Confirmado, Enviado, Entregado, Completada. El comprador recibirá notificaciones en cada cambio de estado.

RF-36

Confirmación de Recepción

El comprador podrá confirmar la recepción del producto, lo que liberará los fondos al vendedor y permitirá emitir una valoración. Si el comprador no confirma en un plazo de 7 días desde la entrega registrada, el sistema confirmará automáticamente la transacción.

RF-37

Gestión de Devoluciones

El comprador podrá solicitar una devolución de un producto en estado ENTREGADO o COMPLETADO dentro de los 7 días siguientes a la entrega. El proceso requiere la selección de un motivo, descripción detallada (mínimo 50 caracteres) y fotografías de evidencia (obligatorias salvo en cambio de opinión). El vendedor podrá aceptar o rechazar la solicitud. Una vez el vendedor confirme la recepción de la devolución, se iniciará automáticamente el reembolso en Stripe.

RF-38

Valoración del Vendedor

El comprador podrá valorar al vendedor (de 1 a 5 estrellas) con un comentario opcional una vez completada la transacción. Cada compra admite una única valoración. El sistema detectará automáticamente las compras pendientes de valorar y sugerirá emitirla al usuario.

RF-39

Respuesta a Valoraciones

El vendedor podrá responder públicamente a cualquier valoración recibida, una única vez por valoración.

6

Envíos y Logística Autenticado

RF-40

Gestión del Envío por el Vendedor

El vendedor recibirá un código de envío único (SHIP-XXXXXXXX) con código QR para depositar el paquete en la oficina del transportista seleccionado. Podrá introducir el número de seguimiento real proporcionado por el transportista. El sistema mostrará los puntos de recogida más cercanos con enlace a Google Maps.

RF-41

Seguimiento del Envío

El comprador podrá consultar el estado del seguimiento de su paquete en tiempo real, con actualización automática periódica desde la API del transportista.

RF-42

Cálculo de Tarifas de Envío

El sistema calculará automáticamente el coste de envío en función del peso del paquete y del transportista seleccionado (Correos, SEUR, MRW), mostrando el desglose antes de confirmar la compra.

7

Interacción Social y Comunicación Autenticado

RF-43

Chat en Tiempo Real

El usuario podrá mantener conversaciones en tiempo real con otros usuarios, asociadas a un producto concreto o como mensajes directos. El chat soporta los siguientes tipos de contenido: texto, imágenes, vídeos, mensajes de audio grabados en la aplicación y GIFs animados. El sistema mostrará confirmaciones de entrega (✓✓) y de lectura (✓✓ azules) en tiempo real, así como el indicador "escribiendo..." con debounce.

RF-44

Notificaciones en Tiempo Real

El usuario recibirá notificaciones instantáneas mediante WebSocket para los eventos relevantes (nuevos mensajes, compras, cambios de estado de envío, alertas de caducidad de anuncios, etc.). Las notificaciones se mostrarán como toasts en la interfaz y se registrarán en el centro de notificaciones. El usuario podrá marcarlas como leídas (individual o todas), destacarlas y eliminarlas.

RF-45

Comentarios en Ofertas

Los usuarios podrán comentar las ofertas publicadas para compartir información adicional, alertar de cambios de precio o resolver dudas de la comunidad.

RF-46

Reportes de Contenido

El usuario podrá denunciar cualquier contenido inapropiado (usuarios, productos, ofertas, vehículos, mensajes o comentarios), seleccionando el motivo de la denuncia y añadiendo una descripción detallada. Las denuncias serán revisadas por el equipo de administración.

8

Actor Empresa Empresa

RF-47

Consulta de Contratos Publicitarios Propios

La empresa podrá consultar el listado de todos sus contratos publicitarios con el estado actual de cada uno y los detalles de cada propuesta recibida del equipo de administración.

RF-48

Aceptación y Pago de Contratos

La empresa podrá aceptar una propuesta de contrato recibida del administrador, lo que redirigirá a una sesión de pago segura en Stripe Checkout. Una vez completado el pago, el contrato pasará a estado ACTIVE y el contenido patrocinado comenzará a mostrarse en la plataforma.

RF-49

Rechazo de Propuestas

La empresa podrá rechazar una propuesta de contrato que no sea de su interés.

9

Actor Administrador Admin

RF-50

Panel de Control y Estadísticas

El administrador dispondrá de un panel de control con métricas clave del sistema: total de usuarios, productos activos, compras realizadas, ingresos por comisiones (diarios, mensuales y anuales), top vendedores, últimas transacciones y últimos reportes pendientes.

RF-51

Gestión de Usuarios

El administrador podrá:

  • Listar y buscar todos los actores del sistema con sus datos completos.
  • Verificar manualmente la cuenta de un usuario.
  • Suspender temporalmente una cuenta (indicando duración y motivo).
  • Banear permanentemente una cuenta (con motivo visible al usuario).
  • Desbanear una cuenta baneada.
  • Marcar y desmarcar un usuario como sospechoso de fraude (flag interno, no visible al usuario).
  • Crear nuevas cuentas de administrador.

RF-52

Moderación de Contenido

El administrador podrá revisar, pausar (con motivo visible al vendedor), despausar, destacar y eliminar productos, ofertas y vehículos. Podrá gestionar la lista de palabras prohibidas que se aplican automáticamente al publicar cualquier contenido.

RF-53

Gestión de Reportes

El administrador podrá ver el listado de denuncias pendientes, consultar el detalle de cada una y resolverla (tomando acción sobre el contenido denunciado) o desestimar la denuncia.

RF-54

Gestión de Contratos Publicitarios

El administrador podrá crear propuestas de contrato para cualquier empresa registrada (tipo BANNER o PUBLICACION, con duración y precio), actualizar el estado de contratos existentes y eliminar contratos.

RF-55

Gestión de Categorías

El administrador podrá crear, editar (nombre, icono, color, orden, estado activo) y eliminar categorías del árbol jerárquico. No podrá eliminar una categoría que tenga contenido activo asociado.

RF-56

Gestión de Cupones de Descuento

El administrador podrá crear, editar, activar y desactivar cupones de descuento, configurando tipo (porcentaje o importe fijo), valor, alcance, límites de uso y vigencia.

RF-57

Soporte de Administrador (Takeover)

El administrador podrá ver todas las sesiones de soporte activas, tomar el control de cualquier conversación (reemplazando al bot de IA), responder mensajes directamente al usuario, emitir reembolsos desde la interfaz de soporte y cerrar sesiones. Podrá también solicitar una encuesta de satisfacción al usuario al finalizar.

RF-58

Auditoría y Trazabilidad

Todas las acciones administrativas sensibles (verificar, suspender, banear, resolver reporte, emitir reembolso) quedarán registradas en un log de auditoría inmutable con el administrador responsable, la acción realizada, la entidad afectada, la IP de origen y el timestamp.

RF-59

Notificaciones Masivas

El administrador podrá enviar notificaciones in-app personalizadas a usuarios específicos del sistema.

RF-60

Gestión de Compras y Reembolsos

El administrador podrá consultar el historial completo de compras del sistema y forzar el reembolso total de cualquier transacción en caso de disputa o resolución a favor del comprador.

10

Soporte al Usuario Público Autenticado

RF-61

Chat de Soporte con IA

El sistema dispondrá de un asistente virtual de soporte (widget flotante) accesible desde cualquier página. El bot responderá preguntas frecuentes sobre envíos, pagos, devoluciones y cuenta en español, con un tono cercano y profesional. El sistema mantendrá el historial de los últimos 10 mensajes como contexto para cada respuesta.

RF-62

Escalación a Agente Humano

El sistema detectará cuando el usuario solicita hablar con una persona real y mostrará la opción de escalación al equipo de soporte humano. Si un administrador está disponible, podrá tomar el control de la conversación en tiempo real.

RF-63

Encuesta de Satisfacción del Soporte

Al finalizar una sesión de soporte, el sistema podrá solicitar al usuario una valoración de la atención recibida (1 a 5 estrellas) con un comentario opcional.

Requisitos No Funcionales

Seguridad y Privacidad

RNF-01

Autenticación Stateless con JWT

El sistema implementará autenticación completamente stateless basada en tokens JWT. El servidor no mantendrá estado de sesión. Cada token incluirá un campo jwtVersion que permite la invalidación masiva inmediata de todos los tokens de un usuario (al cerrar sesión en todos los dispositivos, al completar el onboarding o al eliminar la cuenta). El interceptor HTTP del frontend verificará la caducidad del token en cada petición y lo eliminará localmente si está expirado.

RNF-02

Almacenamiento Seguro de Contraseñas

Las contraseñas se almacenarán exclusivamente como hashes bcrypt generados por el PasswordEncoder de Spring Security. Ningún componente del sistema tendrá acceso a las contraseñas en texto plano. El campo password estará marcado con @JsonIgnore y nunca se incluirá en ninguna respuesta JSON de la API.

RNF-03

Comunicación Segura (HTTPS)

Todas las comunicaciones entre el cliente y el servidor se realizarán exclusivamente mediante HTTPS en el entorno de producción. El frontend en Vercel (nexus-app.es) y el backend en Render (api.nexus-app.es) estarán servidos únicamente sobre TLS.

RNF-04

Protección CSRF y CORS

La protección CSRF está deshabilitada intencionalmente (arquitectura REST stateless sin cookies de sesión). La política CORS será explícita y restrictiva, permitiendo exclusivamente los orígenes autorizados: dominios nexus-app.es, subdominios mediante patrón wildcard (*.nexus-app.es) y despliegues de preview en Vercel (*.vercel.app). No se admitirá el patrón * (cualquier origen) en ningún entorno.

RNF-05

Protección contra Bots y Abuso

Los formularios de registro y recuperación de contraseña estarán protegidos con Google reCAPTCHA v2. El sistema implementará rate limiting en el endpoint de login; si se superan los intentos permitidos, la respuesta incluirá un código 429 y el frontend mostrará un contador de espera al usuario.

RNF-06

Cumplimiento RGPD

El sistema garantizará el cumplimiento del Reglamento General de Protección de Datos (RGPD / GDPR) mediante los siguientes mecanismos:

  • Derecho de acceso (art. 15): Los usuarios podrán exportar todos sus datos en formato JSON en cualquier momento.
  • Derecho al olvido (art. 17): Los usuarios podrán eliminar su cuenta, lo que resultará en un soft-delete con anonimización del email (deleted_{id}@nexus.deleted), preservando la integridad referencial.
  • Consentimiento informado (art. 7): El newsletter utilizará double opt-in. Se registrará la IP, la fecha y la versión de la política de privacidad aceptada en cada suscripción.
  • Derecho de supresión del newsletter: Cada email incluirá un token de baja único. El usuario podrá darse de baja sin necesidad de iniciar sesión.
  • Aceptación explícita de T&C: Los usuarios deberán aceptar los términos y condiciones durante el registro, almacenándose la versión aceptada y la fecha.

RNF-07

Moderación de Contenido Automática

El sistema aplicará un filtro automático sobre el título y la descripción de cualquier publicación antes de persistirla. El filtro usará una lista base de términos prohibidos combinada con una lista dinámica configurable por el administrador en tiempo real desde el panel de administración, sin necesidad de reiniciar el servidor.

Rendimiento y Escalabilidad

RNF-08

Carga Inicial Optimizada (Lazy Loading)

Todas las rutas del frontend Angular utilizarán carga diferida (loadComponent). El bundle inicial de la aplicación será mínimo; los módulos de cada sección se cargarán únicamente cuando el usuario navegue a ellas, reduciendo el tiempo de carga inicial.

RNF-09

Paginación Universal

Todos los listados del sistema utilizarán paginación del lado del servidor con Page<T> y parámetros page y size. El frontend implementará scroll infinito con IntersectionObserver en el buscador y el feed móvil para una experiencia fluida sin botones de paginación.

RNF-10

Índices de Base de Datos

Las tablas con alto volumen de consultas dispondrán de índices declarados explícitamente: idx_producto_vendedor, idx_producto_estado, idx_producto_categoria, idx_oferta_activa, idx_oferta_spark, idx_compra_comprador, idx_reporte_estado, idx_notif_actor.

RNF-11

Caché de Consultas Frecuentes

El backend implementará caché Spring Cache con TTL en las consultas de alta frecuencia y baja variabilidad: categorías raíz, todas las categorías activas y subcategorías por padre. El caché se invalidará automáticamente al crear, editar o eliminar categorías. El tamaño máximo del caché estará acotado para proteger el heap en entornos con memoria limitada.

RNF-12

Optimismo en la Interfaz (Optimistic UI)

Las acciones de alta frecuencia (añadir/eliminar favoritos, toggles de votos) se reflejarán inmediatamente en la interfaz del usuario sin esperar la respuesta del servidor. Si la petición falla, el estado se revertirá automáticamente para mantener la consistencia.

Reglas de Negocio

RNF-13

Ciclo de Vida de los Anuncios (Caducidad)

Los anuncios de productos y vehículos tendrán una vigencia de 180 días desde su publicación. Un scheduler diario a las 8:00h verificará los anuncios próximos a caducar y enviará avisos al vendedor con 30, 14, 7 y 1 día de antelación (cada aviso se envía una única vez). Al caducar, el anuncio pasa a estado EXPIRADO y permanece visible durante 14 días adicionales antes de ocultarse.

RNF-14

Sistema de Escrow en Pagos

Los fondos de una compra no se transferirán al vendedor hasta que el comprador confirme la recepción del producto. El dinero quedará retenido por Stripe (escrow) durante todo el proceso. Si el vendedor no realiza el envío dentro del plazo configurado (por defecto 10 días), el sistema procesará automáticamente un reembolso total al comprador mediante un scheduler que se ejecuta cada hora.

RNF-15

Confirmación Automática de Compra

Si el comprador no confirma la recepción del paquete en un plazo de 7 días desde que el envío registra estado ENTREGADO, el sistema marcará la compra como COMPLETADA automáticamente y liberará los fondos al vendedor.

RNF-16

Escala de Comisiones de Nexus

El sistema aplicará automáticamente la comisión de Nexus en función del precio final de cada transacción:

Rango de precioComisión
Menos de 20 €1,60 €
De 20 € a 100 €3,60 €
De 100 € a 1.000 €5,60 €

El precio máximo permitido por transacción es de 1.000 €.

RNF-17

Badge Automático de Ofertas

El sistema asignará automáticamente el badge visual de una oferta en función de sus características:

  • GRATUITA: si el precio de oferta es ≤ 0 €.
  • EXPIRA_HOY: si la fecha de expiración está en las próximas 24 horas.
  • NUEVA: si fue publicada hace menos de 1 hora.
  • CHOLLAZO: si el descuento es ≥ 70 %.
  • PORCENTAJE: si el descuento es ≥ 40 %.

RNF-18

Ranking de Chollos en Tiempo Real

El sistema recalculará el ranking de ofertas según su puntuación Spark (sparkCount - dripCount) cada 5 minutos mediante un scheduler. El resultado se publicará automáticamente a todos los clientes conectados a través del canal WebSocket /topic/ranking, sin necesidad de que el usuario recargue la página.

RNF-19

Reputación del Vendedor Dinámica

La puntuación de reputación del vendedor (0.0–5.0) se recalculará automáticamente tras cada nueva valoración recibida como la media aritmética de todas las puntuaciones otorgadas por los compradores. La reputación se mostrará en el perfil público y en el detalle de cada anuncio.

RNF-20

Restricciones de Seguridad en Transacciones

El sistema aplicará las siguientes restricciones de seguridad en las operaciones comerciales:

  • Un usuario no podrá comprar sus propios productos.
  • Dos usuarios que se hayan bloqueado mutuamente no podrán ver sus anuncios en búsquedas ni comunicarse por chat.
  • Los usuarios baneados o suspendidos no podrán iniciar sesión ni realizar ninguna operación en el sistema.
  • El precio máximo de una transacción está limitado a 1.000 €.
  • El límite máximo de imágenes por publicación es de 5 fotografías en la galería más la imagen principal.
  • El máximo de ofertas destacadas simultáneas en el banner principal es de 3.

Técnicos, Infraestructura e Integraciones

RNF-21

Comunicación en Tiempo Real (WebSocket STOMP)

El sistema implementará comunicación bidireccional en tiempo real mediante el protocolo STOMP sobre WebSocket con fallback a SockJS (HTTP polling). Los canales de comunicación son:

  • /topic/chat/{roomId}: mensajes de una conversación concreta.
  • /user/{actorId}/queue/notificaciones: notificaciones privadas por usuario.
  • /topic/ranking: ranking de chollos actualizado periódicamente.
  • /topic/chat/{roomId}/leidos y /recibidos >: confirmaciones de estado del mensaje.

La autenticación de la conexión WebSocket se verificará mediante JWT en el interceptor WebSocketAuthInterceptor en el momento de establecer la conexión.

RNF-22

Almacenamiento Externo de Medios (Cloudinary)

Todas las imágenes y vídeos subidos por los usuarios se almacenarán en el servicio externo Cloudinary con optimización automática de calidad y formato. El sistema eliminará los medios de Cloudinary al borrar el contenido asociado. Las imágenes no se almacenarán directamente en la base de datos del sistema.

RNF-23

Integraciones con Servicios de Pago (Stripe)

Todos los pagos del sistema pasarán exclusivamente por Stripe. El sistema utilizará:

  • PaymentIntents: para el proceso de compra de productos.
  • SetupIntents: para guardar tarjetas de crédito/débito sin cargo inmediato.
  • Checkout Sessions: para el pago de contratos publicitarios por empresas.
  • Refunds API: para reembolsos totales y parciales automáticos y manuales.

El webhook de Stripe (/api/webhooks/stripe) estará protegido mediante verificación de firma HMAC. Las claves secretas de Stripe nunca se incluirán en el repositorio de código fuente.

RNF-24

Asistente Virtual de Soporte con IA (Gemini)

El chatbot de soporte utilizará la API de Google Gemini (modelo gemini-2.0-flash-lite) para generar respuestas contextuales en español. El sistema enviará a la API los últimos 10 mensajes de la conversación como contexto. En ausencia de clave de API configurada, el sistema recurrirá a respuestas heurísticas locales predefinidas para los temas más frecuentes (envíos, pagos, devoluciones). La clave de API se gestionará exclusivamente mediante variables de entorno, nunca en el código fuente.

RNF-25

Compatibilidad y Diseño Responsivo

El frontend será compatible con las versiones actuales de los principales navegadores (Chrome, Firefox, Safari, Edge). El diseño se adaptará a dispositivos móviles mediante una capa de UI específica para pantallas de hasta 768 px, con componentes diferenciados: cabecera móvil simplificada, barra de navegación inferior tipo app nativa (Home, Explorar, Publicar, Mensajes, Perfil) y feed de contenido tipo scroll infinito. En pantallas desktop se mostrará el layout clásico con cabecera completa y mega menú.

RNF-26

Tareas Programadas del Sistema (Schedulers)

El sistema ejecutará de forma automática las siguientes tareas en segundo plano:

TareaFrecuenciaFunción
Caducidad de anunciosDiario a las 08:00 hExpira anuncios y envía avisos preventivos (30/14/7/1 días)
Control de plazos de envíoCada horaReembolso automático si el vendedor no envía en el plazo
Confirmación automática de entregaCada horaCompleta la compra si el comprador no confirma en 7 días
Ranking de chollosCada 5 minutosRecalcula puntuaciones Spark y publica el ranking por WebSocket
Desactivación de ofertas expiradasCada horaDesactiva ofertas cuya fecha de expiración ha pasado
Actualización de trackingPeriódico configurableConsulta el estado actualizado del paquete al transportista
Fin de pausas de productosPeriódicoReactiva productos cuya pausa de administrador ha concluido
Nota sobre integraciones externas: El sistema hace uso de los siguientes servicios de terceros en producción: Stripe (pagos), Cloudinary (medios), Google OAuth 2.0 y Gemini AI (autenticación e IA), Google reCAPTCHA (anti-bots), TOTP via dev.samstevens (2FA), ZXing (generación de QR), Correos (logística), Nominatim / OpenStreetMap (geocodificación) y Google AdSense (publicidad). Las credenciales de todos estos servicios se gestionan exclusivamente mediante variables de entorno en el servidor.

Diagrama de Dominio

Modelo de dominio completo del backend de Nexus. Incluye la jerarquía de herencia de actores (DomainEntity → ActorLogin → Actor) y las 29 entidades @Entity con sus relaciones. Las clases base son @MappedSuperclass (sin tabla propia); DireccionEnvio es @Embeddable y se incrusta en las entidades que la usan.

Jerarquía de herencia

Cargando…

Entidades y relaciones

Cargando…

Mockup Interactivo Figma

Como parte de nuestro Proyecto de Fin de Grado, desarrollamos una demo conceptual durante el primer trimestre para definir la experiencia de usuario y la identidad visual de Nexus. Este prototipo interactivo sirvió como base para la implementación final, la cual se asemeja fielmente a este diseño original pero construida sobre una arquitectura robusta en Angular, utilizando Ionic + Capacitor para su compilación multiplataforma y garantizando una experiencia web completamente responsive.

Vista General del Proyecto (Diseño)

Cargando Vista General...

Prototipo Interactivo (App)

Interacciones funcionales del prototipo:

  • Autenticación: Registrarse con Email e Iniciar sesión.
  • Navegación (Inicio): Acceder al Perfil y usar el Buscador.
  • Barra de Navegación: Funcionalidad (Inicio, Categorías, Subir, Favoritos y Mensajes).
Cargando Prototipo...