feat: accordion por hora, activo/finalizado, notas en detalle e informe domicilio

- enfermero_portal: cards colapsables con toggle en cabecera, ordenadas por hora_programada
- enfermero_portal: sección 'Finalizados' colapsada abajo para completado/cancelado
- enfermero_portal: acudiente en ficha clínica solo si fecha nacimiento confirma menor de edad
- lab_domicilios: notas del enfermero read-only en panel de detalle (cargarNotasDetalle)
- lab_domicilios: botón 'Informe' abre modal con datos del paciente + ficha clínica + notas libres
This commit is contained in:
Lizandro Guarnizo
2026-03-25 14:06:42 -05:00
parent 5f695b5604
commit da640680d3
2 changed files with 230 additions and 14 deletions
+164
View File
@@ -140,6 +140,10 @@ $domIdParam = (int)($_GET['id'] ?? 0);
<div class="card-header bg-white border-0 d-flex justify-content-between align-items-center">
<h6 class="mb-0 fw-semibold">Detalle <span id="detail-id" class="text-muted"></span></h6>
<div class="d-flex gap-2">
<button class="btn btn-sm btn-outline-success d-none" id="btn-informe-dom"
onclick="verInformeDom()">
<i class="fas fa-file-medical me-1"></i>Informe
</button>
<button class="btn btn-sm btn-outline-primary d-none" id="btn-editar-dom"
onclick="editarDomicilio(domSeleccionado)">
<i class="fas fa-edit me-1"></i>Editar
@@ -412,6 +416,52 @@ $domIdParam = (int)($_GET['id'] ?? 0);
</div>
</div>
<!-- Modal Informe Domicilio -->
<div class="modal fade" id="modalInformeDom" tabindex="-1">
<div class="modal-dialog modal-lg">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title"><i class="fas fa-file-medical me-2 text-success"></i>Informe de domicilio</h5>
<div class="d-flex gap-2">
<button class="btn btn-sm btn-outline-secondary" onclick="window.print()">
<i class="fas fa-print me-1"></i>Imprimir
</button>
<button class="btn-close" data-bs-dismiss="modal"></button>
</div>
</div>
<div class="modal-body" id="informe-body">
<!-- Datos del paciente -->
<h6 class="text-muted text-uppercase mb-2"><i class="fas fa-user me-1"></i>Datos del paciente</h6>
<dl class="row small mb-3">
<dt class="col-4">Nombre</dt> <dd class="col-8 fw-semibold" id="inf-nombre"></dd>
<dt class="col-4">Tipo doc.</dt> <dd class="col-8" id="inf-tipodoc"></dd>
<dt class="col-4">Nº documento</dt> <dd class="col-8" id="inf-numdoc"></dd>
<dt class="col-4">Teléfono</dt> <dd class="col-8" id="inf-telefono"></dd>
<dt class="col-4">Correo</dt> <dd class="col-8" id="inf-correo"></dd>
<dt class="col-4">Dirección</dt> <dd class="col-8" id="inf-direccion"></dd>
<dt class="col-4">Seguro/tipo</dt> <dd class="col-8" id="inf-seguro"></dd>
<dt class="col-4">Fecha servicio</dt><dd class="col-8" id="inf-fecha"></dd>
<dt class="col-4">Exámenes</dt> <dd class="col-8" id="inf-examenes"></dd>
</dl>
<hr>
<!-- Ficha clínica -->
<h6 class="text-muted text-uppercase mb-2"><i class="fas fa-clipboard-list me-1"></i>Ficha clínica</h6>
<dl class="row small mb-3">
<dt class="col-4">Antecedentes</dt> <dd class="col-8" id="inf-antecedentes"></dd>
<dt class="col-4">Medicamentos</dt> <dd class="col-8" id="inf-medicamentos"></dd>
<dt class="col-4 d-none" id="inf-fila-acudiente">Acudiente</dt>
<dd class="col-8 d-none" id="inf-acudiente"></dd>
</dl>
<hr>
<!-- Notas libres -->
<h6 class="text-muted text-uppercase mb-2"><i class="fas fa-sticky-note me-1"></i>Notas del enfermero</h6>
<div id="inf-notas-libres"></div>
</div>
</div>
</div>
</div>
<!-- Modal Asignar / Reasignar Enfermero -->
<div class="modal fade" id="modalAsignarEnf" tabindex="-1">
<div class="modal-dialog">
@@ -534,6 +584,7 @@ function domQuitarOrden() {
}
const COLOR_DOM = { programado:'secondary', confirmado:'info', en_camino:'primary', en_domicilio:'warning', completado:'success', cancelado:'danger', reprogramado:'dark' };
let domSeleccionado = <?= $domIdParam ?: 'null' ?>;
let _domActual = null; // objeto completo del domicilio activo en el panel de detalle
let paginaActual = 1;
let _listaToken = 0; // anti race-condition
@@ -643,11 +694,13 @@ async function verDomicilio(id) {
document.getElementById('detail-id').textContent = `#${id}`;
document.getElementById('detail-body').innerHTML = '<div class="text-center py-3"><i class="fas fa-spinner fa-spin"></i></div>';
document.getElementById('btn-editar-dom').classList.remove('d-none');
document.getElementById('btn-informe-dom').classList.remove('d-none');
const r = await fetch(`api/lab/get_domicilios.php?id=${id}&no_stats=1`);
const d = await r.json();
const dom = d.domicilio;
if (!dom) return;
_domActual = dom;
// Botones de estado
const SIGUIENTES = {
@@ -758,16 +811,127 @@ async function verDomicilio(id) {
</div>
</div>` : ''}
`;
cargarNotasDetalle(id);
}
function cerrarDetalle() {
domSeleccionado = null;
_domActual = null;
document.querySelectorAll('.dom-row').forEach(r => r.classList.remove('table-active'));
document.getElementById('detail-id').textContent = '';
document.getElementById('btn-editar-dom').classList.add('d-none');
document.getElementById('btn-informe-dom').classList.add('d-none');
document.getElementById('detail-body').innerHTML = '<div class="text-center py-5 text-muted"><i class="fas fa-arrow-left me-2"></i>Selecciona un domicilio</div>';
}
// ── Notas del enfermero (read-only en detalle) ────────────────────────────
async function cargarNotasDetalle(domId) {
try {
const r = await fetch(`api/lab/get_notas_domicilio.php?domicilio_id=${domId}`);
const d = await r.json();
if (!d.success) return;
const c = d.clinica || {};
const libres = d.libres || [];
const hayFicha = c.antecedentes || c.medicamentos || c.acudiente_nombre;
const hayLibres = libres.length > 0;
if (!hayFicha && !hayLibres) return;
let html = `<div class="row mt-3" id="notas-enfermero-bloque">
<div class="col-12">
<hr class="my-2">
<label class="form-label small text-muted text-uppercase">
<i class="fas fa-notes-medical me-1"></i>Notas del enfermero
</label>`;
if (hayFicha) {
html += `<div class="p-2 rounded mb-2" style="background:#fff7ed;border:1px solid #fdba74">
<div class="fw-semibold small mb-1" style="color:#9a3412">
<i class="fas fa-clipboard-list me-1"></i>Ficha clínica
</div>`;
if (c.antecedentes) html += `<div class="small mb-1"><strong>Antecedentes:</strong> ${esc(c.antecedentes)}</div>`;
if (c.medicamentos) html += `<div class="small mb-1"><strong>Medicamentos:</strong> ${esc(c.medicamentos)}</div>`;
if (c.acudiente_nombre) html += `<div class="small mb-1"><strong>Acudiente:</strong> ${esc(c.acudiente_nombre)}${c.acudiente_documento ? ' · ' + esc(c.acudiente_documento) : ''}</div>`;
html += `</div>`;
}
libres.forEach(n => {
html += `<div class="p-2 rounded mb-2" style="background:#f8fafc;border:1px solid #e2e8f0">
<div class="fw-semibold small">${esc(n.titulo||'Sin título')}</div>
<div class="small mt-1" style="white-space:pre-line">${n.cuerpo||''}</div>
${n.imagen_path ? `<img src="${esc(n.imagen_path)}" class="img-fluid rounded mt-1" style="max-height:160px">` : ''}
<div class="text-muted mt-1" style="font-size:.7rem">${esc(n.created_at||'')}</div>
</div>`;
});
html += `</div></div>`;
document.getElementById('detail-body').insertAdjacentHTML('beforeend', html);
} catch(e) { /* silencioso */ }
}
// ── Informe del domicilio ─────────────────────────────────────────────────
async function verInformeDom() {
if (!_domActual) return;
const dom = _domActual;
// Llenar datos básicos del paciente
document.getElementById('inf-nombre').textContent = dom.paciente_nombre || '—';
document.getElementById('inf-tipodoc').textContent = dom.paciente_tipo_documento || '—';
document.getElementById('inf-numdoc').textContent = dom.paciente_numero_documento || '—';
document.getElementById('inf-telefono').textContent= dom.paciente_telefono || '—';
document.getElementById('inf-correo').textContent = dom.paciente_email || '—';
document.getElementById('inf-direccion').textContent = (dom.direccion||'') + (dom.barrio ? ', ' + dom.barrio : '');
document.getElementById('inf-seguro').textContent = dom.tipo_cliente === 'seguro'
? ('Seguro' + (dom.seguro_nombre ? ' — ' + dom.seguro_nombre : ''))
: 'Particular';
document.getElementById('inf-examenes').textContent = dom.examenes_solicitados || '—';
document.getElementById('inf-fecha').textContent = (dom.fecha_programada||'') + ' ' + (dom.hora_programada||'');
// Limpiar campos clínicos mientras cargamos
['inf-antecedentes','inf-medicamentos','inf-acudiente','inf-notas-libres'].forEach(id => {
const el = document.getElementById(id);
if (el) el.innerHTML = '<em class="text-muted">Cargando...</em>';
});
const _modalInforme = bootstrap.Modal.getOrCreateInstance('#modalInformeDom');
_modalInforme.show();
try {
const r = await fetch(`api/lab/get_notas_domicilio.php?domicilio_id=${dom.id}`);
const d = await r.json();
const c = d.success ? (d.clinica||{}) : {};
const libres = d.success ? (d.libres||[]) : [];
document.getElementById('inf-antecedentes').textContent = c.antecedentes || '—';
document.getElementById('inf-medicamentos').textContent = c.medicamentos || '—';
if (c.acudiente_nombre) {
document.getElementById('inf-acudiente').innerHTML =
`<strong>${esc(c.acudiente_nombre)}</strong> · ${esc(c.acudiente_documento||'')}`;
document.getElementById('inf-fila-acudiente').classList.remove('d-none');
} else {
document.getElementById('inf-fila-acudiente').classList.add('d-none');
document.getElementById('inf-acudiente').textContent = '';
}
if (libres.length) {
document.getElementById('inf-notas-libres').innerHTML = libres.map(n =>
`<div class="mb-2 p-2 rounded" style="background:#f8fafc;border:1px solid #e2e8f0">
<div class="fw-semibold small">${esc(n.titulo||'Sin título')}</div>
<div class="small mt-1" style="white-space:pre-line">${n.cuerpo||''}</div>
${n.imagen_path ? `<img src="${esc(n.imagen_path)}" class="img-fluid rounded mt-1" style="max-height:120px">` : ''}
</div>`
).join('');
} else {
document.getElementById('inf-notas-libres').textContent = '—';
}
} catch(e) {
document.getElementById('inf-antecedentes').textContent = '—';
document.getElementById('inf-medicamentos').textContent = '—';
document.getElementById('inf-notas-libres').textContent = '—';
}
}
// ──────────────────────────────────────────────────────────────────────────
// ── Asignación rápida ──────────────────────────────────────────────────────
async function mostrarAsignacion(domId) {
document.getElementById('asig-dom-id').value = domId;