Parte de 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.
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.
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
@RestControllery 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
@Servicey 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
JpaRepositorycon métodos derivados del nombre y consultas JPQL personalizadas con@Query. - Capa de seguridad: El
JWTAuthenticationFilterintercepta todas las peticiones HTTP, extrae el token Bearer, valida la firma y lajwtVersion, y establece el contexto de autenticación en elSecurityContextHolder. ElWebSocketAuthInterceptoraplica 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) yNewsletterScheduler(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 |
|---|---|---|
Actoractor | Clase base de Usuario, Empresa y Admin (estrategia JOINED) | username (unique), email (unique), password (bcrypt), jwtVersion, twoFactorEnabled, baneado, suspendidoHasta, stripeCustomerId, cuentaEliminada, googleId |
Usuariousuario | Extiende Actor (PK compartida) | reputacion (0.0–5.0), totalVentas, ubicacion, cuentaPrivada, newsletterSuscrito, terminosAceptados, direccionPorDefecto (embebida), 6 flags de notificación |
Empresaempresa | Extiende Actor (PK compartida) | cif (unique), nombreComercial, descripcion, web, logo (Cloudinary URL), verificada |
Adminadmin | Extiende Actor (PK compartida) | nivelAcceso (1 básico / 2 moderador / 3 superadmin) |
Catálogo
| Entidad / Tabla | Relaciones e índices | Campos principales |
|---|---|---|
Productoproducto | 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 |
Ofertaoferta | 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 |
Vehiculovehiculo | 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 |
Categoriacategoria | 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 |
|---|---|---|
Compracompra | 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 |
Envioenvio | OneToOne con Compra | codigoEnvio (SHIP-XXXXXXXX), codigoQR (base64),
transportista, numeroSeguimiento, urlTracking, fechaLimiteEnvio, estadoEnvio |
Devoluciondevolucion | FK Compra; tabla devolucion_fotos | motivo (enum), descripcion, fotos de
evidencia (hasta 4 URLs), notaVendedor, importeDevuelto, estado (SOLICITADA/ACEPTADA/RECHAZADA/COMPLETADA) |
Contratocontrato | FK Empresa, FK Actor | tipoContrato (BANNER/PUBLICACION), estado (DRAFT→ACTIVE→EXPIRED),
monto, itemId (ítem patrocinado), diasPatrocinio, stripeCheckoutSessionId |
Cuponcupon | 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 |
|---|---|---|
ChatMensajechat_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 |
Valoracionvaloracion | FK Actor ×2 (comprador, vendedor), FK Compra (unique — una por compra) | puntuacion (1–5 estrellas), comentario, respuestaVendedor, fechaRespuesta |
SparkVotospark_voto | FK Actor, FK Oferta unique (usuario_id + oferta_id) | tipoVoto (SPARK/DRIP), fecha |
Favoritofavorito | FK Actor, FK Producto/Oferta/Vehiculo (opcional) | fecha. Restricción unique por usuario + ítem para
evitar duplicados. |
NewsletterSuscripcionnewsletter_suscripcion | Sin FK externas email (unique), tokenBaja (unique) | email, activa, ipConsentimiento, fechaConsentimiento, versionPolitica, tokenBaja (único por suscriptor), fechaBaja |
AuditLogaudit_log | FK Actor (admin que actuó) idx: actor_id, fechaAccion | tipoAccion, descripcion, fechaAccion. Registro inmutable de cada acción administrativa sensible. |
NotificacionInAppnotificacion_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 recientes | Ninguno |
/login, /register | LoginComponent, RegisterComponent — formularios con reCAPTCHA y validación reactiva | guestGuard |
/forgot-password, /reset-password | ForgotPasswordComponent, ResetPasswordComponent — flujo de recuperación de contraseña por email | Ninguno |
/verify-email | VerifyEmailComponent — verificación del OTP recibido por email | Ninguno |
/search | SearchComponent — búsqueda unificada con panel de filtros y resultados paginados | Ninguno |
/productos/:slug | ProductoDetailComponent — detalle del producto con galería, info del vendedor y botón de compra | Ninguno |
/ofertas/:slug | OfertaDetailComponent — detalle de oferta con votos Spark/Drip y comentarios | Ninguno |
/vehiculos, /vehiculos/:slug | VehiculosComponent, VehiculoDetailComponent — catálogo y ficha técnica de vehículos | Ninguno |
/publicar, /publicar/oferta, /publicar/vehiculo | PublishProductoComponent, PublishOfertaComponent, PublishVehiculoComponent — formularios de publicación multistep | authGuard |
/checkout/:slug | CheckoutComponent — flujo de compra con Stripe.js integrado | authGuard |
/compras/mis-compras, /compras/:id | MisComprasComponent, CompraDetailComponent — historial y detalle de compras | authGuard |
/mensajes | MensajesContainerComponent — chat en tiempo real con lista de conversaciones activas | authGuard |
/perfil, /perfil/:username | MiCuentaComponent, PerfilPublicoComponent — perfil privado y público | authGuard / Ninguno |
/configuracion | ConfiguracionComponent — seguridad, privacidad, notificaciones, 2FA | authGuard |
/notificaciones | NotificacionesComponent — centro de notificaciones in-app | authGuard |
/publicidad/contratos, /publicidad/patrocinios | EmpresaContratosPageComponent, MisPatrociniosPageComponent — gestión de publicidad para empresas | authGuard |
/devoluciones/* | NuevaDevolucionComponent, DevolucionDetailComponent — solicitud y seguimiento de devoluciones | authGuard |
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.
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_USERpuede llamar a rutas como/producto,/compra,/chat. Si intenta acceder a/api/admin/**, recibe un 403. -
Un token de
ROLE_ADMINpuede 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
- 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* yStripeWebhookController - 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
@Querypara 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
- 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
- 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
AdminLayoutComponentcomo 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
-
El usuario envía sus credenciales a
POST /api/auth/logincon token reCAPTCHA v3 generado en el cliente. -
El
CaptchaServiceverifica el token contra la API de Google con un umbral mínimo de 0.5. Si falla, devuelve 400. -
Spring Security autentica al actor mediante
AuthenticationManagercon BCrypt. Si falla, devuelve 401. -
Si el actor tiene
twoFactorEnabled = true, el backend devuelve{twoFactorRequired: true}sin emitir JWT todavía. -
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). -
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). -
El frontend almacena el JWT en localStorage y el
JwtInterceptorlo incluye enAuthorization: Bearer <token>en todas las peticiones HTTP internas. -
El
JWTAuthenticationFilterdel backend extrae el token, valida la firma y lajwtVersion, y establece elSecurityContextHolderpara cada petición.
Flujo de compra con Stripe Payment Intent
-
El usuario accede a
/checkout/:slugy selecciona método de entrega y dirección. -
El frontend llama a
POST /pago/crear-intentcon el ID del producto y datos del pedido. -
El backend calcula el precio final (producto + envío - descuento del
cupón si aplica) y crea un
PaymentIntenten Stripe con el importe en céntimos de euro. -
El backend devuelve el
clientSecretdel PaymentIntent. El frontend inicializa el formulario de Stripe.js (@stripe/stripe-js) con ese secreto. - El usuario introduce sus datos de pago en el formulario seguro de Stripe y confirma el pago.
-
Stripe procesa el pago y envía el evento
payment_intent.succeededal webhookPOST /stripe/webhook. La firma del evento se verifica constripe.webhook.secret. -
El
StripeWebhookControllerllama aCompraService.crearCompra()que crea la entidadCompraen estado PAGADO y la entidadEnviocon el código QR. - El producto pasa a estado RESERVADO. Se envían notificaciones in-app y por email al comprador y al vendedor.
-
El vendedor sube el número de seguimiento en
PATCH /envio/{id}/tracking. El estado del envío pasa a EN_TRANSITO. -
El comprador confirma la recepción en
PATCH /compra/{id}/confirmar-entrega. LaComprapasa a COMPLETADA y el pago se libera. El producto pasa a VENDIDO.
Flujo de mensajería en tiempo real con WebSocket STOMP
-
El frontend crea una conexión WebSocket en el endpoint
/wsusando SockJS como transporte. Se pasa el JWT en la query string o cabecera de la sesión. -
El
WebSocketAuthInterceptorde 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. -
El cliente se suscribe al canal personal
/user/{username}/queue/messagespara recibir sus mensajes entrantes. -
Para enviar un mensaje, el cliente publica en
/app/chatcon el payload:{roomId, texto, tipo, receptorId, productoId (opcional)}. -
El
ChatWebSocketControllerpersiste elChatMensajeen la BD, detecta si el receptor está conectado y envía el mensaje a su canal personal medianteSimpMessagingTemplate. -
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íaGET /chat/historial/{roomId}y los marca como recibidos.
Ciclo de vida de anuncios con caducidad automática
-
Al publicar un producto, oferta o vehículo, el hook
@PrePersistcalcula automáticamente lafechaCaducidad=fechaPublicacion + 180 días. -
El scheduler
AnuncioCaducidadServicecomprueba periódicamente qué anuncios están próximos a caducar. -
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
ultimoAvisoCaducidadDiaspara no duplicar el aviso. -
Cuando la
fechaCaducidades alcanzada, el estado del anuncio cambia automáticamente aEXPIRADO. -
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.
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.
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
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.
Identidad personal
Configuración de seguridad
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 login
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.
Inicio con 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
Introducción de nueva contraseña
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.
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.
Chollos del día
Explorar por categoría
Lo último en Nexus
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.
Menú de categorías
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.).
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 coches
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.
Radio 50km — 2 resultados
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.
Ofertas flash
Productos gratuitos
Viajes
Favoritos
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 del producto (cabecera)
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.
Ficha técnica del vehículo
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 (cabecera)
Votos Spark/Drip y comunidad
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.
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.
Paso 1: Detalles básicos
Paso 2: Fotos y descripción
Paso 3: Precio y ubicación
Paso 4: Revisión final
Confirmación de publicación
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: tipo de vehículo
Datos básicos
Detalles técnicos
Estado y precio
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
Datos de la oferta
Detalles adicionales
Oferta publicada
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
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 (vacío)
Mis productos (con anuncios)
Mis vehículos
Mis ofertas (vacío)
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 ventas
Buzón
Ayuda (1)
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).
Publicidad
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).
Datos del perfil
Datos y privacidad
Notificaciones
Privacidad
Seguridad y 2FA
Tipo de cuenta
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
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 del pedido
Método de envío (1)
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
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.
Detalle del pedido
Información adicional del pedido
Historial de movimientos
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.
Email confirmación de compra
Email detalle del pago
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.
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
Vista del producto
Iniciar compra
Datos del pedido
Envío (1)
Envío (2)
Pago Stripe
Confirmación
Detalle confirmación
Seguimiento (1)
Seguimiento (2)
Notificación
Email confirmación
Email detalle pago
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.
Listado de compras
Filtro por estado PAGADO
Modal de reembolso/cancelación
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.
Contactar desde el producto
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
Recepción de mensaje
Envío de imágenes
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.
Propuesta de precio enviada
Recepción de propuesta
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.
Productos disponibles
Popup de reserva
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.
Acceso al editor de anuncio
Edición paso 1
Edición paso 2
Edición paso 3
Edición paso 4
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 (móvil)
Mensajes
Recepción
Editar anuncio
Edición (1)
Edición (2)
Edición (3)
Edición (4)
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.
Paso 1: Tipo de contrato
Paso 2: Duración
Paso 3: Revisión
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.
Pago patrocinio (1)
Pago patrocinio (2)
Stripe Checkout
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.
Soporte con IA
Escalación a agente humano
Sesión de soporte finalizada
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 (1)
Registro (2)
Verificación OTP
Identidad personal
Seguridad
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
2FA móvil
Login Google
Cuenta Google
Recuperar contraseña
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 (visitante)
Categorías
Cat. coches
Favoritos
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
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.
Publicar
Artículo
Fotos/desc. (1)
Fotos/desc. (2)
Precio/ubic. (1)
Precio/ubic. (2)
Revisión final
Revisión (2)
Revisar coche
Publicado
Vehículo
Vehículo (1)
Vehículo (2)
Fotos vehículo
Estado/precio
Oferta (1)
Oferta (2)
Oferta (3)
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
Info perfil
Editar perfil
Mis cosas
Mis cosas (con producto)
Mis productos
Mis vehículos
Mis ofertas
Mis compras
Mis envíos
Valoraciones
Datos y privacidad
Tipo de cuenta
Cookies
Notificaciones
Privacidad
Seguridad
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/**.
Formulario de login admin
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.
QR para app autenticadora
2FA activado
2FA desactivado
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.
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 (1)
Estadísticas (2)
Estadísticas (3)
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.
Listado de usuarios
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.
Sanciones
CSV exportado a Sheets
Fraude
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
Filtro: estado RESERVADO
Detalle del producto (1)
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
Detalle de oferta
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
Filtro: coches
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).
Listado de reportes
Detalle del reporte
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.
Gestión de compras
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.
Devoluciones
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.
Cupones
Crear cupón (1)
Crear cupón (2)
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.
En revisión
Aprobados
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.
Propuesta publicidad (1)
Propuesta publicidad (2)
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.
Automatización semanal
Emisión manual
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.
Notificaciones masivas
Plantilla activada
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
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 (1)
Configuración (2)
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.
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.
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.
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.
- Caso 1 — Token válido: dado un
Authenticationcon rolROLE_USER, se genera un token, se llama avalidateToken()y se esperatrue. Se extrae elsubjectcongetUsernameOfToken()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()devuelvafalsesin lanzar excepción. - Caso 3 — Invalidación por versión: se mockea el
ActorRepositorypara devolver unActorconjwtVersion = 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
CaptchaServicepara 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 login con email/password

Paso de verificación 2FA

Configuración de seguridad en onboarding
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.
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).
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.
- 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 pordata: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)devuelvetrue. - Caso 4 — Código incorrecto: se pasa un código arbitrario
(
"000000") y se verifica queisCodeValid()devuelvefalse. - 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 vinculación con Google Authenticator

Estado 2FA activado correctamente

Login con validación TOTP obligatoria

Estado 2FA desactivado
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.
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.
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.
- Caso 1 — Cupón expirado:
fechaFinen el pasado →ValidacionResult.valido = falsecon 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 = USUARIOconusuario.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
costeEnviodel request. - Caso 7 — Cupón válido completo: todas las condiciones
superadas →
valido = trueeimporteFinal = importeTotal - descuentoconBigDecimalexacto.
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

Aplicación de cupón en el checkout

Creación del cupón — paso 1

Creación del cupón — paso 2

Listado de cupones en admin
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.
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.
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.
- 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()devolviendotrue→ HTTP 403 sin llamar a Stripe. - Caso 4 — Flujo correcto: se mockean
StripeService.crearIntentoPago()y el repositorio. Se verifica que la entidadComprase guarda con estadoPAGADO, que el producto pasa aVENDIDOy que se invocannotificacionService.notificarNuevaCompraVendedor()yemailService.enviarConfirmacionCompra(). - Caso 5 — Condición de carrera: el producto cambia
de
DISPONIBLEaVENDIDOentre elcrearIntentoy elconfirmarPago→IllegalStateExceptionpropagada 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

Paso 1 — Datos personales del pedido

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

Paso 3 — Confirmación de pago exitosa

Email de confirmación enviado al comprador
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.
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.
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).
- Caso 1 — Mensaje vacío rechazado: texto
nullo en blanco → el controlador RESTMensajeController.enviar()devuelve HTTP 400 sin persistir nada. - Caso 2 — Moderación de contenido: se mockea
ModerationServicepara 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 objetoChatMensajepersistido. - Caso 4 — Propuesta de precio PENDIENTE: mensaje
con
tipo = OFERTA_PRECIOyprecioPropuesto = 250.0→ se persiste conestadoPropuesta = PENDIENTE. - Caso 5 — Precio negociado disponible para compra: tras aceptar la propuesta,
ChatService.getPrecioNegociado(productoId, compradorId)devuelve el precio pactado, queCompraServiceusará 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 conversaciones en escritorio

Envío de mensaje de texto

Envío de propuesta de precio

Recepción y aceptación de propuesta
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().
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.
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.
- 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")yexpand("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 quemaxLat - minLat ≈ 0.9(50 km / 111.1) y quemaxLng - minLng < maxLat - minLatpor 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

Radio 50 km — 2 resultados

Radio 10 km — 1 resultado
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.
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.
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.
- Caso 1 — Primer voto positivo (Spark): no hay voto
previo → se crea
SparkVotoconvalor = 1,oferta.sparkCountincrementa en 1. Se verifica invocación deofertaRepository.save(). - Caso 2 — Toggle off (mismo voto): ya existe un
SparkVotoconvalor = 1→ se elimina ysparkCountdecrece en 1. No debe quedar registro enSparkVotoRepository. - Caso 3 — Cambio de voto (Spark → Drip): existe
voto con
valor = 1, se llama convalor = -1→sparkCountbaja 1,dripCountsube 1. ElSparkVotoexistente 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 quesparkCountpermanece en 0 (no baja a -1). - Caso 5 — Difusión WebSocket: se mockea
SimpMessagingTemplatey se verifica queconvertAndSend()es invocado exactamente una vez con el topic/topic/votos/oferta/{ofertaId}y el mapa correcto desparkCount,dripCountyscore.
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

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 production → npx 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.
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
- VMware Tanzu. (2024). Spring Boot Reference Documentation 3.5. Spring Framework. https://docs.spring.io/spring-boot/docs/3.5.x/reference/html/
- VMware Tanzu. (2024). Spring Security Reference Documentation. Spring Framework. https://docs.spring.io/spring-security/reference/
- VMware Tanzu. (2024). Spring Data JPA Reference Documentation. Spring Framework. https://docs.spring.io/spring-data/jpa/docs/current/reference/html/
- VMware Tanzu. (2024). Spring WebSocket — WebSocket Support. Spring Framework. https://docs.spring.io/spring-framework/reference/web/websocket.html
- Oracle Corporation. (2024). Java 17 (LTS) API Specification. OpenJDK. https://docs.oracle.com/en/java/javase/17/
- Apache Software Foundation. (2024). Apache Maven Documentation. Maven. https://maven.apache.org/guides/
- Red Hat. (2024). Hibernate ORM Documentation 6.x. Hibernate. https://docs.jboss.org/hibernate/orm/6.4/userguide/html_single/Hibernate_User_Guide.html
- Brettwooldridge. (2024). HikariCP — A solid, high-performance, JDBC connection pool at last. GitHub. https://github.com/brettwooldridge/HikariCP
- FasterXML. (2024). Jackson Databind — General data-binding package for Jackson. GitHub. https://github.com/FasterXML/jackson-databind
Seguridad y Autenticación
- JSON Web Token Working Group. (2015). RFC 7519 — JSON Web Token (JWT). Internet Engineering Task Force (IETF). https://tools.ietf.org/html/rfc7519
- jwtk. (2024). JJWT — Java JWT: JSON Web Token for Java and Android 0.11.5. GitHub. https://github.com/jwtk/jjwt
- Google LLC. (2024). Google Identity Services — Sign In With Google. Google Developers. https://developers.google.com/identity/gsi/web/guides/overview
- Google LLC. (2024). reCAPTCHA v3 Developer Guide. Google Developers. https://developers.google.com/recaptcha/docs/v3
- samdjstevens. (2024). java-totp: A library for generating and validating TOTP codes 1.7.1. GitHub. https://github.com/samdjstevens/java-totp
- Internet Engineering Task Force. (2011). RFC 6238 — TOTP: Time-Based One-Time Password Algorithm. IETF. https://tools.ietf.org/html/rfc6238
- Google. (2024). ZXing (“Zebra Crossing”) barcode image processing library 3.5.3. GitHub. https://github.com/zxing/zxing
Base de Datos
- The PostgreSQL Global Development Group. (2024). PostgreSQL 16 Documentation. PostgreSQL. https://www.postgresql.org/docs/16/
- Jakarta EE Working Group. (2023). Jakarta Persistence 3.1 Specification. Eclipse Foundation. https://jakarta.ee/specifications/persistence/3.1/
Frontend — Angular y Ecosistema
- Google LLC. (2025). Angular Documentation — v21. Angular. https://angular.dev/
- Google LLC. (2025). Angular — Standalone Components Guide. Angular. https://angular.dev/guide/components
- Microsoft Corporation. (2025). TypeScript 5.9 Documentation. TypeScript. https://www.typescriptlang.org/docs/
- Ionic. (2025). Ionic Framework v8 Documentation. Ionic. https://ionicframework.com/docs
- Ionic. (2025). Capacitor Documentation 8.x. Capacitor. https://capacitorjs.com/docs
- ReactiveX. (2024). RxJS Documentation 7.8. ReactiveX. https://rxjs.dev/
- @stomp/stompjs. (2024). STOMP.js — STOMP client for Web browsers and node.js applications 7.3. GitHub. https://stomp-js.github.io/guide/stompjs/
- SockJS. (2024). SockJS-client — WebSocket emulation library 1.6.1. GitHub. https://github.com/sockjs/sockjs-client
- Swiper. (2025). Swiper.js — The Most Modern Mobile Touch Slider 12.x. Swiper. https://swiperjs.com/
Pagos e Infraestructura de Medios
- Stripe, Inc. (2025). Stripe Developer Documentation — Payment Intents API. Stripe. https://stripe.com/docs/payments/payment-intents
- Stripe, Inc. (2025). Stripe Checkout — Accept payments with a Stripe-hosted page. Stripe. https://stripe.com/docs/payments/checkout
- Stripe, Inc. (2025). Stripe Java SDK 24.1. GitHub. https://github.com/stripe/stripe-java
- Cloudinary Ltd. (2024). Cloudinary Java SDK Documentation 1.36. Cloudinary. https://cloudinary.com/documentation/java_integration
Inteligencia Artificial y APIs Externas
- Google LLC. (2024). Gemini API Documentation — Gemini 1.5 Flash. Google AI for Developers. https://ai.google.dev/gemini-api/docs
- Groq, Inc. (2024). Groq API Documentation — LLaMA 3.3 70B Versatile. Groq. https://console.groq.com/docs/openai
Infraestructura y Despliegue
- Render Inc. (2024). Render Documentation — Deploying Docker Services. Render. https://render.com/docs/docker
- Vercel Inc. (2025). Vercel Platform Documentation. Vercel. https://vercel.com/docs
- Docker Inc. (2024). Dockerfile Reference. Docker. https://docs.docker.com/engine/reference/builder/
- Astro Build Team. (2025). Astro Documentation. Astro. https://docs.astro.build/
Documentación API y estándares
- Swagger.io / OpenAPI Initiative. (2024). OpenAPI Specification 3.1. OpenAPI Initiative. https://spec.openapis.org/oas/v3.1.0
- springdoc. (2024). SpringDoc OpenAPI — Automating Spring Boot API Documentation 2.6.0. SpringDoc. https://springdoc.org/
- STOMP Specification Team. (2012). STOMP Protocol Specification 1.2. STOMP. https://stomp.github.io/stomp-specification-1.2.html
Normativa y Cumplimiento
- Parlamento Europeo y Consejo de la UE. (2016). Reglamento (UE) 2016/679 del Parlamento Europeo — Reglamento General de Protección de Datos (RGPD). Diario Oficial de la Unión Europea. https://eur-lex.europa.eu/legal-content/ES/TXT/?uri=celex%3A32016R0679
- Agencia Española de Protección de Datos. (2024). Guía práctica para el cumplimiento del RGPD en aplicaciones web. AEPD. https://www.aepd.es/
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 |
|---|---|
| Actor | Clase 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 REST | Interfaz de programación de aplicaciones basada en el protocolo HTTP con arquitectura REST. El backend expone su funcionalidad mediante API REST JSON. |
| Audit Log | Registro inmutable de acciones administrativas en el sistema. Cada entrada almacena qué administrador realizó qué acción y cuándo. |
| Badge | Etiqueta visual calculada automáticamente para las ofertas del chollometro: NUEVA, CHOLLAZO, PORCENTAJE, GRATUITA o EXPIRA_HOY. |
| BCrypt | Algoritmo de hashing adaptativo usado para almacenar las contraseñas de los usuarios. Nunca se almacena la contraseña en texto plano. |
| Bearer Token | Token JWT enviado en la cabecera HTTP Authorization: Bearer <token> para autenticar peticiones al backend. |
| Capacitor | Librerí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.). |
| Chollometro | Sección de Nexus donde los usuarios publican y votan ofertas/chollos de productos nuevos en tiendas online y físicas. |
| Cloudinary | Servicio 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. |
| CORS | Cross-Origin Resource Sharing. Mecanismo HTTP que controla qué dominios externos pueden realizar peticiones al backend de Nexus. |
| Cupón | Có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-auto | Propiedad 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-in | Flujo de suscripción al newsletter que requiere confirmación por email. Requerido por el RGPD para acreditar el consentimiento explícito. |
| Drip | Voto negativo en el sistema de votación de Nexus. Un Drip indica que la oferta no parece válida o interesante. |
| Escrow | Modelo de pago donde el dinero del comprador queda retenido por un tercero (Stripe) hasta que se confirma la correcta recepción del producto. |
| FCM | Firebase 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. |
| HikariCP | Pool 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 / DI | Inversión de Control / Inyección de Dependencias. Patrón central de Spring que gestiona la creación y ciclo de vida de los beans. |
| Ionic | Framework UI para aplicaciones híbridas móviles. Proporciona componentes nativos (botones, listas, tarjetas) adaptados para dispositivos iOS y Android. |
| JPA | Jakarta Persistence API. Estándar Java para mapeo objeto-relacional (ORM). Nexus usa la implementación de Hibernate. |
| JWT | JSON Web Token. Estándar (RFC 7519) para tokens de autenticación compactos y firmados. En Nexus se firman con HMAC-SHA256. |
| jwtVersion | Campo 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. |
| Marketplace | Sección de Nexus donde los usuarios pueden publicar y comprar productos de segunda mano entre particulares. |
| MVP | Minimum Viable Product (Producto Mínimo Viable). Primera versión funcional del producto con las características esenciales para validar el mercado. |
| OAuth 2.0 | Estándar de autorización que permite a usuarios autenticarse con cuentas de terceros (en Nexus, Google) sin compartir su contraseña. |
| OpenAPI / Swagger | Especificación estándar para documentar APIs REST. Nexus genera automáticamente la documentación con SpringDoc OpenAPI 2.6.0. |
| OTP | One-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 Intent | Objeto de Stripe que representa una intención de cobro. Permite el modelo escrow y el flujo de confirmación en dos pasos. |
| RGPD | Reglamento General de Protección de Datos (EU 2016/679). Nexus implementa borrado suave, double opt-in y registro de consentimientos para cumplirlo. |
| ROLE_ADMIN | Rol de Spring Security asignado a los administradores. Da acceso
exclusivo al panel de administración (/api/admin/**). |
| ROLE_EMPRESA | Rol de Spring Security asignado a las empresas. Extiende ROLE_USER con acceso a contratos publicitarios y Stripe Checkout. |
| ROLE_USER | Rol de Spring Security asignado a los usuarios particulares registrados. Es el rol base del sistema. |
| roomId | Identificador único de una conversación de chat entre dos usuarios. Se calcula de forma determinista a partir de los IDs de los participantes. |
| Scheduler | Tarea programada de Spring (@Scheduled) que se
ejecuta automáticamente en intervalos configurados. En Nexus
gestiona caducidad de anuncios y newsletter. |
| Soft Delete | Borrado lógico. En lugar de eliminar físicamente un registro de
la BD, se marca como eliminado (cuentaEliminada = true). Cumple el RGPD. |
| Spark | Voto positivo en el sistema de votación de Nexus. Un Spark indica que la oferta es interesante y válida. |
| sparkScore | Puntuación neta de una oferta = sparkCount - dripCount. Determina el orden de aparición en el feed del chollometro. |
| Standalone Component | Componente Angular que se declara con standalone: true e importa sus dependencias directamente, sin necesidad de NgModule. |
| STOMP | Simple Text Orientated Messaging Protocol. Protocolo de mensajería sobre WebSocket usado en Nexus para el chat en tiempo real. |
| Stripe | Plataforma de pagos online. Nexus la usa para Payment Intents (compras) y Stripe Checkout (contratos de publicidad). |
| TOTP | Time-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. |
| Webhook | Mecanismo de notificación HTTP en el que Stripe llama al endpoint
de Nexus (/stripe/webhook) para confirmar eventos de
pago asíncronos. |
| WebSocket | Protocolo de comunicación bidireccional y persistente sobre TCP. Nexus lo usa con STOMP para el chat en tiempo real y las notificaciones instantáneas. |
| ZXing | Librerí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/register | Registro de nuevo usuario con email, username, contraseña y reCAPTCHA | Pública |
| POST | /api/auth/login | Login con credenciales y reCAPTCHA. Devuelve JWT o flag twoFactorRequired | Pública |
| POST | /api/auth/google | Login/registro con ID token de Google OAuth 2.0 | Pública |
| POST | /api/auth/verify-2fa | Validación del código 2FA (TOTP o Email OTP). Devuelve JWT si es válido | Pública |
| POST | /api/auth/verify-email | Verificación del email con el OTP de 6 dígitos enviado al correo | Pública |
| POST | /api/auth/forgot-password | Solicitar enlace de recuperación de contraseña (válido 15 minutos) | Pública |
| POST | /api/auth/reset-password | Restablecer contraseña con el token recibido por email | Pública |
| Productos | |||
| GET | /producto/filtrar | Bú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}/estado | Cambiar estado: DISPONIBLE, RESERVADO, VENDIDO | JWT |
| DELETE | /producto/{id} | Eliminar producto y sus imágenes de Cloudinary | JWT |
| POST | /producto/{id}/renovar | Reactivar anuncio expirado con nueva vigencia de 180 días | JWT |
| Ofertas (Chollometro) | |||
| GET | /oferta | Listado de ofertas activas paginado, ordenado por sparkScore descendente | Pública |
| POST | /oferta/publicar | Publicar nueva oferta/chollo con imagen (multipart) | JWT |
| POST | /spark-voto/{ofertaId}/votar | Votar Spark (positivo) o Drip (negativo) en una oferta | JWT |
| Vehículos | |||
| GET | /vehiculo/filtrar | Bú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-intent | Crear Stripe Payment Intent y devolver clientSecret para el frontend | JWT |
| POST | /stripe/webhook | Receptor de eventos Stripe con verificación de firma HMAC del payload | Pública (firma) |
| GET | /compra/mis-compras | Historial de compras del usuario autenticado como comprador | JWT |
| PATCH | /compra/{id}/confirmar-entrega | El comprador confirma la recepción del producto. Libera el pago escrow. | JWT |
| Envíos y Devoluciones | |||
| GET | /envio/{id}/qr | Obtener el código QR del envío en formato base64 | JWT |
| POST | /devolucion | Solicitar devolución de una compra con motivo y fotos de evidencia | JWT |
| Chat y Mensajería | |||
| GET | /chat/historial/{roomId} | Obtener historial de mensajes de una conversación | JWT |
| WS | /ws (STOMP /app/chat) | Enviar mensaje en tiempo real por WebSocket STOMP | JWT (handshake) |
| Perfiles y Social | |||
| GET | /usuario/perfil/{username} | Perfil público del usuario (si no está privado) | Pública |
| POST | /favorito | Añadir producto/oferta a favoritos | JWT |
| POST | /valoracion | Crear valoración post-compra (una por compra) | JWT |
| POST | /reporte | Reportar un usuario, producto, oferta u otro contenido | JWT |
| Newsletter y Soporte | |||
| POST | /newsletter/suscribir | Suscribirse al newsletter (inicia el double opt-in) | Pública |
| GET | /newsletter/confirmar | Confirmar la suscripción con el token del email de confirmación | Pública |
| POST | /soporte-chat/mensaje | Enviar mensaje al chatbot de soporte IA | Pública |
| Administración | |||
| GET | /api/admin/usuarios | Listado completo de usuarios con filtros y búsqueda | ROLE_ADMIN |
| POST | /api/admin/sanciones/ban | Aplicar ban permanente a un usuario | ROLE_ADMIN |
| GET | /api/admin/audit-log | Consultar el registro de auditoría de acciones admin | ROLE_ADMIN |
| GET | /api/admin/estadisticas | Métricas del sistema: usuarios, ventas, ingresos, productos activos | ROLE_ADMIN |
| POST | /api/admin/cupones | Crear cupón de descuento | ROLE_ADMIN |
| GET | /api/admin/reportes | Listado de reportes pendientes de resolución | ROLE_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 |
|---|---|---|
actor | username, email, password (bcrypt), jwt_version, two_factor_enabled, baneado, suspendido_hasta, stripe_customer_id, google_id, reset_token | Padre de usuario, empresa, admin · Idx: username, email, google_id, reset_token |
usuario | reputacion, total_ventas, ubicacion, cuenta_privada, newsletter_suscrito, terminos_aceptados, dir_* (dirección embebida) | FK → actor (PK compartida) · Tabla: usuario_bloqueados |
empresa | cif (unique), nombre_comercial, descripcion, web, logo, verificada | FK → actor (PK compartida) · Idx: cif |
admin | nivel_acceso (1/2/3) | FK → actor (PK compartida) |
producto | titulo, precio, tipo_oferta, estado, condicion, admite_envio, latitude, longitude, imagen_principal, fecha_caducidad, patrocinado | FK → actor (vendedor), categoria · Tabla: producto_imagenes · Idx: vendedor, estado, categoria |
oferta | titulo, precio_oferta, precio_original, tienda, badge, spark_count, drip_count, spark_score, es_flash, flash_fin, codigo_descuento, patrocinada | FK → actor, categoria · Tabla: oferta_imagenes · Idx: activa, spark, categoria, publicacion |
vehiculo | tipo_vehiculo, marca, modelo, anio, kilometros, combustible, cambio, potencia, itv, garantia, latitude, longitude | FK → actor (publicador), categoria · Tablas: vehiculo_imagenes, vehiculo_extras · Idx: publicador, tipo, estado |
compra | estado, stripe_payment_intent_id, precio_final, comision_nexus, metodo_entrega, dir_* (dirección), fechas de hitos | FK → actor (comprador), producto · Idx: comprador, estado |
envio | codigo_envio (SHIP-XXXXXXXX), codigo_qr (base64), transportista, numero_seguimiento, url_tracking, fecha_limite_envio, estado_envio | FK → compra (OneToOne) |
devolucion | motivo, descripcion, nota_vendedor, importe_devuelto, estado | FK → compra · Tabla: devolucion_fotos |
chat_mensaje | room_id, texto, media_url, tipo, leido, recibido, precio_propuesto, estado_propuesta, fecha_envio | FK → actor x2 (remitente, receptor), producto (opc.) · Idx: room_id, fechas |
valoracion | puntuacion (1-5), comentario, respuesta_vendedor | FK → actor x2, compra (unique — una por compra) |
contrato | tipo_contrato, estado, monto, dias_patrocinio, stripe_checkout_session_id | FK → empresa, actor |
cupon | codigo (unique), tipo_descuento, valor, alcance, limite_total_usos, fechas vigencia | Tablas: cupon_categorias, cupon_uso · Idx: codigo |
categoria | nombre, slug (unique), icono, color, orden, activa, parent_id | Self-join (parent → hijos) · Idx: slug, parent_id |
newsletter_suscripcion | email (unique), activa, ip_consentimiento, fecha_consentimiento, version_politica, token_baja (unique) | Sin FK externas · Idx: email, token_baja |
audit_log | tipo_accion, descripcion, fecha_accion | FK → actor (admin responsable) · Idx: actor_id, fecha_accion |
spark_voto | tipo_voto (SPARK/DRIP), fecha | FK → actor, oferta · Unique: usuario_id + oferta_id |
favorito | fecha | FK → actor y producto/oferta/vehiculo (opc.) · Unique: usuario + item |
notificacion_in_app | titulo, mensaje, tipo, url_destino, leida, destacada, fecha | FK → actor · Idx: actor_id, leida, fecha |
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.
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
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:
googleIdpara 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:
stripeCustomerIdpara pagos guardados. - Onboarding:
onboardingCompletadopara 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. -
badgeautomá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.
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.
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).
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).
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í.
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.
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.
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.
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.
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.
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 precio | Comisió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}/leidosy/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:
| Tarea | Frecuencia | Función |
|---|---|---|
| Caducidad de anuncios | Diario a las 08:00 h | Expira anuncios y envía avisos preventivos (30/14/7/1 días) |
| Control de plazos de envío | Cada hora | Reembolso automático si el vendedor no envía en el plazo |
| Confirmación automática de entrega | Cada hora | Completa la compra si el comprador no confirma en 7 días |
| Ranking de chollos | Cada 5 minutos | Recalcula puntuaciones Spark y publica el ranking por WebSocket |
| Desactivación de ofertas expiradas | Cada hora | Desactiva ofertas cuya fecha de expiración ha pasado |
| Actualización de tracking | Periódico configurable | Consulta el estado actualizado del paquete al transportista |
| Fin de pausas de productos | Periódico | Reactiva productos cuya pausa de administrador ha concluido |
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
Entidades y relaciones
Mockup Interactivo
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)
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).