From 55d45816af7c8758b776c55346fcf9924427b495 Mon Sep 17 00:00:00 2001 From: Lizandro Guarnizo <77708265+lizandrogd@users.noreply.github.com> Date: Tue, 21 Apr 2026 20:26:01 -0500 Subject: [PATCH] cambios de video --- modules/turnero/api/save_tv_video.php | 127 +++++++++++++++++++++ modules/turnero/views/configuracion.php | 134 +++++++++++++++++++++++ modules/turnero/views/display_global.php | 17 ++- 3 files changed, 277 insertions(+), 1 deletion(-) create mode 100644 modules/turnero/api/save_tv_video.php diff --git a/modules/turnero/api/save_tv_video.php b/modules/turnero/api/save_tv_video.php new file mode 100644 index 0000000..4a62644 --- /dev/null +++ b/modules/turnero/api/save_tv_video.php @@ -0,0 +1,127 @@ + false, 'error' => 'No autenticado']); + exit; +} + +$pdo = Database::getInstance()->getConnection(); +$uploadDir = dirname(__DIR__, 3) . '/uploads/turnero/'; +$publicBase = rtrim(BASE_URL, '/') . '/uploads/turnero/'; + +// ── Crear carpeta si no existe ──────────────────────────────── +if (!is_dir($uploadDir)) { + if (!mkdir($uploadDir, 0755, true)) { + echo json_encode(['ok' => false, 'error' => 'No se pudo crear el directorio de uploads']); + exit; + } +} + +// ── Acción: borrar ──────────────────────────────────────────── +$rawBody = file_get_contents('php://input'); +$bodyJson = $rawBody ? json_decode($rawBody, true) : null; + +if (isset($bodyJson['accion']) && $bodyJson['accion'] === 'borrar') { + // Obtener ruta guardada + $stmt = $pdo->prepare("SELECT valor FROM lab_config WHERE clave = 'turnero_tv_video' LIMIT 1"); + $stmt->execute(); + $row = $stmt->fetch(PDO::FETCH_ASSOC); + + if ($row && $row['valor']) { + // Derivar nombre de archivo de la URL guardada + $filename = basename(parse_url($row['valor'], PHP_URL_PATH)); + $filepath = $uploadDir . $filename; + if ($filepath && is_file($filepath)) { + @unlink($filepath); + } + } + + // Borrar de la BD + $pdo->prepare("DELETE FROM lab_config WHERE clave = 'turnero_tv_video'")->execute(); + echo json_encode(['ok' => true]); + exit; +} + +// ── Acción: subir ────────────────────────────────────────────── +if (!isset($_FILES['video'])) { + echo json_encode(['ok' => false, 'error' => 'No se recibió ningún archivo']); + exit; +} + +$file = $_FILES['video']; + +if ($file['error'] !== UPLOAD_ERR_OK) { + $errores = [ + UPLOAD_ERR_INI_SIZE => 'El archivo supera upload_max_filesize en php.ini', + UPLOAD_ERR_FORM_SIZE => 'El archivo supera MAX_FILE_SIZE del formulario', + UPLOAD_ERR_PARTIAL => 'El archivo se subió parcialmente', + UPLOAD_ERR_NO_FILE => 'No se seleccionó ningún archivo', + UPLOAD_ERR_NO_TMP_DIR => 'Falta la carpeta temporal', + UPLOAD_ERR_CANT_WRITE => 'Error al escribir el archivo', + ]; + echo json_encode(['ok' => false, 'error' => $errores[$file['error']] ?? 'Error de subida desconocido']); + exit; +} + +// Validar tamaño (máx 500 MB) +$maxBytes = 500 * 1024 * 1024; +if ($file['size'] > $maxBytes) { + echo json_encode(['ok' => false, 'error' => 'El video supera el límite de 500 MB']); + exit; +} + +// Validar tipo MIME (solo MP4 y WebM) +$allowedMime = ['video/mp4', 'video/webm', 'video/ogg']; +$finfo = new finfo(FILEINFO_MIME_TYPE); +$mime = $finfo->file($file['tmp_name']); +if (!in_array($mime, $allowedMime, true)) { + echo json_encode(['ok' => false, 'error' => 'Solo se permiten videos MP4 o WebM']); + exit; +} + +// Definir extensión segura desde el MIME real +$extMap = ['video/mp4' => 'mp4', 'video/webm' => 'webm', 'video/ogg' => 'ogv']; +$ext = $extMap[$mime]; + +// Borrar video anterior si existe +foreach (['mp4', 'webm', 'ogv'] as $oldExt) { + $oldPath = $uploadDir . 'tv_video.' . $oldExt; + if (is_file($oldPath)) { + @unlink($oldPath); + } +} + +$destFilename = 'tv_video.' . $ext; +$destPath = $uploadDir . $destFilename; + +if (!move_uploaded_file($file['tmp_name'], $destPath)) { + echo json_encode(['ok' => false, 'error' => 'No se pudo guardar el archivo']); + exit; +} + +$publicUrl = $publicBase . $destFilename; + +// Guardar URL en lab_config (upsert) +$stmt = $pdo->prepare( + "INSERT INTO lab_config (clave, valor) VALUES ('turnero_tv_video', ?) + ON DUPLICATE KEY UPDATE valor = VALUES(valor)" +); +$stmt->execute([$publicUrl]); + +echo json_encode(['ok' => true, 'url' => $publicUrl]); diff --git a/modules/turnero/views/configuracion.php b/modules/turnero/views/configuracion.php index aef9685..1956594 100644 --- a/modules/turnero/views/configuracion.php +++ b/modules/turnero/views/configuracion.php @@ -6,6 +6,7 @@ * TAB 2: Exámenes y Consentimientos * TAB 3: Prioridades * TAB 4: Sesión y WhatsApp + * TAB 5: Pantalla TV (video de fondo) */ require_once __DIR__ . '/../../../config/config.php'; @@ -159,6 +160,11 @@ $tab = $_GET['tab'] ?? 'lugares'; Sesión y WhatsApp +
@@ -706,6 +712,55 @@ $tab = $_GET['tab'] ?? 'lugares';
+ + + + + +

Video de fondo / publicidad

+

El video se reproduce en bucle y sin sonido como fondo en la pantalla TV del turnero. Formatos admitidos: MP4, WebM. Máximo 500 MB.

+ + +
> +

Video actual:

+ +
+ +
+
+ + +
> + + +
+ + + + + @@ -942,6 +997,85 @@ async function cambiarSesion(accion, sesionId) { } catch (e) { toast('Error de conexión', 'error'); } } +// ───────────────────────────────────────────────────────────── +// TAB 5: PANTALLA TV +// ───────────────────────────────────────────────────────────── +(function () { + const inp = document.getElementById('tv-file-input'); + const drop = document.getElementById('tv-drop'); + if (!inp) return; + + // Drag-and-drop visual + drop.addEventListener('dragover', e => { e.preventDefault(); drop.style.borderColor = '#3b82f6'; }); + drop.addEventListener('dragleave', () => { drop.style.borderColor = '#cbd5e1'; }); + drop.addEventListener('drop', e => { + e.preventDefault(); drop.style.borderColor = '#cbd5e1'; + const f = e.dataTransfer.files[0]; + if (f) subirTvVideo(f); + }); + inp.addEventListener('change', () => { if (inp.files[0]) subirTvVideo(inp.files[0]); }); +})(); + +function subirTvVideo(file) { + const allowedMime = ['video/mp4', 'video/webm', 'video/ogg']; + if (!allowedMime.includes(file.type)) { toast('Solo se permiten MP4 o WebM', 'error'); return; } + if (file.size > 500 * 1024 * 1024) { toast('El video supera el límite de 500 MB', 'error'); return; } + + const wrap = document.getElementById('tv-progress-wrap'); + const bar = document.getElementById('tv-progress-bar'); + const pct = document.getElementById('tv-progress-pct'); + const lbl = document.getElementById('tv-progress-lbl'); + wrap.style.display = ''; + + const fd = new FormData(); + fd.append('video', file); + + const xhr = new XMLHttpRequest(); + xhr.open('POST', API + 'save_tv_video.php'); + + xhr.upload.addEventListener('progress', e => { + if (!e.lengthComputable) return; + const p = Math.round(e.loaded / e.total * 100); + bar.style.width = p + '%'; + pct.textContent = p + '%'; + }); + + xhr.addEventListener('load', () => { + wrap.style.display = 'none'; + try { + const json = JSON.parse(xhr.responseText); + if (!json.ok) { toast(json.error || 'Error al subir', 'error'); return; } + toast('Video guardado correctamente'); + // Mostrar preview y ocultar zona de subida + document.getElementById('tv-preview').src = json.url; + document.getElementById('tv-preview-wrap').style.display = ''; + document.getElementById('tv-upload-wrap').style.display = 'none'; + } catch(e) { toast('Respuesta inesperada del servidor', 'error'); } + }); + + xhr.addEventListener('error', () => { wrap.style.display = 'none'; toast('Error de conexión', 'error'); }); + + lbl.textContent = 'Subiendo ' + file.name + '…'; + bar.style.width = '0%'; + xhr.send(fd); +} + +async function borrarTvVideo() { + if (!confirm('¿Quitar el video de la pantalla TV?')) return; + try { + const res = await fetch(API + 'save_tv_video.php', { + method: 'POST', headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ accion: 'borrar' }) + }); + const json = await res.json(); + if (!json.ok) { toast(json.error || 'Error', 'error'); return; } + toast('Video eliminado'); + document.getElementById('tv-preview-wrap').style.display = 'none'; + document.getElementById('tv-upload-wrap').style.display = ''; + document.getElementById('tv-preview').src = ''; + } catch(e) { toast('Error de conexión', 'error'); } +} + async function guardarWhatsApp() { const template = document.getElementById('wa-template')?.value.trim(); const lang = document.getElementById('wa-lang')?.value.trim(); diff --git a/modules/turnero/views/display_global.php b/modules/turnero/views/display_global.php index 2650b85..c51e981 100644 --- a/modules/turnero/views/display_global.php +++ b/modules/turnero/views/display_global.php @@ -4,13 +4,16 @@ $_dispCfg = []; try { $__pdo = Database::getInstance()->getConnection(); $__rows = $__pdo->query( - "SELECT clave, valor FROM lab_config WHERE clave IN ('empresa_nombre','doc_logo_base64','doc_color')" + "SELECT clave, valor FROM lab_config WHERE clave IN ('empresa_nombre','doc_logo_base64','doc_color','turnero_tv_video')" )->fetchAll(PDO::FETCH_KEY_PAIR); $_dispCfg = $__rows ?: []; } catch (\Throwable $_) {} $_labNombre = htmlspecialchars($_dispCfg['empresa_nombre'] ?? 'Sistema de Turnos'); $_labLogo = $_dispCfg['doc_logo_base64'] ?? ''; $_labColor = preg_match('/^#[0-9a-fA-F]{3,8}$/', $_dispCfg['doc_color'] ?? '') ? $_dispCfg['doc_color'] : '#1565c0'; +$_tvVideo = $_dispCfg['turnero_tv_video'] ?? ''; +// Validar que sea una URL del mismo origen (solo rutas relativas o mismo dominio) +if ($_tvVideo && !preg_match('#^https?://#', $_tvVideo)) { $_tvVideo = ''; } ?> @@ -303,6 +306,13 @@ $_labColor = preg_match('/^#[0-9a-fA-F]{3,8}$/', $_dispCfg['doc_color'] ?? '') @keyframes r-pop { 0%{transform:translate(-50%,-50%) scale(1);background:var(--pc,var(--brand));opacity:.38} 100%{transform:translate(-50%,-50%) scale(50);background:var(--pc,var(--brand));opacity:0} } /* Cola dark */ .pg-cola { background: rgba(255,255,255,.022); border-top: 1px solid var(--border); } + /* Video de fondo TV */ + .tv-bg-video { + position: absolute; inset: 0; width: 100%; height: 100%; + object-fit: cover; opacity: .14; z-index: 0; pointer-events: none; + } + .pg-body { position: relative; } + .pg-body > *:not(.tv-bg-video) { position: relative; z-index: 1; } .cola-lbl { color: var(--brand); } .cola-sep { width: 1px; height: 12px; background: var(--border); flex-shrink: 0; } .cola-chip { background: rgba(255,255,255,.04); font-size: .68rem; } @@ -382,6 +392,11 @@ $_labColor = preg_match('/^#[0-9a-fA-F]{3,8}$/', $_dispCfg['doc_color'] ?? '')
+ + +
Recepción