Files
whatsapp/modules/turnero/api/save_tv_media.php
T
Lizandro GuarnizoandClaude Fable 5 587c23adcd Playlist TV: subir en cola con resumen — «subí 5 y solo salieron 2»
El reproductor no se saltaba nada: en la base solo había 2 videos. El defecto
estaba en la subida: los archivos elegidos se mandaban TODOS EN PARALELO
(inp.files.forEach), peleándose el ancho de banda de subida. Los primeros
alcanzaban a entrar y el resto moría por timeout — avisando con un toast que
desaparece en segundos y con una barra de progreso que además era la misma
para todos, pisándose entre sí. El cliente veía «subí 5», la playlist tenía 2.

Ahora los archivos van de a uno (secuencial a propósito), un fallo no frena a
los que siguen, y al terminar queda un RESUMEN FIJO: «3 de 5 agregados», con
el nombre de cada rechazado y su motivo en cristiano. Los videos de iPhone
(.mov) —causa probable del resto de los faltantes— se rechazan de inmediato
con la instrucción de convertirlos a MP4, en el navegador y en el servidor.
Y los límites de PHP (INI_SIZE, FORM_SIZE, PARTIAL) dejan de caer en «error
de subida desconocido».

Verificado además que nginx acepta cuerpos de 90 MB o más: el límite del web
server no era la causa.

scripts/test_tv_playlist.js: 11 pruebas contra el código real — nunca dos
subidas a la vez, los 5 se intentan pese a fallos, el resumen cuenta y nombra,
y los pre-chequeos rechazan sin tocar la red.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-08-27 10:19:02 -05:00

109 lines
3.7 KiB
PHP

<?php
/**
* modules/turnero/api/save_tv_media.php
* Sube un video o imagen a la playlist de la Pantalla TV del turnero.
* POST multipart: campo "media" → sube y agrega al final de la lista
* Guarda el archivo en /uploads/turnero/tv_media/ y una fila en turnero_tv_media.
*/
require_once __DIR__ . '/../../../config/config.php';
header('Content-Type: application/json; charset=utf-8');
if (!isUserLoggedIn()) {
http_response_code(401);
echo json_encode(['ok' => 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',
// Antes estos dos caían en «error desconocido», que no le dice nada a
// quien sube: son los límites de PHP, no del código.
UPLOAD_ERR_INI_SIZE => 'El archivo supera el límite de subida del servidor (php.ini)',
UPLOAD_ERR_FORM_SIZE => 'El archivo supera el límite del formulario',
UPLOAD_ERR_PARTIAL => 'La subida se cortó a medias: intente de nuevo',
];
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])) {
// El caso más común es el video de iPhone: merece su propio mensaje
$msg = ($mime === 'video/quicktime')
? 'Es un video de iPhone (.mov): conviértalo a MP4 antes de subirlo'
: 'Formato no admitido (' . $mime . '). Use MP4, WebM, JPG, PNG o WEBP';
echo json_encode(['ok' => false, 'error' => $msg]);
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,
],
]);