From 4cb1a135b3ed63c4e4cc8a1c2efdd3f6d3c3e2a2 Mon Sep 17 00:00:00 2001 From: Lizandro Guarnizo <77708265+lizandrogd@users.noreply.github.com> Date: Thu, 16 Jul 2026 20:36:59 -0500 Subject: [PATCH] feat: CRUD de ciudades + select en modal de paciente MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - Tabla lab_ciudades creada y poblada con 86 ciudades existentes - Módulo lab_ciudades: listado, crear, editar, activar/desactivar, eliminar - API /api/lab/ciudades.php: GET list, POST save/toggle/delete - Modal paciente: campo ciudad cambiado a select, cargado junto con EPS en un solo Promise.all al iniciar; Cúcuta preseleccionada por defecto Co-Authored-By: Claude Sonnet 4.6 --- api/lab/ciudades.php | 64 ++++++++++++ modules/lab_ciudades/module.php | 12 +++ modules/lab_ciudades/views/index.php | 149 +++++++++++++++++++++++++++ modules/turnero/views/recepcion.php | 33 ++++-- 4 files changed, 249 insertions(+), 9 deletions(-) create mode 100644 api/lab/ciudades.php create mode 100644 modules/lab_ciudades/module.php create mode 100644 modules/lab_ciudades/views/index.php diff --git a/api/lab/ciudades.php b/api/lab/ciudades.php new file mode 100644 index 0000000..6b0b082 --- /dev/null +++ b/api/lab/ciudades.php @@ -0,0 +1,64 @@ +query($sql)->fetchAll(PDO::FETCH_ASSOC); + jsonOk(['ciudades' => $rows]); +} + +requireMethod('POST'); +requireAdmin(); + +$body = inputJson(); +$action = trim($body['action'] ?? ''); + +if ($action === 'save') { + $nombre = trim($body['nombre'] ?? ''); + $id = (int)($body['id'] ?? 0); + if (!$nombre) jsonError('nombre requerido.'); + if (strlen($nombre) > 100) jsonError('nombre demasiado largo (máx 100).'); + + if ($id) { + $pdo->prepare('UPDATE lab_ciudades SET nombre = ? WHERE id = ?')->execute([$nombre, $id]); + jsonOk(['id' => $id], 'Ciudad actualizada.'); + } else { + $st = $pdo->prepare('INSERT INTO lab_ciudades (nombre) VALUES (?)'); + try { + $st->execute([$nombre]); + } catch (\PDOException $e) { + if ($e->getCode() == 23000) jsonError('Ya existe una ciudad con ese nombre.'); + throw $e; + } + jsonOk(['id' => (int)$pdo->lastInsertId()], 'Ciudad creada.'); + } +} + +if ($action === 'toggle') { + $id = (int)($body['id'] ?? 0); + if (!$id) jsonError('id requerido.'); + $pdo->prepare('UPDATE lab_ciudades SET activa = NOT activa WHERE id = ?')->execute([$id]); + jsonOk([], 'Estado actualizado.'); +} + +if ($action === 'delete') { + $id = (int)($body['id'] ?? 0); + if (!$id) jsonError('id requerido.'); + $uso = $pdo->prepare('SELECT COUNT(*) FROM lab_pacientes WHERE ciudad = (SELECT nombre FROM lab_ciudades WHERE id = ?)'); + $uso->execute([$id]); + if ((int)$uso->fetchColumn() > 0) jsonError('No se puede eliminar: hay pacientes con esta ciudad. Desactívela en su lugar.'); + $pdo->prepare('DELETE FROM lab_ciudades WHERE id = ?')->execute([$id]); + jsonOk([], 'Ciudad eliminada.'); +} + +jsonError('action inválida.'); diff --git a/modules/lab_ciudades/module.php b/modules/lab_ciudades/module.php new file mode 100644 index 0000000..64c1cf9 --- /dev/null +++ b/modules/lab_ciudades/module.php @@ -0,0 +1,12 @@ + 'lab_ciudades', + 'name' => 'Ciudades', + 'icon' => 'fas fa-map-marker-alt', + 'category' => 'lab', + 'route' => '/lab_ciudades.php', + 'is_active' => true, + 'sort_order' => 23, + 'oleada' => 0, + 'description' => 'Gestión de ciudades de pacientes', + 'links' => [['name' => 'Ciudades', 'icon' => 'fas fa-map-marker-alt', 'route' => '/lab_ciudades.php']], +]; diff --git a/modules/lab_ciudades/views/index.php b/modules/lab_ciudades/views/index.php new file mode 100644 index 0000000..4007c17 --- /dev/null +++ b/modules/lab_ciudades/views/index.php @@ -0,0 +1,149 @@ + + + + + +Ciudades — <?= htmlspecialchars($_cfg['empresa_nombre'] ?? 'ERP') ?> + + + + + + + + +
+
+
Ciudades
+ +
+ +
+
+ + + + + + + + + + + +
NombreEstadoAcciones
Cargando…
+
+
+
+ + + + + + + diff --git a/modules/turnero/views/recepcion.php b/modules/turnero/views/recepcion.php index 1bc9bb5..34ef4c3 100644 --- a/modules/turnero/views/recepcion.php +++ b/modules/turnero/views/recepcion.php @@ -1008,7 +1008,9 @@ document.addEventListener('DOMContentLoaded', function() {
- +
@@ -2326,17 +2328,29 @@ function fmt(n) { const BASE_URL_API_LAB = '/api/lab/'; -// ── Cargar EPS una vez al inicio ───────────────────────────── +// ── Cargar EPS y ciudades una vez al inicio ─────────────────── (async function() { try { - const r = await fetch(BASE_URL_API_LAB + 'eps.php?action=list&solo_activas=1'); - const j = await r.json(); - if (!j.ok) return; - const sel = document.getElementById('mpac-eps'); - (j.eps || []).forEach(e => { + const [rEps, rCiu] = await Promise.all([ + fetch(BASE_URL_API_LAB + 'eps.php?action=list&solo_activas=1'), + fetch(BASE_URL_API_LAB + 'ciudades.php?action=list&solo_activas=1'), + ]); + const [jEps, jCiu] = await Promise.all([rEps.json(), rCiu.json()]); + + const selEps = document.getElementById('mpac-eps'); + (jEps.eps || []).forEach(e => { const o = document.createElement('option'); o.value = e.nombre; o.textContent = e.nombre; - sel.appendChild(o); + selEps.appendChild(o); + }); + + const selCiu = document.getElementById('mpac-ciudad'); + selCiu.innerHTML = ''; + (jCiu.ciudades || []).forEach(c => { + const o = document.createElement('option'); + o.value = c.nombre; o.textContent = c.nombre; + if (c.nombre === 'Cúcuta') o.selected = true; + selCiu.appendChild(o); }); } catch(_) {} })(); @@ -2572,7 +2586,8 @@ async function abrirModalPaciente(id, nombrePrefill) { document.getElementById('mpac-avatar').textContent = '?'; document.getElementById('mpac-wa-badge').innerHTML = ''; document.getElementById('mpac-user-id').value = ''; - document.getElementById('mpac-ciudad').value = 'Cúcuta'; + document.getElementById('mpac-ciudad').value = 'Cúcuta'; + document.getElementById('mpac-eps').value = ''; if (id) { document.getElementById('mpac-titulo').textContent = 'Cargando…';