From 447658ddc7c709f6f2ddc2736bc6cffb1b0bae99 Mon Sep 17 00:00:00 2001 From: Christian Julian Jimenez <zs22016079@estudiantes.uv.mx> Date: Mon, 10 Mar 2025 00:16:32 -0600 Subject: [PATCH] =?UTF-8?q?feat:=20Corregi=C3=B3n=20de=20archivos?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- corregido/conf/bd.sql | 89 ++++++++ corregido/controlador/VendedorController.php | 48 +++++ corregido/controlador/asientos.php | 38 ++++ corregido/controlador/comprobante.php | 17 ++ corregido/controlador/venta.php | 71 +++++++ corregido/index.php | 5 + corregido/modelo/BaseDatos.php | 94 +++++++++ corregido/modelo/Boleto.php | 46 +++++ corregido/modelo/Sala.php | 62 ++++++ corregido/modelo/Venta.php | 69 +++++++ corregido/vista/comprobante.html | 76 +++++++ corregido/vista/css/comprobante.css | 103 ++++++++++ corregido/vista/css/index.css | 199 ++++++++++++++++++ corregido/vista/images/icono.png | Bin 0 -> 24097 bytes corregido/vista/index.html | 93 +++++++++ corregido/vista/js/comprobante.js | 110 ++++++++++ corregido/vista/js/index.js | 206 +++++++++++++++++++ 17 files changed, 1326 insertions(+) create mode 100644 corregido/conf/bd.sql create mode 100644 corregido/controlador/VendedorController.php create mode 100644 corregido/controlador/asientos.php create mode 100644 corregido/controlador/comprobante.php create mode 100644 corregido/controlador/venta.php create mode 100644 corregido/index.php create mode 100644 corregido/modelo/BaseDatos.php create mode 100644 corregido/modelo/Boleto.php create mode 100644 corregido/modelo/Sala.php create mode 100644 corregido/modelo/Venta.php create mode 100644 corregido/vista/comprobante.html create mode 100644 corregido/vista/css/comprobante.css create mode 100644 corregido/vista/css/index.css create mode 100644 corregido/vista/images/icono.png create mode 100644 corregido/vista/index.html create mode 100644 corregido/vista/js/comprobante.js create mode 100644 corregido/vista/js/index.js diff --git a/corregido/conf/bd.sql b/corregido/conf/bd.sql new file mode 100644 index 0000000..fe44ff0 --- /dev/null +++ b/corregido/conf/bd.sql @@ -0,0 +1,89 @@ +-- Creación de la base de datos +CREATE DATABASE IF NOT EXISTS boletos_db; +USE boletos_db; + +-- Tabla de salas +CREATE TABLE IF NOT EXISTS salas ( + id INT AUTO_INCREMENT PRIMARY KEY, + nombre VARCHAR(100) NOT NULL, + filas INT NOT NULL, + asientos_por_fila INT NOT NULL +); + +-- Tabla de boletos +CREATE TABLE IF NOT EXISTS boletos ( + id INT AUTO_INCREMENT PRIMARY KEY, + id_sala INT NOT NULL, + fila INT NOT NULL, + numero INT NOT NULL, + precio DECIMAL(10,2) NOT NULL, + estado ENUM('disponible', 'vendido') DEFAULT 'disponible', + FOREIGN KEY (id_sala) REFERENCES salas(id), + UNIQUE KEY unique_asiento (id_sala, fila, numero) +); + +-- Tabla de ventas +CREATE TABLE IF NOT EXISTS ventas ( + id VARCHAR(36) PRIMARY KEY, + fecha DATETIME NOT NULL, + nombre_cliente VARCHAR(100) NOT NULL, + total DECIMAL(10,2) NOT NULL +); + +select * from ventas; + +select * from venta_boletos; + +select * from boletos; + +-- Tabla relacional venta-boletos +CREATE TABLE IF NOT EXISTS venta_boletos ( + id INT AUTO_INCREMENT PRIMARY KEY, + id_venta VARCHAR(36) NOT NULL, + id_boleto INT NOT NULL, + FOREIGN KEY (id_venta) REFERENCES ventas(id), + FOREIGN KEY (id_boleto) REFERENCES boletos(id), + UNIQUE KEY unique_venta_boleto (id_venta, id_boleto) +); + +-- Insertar una sala de ejemplo +INSERT INTO salas (id, nombre, filas, asientos_por_fila) +VALUES (1, 'Sala Principal', 10, 15); + +-- Procedimiento para inicializar boletos +DELIMITER // +CREATE PROCEDURE InicializaBoletos(IN sala_id INT, IN precio DECIMAL(10,2)) +BEGIN + DECLARE i INT DEFAULT 1; + DECLARE j INT DEFAULT 1; + DECLARE total_filas INT DEFAULT 0; + DECLARE total_asientos INT DEFAULT 0; + + -- Obtener dimensiones de la sala + SELECT filas, asientos_por_fila INTO total_filas, total_asientos + FROM salas WHERE id = sala_id; + + -- Depuración: Verificar valores obtenidos + SELECT CONCAT('Filas:', total_filas, ' Asientos:', total_asientos) AS Debug_Info; + + -- Si no se encuentran filas/asientos, salir del procedimiento + IF total_filas = 0 OR total_asientos = 0 THEN + SIGNAL SQLSTATE '45000' SET MESSAGE_TEXT = 'Sala no encontrada o sin dimensiones definidas'; + END IF; + + -- Eliminar boletos existentes de la sala + DELETE FROM boletos WHERE id_sala = sala_id; + + -- Crear nuevos boletos + SET i = 1; + WHILE i <= total_filas DO + SET j = 1; + WHILE j <= total_asientos DO + INSERT INTO boletos (id_sala, fila, numero, precio, estado) + VALUES (sala_id, i, j, precio, 'disponible'); + SET j = j + 1; + END WHILE; + SET i = i + 1; + END WHILE; +END // +DELIMITER ; \ No newline at end of file diff --git a/corregido/controlador/VendedorController.php b/corregido/controlador/VendedorController.php new file mode 100644 index 0000000..4d60fab --- /dev/null +++ b/corregido/controlador/VendedorController.php @@ -0,0 +1,48 @@ +<?php + +require_once '../modelo/BaseDatos.php'; +require_once '../modelo/Sala.php'; +require_once '../modelo/Boleto.php'; +require_once '../modelo/Venta.php'; + +class VendedorController { + private $baseDatos; + private $sala; + + public function __construct($baseDatos) { + $this->baseDatos = $baseDatos; + } + + public function cargarSala($idSala) { + $this->sala = $this->baseDatos->cargarSala($idSala); + return $this->sala; + } + + public function mostrarDisponibilidadAsientos() { + if ($this->sala) { + return $this->sala->disponibilidadAsientos(); + } + return null; + } + + public function seleccionarBoletos($idsBoletos) { + if ($this->sala) { + return $this->sala->obtenerBoletosPorId($idsBoletos); + } + return []; + } + + public function procesarVenta($boletos, $nombreCliente) { + $venta = new Venta($nombreCliente); + $venta->agregarBoletos($boletos); + + if (count($venta->getBoletos()) > 0) { + $resultado = $this->baseDatos->guardarVenta($venta); + if ($resultado) { + return $venta->generarComprobante(); + } + } + + return null; + } +} \ No newline at end of file diff --git a/corregido/controlador/asientos.php b/corregido/controlador/asientos.php new file mode 100644 index 0000000..fd8c6e4 --- /dev/null +++ b/corregido/controlador/asientos.php @@ -0,0 +1,38 @@ +<?php +// api/asientos.php - API para obtener el mapa de asientos +session_start(); + +require_once '../modelo/BaseDatos.php'; +require_once '../modelo/Sala.php'; +require_once '../modelo/Boleto.php'; +require_once '../modelo/Venta.php'; +require_once 'VendedorController.php'; + +header('Content-Type: application/json'); + +// Conexión a base de datos +$db = new BaseDatos('localhost:3306', 'root', '481037', 'boletos_db'); + +// Inicializar el controlador +$vendedorController = new VendedorController($db); + +// Cargar sala (solo hay una sala con id=1) +$sala = $vendedorController->cargarSala(1); + +// Si no hay sala, podríamos inicializarla para desarrollo +if (!$sala) { + $sala = new Sala(1, 'Sala Principal'); + $sala->inicializarBoletos(10, 15, 50.00); // 10 filas, 15 asientos por fila, $50 cada uno +} + +// Obtener mapa de asientos +$mapaAsientos = $vendedorController->mostrarDisponibilidadAsientos(); + +// Preparar respuesta +$response = [ + 'success' => true, + 'mapa' => $mapaAsientos, + 'precio' => 50.00 // Agregamos el precio de los boletos a la respuesta +]; + +echo json_encode($response); \ No newline at end of file diff --git a/corregido/controlador/comprobante.php b/corregido/controlador/comprobante.php new file mode 100644 index 0000000..7c544d9 --- /dev/null +++ b/corregido/controlador/comprobante.php @@ -0,0 +1,17 @@ +<?php +// comprobante.php - Lógica de servidor para el comprobante de venta +session_start(); + +// Verificar si hay un comprobante en la sesión +if (!isset($_SESSION['comprobante'])) { + header('Location: ../vista/index.html'); + exit; +} + +// Obtener el comprobante de la sesión +$comprobante = $_SESSION['comprobante']; + +// Devolver datos en formato JSON para ser consumidos por JavaScript +header('Content-Type: application/json'); +echo json_encode($comprobante); +?> \ No newline at end of file diff --git a/corregido/controlador/venta.php b/corregido/controlador/venta.php new file mode 100644 index 0000000..f976d92 --- /dev/null +++ b/corregido/controlador/venta.php @@ -0,0 +1,71 @@ +<?php +// api/venta.php - API para procesar ventas +session_start(); + +require_once '../modelo/BaseDatos.php'; +require_once '../modelo/Sala.php'; +require_once '../modelo/Boleto.php'; +require_once '../modelo/Venta.php'; +require_once 'VendedorController.php'; + +header('Content-Type: application/json'); + +// Verificar método de solicitud +if ($_SERVER['REQUEST_METHOD'] !== 'POST') { + echo json_encode(['success' => false, 'mensaje' => 'Método no permitido']); + exit; +} + +// Obtener datos JSON del cuerpo de la solicitud +$data = json_decode(file_get_contents('php://input'), true); + +if (!$data || !isset($data['asientos']) || !isset($data['nombre_cliente'])) { + echo json_encode(['success' => false, 'mensaje' => 'Datos incompletos']); + exit; +} + +// Extraer datos +$idsBoletos = $data['asientos']; +$nombreCliente = $data['nombre_cliente']; + +// Validar que haya asientos seleccionados +if (empty($idsBoletos)) { + echo json_encode(['success' => false, 'mensaje' => 'No se han seleccionado asientos']); + exit; +} + +// Conexión a base de datos +$db = new BaseDatos('localhost:3306', 'root', '481037', 'boletos_db'); + +// Inicializar el controlador +$vendedorController = new VendedorController($db); + +// Cargar la sala (agregando esta línea) +$vendedorController->cargarSala(1); // O el ID de sala correspondiente + +// Seleccionar boletos +$boletosSeleccionados = $vendedorController->seleccionarBoletos($idsBoletos); + +if (count($boletosSeleccionados) == 0) { + echo json_encode(['success' => false, 'mensaje' => 'No se han seleccionado asientos disponibles']); + exit; +} + +// Procesar venta +$comprobante = $vendedorController->procesarVenta($boletosSeleccionados, $nombreCliente); + +if (!$comprobante) { + echo json_encode(['success' => false, 'mensaje' => 'Error al procesar la venta. Intente nuevamente']); + exit; +} + +// Guardar comprobante en sesión +$_SESSION['comprobante'] = $comprobante; + +// Respuesta exitosa +echo json_encode([ + 'success' => true, + 'mensaje' => 'Venta procesada con éxito', + 'redirect' => '../vista/comprobante.html', + 'comprobante' => $comprobante +]); \ No newline at end of file diff --git a/corregido/index.php b/corregido/index.php new file mode 100644 index 0000000..968cc46 --- /dev/null +++ b/corregido/index.php @@ -0,0 +1,5 @@ +<?php +// Redirigir a la página principal del sistema +header("Location: vista/index.html"); +exit(); +?> diff --git a/corregido/modelo/BaseDatos.php b/corregido/modelo/BaseDatos.php new file mode 100644 index 0000000..c27b5a1 --- /dev/null +++ b/corregido/modelo/BaseDatos.php @@ -0,0 +1,94 @@ +<?php +require_once 'Sala.php'; +require_once 'Boleto.php'; +require_once 'Venta.php'; + +class BaseDatos { + private $conexion; + + public function __construct($host, $usuario, $password, $nombreBD) { + $this->conexion = new mysqli($host, $usuario, $password, $nombreBD); + + if ($this->conexion->connect_error) { + die("Error de conexión: " . $this->conexion->connect_error); + } + } + + public function cargarSala($idSala) { + $sala = null; + $query = "SELECT id, nombre FROM salas WHERE id = ?"; + $stmt = $this->conexion->prepare($query); + $stmt->bind_param("i", $idSala); + $stmt->execute(); + $result = $stmt->get_result(); + + if ($fila = $result->fetch_assoc()) { + $sala = new Sala($fila['id'], $fila['nombre']); + + // Cargar boletos de la sala + $queryBoletos = "SELECT id, fila, numero, precio, estado FROM boletos WHERE id_sala = ?"; + $stmtBoletos = $this->conexion->prepare($queryBoletos); + $stmtBoletos->bind_param("i", $idSala); + $stmtBoletos->execute(); + $resultBoletos = $stmtBoletos->get_result(); + + $boletos = []; + while ($filaBoleto = $resultBoletos->fetch_assoc()) { + $boleto = new Boleto($filaBoleto['id'], $filaBoleto['fila'], $filaBoleto['numero'], $filaBoleto['precio']); + if ($filaBoleto['estado'] === 'vendido') { + $boleto->marcarComoVendido(); + } + $boletos[] = $boleto; + } + + // Asignar boletos a la sala + $sala->setBoletos($boletos); + } + + return $sala; + } + + public function guardarVenta($venta) { + // Iniciar transacción + $this->conexion->begin_transaction(); + + try { + // Insertar venta + $query = "INSERT INTO ventas (id, fecha, nombre_cliente, total) VALUES (?, ?, ?, ?)"; + $stmt = $this->conexion->prepare($query); + $id = $venta->getId(); + $fecha = $venta->getFecha(); + $nombreCliente = $venta->getNombreCliente(); + $total = $venta->getTotal(); + $stmt->bind_param("sssd", $id, $fecha, $nombreCliente, $total); + $stmt->execute(); + + // Actualizar estado de boletos + foreach ($venta->getBoletos() as $boleto) { + $queryBoleto = "UPDATE boletos SET estado = 'vendido' WHERE id = ?"; + $stmtBoleto = $this->conexion->prepare($queryBoleto); + $idBoleto = $boleto->getId(); + $stmtBoleto->bind_param("i", $idBoleto); + $stmtBoleto->execute(); + + // Insertar relación venta-boleto + $queryRelacion = "INSERT INTO venta_boletos (id_venta, id_boleto) VALUES (?, ?)"; + $stmtRelacion = $this->conexion->prepare($queryRelacion); + $stmtRelacion->bind_param("si", $id, $idBoleto); + $stmtRelacion->execute(); + } + + // Confirmar transacción + $this->conexion->commit(); + return true; + } catch (Exception $e) { + // Revertir transacción en caso de error + $this->conexion->rollback(); + return false; + } + } + + public function cerrarConexion() { + $this->conexion->close(); + } +} \ No newline at end of file diff --git a/corregido/modelo/Boleto.php b/corregido/modelo/Boleto.php new file mode 100644 index 0000000..bec7a82 --- /dev/null +++ b/corregido/modelo/Boleto.php @@ -0,0 +1,46 @@ +<?php +class Boleto { + private $id; + private $fila; + private $numero; + private $precio; + private $estado; // 'disponible' o 'vendido' + + public function __construct($id, $fila, $numero, $precio) { + $this->id = $id; + $this->fila = $fila; + $this->numero = $numero; + $this->precio = $precio; + $this->estado = 'disponible'; + } + + // Getters y setters + public function getId() { + return $this->id; + } + + public function getFila() { + return $this->fila; + } + + public function getNumero() { + return $this->numero; + } + + public function getPrecio() { + return $this->precio; + } + + public function getEstado() { + return $this->estado; + } + + public function marcarComoVendido() { + $this->estado = 'vendido'; + return true; + } + + public function estaDisponible() { + return $this->estado === 'disponible'; + } +} \ No newline at end of file diff --git a/corregido/modelo/Sala.php b/corregido/modelo/Sala.php new file mode 100644 index 0000000..b1ea1af --- /dev/null +++ b/corregido/modelo/Sala.php @@ -0,0 +1,62 @@ +<?php +require_once 'Boleto.php'; + +class Sala { + private $id; + private $nombre; + private $boletos = []; + + public function __construct($id, $nombre) { + $this->id = $id; + $this->nombre = $nombre; + } + + public function getId() { + return $this->id; + } + + public function getNombre() { + return $this->nombre; + } + + public function inicializarBoletos($filas, $asientosPorFila, $precio) { + $this->boletos = []; + $contador = 1; + + for ($i = 1; $i <= $filas; $i++) { + for ($j = 1; $j <= $asientosPorFila; $j++) { + $this->boletos[] = new Boleto($contador, $i, $j, $precio); + $contador++; + } + } + } + + public function obtenerBoletos() { + return $this->boletos; + } + + public function obtenerBoletosPorEstado($estado) { + return array_filter($this->boletos, function($boleto) use ($estado) { + return $boleto->getEstado() === $estado; + }); + } + + public function obtenerBoletosPorId($ids) { + return array_filter($this->boletos, function($boleto) use ($ids) { + return in_array($boleto->getId(), $ids); + }); + } + + public function disponibilidadAsientos() { + $mapa = []; + foreach ($this->boletos as $boleto) { + $mapa[$boleto->getFila()][$boleto->getNumero()] = $boleto->getEstado(); + } + return $mapa; + } + + // Setter para asignar boletos desde la base de datos + public function setBoletos($boletos) { + $this->boletos = $boletos; + } +} \ No newline at end of file diff --git a/corregido/modelo/Venta.php b/corregido/modelo/Venta.php new file mode 100644 index 0000000..1df14ae --- /dev/null +++ b/corregido/modelo/Venta.php @@ -0,0 +1,69 @@ +<?php +require_once 'Boleto.php'; + +class Venta { + private $id; + private $fecha; + private $boletos = []; + private $total; + private $nombreCliente; + + public function __construct($nombreCliente) { + $this->id = bin2hex(random_bytes(8)); + date_default_timezone_set('America/Mexico_City'); + $this->fecha = date('Y-m-d H:i:s'); + $this->nombreCliente = $nombreCliente; + $this->total = 0; + } + + public function getId() { + return $this->id; + } + + public function getFecha() { + return $this->fecha; + } + + public function getNombreCliente() { + return $this->nombreCliente; + } + + public function agregarBoletos($boletos) { + foreach ($boletos as $boleto) { + if ($boleto->estaDisponible()) { + $this->boletos[] = $boleto; + $this->total += $boleto->getPrecio(); + $boleto->marcarComoVendido(); + } + } + } + + public function generarComprobante() { + $comprobante = [ + 'id_venta' => $this->id, + 'fecha' => $this->fecha, + 'cliente' => $this->nombreCliente, + 'boletos' => [], + 'total' => $this->total + ]; + + foreach ($this->boletos as $boleto) { + $comprobante['boletos'][] = [ + 'id' => $boleto->getId(), + 'fila' => $boleto->getFila(), + 'numero' => $boleto->getNumero(), + 'precio' => $boleto->getPrecio() + ]; + } + + return $comprobante; + } + + public function getTotal() { + return $this->total; + } + + public function getBoletos() { + return $this->boletos; + } +} \ No newline at end of file diff --git a/corregido/vista/comprobante.html b/corregido/vista/comprobante.html new file mode 100644 index 0000000..b517579 --- /dev/null +++ b/corregido/vista/comprobante.html @@ -0,0 +1,76 @@ +<!DOCTYPE html> +<html lang="es"> +<head> + <meta charset="UTF-8"> + <meta name="viewport" content="width=device-width, initial-scale=1.0"> + <title>Comprobante de Venta</title> + <link rel="stylesheet" href="css/comprobante.css"> +</head> +<body> + <div class="container"> + <h1>Comprobante de Venta</h1> + + <div class="comprobante" id="comprobante-imprimible"> + <div class="header"> + <h2>Boletos para Concierto - Sala Principal</h2> + </div> + + <div class="info-venta"> + <div> + <div class="info-item"> + <span class="info-label">Nº de Venta:</span> + <span id="id-venta"></span> + </div> + <div class="info-item"> + <span class="info-label">Fecha:</span> + <span id="fecha-venta"></span> + </div> + </div> + <div> + <div class="info-item"> + <span class="info-label">Cliente:</span> + <span id="cliente-venta"></span> + </div> + </div> + </div> + + <h3>Detalle de Boletos</h3> + <table> + <thead> + <tr> + <th>#</th> + <th>Ubicación</th> + <th>Precio</th> + </tr> + </thead> + <tbody id="detalle-boletos"> + <!-- El contenido de la tabla se generará dinámicamente con JavaScript --> + </tbody> + </table> + + <div class="total"> + Total: $<span id="total-venta"></span> + </div> + + <div class="qr-container"> + <div id="qr-code"></div> + <p class="qr-info">Este QR contiene el número de venta para verificación</p> + </div> + + <div class="mensaje"> + <p>¡Gracias por su compra!</p> + <p>Este comprobante es su entrada oficial para el evento.</p> + <p>Por favor, preséntelo en la entrada del concierto.</p> + </div> + </div> + + <div class="acciones"> + <a href="index.html" class="btn">Volver a Ventas</a> + <button class="btn btn-print" onclick="imprimirComprobante()">Imprimir Comprobante</button> + </div> + </div> + + <script src="https://cdnjs.cloudflare.com/ajax/libs/qrcodejs/1.0.0/qrcode.min.js"></script> + <script src="js/comprobante.js"></script> +</body> +</html> \ No newline at end of file diff --git a/corregido/vista/css/comprobante.css b/corregido/vista/css/comprobante.css new file mode 100644 index 0000000..fcf94c0 --- /dev/null +++ b/corregido/vista/css/comprobante.css @@ -0,0 +1,103 @@ +body { + font-family: Arial, sans-serif; + margin: 0; + padding: 20px; + background-color: #f5f5f5; +} +.container { + max-width: 800px; + margin: 0 auto; + background-color: #fff; + padding: 20px; + border-radius: 8px; + box-shadow: 0 2px 4px rgba(0,0,0,0.1); +} +h1, h2 { + color: #333; +} +.comprobante { + border: 1px solid #ddd; + padding: 20px; + border-radius: 4px; + margin: 20px 0; +} +.header { + border-bottom: 2px solid #333; + padding-bottom: 10px; + margin-bottom: 20px; +} +.info-venta { + display: flex; + justify-content: space-between; + margin-bottom: 20px; +} +.info-item { + margin-bottom: 10px; +} +.info-label { + font-weight: bold; +} +table { + width: 100%; + border-collapse: collapse; + margin-bottom: 20px; +} +th, td { + border: 1px solid #ddd; + padding: 8px; + text-align: left; +} +th { + background-color: #f8f9fa; +} +.total { + font-size: 18px; + font-weight: bold; + text-align: right; + margin-top: 20px; + padding-top: 10px; + border-top: 1px solid #ddd; +} +.acciones { + display: flex; + justify-content: space-between; + margin-top: 20px; +} +.btn { + background-color: #007bff; + color: white; + border: none; + padding: 10px 20px; + border-radius: 4px; + text-decoration: none; + cursor: pointer; + display: inline-block; +} +.btn-print { + background-color: #6c757d; +} +.btn:hover { + opacity: 0.9; +} + +.qr-container { + text-align: center; + margin: 30px 0; + padding: 10px; + border-top: 1px dashed #ccc; + padding-top: 20px; +} +#qr-code { + display: inline-block; + margin: 0 auto; +} +.qr-info { + font-size: 14px; + margin-top: 10px; +} + +.mensaje { + margin-top: 40px; + font-size: 14px; + text-align: center; +} \ No newline at end of file diff --git a/corregido/vista/css/index.css b/corregido/vista/css/index.css new file mode 100644 index 0000000..bb656a5 --- /dev/null +++ b/corregido/vista/css/index.css @@ -0,0 +1,199 @@ +body { + font-family: Arial, sans-serif; + margin: 0; + padding: 20px; + background-color: #f5f5f5; +} + +.container { + max-width: 1200px; + margin: 0 auto; + background-color: #fff; + padding: 20px; + border-radius: 8px; + box-shadow: 0 2px 4px rgba(0,0,0,0.1); +} + +.main-content { + display: flex; + flex-wrap: wrap; + gap: 20px; + margin-top: 20px; +} + +.sala { + flex: 1; + min-width: 300px; + margin: 20px 0; + text-align: center; +} + +.sidebar { + flex: 0 0 350px; +} + +h1, h2 { + color: #333; +} + +.mensaje { + background-color: #f8d7da; + color: #721c24; + padding: 10px; + margin-bottom: 20px; + border-radius: 4px; + display: none; +} + +.escenario { + background-color: #ddd; + padding: 10px; + margin-bottom: 30px; + border-radius: 4px; + text-align: center; + font-weight: bold; +} + +.filas { + display: flex; + flex-direction: column; + align-items: center; + gap: 10px; +} + +.fila { + display: flex; + gap: 10px; + align-items: center; +} + +.numero-fila { + width: 30px; + text-align: center; + font-weight: bold; +} + +.asientos { + display: flex; + gap: 5px; +} + +.asiento { + width: 35px; + height: 35px; + display: flex; + align-items: center; + justify-content: center; + border-radius: 5px; + cursor: pointer; + user-select: none; + font-size: 12px; +} + +.disponible { + background-color: #28a745; + color: white; +} + +.vendido { + background-color: #dc3545; + color: white; + cursor: not-allowed; +} + +.seleccionado { + background-color: #007bff; + color: white; +} + +.form-group { + margin-bottom: 15px; +} + +label { + display: block; + margin-bottom: 5px; + font-weight: bold; +} + +input[type="text"] { + width: 100%; + padding: 8px; + border: 1px solid #ddd; + border-radius: 4px; + box-sizing: border-box; +} + +.btn { + background-color: #007bff; + color: white; + border: none; + padding: 10px 20px; + border-radius: 4px; + cursor: pointer; +} + +.btn:hover { + background-color: #0069d9; +} + +.resumen, .concierto-detalles, #formularioVenta { + background-color: #f8f9fa; + border-radius: 4px; +} + +.resumen { + margin-top: 20px; + padding: 15px; +} + +.concierto-detalles { + border-left: 4px solid #6c757d; +} + +#formularioVenta { + padding: 20px; + border: 1px solid #ddd; + height: fit-content; +} + +.concierto-detalles i { + color: #6c757d; + margin-right: 5px; + width: 20px; + text-align: center; +} + +#formularioVenta i { + color: #495057; + margin-right: 5px; +} + +.leyenda { + display: flex; + gap: 15px; + margin-top: 20px; + justify-content: center; +} + +.leyenda-item { + display: flex; + align-items: center; + gap: 5px; +} + +.leyenda-color { + width: 20px; + height: 20px; + border-radius: 3px; +} + +@media (max-width: 992px) { + .main-content { + flex-direction: column; + } + + .sidebar { + width: 100%; + } +} \ No newline at end of file diff --git a/corregido/vista/images/icono.png b/corregido/vista/images/icono.png new file mode 100644 index 0000000000000000000000000000000000000000..b7d11e951f10559c3f2c07aae868aabab8f646ff GIT binary patch literal 24097 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 literal 0 HcmV?d00001 diff --git a/corregido/vista/index.html b/corregido/vista/index.html new file mode 100644 index 0000000..973f533 --- /dev/null +++ b/corregido/vista/index.html @@ -0,0 +1,93 @@ +<!DOCTYPE html> +<html lang="es"> +<head> + <meta charset="UTF-8"> + <meta name="viewport" content="width=device-width, initial-scale=1.0"> + <title>Sistema de Venta de Boletos</title> + <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.0.2/dist/css/bootstrap.min.css" rel="stylesheet" integrity="sha384-EVSTQN3/azprG1Anm3QDgpJLIm9Nao0Yz1ztcQTwFspd3yD65VohhpuuCOmLASjC" crossorigin="anonymous"> + <link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.0/font/bootstrap-icons.css"> + <link rel="stylesheet" href="css/index.css"> + <style> + .app-title { + display: flex; + align-items: center; + gap: 15px; + } + + .app-icon { + height: 40px; + width: auto; + } + </style> +</head> +<body> + <div class="container"> + <div class="app-title"> + <img src="images/icono.png" alt="TicketFlow Logo" class="app-icon"> + <h1>TicketFlow</h1> + </div> + + <div id="mensajeAlerta" class="alert alert-warning d-none" role="alert"></div> + + <div class="main-content"> + <div class="sala"> + <h2>Selección de Asientos - Sala Principal</h2> + <div class="escenario">ESCENARIO</div> + + <div class="filas" id="mapaAsientos"> + <!-- El mapa de asientos se cargará dinámicamente con JavaScript --> + </div> + + <div class="leyenda"> + <div class="leyenda-item"> + <div class="leyenda-color" style="background-color: #28a745;"></div> + <span>Disponible</span> + </div> + <div class="leyenda-item"> + <div class="leyenda-color" style="background-color: #007bff;"></div> + <span>Seleccionado</span> + </div> + <div class="leyenda-item"> + <div class="leyenda-color" style="background-color: #dc3545;"></div> + <span>Vendido</span> + </div> + </div> + </div> + + <div class="sidebar"> + <div class="concierto-detalles mb-4 p-3 bg-light border rounded"> + <h3 class="mb-3"><i class="bi bi-music-note-beamed"></i> Detalles del Concierto</h3> + <ul class="list-unstyled"> + <li class="mb-2"><i class="bi bi-person-circle"></i> <strong>Artista:</strong> Nombre del Artista</li> + <li class="mb-2"><i class="bi bi-calendar-event"></i> <strong>Fecha:</strong> 15 de abril, 2025</li> + <li class="mb-2"><i class="bi bi-clock"></i> <strong>Hora:</strong> 20:00 hrs</li> + <li class="mb-2"><i class="bi bi-geo-alt"></i> <strong>Lugar:</strong> Arena Principal</li> + <li class="mb-2"><i class="bi bi-hourglass-split"></i> <strong>Duración:</strong> 2 horas</li> + <li class="mb-2"><i class="bi bi-door-open"></i> <strong>Apertura:</strong> 18:30 hrs</li> + </ul> + </div> + + <form id="formularioVenta"> + <h3><i class="bi bi-cart"></i> Datos de Venta</h3> + <div class="form-group mb-3"> + <label for="nombre_cliente"><i class="bi bi-person"></i> Nombre del Cliente:</label> + <input type="text" id="nombre_cliente" name="nombre_cliente" class="form-control" required> + </div> + + <div class="resumen" id="resumen"> + <h3><i class="bi bi-ticket-perforated"></i> Resumen de Selección</h3> + <p><i class="bi bi-seat"></i> Asientos seleccionados: <span id="asientosSeleccionados">Ninguno</span></p> + <p><i class="bi bi-cash"></i> Total: $<span id="totalVenta">0.00</span></p> + <button type="submit" id="btnVender" class="btn btn-primary"><i class="bi bi-check-circle"></i> Confirmar Venta</button> + </div> + + <!-- Campo oculto para almacenar IDs de asientos seleccionados --> + <div id="asientosSeleccionadosInput"></div> + </form> + </div> + </div> + </div> + + <script src="js/index.js"></script> +</body> +</html> \ No newline at end of file diff --git a/corregido/vista/js/comprobante.js b/corregido/vista/js/comprobante.js new file mode 100644 index 0000000..0978029 --- /dev/null +++ b/corregido/vista/js/comprobante.js @@ -0,0 +1,110 @@ +// comprobante.js - Maneja la comunicación entre el PHP y el HTML + +// Cuando el documento esté listo, cargar los datos del comprobante +document.addEventListener('DOMContentLoaded', cargarComprobante); + +// Función para cargar los datos del comprobante desde el servidor +function cargarComprobante() { + fetch('../controlador/comprobante.php') + .then(response => { + if (!response.ok) { + throw new Error('Error al obtener los datos del comprobante'); + } + return response.json(); + }) + .then(comprobante => { + // Llenar los datos básicos del comprobante + document.getElementById('id-venta').textContent = comprobante.id_venta; + document.getElementById('fecha-venta').textContent = comprobante.fecha; + document.getElementById('cliente-venta').textContent = comprobante.cliente; + document.getElementById('total-venta').textContent = formatearNumero(comprobante.total); + + // Generar las filas de la tabla de boletos + const tablaBoletos = document.getElementById('detalle-boletos'); + let contenidoTabla = ''; + + comprobante.boletos.forEach((boleto, index) => { + contenidoTabla += ` + <tr> + <td>${index + 1}</td> + <td>Fila ${boleto.fila}, Asiento ${boleto.numero}</td> + <td>$${formatearNumero(boleto.precio)}</td> + </tr> + `; + }); + + tablaBoletos.innerHTML = contenidoTabla; + + // Generar código QR con el ID de la venta + generarQR(comprobante.id_venta); + }) + .catch(error => { + console.error('Error:', error); + alert('No se pudo cargar el comprobante. Por favor, inténtelo de nuevo.'); + window.location.href = 'index.html'; + }); +} + +// Función para generar el código QR +function generarQR(idVenta) { + // Verificar si existe el elemento donde se mostrará el QR + const qrContainer = document.getElementById('qr-code'); + if (!qrContainer) { + console.error('No se encontró el elemento para mostrar el código QR'); + return; + } + + // Limpiar el contenedor por si ya tiene contenido + qrContainer.innerHTML = ''; + + // Crear una nueva instancia de QRCode + new QRCode(qrContainer, { + text: idVenta.toString(), + width: 128, + height: 128, + colorDark: "#000000", + colorLight: "#ffffff", + correctLevel: QRCode.CorrectLevel.H // Alta corrección de errores + }); +} + +// Función para imprimir el comprobante +function imprimirComprobante() { + const contenido = document.getElementById('comprobante-imprimible').innerHTML; + const ventanaImpresion = window.open('', '_blank'); + + ventanaImpresion.document.write(` + <html> + <head> + <title>Comprobante de Venta</title> + <style> + body { font-family: Arial, sans-serif; } + .header { border-bottom: 2px solid #333; padding-bottom: 10px; margin-bottom: 20px; } + .info-venta { display: flex; justify-content: space-between; margin-bottom: 20px; } + .info-item { margin-bottom: 10px; } + .info-label { font-weight: bold; } + table { width: 100%; border-collapse: collapse; margin-bottom: 20px; } + th, td { border: 1px solid #ddd; padding: 8px; text-align: left; } + th { background-color: #f8f9fa; } + .total { font-size: 18px; font-weight: bold; text-align: right; margin-top: 20px; padding-top: 10px; border-top: 1px solid #ddd; } + .qr-container { text-align: center; margin: 20px 0; border-top: 1px dashed #ccc; padding-top: 20px;} + #qr-code { display: inline-block; margin: 0 auto; } + .qr-info { font-size: 14px; margin-top: 10px; } + .mensaje { margin-top: 40px; font-size: 14px; text-align: center;} + </style> + </head> + <body> + ${contenido} + </body> + </html> + `); + + ventanaImpresion.document.close(); + ventanaImpresion.focus(); + ventanaImpresion.print(); +} + +// Función auxiliar para formatear números con dos decimales +function formatearNumero(numero) { + return Number(numero).toFixed(2); +} \ No newline at end of file diff --git a/corregido/vista/js/index.js b/corregido/vista/js/index.js new file mode 100644 index 0000000..ce74209 --- /dev/null +++ b/corregido/vista/js/index.js @@ -0,0 +1,206 @@ +// js/index.js - Script para manejar la interfaz de usuario y la comunicación con el backend + +document.addEventListener('DOMContentLoaded', function() { + let seleccionados = []; + const precioBoleto = 50.00; // Precio por boleto + + // Cargar el mapa de asientos al iniciar + cargarMapaAsientos(); + + // Agregar manejador para el formulario + const formulario = document.getElementById('formularioVenta'); + formulario.addEventListener('submit', validarYEnviarFormulario); + + // Función para cargar el mapa de asientos desde la API + async function cargarMapaAsientos() { + try { + const response = await fetch('../controlador/asientos.php'); + const data = await response.json(); + + if (!data.success) { + mostrarMensaje('Error al cargar el mapa de asientos', 'error'); + return; + } + + renderizarMapaAsientos(data.mapa); + } catch (error) { + console.error('Error al cargar el mapa de asientos:', error); + mostrarMensaje('Error de conexión con el servidor', 'error'); + } + } + + // Función para renderizar el mapa de asientos + function renderizarMapaAsientos(mapa) { + const contenedor = document.getElementById('mapaAsientos'); + contenedor.innerHTML = ''; + + Object.entries(mapa).forEach(([numeroFila, asientosEnFila]) => { + const filaElement = document.createElement('div'); + filaElement.className = 'fila'; + + // Crear elemento para el número de fila + const numeroFilaElement = document.createElement('div'); + numeroFilaElement.className = 'numero-fila'; + numeroFilaElement.textContent = `F${numeroFila}`; + filaElement.appendChild(numeroFilaElement); + + // Crear contenedor para los asientos + const asientosContainer = document.createElement('div'); + asientosContainer.className = 'asientos'; + + // Crear cada asiento + Object.entries(asientosEnFila).forEach(([numeroAsiento, estado]) => { + const idBoleto = ((parseInt(numeroFila) - 1) * Object.keys(asientosEnFila).length) + parseInt(numeroAsiento); + const asiento = document.createElement('div'); + + // Determinar la clase según el estado + asiento.className = `asiento ${estado === 'disponible' ? 'disponible' : 'vendido'}`; + asiento.dataset.id = idBoleto; + asiento.textContent = numeroAsiento; + + asientosContainer.appendChild(asiento); + }); + + filaElement.appendChild(asientosContainer); + contenedor.appendChild(filaElement); + }); + + // Una vez que se ha renderizado, añadir los eventos + configurarEventosAsientos(); + } + + // Configurar eventos para los asientos + function configurarEventosAsientos() { + const asientos = document.querySelectorAll('.asiento.disponible'); + + asientos.forEach(asiento => { + asiento.addEventListener('click', function() { + const asientoId = parseInt(this.getAttribute('data-id')); + + // Verificar si ya está seleccionado + const indice = seleccionados.findIndex(a => a.id === asientoId); + + if (indice === -1) { + // Agregar a seleccionados + seleccionados.push({ + id: asientoId, + elemento: this + }); + this.classList.remove('disponible'); + this.classList.add('seleccionado'); + } else { + // Quitar de seleccionados + seleccionados.splice(indice, 1); + this.classList.remove('seleccionado'); + this.classList.add('disponible'); + } + + actualizarResumen(); + }); + }); + } + + // Función para actualizar el resumen de venta + function actualizarResumen() { + const resumenAsientos = document.getElementById('asientosSeleccionados'); + const resumenTotal = document.getElementById('totalVenta'); + const asientosInput = document.getElementById('asientosSeleccionadosInput'); + + if (seleccionados.length === 0) { + resumenAsientos.textContent = 'Ninguno'; + resumenTotal.textContent = '0.00'; + + // Limpiar los inputs ocultos + if (asientosInput) { + asientosInput.innerHTML = ''; + } + } else { + // Mostrar los asientos seleccionados + const detalles = seleccionados.map(asiento => { + const fila = Math.floor((asiento.id - 1) / 15) + 1; + const numero = ((asiento.id - 1) % 15) + 1; + return `F${fila}-${numero}`; + }); + + resumenAsientos.textContent = detalles.join(', '); + resumenTotal.textContent = (seleccionados.length * precioBoleto).toFixed(2); + + // Actualizar campo oculto para el envío del formulario + if (asientosInput) { + asientosInput.innerHTML = ''; + seleccionados.forEach(asiento => { + const input = document.createElement('input'); + input.type = 'hidden'; + input.name = 'asientos[]'; + input.value = asiento.id; + asientosInput.appendChild(input); + }); + } + } + } + + // Función para validar y enviar el formulario + function validarYEnviarFormulario(e) { + e.preventDefault(); + + if (seleccionados.length === 0) { + mostrarMensaje('Por favor, seleccione al menos un asiento.'); + return false; + } + + const nombreCliente = document.getElementById('nombre_cliente').value.trim(); + if (nombreCliente === '') { + mostrarMensaje('Por favor, ingrese el nombre del cliente.'); + return false; + } + + // Preparar los datos para enviar + const asientosIds = seleccionados.map(asiento => asiento.id); + const datos = { + asientos: asientosIds, + nombre_cliente: nombreCliente + }; + + // Enviar los datos mediante fetch + fetch('../controlador/venta.php', { + method: 'POST', + headers: { + 'Content-Type': 'application/json' + }, + body: JSON.stringify(datos) + }) + .then(response => response.json()) + .then(result => { + if (result.success) { + // Redireccionar a la página de comprobante + window.location.href = result.redirect || 'comprobante.php'; + } else { + mostrarMensaje(result.mensaje || 'Error al procesar la venta', 'error'); + } + }) + .catch(error => { + console.error('Error:', error); + mostrarMensaje('Error de conexión con el servidor', 'error'); + }); + + return false; + } + + // Función para mostrar mensajes + function mostrarMensaje(texto, tipo = 'warning') { + const mensajeElement = document.getElementById('mensajeAlerta'); + if (mensajeElement) { + mensajeElement.textContent = texto; + mensajeElement.className = `alert alert-${tipo === 'error' ? 'danger' : 'warning'}`; + mensajeElement.classList.remove('d-none'); + + // Ocultar después de 5 segundos + setTimeout(() => { + mensajeElement.classList.add('d-none'); + }, 5000); + } else { + // Si no existe el elemento, usar alert + alert(texto); + } + } +}); \ No newline at end of file