Arquitectura Tecnológica del Sistema
La implementación de un sistema de venta de boletos para cine se sustenta en una pila tecnológica robusta, orientada a la escalabilidad y mantenibilidad.
Componentes del Lado del Servidor (Backend)
- Framework Principal: Spring Boot 2.x, para una configuración ágil y desarrollo rápido de microservicios.
- Capa de Persistencia: MyBatis-Plus o Spring Data JPA para la interacción eficiente con la base de datos.
- Gestión de Datos:
- MySQL 8.0 como base de datos relacional principal.
- Redis para caching de datos frecuentes y la gestión de la concurrencia en procesos críticos como la venta de boletos.
- Seguridad: Spring Security en combinación con JWT (JSON Web Tokens) para autenticación y autorización segura de usuarios.
- Middleware:
- RabbitMQ, actuando como broker de mensajes para operaciones asíncronas y desacoplamiento de servicios.
- Elasticsearch, optimizado para la funcionalidad de búsqueda de contenido y películas.
- Utilidades: Lombok para reducir el boilerplate, Swagger para documentación interactiva de APIs y Logback para la gestión de logs.
Componentes del Lado del Cliente (Frontend - Opcional Arquitectura Separada)
- Aplicación Web: Thymeleaf (para renderizado del lado del servidor) o un enfoque con Vue.js y la biblioteca de componentes Element UI.
- Aplicaciones Móviles: Uniapp o desarrollo nativo para WeChat Mini Programs.
Infraestructura y Despliegue
- Contenedorización: Docker y Docker Compose para empaquetar y orquestar los servicios de la aplicación.
- CI/CD (Integración y Entrega Continua): Jenkins o GitHub Actions para automatizar los flujos de compilación, prueba y despliegue.
Funcionalidades Clave del Sistema
La plataforma ofrece un conjunto completo de características para usuarios y administradores, estructuradas en módulos principales:
Módulo de Usuarios y Cuentas
- Proceso de registro y autenticación (usando número de teléfono con SMS OTP o tokens JWT).
- Gestión de perfil (actualización de datos personales, cambio de contraseña, carga de avatares).
- Sistema de fidelización por niveles de membresía, con acumulación de puntos canjeables por beneficios.
Módulo de Gestión de Películas
- Clasificación de títulos (estrenos, próximas, mejer valoradas).
- Páginas de detalle de película (ficha técnica, elenco, tráileres, calificaciones y reseñas).
- Búsqueda avanzada de películas utilizando Elasticsearch para resultados rápidos y precisso.
Módulo de Venta de Boletos
- Consulta de sesiones y horarios disponibles en cines, mostrando el estado actual de los asientos.
- Proceso de selección de asientos y compra, con protección contra sobreventa mediante bloqueos distribuidos de Redis.
- Gestión de historial de pedidos, incluyendo la cancelación automática de reservas no pagadas a tiempo.
Módulo de Pagos
- Integración con pasarelas de pago (ej. Alipay/WeChat Pay en modo sandbox).
- Funcionalidad para procesamiento de reembolsos (parciales o totales).
Módulo de Administración del Sistema
- Herramientas CRUD (Crear, Leer, Actualizar, Eliminar) para la gestión de cines y la configuración de horarios de proyección.
- Generación de informes y estadísticas de ventas de boletos con visualizaciones (ej. ECharts).
- Sistema de auditoría de logs, registrando opearciones críticas mediante Aspect-Oriented Programming (AOP).
Esquema de Base de Datos (Tablas Principales)
A continuación, se presenta el diseño lógico de las tablas esenciales para el funcionamiento de la plataforma.
-- Tabla de Clientes
CREATE TABLE `clientes` (
`id_cliente` BIGINT PRIMARY KEY AUTO_INCREMENT,
`nombre_acceso` VARCHAR(50) UNIQUE NOT NULL,
`hash_contrasena` VARCHAR(255) NOT NULL, -- Mayor longitud para contraseñas hasheadas
`email_contacto` VARCHAR(100) UNIQUE,
`telefono_movil` VARCHAR(15) UNIQUE,
`url_imagen_perfil` VARCHAR(255),
`puntos_fidelidad` INT DEFAULT 0
);
-- Tabla de Contenido Fílmico
CREATE TABLE `titulos_cine` (
`id_titulo` BIGINT PRIMARY KEY AUTO_INCREMENT,
`nombre_pelicula` VARCHAR(150) NOT NULL,
`director_principal` VARCHAR(80),
`calificacion_promedio` DECIMAL(2,1), -- Calificación sobre 10
`duracion_minutos` INT COMMENT 'Duración en minutos',
`url_poster` VARCHAR(255),
`genero` VARCHAR(50)
);
-- Tabla de Proyecciones (Sesiones de Cine)
CREATE TABLE `sesiones_cine` (
`id_sesion` BIGINT PRIMARY KEY AUTO_INCREMENT,
`id_titulo` BIGINT NOT NULL,
`id_sala` BIGINT NOT NULL, -- Asumiendo una tabla 'salas' o 'cines'
`hora_inicio_proyeccion` DATETIME NOT NULL,
`precio_unitario` DECIMAL(10,2) NOT NULL,
`asientos_disponibles` INT,
FOREIGN KEY (`id_titulo`) REFERENCES `titulos_cine`(`id_titulo`)
);
-- Tabla de Reservas/Órdenes
CREATE TABLE `reservas_boletos` (
`id_reserva` BIGINT PRIMARY KEY AUTO_INCREMENT,
`id_cliente` BIGINT NOT NULL,
`id_sesion` BIGINT NOT NULL,
`lista_asientos_reservados` VARCHAR(255) COMMENT 'Ej: A1, B3, C5',
`estado_reserva` TINYINT COMMENT '0: Pendiente Pago, 1: Confirmada, 2: Cancelada',
`fecha_creacion` DATETIME DEFAULT CURRENT_TIMESTAMP,
`fecha_actualizacion` DATETIME DEFAULT CURRENT_TIMESTAMP ON UPDATE CURRENT_TIMESTAMP,
FOREIGN KEY (`id_cliente`) REFERENCES `clientes`(`id_cliente`),
FOREIGN KEY (`id_sesion`) REFERENCES `sesiones_cine`(`id_sesion`)
);
Estrategias de Prueba del Sistema
Para asegurar la calidad y fiabilidad de la plataforma, se proponen las siguientes metodologías de prueba:
Pruebas Unitarias
Empleo de JUnit 5 y Mockito para la validación de la lógica de negocio en la capa de servicios, aislando componentes.
Ejemplo de prueba de concurrencia en la reserva de boletos:
import org.junit.jupiter.api.Assertions;
import org.junit.jupiter.api.Test;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.Mockito;
import org.springframework.boot.test.context.SpringBootTest;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicInteger;
@SpringBootTest // O usar @ExtendWith(MockitoExtension.class) para un alcance más específico
class PruebaServicioReservasConcurrencia {
@Mock
private RepositorioSesiones repositorioSesiones; // Mock para la interacción con la base de datos de sesiones
@InjectMocks
private ServicioGestorBoletos servicioGestorBoletos; // El servicio que queremos probar
@Test
void verificarReservasEnEscenarioConcurrente() throws InterruptedException {
final Long ID_SESION_PELICULA = 101L;
final int ASIENTOS_INICIALES = 1; // Suponemos que inicialmente hay 1 asiento disponible
final int NUM_INTENTOS_CONCURRENTES = 50; // Múltiples usuarios intentan reservar
// Simula el estado inicial de la sesión: 1 asiento disponible.
// Utilizamos un AtomicInteger para modelar el decremento concurrente de asientos.
AtomicInteger asientosDisponiblesSimulados = new AtomicInteger(ASIENTOS_INICIALES);
// Configura el mock para simular la obtención del total de asientos disponibles.
Mockito.when(repositorioSesiones.obtenerTotalAsientosDisponibles(ID_SESION_PELICULA))
.thenAnswer(invocation -> asientosDisponiblesSimulados.get()); // Retorna el valor actual
// Simula la lógica de intentar reservar un asiento: si hay disponibilidad, se decrementa.
Mockito.doAnswer(invocation -> {
if (asientosDisponiblesSimulados.get() > 0) {
asientosDisponiblesSimulados.decrementAndGet();
return true; // Simula una reserva exitosa
}
return false; // Simula una reserva fallida por falta de asientos
}).when(repositorioSesiones).intentarReservarUnAsiento(ID_SESION_PELICULA);
ExecutorService ejecutorHilos = Executors.newFixedThreadPool(NUM_INTENTOS_CONCURRENTES);
CountDownLatch compuertaSincronizacion = new CountDownLatch(NUM_INTENTOS_CONCURRENTES);
for (int i = 0; i < NUM_INTENTOS_CONCURRENTES; i++) {
final int clienteId = i + 1;
ejecutorHilos.submit(() -> {
try {
// Cada "cliente" intenta reservar un boleto para la misma sesión.
// Se asume un método en el servicio que gestiona la compra.
servicioGestorBoletos.procesarCompraDeBoleto(ID_SESION_PELICULA, (long) clienteId);
} catch (Exception ignored) {
// Las excepciones por falta de asientos son esperadas y manejadas internamente por el servicio.
} finally {
compuertaSincronizacion.countDown();
}
});
}
compuertaSincronizacion.await(10, TimeUnit.SECONDS); // Espera hasta que todos los hilos terminen
ejecutorHilos.shutdown(); // Apaga el pool de hilos
// Verifica que, a pesar de los múltiples intentos, solo una reserva fue exitosa
// y el conteo de asientos disponibles simulados es 0.
Mockito.verify(repositorioSesiones, Mockito.times(1))
.intentarReservarUnAsiento(ID_SESION_PELICULA);
Assertions.assertEquals(0, asientosDisponiblesSimulados.get(),
"Debería quedar 0 asientos disponibles después de la reserva concurrente del único asiento.");
}
}
Pruebas de Integración
- Utilización de Postman o herramientas similares para validar la funcionalidad completa de las APIs, incluyendo escenarios de éxito y manejo de errores (ej. intentos de compra duplicados).
- Uso de Testcontainers para entornos de prueba aislados con bases de datos como MySQL en contenedores Docker.
Pruebas de Carga
- Realización de pruebas de estrés con herramientas como JMeter para simular volúmenes altos de transacciones (ej. >500 QPS en picos de venta de boletos).
- Monitoreo del rendimiento de Redis (tasa de aciertos de caché) y análisis de consultas lentas en MySQL para optimización.
Estructura de Directorios del Código Fuente
La organización del proyecto sigue una estructura modular estándar para aplicaciones Spring Boot:
src/
├── main/
│ ├── java/com/aplicacion/cine/
│ │ ├── configuracion/ # Clases para configuración general (seguridad, Redis, etc.)
│ │ ├── controladores/ # Puntos de entrada para las peticiones HTTP (APIs REST)
│ │ ├── servicios/ # Implementación de la lógica de negocio principal
│ │ ├── repositorios/ # Interfaces de acceso a datos (ej. Spring Data JPA, MyBatis)
│ │ └── modelos/ # Clases de entidades de dominio y DTOs
│ └── resources/
│ ├── application.yml # Ficheros de propiedades y configuración por entorno
│ └── mapper/ # (Si se usa MyBatis) Ficheros XML de mapeo SQL
└── test/ # Contiene todas las pruebas unitarias y de integración