18 Commits

Author SHA1 Message Date
jorge d8c8cea558 Merge pull request 'añadir funcion iniciar y finalizar en la ClasePartida' (#14) from control_inicio into main
Reviewed-on: #14
2026-09-29 13:27:18 +02:00
jorge de19ea1861 Merge pull request 'Resultadophp' (#13) from resultadophp into main
Reviewed-on: #13
2026-09-29 13:23:03 +02:00
jorge 30dd4a0e2f Merge pull request 'Add funcionamiento del juego' (#12) from juegophp into main
Reviewed-on: #12
Reviewed-by: jorge <jdavidnunez@gmail.com>
2026-09-29 13:20:15 +02:00
dbvictor2002 7ae1b14e6e Subir archivos a "js" 2026-09-29 13:19:26 +02:00
dbvictor2002 e64761400d Subir archivos a "css" 2026-09-29 13:18:57 +02:00
dbvictor2002 00e1c04868 Subir archivos a "controladores" 2026-09-29 13:18:33 +02:00
dbvictor2002 8e1ccc6718 Subir archivos a "/" 2026-09-29 13:18:16 +02:00
jorge bdebd32f80 Merge pull request 'Host2' (#11) from host2 into main
Reviewed-on: #11
2026-09-29 13:12:04 +02:00
jorge 49f950bbd0 Merge pull request 'Toast de confirmacion de pregunta creada' (#10) from toast into main
Reviewed-on: #10
Reviewed-by: jorge <jdavidnunez@gmail.com>
2026-09-29 13:04:21 +02:00
jorge b8f977e437 Merge pull request 'archivo .env + conexionBD' (#2) from archivo_env into main
Reviewed-on: #2
2026-09-29 12:19:14 +02:00
Isaac c8c0792bd7 Más funcionalidades de host 2026-09-29 11:27:18 +02:00
Isaac 534c20a45d Eliminar Sala (Se ejecuta cuando el host abandona la sala)
He modificado salirDeSala() de ClaseEquipoxUsuario.php
2026-09-29 11:25:56 +02:00
jorge c8199aa261 Añadida BD definitiva 2.0
Añadido a main la base de datos (modificada por Víctor Díaz)
2026-09-29 11:25:26 +02:00
v.rodriguezcamacho f5a354025e Toast de confirmacion de pregunta creada 2026-09-29 11:24:42 +02:00
margarita 04fe80946f añadir funcion iniciar y finalizar en la ClasePartida 2026-09-29 11:06:35 +02:00
alexis 449cb1d976 Add funcionamiento del juego 2026-09-29 10:50:58 +02:00
Isaac 94bfe480cb Arreglado iconos bootstrap 2026-09-29 10:11:25 +02:00
Juanjo 14e2fd48a1 archivo .env + conexionBD 2026-09-29 09:41:40 +02:00
26 changed files with 2306 additions and 420 deletions
+2 -1
View File
@@ -7,6 +7,7 @@
<title>Trivial - Lobby</title> <title>Trivial - Lobby</title>
<link rel="icon" type="image/svg+xml" href="img/favicon/favicon.svg"> <link rel="icon" type="image/svg+xml" href="img/favicon/favicon.svg">
<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet"> <link href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css" rel="stylesheet">
<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.13.1/font/bootstrap-icons.min.css">
<link rel="stylesheet" href="css/index.css"> <link rel="stylesheet" href="css/index.css">
<link rel="stylesheet" href="css/buscadorSala.css"> <link rel="stylesheet" href="css/buscadorSala.css">
</head> </head>
@@ -83,7 +84,7 @@
<div class="modal-body"> <div class="modal-body">
<div class="mb-3 fs-1">⚠️</div> <div class="mb-3 fs-1">⚠️</div>
<h4 class="modal-title mb-2 fw-bold" id="modalAbandonarSalaLabel">¿Abandonar la sala?</h4> <h4 class="modal-title mb-2 fw-bold" id="modalAbandonarSalaLabel">¿Abandonar la sala?</h4>
<p class="text-dark-50">Si sales de la sala, perderás tu lugar en el equipo.</p> <p class="text-dark-50" id="modalAbandonarSalaMensaje">Si sales de la sala, perderás tu lugar en el equipo.</p>
</div> </div>
<div class="modal-footer justify-content-center border-0 gap-2"> <div class="modal-footer justify-content-center border-0 gap-2">
<button type="button" class="btn btn-secondary px-4 py-2" data-bs-dismiss="modal"> <button type="button" class="btn btn-secondary px-4 py-2" data-bs-dismiss="modal">
+105
View File
@@ -12,6 +12,40 @@ class Categoria{
$this->nombreCategoria = $nombreCategoria; $this->nombreCategoria = $nombreCategoria;
} }
function crear(){
$conexion = conexionBD();
if(is_array($conexion)){
return $conexion;
}else{
try{
$consulta = $conexion->prepare("INSERT INTO categoria (nombreCategoria) VALUES (:nombreCategoria)");
$consulta->bindParam(':nombreCategoria', $this->nombreCategoria);
$consulta->execute();
$respuesta = [
'exito' => 'Categoría creada correctamente',
'exito_tecnico' => $this->nombreCategoria
];
return $respuesta;
}catch(PDOException $error){
$respuesta = [
'error' => 'Ocurrió un error al guardar la categoría.',
'error_tecnico' => $error->getMessage()
];
$conexion = null;
return $respuesta;
}
}
}
function listar(){ function listar(){
$conexion = conexionBD(); $conexion = conexionBD();
@@ -39,6 +73,77 @@ class Categoria{
} }
} }
} }
function buscar(){
$conexion = conexionBD();
if(is_array($conexion)){
return $conexion;
}else{
try{
$consulta = $conexion->prepare("SELECT * FROM categoria WHERE idCategoria = :idCategoria");
$consulta->bindParam(':idCategoria', $this->idCategoria);
$consulta->execute();
$resultado = $consulta->fetch(PDO::FETCH_ASSOC);
return $resultado;
}catch(PDOException $error){
$respuesta = [
'error' => 'Ocurrió un error al buscar la categoría.',
'error_tecnico' => $error->getMessage()
];
$conexion = null;
return $respuesta;
}
}
}
function borrar(){
$conexion = conexionBD();
if(is_array($conexion)){
return $conexion;
}else{
try{
$consulta = $conexion->prepare("DELETE FROM categoria WHERE idCategoria = :idCategoria");
$consulta->bindParam(':idCategoria', $this->idCategoria);
$consulta->execute();
if($consulta->rowCount() > 0){
$respuesta = [
'exito' => 'Se eliminó correctamente la categoría',
'exito_tecnico' => $this->idCategoria
];
}else{
$respuesta = [
'error' => 'No existe ninguna categoría con ese ID.',
'error_tecnico' => $this->idCategoria
];
}
return $respuesta;
}catch(PDOException $error){
$respuesta = [
'error' => 'Ocurrió un error al eliminar la categoría.',
'error_tecnico' => $error->getMessage()
];
$conexion = null;
return $respuesta;
}
}
}
} }
?> ?>
+127 -5
View File
@@ -60,6 +60,7 @@ class EquipoxUsuario
]; ];
} }
} }
public function obtenerSalaUsuario($idUsuario) public function obtenerSalaUsuario($idUsuario)
{ {
$conexion = conexionBD(); $conexion = conexionBD();
@@ -108,7 +109,6 @@ class EquipoxUsuario
'idEquipo' => $resultado['idEquipo'], 'idEquipo' => $resultado['idEquipo'],
'idPartida' => $resultado['idPartida'] 'idPartida' => $resultado['idPartida']
]; ];
} catch (PDOException $error) { } catch (PDOException $error) {
return [ return [
@@ -127,21 +127,143 @@ public static function salirDeSala($idUsuario)
} }
try { try {
$sql = "DELETE FROM equipoxusuario WHERE idUsuario = :idUsuario";
// 1. Averiguamos el equipo y la partida del usuario
$sql = "SELECT exu.idEquipo, exp.idPartida, p.idUsuario AS idHost FROM equipoxusuario exu INNER JOIN equipoxpartida exp ON exu.idEquipo = exp.idEquipo INNER JOIN partida p ON exp.idPartida = p.idPartida WHERE exu.idUsuario = :idUsuario LIMIT 1";
$consulta = $conexion->prepare($sql); $consulta = $conexion->prepare($sql);
$consulta->bindParam(':idUsuario',$idUsuario, PDO::PARAM_INT); $consulta->bindParam(':idUsuario',$idUsuario, PDO::PARAM_INT);
$consulta->execute(); $consulta->execute();
$datosSala = $consulta->fetch(PDO::FETCH_ASSOC);
// El usuario no pertenece a ninguna sala
if (!$datosSala) {
return [
'error' => 'No perteneces a ninguna sala.'
];
}
$idEquipo = (int)$datosSala['idEquipo'];
$idPartida = (int)$datosSala['idPartida'];
$idHost = (int)$datosSala['idHost'];
// Comenzamos una transacción
$conexion->beginTransaction();
/*
* 2. Comprobamos si quien abandona es el HOST
*/
if ($idUsuario === $idHost) {
/*
* Obtenemos todos los equipos pertenecientes
* a esta partida.
*/
$sqlEquipos = "SELECT idEquipo FROM equipoxpartida WHERE idPartida = :idPartida";
$consultaEquipos = $conexion->prepare($sqlEquipos);
$consultaEquipos->bindParam(':idPartida', $idPartida, PDO::PARAM_INT);
$consultaEquipos->execute();
$equipos = $consultaEquipos->fetchAll(PDO::FETCH_COLUMN);
/*
* 3. Eliminamos los jugadores de esos equipos
* de la tabla equipoxusuario.
*/
if (!empty($equipos)) {
$placeholders = implode(',', array_fill(0, count($equipos), '?'));
$sqlUsuarios = "DELETE FROM equipoxusuario WHERE idEquipo IN ($placeholders)";
$consultaUsuarios = $conexion->prepare($sqlUsuarios);
$consultaUsuarios->execute($equipos);
}
/*
* 4. Eliminamos las relaciones
* entre equipos y partida.
*/
$sqlEquipoxPartida = "DELETE FROM equipoxpartida WHERE idPartida = :idPartida";
$consultaEquipoxPartida = $conexion->prepare($sqlEquipoxPartida);
$consultaEquipoxPartida->bindParam(':idPartida', $idPartida,PDO::PARAM_INT);
$consultaEquipoxPartida->execute();
/*
* 5. Eliminamos los equipos.
*
* Los equipos ya no tienen relaciones con
* usuarios ni con la partida.
*/
if (!empty($equipos)) {
$placeholders = implode(',', array_fill(0, count($equipos), '?'));
$sqlEliminarEquipos = "DELETE FROM equipo WHERE idEquipo IN ($placeholders)";
$consultaEliminarEquipos = $conexion->prepare($sqlEliminarEquipos);
$consultaEliminarEquipos->execute($equipos);
}
/*
* 6. Eliminamos las relaciones
* entre preguntas y partida.
*/
$sqlPartidaxPregunta = "DELETE FROM partidaxpregunta WHERE idPartida = :idPartida";
$consultaPartidaxPregunta = $conexion->prepare($sqlPartidaxPregunta);
$consultaPartidaxPregunta->bindParam(':idPartida', $idPartida, PDO::PARAM_INT);
$consultaPartidaxPregunta->execute();
/*
* 7. Finalmente eliminamos la partida.
*/
$sqlPartida = "DELETE FROM partida WHERE idPartida = :idPartida";
$consultaPartida = $conexion->prepare($sqlPartida);
$consultaPartida->bindParam(':idPartida', $idPartida, PDO::PARAM_INT);
$consultaPartida->execute();
// Todo ha ido correctamente
$conexion->commit();
return [ return [
'exito' => true, 'exito' => true,
'mensaje' => 'Usuario eliminado de la sala correctamente.' 'salaEliminada' => true,
'mensaje' => 'La sala ha sido eliminada correctamente.'
];
}
/*
* 7. Si NO es el host, simplemente abandona
* su relación con el equipo.
*/
$sqlSalir = "DELETE FROM equipoxusuario WHERE idUsuario = :idUsuario";
$consultaSalir = $conexion->prepare($sqlSalir);
$consultaSalir->bindParam(':idUsuario', $idUsuario, PDO::PARAM_INT);
$consultaSalir->execute();
$conexion->commit();
return [
'exito' => true,
'salaEliminada' => false,
'mensaje' => 'Has abandonado la sala correctamente.'
]; ];
} catch (PDOException $error) { } catch (PDOException $error) {
// Si había una transacción activa, la deshacemos
if ($conexion->inTransaction()) {
$conexion->rollBack();
}
return [ return [
'error' => 'Ocurrió un error al salir de la sala.', 'error' => 'Ocurrió un error al abandonar la sala.',
'error_tecnico' => $error->getMessage() 'error_tecnico' => $error->getMessage()
]; ];
} }
} }
} }
+348
View File
@@ -0,0 +1,348 @@
<?php
require_once __DIR__ . '/../includes/conexionBD.php';
require_once __DIR__ . '/ClaseRespuestaAlmacenada.php';
/**
* Lógica de la dinámica de juego (la usa juego.php a través de ControladorJuego.php):
* - deja preparadas las 10 preguntas de la partida
* - se las devuelve al jugador con sus respuestas SIN decir cuál es la correcta
* - guarda la respuesta de cada jugador y le dice si ha acertado
*
* Todos los métodos devuelven un array con la clave 'error' si algo falla,
* igual que el resto de clases del proyecto.
*/
class Juego
{
const NUM_PREGUNTAS = 10;
/**
* Comprueba que el usuario pertenece a alguno de los equipos de la partida.
* Devuelve true/false (o el array de error si falla la BD).
*/
public static function usuarioEnPartida($idPartida, $idUsuario)
{
$conexion = conexionBD();
if (is_array($conexion)) {
return $conexion;
}
try {
$consulta = $conexion->prepare("
SELECT COUNT(*) AS total
FROM equipoxpartida exp
INNER JOIN equipoxusuario exu
ON exu.idEquipo = exp.idEquipo
WHERE exp.idPartida = :idPartida
AND exu.idUsuario = :idUsuario
");
$consulta->execute([
':idPartida' => $idPartida,
':idUsuario' => $idUsuario
]);
return (int)$consulta->fetchColumn() > 0;
} catch (PDOException $error) {
return [
'error' => 'Ocurrió un error al comprobar si perteneces a la partida.',
'error_tecnico' => $error->getMessage()
];
}
}
/**
* Si la partida todavía no tiene preguntas, le asigna 10 al azar.
* Se hace dentro de una transacción con la fila de la partida bloqueada
* para que, si varios jugadores entran a la vez, las preguntas se
* asignen una sola vez.
* Devuelve null si todo va bien, o un array de error.
*/
private static function asegurarPreguntas($conexion, $idPartida)
{
try {
$conexion->beginTransaction();
$bloqueo = $conexion->prepare("
SELECT idPartida FROM partida
WHERE idPartida = :idPartida
FOR UPDATE
");
$bloqueo->execute([':idPartida' => $idPartida]);
if (!$bloqueo->fetch()) {
$conexion->rollBack();
return ['error' => 'La partida no existe.'];
}
$cuenta = $conexion->prepare("
SELECT COUNT(*) FROM partidaxpregunta
WHERE idPartida = :idPartida
");
$cuenta->execute([':idPartida' => $idPartida]);
if ((int)$cuenta->fetchColumn() === 0) {
// Solo preguntas que tengan alguna respuesta marcada como correcta
$elegidas = $conexion->query("
SELECT p.idPregunta
FROM pregunta p
WHERE EXISTS (
SELECT 1 FROM respuesta r
WHERE r.idPregunta = p.idPregunta
AND r.correcta = 1
)
ORDER BY RAND()
LIMIT " . self::NUM_PREGUNTAS
)->fetchAll(PDO::FETCH_COLUMN);
$insertar = $conexion->prepare("
INSERT INTO partidaxpregunta (idPartida, idPregunta)
VALUES (:idPartida, :idPregunta)
");
foreach ($elegidas as $idPregunta) {
$insertar->execute([
':idPartida' => $idPartida,
':idPregunta' => $idPregunta
]);
}
}
$conexion->commit();
return null;
} catch (PDOException $error) {
if ($conexion->inTransaction()) {
$conexion->rollBack();
}
return [
'error' => 'Ocurrió un error al preparar las preguntas de la partida.',
'error_tecnico' => $error->getMessage()
];
}
}
/**
* Devuelve las preguntas de la partida (máximo 10) con sus respuestas
* mezcladas. NO incluye el campo 'correcta' para que no se pueda hacer
* trampa mirando la respuesta del servidor.
* Cada pregunta indica si este usuario ya la ha respondido, para poder
* retomar la partida si recarga la página.
*/
public static function cargarPreguntas($idPartida, $idUsuario)
{
$conexion = conexionBD();
if (is_array($conexion)) {
return $conexion;
}
$error = self::asegurarPreguntas($conexion, $idPartida);
if ($error !== null) {
return $error;
}
try {
// 1. Las preguntas, siempre en el mismo orden para todos los jugadores
$consulta = $conexion->prepare("
SELECT
pxp.idPartidaxPregunta,
p.idPregunta,
p.nombrePregunta
FROM partidaxpregunta pxp
INNER JOIN pregunta p
ON p.idPregunta = pxp.idPregunta
WHERE pxp.idPartida = :idPartida
ORDER BY pxp.idPartidaxPregunta
LIMIT " . self::NUM_PREGUNTAS
);
$consulta->execute([':idPartida' => $idPartida]);
$preguntas = $consulta->fetchAll(PDO::FETCH_ASSOC);
// 2. Las respuestas de todas esas preguntas, agrupadas por pregunta
$consulta = $conexion->prepare("
SELECT DISTINCT
r.idRespuesta,
r.idPregunta,
r.nombreRespuesta
FROM respuesta r
INNER JOIN partidaxpregunta pxp
ON pxp.idPregunta = r.idPregunta
WHERE pxp.idPartida = :idPartida
");
$consulta->execute([':idPartida' => $idPartida]);
$respuestasPorPregunta = [];
foreach ($consulta->fetchAll(PDO::FETCH_ASSOC) as $fila) {
$respuestasPorPregunta[(int)$fila['idPregunta']][] = [
'idRespuesta' => (int)$fila['idRespuesta'],
'nombreRespuesta' => $fila['nombreRespuesta']
];
}
// 3. Qué preguntas ha respondido ya este usuario
$consulta = $conexion->prepare("
SELECT ra.idPartidaxPregunta
FROM respuestaalmacenada ra
INNER JOIN partidaxpregunta pxp
ON pxp.idPartidaxPregunta = ra.idPartidaxPregunta
WHERE pxp.idPartida = :idPartida
AND ra.idUsuario = :idUsuario
");
$consulta->execute([
':idPartida' => $idPartida,
':idUsuario' => $idUsuario
]);
$respondidas = array_flip($consulta->fetchAll(PDO::FETCH_COLUMN));
// 4. Montamos la respuesta final
$resultado = [];
foreach ($preguntas as $pregunta) {
$idPregunta = (int)$pregunta['idPregunta'];
$idPxp = (int)$pregunta['idPartidaxPregunta'];
$respuestas = $respuestasPorPregunta[$idPregunta] ?? [];
shuffle($respuestas);
$resultado[] = [
'idPartidaxPregunta' => $idPxp,
'idPregunta' => $idPregunta,
'nombrePregunta' => $pregunta['nombrePregunta'],
'respondida' => isset($respondidas[$idPxp]),
'respuestas' => $respuestas
];
}
return [
'total' => count($resultado),
'respondidas' => count($respondidas),
'preguntas' => $resultado
];
} catch (PDOException $error) {
return [
'error' => 'Ocurrió un error al cargar las preguntas.',
'error_tecnico' => $error->getMessage()
];
}
}
/**
* Guarda la respuesta que ha elegido el usuario en una pregunta y
* devuelve si ha acertado y cuál era la correcta (para el feedback).
* Comprueba en el servidor que:
* - la pregunta pertenece a la partida
* - la respuesta pertenece a esa pregunta
* - el usuario no ha respondido ya esa pregunta
*/
public static function registrarRespuesta($idPartida, $idUsuario, $idPartidaxPregunta, $idRespuesta)
{
$conexion = conexionBD();
if (is_array($conexion)) {
return $conexion;
}
try {
// 1. La pregunta tiene que ser de esta partida
$consulta = $conexion->prepare("
SELECT idPregunta
FROM partidaxpregunta
WHERE idPartidaxPregunta = :idPxp
AND idPartida = :idPartida
");
$consulta->execute([
':idPxp' => $idPartidaxPregunta,
':idPartida' => $idPartida
]);
$idPregunta = $consulta->fetchColumn();
if ($idPregunta === false) {
return ['error' => 'Esa pregunta no pertenece a esta partida.'];
}
// 2. La respuesta tiene que ser una de las de esa pregunta
$consulta = $conexion->prepare("
SELECT idRespuesta, correcta
FROM respuesta
WHERE idPregunta = :idPregunta
");
$consulta->execute([':idPregunta' => $idPregunta]);
$elegida = null;
$primeraCorrecta = null;
foreach ($consulta->fetchAll(PDO::FETCH_ASSOC) as $fila) {
if ((int)$fila['correcta'] === 1 && $primeraCorrecta === null) {
$primeraCorrecta = (int)$fila['idRespuesta'];
}
if ((int)$fila['idRespuesta'] === $idRespuesta) {
$elegida = $fila;
}
}
if ($elegida === null) {
return ['error' => 'Esa respuesta no pertenece a la pregunta.'];
}
// 3. Solo se puede responder una vez a cada pregunta
$consulta = $conexion->prepare("
SELECT COUNT(*)
FROM respuestaalmacenada
WHERE idPartidaxPregunta = :idPxp
AND idUsuario = :idUsuario
");
$consulta->execute([
':idPxp' => $idPartidaxPregunta,
':idUsuario' => $idUsuario
]);
if ((int)$consulta->fetchColumn() > 0) {
return ['error' => 'Ya has respondido a esta pregunta.'];
}
// 4. Guardamos la respuesta con la clase que ya existía
$almacenada = new RespuestaAlmacenada(
null,
$idPartidaxPregunta,
$idUsuario,
$idRespuesta
);
$guardado = $almacenada->guardarRespuesta();
if (isset($guardado['error'])) {
return $guardado;
}
// 5. Progreso del usuario en la partida
$consulta = $conexion->prepare("
SELECT COUNT(*)
FROM respuestaalmacenada ra
INNER JOIN partidaxpregunta pxp
ON pxp.idPartidaxPregunta = ra.idPartidaxPregunta
WHERE pxp.idPartida = :idPartida
AND ra.idUsuario = :idUsuario
");
$consulta->execute([
':idPartida' => $idPartida,
':idUsuario' => $idUsuario
]);
$respondidas = (int)$consulta->fetchColumn();
$consulta = $conexion->prepare("
SELECT COUNT(*) FROM partidaxpregunta
WHERE idPartida = :idPartida
");
$consulta->execute([':idPartida' => $idPartida]);
$total = min((int)$consulta->fetchColumn(), self::NUM_PREGUNTAS);
$acierto = (int)$elegida['correcta'] === 1;
return [
'exito' => 'Respuesta guardada.',
'acierto' => $acierto,
'idRespuestaCorrecta' => $acierto ? $idRespuesta : $primeraCorrecta,
'respondidas' => $respondidas,
'total' => $total,
'terminada' => $respondidas >= $total
];
} catch (PDOException $error) {
return [
'error' => 'Ocurrió un error al guardar la respuesta.',
'error_tecnico' => $error->getMessage()
];
}
}
}
+58 -7
View File
@@ -143,10 +143,63 @@ class Partida
} }
} }
/** public function iniciar()
* Devuelve las preguntas asignadas a la partida, junto con el {
* idPartidaxPregunta (necesario para guardar respuestas después). $conexion = conexionBD();
*/ if (is_array($conexion)) {
return $conexion;
}
try {
$consulta = $conexion->prepare("
UPDATE partida
SET fechaInicio = NOW()
WHERE idPartida = :idPartida
AND fechaInicio IS NULL
");
$consulta->bindParam(":idPartida", $this->idPartida);
$consulta->execute();
return [
"exito" => "La partida se inició correctamente.",
"exito_tecnico" => $this->idPartida
];
} catch (PDOException $error) {
return [
"error" => "No se pudo iniciar la partida.",
'error_tecnico' => $error->getMessage()
];
}
}
public function finalizar()
{
$conexion = conexionBD();
if (is_array($conexion)) {
return $conexion;
}
try {
$consulta = $conexion->prepare("
UPDATE partida
SET fechaFin = NOW()
WHERE idPartida = :idPartida
AND fechaInicio IS NOT NULL
AND fechaFin IS NULL
");
$consulta->bindParam(":idPartida", $this->idPartida);
$consulta->execute();
return [
"exito" => "La partida se finalizó correctamente.",
"exito_tecnico" => $this->idPartida
];
} catch (PDOException $error) {
return [
"error" => "No se pudo finalizar la partida.",
'error_tecnico' => $error->getMessage()
];
}
}
public function getPreguntas() public function getPreguntas()
{ {
$conexion = conexionBD(); $conexion = conexionBD();
@@ -175,9 +228,7 @@ class Partida
} }
} }
/**
* Asocia una pregunta ya existente a esta partida.
*/
public function asignarPregunta($idPregunta) public function asignarPregunta($idPregunta)
{ {
$conexion = conexionBD(); $conexion = conexionBD();
+51
View File
@@ -57,9 +57,60 @@ class Pregunta {
} }
} }
public function cargarRespuestas() {
if ($this->idPregunta === null) return;
$conexion = conexionBD();
if (is_array($conexion)) {
return $conexion;
}
try {
$sql = "SELECT idRespuesta, nombreRespuesta, correcta FROM respuesta WHERE idPregunta = :idPreg";
$stmt = $conexion->prepare($sql);
$stmt->execute([':idPreg' => $this->idPregunta]);
$this->respuestas = $stmt->fetchAll(PDO::FETCH_ASSOC);
} catch (PDOException $error) {
return [
'error' => 'Ocurrió un error al cargar las respuestas.',
'error_tecnico' => $error->getMessage()
];
}
}
public static function obtenerPorCategoria(int $idCategoria, int $limite = 10) {
$conexion = conexionBD();
if (is_array($conexion)) {
return $conexion;
}
try {
$sql = "SELECT * FROM pregunta WHERE idCategoria = :idCat ORDER BY RAND() LIMIT :limite";
$stmt = $conexion->prepare($sql);
$stmt->bindValue(':idCat', $idCategoria, PDO::PARAM_INT);
$stmt->bindValue(':limite', $limite, PDO::PARAM_INT);
$stmt->execute();
$preguntas = [];
while ($datos = $stmt->fetch(PDO::FETCH_ASSOC)) {
$preg = new Pregunta((int)$datos['idCategoria'], $datos['nombrePregunta'], (int)$datos['idPregunta']);
$preg->cargarRespuestas();
$preguntas[] = $preg;
}
return $preguntas;
} catch (PDOException $error) {
return [
'error' => 'Ocurrió un error al obtener las preguntas aleatorias.',
'error_tecnico' => $error->getMessage()
];
}
}
public static function getTodosIds()//devuelve solamente los id de todas las preguntas public static function getTodosIds()//devuelve solamente los id de todas las preguntas
{ {
+124
View File
@@ -40,6 +40,130 @@ class Respuesta{
} }
} }
}//fin método insertar }//fin método insertar
public function listar(){
$conexion = conexionBD();
if(is_array($conexion)){
return $conexion;
}else{
try{
$consulta = $conexion->prepare("SELECT * FROM respuesta");
$consulta->execute();
return $consulta->fetchAll(PDO::FETCH_ASSOC);
}catch(PDOException $error){
$respuesta = [
'error' => 'Ocurrió un error al obtener el listado.',
'error_tecnico' => $error->getMessage()
];
$conexion = null;
return $respuesta;
}
}
}//fin método listar
public function eliminar(){
$conexion = conexionBD();
if(is_array($conexion)){
return $conexion;
}else{
try{
$consulta = $conexion->prepare("DELETE FROM respuesta WHERE idRespuesta=:idRespuesta");
$consulta->bindParam(':idRespuesta', $this->idRespuesta);
$consulta->execute();
$respuesta = [
'exito' => 'La respuesta se eliminó correctamente.',
'exito_tecnico' => $this->idRespuesta
];
return $respuesta;
}catch(PDOException $error){
$respuesta = [
'error' => 'No se pudo eliminar la respuesta.',
'error_tecnico' => $error->getMessage()
];
$conexion = null;
return $respuesta;
}
}
}//fin método eliminar
public function editar(){
$conexion = conexionBD();
if(is_array($conexion)){
return $conexion;
}else{
try{
$consulta = $conexion->prepare("UPDATE respuesta SET idPregunta=:idPregunta, nombreRespuesta=:nombreRespuesta, correcta=:correcta WHERE idRespuesta=:idRespuesta");
$consulta->bindParam(':idRespuesta', $this->idRespuesta);
$consulta->bindParam(':idPregunta', $this->idPregunta);
$consulta->bindParam(':nombreRespuesta', $this->nombreRespuesta);
$consulta->bindParam(':correcta', $this->correcta);
$consulta->execute();
$respuesta = [
'exito' => 'La respuesta se editó correctamente.',
'exito_tecnico' => $this->idRespuesta
];
return $respuesta;
}catch(PDOException $error){
$respuesta = [
'error' => 'Ocurrió un error al editar la respuesta.',
'error_tecnico' => $error->getMessage()
];
$conexion = null;
return $respuesta;
}
}
}//fin método editar
public function buscar(){
$conexion = conexionBD();
if(is_array($conexion)){
return $conexion;
}else{
try{
$consulta = $conexion->prepare("SELECT * FROM respuesta WHERE idRespuesta=:idRespuesta");
$consulta->bindParam(':idRespuesta', $this->idRespuesta);
$consulta->execute();
return $consulta->fetchAll(PDO::FETCH_ASSOC);
}catch(PDOException $error){
$respuesta = [
'error' => 'Ocurrió un error al buscar la respuesta.',
'error_tecnico' => $error->getMessage()
];
$conexion = null;
return $respuesta;
}
}
}//fin método buscar
public function listarPorPregunta(){//trae todas las respuestas posibles de una pregunta
$conexion = conexionBD();
if(is_array($conexion)){
return $conexion;
}else{
try{
$consulta = $conexion->prepare("SELECT * FROM respuesta WHERE idPregunta=:idPregunta");
$consulta->bindParam(':idPregunta', $this->idPregunta);
$consulta->execute();
$resultado = $consulta->fetchAll(PDO::FETCH_ASSOC);
if(!isset($resultado[0])){
$respuesta = [
'error' => 'La pregunta no tiene respuestas.'
];
return $respuesta;
}else{
return $resultado;
}
}catch(PDOException $error){
$respuesta = [
'error' => 'Ocurrió un error al obtener las respuestas de la pregunta.',
'error_tecnico' => $error->getMessage()
];
$conexion = null;
return $respuesta;
}
}
}//fin método listarPorPregunta
}//fin clase }//fin clase
?> ?>
+39
View File
@@ -6,6 +6,22 @@ require_once '../clases/ClaseCategoria.php';
switch ($_POST['orden']) { switch ($_POST['orden']) {
case 'crearCategoria':
$nombreCategoria = trim($_POST['nombreCategoria'] ?? '');
if ($nombreCategoria == '') {
echo json_encode(['error' => 'El nombre de la categoría es obligatorio']);
break;
}
$categoria = new Categoria(null, $nombreCategoria);
$resultado = $categoria->crear();
echo json_encode($resultado);
break;
case 'listarCategorias': case 'listarCategorias':
$categoria = new Categoria(); $categoria = new Categoria();
@@ -13,4 +29,27 @@ switch ($_POST['orden']) {
echo json_encode($resultado); echo json_encode($resultado);
break; break;
case 'buscarCategoria':
$idCategoria = intval($_POST['idCategoria'] ?? 0);
$categoria = new Categoria($idCategoria);
$resultado = $categoria->buscar();
echo json_encode($resultado);
break;
case 'borrarCategoria':
$idCategoria = intval($_POST['idCategoria'] ?? 0);
$categoria = new Categoria($idCategoria);
$resultado = $categoria->borrar();
echo json_encode($resultado);
break;
} }
+55
View File
@@ -0,0 +1,55 @@
<?php
session_start();
require_once '../clases/ClaseEquipo.php';
switch ($_POST['orden']) {
case 'crearEquipo':
$nombreEquipo = trim($_POST['nombreEquipo'] ?? '');
if ($nombreEquipo == '') {
echo json_encode(['error' => 'El nombre del equipo es obligatorio']);
break;
}
$equipo = new Equipo(null, $nombreEquipo);
$resultado = $equipo->crear();
echo json_encode($resultado);
break;
case 'listarEquipos':
$equipo = new Equipo();
$resultado = $equipo->listar();
echo json_encode($resultado);
break;
case 'buscarEquipo':
$idEquipo = intval($_POST['idEquipo'] ?? 0);
$equipo = new Equipo($idEquipo);
$resultado = $equipo->buscar();
echo json_encode($resultado);
break;
case 'borrarEquipo':
$idEquipo = intval($_POST['idEquipo'] ?? 0);
$equipo = new Equipo($idEquipo);
$resultado = $equipo->borrar();
echo json_encode($resultado);
break;
}
+96
View File
@@ -0,0 +1,96 @@
<?php
session_start();
require_once '../clases/ClaseJuego.php';
header('Content-Type: application/json; charset=utf-8');
// Hay que estar logueado y decir de qué partida se trata
if (!isset($_SESSION['idUsuario'])) {
echo json_encode(['error' => 'Debes iniciar sesión para jugar.']);
exit;
}
$idUsuario = (int)$_SESSION['idUsuario'];
$idPartida = intval($_POST['idPartida'] ?? 0);
if ($idPartida <= 0) {
echo json_encode(['error' => 'No se ha indicado la partida.']);
exit;
}
// Solo pueden jugar los usuarios que están en uno de los equipos de la partida
$perteneceAPartida = Juego::usuarioEnPartida($idPartida, $idUsuario);
if (is_array($perteneceAPartida)) {
echo json_encode($perteneceAPartida);
exit;
}
if (!$perteneceAPartida) {
echo json_encode(['error' => 'No formas parte de esta partida.']);
exit;
}
switch ($_POST['orden'] ?? '') {
// Devuelve las 10 preguntas con sus respuestas (sin marcar la correcta)
case 'cargarPreguntas':
echo json_encode(Juego::cargarPreguntas($idPartida, $idUsuario));
break;
// Guarda la respuesta del usuario y dice si ha acertado
case 'responder':
$idPartidaxPregunta = intval($_POST['idPartidaxPregunta'] ?? 0);
$idRespuesta = intval($_POST['idRespuesta'] ?? 0);
if ($idPartidaxPregunta <= 0 || $idRespuesta <= 0) {
echo json_encode(['error' => 'Faltan datos para registrar la respuesta.']);
break;
}
echo json_encode(
Juego::registrarRespuesta(
$idPartida,
$idUsuario,
$idPartidaxPregunta,
$idRespuesta
)
);
break;
// Recibe los aciertos del jugador al terminar sus 10 preguntas,
// para que resultados.php los pueda usar al montar la hoja de resultados
case 'finalizarPartida':
$aciertos = intval($_POST['aciertos'] ?? 0);
if (!isset($_SESSION['aciertosPorPartida'])) {
$_SESSION['aciertosPorPartida'] = [];
}
$_SESSION['aciertosPorPartida'][$idPartida] = $aciertos;
echo json_encode([
'exito' => 'Aciertos registrados correctamente.',
'idPartida' => $idPartida,
'aciertos' => $aciertos
]);
break;
default:
echo json_encode(['error' => 'La orden indicada no existe.']);
break;
}
+29
View File
@@ -121,4 +121,33 @@ switch ($_POST['orden']) {
]); ]);
break; break;
case 'obtenerPreguntasPorCategoria':
$idCategoria = intval($_POST['idCategoria'] ?? 0);
$limite = intval($_POST['limite'] ?? 10);
$preguntas = Pregunta::obtenerPorCategoria($idCategoria, $limite);
// Si ha devuelto un array de errores (formato ['error'=>...]) lo mandamos tal cual
if (isset($preguntas['error'])) {
echo json_encode($preguntas);
break;
}
// Convertimos cada objeto Pregunta en un array simple para poder mandarlo en JSON
$listado = [];
foreach ($preguntas as $p) {
$listado[] = [
'idPregunta' => $p->getIdPregunta(),
'idCategoria' => $p->getIdCategoria(),
'nombrePregunta' => $p->getNombrePregunta(),
'respuestas' => $p->getRespuestas()
];
}
echo json_encode($listado);
break;
} }
+71
View File
@@ -0,0 +1,71 @@
<?php
session_start();
require_once '../clases/ClaseRespuesta.php';
switch ($_POST['orden']) {
case 'crearRespuesta':
$idPregunta = intval($_POST['idPregunta'] ?? 0);
$nombreRespuesta = trim($_POST['nombreRespuesta'] ?? '');
$correcta = intval($_POST['correcta'] ?? 0);
if ($idPregunta <= 0 || $nombreRespuesta == '') {
echo json_encode(['error' => 'La pregunta y el texto de la respuesta son obligatorios']);
break;
}
$respuesta = new Respuesta(null, $idPregunta, $nombreRespuesta, $correcta);
$resultado = $respuesta->insertar();
echo json_encode($resultado);
break;
case 'listarRespuestas':
$respuesta = new Respuesta();
$resultado = $respuesta->listar();
echo json_encode($resultado);
break;
case 'listarRespuestasPorPregunta':
$idPregunta = intval($_POST['idPregunta'] ?? 0);
$respuesta = new Respuesta(null, $idPregunta);
$resultado = $respuesta->listarPorPregunta();
echo json_encode($resultado);
break;
case 'editarRespuesta':
$idRespuesta = intval($_POST['idRespuesta'] ?? 0);
$idPregunta = intval($_POST['idPregunta'] ?? 0);
$nombreRespuesta = trim($_POST['nombreRespuesta'] ?? '');
$correcta = intval($_POST['correcta'] ?? 0);
$respuesta = new Respuesta($idRespuesta, $idPregunta, $nombreRespuesta, $correcta);
$resultado = $respuesta->editar();
echo json_encode($resultado);
break;
case 'borrarRespuesta':
$idRespuesta = intval($_POST['idRespuesta'] ?? 0);
$respuesta = new Respuesta($idRespuesta);
$resultado = $respuesta->eliminar();
echo json_encode($resultado);
break;
}
+166
View File
@@ -0,0 +1,166 @@
<?php
session_start();
require_once $_SERVER['DOCUMENT_ROOT'] . '/triviagmatico/includes/conexionBD.php';
header('Content-Type: application/json; charset=utf-8');
if (!isset($_SESSION['idUsuario'])) {
echo json_encode(['error' => 'Sesión no iniciada']);
exit;
}
$idPartida = intval($_POST['idPartida'] ?? 0);
$orden = $_POST['orden'] ?? '';
if ($idPartida <= 0) {
echo json_encode(['error' => 'Partida no válida']);
exit;
}
$conexion = conexionBD();
if (is_array($conexion)) {
echo json_encode($conexion);
exit;
}
switch ($orden) {
case 'comprobarEstado':
try {
// Contar jugadores totales apuntados en los equipos de esta partida
$stmt = $conexion->prepare("
SELECT COUNT(exu.idUsuario) AS total_jugadores
FROM equipoxpartida exp
INNER JOIN equipoxusuario exu ON exp.idEquipo = exu.idEquipo
WHERE exp.idPartida = :idPartida
");
$stmt->execute([':idPartida' => $idPartida]);
$totalJugadores = (int)$stmt->fetchColumn();
$preguntasTotalesEsperadas = $totalJugadores * 10;
// Contar respuestas dadas en la partida
$stmtRespuestas = $conexion->prepare("
SELECT COUNT(*)
FROM respuestaalmacenada ra
INNER JOIN partidaxpregunta pxp ON ra.idPartidaxPregunta = pxp.idPartidaxPregunta
WHERE pxp.idPartida = :idPartida
");
$stmtRespuestas->execute([':idPartida' => $idPartida]);
$respuestasRespondidas = (int)$stmtRespuestas->fetchColumn();
$terminada = ($respuestasRespondidas >= $preguntasTotalesEsperadas && $preguntasTotalesEsperadas > 0);
// Cuando la partida finaliza, se actualizan los históricos
if ($terminada && !isset($_SESSION['partidas_procesadas'][$idPartida])) {
$conexion->beginTransaction();
// CÁLCULO DE VELOCIDAD PREGUNTA A PREGUNTA EN SESIÓN
if (!isset($_SESSION['primeros_en_acertar'][$idPartida])) {
$_SESSION['primeros_en_acertar'][$idPartida] = [];
}
// Vemos quién fue el primero
$stmtVelocidad = $conexion->prepare("
SELECT ra.idPartidaxPregunta, ra.idUsuario
FROM respuestaalmacenada ra
INNER JOIN respuesta r ON ra.idRespuesta = r.idRespuesta
INNER JOIN partidaxpregunta pxp ON ra.idPartidaxPregunta = pxp.idPartidaxPregunta
WHERE pxp.idPartida = :idPartida AND r.correcta = 1
ORDER BY ra.idRespuestaAlmacenada ASC
");
$stmtVelocidad->execute([':idPartida' => $idPartida]);
$todosLosAciertos = $stmtVelocidad->fetchAll(PDO::FETCH_ASSOC);
foreach ($todosLosAciertos as $acierto) {
$idPxp = $acierto['idPartidaxPregunta'];
$idU = $acierto['idUsuario'];
if (!isset($_SESSION['primeros_en_acertar'][$idPartida][$idPxp])) {
$_SESSION['primeros_en_acertar'][$idPartida][$idPxp] = $idU;
}
}
// Actualizamos la base de datos
// Buscamos los aciertos de cada jugador en la partida actual
$stmtAciertos = $conexion->prepare("
SELECT ra.idUsuario, exu.idEquipo, COUNT(*) as aciertos_partida
FROM respuestaalmacenada ra
INNER JOIN partidaxpregunta pxp ON ra.idPartidaxPregunta = pxp.idPartidaxPregunta
INNER JOIN respuesta r ON ra.idRespuesta = r.idRespuesta
INNER JOIN equipoxusuario exu ON ra.idUsuario = exu.idUsuario
WHERE pxp.idPartida = :idPartida AND r.correcta = 1
GROUP BY ra.idUsuario
");
$stmtAciertos->execute([':idPartida' => $idPartida]);
$jugadoresPartida = $stmtAciertos->fetchAll(PDO::FETCH_ASSOC);
$updateAciertosGlobales = $conexion->prepare("
UPDATE usuario
SET aciertos = aciertos + :nuevos_aciertos
WHERE idUsuario = :idUsuario
");
$puntosEquipos = [];
foreach ($jugadoresPartida as $jp) {
$idU = (int)$jp['idUsuario'];
$idE = (int)$jp['idEquipo'];
$nuevosAciertos = (int)$jp['aciertos_partida'];
// Sumamos los aciertos al perfil del usuario
$updateAciertos->execute([
':nuevos_aciertos' => $nuevosAciertos,
':idUsuario' => $idU
]);
if (!isset($puntosEquipos[$idE])) {
$puntosEquipos[$idE] = 0;
}
$puntosEquipos[$idE] += $nuevosAciertos;
}
// Determinamos qué equipo ha obtenido más aciertos en la partida
arsort($puntosEquipos);
$idEquipoGanador = key($puntosEquipos);
// Obtenemos todos los usuarios de la partida para asignar victorias/derrotas
$stmtMiembros = $conexion->prepare("
SELECT exu.idUsuario, exu.idEquipo
FROM equipoxpartida exp
INNER JOIN equipoxusuario exu ON exp.idEquipo = exu.idEquipo
WHERE exp.idPartida = :idPartida
");
$stmtMiembros->execute([':idPartida' => $idPartida]);
$todosLosMiembros = $stmtMiembros->fetchAll(PDO::FETCH_ASSOC);
$updateVictoria = $conexion->prepare("UPDATE usuario SET victorias = victorias + 1 WHERE idUsuario = :idUsuario");
$updateDerrota = $conexion->prepare("UPDATE usuario SET derrotas = derrotas + 1 WHERE idUsuario = :idUsuario");
foreach ($todosLosMiembros as $miembro) {
$idU = (int)$miembro['idUsuario'];
$idE = (int)$miembro['idEquipo'];
if ($idE === $idEquipoGanador) {
$updateVictoria->execute([':idUsuario' => $idU]);
} else {
$updateDerrota->execute([':idUsuario' => $idU]);
}
}
$conexion->commit();
$_SESSION['partidas_procesadas'][$idPartida] = true;
}
echo json_encode(['terminada' => $terminada]);
} catch (PDOException $e) {
if ($conexion->inTransaction()) {
$conexion->rollBack();
}
echo json_encode(['error' => $e->getMessage()]);
}
break;
default:
echo json_encode(['error' => 'Acción no permitida']);
break;
}
+62
View File
@@ -117,3 +117,65 @@
font-size: 18px; font-size: 18px;
} }
} }
/* ===== Lógica de juego (js/juego.js) ===== */
.game-header {
display: flex;
justify-content: space-between;
align-items: center;
font-weight: 600;
color: #333;
margin-bottom: 8px;
}
.progress-track {
height: 8px;
background-color: #ddd;
border-radius: 4px;
overflow: hidden;
margin-bottom: 16px;
}
.progress-fill {
height: 100%;
width: 0;
background-color: #1368ce;
transition: width 0.3s ease;
}
.feedback {
min-height: 1.5em;
margin-top: 12px;
font-size: 18px;
font-weight: 600;
}
.feedback.ok { color: #26890c; }
.feedback.ko { color: #e21b3c; }
/* Mientras se envía la respuesta o se ve el feedback no se puede volver a pulsar */
.answers-grid.bloqueado .btn-answer {
pointer-events: none;
}
.btn-answer.correcta {
box-shadow: 0 0 0 6px #fff, 0 0 0 10px #26890c;
}
.btn-answer.incorrecta,
.btn-answer.atenuada {
opacity: 0.45;
}
.btn-answer:focus-visible {
outline: 4px solid #333;
outline-offset: 3px;
}
@media (prefers-reduced-motion: reduce) {
.progress-fill,
.btn-answer {
transition: none;
}
}
+179
View File
@@ -0,0 +1,179 @@
/* ==========================================================================
ESTILOS GENERALES Y FONDO
========================================================================== */
* {
margin: 0;
padding: 0;
box-shadow: none;
box-sizing: border-box;
}
body {
font-family: 'Segoe UI', Roboto, Helvetica, Arial, sans-serif;
background-color: #f0f4f8;
color: #2c3e50;
display: flex;
justify-content: center;
align-items: center;
min-height: 100vh;
padding: 20px;
}
/* Contenedor principal estilo tarjeta */
.contenedor {
width: 100%;
max-width: 900px;
background: #ffffff;
padding: 40px;
border-radius: 16px;
box-shadow: 0 10px 30px rgba(0, 0, 0, 0.08);
text-align: center;
animation: fadeIn 0.5s ease-in-out;
}
h1 {
font-size: 2.5rem;
color: #1a365d;
margin-bottom: 30px;
}
h2 {
font-size: 1.6rem;
color: #2b6cb0;
margin-top: 30px;
margin-bottom: 15px;
text-align: left;
border-bottom: 2px solid #e2e8f0;
padding-bottom: 8px;
}
p {
font-size: 1.1rem;
color: #718096;
margin-bottom: 30px;
}
/* ==========================================================================
PANTALLA DE ESPERA Y ANIMACIÓN DE CARGA (LOADER)
========================================================================== */
#pantalla-espera {
padding: 60px 20px;
}
.loader {
width: 60px;
height: 60px;
border: 6px solid #e2e8f0;
border-top: 6px solid #3182ce;
border-radius: 50%;
margin: 0 auto;
animation: spin 1.2s linear infinite;
}
@keyframes spin {
0% { transform: rotate(0deg); }
100% { transform: rotate(360deg); }
}
/* ==========================================================================
TABLAS DE CLASIFICACIÓN (RANKING)
========================================================================== */
.tabla-ranking {
width: 100%;
border-collapse: collapse;
margin-bottom: 20px;
font-size: 1.05rem;
}
.tabla-ranking th,
.tabla-ranking td {
padding: 16px 20px;
text-align: left;
}
.tabla-ranking th {
background-color: #2d3748;
color: #ffffff;
font-weight: 600;
text-transform: uppercase;
font-size: 0.9rem;
letter-spacing: 0.5px;
}
/* Alinear los puntos y aciertos a la derecha para mejor lectura numérica */
.tabla-ranking th:last-child,
.tabla-ranking td:last-child {
text-align: right;
}
.tabla-ranking tr {
border-bottom: 1px solid #e2e8f0;
transition: background-color 0.2s;
}
.tabla-ranking tbody tr:hover {
background-color: #f7fafc;
}
/* ==========================================================================
RESALTADO DE GANADORES (1º PUESTO)
========================================================================== */
/* Fila del Equipo Ganador y Jugador Ganador */
.tabla-ranking tr.top-team,
.tabla-ranking tr.top-player {
background-color: #fefcbf !important; /* Amarillo suave */
font-weight: bold;
}
/* Cambiar el color del texto del puesto #1 a dorado/corona */
.tabla-ranking tr.top-team td:first-child,
.tabla-ranking tr.top-player td:first-child {
color: #b7791f;
font-size: 1.2rem;
}
.tabla-ranking td strong {
color: #2f855a; /* Verde para destacar los aciertos conseguidos */
font-size: 1.1rem;
}
/* ==========================================================================
EFECTOS VISUALES SUAVES
========================================================================== */
@keyframes fadeIn {
from { opacity: 0; transform: translateY(10px); }
to { opacity: 1; transform: translateY(0); }
}
/* ==========================================================================
ESTILOS DEL BOTÓN DE RETORNO A INICIO
========================================================================== */
.contenedor-boton {
margin-top: 40px;
padding-top: 20px;
text-align: center;
}
.btn-inicio {
display: inline-block;
background-color: #3182ce; /* Azul corporativo */
color: #ffffff;
padding: 14px 28px;
font-size: 1.1rem;
font-weight: 600;
text-decoration: none;
border-radius: 8px;
box-shadow: 0 4px 6px rgba(49, 130, 206, 0.2);
transition: background-color 0.2s, transform 0.2s, box-shadow 0.2s;
}
.btn-inicio:hover {
background-color: #2b6cb0; /* Azul ligeramente más oscuro al pasar el cursor */
box-shadow: 0 6px 12px rgba(43, 108, 176, 0.3);
transform: translateY(-2px); /* Pequeño salto hacia arriba */
}
.btn-inicio:active {
transform: translateY(0); /* Vuelve a su posición normal al pulsar */
box-shadow: 0 2px 4px rgba(43, 108, 176, 0.2);
}
+19 -22
View File
@@ -1,29 +1,26 @@
<?php <?php
//CARGA DE ARCHIVOS .ENV CON PHPDOTENV
require('c:/laragon/vendor/autoload.php');//Se carga el módulo instalado con composer
$dotenv = Dotenv\Dotenv::createImmutable('c:/laragon/', 'triviagmatico.env');//leemos el archivo. El primer parámetro es la ruta para llegar y el segundo el nombre del archivo .env
$dotenv->load();
function conexionBD (){ function conexionBD (){
// Configuración para Laragon
$servidor = "localhost";
$bd = "triviagmatico"; // Asegúrate de que este sea el nombre de tu BD en HeidiSQL / phpMyAdmin
$usuario = "root";
$pass = ""; // Laragon deja la contraseña de root vacía por defecto
$puerto = "3306";
try{ try{
$dsn = "mysql:host=$servidor;port=$puerto;dbname=$bd;charset=utf8mb4"; $servidor = 'mysql:host='.$_ENV['bDatosServer'].';dbname='.$_ENV['bDatos'];
$user=$_ENV['bDatosUser'];
$opciones = [ $password=$_ENV['bDatosPass'];
$options=array(
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION, PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC, PDO::MYSQL_ATTR_INIT_COMMAND => "SET NAMES utf8"
PDO::ATTR_EMULATE_PREPARES => false, );
]; //Creamos la conexión PDO a la base de datos
$pdo = new PDO($servidor, $user, $password, $options);
return new PDO($dsn, $usuario, $pass, $opciones);
} catch (PDOException $error) {
return [
'error' => 'No se pudo conectar a la base de datos.',
'error_tecnico' => $error->getMessage()
];
} }
catch(PDOException $e){
//$resultado = ['error' => $e->getMessage()];
$resultado = ['error' => 'Error Temporal: No se puede conectar con la base de datos.'];
return $resultado;
}
return $pdo;
} }
?>
+28 -4
View File
@@ -130,7 +130,7 @@ function mostrarSala(partida, equipos, jugadores) {
esHost ? "jugador-host" : "" esHost ? "jugador-host" : ""
}"> }">
<div> <div>
<span class="avatar">${esHost ? "👑" : "👤"}</span> <span class="avatar">${esHost ? '<i class="bi bi-award-fill"></i>' : '<i class="bi bi-person-fill"></i>'}</span>
${escapeHTML(jugador.nombreUsuario)} ${escapeHTML(jugador.nombreUsuario)}
</div> </div>
@@ -174,14 +174,15 @@ function mostrarSala(partida, equipos, jugadores) {
let accionesSalaHTML = ""; let accionesSalaHTML = "";
if (usuarioYaEstaEnEquipo) { if (usuarioYaEstaEnEquipo) {
const esHost = partida.idUsuario == idUsuarioSesion;
let botonIniciarHTML = ""; let botonIniciarHTML = "";
// Solo el host puede iniciar la partida // Solo el host puede iniciar la partida
if (partida.idUsuario == idUsuarioSesion) { if (esHost) {
botonIniciarHTML = ` botonIniciarHTML = `
<a href="juego.php?idPartida=${partida.idPartida}" <a href="juego.php?idPartida=${partida.idPartida}"
class="btn btn-success btn-lg px-4"> class="btn btn-success btn-lg px-4">
🎮 ¡Iniciar Partida! <i class="bi bi-controller"></i> ¡Iniciar Partida!
</a> </a>
`; `;
} }
@@ -191,7 +192,8 @@ function mostrarSala(partida, equipos, jugadores) {
<button type="button" <button type="button"
class="btn btn-outline-danger btn-abandonar-sala" class="btn btn-outline-danger btn-abandonar-sala"
data-id-partida="${partida.idPartida}"> data-id-partida="${partida.idPartida}"
data-es-host="${esHost}">
Abandonar sala Abandonar sala
</button> </button>
@@ -263,12 +265,34 @@ let botonAbandonarReferencia = null;
// A. Al hacer clic en el botón "Abandonar sala" de la tarjeta, abrimos el modal // A. Al hacer clic en el botón "Abandonar sala" de la tarjeta, abrimos el modal
$(document).on("click", ".btn-abandonar-sala", function () { $(document).on("click", ".btn-abandonar-sala", function () {
botonAbandonarReferencia = $(this); botonAbandonarReferencia = $(this);
const esHost = $(this).data("es-host") === true || $(this).data("es-host") === "true";
const titulo = $("#modalAbandonarSalaLabel");
const mensaje = $("#modalAbandonarSalaMensaje");
const botonConfirmar = $("#btnConfirmarAbandonar");
if (esHost) {
titulo.text("¿Abandonar la sala?");
mensaje.text(
"Si abandonas la sala como anfitrión, la partida terminará para todos los jugadores.",
);
botonConfirmar.text("Sí, abandonar");
} else {
titulo.text("¿Abandonar la sala?");
mensaje.text(
"Si sales de la sala, perderás tu lugar en el equipo.",
);
botonConfirmar.text("Sí, salir");
}
const modal = new bootstrap.Modal( const modal = new bootstrap.Modal(
document.getElementById("modalAbandonarSala"), document.getElementById("modalAbandonarSala"),
); );
modal.show(); modal.show();
}); });
// B. Al hacer clic en "Sí, salir" dentro del modal Kahoot // B. Al hacer clic en "Sí, salir" dentro del modal Kahoot
$(document).on("click", "#btnConfirmarAbandonar", function () { $(document).on("click", "#btnConfirmarAbandonar", function () {
const modalEl = document.getElementById("modalAbandonarSala"); const modalEl = document.getElementById("modalAbandonarSala");
+219
View File
@@ -0,0 +1,219 @@
// Lógica de juego.php
// Carga las preguntas de la partida, las muestra de una en una,
// guarda cada respuesta en el servidor y al terminar lleva a resultados.php
const URL_CONTROLADOR = "controladores/ControladorJuego.php";
const PAUSA_FEEDBACK = 1800; // ms que se ve si has acertado o fallado
// Colores y formas de los botones (estilo Kahoot, definidos en juego.css)
const ESTILOS_BOTON = [
{ color: "red", forma: "triangle" },
{ color: "blue", forma: "diamond" },
{ color: "yellow", forma: "circle" },
{ color: "green", forma: "square" },
];
let idPartida = 0;
let preguntas = [];
let indice = 0; // pregunta que se está mostrando
let bloqueado = false; // true mientras se envía una respuesta o se ve el feedback
let numeroAciertos = 0; // número de aciertos del jugador
$(document).ready(function () {
idPartida = parseInt($("body").data("id-partida"), 10);
// Un solo listener para todos los botones de respuesta
$("#respuestas").on("click", ".btn-answer", function () {
responder($(this));
});
cargarPreguntas();
});
// 1. Pedir las preguntas al servidor
function cargarPreguntas() {
$.ajax({
url: URL_CONTROLADOR,
type: "POST",
dataType: "json",
data: { orden: "cargarPreguntas", idPartida: idPartida },
success: function (datos) {
if (datos.error) {
mostrarError(datos.error);
return;
}
preguntas = datos.preguntas;
if (preguntas.length === 0) {
mostrarError("Esta partida no tiene preguntas.");
return;
}
// Retomamos por la primera pregunta sin responder (por si recarga la página)
indice = preguntas.findIndex(function (p) {
return !p.respondida;
});
if (indice === -1) {
irAResultados();
return;
}
mostrarPregunta();
},
error: function () {
mostrarError("No se pudo conectar con el servidor.");
},
});
}
// 2. Pintar la pregunta actual y sus respuestas
function mostrarPregunta() {
const pregunta = preguntas[indice];
bloqueado = false;
$("#textoPregunta").text(pregunta.nombrePregunta);
$("#feedback").text("").removeClass("ok ko");
$("#contadorPregunta").text(
"Pregunta " + (indice + 1) + " de " + preguntas.length,
);
$("#barraProgreso").css("width", (indice / preguntas.length) * 100 + "%");
const $respuestas = $("#respuestas").empty();
pregunta.respuestas.forEach(function (respuesta, i) {
const estilo = ESTILOS_BOTON[i % ESTILOS_BOTON.length];
// .text() y createTextNode evitan que el texto de la BD se interprete como HTML
const $boton = $("<button>")
.attr("type", "button")
.addClass("btn-answer " + estilo.color)
.attr("data-id-respuesta", respuesta.idRespuesta)
.append($("<span>").addClass("shape " + estilo.forma))
.append(document.createTextNode(respuesta.nombreRespuesta));
$respuestas.append($boton);
});
}
// 3. El usuario pulsa una respuesta: se guarda y se enseña si ha acertado
function responder($botonPulsado) {
if (bloqueado) {
return;
}
bloqueado = true;
const pregunta = preguntas[indice];
const idRespuesta = parseInt($botonPulsado.attr("data-id-respuesta"), 10);
$("#respuestas").addClass("bloqueado");
$.ajax({
url: URL_CONTROLADOR,
type: "POST",
dataType: "json",
data: {
orden: "responder",
idPartida: idPartida,
idPartidaxPregunta: pregunta.idPartidaxPregunta,
idRespuesta: idRespuesta,
},
success: function (datos) {
if (datos.error) {
// No se guardó: dejamos que vuelva a intentarlo
$("#feedback").text(datos.error).removeClass("ok").addClass("ko");
$("#respuestas").removeClass("bloqueado");
bloqueado = false;
return;
}
pregunta.respondida = true;
mostrarFeedback($botonPulsado, datos);
setTimeout(function () {
if (datos.terminada || indice >= preguntas.length - 1) {
terminar();
} else {
indice++;
$("#respuestas").removeClass("bloqueado");
mostrarPregunta();
}
}, PAUSA_FEEDBACK);
},
error: function () {
$("#feedback")
.text("No se pudo guardar la respuesta. Inténtalo de nuevo.")
.removeClass("ok")
.addClass("ko");
$("#respuestas").removeClass("bloqueado");
bloqueado = false;
},
});
}
// Marca la respuesta correcta en verde y atenúa el resto
function mostrarFeedback($botonPulsado, datos) {
$("#respuestas .btn-answer").each(function () {
const $boton = $(this);
const esLaCorrecta =
parseInt($boton.attr("data-id-respuesta"), 10) ===
datos.idRespuestaCorrecta;
if (esLaCorrecta) {
$boton.addClass("correcta");
} else {
$boton.addClass("atenuada");
}
});
if (datos.acierto) {
$("#feedback").text("¡Correcto!").removeClass("ko").addClass("ok");
numeroAciertos++;
} else {
$botonPulsado.addClass("incorrecta");
$("#feedback")
.text("Respuesta incorrecta. La correcta está marcada.")
.removeClass("ok")
.addClass("ko");
}
}
// 4. Última pregunta respondida: se avisa al backend con los aciertos y luego se va a resultados
function terminar() {
$("#barraProgreso").css("width", "100%");
$("#respuestas").empty();
$("#contadorPregunta").text("Partida terminada");
$("#textoPregunta").text("¡Has terminado! Cargando resultados…");
$("#feedback").text("").removeClass("ok ko");
setTimeout(function () {
$.ajax({
url: URL_CONTROLADOR,
type: "POST",
dataType: "json",
data: {
orden: "finalizarPartida",
idPartida: idPartida,
aciertos: numeroAciertos,
},
complete: function () {
// Se va a resultados tanto si el AJAX ha ido bien como si ha fallado
irAResultados();
},
});
}, 1200);
}
function irAResultados() {
window.location.href = "resultados.php?idPartida=" + idPartida;
}
function mostrarError(mensaje) {
$("#respuestas").empty();
$("#textoPregunta").text(mensaje);
$("#feedback").html(
'<a href="buscadorSala.php">Volver al buscador de salas</a>',
);
}
+210
View File
@@ -0,0 +1,210 @@
// ============================================
// DATOS DE PRUEBA
// ============================================
const salas = [
{
id: 1,
nombre: "Sala Principal",
equipos: [
{
id: 1,
nombre: "Equipo 1",
jugadores: [
"Carlos",
"María"
]
},
{
id: 2,
nombre: "Equipo 2",
jugadores: [
"Jorge"
]
}
]
},
{
id: 2,
nombre: "Los Trivialeros",
equipos: [
{
id: 3,
nombre: "Equipo Rojo",
jugadores: [
"Ana",
"Pedro",
"Lucía"
]
},
{
id: 4,
nombre: "Equipo Azul",
jugadores: [
"David",
"Laura"
]
}
]
},
{
id: 3,
nombre: "Partida rápida",
equipos: [
{
id: 5,
nombre: "Equipo 1",
jugadores: [
"Miguel"
]
},
{
id: 6,
nombre: "Equipo 2",
jugadores: []
}
]
},
{
id: 4,
nombre: "Noche de Trivial",
equipos: [
{
id: 7,
nombre: "Los quesitos",
jugadores: [
"Pablo",
"Sara"
]
},
{
id: 8,
nombre: "Los cerebritos",
jugadores: [
"Mario",
"Elena",
"Álvaro"
]
}
]
}
];
// ============================================
// MOSTRAR SALAS
// ============================================
function mostrarSalas(salas) {
const contenedor = document.getElementById("contenedorSalas");
contenedor.innerHTML = "";
salas.forEach(sala => {
const columna = document.createElement("div");
columna.className = "col-12 col-md-6 col-xl-5";
columna.innerHTML = `
<div class="sala">
<div class="sala-header">
${sala.nombre}
</div>
<div class="sala-body">
<div class="equipos">
${crearEquipo(sala, sala.equipos[0])}
${crearEquipo(sala, sala.equipos[1])}
</div>
</div>
</div>
`;
contenedor.appendChild(columna);
});
}
// ============================================
// CREAR EQUIPO
// ============================================
function crearEquipo(sala, equipo) {
let jugadoresHTML = "";
equipo.jugadores.forEach(jugador => {
jugadoresHTML += `
<div class="jugador">
<span class="avatar">👤</span>
${jugador}
</div>
`;
});
return `
<div class="equipo">
<h6>
${equipo.nombre}
</h6>
<div class="jugadores">
${jugadoresHTML}
</div>
<button
type="button"
class="btn btn-unirse"
onclick="unirseEquipo(${sala.id}, ${equipo.id})">
Unirse
</button>
</div>
`;
}
function unirseEquipo(idSala, idEquipo) {
console.log("Sala:", idSala);
console.log("Equipo:", idEquipo);
alert(
"Has pulsado unirse al equipo " +
idEquipo +
" de la sala " +
idSala
);
}
// ============================================
// CARGAR DATOS DE PRUEBA
// ============================================
mostrarSalas(salas);
+10
View File
@@ -231,6 +231,8 @@ $("#formCrearPregunta").on("submit", function (e) {
// Volvemos a activar el botón // Volvemos a activar el botón
boton.prop("disabled", false); boton.prop("disabled", false);
boton.text("Crear pregunta"); boton.text("Crear pregunta");
// Toast exito
mostrarToast()
} }
}, },
@@ -244,3 +246,11 @@ $("#formCrearPregunta").on("submit", function (e) {
}, },
}); });
}); });
function mostrarToast() {
const toastMensaje = document.getElementById("ToastCrearPregunta");
if (toastMensaje) {
const toastBootstrap = bootstrap.Toast.getOrCreateInstance(toastMensaje);
toastBootstrap.show();
}
}
+38
View File
@@ -0,0 +1,38 @@
$(document).ready(function () {
const idPartida = $("body").data("id-partida");
comprobarEstadoPartida(idPartida);
const intervalo = setInterval(function () {
comprobarEstadoPartida(idPartida, intervalo);
}, 3000);
});
function comprobarEstadoPartida(idPartida, intervalo = null) {
$.ajax({
url: "controladores/ControladorResultados.php",
type: "POST",
dataType: "json",
data: {
orden: "comprobarEstado",
idPartida: idPartida
},
success: function (respuesta) {
if (respuesta.error) {
console.error("Error:", respuesta.error);
return;
}
if (respuesta.terminada) {
if (intervalo !== null) clearInterval(intervalo);
$("#pantalla-espera").fadeOut(400, function() {
location.reload();
});
}
},
error: function () {
console.log("Error de comunicación con el controlador.");
}
});
}
+33 -28
View File
@@ -1,3 +1,20 @@
<?php
session_start();
// Hay que estar logueado para jugar
if (!isset($_SESSION['idUsuario'])) {
header('Location: index.php');
exit;
}
// La partida llega por la URL: juego.php?idPartida=3
$idPartida = filter_input(INPUT_GET, 'idPartida', FILTER_VALIDATE_INT);
if (!$idPartida || $idPartida < 1) {
header('Location: buscadorSala.php');
exit;
}
?>
<!DOCTYPE html> <!DOCTYPE html>
<html lang="es"> <html lang="es">
<head> <head>
@@ -8,39 +25,27 @@
<link rel="stylesheet" href="css/juego.css"> <link rel="stylesheet" href="css/juego.css">
<link rel="icon" type="image/svg+xml" href="img/favicon/favicon.svg"> <link rel="icon" type="image/svg+xml" href="img/favicon/favicon.svg">
</head> </head>
<body> <body data-id-partida="<?= (int)$idPartida ?>">
<!-- Contenedor de la Pregunta --> <!-- Progreso: "Pregunta 3 de 10" + barra -->
<div class="game-header">
<span id="contadorPregunta">Cargando…</span>
</div>
<div class="progress-track">
<div class="progress-fill" id="barraProgreso"></div>
</div>
<!-- Contenedor de la Pregunta (lo rellena js/juego.js) -->
<div class="question-container"> <div class="question-container">
¿Cuál es el planeta más grande de nuestro sistema solar? <div id="textoPregunta">Cargando preguntas…</div>
<div id="feedback" class="feedback" aria-live="polite"></div>
</div> </div>
<!-- Contenedor de Respuestas (Estilo Kahoot) --> <!-- Contenedor de Respuestas (Estilo Kahoot, lo rellena js/juego.js) -->
<div class="answers-grid"> <div class="answers-grid" id="respuestas"></div>
<button class="btn-answer red" onclick="checkAnswer('Marte')">
<span class="shape triangle"></span>
Marte
</button>
<button class="btn-answer blue" onclick="checkAnswer('Júpiter')"> <script src="js/jquery-4.0.0.min.js"></script>
<span class="shape diamond"></span> <script src="js/juego.js"></script>
Júpiter
</button>
<button class="btn-answer yellow" onclick="checkAnswer('Saturno')">
<span class="shape circle"></span>
Saturno
</button>
<button class="btn-answer green" onclick="checkAnswer('Tierra')">
<span class="shape square"></span>
Tierra
</button>
</div>
<script>
</script>
</body> </body>
</html> </html>
+8 -4
View File
@@ -166,6 +166,9 @@ CREATE TABLE `partida` (
`idPartida` int(11) NOT NULL AUTO_INCREMENT, `idPartida` int(11) NOT NULL AUTO_INCREMENT,
`nombrePartida` varchar(100) NOT NULL, `nombrePartida` varchar(100) NOT NULL,
`idUsuario` int(11) NOT NULL, `idUsuario` int(11) NOT NULL,
`fechaCreacion` DATETIME NOT NULL DEFAULT CURRENT_TIMESTAMP,
`fechaInicio` DATETIME NULL,
`fechaFin` DATETIME NULL,
PRIMARY KEY (`idPartida`), PRIMARY KEY (`idPartida`),
KEY `FK idUsuario tabla usuario` (`idUsuario`), KEY `FK idUsuario tabla usuario` (`idUsuario`),
CONSTRAINT `FK idUsuario tabla usuario` FOREIGN KEY (`idUsuario`) REFERENCES `usuario` (`idUsuario`) CONSTRAINT `FK idUsuario tabla usuario` FOREIGN KEY (`idUsuario`) REFERENCES `usuario` (`idUsuario`)
@@ -178,10 +181,10 @@ CREATE TABLE `partida` (
LOCK TABLES `partida` WRITE; LOCK TABLES `partida` WRITE;
/*!40000 ALTER TABLE `partida` DISABLE KEYS */; /*!40000 ALTER TABLE `partida` DISABLE KEYS */;
INSERT INTO `partida` VALUES INSERT INTO `partida` (idPartida, nombrePartida, idUsuario, fechaInicio, fechaFin) VALUES
(1,'Partida de prueba 1',1), (1,'Partida de prueba 1',1, NULL, NULL),
(2,'Partida de prueba 2',4), (2,'Partida de prueba 2',4, NULL, NULL),
(3,'Partida de prueba 3',9); (3,'Partida de prueba 3',9, NULL, NULL);
/*!40000 ALTER TABLE `partida` ENABLE KEYS */; /*!40000 ALTER TABLE `partida` ENABLE KEYS */;
UNLOCK TABLES; UNLOCK TABLES;
@@ -2362,3 +2365,4 @@ UNLOCK TABLES;
/*M!100616 SET NOTE_VERBOSITY=@OLD_NOTE_VERBOSITY */; /*M!100616 SET NOTE_VERBOSITY=@OLD_NOTE_VERBOSITY */;
-- Dump completed on 2026-09-25 10:00:41 -- Dump completed on 2026-09-25 10:00:41
+11 -2
View File
@@ -174,8 +174,17 @@ $avatarUsuario = htmlspecialchars($_SESSION['avatar'] ?? 'img/avatars/defaultPro
</div> </div>
</div> </div>
<!-- TOAST PREGUNTA CREADA -->
<div class="toast-container position-fixed bottom-0 end-0 p-3">
<div id="ToastCrearPregunta" class="toast" role="alert" aria-live="assertive" aria-atomic="true">
<div class=" toast-header" style="background-color: #46178f;">
<h5 class="text-white me-auto bi bi-joystick me-2"> Pregunta Creada</h5>
</div>
<div class="fs-6 toast-body">
La pregunta ha sido creada correctamente
</div>
</div>
</div>
<!-- MODAL CREAR PREGUNTA --> <!-- MODAL CREAR PREGUNTA -->
<div class="modal fade" <div class="modal fade"
id="modalCrearPregunta" id="modalCrearPregunta"
+141 -276
View File
@@ -1,291 +1,156 @@
<?php
session_start();
require_once $_SERVER['DOCUMENT_ROOT'] . '/triviagmatico/includes/conexionBD.php';
$idPartida = intval($_GET['idPartida'] ?? 0);
if ($idPartida <= 0) {
die("Partida no válida.");
}
$conexion = conexionBD();
if (is_array($conexion)) {
die("Error de conexión a la base de datos.");
}
// Obtener listado de jugadores y sus equipos en esta partida
$stmt = $conexion->prepare("
SELECT u.idUsuario, u.nombreUsuario, e.idEquipo, e.nombreEquipo
FROM equipoxpartida exp
INNER JOIN equipoxusuario exu ON exp.idEquipo = exu.idEquipo
INNER JOIN usuario u ON exu.idUsuario = u.idUsuario
INNER JOIN equipo e ON exu.idEquipo = e.idEquipo
WHERE exp.idPartida = :idPartida
");
$stmt->execute([':idPartida' => $idPartida]);
$listaJugadoresRaw = $stmt->fetchAll(PDO::FETCH_ASSOC);
$primerosEnAcertar = $_SESSION['primeros_en_acertar'][$idPartida] ?? [];
$jugadoresFinales = [];
$equiposFinales = [];
// Respuestas de velocidad (+2 o +1)
foreach ($listaJugadoresRaw as $jugador) {
$idU = (int)$jugador['idUsuario'];
$idE = (int)$jugador['idEquipo'];
$stmtRespuestas = $conexion->prepare("
SELECT ra.idPartidaxPregunta, r.correcta
FROM respuestaalmacenada ra
INNER JOIN respuesta r ON ra.idRespuesta = r.idRespuesta
INNER JOIN partidaxpregunta pxp ON ra.idPartidaxPregunta = pxp.idPartidaxPregunta
WHERE pxp.idPartida = :idPartida AND ra.idUsuario = :idUsuario
");
$stmtRespuestas->execute([':idPartida' => $idPartida, ':idUsuario' => $idU]);
$respuestasUsuario = $stmtRespuestas->fetchAll(PDO::FETCH_ASSOC);
$aciertos = 0;
$puntosTotalesJugador = 0;
foreach ($respuestasUsuario as $resp) {
if ((int)$resp['correcta'] === 1) {
$aciertos++;
$idPxp = $resp['idPartidaxPregunta'];
// Si el ID de usuario coincide con el primero guardado de esa pregunta se lleva 2 puntos
if (isset($primerosEnAcertar[$idPxp]) && $primerosEnAcertar[$idPxp] === $idU) {
$puntosTotalesJugador += 2;
} else {
$puntosTotalesJugador += 1;
}
}
}
$jugadoresFinales[] = [
'nombre' => $jugador['nombreUsuario'],
'equipo' => $jugador['nombreEquipo'],
'aciertos' => $aciertos,
'puntos' => $puntosTotalesJugador
];
if (!isset($equiposFinales[$idE])) {
$equiposFinales[$idE] = [
'nombre' => $jugador['nombreEquipo'],
'puntos' => 0
];
}
$equiposFinales[$idE]['puntos'] += $puntosTotalesJugador;
}
// Ordenar según la puntuación final de mayor a menor
usort($jugadoresFinales, function($a, $b) { return $b['puntos'] <=> $a['puntos']; });
usort($equiposFinales, function($a, $b) { return $b['puntos'] <=> $a['puntos']; });
?>
<!DOCTYPE html> <!DOCTYPE html>
<html lang="es"> <html lang="es">
<head> <head>
<meta charset="UTF-8"> <meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0"> <meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Resultados - Trivial</title>
<title>Trivial - Resultado final</title> <link rel="stylesheet" href="css/resultados.css">
<script src="https://jquery.com"></script>
<link rel="icon" type="image/svg+xml" href="img/favicon/favicon.svg">
<link
href="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/css/bootstrap.min.css"
rel="stylesheet">
<!-- Bootstrap Icons -->
<link
rel="stylesheet"
href="https://cdn.jsdelivr.net/npm/bootstrap-icons@1.11.3/font/bootstrap-icons.min.css">
<link
href="https://fonts.googleapis.com/css2?family=Fredoka:wght@400;500;600;700&display=swap"
rel="stylesheet">
<link rel="stylesheet" href="css/index.css">
</head> </head>
<body data-id-partida="<?php echo $idPartida; ?>">
<body> <div id="pantalla-espera" class="contenedor">
<h2>⌛ Esperando a que todos los jugadores terminen...</h2>
<?php <p>Calculando estadísticas de velocidad y actualizando perfiles históricos en el servidor.</p>
include_once("cabecera.php"); <div class="loader"></div>
?>
<main class="container py-5">
<!-- Título -->
<div class="text-center mb-5">
<div class="trophy-icon">
<i class="bi bi-trophy-fill"></i>
</div> </div>
<h1 class="fw-bold"> <div id="pantalla-resultados" class="contenedor" style="display:none;">
¡Trivial terminado! <h1>🏆 Resultados de la Partida 🏆</h1>
</h1>
<p class="text-muted fs-5 mb-0"> <h2>Clasificación por Equipos</h2>
Estos son los resultados finales <table class="tabla-ranking">
</p> <thead>
<tr>
<th>Posición</th>
<th>Equipo</th>
<th>Puntos Totales</th>
</tr>
</thead>
<tbody>
<?php foreach ($equiposFinales as $index => $eq): ?>
<tr class="<?php echo $index === 0 ? 'top-team' : ''; ?>">
<td>#<?php echo $index + 1; ?></td>
<td><?php echo htmlspecialchars($eq['nombre']); ?></td>
<td><strong><?php echo $eq['puntos']; ?> pts</strong></td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
<h2>Rendimiento Individual</h2>
<table class="tabla-ranking">
<thead>
<tr>
<th>Posición</th>
<th>Jugador</th>
<th>Equipo</th>
<th>Aciertos</th>
<th>Puntos Logrados</th>
</tr>
</thead>
<tbody>
<?php foreach ($jugadoresFinales as $index => $jug): ?>
<tr class="<?php echo $index === 0 ? 'top-player' : ''; ?>">
<td>#<?php echo $index + 1; ?></td>
<td><?php echo htmlspecialchars($jug['nombre']); ?></td>
<td><?php echo htmlspecialchars($jug['equipo']); ?></td>
<td><?php echo $jug['aciertos']; ?> / 10</td>
<td><strong><?php echo $jug['puntos']; ?> pts</strong></td>
</tr>
<?php endforeach; ?>
</tbody>
</table>
<div class="contenedor-boton">
<a href="/triviagmatico/buscadorSala.php" class="btn-inicio">Volver al Inicio 🏠</a>
</div>
</div> </div>
<script src="js/resultados.js"></script>
<!-- Equipo ganador -->
<section class="row justify-content-center mb-5">
<div class="col-12 col-lg-8">
<div class="ranking-card">
<div class="ranking-header text-center">
<i class="bi bi-trophy-fill me-2"></i>
Equipo ganador
</div>
<div class="card-body text-center p-4">
<h2 class="fw-bold mb-2">
Los Cerebritos
</h2>
<div class="display-3 fw-bold mb-3">
850
<span class="fs-4 text-muted">
puntos
</span>
</div>
<p class="text-muted mb-0">
<i class="bi bi-stars me-1"></i>
¡Enhorabuena por la victoria!
</p>
</div>
</div>
</div>
</section>
<!-- Resultados de los dos equipos -->
<section class="row justify-content-center g-4">
<!-- EQUIPO GANADOR -->
<div class="col-12 col-lg-6">
<div class="ranking-card h-100">
<div class="ranking-header d-flex justify-content-between align-items-center">
<span>
Los Cerebritos
</span>
<span>
<i class="bi bi-trophy-fill"></i>
</span>
</div>
<div class="card-body p-4">
<div class="text-center mb-4">
<div class="fs-1 fw-bold">
850
</div>
<div class="text-muted">
puntos
</div>
</div>
<h5 class="fw-bold mb-3">
<i class="bi bi-people-fill me-2"></i>
Jugadores
</h5>
<ul class="list-group">
<li class="list-group-item">
1. Carlos
</li>
<li class="list-group-item">
2. Marta
</li>
<li class="list-group-item">
3. Pablo
</li>
<li class="list-group-item">
4. Laura
</li>
<li class="list-group-item">
5. David
</li>
<li class="list-group-item">
6. Ana
</li>
<li class="list-group-item">
7. Sergio
</li>
<li class="list-group-item">
8. Lucía
</li>
<li class="list-group-item">
9. Miguel
</li>
<li class="list-group-item">
10. Elena
</li>
</ul>
</div>
</div>
</div>
<!-- EQUIPO PERDEDOR -->
<div class="col-12 col-lg-6">
<div class="ranking-card h-100">
<!-- Cabecera plateada -->
<div
class="ranking-header d-flex justify-content-between align-items-center"
style="background-color: #c0c0c0;">
<span>
Los Campeones
</span>
<span>
<i class="bi bi-award-fill"></i>
</span>
</div>
<div class="card-body p-4">
<div class="text-center mb-4">
<div class="fs-1 fw-bold">
720
</div>
<div class="text-muted">
puntos
</div>
</div>
<h5 class="fw-bold mb-3">
<i class="bi bi-people-fill me-2"></i>
Jugadores
</h5>
<ul class="list-group">
<li class="list-group-item">
1. Javier
</li>
<li class="list-group-item">
2. Sofía
</li>
<li class="list-group-item">
3. Diego
</li>
<li class="list-group-item">
4. María
</li>
<li class="list-group-item">
5. Alejandro
</li>
<li class="list-group-item">
6. Paula
</li>
<li class="list-group-item">
7. Álvaro
</li>
<li class="list-group-item">
8. Carmen
</li>
</ul>
</div>
</div>
</div>
</section>
<!-- Volver al buscador -->
<div class="text-center mt-5">
<a
href="buscadorSala.php"
class="btn btn-play-kahoot">
<i class="bi bi-arrow-left-circle-fill"></i>
Volver al buscador de salas
</a>
</div>
</main>
<script
src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.3/dist/js/bootstrap.bundle.min.js">
</script>
</body> </body>
</html> </html>
+6
View File
@@ -0,0 +1,6 @@
bDatosServer="localhost"
bDatos="triviagmatico"
bDatosUser="root"
bDatosPass=""
urlBase="http://triviagmatico.test"
dirBase="C:/laragon/www/triviagmatico"