From bad3d42a92d84542e1d12fd67def23599dea3b02 Mon Sep 17 00:00:00 2001 From: Lizandro Guarnizo <77708265+lizandrogd@users.noreply.github.com> Date: Tue, 7 Jul 2026 23:08:33 -0500 Subject: [PATCH] =?UTF-8?q?feat(turnero):=20gesti=C3=B3n=20de=20dispositiv?= =?UTF-8?q?os/tablets=20en=20configuraci=C3=B3n?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Sección nueva en tab Lugares con tabla CRUD de turnero_dispositivos: IP, nombre, lugar asignado, estado activo/inactivo. API save_dispositivo.php para crear/editar/eliminar. Co-Authored-By: Claude Sonnet 4.6 --- modules/turnero/api/save_dispositivo.php | 44 ++++++ modules/turnero/views/configuracion.php | 189 +++++++++++++++++++++++ 2 files changed, 233 insertions(+) create mode 100644 modules/turnero/api/save_dispositivo.php diff --git a/modules/turnero/api/save_dispositivo.php b/modules/turnero/api/save_dispositivo.php new file mode 100644 index 0000000..46997de --- /dev/null +++ b/modules/turnero/api/save_dispositivo.php @@ -0,0 +1,44 @@ +prepare('DELETE FROM turnero_dispositivos WHERE id = ?')->execute([$id]); + jsonOk([], 'Dispositivo eliminado'); +} + +$ip = trim($input['ip'] ?? ''); +$nombre = trim($input['nombre'] ?? ''); +$lugarId = (int)($input['lugar_id'] ?? 0); +$activo = (int)($input['activo'] ?? 1); + +if ($ip === '') jsonError('La IP es requerida'); +if ($nombre === '') jsonError('El nombre es requerido'); +if ($lugarId <= 0) jsonError('Debes asignar un lugar'); + +if ($id) { + $stmt = db()->prepare( + 'UPDATE turnero_dispositivos SET ip=?, nombre=?, lugar_id=?, activo=? WHERE id=?' + ); + $stmt->execute([$ip, $nombre, $lugarId, $activo, $id]); + jsonOk(['id' => $id], 'Dispositivo actualizado'); +} else { + $stmt = db()->prepare( + 'INSERT INTO turnero_dispositivos (ip, nombre, lugar_id, activo) VALUES (?, ?, ?, ?) + ON DUPLICATE KEY UPDATE nombre=VALUES(nombre), lugar_id=VALUES(lugar_id), activo=VALUES(activo)' + ); + $stmt->execute([$ip, $nombre, $lugarId, $activo]); + jsonOk(['id' => (int)db()->lastInsertId()], 'Dispositivo creado'); +} diff --git a/modules/turnero/views/configuracion.php b/modules/turnero/views/configuracion.php index 42c7425..627c5a3 100644 --- a/modules/turnero/views/configuracion.php +++ b/modules/turnero/views/configuracion.php @@ -40,6 +40,16 @@ try { } } catch (\Throwable $e) { $_loadErrors[] = 'turnero_lugares: ' . $e->getMessage(); $lugarFormIds = []; } +$dispositivos = []; +try { + $dispositivos = $pdo->query( + "SELECT d.*, l.nombre AS lugar_nombre + FROM turnero_dispositivos d + LEFT JOIN turnero_lugares l ON l.id = d.lugar_id + ORDER BY d.activo DESC, l.sort_order ASC, d.nombre ASC" + )->fetchAll(PDO::FETCH_ASSOC); +} catch (\Throwable $e) { $_loadErrors[] = 'turnero_dispositivos: ' . $e->getMessage(); } + try { $prioridades = $pdo->query( 'SELECT * FROM turnero_prioridades ORDER BY orden_peso ASC' @@ -462,6 +472,129 @@ $tab = $_GET['tab'] ?? 'lugares'; + +
+
+ +

Dispositivos / Tablets autorizadas

+ +
+

+ Cada tablet queda bloqueada a su lugar asignado según la IP de red. + Si la IP no aparece aquí, el dispositivo verá todos los lugares disponibles. +

+ +
+ + + + + + + + + + + + + + + + + + + + + + + + +
IPNombre / descripciónLugar asignadoEstado
+ + + + + + +
No hay dispositivos registrados
+
+ +
+

Agregar dispositivo

+
+
+ + +
+
+ + +
+
+ + +
+
+ +
+
+
+ + + + @@ -1147,6 +1280,62 @@ async function eliminarLugar(id, nombre) { } catch (e) { toast('Error de conexión', 'error'); } } +// ───────────────────────────────────────────────────────────── +// DISPOSITIVOS / TABLETS +// ───────────────────────────────────────────────────────────── +async function guardarDispositivo(id = null) { + const pfx = id ? 'edit-dv-' : 'dv-'; + const ip = document.getElementById(pfx + 'ip')?.value.trim(); + const nombre = document.getElementById(pfx + 'nombre')?.value.trim(); + const lugarId = parseInt(document.getElementById(pfx + 'lugar')?.value); + const activo = id ? (document.getElementById('edit-dv-activo')?.checked ? 1 : 0) : 1; + + if (!ip) { toast('La IP es requerida', 'error'); return; } + if (!nombre) { toast('El nombre es requerido', 'error'); return; } + if (!lugarId) { toast('Selecciona un lugar', 'error'); return; } + + try { + const body = { ip, nombre, lugar_id: lugarId, activo }; + if (id) body.id = id; + const res = await fetch(API + 'save_dispositivo.php', { + method: 'POST', headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify(body) + }); + const json = await res.json(); + if (!json.ok) { toast(json.error || 'Error', 'error'); return; } + toast(id ? 'Dispositivo actualizado' : 'Dispositivo creado'); + bootstrap.Modal.getInstance(document.getElementById('modalEditarDispositivo'))?.hide(); + setTimeout(() => location.reload(), 600); + } catch (e) { toast('Error de conexión', 'error'); } +} + +function editarDispositivo(id, ip, nombre, lugarId, activo) { + document.getElementById('edit-dv-id').value = id; + document.getElementById('edit-dv-ip').value = ip; + document.getElementById('edit-dv-nombre').value = nombre; + document.getElementById('edit-dv-lugar').value = lugarId; + document.getElementById('edit-dv-activo').checked = !!activo; + new bootstrap.Modal(document.getElementById('modalEditarDispositivo')).show(); +} + +function guardarEditarDispositivo() { + guardarDispositivo(parseInt(document.getElementById('edit-dv-id').value)); +} + +async function eliminarDispositivo(id, nombre) { + if (!confirm(`¿Eliminar el dispositivo "${nombre}"?`)) return; + try { + const res = await fetch(API + 'save_dispositivo.php', { + method: 'POST', headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ id, _delete: true }) + }); + const json = await res.json(); + if (!json.ok) { toast(json.error || 'Error', 'error'); return; } + toast('Dispositivo eliminado'); + setTimeout(() => location.reload(), 600); + } catch (e) { toast('Error de conexión', 'error'); } +} + // ───────────────────────────────────────────────────────────── // TAB 2: EXÁMENES // ─────────────────────────────────────────────────────────────