Backend REST + WebSocket para la plataforma MentTime — gestión de citas, negocios y suscripciones.
| Capa | Tecnología |
|---|---|
| Runtime | Java 21 |
| Framework | Spring Boot 3.2.5 |
| Base de datos | PostgreSQL + Flyway (V1–V23) |
| ORM | Spring Data JPA / Hibernate |
| Seguridad | Spring Security + JWT (jjwt 0.12.5) |
| WebSocket | Spring WebSocket STOMP (SockJS) |
| Mapping | MapStruct 1.5.5 + Lombok 1.18.30 |
| Storage | AWS SDK v2 (S3-compatible: MinIO dev / Supabase prod) |
| Imágenes | Thumbnailator 0.4.20 + Scrimage 4.0.39 (auto-WebP) |
| Spring Mail + Thymeleaf | |
| Pagos | Stripe Java SDK 26.3.0 |
| Thymeleaf + OpenPDF | |
| Web Push | nl.martijndwars web-push 5.1.1 + BouncyCastle 1.78.1 (VAPID) |
| API Docs | Springdoc OpenAPI 2.5.0 (Swagger UI) |
| Observabilidad | Spring Actuator + Micrometer / Prometheus |
| Build | Maven |
| Contenedores | Docker + Docker Compose |
| Módulo | Responsabilidad |
|---|---|
auth |
JWT access/refresh, registro, confirmación email, reset password. RegistrationOrchestratorService |
business |
CRUD negocio, BusinessSettings, IndustryTag, scraper SEO schedulado |
employee |
Relación user↔negocio como empleado, WorkingHours, SpecialAvailability. EmployeeOrchestratorService |
offering |
Servicios del negocio con duración, precio y asignación a empleados |
appointment |
Booking con lock anti-overbooking (AppointmentLockService). Citas manuales (admin). Notificaciones por ciclo de vida |
client |
Relación user↔negocio como cliente. Clientes sin cuenta. Ban/unban |
dashboard |
KPIs diarios y semanales, rendimiento por empleado |
message |
Mensajería directa usuario a usuario con push WebSocket en tiempo real |
notification |
Notificaciones persistidas + push WebSocket al crear. Lectura/eliminación solo REST |
media |
Tabla documents polimórfica. S3-compatible (MinIO/Supabase). Auto-conversión a WebP. Documentos singleton vs. acumulativos |
mail |
Templates Thymeleaf: confirmación de cuenta, reset de contraseña, citas |
websocket |
Hub STOMP centralizado (WebSocketService). 4 destinations activos, 3 huérfanos (ver contrato en WS_BACKEND_CONTRACT.md) |
subscription |
Planes FREE_TRIAL / BASIC / PRO. Límites por plan. SubscriptionExpirationJob |
billing |
Stripe Checkout, cambios de plan con proration, perfiles fiscales, facturas PDF con numeración secuencial |
superadmin |
Gestión global: suscripciones, negocios, audit log, industry tags, trigger pipeline SEO |
support |
Tickets de soporte (V16). Infraestructura WebSocket preparada pero sin callers activos |
push |
Web Push VAPID. Entidad PushSubscription. WebPushService. Endpoint POST /api/v1/push-subscriptions |
errorlog |
Persiste errores 500 backend y crashes frontend. POST /api/v1/errors sin autenticación |
config |
SecurityConfig, WebSocketConfig, StorageConfigurationService, DataSeeder |
- JWT access token (1 h) + refresh token en cookie HTTP-only (7 d)
JwtAuthenticationFilter— valida cada request RESTJwtChannelInterceptor— valida JWT en frame STOMP CONNECTSubscriptionCheckFilter— HTTP 402 si la suscripción del negocio ha expirado- Roles:
SUPER_ADMIN,BUSINESS_ADMIN,EMPLOYEE,CLIENT - Control de acceso con
@Secured/@PreAuthorizeen controllers y services
users, refresh_tokens, businesses, business_settings, business_clients, employees, offerings, employee_services, working_hours, special_availabilities, appointments, appointment_locks, notifications, messages, documents, industry_tags, subscription_plans, business_subscriptions (con pending_plan_code), business_fiscal_profiles, payment_invoices, invoice_sequences, app_issuer_config, support_tickets, support_messages, admin_audit_log, error_logs, push_subscriptions
La tabla documents es polimórfica (entity_type + entity_uuid): ninguna otra tabla guarda URLs de archivos.
| Destination | Uso |
|---|---|
/user/{email}/queue/notifications |
Notificaciones de sistema y citas |
/user/{email}/queue/messages |
Mensajería directa |
/user/{email}/queue/appointments |
Eventos de cita al cliente |
/topic/business/{uuid}/appointments |
Broadcast de citas al equipo del negocio |
Ver auditoría completa en WS_BACKEND_CONTRACT.md.
- Registro → FREE_TRIAL automático (30 d)
- Upgrade BASIC → PRO: inmediato con proration (
always_invoice) - Downgrade PRO → BASIC:
pending_plan_codeaplica al final del periodo via webhook - Cancelación:
cancel_at_period_end SubscriptionExpirationJob: scheduler que expira trials vencidos- Webhooks:
checkout.session.completed,invoice.paid/failed,customer.subscription.updated/deleted
- Librería:
nl.martijndwars/web-push 5.1.1+ BouncyCastle - Entidad
push_subscriptionscon endpoint único, FK user ON DELETE CASCADE POST /api/v1/push-subscriptions— registro de suscripción desde el navegador- Variables necesarias:
VAPID_PUBLIC_KEY,VAPID_PRIVATE_KEY,VAPID_SUBJECT - Generar par de claves:
npx web-push generate-vapid-keys --json
- Docker y Docker Compose
- Java 21 + Maven (solo para tests locales sin Docker)
Copia la plantilla y edita los valores:
cp .env.example .env| Variable | Obligatoria | Descripción |
|---|---|---|
DB_NAME / DB_USER / DB_PASSWORD |
✅ | PostgreSQL |
JWT_SECRET |
✅ | Clave HMAC-SHA — generar con openssl rand -hex 32 |
CORS_ALLOWED_ORIGINS |
✅ | Orígenes permitidos separados por comas |
APP_FRONTEND_URL |
✅ | URL base del frontend (usado en emails) |
APP_FRONTEND_EMAIL_CONFIRM_PATH |
— | Ruta confirmación email (default /confirmar-correo) |
APP_FRONTEND_PASSWORD_RESET_PATH |
— | Ruta reset contraseña (default /nueva-contrasena) |
STORAGE_ENDPOINT / STORAGE_BUCKET / STORAGE_ACCESS_KEY / STORAGE_SECRET_KEY / STORAGE_PUBLIC_URL |
✅ | S3-compatible (MinIO en dev, Supabase en prod) |
STORAGE_PATH_STYLE |
— | true para MinIO/Supabase |
MAIL_HOST / MAIL_USERNAME / MAIL_PASSWORD |
✅ | SMTP |
STRIPE_SECRET_KEY / STRIPE_WEBHOOK_SECRET |
✅ | Stripe |
VAPID_PUBLIC_KEY / VAPID_PRIVATE_KEY / VAPID_SUBJECT |
✅ | Web Push VAPID |
APP_INITIAL_ADMIN_EMAIL / APP_INITIAL_ADMIN_PASSWORD |
Seeder SUPER_ADMIN |
✅ La app no arranca sin esta variable.
⚠️ Sin default seguro, debes definirla.
docker compose -f docker-compose.dev.yml up --build| Servicio | URL |
|---|---|
| API REST | http://localhost:8080 |
| Swagger UI | http://localhost:8080/swagger-ui/index.html |
| Actuator health | http://localhost:8080/actuator/health |
| PostgreSQL (externo) | localhost:5433 |
| MinIO API (S3) | http://localhost:9000 |
| MinIO consola web | http://localhost:9001 |
./restart.sh # Rebuild sin perder la DB
./restart.sh --reset # Reset completo (borra DB y vuelve a sembrar)Los tests usan H2 embebido — no necesitan Docker:
./run-all-tests.sh # Todos los tests
./run-package-tests.sh <módulo> # Tests de un módulo concreto
./test-menu.sh # Menú interactivoConsulta TESTING_README.md para más detalles.
- Orchestrator Services para flujos complejos:
RegistrationOrchestratorService,BusinessOrchestratorService,EmployeeOrchestratorService. No duplicar lógica en controllers. - Tabla
documentspolimórfica — toda URL de archivo pasa porMediaService. Ninguna entidad guarda columnas URL propias. SpecificationBuilderes el mecanismo único de filtrado dinámico. No crear queries custom en repositories si puede resolverse con Specifications.- Stripe es la fuente de verdad para suscripciones. El backend sincroniza via webhooks, no via polling.
- Estados de lectura (mensajes leídos, notificaciones leídas) son operaciones REST sin push WebSocket — gap conocido y documentado.
SPRING_PROFILES_ACTIVE=prodDB_HOST→ hostname interno del servicio PostgreSQL en Docker de CoolifySTORAGE_ENDPOINT→ MinIO en Coolify o Supabase
Este proyecto se desarrolla con Claude Code (CLI de Anthropic) usando el modelo Claude Sonnet 4.6.
© 2024–2026 MentTime. Todos los derechos reservados.