From 2abce6d4c240163b6de99d80f705fb775577ad60 Mon Sep 17 00:00:00 2001 From: Lizandro Guarnizo <77708265+lizandrogd@users.noreply.github.com> Date: Tue, 24 Mar 2026 21:51:29 -0500 Subject: [PATCH] =?UTF-8?q?feat:=20multi-archivo=20=C3=B3rdenes=20visible?= =?UTF-8?q?=20en=20enfermero,=20domicilios=20y=20=C3=B3rdenes=20m=C3=A9dic?= =?UTF-8?q?as?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - OrdenMedica::filtrarCampos(): agrega domicilio_id al whitelist - Domicilio::obtener(): consulta secundaria archivos_ordenes[] con fallback - Enfermera::agenda(): bulk-query archivos_ordenes[] por domicilio_id - api/lab/link_ordenes_domicilio.php: API POST vincula orden_ids a domicilio_id - conversations.php guardar(): trackea allOrdenIds[], llama link API post-save - lab_domicilios.php: renderOrdenesDetail() multi-archivo en detalle - enfermero_portal.php: renderOrdenesEnfermero() multi-archivo en cards - lab_ordenes.php: botón 'Ver domicilio' cuando domicilio_id está vinculado - migrations/: ALTER TABLE lab_ordenes_medicas ADD domicilio_id + índice --- api/lab/link_ordenes_domicilio.php | 50 +++++++++++++ classes/lab/Domicilio.php | 20 ++++- classes/lab/Enfermera.php | 48 +++++++++++- classes/lab/OrdenMedica.php | 2 +- conversations.php | 17 ++++- enfermero_portal.php | 46 +++++++----- lab_domicilios.php | 73 ++++++++++++------- lab_ordenes.php | 8 ++ .../20260324_lab_11_ordenes_domicilio_id.sql | 11 +++ .../run_20260324_ordenes_domicilio_id.php | 38 ++++++++++ 10 files changed, 262 insertions(+), 51 deletions(-) create mode 100644 api/lab/link_ordenes_domicilio.php create mode 100644 migrations/20260324_lab_11_ordenes_domicilio_id.sql create mode 100644 migrations/run_20260324_ordenes_domicilio_id.php diff --git a/api/lab/link_ordenes_domicilio.php b/api/lab/link_ordenes_domicilio.php new file mode 100644 index 0000000..753df4f --- /dev/null +++ b/api/lab/link_ordenes_domicilio.php @@ -0,0 +1,50 @@ + false, 'error' => 'Método no permitido']); + exit; +} + +$input = json_decode(file_get_contents('php://input'), true); +if (empty($input['domicilio_id']) || empty($input['orden_ids']) || !is_array($input['orden_ids'])) { + echo json_encode(['success' => false, 'error' => 'domicilio_id y orden_ids[] requeridos']); + exit; +} + +$domicilioId = (int)$input['domicilio_id']; +$ordenIds = array_map('intval', $input['orden_ids']); +$ordenIds = array_filter($ordenIds, fn($v) => $v > 0); + +if (!$domicilioId || empty($ordenIds)) { + echo json_encode(['success' => false, 'error' => 'Valores inválidos']); + exit; +} + +try { + $db = Database::getInstance(); + $placeholders = implode(',', array_fill(0, count($ordenIds), '?')); + $params = array_merge([$domicilioId], array_values($ordenIds)); + + $db->execute( + "UPDATE lab_ordenes_medicas SET domicilio_id = ? WHERE id IN ($placeholders)", + $params + ); + + echo json_encode(['success' => true, 'vinculadas' => count($ordenIds)]); +} catch (Exception $e) { + error_log('[link_ordenes_domicilio] ' . $e->getMessage()); + echo json_encode(['success' => false, 'error' => 'Error interno']); +} diff --git a/classes/lab/Domicilio.php b/classes/lab/Domicilio.php index 967e760..0055d5c 100644 --- a/classes/lab/Domicilio.php +++ b/classes/lab/Domicilio.php @@ -111,7 +111,7 @@ class Domicilio { * Domicilio por ID con todos los datos relacionados. */ public function obtener(int $id): ?array { - return $this->db->fetch(" + $dom = $this->db->fetch(" SELECT d.*, p.nombre_completo AS paciente_nombre, @@ -133,7 +133,23 @@ class Domicilio { LEFT JOIN lab_enfermeras e ON e.id = a.enfermera_id LEFT JOIN admin_users adm ON adm.id = d.creado_por WHERE d.id = ? - ", [$id]) ?: null; + ", [$id]); + if (!$dom) return null; + + // Todos los archivos de órdenes vinculados a este domicilio + $archivos = $this->db->fetchAll( + "SELECT id AS orden_id, local_file + FROM lab_ordenes_medicas + WHERE domicilio_id = ? AND local_file IS NOT NULL + ORDER BY id ASC", + [$id] + ); + // Fallback para domicilios creados antes de la migración (usan orden_id FK) + if (empty($archivos) && !empty($dom['orden_local_file'])) { + $archivos = [['orden_id' => $dom['orden_id'], 'local_file' => $dom['orden_local_file']]]; + } + $dom['archivos_ordenes'] = $archivos; + return $dom; } /** diff --git a/classes/lab/Enfermera.php b/classes/lab/Enfermera.php index 1ab0d37..31204cf 100644 --- a/classes/lab/Enfermera.php +++ b/classes/lab/Enfermera.php @@ -91,7 +91,7 @@ class Enfermera { */ public function agenda(int $enfermeraId, string $fecha = ''): array { $fecha = $fecha ?: date('Y-m-d'); - return $this->db->fetchAll(" + $rows = $this->db->fetchAll(" SELECT d.id AS domicilio_id, d.fecha_programada, @@ -151,6 +151,52 @@ class Enfermera { AND d.fecha_programada = ? ORDER BY d.hora_programada ASC ", [$enfermeraId, $fecha]); + + // Bulk-load todos los archivos de órdenes vinculados a estos domicilios + $domIds = array_values(array_filter(array_column($rows, 'domicilio_id'))); + if (!empty($domIds)) { + $ph = implode(',', array_fill(0, count($domIds), '?')); + $archivos = $this->db->fetchAll( + "SELECT domicilio_id, id AS orden_id, local_file + FROM lab_ordenes_medicas + WHERE domicilio_id IN ($ph) AND local_file IS NOT NULL + ORDER BY id ASC", + $domIds + ); + $archivosPorDom = []; + foreach ($archivos as $a) { + $archivosPorDom[$a['domicilio_id']][] = $a; + } + foreach ($rows as &$row) { + $did = $row['domicilio_id']; + if (!empty($archivosPorDom[$did])) { + $row['archivos_ordenes'] = $archivosPorDom[$did]; + } elseif (!empty($row['orden_local_file'])) { + // Fallback: domicilio creado antes de la migración + $row['archivos_ordenes'] = [[ + 'orden_id' => $row['orden_id_display'] ?? $row['orden_id'], + 'local_file' => $row['orden_local_file'], + ]]; + } else { + $row['archivos_ordenes'] = []; + } + } + unset($row); + } else { + foreach ($rows as &$row) { + if (!empty($row['orden_local_file'])) { + $row['archivos_ordenes'] = [[ + 'orden_id' => $row['orden_id_display'] ?? $row['orden_id'], + 'local_file' => $row['orden_local_file'], + ]]; + } else { + $row['archivos_ordenes'] = []; + } + } + unset($row); + } + + return $rows; } /** diff --git a/classes/lab/OrdenMedica.php b/classes/lab/OrdenMedica.php index e9fa6a9..c5a7660 100644 --- a/classes/lab/OrdenMedica.php +++ b/classes/lab/OrdenMedica.php @@ -306,7 +306,7 @@ class OrdenMedica { private function filtrarCampos(array $datos): array { $permitidos = [ 'paciente_id', 'conversation_id', 'whatsapp_media_id', 'local_file', - 'estado', 'medico_nombre', 'medico_registro', 'fecha_orden', + 'domicilio_id', 'estado', 'medico_nombre', 'medico_registro', 'fecha_orden', 'diagnostico', 'examenes_solicitados', 'requiere_ayuno', 'horas_ayuno', 'indicaciones', 'revisada_por', 'revisada_at', 'autorizada_por', 'autorizada_at', 'comentario_revision', 'notas_admin', diff --git a/conversations.php b/conversations.php index c619528..81bb0da 100644 --- a/conversations.php +++ b/conversations.php @@ -7439,6 +7439,7 @@ const labDomicilio = (() => { try { const convIdNum = (_convId && !String(_convId).startsWith('__user:')) ? parseInt(_convId) : null; + const allOrdenIds = []; for (let _fi = 0; _fi < _localFiles.length; _fi++) { const orRes = await fetch('api/lab/crear_desde_whatsapp.php', { method: 'POST', @@ -7453,10 +7454,12 @@ const labDomicilio = (() => { }), }); const orData = await orRes.json(); - if (orData.success && orData.orden_id && _fi === 0) { - datos.orden_id = orData.orden_id; + if (orData.success && orData.orden_id) { + allOrdenIds.push(orData.orden_id); + if (_fi === 0) datos.orden_id = orData.orden_id; } } + datos._allOrdenIds = allOrdenIds; } catch (_) { /* no bloquea */ } } @@ -7469,6 +7472,16 @@ const labDomicilio = (() => { const d = await r.json(); if (d.success) { + // Vincular todas las órdenes al domicilio recién creado + const _oids = datos._allOrdenIds; + if (Array.isArray(_oids) && _oids.length > 0 && d.id) { + fetch('api/lab/link_ordenes_domicilio.php', { + method: 'POST', + headers: { 'Content-Type': 'application/json' }, + body: JSON.stringify({ domicilio_id: d.id, orden_ids: _oids }), + }).catch(() => {}); + } + // Actualizar _pacNombre si el usuario editó el nombre en el input const _editedNombre = fv('labdom-pac-nombre'); if (_editedNombre) _pacNombre = _editedNombre; diff --git a/enfermero_portal.php b/enfermero_portal.php index b91e5fd..961d9a6 100644 --- a/enfermero_portal.php +++ b/enfermero_portal.php @@ -531,23 +531,8 @@ const portal = { ${cobroHtml} - - ${item.orden_local_file ? ` -
- - Orden - - - - Ver / Descargar orden #${item.orden_id_display || item.orden_id || ''} - -
` : ''} + + ${renderOrdenesEnfermero(item)} ${seSuf ? `
${seSuf}
` : ''} @@ -708,6 +693,33 @@ function resOrden(f) { return `api/lab/descargar_orden.php?file=${encodeURIComponent(f)}`; } +/** + * Renderiza todas las órdenes médicas adjuntas a un domicilio. + * Soporta multi-archivo (archivos_ordenes[]) con fallback a orden_local_file. + */ +function renderOrdenesEnfermero(item) { + const archivos = item.archivos_ordenes || []; + if (!archivos.length) return ''; + return archivos.map(a => { + const lf = a.local_file || ''; + const url = resOrden(lf); + const oid = a.orden_id || ''; + if (!lf) return ''; + const isImg = /\.(jpe?g|png|gif|webp)$/i.test(lf); + return `
+ ${isImg ? `Orden` : ''} + + + Ver / Descargar orden${oid ? ' #' + oid : ''} + +
`; + }).join(''); +} + document.addEventListener('DOMContentLoaded', () => { portal.init(); agendaNueva.init(); diff --git a/lab_domicilios.php b/lab_domicilios.php index 01f6841..9f18f51 100644 --- a/lab_domicilios.php +++ b/lab_domicilios.php @@ -685,34 +685,7 @@ async function verDomicilio(id) { `} - ${dom.orden_id ? ` -
- - ${(() => { - const lf = dom.orden_local_file || ''; - const isImg = /\.(jpe?g|png|gif|webp)$/i.test(lf); - const url = lf ? 'uploads/media/' + encodeURIComponent(lf) : ''; - if (lf && isImg) return ` - - Orden médica - - - Ver ficha orden #${dom.orden_id} - `; - if (lf) return ` - - Abrir / Descargar archivo - - - Ver ficha orden #${dom.orden_id} - `; - return ` - Ver orden #${dom.orden_id} - `; - })()} -
` : ''} + ${renderOrdenesDetail(dom)}
${btns}
@@ -998,6 +971,50 @@ function verHoy() { // ── Utils ────────────────────────────────────────────────────────────────── const esc = s => String(s||'').replace(/[<>&"]/g, c => ({'<':'<','>':'>','&':'&','"':'"'}[c])); + +/** + * Renderiza el bloque de órdenes médicas (multi-archivo) en el detalle de domicilio. + */ +function renderOrdenesDetail(dom) { + const archivos = dom.archivos_ordenes || []; + if (!archivos.length && !dom.orden_id) return ''; + // Si no hay archivos cargados pero sí hay orden_id, mostramos solo el link + if (!archivos.length) { + return `
+ + + Ver orden #${dom.orden_id} + +
`; + } + const items = archivos.map(a => { + const lf = a.local_file || ''; + const isImg = /\.(jpe?g|png|gif|webp)$/i.test(lf); + const url = lf ? ('uploads/media/' + encodeURIComponent(lf)) : ''; + const ficha = ` + Orden #${a.orden_id} + `; + if (lf && isImg) return ` +
+ + Orden médica + ${ficha} +
`; + if (lf) return ` +
+ + Abrir / Descargar archivo + ${ficha} +
`; + return `
${ficha}
`; + }).join(''); + return `
+ + ${items} +
`; +} function mostrarToast(msg, tipo='success') { const div = document.createElement('div'); div.className = `alert alert-${tipo} alert-dismissible position-fixed bottom-0 end-0 m-3`; diff --git a/lab_ordenes.php b/lab_ordenes.php index acbbbac..524e006 100644 --- a/lab_ordenes.php +++ b/lab_ordenes.php @@ -316,6 +316,14 @@ async function verOrden(id) {
${esc(o.indicaciones).replace(/\n/g,'
')}
` : ''} + ${o.domicilio_id ? ` +
+ + + Ver domicilio #${o.domicilio_id} + +
` : ''} + ${o.comentario_revision ? `
diff --git a/migrations/20260324_lab_11_ordenes_domicilio_id.sql b/migrations/20260324_lab_11_ordenes_domicilio_id.sql new file mode 100644 index 0000000..89b7934 --- /dev/null +++ b/migrations/20260324_lab_11_ordenes_domicilio_id.sql @@ -0,0 +1,11 @@ +-- Migración: agrega domicilio_id a lab_ordenes_medicas +-- Permite vincular múltiples órdenes a un mismo domicilio (multi-archivo). +-- Se omite FK para evitar dependencia circular con lab_domicilios ↔ lab_ordenes_medicas. + +ALTER TABLE `lab_ordenes_medicas` + ADD COLUMN `domicilio_id` INT(11) DEFAULT NULL + COMMENT 'FK a lab_domicilios.id — domicilio al que pertenece esta orden/archivo' + AFTER `paciente_id`; + +ALTER TABLE `lab_ordenes_medicas` + ADD INDEX `idx_om_domicilio_id` (`domicilio_id`); diff --git a/migrations/run_20260324_ordenes_domicilio_id.php b/migrations/run_20260324_ordenes_domicilio_id.php new file mode 100644 index 0000000..c71f3c6 --- /dev/null +++ b/migrations/run_20260324_ordenes_domicilio_id.php @@ -0,0 +1,38 @@ +getConnection(); +$ok = 0; +$err = 0; + +$statements = [ + "ALTER TABLE `lab_ordenes_medicas` + ADD COLUMN `domicilio_id` INT(11) DEFAULT NULL + COMMENT 'FK a lab_domicilios.id — domicilio al que pertenece esta orden/archivo' + AFTER `paciente_id`", + "ALTER TABLE `lab_ordenes_medicas` + ADD INDEX `idx_om_domicilio_id` (`domicilio_id`)", +]; + +foreach ($statements as $sql) { + try { + $db->exec($sql); + echo "✅ OK: " . substr(trim($sql), 0, 80) . "\n"; + $ok++; + } catch (PDOException $e) { + // Ignorar si la columna/índice ya existe + if (str_contains($e->getMessage(), 'Duplicate column') || str_contains($e->getMessage(), 'Duplicate key')) { + echo "⚠️ Ya existe (omitido): " . substr(trim($sql), 0, 60) . "\n"; + } else { + echo "❌ ERROR: " . $e->getMessage() . "\n"; + $err++; + } + } +} + +echo "\nMigración completada: {$ok} OK, {$err} errores.\n";