From 3dbf40e4fabf18a79f25f7853d5060d1326579cc Mon Sep 17 00:00:00 2001 From: Lizandro Guarnizo <77708265+lizandrogd@users.noreply.github.com> Date: Fri, 3 Jul 2026 12:49:39 -0500 Subject: [PATCH] feat(formulario): firma profesional directa en modal embebido (formularios solo-pro) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit - 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 --- .../api/firmar_profesional_consentimiento.php | 25 ++++-- ver_formulario_enviado.php | 78 ++++++++++++++----- 2 files changed, 76 insertions(+), 27 deletions(-) diff --git a/modules/turnero/api/firmar_profesional_consentimiento.php b/modules/turnero/api/firmar_profesional_consentimiento.php index 6a09abf..9b3b2af 100644 --- a/modules/turnero/api/firmar_profesional_consentimiento.php +++ b/modules/turnero/api/firmar_profesional_consentimiento.php @@ -16,6 +16,9 @@ $body = inputJson(); $turnoId = (int)($body['turno_id'] ?? 0); $formularioId = (int)($body['formulario_id'] ?? 0); $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 (!$formularioId) jsonError('formulario_id requerido.'); @@ -37,12 +40,22 @@ $tc = $stmt->fetch(PDO::FETCH_ASSOC); if (!$tc) jsonError('Consentimiento no encontrado.', 404); -$stmt = $pdo->prepare( - "UPDATE turnero_consentimientos - SET firma_profesional_svg = ?, firmado_profesional_at = NOW() - WHERE turno_id = ? AND formulario_id = ?" -); -$stmt->execute([$svg, $turnoId, $formularioId]); +if ($soloPro) { + // El profesional es el firmante final: guardar datos + marcar como firmado + $pdo->prepare( + "UPDATE turnero_consentimientos + SET firma_profesional_svg = ?, firmado_profesional_at = NOW(), + 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']); diff --git a/ver_formulario_enviado.php b/ver_formulario_enviado.php index 6f66022..4287875 100644 --- a/ver_formulario_enviado.php +++ b/ver_formulario_enviado.php @@ -189,6 +189,9 @@ $datosPrefilled = json_decode($envio['datos_prefilled'] ?? '{}', true) ?? []; $todos = array_merge($datosPrefilled, $datosCliente); $modoEditar = $modoTurnero && $envio['estado'] !== 'firmado'; $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 $labelMap = []; @@ -557,14 +560,19 @@ function esc2(mixed $v): string {
- - + +
- data-turno="" - data-formulario="" - > + data-solo-pro="" + data-turno="" + data-formulario="">

Dibuje su firma en el recuadro:

@@ -891,7 +899,6 @@ function esc2(mixed $v): string { btnSave.addEventListener('click', function() { const svg = canvas.toDataURL('image/png'); - // Verificar que no esté vacío (al menos 1000 bytes de data) if (svg.length < 1000) { msg.innerHTML = 'Por favor dibuje su firma antes de guardar.'; return; @@ -902,12 +909,32 @@ function esc2(mixed $v): string { const turnoId = widget.dataset.turno; const formularioId = widget.dataset.formulario; + const soloPro = widget.dataset.soloPro === '1'; const isTurnero = !!(turnoId && formularioId); const saveUrl = isTurnero ? 'modules/turnero/api/firmar_profesional_consentimiento.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 }; fetch(saveUrl, { @@ -918,18 +945,27 @@ function esc2(mixed $v): string { .then(function(r){ return r.json(); }) .then(function(data) { if (data.ok) { - // Reemplazar el canvas con la imagen firmada - const img = document.createElement('img'); - img.src = svg; - img.alt = 'Firma profesional'; - img.style.maxHeight = '140px'; - img.style.maxWidth = '340px'; - img.style.display = 'block'; - const box = document.createElement('div'); - box.className = 'firma-box'; - box.style.borderColor = '#198754'; - box.appendChild(img); - widget.replaceWith(box); + if (soloPro) { + // Modo solo-profesional: mostrar éxito y notificar al padre + document.querySelectorAll('.firma-pro-widget, .campo-edit, .section-title').forEach(function(el) { + el.style.display = 'none'; + }); + var ok = document.createElement('div'); + ok.className = 'alert alert-success mt-4 d-flex align-items-center gap-2 no-print'; + ok.innerHTML = '
Firmado correctamente.
Puede cerrar esta ventana.
'; + widget.parentNode.insertBefore(ok, widget.nextSibling); + widget.style.display = 'none'; + try { window.parent.postMessage({ type: 'turneroFirmado' }, '*'); } catch(e) {} + } 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 { msg.innerHTML = '' + (data.error||'Error al guardar') + ''; btnSave.disabled = false;