diff --git a/modules/turnero/api/delete_tv_media.php b/modules/turnero/api/delete_tv_media.php new file mode 100644 index 0000000..e82ffc3 --- /dev/null +++ b/modules/turnero/api/delete_tv_media.php @@ -0,0 +1,45 @@ + false, 'error' => 'No autenticado']); + exit; +} + +$pdo = Database::getInstance()->getConnection(); +$body = json_decode(file_get_contents('php://input'), true) ?? []; +$id = (int)($body['id'] ?? 0); + +if (!$id) { + echo json_encode(['ok' => false, 'error' => 'id requerido']); + exit; +} + +$stmt = $pdo->prepare("SELECT url FROM turnero_tv_media WHERE id = ?"); +$stmt->execute([$id]); +$row = $stmt->fetch(PDO::FETCH_ASSOC); + +if (!$row) { + echo json_encode(['ok' => false, 'error' => 'No encontrado']); + exit; +} + +$uploadDir = dirname(__DIR__, 3) . '/uploads/turnero/tv_media/'; +$filename = basename(parse_url($row['url'], PHP_URL_PATH)); +$filepath = $uploadDir . $filename; +if (is_file($filepath)) { + @unlink($filepath); +} + +$pdo->prepare("DELETE FROM turnero_tv_media WHERE id = ?")->execute([$id]); + +echo json_encode(['ok' => true]); diff --git a/modules/turnero/api/reorder_tv_media.php b/modules/turnero/api/reorder_tv_media.php new file mode 100644 index 0000000..4c426fc --- /dev/null +++ b/modules/turnero/api/reorder_tv_media.php @@ -0,0 +1,37 @@ + false, 'error' => 'No autenticado']); + exit; +} + +$pdo = Database::getInstance()->getConnection(); +$body = json_decode(file_get_contents('php://input'), true) ?? []; +$items = is_array($body['items'] ?? null) ? $body['items'] : []; + +if (!$items) { + echo json_encode(['ok' => false, 'error' => 'items requerido']); + exit; +} + +$stmt = $pdo->prepare( + "UPDATE turnero_tv_media SET orden = ?, duracion_segundos = ? WHERE id = ?" +); +foreach ($items as $i => $it) { + $id = (int)($it['id'] ?? 0); + if (!$id) continue; + $dur = max(1, (int)($it['duracion_segundos'] ?? 8)); + $stmt->execute([$i, $dur, $id]); +} + +echo json_encode(['ok' => true]); diff --git a/modules/turnero/api/save_tv_media.php b/modules/turnero/api/save_tv_media.php new file mode 100644 index 0000000..76c2051 --- /dev/null +++ b/modules/turnero/api/save_tv_media.php @@ -0,0 +1,99 @@ + false, 'error' => 'No autenticado']); + exit; +} + +$pdo = Database::getInstance()->getConnection(); +$uploadDir = dirname(__DIR__, 3) . '/uploads/turnero/tv_media/'; +$publicBase = rtrim(BASE_URL, '/') . '/uploads/turnero/tv_media/'; + +if (!is_dir($uploadDir) && !mkdir($uploadDir, 0755, true)) { + echo json_encode(['ok' => false, 'error' => 'No se pudo crear el directorio de uploads']); + exit; +} + +if (!isset($_FILES['media'])) { + echo json_encode(['ok' => false, 'error' => 'No se recibió ningún archivo']); + exit; +} + +$file = $_FILES['media']; + +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; +} + +$maxBytes = 500 * 1024 * 1024; +if ($file['size'] > $maxBytes) { + echo json_encode(['ok' => false, 'error' => 'El archivo supera el límite de 500 MB']); + exit; +} + +$mimeMap = [ + 'video/mp4' => ['video', 'mp4'], + 'video/webm' => ['video', 'webm'], + 'video/ogg' => ['video', 'ogv'], + 'image/jpeg' => ['imagen', 'jpg'], + 'image/png' => ['imagen', 'png'], + 'image/webp' => ['imagen', 'webp'], +]; + +$finfo = new finfo(FILEINFO_MIME_TYPE); +$mime = $finfo->file($file['tmp_name']); +if (!isset($mimeMap[$mime])) { + echo json_encode(['ok' => false, 'error' => 'Formato no admitido. Usa MP4, WebM, JPG, PNG o WEBP']); + exit; +} + +[$tipo, $ext] = $mimeMap[$mime]; + +$destFilename = uniqid('tv_', true) . '.' . $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; + +$maxOrden = (int)($pdo->query("SELECT COALESCE(MAX(orden),-1) FROM turnero_tv_media")->fetchColumn()); + +$stmt = $pdo->prepare( + "INSERT INTO turnero_tv_media (tipo, url, orden, duracion_segundos, activo) + VALUES (?, ?, ?, 8, 1)" +); +$stmt->execute([$tipo, $publicUrl, $maxOrden + 1]); + +echo json_encode([ + 'ok' => true, + 'item' => [ + 'id' => (int)$pdo->lastInsertId(), + 'tipo' => $tipo, + 'url' => $publicUrl, + 'orden' => $maxOrden + 1, + 'duracion_segundos' => 8, + ], +]); diff --git a/modules/turnero/api/save_tv_video.php b/modules/turnero/api/save_tv_video.php deleted file mode 100644 index 4a62644..0000000 --- a/modules/turnero/api/save_tv_video.php +++ /dev/null @@ -1,127 +0,0 @@ - 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 1ced4ed..c12b01c 100644 --- a/modules/turnero/views/configuracion.php +++ b/modules/turnero/views/configuracion.php @@ -103,6 +103,13 @@ try { foreach ($cfgRows as $r) { $cfg[$r['clave']] = $r['valor']; } } catch (\Throwable $e) { $_loadErrors[] = 'lab_config: ' . $e->getMessage(); } +$tvMedia = []; +try { + $tvMedia = $pdo->query( + "SELECT id, tipo, url, orden, duracion_segundos FROM turnero_tv_media ORDER BY orden ASC" + )->fetchAll(PDO::FETCH_ASSOC); +} catch (\Throwable $e) { $_loadErrors[] = 'turnero_tv_media: ' . $e->getMessage(); } + $plantillasAprobadas = []; $plantillasHabilitadas = []; try { @@ -1140,36 +1147,51 @@ $tab = $_GET['tab'] ?? 'lugares'; TAB 5 — PANTALLA TV ════════════════════════════════════════ --> - -

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.

+

Playlist de la Pantalla TV

+

Videos e imágenes se reproducen en bucle, uno detrás de otro, sin sonido, como fondo en la pantalla TV del turnero. Formatos: MP4, WebM, JPG, PNG, WEBP — máximo 500 MB por archivo.

- -
> -

Video actual:

- -
-
+ + +
Sin videos ni imágenes en la playlist.
+
- -
> - - +
+ +
+
-
- -
+
@@ -456,6 +457,38 @@ $_hasVideo = (bool)$_tvVideo;