up
This commit is contained in:
@@ -0,0 +1,4 @@
|
||||
-- Agrega columna para guardar respuestas de campos al firmar presencialmente
|
||||
ALTER TABLE turnero_consentimientos
|
||||
ADD COLUMN IF NOT EXISTS datos_respuestas MEDIUMTEXT DEFAULT NULL
|
||||
COMMENT 'Respuestas del formulario en JSON (campos llenados al firmar presencialmente)';
|
||||
@@ -0,0 +1,52 @@
|
||||
<?php
|
||||
/**
|
||||
* Runner — Migración 20260616_turnero_consentimientos_datos
|
||||
* Agrega columna datos_respuestas a turnero_consentimientos.
|
||||
* Ejecutar una sola vez desde el navegador o CLI.
|
||||
*/
|
||||
|
||||
require_once __DIR__ . '/config/config.php';
|
||||
|
||||
$sqlFile = __DIR__ . '/migrations/20260616_turnero_consentimientos_datos.sql';
|
||||
|
||||
if (!file_exists($sqlFile)) {
|
||||
die("❌ Archivo no encontrado: $sqlFile\n");
|
||||
}
|
||||
|
||||
try {
|
||||
$pdo = Database::getInstance()->getConnection();
|
||||
$sql = file_get_contents($sqlFile);
|
||||
|
||||
$statements = array_filter(array_map('trim', explode(';', $sql)));
|
||||
|
||||
echo "<pre>🚀 Ejecutando migración: 20260616_turnero_consentimientos_datos\n\n";
|
||||
|
||||
foreach ($statements as $stmt) {
|
||||
$clean = preg_replace('/--[^\n]*/', '', $stmt);
|
||||
$clean = trim($clean);
|
||||
if (empty($clean)) continue;
|
||||
|
||||
try {
|
||||
$pdo->exec($clean);
|
||||
$preview = substr(preg_replace('/\s+/', ' ', $clean), 0, 120);
|
||||
echo "✓ {$preview}\n";
|
||||
} catch (PDOException $e) {
|
||||
$msg = $e->getMessage();
|
||||
if (str_contains($msg, 'Duplicate column') || str_contains($msg, 'errno: 1060')) {
|
||||
echo "ℹ️ (columna ya existe, omitida)\n";
|
||||
} else {
|
||||
throw $e;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
echo "\n✅ Migración completada.\n";
|
||||
echo " Columna agregada a <strong>turnero_consentimientos</strong>:\n";
|
||||
echo " • datos_respuestas (MEDIUMTEXT, JSON con campos llenados al firmar presencialmente)\n</pre>";
|
||||
|
||||
$cols = $pdo->query("SHOW COLUMNS FROM turnero_consentimientos")->fetchAll(PDO::FETCH_COLUMN);
|
||||
echo "<pre>📋 Columnas actuales: " . implode(', ', $cols) . "</pre>";
|
||||
|
||||
} catch (Throwable $e) {
|
||||
echo "<pre>❌ Error: " . htmlspecialchars($e->getMessage()) . "\n</pre>";
|
||||
}
|
||||
+130
-7
@@ -30,7 +30,7 @@ if ($modoTurnero) {
|
||||
$tcRow = $db->fetch(
|
||||
"SELECT tc.id, tc.turno_id, tc.formulario_id, tc.token, tc.estado,
|
||||
tc.enviado_at, tc.firmado_at, tc.ip_firma, tc.ua_firma, tc.firma_svg,
|
||||
tc.firma_profesional_svg, tc.firmado_profesional_at,
|
||||
tc.firma_profesional_svg, tc.firmado_profesional_at, tc.datos_respuestas,
|
||||
f.nombre AS form_nombre, f.categoria, f.descripcion AS form_descripcion,
|
||||
f.esquema, f.doc_encabezado, f.doc_subtitulo, f.doc_logo_base64, f.doc_color, f.doc_pie_pagina,
|
||||
p.nombre_completo AS paciente_nombre,
|
||||
@@ -58,6 +58,9 @@ if ($modoTurnero) {
|
||||
if (strlen($firmaSvg) < 100) {
|
||||
echo json_encode(['ok' => false, 'error' => 'Firma requerida']); exit;
|
||||
}
|
||||
$datosRespuestas = isset($input['datos_respuestas']) && is_array($input['datos_respuestas'])
|
||||
? json_encode($input['datos_respuestas'], JSON_UNESCAPED_UNICODE)
|
||||
: null;
|
||||
$ip = filter_var(
|
||||
$_SERVER['HTTP_X_FORWARDED_FOR'] ?? $_SERVER['REMOTE_ADDR'] ?? '',
|
||||
FILTER_VALIDATE_IP
|
||||
@@ -67,10 +70,10 @@ if ($modoTurnero) {
|
||||
$stmt = $pdo->prepare(
|
||||
"UPDATE turnero_consentimientos
|
||||
SET estado='firmado', firmado_at=NOW(), firma_svg=?,
|
||||
ip_firma=?, ua_firma=?, version_formulario=?
|
||||
ip_firma=?, ua_firma=?, version_formulario=?, datos_respuestas=?
|
||||
WHERE token=?"
|
||||
);
|
||||
$stmt->execute([$firmaSvg, $ip, $ua, (int)$tcRow['formulario_id'], $tokenTurnero]);
|
||||
$stmt->execute([$firmaSvg, $ip, $ua, (int)$tcRow['formulario_id'], $datosRespuestas, $tokenTurnero]);
|
||||
require_once __DIR__ . '/modules/turnero/api/_helpers.php';
|
||||
notificarSSE((int)$tcRow['sesion_id']);
|
||||
echo json_encode(['ok' => true, 'message' => 'Consentimiento firmado correctamente']); exit;
|
||||
@@ -99,7 +102,7 @@ if ($modoTurnero) {
|
||||
'enviado_por_nombre'=> $_SESSION['admin_user']['full_name'] ?? $_SESSION['admin_user']['username'] ?? 'Turnero',
|
||||
'enviado_por_email' => $_SESSION['admin_user']['email'] ?? null,
|
||||
'firma_profesional_svg' => $tcRow['firma_profesional_svg'] ?? null,
|
||||
'datos_cliente' => '{}',
|
||||
'datos_cliente' => $tcRow['datos_respuestas'] ?: '{}',
|
||||
'datos_prefilled' => json_encode(['__paciente' => [
|
||||
'nombre_completo' => $tcRow['paciente_nombre'] ?? '',
|
||||
'numero_documento' => $tcRow['numero_documento'] ?? '',
|
||||
@@ -184,6 +187,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';
|
||||
|
||||
// Mapa id → label
|
||||
$labelMap = [];
|
||||
@@ -299,6 +303,23 @@ function esc2(mixed $v): string {
|
||||
.fpw-canvas { border: 2px solid #198754; border-radius: 8px; background: #f8fff9;
|
||||
cursor: crosshair; display: block; max-width: 100%; touch-action: none; }
|
||||
|
||||
/* ── Campos interactivos (modo turnero editar) ──── */
|
||||
.campo-edit { margin-bottom: 14px; }
|
||||
.campo-edit label { display: block; font-size: 12px; color: #6c757d;
|
||||
font-weight: 600; margin-bottom: 4px; }
|
||||
.campo-edit .form-control,
|
||||
.campo-edit .form-select { font-size: 13px; border-color: #c3d3f7;
|
||||
background: #f8faff; }
|
||||
.campo-edit .form-control:focus,
|
||||
.campo-edit .form-select:focus { border-color: #1565c0;
|
||||
box-shadow: 0 0 0 3px rgba(21,101,192,.12); }
|
||||
.campo-edit .form-check-label { font-size: 13px; }
|
||||
.campo-linked { display: flex; gap: 16px; padding: 6px 0;
|
||||
border-bottom: 1px solid #f0f0f0; }
|
||||
.campo-linked-label { flex: 0 0 38%; font-size: 12px; color: #6c757d; }
|
||||
.campo-linked-valor { flex: 1; font-size: 13px; font-weight: 600;
|
||||
color: #1565c0; }
|
||||
|
||||
/* ═══════ ESTILOS DE IMPRESIÓN ═══════════════════ */
|
||||
@media print {
|
||||
body { background: #fff !important; font-size: 12px; }
|
||||
@@ -419,7 +440,10 @@ function esc2(mixed $v): string {
|
||||
<?php endif; ?>
|
||||
|
||||
<!-- Respuestas del formulario (en orden del esquema) -->
|
||||
<div class="section-title"><i class="fas fa-wpforms me-1"></i>Respuestas del formulario</div>
|
||||
<div class="section-title">
|
||||
<i class="fas fa-wpforms me-1"></i>
|
||||
<?= $modoEditar ? 'Formulario de consentimiento' : 'Respuestas del formulario' ?>
|
||||
</div>
|
||||
<?php
|
||||
$paciente = $datosPrefilled['__paciente'] ?? [];
|
||||
|
||||
@@ -521,7 +545,88 @@ function esc2(mixed $v): string {
|
||||
$cid = $campo['id'] ?? null;
|
||||
if (!$cid) continue;
|
||||
|
||||
// ── Linked: valor viene del paciente prefilled ────────
|
||||
// ── Modo editar (turnero, consentimiento pendiente): campos interactivos ──
|
||||
if ($modoEditar):
|
||||
$prefill = $todos[$cid] ?? '';
|
||||
$label = esc2($campo['label'] ?? $cid);
|
||||
$req = !empty($campo['required']) ? ' required' : '';
|
||||
if ($tipo === 'linked'):
|
||||
$lk = $campo['linked_key'] ?? '';
|
||||
$lval = $paciente[$lk] ?? $todos[$cid] ?? '';
|
||||
if ($lval !== ''):
|
||||
?>
|
||||
<div class="campo-linked">
|
||||
<div class="campo-linked-label"><?= $label ?></div>
|
||||
<div class="campo-linked-valor"><?= esc2($lval) ?></div>
|
||||
</div>
|
||||
<?php endif; continue; endif; // linked
|
||||
if ($tipo === 'textarea'):
|
||||
?>
|
||||
<div class="campo-edit">
|
||||
<label><?= $label ?></label>
|
||||
<textarea class="form-control form-control-sm" name="<?= esc2($cid) ?>"
|
||||
rows="3"<?= $req ?>><?= esc2($prefill) ?></textarea>
|
||||
</div>
|
||||
<?php continue; endif;
|
||||
if ($tipo === 'radio'):
|
||||
$opts = $campo['opciones'] ?? [];
|
||||
?>
|
||||
<div class="campo-edit">
|
||||
<label><?= $label ?></label>
|
||||
<?php foreach ($opts as $opt): ?>
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="radio"
|
||||
name="<?= esc2($cid) ?>" value="<?= esc2($opt) ?>"
|
||||
<?= ($prefill === $opt ? 'checked' : '') . $req ?>>
|
||||
<label class="form-check-label"><?= esc2($opt) ?></label>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
<?php continue; endif;
|
||||
if ($tipo === 'checkbox' || $tipo === 'lista_marcable'):
|
||||
$opts = $campo['opciones'] ?? [];
|
||||
$checkedArr = is_array($prefill) ? $prefill
|
||||
: (is_string($prefill) && $prefill !== '' ? (json_decode($prefill, true) ?: [$prefill]) : []);
|
||||
?>
|
||||
<div class="campo-edit">
|
||||
<label><?= $label ?></label>
|
||||
<?php foreach ($opts as $opt): ?>
|
||||
<div class="form-check">
|
||||
<input class="form-check-input" type="checkbox"
|
||||
name="<?= esc2($cid) ?>[]" value="<?= esc2($opt) ?>"
|
||||
<?= in_array($opt, $checkedArr, true) ? 'checked' : '' ?>>
|
||||
<label class="form-check-label"><?= esc2($opt) ?></label>
|
||||
</div>
|
||||
<?php endforeach; ?>
|
||||
</div>
|
||||
<?php continue; endif;
|
||||
if ($tipo === 'select'):
|
||||
$opts = $campo['opciones'] ?? [];
|
||||
?>
|
||||
<div class="campo-edit">
|
||||
<label><?= $label ?></label>
|
||||
<select class="form-select form-select-sm" name="<?= esc2($cid) ?>"<?= $req ?>>
|
||||
<option value="">— Seleccione —</option>
|
||||
<?php foreach ($opts as $opt): ?>
|
||||
<option value="<?= esc2($opt) ?>"<?= $prefill === $opt ? ' selected' : '' ?>><?= esc2($opt) ?></option>
|
||||
<?php endforeach; ?>
|
||||
</select>
|
||||
</div>
|
||||
<?php continue; endif;
|
||||
// texto, numero, fecha, hora, y cualquier otro → input
|
||||
$inputType = match($tipo) {
|
||||
'numero' => 'number', 'fecha' => 'date', 'hora' => 'time', default => 'text'
|
||||
};
|
||||
?>
|
||||
<div class="campo-edit">
|
||||
<label><?= $label ?></label>
|
||||
<input type="<?= $inputType ?>" class="form-control form-control-sm"
|
||||
name="<?= esc2($cid) ?>" value="<?= esc2($prefill) ?>"<?= $req ?>>
|
||||
</div>
|
||||
<?php continue;
|
||||
endif; // modoEditar
|
||||
|
||||
// ── Modo vista: mostrar valores existentes ─────────────
|
||||
if ($tipo === 'linked') {
|
||||
$lk = $campo['linked_key'] ?? '';
|
||||
$valor = $paciente[$lk] ?? $todos[$cid] ?? null;
|
||||
@@ -800,10 +905,28 @@ function esc2(mixed $v): string {
|
||||
btnFirm.innerHTML = '<i class="fas fa-spinner fa-spin me-1"></i>Guardando...';
|
||||
msgEl.textContent = '';
|
||||
|
||||
// Recopilar respuestas de campos interactivos
|
||||
var campos = {};
|
||||
document.querySelectorAll('[name]').forEach(function(el) {
|
||||
var rawName = el.name;
|
||||
var isArr = rawName.slice(-2) === '[]';
|
||||
var name = isArr ? rawName.slice(0, -2) : rawName;
|
||||
if (el.type === 'checkbox') {
|
||||
if (el.checked) {
|
||||
if (!Array.isArray(campos[name])) campos[name] = [];
|
||||
campos[name].push(el.value);
|
||||
}
|
||||
} else if (el.type === 'radio') {
|
||||
if (el.checked) campos[name] = el.value;
|
||||
} else if (el.value !== '') {
|
||||
campos[name] = el.value;
|
||||
}
|
||||
});
|
||||
|
||||
fetch(window.location.href, {
|
||||
method: 'POST',
|
||||
headers: { 'Content-Type': 'application/json' },
|
||||
body: JSON.stringify({ firma_svg: png })
|
||||
body: JSON.stringify({ firma_svg: png, datos_respuestas: campos })
|
||||
})
|
||||
.then(function(r) { return r.json(); })
|
||||
.then(function(data) {
|
||||
|
||||
Reference in New Issue
Block a user