feat(lab_pacientes): columna origen + vista mejorada con stats y filtros
- Migration: columna 'origen' (manual|rips|whatsapp) en lab_pacientes - ingest_paciente.php: guarda origen='rips' en cada paciente importado - Paciente::listar(): filtro por origen + orden por created_at DESC - Paciente::stats(): conteo total/importados/con_wa/manuales - get_pacientes.php: expone ?origen= y ?stats=1 - lab_pacientes.php: cards de stats clicables, badge origen por fila, columna ciudad, columna fecha registro, dropdown filtro por origen Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 4.6
parent
32c166360c
commit
828e323eba
+95
-11
@@ -155,8 +155,37 @@ require_once __DIR__ . '/shared/components/sidebar.php';
|
||||
</header>
|
||||
|
||||
<div class="container-fluid py-3">
|
||||
<!-- Buscador -->
|
||||
<div class="row mb-3">
|
||||
|
||||
<!-- Stats -->
|
||||
<div class="row g-2 mb-3" id="stats-row">
|
||||
<div class="col-6 col-md-3">
|
||||
<div class="card border-0 shadow-sm rounded-3 text-center py-2">
|
||||
<div class="fw-bold fs-4" id="stat-total">—</div>
|
||||
<div class="text-muted small">Total</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-6 col-md-3">
|
||||
<div class="card border-0 shadow-sm rounded-3 text-center py-2" style="cursor:pointer" onclick="filtrarOrigen('rips')">
|
||||
<div class="fw-bold fs-4 text-primary" id="stat-rips">—</div>
|
||||
<div class="text-muted small"><i class="fas fa-database me-1 text-primary"></i>Importados RIPS</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-6 col-md-3">
|
||||
<div class="card border-0 shadow-sm rounded-3 text-center py-2" style="cursor:pointer" onclick="filtrarOrigen('whatsapp')">
|
||||
<div class="fw-bold fs-4 text-success" id="stat-wa">—</div>
|
||||
<div class="text-muted small"><i class="fab fa-whatsapp me-1 text-success"></i>Vinculados WA</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-6 col-md-3">
|
||||
<div class="card border-0 shadow-sm rounded-3 text-center py-2" style="cursor:pointer" onclick="filtrarOrigen('manual')">
|
||||
<div class="fw-bold fs-4 text-secondary" id="stat-manual">—</div>
|
||||
<div class="text-muted small"><i class="fas fa-pen me-1"></i>Manuales</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<!-- Filtros -->
|
||||
<div class="row mb-3 g-2">
|
||||
<div class="col-md-5">
|
||||
<div class="input-group">
|
||||
<span class="input-group-text bg-white border-end-0"><i class="fas fa-search text-muted"></i></span>
|
||||
@@ -165,6 +194,19 @@ require_once __DIR__ . '/shared/components/sidebar.php';
|
||||
oninput="debounce(cargarLista, 380)()">
|
||||
</div>
|
||||
</div>
|
||||
<div class="col-md-3">
|
||||
<select id="filtro-origen" class="form-select" onchange="cargarLista(1)">
|
||||
<option value="">Todos los orígenes</option>
|
||||
<option value="rips">Importados RIPS</option>
|
||||
<option value="manual">Manuales</option>
|
||||
<option value="whatsapp">Desde WhatsApp</option>
|
||||
</select>
|
||||
</div>
|
||||
<div class="col-md-auto ms-auto">
|
||||
<button class="btn btn-sm btn-outline-secondary" onclick="cargarStats()" title="Actualizar stats">
|
||||
<i class="fas fa-sync-alt"></i>
|
||||
</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="row g-3">
|
||||
@@ -178,14 +220,15 @@ require_once __DIR__ . '/shared/components/sidebar.php';
|
||||
<tr>
|
||||
<th class="ps-3">Paciente</th>
|
||||
<th>Documento</th>
|
||||
<th>Teléfono</th>
|
||||
<th>EPS</th>
|
||||
<th>Ciudad</th>
|
||||
<th>Origen</th>
|
||||
<th>Registro</th>
|
||||
<th>Órd.</th>
|
||||
<th></th>
|
||||
</tr>
|
||||
</thead>
|
||||
<tbody id="tabla-body">
|
||||
<tr><td colspan="6" class="text-center py-4 text-muted">
|
||||
<tr><td colspan="7" class="text-center py-4 text-muted">
|
||||
<i class="fas fa-spinner fa-spin me-2"></i>Cargando...
|
||||
</td></tr>
|
||||
</tbody>
|
||||
@@ -367,16 +410,53 @@ require_once __DIR__ . '/shared/components/sidebar.php';
|
||||
let paginaActual = 1;
|
||||
const modal = new bootstrap.Modal('#modalPaciente');
|
||||
|
||||
// ── Stats ──────────────────────────────────────────────────────────────────
|
||||
async function cargarStats() {
|
||||
try {
|
||||
const r = await fetch('api/lab/get_pacientes.php?stats=1');
|
||||
const d = await r.json();
|
||||
const s = d.stats || {};
|
||||
document.getElementById('stat-total').textContent = s.total ?? '—';
|
||||
document.getElementById('stat-rips').textContent = s.importados ?? '—';
|
||||
document.getElementById('stat-wa').textContent = s.con_wa ?? '—';
|
||||
document.getElementById('stat-manual').textContent = s.manuales ?? '—';
|
||||
} catch(_) {}
|
||||
}
|
||||
|
||||
function filtrarOrigen(origen) {
|
||||
const sel = document.getElementById('filtro-origen');
|
||||
sel.value = sel.value === origen ? '' : origen;
|
||||
cargarLista(1);
|
||||
}
|
||||
|
||||
// ── Lista ──────────────────────────────────────────────────────────────────
|
||||
const _origenBadge = {
|
||||
rips: '<span class="badge" style="background:#e3f2fd;color:#1565c0;font-size:.7rem"><i class="fas fa-database me-1"></i>RIPS</span>',
|
||||
whatsapp: '<span class="badge" style="background:#e8f5e9;color:#2e7d32;font-size:.7rem"><i class="fab fa-whatsapp me-1"></i>WA</span>',
|
||||
manual: '<span class="badge bg-secondary bg-opacity-10 text-secondary" style="font-size:.7rem"><i class="fas fa-pen me-1"></i>Manual</span>',
|
||||
};
|
||||
|
||||
function origenBadge(origen) {
|
||||
return _origenBadge[origen] || _origenBadge.manual;
|
||||
}
|
||||
|
||||
function fmtFecha(str) {
|
||||
if (!str) return '—';
|
||||
return str.slice(0, 10).split('-').reverse().join('/');
|
||||
}
|
||||
|
||||
async function cargarLista(pag = 1) {
|
||||
paginaActual = pag;
|
||||
const busq = document.getElementById('buscador').value.trim();
|
||||
const r = await fetch(`api/lab/get_pacientes.php?busqueda=${encodeURIComponent(busq)}&page=${pag}&limit=25`);
|
||||
const busq = document.getElementById('buscador').value.trim();
|
||||
const origen = document.getElementById('filtro-origen').value;
|
||||
const r = await fetch(
|
||||
`api/lab/get_pacientes.php?busqueda=${encodeURIComponent(busq)}&page=${pag}&limit=25&origen=${encodeURIComponent(origen)}`
|
||||
);
|
||||
const d = await r.json();
|
||||
|
||||
const tbody = document.getElementById('tabla-body');
|
||||
if (!d.data?.length) {
|
||||
tbody.innerHTML = '<tr><td colspan="6" class="text-center py-4 text-muted">Sin resultados</td></tr>';
|
||||
tbody.innerHTML = '<tr><td colspan="7" class="text-center py-4 text-muted">Sin resultados</td></tr>';
|
||||
document.getElementById('paginacion').innerHTML = '';
|
||||
return;
|
||||
}
|
||||
@@ -385,11 +465,12 @@ async function cargarLista(pag = 1) {
|
||||
<tr style="cursor:pointer" onclick="verDetalle(${p.id})">
|
||||
<td class="ps-3">
|
||||
<div class="fw-semibold">${esc(p.nombre_completo)}</div>
|
||||
${p.phone_number ? `<small class="text-muted"><i class="fab fa-whatsapp text-success"></i> ${esc(p.phone_number)}</small>` : ''}
|
||||
${p.telefono ? `<small class="text-muted">${esc(p.telefono)}</small>` : ''}
|
||||
</td>
|
||||
<td class="small">${tipoDocLabel(p.tipo_documento)} ${esc(p.numero_documento||'—')}</td>
|
||||
<td class="small">${esc(p.telefono||'—')}</td>
|
||||
<td class="small">${esc(p.eps||'—')}</td>
|
||||
<td class="small text-muted">${esc(p.ciudad||'—')}</td>
|
||||
<td>${origenBadge(p.origen)}</td>
|
||||
<td class="small text-muted">${fmtFecha(p.created_at)}</td>
|
||||
<td><span class="badge bg-primary hist-badge">${p.total_ordenes||0}</span></td>
|
||||
<td>
|
||||
<button class="btn btn-sm btn-outline-secondary py-1 px-2"
|
||||
@@ -436,6 +517,8 @@ async function verDetalle(id) {
|
||||
<dt class="col-5 text-muted">Documento</dt><dd class="col-7">${tipoDocLabel(p.tipo_documento)} ${esc(p.numero_documento||'—')}</dd>
|
||||
<dt class="col-5 text-muted">Teléfono</dt><dd class="col-7">${esc(p.telefono||'—')}</dd>
|
||||
<dt class="col-5 text-muted">WhatsApp</dt><dd class="col-7">${p.phone_number ? `<i class="fab fa-whatsapp text-success"></i> ${esc(p.phone_number)}` : '—'}</dd>
|
||||
<dt class="col-5 text-muted">Origen</dt><dd class="col-7">${origenBadge(p.origen)}</dd>
|
||||
<dt class="col-5 text-muted">Registro</dt><dd class="col-7 text-muted small">${fmtFecha(p.created_at)}</dd>
|
||||
${p.email ? `<dt class="col-5 text-muted">Email</dt><dd class="col-7">${esc(p.email)}</dd>` : ''}
|
||||
${p.fecha_nacimiento ? `<dt class="col-5 text-muted">Nacimiento</dt><dd class="col-7">${esc(p.fecha_nacimiento.slice(0,10))}</dd>` : ''}
|
||||
${p.genero ? `<dt class="col-5 text-muted">Género</dt><dd class="col-7">${p.genero==='M'?'Masculino':p.genero==='F'?'Femenino':'Otro'}</dd>` : ''}
|
||||
@@ -628,6 +711,7 @@ function mostrarToast(msg, tipo = 'success') {
|
||||
}
|
||||
|
||||
cargarLista();
|
||||
cargarStats();
|
||||
</script>
|
||||
<script src="assets/js/lab-sidebar.js"></script>
|
||||
</body>
|
||||
|
||||
Reference in New Issue
Block a user