fix(turnero): corregir 6 hallazgos de auditoría en tomas previas
- cancelar_toma_pendiente: añade JOIN por paciente_id para prevenir IDOR - cancelar_toma_pendiente: requiere turno_id para validar propiedad - get_tomas_pendientes: porta filtros condicionales y _tomas_config de guardar_toma.php para mostrar conteo de firmas correcto - lugar.php: refresca sección tomas previas al completar via turneroFirmado - lugar.php: muestra error en catch de cargarTomasPrevias en vez de silenciar - lugar.php: limpia _tomaAlertados al cancelar toma previa - lugar.php: pasa turno_id en POST de cancelar para ownership check Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 4.6
parent
814bd2ce5c
commit
49a622e05f
@@ -8,18 +8,26 @@ require_once __DIR__ . '/_helpers.php';
|
|||||||
requireMethod('POST');
|
requireMethod('POST');
|
||||||
requireTurnero();
|
requireTurnero();
|
||||||
|
|
||||||
$body = inputJson();
|
$body = inputJson();
|
||||||
$consentId = (int)($body['consentimiento_id'] ?? 0);
|
$consentId = (int)($body['consentimiento_id'] ?? 0);
|
||||||
|
$turnoId = (int)($body['turno_id'] ?? 0);
|
||||||
if (!$consentId) jsonError('consentimiento_id requerido.');
|
if (!$consentId) jsonError('consentimiento_id requerido.');
|
||||||
|
if (!$turnoId) jsonError('turno_id requerido.');
|
||||||
|
|
||||||
$pdo = db();
|
$pdo = db();
|
||||||
$stmt = $pdo->prepare(
|
|
||||||
"UPDATE turnero_consentimientos
|
|
||||||
SET estado = 'rechazado'
|
|
||||||
WHERE id = ? AND estado = 'en_progreso'"
|
|
||||||
);
|
|
||||||
$stmt->execute([$consentId]);
|
|
||||||
|
|
||||||
if ($stmt->rowCount() === 0) jsonError('No se encontró toma en progreso con ese id.', 404);
|
// Verificar que el consentimiento pertenece al mismo paciente que el turno activo.
|
||||||
|
// Previene que un turnero cancele tomas de pacientes que no tiene abiertos (IDOR).
|
||||||
|
$stmt = $pdo->prepare(
|
||||||
|
"UPDATE turnero_consentimientos tc
|
||||||
|
JOIN turnero_turnos t_old ON t_old.id = tc.turno_id
|
||||||
|
JOIN turnero_turnos t_cur ON t_cur.id = ? AND t_cur.paciente_id IS NOT NULL
|
||||||
|
AND t_cur.paciente_id = t_old.paciente_id
|
||||||
|
SET tc.estado = 'rechazado'
|
||||||
|
WHERE tc.id = ? AND tc.estado = 'en_progreso'"
|
||||||
|
);
|
||||||
|
$stmt->execute([$turnoId, $consentId]);
|
||||||
|
|
||||||
|
if ($stmt->rowCount() === 0) jsonError('Toma no encontrada o el paciente no coincide.', 404);
|
||||||
|
|
||||||
jsonOk(['cancelado' => true]);
|
jsonOk(['cancelado' => true]);
|
||||||
|
|||||||
@@ -42,14 +42,46 @@ foreach ($rows as $r) {
|
|||||||
$esquema = json_decode($r['esquema'] ?? '[]', true) ?: [];
|
$esquema = json_decode($r['esquema'] ?? '[]', true) ?: [];
|
||||||
$datos = json_decode($r['datos_respuestas'] ?? '[]', true) ?: [];
|
$datos = json_decode($r['datos_respuestas'] ?? '[]', true) ?: [];
|
||||||
|
|
||||||
// Contar firmas totales y hechas
|
// Contar firmas activas — mismo filtro que guardar_toma.php
|
||||||
$totalFirmas = 0;
|
// (condicionales de separador + _tomas_config)
|
||||||
$firmasHechas = 0;
|
$firmasCampos = [];
|
||||||
|
$separadorLabel = null;
|
||||||
|
$curCondField = null;
|
||||||
|
$curCondVals = [];
|
||||||
foreach ($esquema as $c) {
|
foreach ($esquema as $c) {
|
||||||
if (($c['tipo'] ?? '') === 'firma_profesional') {
|
$tipo = $c['tipo'] ?? '';
|
||||||
$totalFirmas++;
|
if ($tipo === 'separador') {
|
||||||
if (!empty($datos[$c['id']]) && strlen($datos[$c['id']]) > 10) $firmasHechas++;
|
$separadorLabel = $c['label'] ?? '';
|
||||||
|
$cond = $c['condicion'] ?? null;
|
||||||
|
if ($cond) {
|
||||||
|
$curCondField = $cond['campo_id'] ?? null;
|
||||||
|
$curCondVals = $cond['valores'] ?? ($cond['valor'] ? [$cond['valor']] : []);
|
||||||
|
} else {
|
||||||
|
$curCondField = null;
|
||||||
|
$curCondVals = [];
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
if ($tipo === 'firma_profesional') {
|
||||||
|
if ($curCondField !== null && !empty($curCondVals)) {
|
||||||
|
$ctrlVal = $datos[$curCondField] ?? null;
|
||||||
|
$ctrlArr = is_array($ctrlVal) ? $ctrlVal : ($ctrlVal !== null ? [$ctrlVal] : []);
|
||||||
|
if (empty(array_intersect($curCondVals, $ctrlArr))) continue;
|
||||||
|
}
|
||||||
|
$firmasCampos[] = $c['id'];
|
||||||
|
}
|
||||||
|
}
|
||||||
|
$tcFirmas = $datos['_tomas_config'] ?? null;
|
||||||
|
if (is_array($tcFirmas)) {
|
||||||
|
$allowed = [];
|
||||||
|
foreach ($tcFirmas as $ids) {
|
||||||
|
if (is_array($ids)) foreach ($ids as $id) $allowed[$id] = true;
|
||||||
|
}
|
||||||
|
$firmasCampos = array_values(array_filter($firmasCampos, fn($id) => isset($allowed[$id])));
|
||||||
|
}
|
||||||
|
$totalFirmas = count($firmasCampos);
|
||||||
|
$firmasHechas = 0;
|
||||||
|
foreach ($firmasCampos as $fid) {
|
||||||
|
if (!empty($datos[$fid]) && strlen($datos[$fid]) > 10) $firmasHechas++;
|
||||||
}
|
}
|
||||||
|
|
||||||
$tomas[] = [
|
$tomas[] = [
|
||||||
|
|||||||
@@ -1496,7 +1496,7 @@ async function cargarTomasPrevias(turnoId) {
|
|||||||
const fecha = t.turno_fecha ? escHtml(t.turno_fecha.slice(0,10)) : '';
|
const fecha = t.turno_fecha ? escHtml(t.turno_fecha.slice(0,10)) : '';
|
||||||
const prog = t.tomas_total > 0
|
const prog = t.tomas_total > 0
|
||||||
? `<span class="toma-progreso">${t.tomas_firmadas}/${t.tomas_total}</span>` : '';
|
? `<span class="toma-progreso">${t.tomas_firmadas}/${t.tomas_total}</span>` : '';
|
||||||
return `<div class="toma-prev-row" data-consent-id="${t.id}">
|
return `<div class="toma-prev-row" data-consent-id="${t.id}" data-turno-id="${t.turno_id}">
|
||||||
<i class="fas fa-hourglass-half"></i>
|
<i class="fas fa-hourglass-half"></i>
|
||||||
<span class="nom-form">${fNom}</span>
|
<span class="nom-form">${fNom}</span>
|
||||||
${prog}
|
${prog}
|
||||||
@@ -1513,22 +1513,23 @@ async function cargarTomasPrevias(turnoId) {
|
|||||||
</div>`;
|
</div>`;
|
||||||
}).join('');
|
}).join('');
|
||||||
sec.style.display = '';
|
sec.style.display = '';
|
||||||
} catch (_) {}
|
} catch (e) { mostrarError('No se pudieron cargar tomas previas: ' + e.message); }
|
||||||
}
|
}
|
||||||
|
|
||||||
async function cancelarTomaPrevia(consentId, btn) {
|
async function cancelarTomaPrevia(consentId, btn) {
|
||||||
if (!confirm('¿El paciente no quiere continuar la toma? Se marcará como cancelada.')) return;
|
if (!confirm('¿El paciente no quiere continuar la toma? Se marcará como cancelada.')) return;
|
||||||
btn.disabled = true;
|
btn.disabled = true;
|
||||||
|
const row = document.querySelector(`.toma-prev-row[data-consent-id="${consentId}"]`);
|
||||||
|
const oldTId = row ? parseInt(row.dataset.turnoId) : 0;
|
||||||
try {
|
try {
|
||||||
const res = await fetch(`${API}cancelar_toma_pendiente.php`, {
|
const res = await fetch(`${API}cancelar_toma_pendiente.php`, {
|
||||||
method: 'POST',
|
method: 'POST',
|
||||||
headers: { 'Content-Type': 'application/json' },
|
headers: { 'Content-Type': 'application/json' },
|
||||||
body: JSON.stringify({ consentimiento_id: consentId }),
|
body: JSON.stringify({ consentimiento_id: consentId, turno_id: turnoActivo?.id }),
|
||||||
});
|
});
|
||||||
const json = await res.json();
|
const json = await res.json();
|
||||||
if (!json.ok) { mostrarError(json.error); btn.disabled = false; return; }
|
if (!json.ok) { mostrarError(json.error); btn.disabled = false; return; }
|
||||||
// Quitar fila
|
if (oldTId) _tomaAlertados.delete(oldTId);
|
||||||
const row = document.querySelector(`.toma-prev-row[data-consent-id="${consentId}"]`);
|
|
||||||
if (row) row.remove();
|
if (row) row.remove();
|
||||||
const list = document.getElementById('lista-tomas-prev');
|
const list = document.getElementById('lista-tomas-prev');
|
||||||
if (list && !list.children.length) document.getElementById('sec-tomas-prev').style.display = 'none';
|
if (list && !list.children.length) document.getElementById('sec-tomas-prev').style.display = 'none';
|
||||||
@@ -1660,7 +1661,10 @@ window.addEventListener('message', function(e) {
|
|||||||
cerrarBannerToma();
|
cerrarBannerToma();
|
||||||
_tomaAlertados.clear();
|
_tomaAlertados.clear();
|
||||||
mostrarToast('Tomas completadas ✓', 'success', 3000);
|
mostrarToast('Tomas completadas ✓', 'success', 3000);
|
||||||
if (turnoActivo) actualizarConsentimientos(turnoActivo.id);
|
if (turnoActivo) {
|
||||||
|
actualizarConsentimientos(turnoActivo.id);
|
||||||
|
cargarTomasPrevias(turnoActivo.id); // limpia fila completada del turno anterior
|
||||||
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user