diff --git a/modules/turnero/api/get_consentimientos.php b/modules/turnero/api/get_consentimientos.php
index bf527ad..74befbb 100644
--- a/modules/turnero/api/get_consentimientos.php
+++ b/modules/turnero/api/get_consentimientos.php
@@ -81,10 +81,10 @@ foreach ($consentimientos as &$c) {
// Toma progresiva: override explícito en DB gana; si es NULL, usar heurística (>1 campo firma_profesional)
$overrideProg = $c['formulario_es_toma_prog'] ?? null;
$esTomaProg = $overrideProg !== null ? (bool)(int)$overrideProg : count($camposFirmaPro) > 1;
+ $dr = $c['datos_respuestas'] ? (json_decode($c['datos_respuestas'], true) ?? []) : [];
$tomasTotal = 0;
$tomasFirm = 0;
if ($esTomaProg) {
- $dr = $c['datos_respuestas'] ? (json_decode($c['datos_respuestas'], true) ?? []) : [];
// Mapear cada firma_profesional a los valores de examen de su sección condicional
$firmaCondMap = [];
@@ -147,6 +147,7 @@ foreach ($consentimientos as &$c) {
$c['es_toma_progresiva'] = $esTomaProg;
$c['tomas_total'] = $tomasTotal;
$c['tomas_firmadas'] = $tomasFirm;
+ $c['cierre_anticipado'] = $dr['_cierre_anticipado'] ?? null;
// Exponer campos firma_profesional (ids) para el frontend
$c['campos_firma_pro'] = array_column($camposFirmaPro, 'id');
unset($c['formulario_esquema'], $c['formulario_es_toma_prog'], $c['formulario_solo_profesional'], $c['datos_respuestas']);
@@ -333,10 +334,10 @@ if ($incluirSolicitud) {
$tieneFirmaPac = !empty(array_filter($campos, fn($f) => ($f['tipo'] ?? '') === 'firma'));
$overrideProg = $c['formulario_es_toma_prog'] ?? null;
$esTomaProg = $overrideProg !== null ? (bool)(int)$overrideProg : count($camposFirmaPro) > 1;
+ $dr2 = $c['datos_respuestas'] ? (json_decode($c['datos_respuestas'], true) ?? []) : [];
$tomasTotal2 = 0;
$tomasFirm = 0;
if ($esTomaProg) {
- $dr2 = $c['datos_respuestas'] ? (json_decode($c['datos_respuestas'], true) ?? []) : [];
$firmaCondMap2 = [];
$curC2 = null; $curV2 = [];
foreach ($campos as $campo2) {
@@ -384,6 +385,7 @@ if ($incluirSolicitud) {
$c['es_toma_progresiva'] = $esTomaProg;
$c['tomas_total'] = $tomasTotal2;
$c['tomas_firmadas'] = $tomasFirm;
+ $c['cierre_anticipado'] = $dr2['_cierre_anticipado'] ?? null;
$c['campos_firma_pro'] = array_column($camposFirmaPro, 'id');
unset($c['formulario_esquema'], $c['formulario_es_toma_prog'], $c['formulario_solo_profesional'], $c['datos_respuestas']);
}
diff --git a/modules/turnero/views/lugar.php b/modules/turnero/views/lugar.php
index 279238f..350b7f6 100644
--- a/modules/turnero/views/lugar.php
+++ b/modules/turnero/views/lugar.php
@@ -386,7 +386,8 @@ foreach ($lugares as $_el) {
padding: .5rem .7rem; border-radius: 10px; margin-bottom: .35rem;
font-size: .84rem; border: 1px solid transparent; min-height: 46px;
}
- .consent-row.firmado { background: #f0fdf4; color: #166534; border-color: #bbf7d0; }
+ .consent-row.firmado { background: #f0fdf4; color: #166534; border-color: #bbf7d0; }
+ .consent-row.cerrado_anticipado { background: #fef9c3; color: #78350f; border-color: #fde68a; }
.consent-row.rechazado { background: #f8fafc; color: #64748b; border-color: #e2e8f0; }
.consent-row.enviado { background: #eff6ff; color: #1e40af; border-color: #bfdbfe; }
.consent-row.pendiente { background: #fffbeb; color: #92400e; border-color: #fde68a; }
@@ -1394,10 +1395,11 @@ const CONSENT_IC = {
firmado:'fa-check-circle', rechazado:'fa-ban',
enviado:'fa-envelope', visto:'fa-eye', pendiente:'fa-clock',
en_progreso:'fa-hourglass-half',
+ cerrado_anticipado:'fa-exclamation-triangle',
};
const CONSENT_LBL = {
firmado:'Firmado', rechazado:'Rechazado', enviado:'Enviado', visto:'Visto', pendiente:'Pendiente',
- en_progreso:'En progreso',
+ en_progreso:'En progreso', cerrado_anticipado:'Cerrado anticipadamente',
};
// ── Countdown en tarjetas de cola (toma progresiva) ──────────
@@ -1530,7 +1532,7 @@ function renderConsentimientos(lista) {
// Mostrar consentimientos de examen (origen null) + los de esta estación
lista = lista.filter(c => !c.origen_lugar_id || c.origen_lugar_id == lugarId);
tieneConsent = lista.length > 0;
- hayPendientes = lista.some(c => !['firmado','rechazado'].includes(c.estado));
+ hayPendientes = lista.some(c => !['firmado','rechazado','cerrado_anticipado'].includes(c.estado));
// en_progreso bloquea finalizar pero no muestra aviso de "pendiente sin acción"
const pendCount = lista.filter(c => ['pendiente','enviado','visto'].includes(c.estado)).length;
@@ -1574,7 +1576,7 @@ function renderConsentimientos(lista) {
}
listEl.innerHTML = lista.map(c => {
- const ya = ['firmado','rechazado'].includes(c.estado);
+ const ya = ['firmado','rechazado','cerrado_anticipado'].includes(c.estado);
const ico = CONSENT_IC[c.estado] || 'fa-clock';
const label = CONSENT_LBL[c.estado] || c.estado;
const token = escHtml(c.token || '');
@@ -1631,13 +1633,20 @@ function renderConsentimientos(lista) {
`;
- return `
-
-
${escHtml(c.formulario_nombre || 'Consentimiento')}
- ${progresoBadge}
- ${countdownEl}
-
${label}
-
${btnAccion}${btnVer}${btnQuitar}
+ const motivoRow = (c.estado === 'cerrado_anticipado' && c.cierre_anticipado?.motivo)
+ ? `
+ ${escHtml(c.cierre_anticipado.motivo)}
+
` : '';
+ return `
+
+
+
${escHtml(c.formulario_nombre || 'Consentimiento')}
+ ${progresoBadge}
+ ${countdownEl}
+
${label}
+
${btnAccion}${btnVer}${btnQuitar}
+
+ ${motivoRow}
`;
}).join('');
}
diff --git a/modules/turnero/views/recepcion.php b/modules/turnero/views/recepcion.php
index 5e9b048..60c5d74 100644
--- a/modules/turnero/views/recepcion.php
+++ b/modules/turnero/views/recepcion.php
@@ -1960,11 +1960,12 @@ async function guardarSolicitud() {
// ── Labels / iconos para estados ─────────────────────────────
const CONSENT_META = {
- firmado : { cls:'firmado', ico:'fa-check-circle', lbl:'Firmado' },
- enviado : { cls:'enviado', ico:'fa-envelope', lbl:'Enviado' },
- visto : { cls:'visto', ico:'fa-eye', lbl:'Visto' },
- rechazado : { cls:'rechazado', ico:'fa-ban', lbl:'Rechazado' },
- pendiente : { cls:'pendiente', ico:'fa-clock', lbl:'Pendiente' },
+ firmado : { cls:'firmado', ico:'fa-check-circle', lbl:'Firmado' },
+ enviado : { cls:'enviado', ico:'fa-envelope', lbl:'Enviado' },
+ visto : { cls:'visto', ico:'fa-eye', lbl:'Visto' },
+ rechazado : { cls:'rechazado', ico:'fa-ban', lbl:'Rechazado' },
+ pendiente : { cls:'pendiente', ico:'fa-clock', lbl:'Pendiente' },
+ cerrado_anticipado : { cls:'rechazado', ico:'fa-exclamation-triangle', lbl:'Cerrado anticipadamente' },
};
function renderConsentimientos(lista) {
@@ -1977,7 +1978,7 @@ function renderConsentimientos(lista) {
}
el.innerHTML = lista.map(c => {
const m = CONSENT_META[c.estado] || CONSENT_META.pendiente;
- const ya = ['firmado','rechazado'].includes(c.estado);
+ const ya = ['firmado','rechazado','cerrado_anticipado'].includes(c.estado);
const token = escHtml(c.token || '');
const nom = escHtml(c.formulario_nombre || 'Consentimiento');
const fId = c.formulario_id;
@@ -2037,6 +2038,10 @@ function renderConsentimientos(lista) {
}
}
+ const motivoRec = (c.estado === 'cerrado_anticipado' && c.cierre_anticipado?.motivo)
+ ? `
+ ${escHtml(c.cierre_anticipado.motivo)}
+
` : '';
return `
@@ -2045,6 +2050,7 @@ function renderConsentimientos(lista) {
${btnFirmar}${btnWa}
${profRow}
+ ${motivoRec}
`;
}).join('');
}
diff --git a/ver_formulario_enviado.php b/ver_formulario_enviado.php
index c011caa..8d25105 100644
--- a/ver_formulario_enviado.php
+++ b/ver_formulario_enviado.php
@@ -56,10 +56,23 @@ if ($modoTurnero) {
// ── POST: guardar firma y marcar como firmado ──────────────────────
if ($_SERVER['REQUEST_METHOD'] === 'POST') {
header('Content-Type: application/json; charset=utf-8');
- if ($tcRow['estado'] === 'firmado') {
- echo json_encode(['ok' => false, 'error' => 'Este consentimiento ya fue firmado']); exit;
+ if (in_array($tcRow['estado'], ['firmado', 'cerrado_anticipado'])) {
+ echo json_encode(['ok' => false, 'error' => 'Este consentimiento ya fue finalizado']); exit;
}
$input = json_decode(file_get_contents('php://input'), true) ?? [];
+ // Cierre anticipado de toma progresiva con justificación
+ if (isset($input['mp_cerrar_anticipado'])) {
+ $motivo = trim($input['motivo'] ?? '');
+ if (!$motivo) { echo json_encode(['ok' => false, 'error' => 'Motivo requerido']); exit; }
+ $existing = json_decode($tcRow['datos_respuestas'] ?? '{}', true) ?: [];
+ $existing['_cierre_anticipado'] = ['motivo' => $motivo, 'cerrado_at' => date('Y-m-d H:i:s')];
+ $db->getConnection()
+ ->prepare("UPDATE turnero_consentimientos SET estado='cerrado_anticipado', firmado_at=NOW(), datos_respuestas=? WHERE token=?")
+ ->execute([json_encode($existing, JSON_UNESCAPED_UNICODE), $tokenTurnero]);
+ require_once __DIR__ . '/modules/turnero/api/_helpers.php';
+ notificarSSE((int)$tcRow['sesion_id']);
+ echo json_encode(['ok' => true, 'mp_cerrado' => true]); exit;
+ }
// Borrador: guardar campos sin firma (auto-save desde el formulario)
// Muestras Prolongadas: marcar completo sin SVG global
if (isset($input['mp_completar'])) {
@@ -226,7 +239,7 @@ $esquema = json_decode($envio['esquema'], true) ?? [];
$datosCliente = json_decode($envio['datos_cliente'] ?? '{}', true) ?? [];
$datosPrefilled = json_decode($envio['datos_prefilled'] ?? '{}', true) ?? [];
$todos = array_merge($datosPrefilled, $datosCliente);
-$modoEditar = $modoTurnero && $envio['estado'] !== 'firmado';
+$modoEditar = $modoTurnero && !in_array($envio['estado'], ['firmado', 'cerrado_anticipado']);
$embebido = isset($_GET['embed']) && $_GET['embed'] === '1';
$compact = $embebido && isset($_GET['compact']);
$autoprint = isset($_GET['autoprint']) && $_GET['autoprint'] === '1';
@@ -1359,6 +1372,22 @@ function _addExamWizardHtml(string $cid): string {
+
+
+
+
+
+
Toma cerrada anticipadamente
+
+
+ = esc2($_ca['motivo']) ?>
+
+
+
+
+
@@ -1377,6 +1406,29 @@ function _addExamWizardHtml(string $cid): string {
+
+
+
+
+
+
+
+
Cerrar anticipadamente
+
Explique por qué no se completaron todas las tomas. Este mensaje quedará visible en toma de muestras y recepción.
+
+
+
+
+
+
+
+
+
+