Initial commit: Proyecto Portal ePromos completo con documentación unificada y seguridad base
This commit is contained in:
@@ -0,0 +1,129 @@
|
||||
<?php
|
||||
// memoria/index.php
|
||||
session_start();
|
||||
require_once __DIR__ . '/../admin/db.php'; // ajusta la ruta si tu db.php está en otro lugar
|
||||
|
||||
$juego_id = (int)($_GET['juego_id'] ?? 1);
|
||||
|
||||
// 1) Cargar config del juego (banners, fondo, reverso, icono FS y ruleta_id a la que enlaza)
|
||||
$stmt = $conn->prepare("SELECT
|
||||
nombre, banner_1, banner_2, banner_3, banner_4,
|
||||
background_image, fullscreen_icon, reverso_default, ruleta_id
|
||||
FROM juegos_config
|
||||
WHERE juego_id = ? LIMIT 1");
|
||||
$stmt->bind_param("i", $juego_id);
|
||||
$stmt->execute();
|
||||
$config = $stmt->get_result()->fetch_assoc();
|
||||
$stmt->close();
|
||||
|
||||
if (!$config) {
|
||||
die("Config del juego no encontrada (juego_id=$juego_id).");
|
||||
}
|
||||
|
||||
// 2) Cargar cartas activas (traseras a emparejar). Puedes manejar n pares desde la BD.
|
||||
$cartas = [];
|
||||
$res = $conn->prepare("SELECT id, nombre, imagen_trasera, COALESCE(imagen_frontal, '') AS imagen_frontal
|
||||
FROM cartas
|
||||
WHERE juego_id = ? AND activo = 1");
|
||||
$res->bind_param("i", $juego_id);
|
||||
$res->execute();
|
||||
$q = $res->get_result();
|
||||
while ($row = $q->fetch_assoc()) { $cartas[] = $row; }
|
||||
$res->close();
|
||||
|
||||
if (!$cartas) {
|
||||
die("No hay cartas activas para este juego (juego_id=$juego_id).");
|
||||
}
|
||||
|
||||
// Reverso por defecto (lado frontal que se ve al inicio)
|
||||
$reverso = $config['reverso_default'] ?: '/memoria/images/back.png';
|
||||
|
||||
// Generar parejas: duplico la lista de cartas. Si tienes más de 12, puedes recortar.
|
||||
$base = $cartas;
|
||||
shuffle($base);
|
||||
$max_pairs = 6; // cambia si quieres más o menos pares
|
||||
$base = array_slice($base, 0, $max_pairs);
|
||||
|
||||
// Duplicar para formar pares y asignar una clave de pareja consistente
|
||||
$deck = [];
|
||||
$pair_idx = 1;
|
||||
foreach ($base as $c) {
|
||||
$key = 'pair_' . $pair_idx++;
|
||||
$deck[] = ['key'=>$key, 'back'=>$c['imagen_trasera'], 'front'=>$c['imagen_frontal'] ?: $reverso];
|
||||
$deck[] = ['key'=>$key, 'back'=>$c['imagen_trasera'], 'front'=>$c['imagen_frontal'] ?: $reverso];
|
||||
}
|
||||
shuffle($deck);
|
||||
|
||||
// Pasar al front la config necesaria
|
||||
$CONFIG_JS = [
|
||||
'juego_id' => $juego_id,
|
||||
'ruleta_id' => (int)$config['ruleta_id'],
|
||||
'banners' => [
|
||||
'banner_1' => $config['banner_1'] ?: null,
|
||||
'banner_2' => $config['banner_2'] ?: null,
|
||||
'banner_3' => $config['banner_3'] ?: null,
|
||||
'banner_4' => $config['banner_4'] ?: null,
|
||||
],
|
||||
'background_image' => $config['background_image'] ?: null,
|
||||
'fullscreen_icon' => $config['fullscreen_icon'] ?: null,
|
||||
];
|
||||
?>
|
||||
<!doctype html>
|
||||
<html lang="es" dir="ltr">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<title>Juego de Memoria - ePromo</title>
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
||||
<link rel="stylesheet" href="style.css">
|
||||
</head>
|
||||
<body>
|
||||
<!-- Banners dinámicos -->
|
||||
<div class="banner banner-1"></div>
|
||||
<div class="banner banner-2"></div>
|
||||
<div class="banner banner-3"></div>
|
||||
<div class="banner banner-4"></div>
|
||||
|
||||
<div class="wrapper">
|
||||
<ul class="cards" id="cards">
|
||||
<?php foreach ($deck as $card): ?>
|
||||
<li class="card" data-key="<?= htmlspecialchars($card['key']) ?>">
|
||||
<div class="view front-view">
|
||||
<img src="<?= htmlspecialchars($card['front']) ?>" alt="front">
|
||||
</div>
|
||||
<div class="view back-view">
|
||||
<img src="<?= htmlspecialchars($card['back']) ?>" alt="back">
|
||||
</div>
|
||||
</li>
|
||||
<?php endforeach; ?>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<!-- Fullscreen -->
|
||||
<button id="fullscreen-btn"><img id="fullscreen-icon" src="" alt="Pantalla Completa"></button>
|
||||
|
||||
<script>
|
||||
// Inyectar config al front
|
||||
window.MEMORIA_CONFIG = <?= json_encode($CONFIG_JS, JSON_UNESCAPED_SLASHES|JSON_UNESCAPED_UNICODE) ?>;
|
||||
// Aplicar banners/bg/icono
|
||||
(function(){
|
||||
const C = window.MEMORIA_CONFIG || {};
|
||||
if (C.background_image) {
|
||||
document.body.style.backgroundImage = `url("${C.background_image}")`;
|
||||
document.body.style.backgroundSize = 'cover';
|
||||
document.body.style.backgroundRepeat = 'no-repeat';
|
||||
document.body.style.backgroundPosition = 'center center';
|
||||
}
|
||||
const b1 = document.querySelector('.banner-1'), b2 = document.querySelector('.banner-2'),
|
||||
b3 = document.querySelector('.banner-3'), b4 = document.querySelector('.banner-4');
|
||||
if (b1 && C.banners.banner_1) b1.style.backgroundImage = `url("${C.banners.banner_1}")`;
|
||||
if (b2 && C.banners.banner_2) b2.style.backgroundImage = `url("${C.banners.banner_2}")`;
|
||||
if (b3 && C.banners.banner_3) b3.style.backgroundImage = `url("${C.banners.banner_3}")`;
|
||||
if (b4 && C.banners.banner_4) b4.style.backgroundImage = `url("${C.banners.banner_4}")`;
|
||||
if (C.fullscreen_icon) document.getElementById('fullscreen-icon').src = C.fullscreen_icon;
|
||||
})();
|
||||
</script>
|
||||
|
||||
<script src="script.js"></script>
|
||||
<script src="https://cdn.jsdelivr.net/npm/canvas-confetti@1.5.1/dist/confetti.browser.min.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,76 @@
|
||||
<?php
|
||||
// memoria/registrar_jugada.php
|
||||
header('Content-Type: application/json; charset=utf-8');
|
||||
|
||||
session_start();
|
||||
require_once __DIR__ . '/../admin/db.php';
|
||||
|
||||
// Leer payload
|
||||
$payload = json_decode(file_get_contents('php://input'), true) ?: [];
|
||||
$juego_id = (int)($payload['juego_id'] ?? 1);
|
||||
$elapsed = (int)($payload['elapsed_ms'] ?? 0);
|
||||
$aciertos = (int)($payload['aciertos'] ?? 0);
|
||||
|
||||
$user_agent = $_SERVER['HTTP_USER_AGENT'] ?? '';
|
||||
$ip_address = $_SERVER['REMOTE_ADDR'] ?? '';
|
||||
|
||||
// 1) Traer la ruleta asociada a este juego
|
||||
$stmt = $conn->prepare("SELECT ruleta_id FROM juegos_config WHERE juego_id=? LIMIT 1");
|
||||
$stmt->bind_param("i", $juego_id);
|
||||
$stmt->execute();
|
||||
$ruleta_id = (int)($stmt->get_result()->fetch_assoc()['ruleta_id'] ?? 0);
|
||||
$stmt->close();
|
||||
|
||||
// 2) Registrar jugada propia del juego de memoria
|
||||
$stmt = $conn->prepare("INSERT INTO jugadas_memoria (juego_id, ip_address, user_agent, tiempo_ms, aciertos) VALUES (?,?,?,?,?)");
|
||||
$stmt->bind_param("issii", $juego_id, $ip_address, $user_agent, $elapsed, $aciertos);
|
||||
$stmt->execute();
|
||||
$stmt->close();
|
||||
|
||||
// 3) Seleccionar premio usando la misma lógica que la ruleta
|
||||
$premio_nombre = null;
|
||||
if ($ruleta_id > 0) {
|
||||
// Leer premios con inventario y probabilidad
|
||||
$stmt = $conn->prepare("SELECT id, nombre, cantidad, probabilidad FROM premios WHERE ruleta_id=? AND cantidad > 0");
|
||||
$stmt->bind_param("i", $ruleta_id);
|
||||
$stmt->execute();
|
||||
$res = $stmt->get_result();
|
||||
$premios = [];
|
||||
$totalProb = 0.0;
|
||||
while ($row = $res->fetch_assoc()) {
|
||||
$row['probabilidad'] = (float)$row['probabilidad'];
|
||||
$premios[] = $row;
|
||||
$totalProb += $row['probabilidad'];
|
||||
}
|
||||
$stmt->close();
|
||||
|
||||
if ($premios && $totalProb > 0) {
|
||||
// sorteo ponderado
|
||||
$rnd = mt_rand() / mt_getrandmax() * $totalProb;
|
||||
$acc = 0.0; $ganador = null;
|
||||
foreach ($premios as $p) {
|
||||
$acc += $p['probabilidad'];
|
||||
if ($rnd <= $acc) { $ganador = $p; break; }
|
||||
}
|
||||
if (!$ganador) $ganador = end($premios);
|
||||
|
||||
// Guardar jugada general de la ruleta (para tus reportes unificados)
|
||||
$stmt = $conn->prepare("INSERT INTO jugadas (ruleta_id, premio_id, ip_address, user_agent) VALUES (?,?,?,?)");
|
||||
$stmt->bind_param("iiss", $ruleta_id, $ganador['id'], $ip_address, $user_agent);
|
||||
$stmt->execute();
|
||||
$stmt->close();
|
||||
|
||||
// Descontar inventario
|
||||
$stmt = $conn->prepare("UPDATE premios SET cantidad = cantidad - 1 WHERE id=? AND cantidad > 0");
|
||||
$stmt->bind_param("i", $ganador['id']);
|
||||
$stmt->execute();
|
||||
$stmt->close();
|
||||
|
||||
$premio_nombre = $ganador['nombre'];
|
||||
}
|
||||
}
|
||||
|
||||
echo json_encode([
|
||||
'ok' => true,
|
||||
'premio' => $premio_nombre
|
||||
]);
|
||||
@@ -0,0 +1,101 @@
|
||||
// memoria/script.js
|
||||
(function () {
|
||||
const cards = document.querySelectorAll(".card");
|
||||
const totalPairs = cards.length / 2;
|
||||
|
||||
let matched = 0;
|
||||
let cardOne = null, cardTwo = null;
|
||||
let disableDeck = false;
|
||||
const startTime = Date.now();
|
||||
|
||||
function flipCard({ target: clickedCard }) {
|
||||
if (disableDeck || clickedCard === cardOne || clickedCard.classList.contains("flip")) return;
|
||||
clickedCard.classList.add("flip");
|
||||
if (!cardOne) { cardOne = clickedCard; return; }
|
||||
cardTwo = clickedCard;
|
||||
disableDeck = true;
|
||||
|
||||
const k1 = cardOne.getAttribute("data-key");
|
||||
const k2 = cardTwo.getAttribute("data-key");
|
||||
matchCards(k1, k2);
|
||||
}
|
||||
|
||||
function matchCards(k1, k2) {
|
||||
if (k1 === k2) {
|
||||
matched++;
|
||||
cardOne.removeEventListener("click", flipCard);
|
||||
cardTwo.removeEventListener("click", flipCard);
|
||||
cardOne = cardTwo = null;
|
||||
disableDeck = false;
|
||||
|
||||
if (matched === totalPairs) {
|
||||
// Juego terminado
|
||||
setTimeout(onGameFinished, 600);
|
||||
}
|
||||
return;
|
||||
}
|
||||
setTimeout(() => {
|
||||
cardOne.classList.add("shake");
|
||||
cardTwo.classList.add("shake");
|
||||
}, 300);
|
||||
|
||||
setTimeout(() => {
|
||||
cardOne.classList.remove("shake", "flip");
|
||||
cardTwo.classList.remove("shake", "flip");
|
||||
cardOne = cardTwo = null;
|
||||
disableDeck = false;
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
function onGameFinished() {
|
||||
const elapsedMs = Date.now() - startTime;
|
||||
const C = window.MEMORIA_CONFIG || {};
|
||||
const juego_id = C.juego_id || 1;
|
||||
|
||||
// Registrar jugada y obtener premio (usando la ruleta vinculada en la config del juego)
|
||||
fetch("registrar_jugada.php", {
|
||||
method: "POST",
|
||||
headers: { "Content-Type": "application/json" },
|
||||
body: JSON.stringify({
|
||||
juego_id,
|
||||
elapsed_ms: elapsedMs,
|
||||
aciertos: matched,
|
||||
// ruleta_id es opcional aquí; el servidor puede leerlo de la config
|
||||
}),
|
||||
})
|
||||
.then(r => r.json())
|
||||
.then(data => {
|
||||
const nombrePremio = data && data.premio ? data.premio : "¡Completado!";
|
||||
mostrarConfetiConTexto(nombrePremio);
|
||||
// Si quieres, podrías reiniciar el mazo o redirigir:
|
||||
// setTimeout(() => location.reload(), 2500);
|
||||
})
|
||||
.catch(err => {
|
||||
console.error(err);
|
||||
mostrarConfetiConTexto("¡Completado!");
|
||||
});
|
||||
}
|
||||
|
||||
// Eventos
|
||||
cards.forEach(card => card.addEventListener("click", flipCard));
|
||||
|
||||
function toggleFullScreen() {
|
||||
if (!document.fullscreenElement) {
|
||||
document.documentElement.requestFullscreen().catch(err => console.log(err));
|
||||
} else {
|
||||
document.exitFullscreen().catch(err => console.log(err));
|
||||
}
|
||||
}
|
||||
document.getElementById("fullscreen-btn").addEventListener("click", toggleFullScreen);
|
||||
|
||||
// Confeti
|
||||
function mostrarConfetiConTexto(texto = "") {
|
||||
const duration = 4000, end = Date.now() + duration;
|
||||
(function frame() {
|
||||
confetti({ particleCount: 7, angle: 60, spread: 55, origin: { x: 0 }, scalar: 1.1 });
|
||||
confetti({ particleCount: 7, angle: 120, spread: 55, origin: { x: 1 }, scalar: 1.1 });
|
||||
if (Date.now() < end) requestAnimationFrame(frame);
|
||||
})();
|
||||
if (texto) console.log("🎉 Premio:", texto);
|
||||
}
|
||||
})();
|
||||
@@ -0,0 +1,130 @@
|
||||
/* Import Google Font - Poppins */
|
||||
@import url('https://fonts.googleapis.com/css2?family=Poppins:wght@400;500;600;700&display=swap');
|
||||
*{
|
||||
margin: 0;
|
||||
padding: 0;
|
||||
box-sizing: border-box;
|
||||
font-family: 'Poppins', sans-serif;
|
||||
}
|
||||
body{
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
min-height: 100vh;
|
||||
background: #E9AD14 /*url(https://portal.gomezleemarketing.com/wp-content/uploads/2024/02/BANNER-SUPERIOR-GRANDE.png) repeat center center*/;
|
||||
}
|
||||
.wrapper{
|
||||
padding: 10px;
|
||||
border-radius: 10px;
|
||||
background: #727272;
|
||||
box-shadow: 0 10px 30px rgba(0,0,0,0.1);
|
||||
}
|
||||
.cards, .card, .view{
|
||||
display: flex;
|
||||
align-items: center;
|
||||
justify-content: center;
|
||||
}
|
||||
.cards{
|
||||
height: 800px;
|
||||
width: 800px;
|
||||
flex-wrap: wrap;
|
||||
justify-content: space-between;
|
||||
}
|
||||
.cards .card{
|
||||
cursor: pointer;
|
||||
list-style: none;
|
||||
user-select: none;
|
||||
position: relative;
|
||||
perspective: 1000px;
|
||||
transform-style: preserve-3d;
|
||||
height: calc(100% / 4 - 10px);
|
||||
width: calc(100% / 3 - 10px);
|
||||
}
|
||||
.card.shake{
|
||||
animation: shake 0.35s ease-in-out;
|
||||
}
|
||||
@keyframes shake {
|
||||
0%, 100%{
|
||||
transform: translateX(0);
|
||||
}
|
||||
20%{
|
||||
transform: translateX(-13px);
|
||||
}
|
||||
40%{
|
||||
transform: translateX(13px);
|
||||
}
|
||||
60%{
|
||||
transform: translateX(-8px);
|
||||
}
|
||||
80%{
|
||||
transform: translateX(8px);
|
||||
}
|
||||
}
|
||||
.card .view{
|
||||
width: 100%;
|
||||
height: 100%;
|
||||
position: absolute;
|
||||
border-radius: 7px;
|
||||
background: #fff;
|
||||
pointer-events: none;
|
||||
backface-visibility: hidden;
|
||||
box-shadow: 0 3px 10px rgba(0,0,0,0.1);
|
||||
transition: transform 0.25s linear;
|
||||
}
|
||||
.card .front-view img{
|
||||
width: 120px;
|
||||
}
|
||||
.card .back-view img{
|
||||
max-width: 150px;
|
||||
}
|
||||
.card .back-view{
|
||||
transform: rotateY(-180deg);
|
||||
}
|
||||
.card.flip .back-view{
|
||||
transform: rotateY(0);
|
||||
}
|
||||
.card.flip .front-view{
|
||||
transform: rotateY(180deg);
|
||||
}
|
||||
|
||||
@media screen and (max-width: 700px) {
|
||||
.cards{
|
||||
height: 400px;
|
||||
width: 400px;
|
||||
}
|
||||
.card .front-view img{
|
||||
width: 70px;
|
||||
}
|
||||
.card .back-view img{
|
||||
max-width: 80px;
|
||||
}
|
||||
}
|
||||
|
||||
@media screen and (max-width: 530px) {
|
||||
.cards{
|
||||
height: 400px;
|
||||
width: 400px;
|
||||
}
|
||||
.card .front-view img{
|
||||
width: 70px;
|
||||
}
|
||||
.card .back-view img{
|
||||
max-width: 80px;
|
||||
}
|
||||
}
|
||||
|
||||
#fullscreen-btn {
|
||||
position: fixed;
|
||||
top: 10px;
|
||||
right: 10px;
|
||||
background: none;
|
||||
border: none;
|
||||
cursor: pointer;
|
||||
z-index: 1000;
|
||||
}
|
||||
|
||||
#fullscreen-btn img {
|
||||
width: 40px;
|
||||
height: 40px;
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user