feat(turnero): toma prolongada secuencial + bloqueo muestras/cierre
- ver_formulario_enviado: auto-fill médico (nombres/especialidad/código) en datos_prefilled desde solicitud - ver_formulario_enviado: canvases de toma progresiva bloqueados secuencialmente — solo el turno activo es firmable, los siguientes aparecen al expirar el countdown - ver_formulario_enviado: countdown prominente con reloj grande, color dinámico, sonido (Web Audio) y vibración al llegar la hora - lugar: bloquear cierre del modal durante toma progresiva activa (botón X deshabilitado hasta mp_completar) - lugar: bloquear Finalizar atención si hay muestras pendientes sin decisión (recibida/rechazada) - lugar: _syncBtnFinalizar combina consentimientos + muestras para el aviso y estado del botón - lugar: restricción de usuario a lugar específico via turnero_lugar_id (admin_users) + bloqueo JS en confirmarLugar() - migrations: turnero_lugar_id en admin_users para restringir bacteriólogos por usuario Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 4.6
parent
0e116359f8
commit
f4da864db7
@@ -91,6 +91,19 @@ try {
|
||||
$eid = enfermeraId();
|
||||
if ($eid) $datos['enfermera_id'] = $eid;
|
||||
}
|
||||
// Asignar número de orden D-YYYYMMDD-NNN si no viene uno
|
||||
if (empty($datos['numero_orden'])) {
|
||||
$pdo = db();
|
||||
$prefix = 'D-' . date('Ymd') . '-';
|
||||
$st = $pdo->prepare(
|
||||
"SELECT MAX(CAST(SUBSTRING_INDEX(numero_orden, '-', -1) AS UNSIGNED)) AS ultimo
|
||||
FROM lab_domicilios WHERE numero_orden LIKE ?"
|
||||
);
|
||||
$st->execute([$prefix . '%']);
|
||||
$ultimo = (int)($st->fetch(\PDO::FETCH_ASSOC)['ultimo'] ?? 0);
|
||||
$datos['numero_orden'] = $prefix . str_pad($ultimo + 1, 3, '0', STR_PAD_LEFT);
|
||||
}
|
||||
|
||||
$id = $dom->crear($datos, $admin);
|
||||
|
||||
// Crear asignación automática si se indicó enfermera_id
|
||||
|
||||
@@ -364,7 +364,7 @@ class Domicilio {
|
||||
|
||||
private function filtrarCampos(array $datos): array {
|
||||
$permitidos = [
|
||||
'orden_id', 'paciente_id', 'direccion', 'ciudad', 'barrio',
|
||||
'orden_id', 'paciente_id', 'numero_orden', 'direccion', 'ciudad', 'barrio',
|
||||
'indicaciones_dir', 'fecha_programada', 'hora_programada',
|
||||
'tipo_servicio', 'tipo_cliente', 'examenes_solicitados',
|
||||
'estado', 'motivo_cancelacion',
|
||||
|
||||
@@ -681,6 +681,7 @@ async function cargarLista(pag = 1) {
|
||||
<td>
|
||||
<div class="fw-semibold">${esc(dom.paciente_nombre)}</div>
|
||||
<small class="text-muted">${esc(dom.barrio||'')}</small>
|
||||
${dom.numero_orden ? `<div><span style="font-size:.68rem;background:#f0fdf4;color:#15803d;border:1px solid #86efac;border-radius:20px;padding:0 7px;font-weight:700">#${esc(dom.numero_orden)}</span></div>` : ''}
|
||||
</td>
|
||||
<td>
|
||||
${dom.enfermera_nombre ? `<small>${esc(dom.enfermera_nombre)}</small>` : '<span class="text-danger small"><i class="fas fa-exclamation-triangle me-1"></i>Sin asignar</span>'}
|
||||
@@ -799,6 +800,13 @@ async function verDomicilio(id) {
|
||||
</dl>
|
||||
</div>
|
||||
<div class="col-md-6">
|
||||
${dom.numero_orden ? `<div class="mb-3">
|
||||
<label class="form-label small text-muted text-uppercase">Número de orden</label><br>
|
||||
<span style="font-size:.82rem;background:#f0fdf4;color:#15803d;border:1px solid #86efac;
|
||||
border-radius:20px;padding:2px 12px;font-weight:700">
|
||||
<i class="fas fa-hashtag me-1" style="font-size:.7rem"></i>${esc(dom.numero_orden)}
|
||||
</span>
|
||||
</div>` : ''}
|
||||
<div class="mb-3">
|
||||
<label class="form-label small text-muted text-uppercase">Estado actual</label><br>
|
||||
<span class="badge bg-${COLOR_DOM[dom.estado]||'secondary'} fs-6">${esc(dom.estado)}</span>
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
-- Agrega número de orden visible a domicilios (formato D-YYYYMMDD-NNN)
|
||||
ALTER TABLE lab_domicilios
|
||||
ADD COLUMN numero_orden VARCHAR(20) NULL DEFAULT NULL
|
||||
COMMENT 'Número de orden del domicilio. Formato D-YYYYMMDD-NNN'
|
||||
AFTER paciente_id;
|
||||
|
||||
CREATE INDEX idx_lab_domicilios_numero_orden ON lab_domicilios (numero_orden);
|
||||
@@ -0,0 +1,6 @@
|
||||
-- Restricción de usuario a un lugar específico del turnero.
|
||||
-- NULL = acceso libre (admin, recepcionista, etc.)
|
||||
-- Valor = solo puede operar en ese lugar (ej. bacteriólogo asignado a Toma 1)
|
||||
ALTER TABLE admin_users
|
||||
ADD COLUMN IF NOT EXISTS turnero_lugar_id INT UNSIGNED DEFAULT NULL
|
||||
COMMENT 'FK turnero_lugares.id — si != NULL el usuario solo opera en ese lugar';
|
||||
@@ -1,23 +1,40 @@
|
||||
<?php
|
||||
/**
|
||||
* GET modules/turnero/api/get_consecutivo.php
|
||||
* Devuelve el siguiente número de orden del día (YYYYMMDD-NNN).
|
||||
* Devuelve el siguiente número de orden del día.
|
||||
*
|
||||
* Parámetros:
|
||||
* tipo string 'F' = Físico/turnero (default) | 'D' = Domicilio
|
||||
*
|
||||
* Formato resultante:
|
||||
* F-YYYYMMDD-NNN para tipo=F
|
||||
* D-YYYYMMDD-NNN para tipo=D
|
||||
*/
|
||||
require_once __DIR__ . '/_helpers.php';
|
||||
requireTurnero();
|
||||
requireMethod('GET');
|
||||
|
||||
$pdo = db();
|
||||
$prefix = date('Ymd') . '-';
|
||||
$tipo = strtoupper(trim($_GET['tipo'] ?? 'F'));
|
||||
if (!in_array($tipo, ['F', 'D'], true)) $tipo = 'F';
|
||||
|
||||
$row = $pdo->prepare(
|
||||
"SELECT MAX(CAST(SUBSTRING_INDEX(numero_orden,'-',-1) AS UNSIGNED)) AS ultimo
|
||||
FROM turnero_solicitudes
|
||||
WHERE numero_orden LIKE ?"
|
||||
);
|
||||
$row->execute([$prefix . '%']);
|
||||
$ultimo = (int)($row->fetch(PDO::FETCH_ASSOC)['ultimo'] ?? 0);
|
||||
$pdo = db();
|
||||
$prefix = $tipo . '-' . date('Ymd') . '-';
|
||||
|
||||
if ($tipo === 'D') {
|
||||
$stmt = $pdo->prepare(
|
||||
"SELECT MAX(CAST(SUBSTRING_INDEX(numero_orden, '-', -1) AS UNSIGNED)) AS ultimo
|
||||
FROM lab_domicilios
|
||||
WHERE numero_orden LIKE ?"
|
||||
);
|
||||
} else {
|
||||
$stmt = $pdo->prepare(
|
||||
"SELECT MAX(CAST(SUBSTRING_INDEX(numero_orden, '-', -1) AS UNSIGNED)) AS ultimo
|
||||
FROM turnero_solicitudes
|
||||
WHERE numero_orden LIKE ?"
|
||||
);
|
||||
}
|
||||
$stmt->execute([$prefix . '%']);
|
||||
$ultimo = (int)($stmt->fetch(PDO::FETCH_ASSOC)['ultimo'] ?? 0);
|
||||
|
||||
$siguiente = $prefix . str_pad($ultimo + 1, 3, '0', STR_PAD_LEFT);
|
||||
|
||||
jsonOk(['consecutivo' => $siguiente]);
|
||||
|
||||
@@ -10,6 +10,7 @@ if (!isUserLoggedIn()) {
|
||||
}
|
||||
|
||||
// Tablet asignada → forzar su lugar, bloquear cualquier otro
|
||||
$lugarForzado = 0;
|
||||
if (!empty($_SESSION['turnero_dispositivo'])) {
|
||||
$_d = $_SESSION['turnero_dispositivo'];
|
||||
$_forzado = (int)$_d['lugar_id'];
|
||||
@@ -19,6 +20,17 @@ if (!empty($_SESSION['turnero_dispositivo'])) {
|
||||
if ((int)($_GET['lugar_id'] ?? 0) !== $_forzado) {
|
||||
header('Location: ' . BASE_URL . 'erp.php?m=turnero&v=lugar&lugar_id=' . $_forzado); exit;
|
||||
}
|
||||
$lugarForzado = $_forzado;
|
||||
}
|
||||
// Restricción a nivel de usuario (turnero_lugar_id en admin_users)
|
||||
if (!$lugarForzado) {
|
||||
$_userLugar = (int)($_SESSION['admin_user']['turnero_lugar_id'] ?? 0);
|
||||
if ($_userLugar) {
|
||||
if ((int)($_GET['lugar_id'] ?? 0) !== $_userLugar) {
|
||||
header('Location: ' . BASE_URL . 'erp.php?m=turnero&v=lugar&lugar_id=' . $_userLugar); exit;
|
||||
}
|
||||
$lugarForzado = $_userLugar;
|
||||
}
|
||||
}
|
||||
|
||||
try {
|
||||
@@ -495,9 +507,11 @@ require_once __DIR__ . '/../../../shared/components/sidebar.php';
|
||||
onclick="abrirModalMiFirma()" title="<?= $adminFirmaSvg ? 'Ver / cambiar mi firma' : 'Configurar mi firma' ?>">
|
||||
<i class="fas fa-signature me-1"></i><?= $adminFirmaSvg ? 'Mi firma' : 'Configurar firma' ?>
|
||||
</button>
|
||||
<?php if (!$lugarForzado): ?>
|
||||
<button class="btn btn-sm btn-outline-secondary" onclick="cambiarLugar()">
|
||||
<i class="fas fa-exchange-alt me-1"></i>Cambiar
|
||||
</button>
|
||||
<?php endif; ?>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -751,8 +765,9 @@ require_once __DIR__ . '/../../../shared/components/sidebar.php';
|
||||
<span style="font-weight:700;font-size:.95rem;color:#1e293b">
|
||||
<i class="fas fa-file-signature me-2 text-primary"></i>Formulario de consentimiento
|
||||
</span>
|
||||
<button onclick="cerrarModalConsentimiento()"
|
||||
style="background:none;border:none;font-size:1.2rem;color:#94a3b8;cursor:pointer;padding:2px 6px">
|
||||
<button id="btn-cerrar-modal-consent" onclick="cerrarModalConsentimiento()"
|
||||
style="background:none;border:none;font-size:1.2rem;color:#94a3b8;cursor:pointer;padding:2px 6px"
|
||||
title="Cerrar">
|
||||
<i class="fas fa-times"></i>
|
||||
</button>
|
||||
</div>
|
||||
@@ -769,6 +784,7 @@ require_once __DIR__ . '/../../../shared/components/sidebar.php';
|
||||
|
||||
<script>
|
||||
// ── Estado global ─────────────────────────────────────────────
|
||||
const LUGAR_FORZADO = <?= $lugarForzado ?: 0 ?>;
|
||||
let lugarId = <?= $lugarIdParam ?: 0 ?>;
|
||||
let turnoActivo = null;
|
||||
let tieneConsent = false;
|
||||
@@ -777,6 +793,8 @@ let pollingColaId = null;
|
||||
let pollingConsentId = null;
|
||||
let _consentTokenCache = null;
|
||||
let _pacienteActivo = null;
|
||||
let _tomaProgresivaActiva = false;
|
||||
let hayMuestrasPendientes = false;
|
||||
let _solicitudActiva = null;
|
||||
let _muestrasActivas = [];
|
||||
|
||||
@@ -828,6 +846,7 @@ function confirmarLugar() {
|
||||
const sel = document.getElementById('sel-lugar-init');
|
||||
const id = parseInt(sel.value);
|
||||
if (!id) return mostrarError('Seleccione un lugar.');
|
||||
if (LUGAR_FORZADO && id !== LUGAR_FORZADO) return mostrarError('No tiene acceso a esa estación.');
|
||||
lugarId = id;
|
||||
const txt = sel.options[sel.selectedIndex].text;
|
||||
document.getElementById('lbl-lugar-titulo').textContent = txt;
|
||||
@@ -1146,6 +1165,24 @@ setInterval(() => {
|
||||
}
|
||||
}, 1000);
|
||||
|
||||
function _syncBtnFinalizar() {
|
||||
const btnFin = document.getElementById('btn-finalizar');
|
||||
const warnStrip = document.getElementById('accion-consent-warn');
|
||||
const warnTxt = document.getElementById('accion-warn-txt');
|
||||
const bloqueado = hayPendientes || hayMuestrasPendientes;
|
||||
if (btnFin) btnFin.disabled = bloqueado;
|
||||
if (!warnStrip || !warnTxt) return;
|
||||
if (bloqueado) {
|
||||
const msgs = [];
|
||||
if (hayPendientes) msgs.push('consentimientos pendientes');
|
||||
if (hayMuestrasPendientes) msgs.push('muestras sin decisión');
|
||||
warnTxt.textContent = msgs.join(' · ');
|
||||
warnStrip.classList.remove('d-none');
|
||||
} else {
|
||||
warnStrip.classList.add('d-none');
|
||||
}
|
||||
}
|
||||
|
||||
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);
|
||||
@@ -1159,29 +1196,17 @@ function renderConsentimientos(lista) {
|
||||
const banner = document.getElementById('bloqueo-banner');
|
||||
const btnFin = document.getElementById('btn-finalizar');
|
||||
|
||||
// Aviso en barra de acciones
|
||||
const warnStrip = document.getElementById('accion-consent-warn');
|
||||
const warnTxt = document.getElementById('accion-warn-txt');
|
||||
if (warnStrip) {
|
||||
if (hayPendientes) {
|
||||
warnStrip.classList.remove('d-none');
|
||||
warnTxt.textContent = pendCount + ' consentimiento' + (pendCount > 1 ? 's' : '') + ' pendiente' + (pendCount > 1 ? 's' : '');
|
||||
} else {
|
||||
warnStrip.classList.add('d-none');
|
||||
}
|
||||
}
|
||||
_syncBtnFinalizar();
|
||||
|
||||
if (!tieneConsent) {
|
||||
listEl.innerHTML = '';
|
||||
sinEl.classList.remove('d-none');
|
||||
banner.classList.add('d-none');
|
||||
if (btnFin) btnFin.disabled = false;
|
||||
return;
|
||||
}
|
||||
|
||||
sinEl.classList.add('d-none');
|
||||
banner.classList.toggle('d-none', !hayPendientes);
|
||||
if (btnFin) btnFin.disabled = hayPendientes;
|
||||
|
||||
// Actualizar mapa de timers para tomas progresivas
|
||||
_tomaTimers = {};
|
||||
@@ -1324,14 +1349,25 @@ function abrirTomaProgresiva(token, turnoId, formularioId) {
|
||||
iframe.src = '';
|
||||
load.style.display = '';
|
||||
modal.style.display = 'flex';
|
||||
// Pasa modo=toma_progresiva para que el renderer solo muestre
|
||||
// los campos del siguiente tiempo pendiente y el botón de guardar parcial
|
||||
_tomaProgresivaActiva = true;
|
||||
_setBtnCerrarConsent(false);
|
||||
setTimeout(() => {
|
||||
iframe.src = `${BASE_WA}ver_formulario_enviado.php?token=${encodeURIComponent(token)}&embed=1&compact=1&modo=toma_progresiva`;
|
||||
}, 80);
|
||||
}
|
||||
|
||||
function cerrarModalConsentimiento() {
|
||||
function _setBtnCerrarConsent(habilitado) {
|
||||
const btn = document.getElementById('btn-cerrar-modal-consent');
|
||||
if (!btn) return;
|
||||
btn.disabled = !habilitado;
|
||||
btn.style.opacity = habilitado ? '' : '.25';
|
||||
btn.title = habilitado ? 'Cerrar' : 'No puede cerrar durante una toma en progreso';
|
||||
}
|
||||
|
||||
function cerrarModalConsentimiento(forzar) {
|
||||
if (!forzar && _tomaProgresivaActiva) return; // bloqueado hasta finalizar toma
|
||||
_tomaProgresivaActiva = false;
|
||||
_setBtnCerrarConsent(true);
|
||||
const modal = document.getElementById('modal-consentimiento');
|
||||
const iframe = document.getElementById('modal-consent-iframe');
|
||||
modal.style.display = 'none';
|
||||
@@ -1342,8 +1378,14 @@ function cerrarModalConsentimiento() {
|
||||
}
|
||||
|
||||
window.addEventListener('message', function(e) {
|
||||
if (e.data && e.data.type === 'tomaProgresivaIniciada') {
|
||||
_tomaProgresivaActiva = true;
|
||||
_setBtnCerrarConsent(false);
|
||||
}
|
||||
if (e.data && e.data.type === 'turneroFirmado') {
|
||||
cerrarModalConsentimiento();
|
||||
_tomaProgresivaActiva = false;
|
||||
_setBtnCerrarConsent(true);
|
||||
cerrarModalConsentimiento(true);
|
||||
mostrarToast('Consentimiento firmado ✓', 'success', 3000);
|
||||
if (turnoActivo) actualizarConsentimientos(turnoActivo.id);
|
||||
}
|
||||
@@ -1380,7 +1422,8 @@ async function iniciarAtencion() {
|
||||
|
||||
async function finalizarAtencion() {
|
||||
if (!turnoActivo) return;
|
||||
if (hayPendientes) { mostrarError('Debe firmar todos los consentimientos antes de finalizar.'); return; }
|
||||
if (hayPendientes) { mostrarError('Debe firmar todos los consentimientos antes de finalizar.'); return; }
|
||||
if (hayMuestrasPendientes) { mostrarError('Debe marcar cada muestra como recibida o rechazada antes de finalizar.'); return; }
|
||||
if (!confirm(`¿Finalizar atención del turno ${turnoActivo.codigo}?`)) return;
|
||||
await cambiarEstadoTurno('finalizado');
|
||||
}
|
||||
@@ -1429,12 +1472,13 @@ async function cambiarEstadoTurno(nuevoEstado) {
|
||||
function resetFicha() {
|
||||
clearInterval(pollingConsentId);
|
||||
turnoActivo = null;
|
||||
tieneConsent = false;
|
||||
hayPendientes = false;
|
||||
_consentTokenCache = null;
|
||||
_pacienteActivo = null;
|
||||
_solicitudActiva = null;
|
||||
_muestrasActivas = [];
|
||||
tieneConsent = false;
|
||||
hayPendientes = false;
|
||||
hayMuestrasPendientes = false;
|
||||
_consentTokenCache = null;
|
||||
_pacienteActivo = null;
|
||||
_solicitudActiva = null;
|
||||
_muestrasActivas = [];
|
||||
const secMuestras = document.getElementById('sec-muestras');
|
||||
if (secMuestras) secMuestras.classList.add('d-none');
|
||||
document.getElementById('ficha-orden').classList.add('d-none');
|
||||
@@ -1750,6 +1794,9 @@ function renderMuestras(lista) {
|
||||
const nRec = lista.filter(m => m.estado === 'recibida').length;
|
||||
const nRech = lista.filter(m => m.estado === 'rechazada').length;
|
||||
|
||||
hayMuestrasPendientes = nPend > 0;
|
||||
_syncBtnFinalizar();
|
||||
|
||||
let chips = '';
|
||||
if (nPend) chips += `<span class="mc pend">${nPend} pendiente${nPend > 1 ? 's' : ''}</span>`;
|
||||
if (nRec) chips += `<span class="mc rec">${nRec} recibida${nRec > 1 ? 's' : ''}</span>`;
|
||||
|
||||
+72
-17
@@ -37,12 +37,16 @@ if ($modoTurnero) {
|
||||
p.numero_documento, p.tipo_documento,
|
||||
p.fecha_nacimiento, p.telefono AS paciente_telefono, p.eps,
|
||||
t.sesion_id,
|
||||
ts.numero_orden
|
||||
ts.numero_orden,
|
||||
med.nombres AS medico_nombres, med.apellidos AS medico_apellidos,
|
||||
med.cod_especialidad AS medico_especialidad,
|
||||
med.codigo AS medico_codigo, med.docidmedico AS medico_docid
|
||||
FROM turnero_consentimientos tc
|
||||
JOIN lab_formularios f ON f.id = tc.formulario_id
|
||||
JOIN turnero_turnos t ON t.id = tc.turno_id
|
||||
LEFT JOIN turnero_solicitudes ts ON ts.turno_id = tc.turno_id
|
||||
LEFT JOIN lab_pacientes p ON p.id = COALESCE(ts.paciente_id, t.paciente_id)
|
||||
LEFT JOIN medicos med ON med.id = ts.medico_id
|
||||
WHERE tc.token = ?",
|
||||
[$tokenTurnero]
|
||||
);
|
||||
@@ -124,11 +128,15 @@ if ($modoTurnero) {
|
||||
'firma_profesional_svg' => $tcRow['firma_profesional_svg'] ?? null,
|
||||
'datos_cliente' => $tcRow['datos_respuestas'] ?: '{}',
|
||||
'datos_prefilled' => json_encode(['__paciente' => [
|
||||
'nombre_completo' => $tcRow['paciente_nombre'] ?? '',
|
||||
'numero_documento' => $tcRow['numero_documento'] ?? '',
|
||||
'tipo_documento' => $tcRow['tipo_documento'] ?? '',
|
||||
'telefono' => $tcRow['paciente_telefono'] ?? '',
|
||||
'eps' => $tcRow['eps'] ?? '',
|
||||
'nombre_completo' => $tcRow['paciente_nombre'] ?? '',
|
||||
'numero_documento' => $tcRow['numero_documento'] ?? '',
|
||||
'tipo_documento' => $tcRow['tipo_documento'] ?? '',
|
||||
'telefono' => $tcRow['paciente_telefono'] ?? '',
|
||||
'eps' => $tcRow['eps'] ?? '',
|
||||
'medico_nombre' => trim(($tcRow['medico_nombres'] ?? '') . ' ' . ($tcRow['medico_apellidos'] ?? '')),
|
||||
'medico_especialidad'=> $tcRow['medico_especialidad'] ?? '',
|
||||
'medico_codigo' => $tcRow['medico_codigo'] ?? '',
|
||||
'medico_docid' => $tcRow['medico_docid'] ?? '',
|
||||
]]),
|
||||
'created_at' => $tcRow['enviado_at'] ?? date('Y-m-d H:i:s'),
|
||||
'completado_en' => $tcRow['firmado_at'],
|
||||
@@ -271,6 +279,17 @@ if ($modoTurnero && $embebido && $_soloFirmaPro) {
|
||||
}
|
||||
}
|
||||
|
||||
// Primera firma pendiente en toma progresiva (las demás se ocultan hasta su turno)
|
||||
$_mpPrimeraPendiente = null;
|
||||
if (!empty($_mpMap)) {
|
||||
foreach ($_mpMap as $_fid => $_) {
|
||||
if (empty($datosCliente[$_fid . '_svg'])) {
|
||||
$_mpPrimeraPendiente = $_fid;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Mapa id → label
|
||||
$labelMap = [];
|
||||
foreach ($esquema as $c) {
|
||||
@@ -704,11 +723,18 @@ function esc2(mixed $v): string {
|
||||
<script>window._fpwPreFirma = <?= json_encode($firmaProfPreguardada) ?>;</script>
|
||||
<?php endif; ?>
|
||||
<!-- Canvas del profesional -->
|
||||
<?php
|
||||
$_mpBloqueado = !empty($_mpMap)
|
||||
&& $_mpPrimeraPendiente !== null
|
||||
&& $cid !== $_mpPrimeraPendiente
|
||||
&& empty($datosCliente[$cid . '_svg']);
|
||||
?>
|
||||
<div class="firma-pro-widget no-print" id="fpw-<?= htmlspecialchars($cid) ?>"
|
||||
data-envio="<?= (int)$envio['id'] ?>" data-campo="<?= htmlspecialchars($cid) ?>"
|
||||
data-solo-pro="<?= ($modoTurnero && $_soloFirmaPro) ? '1' : '0' ?>"
|
||||
data-turno="<?= isset($tcRow) ? (int)$tcRow['turno_id'] : '' ?>"
|
||||
data-formulario="<?= isset($tcRow) ? (int)$tcRow['formulario_id'] : '' ?>">
|
||||
data-formulario="<?= isset($tcRow) ? (int)$tcRow['formulario_id'] : '' ?>"
|
||||
<?= $_mpBloqueado ? 'style="display:none"' : '' ?>>
|
||||
<?php if ($firmaProfPreguardada): ?>
|
||||
<div class="fpw-oneclic">
|
||||
<button class="btn btn-success fpw-oneclic-btn w-100" style="font-size:1rem;padding:.55rem 1rem">
|
||||
@@ -1139,39 +1165,68 @@ function _guardarFirmaMP(widget, svg, msgEl, entry, campoId) {
|
||||
});
|
||||
}
|
||||
|
||||
function _mpPlayBeep() {
|
||||
try {
|
||||
var ctx = new (window.AudioContext || window.webkitAudioContext)();
|
||||
[880, 1100, 880, 1100].forEach(function(freq, i) {
|
||||
var osc = ctx.createOscillator(), gain = ctx.createGain();
|
||||
osc.connect(gain); gain.connect(ctx.destination);
|
||||
osc.frequency.value = freq;
|
||||
var t0 = ctx.currentTime + i * 0.22;
|
||||
gain.gain.setValueAtTime(0.7, t0);
|
||||
gain.gain.exponentialRampToValueAtTime(0.001, t0 + 0.18);
|
||||
osc.start(t0); osc.stop(t0 + 0.18);
|
||||
});
|
||||
} catch(e) {}
|
||||
}
|
||||
|
||||
function _mpIniciarCountdown(minutos, nextLabel, nextFirmaId) {
|
||||
var targetMs = Date.now() + minutos * 60000;
|
||||
var box = document.getElementById('mp-countdown');
|
||||
if (!box) {
|
||||
box = document.createElement('div');
|
||||
box.id = 'mp-countdown';
|
||||
box.style.cssText = 'position:sticky;bottom:12px;background:#1565c0;color:#fff;padding:10px 16px;border-radius:8px;margin:12px 0;text-align:center;font-size:.9rem;z-index:50';
|
||||
box.style.cssText = 'position:sticky;bottom:0;left:0;right:0;background:#1565c0;color:#fff;'
|
||||
+ 'padding:14px 20px;margin:16px -14px -12px;text-align:center;z-index:60;'
|
||||
+ 'box-shadow:0 -4px 20px rgba(21,101,192,.35)';
|
||||
document.querySelector('.doc-body').appendChild(box);
|
||||
}
|
||||
if (box._mpTick) clearInterval(box._mpTick);
|
||||
|
||||
// Notificar al padre que bloquee el cierre
|
||||
try { window.parent.postMessage({ type: 'tomaProgresivaIniciada' }, '*'); } catch(e) {}
|
||||
|
||||
box._mpTick = setInterval(function() {
|
||||
var left = targetMs - Date.now();
|
||||
if (left <= 0) {
|
||||
clearInterval(box._mpTick);
|
||||
box.style.background = '#dc3545';
|
||||
box.style.animation = 'mp-pulse 1s ease-in-out infinite';
|
||||
box.innerHTML = '<strong><i class="fas fa-bell me-2"></i>¡Hora de la siguiente muestra!</strong>'
|
||||
+ (nextLabel ? '<br><small>' + nextLabel + '</small>' : '');
|
||||
if (navigator.vibrate) navigator.vibrate([400,200,400,200,400]);
|
||||
var nw = nextFirmaId ? document.getElementById('fpw-' + nextFirmaId) : document.querySelector('.firma-pro-widget');
|
||||
box.innerHTML = '<div style="font-size:1.1rem;font-weight:700"><i class="fas fa-bell me-2"></i>¡Hora de la siguiente muestra!</div>'
|
||||
+ (nextLabel ? '<div style="font-size:.85rem;opacity:.9;margin-top:4px">' + nextLabel + '</div>' : '');
|
||||
if (navigator.vibrate) navigator.vibrate([500,150,500,150,500]);
|
||||
_mpPlayBeep();
|
||||
// Desbloquear y mostrar siguiente canvas
|
||||
var nw = nextFirmaId ? document.getElementById('fpw-' + nextFirmaId) : null;
|
||||
if (!nw) nw = document.querySelector('.firma-pro-widget[style*="display:none"]');
|
||||
if (nw) {
|
||||
nw.scrollIntoView({ behavior:'smooth', block:'center' });
|
||||
nw.style.display = '';
|
||||
nw.style.outline = '3px solid #dc3545';
|
||||
nw.style.borderRadius = '6px';
|
||||
nw.style.borderRadius = '8px';
|
||||
setTimeout(function() { nw.scrollIntoView({ behavior:'smooth', block:'center' }); }, 120);
|
||||
}
|
||||
return;
|
||||
}
|
||||
var m = Math.floor(left / 60000);
|
||||
var s = Math.floor((left % 60000) / 1000);
|
||||
box.innerHTML = '⏱ Próxima muestra'
|
||||
+ (nextLabel ? ' <small>(' + nextLabel + ')</small>' : '')
|
||||
+ ' en <strong>' + String(m).padStart(2,'0') + ':' + String(s).padStart(2,'0') + '</strong>';
|
||||
var pct = Math.max(0, left / (minutos * 60000)) * 100;
|
||||
box.style.background = pct < 15 ? '#f59e0b' : '#1565c0';
|
||||
box.style.animation = '';
|
||||
box.innerHTML = '<div style="font-size:.75rem;opacity:.85;margin-bottom:4px;text-transform:uppercase;letter-spacing:.05em">'
|
||||
+ '<i class="fas fa-clock me-1"></i>Próxima muestra' + (nextLabel ? ' · ' + nextLabel : '') + '</div>'
|
||||
+ '<div style="font-size:2.4rem;font-weight:900;font-family:monospace;line-height:1;letter-spacing:.04em">'
|
||||
+ String(m).padStart(2,'0') + '<span style="opacity:.6;animation:mp-pulse .8s infinite">:</span>' + String(s).padStart(2,'0')
|
||||
+ '</div>';
|
||||
}, 1000);
|
||||
}
|
||||
|
||||
|
||||
Reference in New Issue
Block a user