feat(formulario): firma profesional directa en modal embebido (formularios solo-pro)
- ver_formulario_enviado.php: pre-scan $_soloFirmaPro; muestra canvas pro cuando modoTurnero+embebido+sesión activa+sin firma paciente - fpw-save JS: recopila campos del form y envía solo_profesional=true; al guardar muestra éxito y postMessage turneroFirmado - firmar_profesional_consentimiento.php: si solo_profesional=true guarda datos_respuestas y marca estado=firmado Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
co-authored by
Claude Sonnet 4.6
parent
1e2cfbaa0b
commit
3dbf40e4fa
@@ -16,6 +16,9 @@ $body = inputJson();
|
|||||||
$turnoId = (int)($body['turno_id'] ?? 0);
|
$turnoId = (int)($body['turno_id'] ?? 0);
|
||||||
$formularioId = (int)($body['formulario_id'] ?? 0);
|
$formularioId = (int)($body['formulario_id'] ?? 0);
|
||||||
$svg = $body['svg'] ?? '';
|
$svg = $body['svg'] ?? '';
|
||||||
|
$soloPro = !empty($body['solo_profesional']);
|
||||||
|
$datosResp = (isset($body['datos_respuestas']) && is_array($body['datos_respuestas']))
|
||||||
|
? json_encode($body['datos_respuestas'], JSON_UNESCAPED_UNICODE) : null;
|
||||||
|
|
||||||
if (!$turnoId) jsonError('turno_id requerido.');
|
if (!$turnoId) jsonError('turno_id requerido.');
|
||||||
if (!$formularioId) jsonError('formulario_id requerido.');
|
if (!$formularioId) jsonError('formulario_id requerido.');
|
||||||
@@ -37,12 +40,22 @@ $tc = $stmt->fetch(PDO::FETCH_ASSOC);
|
|||||||
|
|
||||||
if (!$tc) jsonError('Consentimiento no encontrado.', 404);
|
if (!$tc) jsonError('Consentimiento no encontrado.', 404);
|
||||||
|
|
||||||
$stmt = $pdo->prepare(
|
if ($soloPro) {
|
||||||
"UPDATE turnero_consentimientos
|
// El profesional es el firmante final: guardar datos + marcar como firmado
|
||||||
SET firma_profesional_svg = ?, firmado_profesional_at = NOW()
|
$pdo->prepare(
|
||||||
WHERE turno_id = ? AND formulario_id = ?"
|
"UPDATE turnero_consentimientos
|
||||||
);
|
SET firma_profesional_svg = ?, firmado_profesional_at = NOW(),
|
||||||
$stmt->execute([$svg, $turnoId, $formularioId]);
|
estado = 'firmado', firmado_at = NOW(),
|
||||||
|
datos_respuestas = COALESCE(?, datos_respuestas)
|
||||||
|
WHERE turno_id = ? AND formulario_id = ?"
|
||||||
|
)->execute([$svg, $datosResp, $turnoId, $formularioId]);
|
||||||
|
} else {
|
||||||
|
$pdo->prepare(
|
||||||
|
"UPDATE turnero_consentimientos
|
||||||
|
SET firma_profesional_svg = ?, firmado_profesional_at = NOW()
|
||||||
|
WHERE turno_id = ? AND formulario_id = ?"
|
||||||
|
)->execute([$svg, $turnoId, $formularioId]);
|
||||||
|
}
|
||||||
|
|
||||||
notificarSSE((int)$tc['sesion_id']);
|
notificarSSE((int)$tc['sesion_id']);
|
||||||
|
|
||||||
|
|||||||
+57
-21
@@ -189,6 +189,9 @@ $datosPrefilled = json_decode($envio['datos_prefilled'] ?? '{}', true) ?? [];
|
|||||||
$todos = array_merge($datosPrefilled, $datosCliente);
|
$todos = array_merge($datosPrefilled, $datosCliente);
|
||||||
$modoEditar = $modoTurnero && $envio['estado'] !== 'firmado';
|
$modoEditar = $modoTurnero && $envio['estado'] !== 'firmado';
|
||||||
$embebido = isset($_GET['embed']) && $_GET['embed'] === '1';
|
$embebido = isset($_GET['embed']) && $_GET['embed'] === '1';
|
||||||
|
// Pre-scan: formulario que solo requiere firma del profesional (sin firma paciente)
|
||||||
|
$_soloFirmaPro = !empty(array_filter($esquema, fn($c) => ($c['tipo'] ?? '') === 'firma_profesional'))
|
||||||
|
&& empty(array_filter($esquema, fn($c) => ($c['tipo'] ?? '') === 'firma'));
|
||||||
|
|
||||||
// Mapa id → label
|
// Mapa id → label
|
||||||
$labelMap = [];
|
$labelMap = [];
|
||||||
@@ -557,14 +560,19 @@ function esc2(mixed $v): string {
|
|||||||
<div class="turnero-msg mt-2 small"></div>
|
<div class="turnero-msg mt-2 small"></div>
|
||||||
</div>
|
</div>
|
||||||
<?php elseif ($isPro): ?>
|
<?php elseif ($isPro): ?>
|
||||||
<?php if (!$modoTurnero && !$modoPublico && !$yaHayCanvasPro): ?>
|
<?php
|
||||||
<!-- Canvas del profesional: solo aparece una vez -->
|
// Mostrar canvas pro si: admin normal, O turnero+embebido+sesión activa+solo firma pro
|
||||||
|
$_mostrarCanvasPro = !$yaHayCanvasPro && (
|
||||||
|
(!$modoTurnero && !$modoPublico) ||
|
||||||
|
($modoTurnero && $embebido && isUserLoggedIn() && $_soloFirmaPro && $modoEditar)
|
||||||
|
);
|
||||||
|
if ($_mostrarCanvasPro): ?>
|
||||||
|
<!-- Canvas del profesional -->
|
||||||
<div class="firma-pro-widget no-print" id="fpw-<?= htmlspecialchars($cid) ?>"
|
<div class="firma-pro-widget no-print" id="fpw-<?= htmlspecialchars($cid) ?>"
|
||||||
data-envio="<?= (int)$envio['id'] ?>" data-campo="<?= htmlspecialchars($cid) ?>"
|
data-envio="<?= (int)$envio['id'] ?>" data-campo="<?= htmlspecialchars($cid) ?>"
|
||||||
<?php if ($modoTurnero): ?>
|
data-solo-pro="<?= ($modoTurnero && $_soloFirmaPro) ? '1' : '0' ?>"
|
||||||
data-turno="<?= (int)$tcRow['turno_id'] ?>"
|
data-turno="<?= isset($tcRow) ? (int)$tcRow['turno_id'] : '' ?>"
|
||||||
data-formulario="<?= (int)$tcRow['formulario_id'] ?>"
|
data-formulario="<?= isset($tcRow) ? (int)$tcRow['formulario_id'] : '' ?>">
|
||||||
<?php endif; ?>>
|
|
||||||
<p class="text-muted small mb-2"><i class="fas fa-pen me-1"></i>Dibuje su firma en el recuadro:</p>
|
<p class="text-muted small mb-2"><i class="fas fa-pen me-1"></i>Dibuje su firma en el recuadro:</p>
|
||||||
<canvas class="fpw-canvas" width="500" height="150"></canvas>
|
<canvas class="fpw-canvas" width="500" height="150"></canvas>
|
||||||
<div class="mt-2 d-flex gap-2">
|
<div class="mt-2 d-flex gap-2">
|
||||||
@@ -891,7 +899,6 @@ function esc2(mixed $v): string {
|
|||||||
|
|
||||||
btnSave.addEventListener('click', function() {
|
btnSave.addEventListener('click', function() {
|
||||||
const svg = canvas.toDataURL('image/png');
|
const svg = canvas.toDataURL('image/png');
|
||||||
// Verificar que no esté vacío (al menos 1000 bytes de data)
|
|
||||||
if (svg.length < 1000) {
|
if (svg.length < 1000) {
|
||||||
msg.innerHTML = '<span class="text-danger"><i class="fas fa-exclamation-triangle me-1"></i>Por favor dibuje su firma antes de guardar.</span>';
|
msg.innerHTML = '<span class="text-danger"><i class="fas fa-exclamation-triangle me-1"></i>Por favor dibuje su firma antes de guardar.</span>';
|
||||||
return;
|
return;
|
||||||
@@ -902,12 +909,32 @@ function esc2(mixed $v): string {
|
|||||||
|
|
||||||
const turnoId = widget.dataset.turno;
|
const turnoId = widget.dataset.turno;
|
||||||
const formularioId = widget.dataset.formulario;
|
const formularioId = widget.dataset.formulario;
|
||||||
|
const soloPro = widget.dataset.soloPro === '1';
|
||||||
const isTurnero = !!(turnoId && formularioId);
|
const isTurnero = !!(turnoId && formularioId);
|
||||||
const saveUrl = isTurnero
|
const saveUrl = isTurnero
|
||||||
? 'modules/turnero/api/firmar_profesional_consentimiento.php'
|
? 'modules/turnero/api/firmar_profesional_consentimiento.php'
|
||||||
: 'api/lab/firmar_profesional.php';
|
: 'api/lab/firmar_profesional.php';
|
||||||
const savePayload = isTurnero
|
|
||||||
? { turno_id: parseInt(turnoId), formulario_id: parseInt(formularioId), svg: svg }
|
// Recopilar campos del formulario cuando el pro es el firmante final
|
||||||
|
var datosRespuestas = {};
|
||||||
|
if (soloPro) {
|
||||||
|
document.querySelectorAll('[name]').forEach(function(el) {
|
||||||
|
var raw = el.name;
|
||||||
|
var isArr = raw.slice(-2) === '[]';
|
||||||
|
var name = isArr ? raw.slice(0, -2) : raw;
|
||||||
|
if (el.type === 'checkbox') {
|
||||||
|
if (el.checked) { if (!Array.isArray(datosRespuestas[name])) datosRespuestas[name] = []; datosRespuestas[name].push(el.value); }
|
||||||
|
} else if (el.type === 'radio') {
|
||||||
|
if (el.checked) datosRespuestas[name] = el.value;
|
||||||
|
} else if (el.value !== '') {
|
||||||
|
datosRespuestas[name] = el.value;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
const savePayload = isTurnero
|
||||||
|
? { turno_id: parseInt(turnoId), formulario_id: parseInt(formularioId), svg: svg,
|
||||||
|
...(soloPro ? { solo_profesional: true, datos_respuestas: datosRespuestas } : {}) }
|
||||||
: { envio_id: envioId, campo_id: campoId, svg: svg };
|
: { envio_id: envioId, campo_id: campoId, svg: svg };
|
||||||
|
|
||||||
fetch(saveUrl, {
|
fetch(saveUrl, {
|
||||||
@@ -918,18 +945,27 @@ function esc2(mixed $v): string {
|
|||||||
.then(function(r){ return r.json(); })
|
.then(function(r){ return r.json(); })
|
||||||
.then(function(data) {
|
.then(function(data) {
|
||||||
if (data.ok) {
|
if (data.ok) {
|
||||||
// Reemplazar el canvas con la imagen firmada
|
if (soloPro) {
|
||||||
const img = document.createElement('img');
|
// Modo solo-profesional: mostrar éxito y notificar al padre
|
||||||
img.src = svg;
|
document.querySelectorAll('.firma-pro-widget, .campo-edit, .section-title').forEach(function(el) {
|
||||||
img.alt = 'Firma profesional';
|
el.style.display = 'none';
|
||||||
img.style.maxHeight = '140px';
|
});
|
||||||
img.style.maxWidth = '340px';
|
var ok = document.createElement('div');
|
||||||
img.style.display = 'block';
|
ok.className = 'alert alert-success mt-4 d-flex align-items-center gap-2 no-print';
|
||||||
const box = document.createElement('div');
|
ok.innerHTML = '<i class="fas fa-check-circle fs-4"></i><div><strong>Firmado correctamente.</strong><br>Puede cerrar esta ventana.</div>';
|
||||||
box.className = 'firma-box';
|
widget.parentNode.insertBefore(ok, widget.nextSibling);
|
||||||
box.style.borderColor = '#198754';
|
widget.style.display = 'none';
|
||||||
box.appendChild(img);
|
try { window.parent.postMessage({ type: 'turneroFirmado' }, '*'); } catch(e) {}
|
||||||
widget.replaceWith(box);
|
} else {
|
||||||
|
// Reemplazar canvas con imagen firmada
|
||||||
|
var img = document.createElement('img');
|
||||||
|
img.src = svg; img.alt = 'Firma profesional';
|
||||||
|
img.style.maxHeight = '140px'; img.style.maxWidth = '340px'; img.style.display = 'block';
|
||||||
|
var box = document.createElement('div');
|
||||||
|
box.className = 'firma-box'; box.style.borderColor = '#198754';
|
||||||
|
box.appendChild(img);
|
||||||
|
widget.replaceWith(box);
|
||||||
|
}
|
||||||
} else {
|
} else {
|
||||||
msg.innerHTML = '<span class="text-danger"><i class="fas fa-times me-1"></i>' + (data.error||'Error al guardar') + '</span>';
|
msg.innerHTML = '<span class="text-danger"><i class="fas fa-times me-1"></i>' + (data.error||'Error al guardar') + '</span>';
|
||||||
btnSave.disabled = false;
|
btnSave.disabled = false;
|
||||||
|
|||||||
Reference in New Issue
Block a user