This commit is contained in:
Lizandro Guarnizo
2026-03-16 21:00:39 -05:00
parent 79bcc5e4bf
commit fe1d3c31b3
4 changed files with 255 additions and 9 deletions
+1
View File
@@ -16,6 +16,7 @@ try {
if (!empty($_GET['formulario_id'])) $filtros['formulario_id'] = (int)$_GET['formulario_id'];
if (!empty($_GET['paciente_id'])) $filtros['paciente_id'] = (int)$_GET['paciente_id'];
if (!empty($_GET['estado'])) $filtros['estado'] = $_GET['estado'];
if (!empty($_GET['fecha_desde'])) $filtros['fecha_desde'] = $_GET['fecha_desde'];
// Enfermero solo ve los suyos
if (userRole() === 'enfermero') {
$filtros['enviado_por'] = adminId();
+2 -1
View File
@@ -120,7 +120,7 @@ class Formulario {
public function obtenerPorToken(string $token): ?array {
$envio = $this->db->fetch(
"SELECT e.*, f.nombre AS form_nombre, f.esquema, f.permite_firma, f.requiere_firma, f.descripcion AS form_descripcion,
f.doc_color, f.doc_logo_base64, f.doc_encabezado, f.doc_subtitulo, f.doc_pie_pagina,
f.doc_color, f.doc_logo_base64, f.doc_encabezado, f.doc_subtitulo, f.doc_pie_pagina, f.firma_modos,
p.nombre_completo AS paciente_nombre, p.numero_documento, p.telefono AS paciente_telefono
FROM lab_form_envios e
JOIN lab_formularios f ON f.id = e.formulario_id
@@ -180,6 +180,7 @@ class Formulario {
if (!empty($filtros['paciente_id'])) { $where[] = 'e.paciente_id = ?'; $params[] = $filtros['paciente_id']; }
if (!empty($filtros['enviado_por'])) { $where[] = 'e.enviado_por = ?'; $params[] = $filtros['enviado_por']; }
if (!empty($filtros['estado'])) { $where[] = 'e.estado = ?'; $params[] = $filtros['estado']; }
if (!empty($filtros['fecha_desde'])) { $where[] = 'DATE(e.created_at) >= ?'; $params[] = $filtros['fecha_desde']; }
$w = implode(' AND ', $where);
return $this->db->fetchAll("
SELECT e.*, f.nombre AS form_nombre, f.categoria, f.esquema,
+10 -5
View File
@@ -369,9 +369,10 @@ const firma = (() => {
let _fotoDato = null;
// setModos: controla qué secciones se muestran (sin tabs exclusivos)
// setModos: controla qué secciones se muestran
// modos puede ser un array ['canvas','foto'] o null
function setModos(modos) {
const m = Array.isArray(modos) && modos.length ? modos : ['canvas','foto'];
const m = Array.isArray(modos) && modos.length ? modos : ['canvas'];
$('firma-canvas-area').style.display = m.includes('canvas') ? '' : 'none';
$('firma-foto-area').style.display = m.includes('foto') ? '' : 'none';
}
@@ -691,9 +692,13 @@ const formCliente = {
if (form.permite_firma) {
show('firma-container');
if (form.requiere_firma) show('firma-req-badge');
// Configurar modos según el campo firma del esquema
const firmaCampo = form.esquema_decoded.find(c => c.tipo === 'firma');
firma.setModos(firmaCampo?.modos);
// Configurar modos de firma: primero columna firma_modos, luego campo en esquema
const firmaCampo = form.esquema_decoded.find(c => c.tipo === 'firma');
const fModosStr = form.firma_modos || '';
const firmaModosArr = fModosStr
? fModosStr.split(',').map(m => m.trim()).filter(Boolean)
: (firmaCampo?.modos ?? null);
firma.setModos(firmaModosArr);
}
hide('loading-screen');
+242 -3
View File
@@ -56,15 +56,31 @@ $domIdParam = (int)($_GET['id'] ?? 0);
<main class="main-content">
<header class="content-header d-flex align-items-center justify-content-between">
<div>
<h1><i class="fas fa-house-medical text-primary"></i> Domicilios</h1>
<h1 id="header-titulo"><i class="fas fa-house-medical text-primary"></i> Domicilios</h1>
<small class="text-muted"><?= htmlspecialchars($adminNombre) ?></small>
</div>
<button class="btn btn-primary btn-sm" onclick="abrirFormulario()">
<button class="btn btn-primary btn-sm" id="btn-nuevo-domicilio" onclick="abrirFormulario()">
<i class="fas fa-plus me-1"></i> Nuevo Domicilio
</button>
</header>
<div class="container-fluid py-3">
<!-- Tabs -->
<div class="container-fluid pt-2 pb-0">
<ul class="nav nav-tabs border-bottom-0">
<li class="nav-item">
<a class="nav-link active" id="tab-btn-domicilios" href="#" onclick="switchTab('domicilios',this);return false;">
<i class="fas fa-house-medical me-1"></i>Domicilios
</a>
</li>
<li class="nav-item">
<a class="nav-link" id="tab-btn-formularios" href="#" onclick="switchTab('formularios',this);return false;">
<i class="fas fa-wpforms me-1"></i>Formularios recibidos
</a>
</li>
</ul>
</div>
<div class="container-fluid py-3" id="seccion-domicilios">
<!-- Filtros -->
<div class="row g-2 mb-3">
<div class="col-md-2">
@@ -135,6 +151,59 @@ $domIdParam = (int)($_GET['id'] ?? 0);
</div>
</div>
</div>
<!-- ═══════════ SECCIÓN FORMULARIOS RECIBIDOS ═══════════ -->
<div class="container-fluid py-3" id="seccion-formularios" style="display:none">
<!-- Filtros -->
<div class="row g-2 mb-3">
<div class="col-md-3">
<select id="ff-plantilla" class="form-select form-select-sm" onchange="cargarFormulariosEnviados()">
<option value="">Todos los formularios</option>
</select>
</div>
<div class="col-md-2">
<select id="ff-estado" class="form-select form-select-sm" onchange="cargarFormulariosEnviados()">
<option value="">Todos los estados</option>
<option value="pendiente">⏳ Pendiente</option>
<option value="completado">✅ Completado</option>
<option value="firmado">✍️ Firmado</option>
<option value="expirado">❌ Expirado</option>
</select>
</div>
<div class="col-md-2">
<input type="date" id="ff-fecha-desde" class="form-control form-control-sm"
placeholder="Desde" onchange="cargarFormulariosEnviados()">
</div>
<div class="col-md-1">
<button class="btn btn-sm btn-secondary w-100" onclick="document.getElementById('ff-fecha-desde').value='';cargarFormulariosEnviados()">
<i class="fas fa-times"></i>
</button>
</div>
</div>
<div class="card border-0 shadow-sm">
<div class="card-body p-0">
<div class="table-responsive">
<table class="table table-hover mb-0">
<thead class="table-light">
<tr>
<th>Fecha</th>
<th>Paciente</th>
<th>Formulario</th>
<th>Enviado por</th>
<th>Estado</th>
<th></th>
</tr>
</thead>
<tbody id="ff-tbody">
<tr><td colspan="6" class="text-center py-4 text-muted"><i class="fas fa-spinner fa-spin"></i></td></tr>
</tbody>
</table>
</div>
<div class="px-3 py-2 border-top text-muted small" id="ff-total"></div>
</div>
</div>
</div>
</main>
<!-- Modal Formulario (Nuevo / Editar) -->
@@ -305,9 +374,31 @@ $domIdParam = (int)($_GET['id'] ?? 0);
</div>
</div>
<!-- Modal Ver Respuesta de Formulario -->
<div class="modal fade" id="modalRespuestaForm" tabindex="-1">
<div class="modal-dialog modal-lg modal-dialog-scrollable">
<div class="modal-content">
<div class="modal-header">
<h5 class="modal-title"><i class="fas fa-wpforms me-2"></i>Formulario diligenciado</h5>
<button class="btn-close" data-bs-dismiss="modal"></button>
</div>
<div class="modal-body" id="resp-form-body">
<div class="text-center py-3"><i class="fas fa-spinner fa-spin"></i></div>
</div>
<div class="modal-footer">
<a href="#" id="btn-resp-pdf" target="_blank" class="btn btn-outline-secondary btn-sm">
<i class="fas fa-print me-1"></i>Ver / Imprimir PDF
</a>
<button class="btn btn-secondary btn-sm" data-bs-dismiss="modal">Cerrar</button>
</div>
</div>
</div>
</div>
<script src="https://cdn.jsdelivr.net/npm/bootstrap@5.3.0/dist/js/bootstrap.bundle.min.js"></script>
<script>
const modal = new bootstrap.Modal('#modalDomicilio');
let _modalRespForm = null;
// ── Estado orden en el modal ───────────────────────────────────────────────
let _domOrderFile = null; // File pendiente de subir
@@ -849,6 +940,154 @@ function mostrarToast(msg, tipo='success') {
document.body.appendChild(div);
setTimeout(() => div.remove(), 4000);
}
// ══════════════════════════════════════════════════════════════════════
// TABS
// ══════════════════════════════════════════════════════════════════════
function switchTab(tab, linkEl) {
// Actualizar estilos de tabs
document.querySelectorAll('.nav-tabs .nav-link').forEach(a => a.classList.remove('active'));
linkEl.classList.add('active');
const esFormularios = tab === 'formularios';
document.getElementById('seccion-domicilios').style.display = esFormularios ? 'none' : '';
document.getElementById('seccion-formularios').style.display = esFormularios ? '' : 'none';
document.getElementById('btn-nuevo-domicilio').style.display = esFormularios ? 'none' : '';
if (esFormularios) {
cargarPlantillasFF();
cargarFormulariosEnviados();
}
}
// ══════════════════════════════════════════════════════════════════════
// FORMULARIOS RECIBIDOS
// ══════════════════════════════════════════════════════════════════════
let _ffData = [];
let _ffPlantillasCargadas = false;
async function cargarPlantillasFF() {
if (_ffPlantillasCargadas) return;
_ffPlantillasCargadas = true;
const r = await fetch('api/lab/get_formularios.php');
const d = await r.json();
const sel = document.getElementById('ff-plantilla');
(d.data || []).forEach(f => {
sel.insertAdjacentHTML('beforeend', `<option value="${f.id}">${esc(f.nombre)}</option>`);
});
}
async function cargarFormulariosEnviados() {
const tbody = document.getElementById('ff-tbody');
tbody.innerHTML = '<tr><td colspan="6" class="text-center py-3 text-muted"><i class="fas fa-spinner fa-spin"></i></td></tr>';
const params = new URLSearchParams({ envios: 1 });
const estado = document.getElementById('ff-estado').value;
const plantilla = document.getElementById('ff-plantilla').value;
const fechaDesde = document.getElementById('ff-fecha-desde').value;
if (estado) params.set('estado', estado);
if (plantilla) params.set('formulario_id', plantilla);
if (fechaDesde) params.set('fecha_desde', fechaDesde);
const r = await fetch(`api/lab/get_formularios.php?${params}`);
const d = await r.json();
_ffData = d.data || [];
document.getElementById('ff-total').textContent = `${_ffData.length} registro(s)`;
if (!_ffData.length) {
tbody.innerHTML = '<tr><td colspan="6" class="text-center py-4 text-muted">Sin registros</td></tr>';
return;
}
const ESTADO_BADGE = {
pendiente: 'badge bg-warning text-dark',
completado: 'badge bg-success',
firmado: 'badge bg-primary',
expirado: 'badge bg-secondary',
};
const ESTADO_ICON = { pendiente:'⏳', completado:'✅', firmado:'✍️', expirado:'❌' };
tbody.innerHTML = _ffData.map(e => {
const fecha = (e.created_at || '').slice(0, 16).replace('T', ' ');
const estadoBadge = ESTADO_BADGE[e.estado] || 'badge bg-secondary';
const estadoIcon = ESTADO_ICON[e.estado] || '—';
const puedeVer = e.estado === 'completado' || e.estado === 'firmado';
return `<tr>
<td class="small text-nowrap">${esc(fecha)}</td>
<td class="small">${esc(e.paciente_nombre || '—')}</td>
<td class="small">${esc(e.form_nombre)}</td>
<td class="small">${esc(e.enviado_por_nombre || '—')}</td>
<td><span class="${estadoBadge}">${estadoIcon} ${esc(e.estado)}</span></td>
<td>
${puedeVer
? `<button class="btn btn-xs btn-outline-primary py-0 px-2 me-1" onclick="verRespuestaFF(${e.id})">
<i class="fas fa-eye me-1"></i>Ver
</button>
<a href="ver_formulario_enviado.php?id=${e.id}" target="_blank"
class="btn btn-xs btn-outline-secondary py-0 px-2">
<i class="fas fa-print"></i>
</a>`
: '—'}
</td>
</tr>`;
}).join('');
}
function verRespuestaFF(envioId) {
const e = _ffData.find(x => x.id == envioId);
if (!e) return;
const cliente = JSON.parse(e.datos_cliente || '{}');
const prefill = JSON.parse(e.datos_prefilled || '{}');
const todos = { ...prefill, ...cliente };
const esquema = JSON.parse(e.esquema || '[]');
const labels = {};
esquema.forEach(c => { if (c.id) labels[c.id] = c.label || c.id; });
let html = `<h6 class="fw-bold mb-1">${esc(e.form_nombre)}</h6>
<p class="small text-muted mb-3">
Paciente: <strong>${esc(e.paciente_nombre || '—')}</strong>
&nbsp;·&nbsp;
${e.completado_en ? 'Completado: ' + esc(e.completado_en.slice(0,16).replace('T',' ')) : 'Pendiente'}
</p><hr class="my-2">`;
const ordenados = esquema.filter(c =>
c.tipo !== 'separador' && c.tipo !== 'firma' && todos[c.id] !== undefined
);
if (ordenados.length) {
ordenados.forEach(c => {
const v = todos[c.id];
const display = Array.isArray(v) ? v.join(', ') : String(v ?? '—');
html += `<div class="row mb-2">
<div class="col-5 text-muted small">${esc(c.label || c.id)}</div>
<div class="col-7 small fw-semibold">${esc(display)}</div>
</div>`;
});
} else {
Object.entries(todos).forEach(([k, v]) => {
if (k.startsWith('__')) return;
const display = Array.isArray(v) ? v.join(', ') : String(v ?? '—');
html += `<div class="row mb-2">
<div class="col-5 text-muted small">${esc(labels[k] || k)}</div>
<div class="col-7 small fw-semibold">${esc(display)}</div>
</div>`;
});
}
if (e.firma_svg) {
html += `<hr class="my-3"><p class="small fw-semibold text-muted">Firma digital:</p>
<img src="${e.firma_svg}" class="border rounded p-2"
style="max-width:260px;max-height:160px;display:block">`;
}
document.getElementById('resp-form-body').innerHTML = html;
document.getElementById('btn-resp-pdf').href = `ver_formulario_enviado.php?id=${envioId}`;
if (!_modalRespForm) _modalRespForm = new bootstrap.Modal('#modalRespuestaForm');
_modalRespForm.show();
}
</script>
</body>
</html>