Compare commits
7
Commits
7b734195e0
..
master
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
9749d726fa | ||
|
|
339ca8a40d | ||
|
|
3dbf40e4fa | ||
|
|
1e2cfbaa0b | ||
|
|
b399c3710e | ||
|
|
a0189a61eb | ||
|
|
27135e4d2f |
@@ -64,10 +64,60 @@ try {
|
|||||||
|
|
||||||
$estadoActual = $turno['estado'];
|
$estadoActual = $turno['estado'];
|
||||||
|
|
||||||
// Estados finales no modificables
|
// Estados finales — solo admin (sin role_id) puede reabrir
|
||||||
if (in_array($estadoActual, ['finalizado', 'ausente', 'cancelado'], true)) {
|
if (in_array($estadoActual, ['finalizado', 'ausente', 'cancelado'], true)) {
|
||||||
$pdo->rollBack();
|
$roleId = $_SESSION['admin_user']['role_id'] ?? null;
|
||||||
jsonError("El turno ya está en estado '{$estadoActual}' y no puede modificarse.", 422);
|
if (!empty($roleId)) {
|
||||||
|
$pdo->rollBack();
|
||||||
|
jsonError("El turno ya está en estado '{$estadoActual}' y no puede modificarse.", 422);
|
||||||
|
}
|
||||||
|
|
||||||
|
$permitidosAdmin = ['en_servicio', 'en_espera_lugar', 'en_recepcion', 'espera'];
|
||||||
|
if (!in_array($nuevoEstado, $permitidosAdmin, true)) {
|
||||||
|
$pdo->rollBack();
|
||||||
|
jsonError("Estado destino inválido para reapertura. Opciones: " . implode(', ', $permitidosAdmin), 422);
|
||||||
|
}
|
||||||
|
|
||||||
|
$sets = ['estado = ?'];
|
||||||
|
$binds = [$nuevoEstado];
|
||||||
|
|
||||||
|
if ($nuevoEstado === 'en_servicio') {
|
||||||
|
$sets[] = 'fin_lugar_at = NULL';
|
||||||
|
$sets[] = 'inicio_lugar_at = COALESCE(inicio_lugar_at, NOW())';
|
||||||
|
$sets[] = 'atendido_lugar_por = COALESCE(atendido_lugar_por, ?)';
|
||||||
|
$binds[] = adminId();
|
||||||
|
} elseif ($nuevoEstado === 'en_espera_lugar') {
|
||||||
|
$sets[] = 'fin_lugar_at = NULL';
|
||||||
|
$sets[] = 'inicio_lugar_at = NULL';
|
||||||
|
$sets[] = 'llamado_lugar_at = NULL';
|
||||||
|
} elseif ($nuevoEstado === 'espera') {
|
||||||
|
$sets[] = 'llamado_recepcion_at = NULL';
|
||||||
|
$sets[] = 'inicio_recepcion_at = NULL';
|
||||||
|
$sets[] = 'fin_recepcion_at = NULL';
|
||||||
|
$sets[] = 'llamado_lugar_at = NULL';
|
||||||
|
$sets[] = 'inicio_lugar_at = NULL';
|
||||||
|
$sets[] = 'fin_lugar_at = NULL';
|
||||||
|
$sets[] = 'atendido_recepcion_por = NULL';
|
||||||
|
$sets[] = 'atendido_lugar_por = NULL';
|
||||||
|
$sets[] = 'recepcion_desk_id = NULL';
|
||||||
|
}
|
||||||
|
|
||||||
|
$sql = 'UPDATE turnero_turnos SET ' . implode(', ', $sets) . ' WHERE id = ?';
|
||||||
|
$binds[] = $turnoId;
|
||||||
|
$pdo->prepare($sql)->execute($binds);
|
||||||
|
$pdo->commit();
|
||||||
|
|
||||||
|
$stmt = $pdo->prepare(
|
||||||
|
'SELECT t.*, p.codigo AS prioridad_codigo, p.nombre AS prioridad_nombre, p.color AS prioridad_color
|
||||||
|
FROM turnero_turnos t
|
||||||
|
JOIN turnero_prioridades p ON p.id = t.prioridad_id
|
||||||
|
WHERE t.id = ?'
|
||||||
|
);
|
||||||
|
$stmt->execute([$turnoId]);
|
||||||
|
$turnoActualizado = $stmt->fetch(PDO::FETCH_ASSOC);
|
||||||
|
|
||||||
|
notificarSSE((int) $turnoActualizado['sesion_id']);
|
||||||
|
jsonOk(['turno' => $turnoActualizado], "Turno reabierto: estado actualizado a '{$nuevoEstado}'");
|
||||||
}
|
}
|
||||||
|
|
||||||
// Verificar que la transición sea válida
|
// Verificar que la transición sea válida
|
||||||
|
|||||||
@@ -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']);
|
||||||
|
|
||||||
|
|||||||
@@ -111,6 +111,21 @@ Layout::open('Historial de Turnos', 'fas fa-history');
|
|||||||
.doc-pill.enviado { background:#fef9c3; color:#78350f; border:1px solid #fde68a; }
|
.doc-pill.enviado { background:#fef9c3; color:#78350f; border:1px solid #fde68a; }
|
||||||
.doc-pill.pendiente{ background:#f1f5f9; color:#64748b; border:1px solid #e2e8f0; }
|
.doc-pill.pendiente{ background:#f1f5f9; color:#64748b; border:1px solid #e2e8f0; }
|
||||||
.docs-empty { font-size:.78rem; color:#94a3b8; font-style:italic; }
|
.docs-empty { font-size:.78rem; color:#94a3b8; font-style:italic; }
|
||||||
|
|
||||||
|
/* ── Modal cambio de estado ── */
|
||||||
|
.estado-modal-backdrop {
|
||||||
|
position:fixed; inset:0; background:rgba(0,0,0,.45); z-index:1050;
|
||||||
|
display:flex; align-items:center; justify-content:center;
|
||||||
|
}
|
||||||
|
.estado-modal {
|
||||||
|
background:#fff; border-radius:14px; padding:24px 28px; width:340px;
|
||||||
|
box-shadow:0 20px 60px rgba(0,0,0,.2);
|
||||||
|
}
|
||||||
|
.estado-modal h5 { font-size:.95rem; font-weight:700; margin-bottom:4px; }
|
||||||
|
.estado-modal .sub { font-size:.78rem; color:#64748b; margin-bottom:16px; }
|
||||||
|
.estado-modal select { width:100%; padding:8px 10px; border:1px solid #e2e8f0;
|
||||||
|
border-radius:8px; font-size:.85rem; margin-bottom:16px; }
|
||||||
|
.estado-modal .actions { display:flex; gap:8px; justify-content:flex-end; }
|
||||||
</style>
|
</style>
|
||||||
|
|
||||||
<!-- ── Encabezado ────────────────────────────────────────────── -->
|
<!-- ── Encabezado ────────────────────────────────────────────── -->
|
||||||
@@ -445,12 +460,17 @@ function renderTabla(turnos) {
|
|||||||
<td><span class="eb ${cls}">${lbl}</span></td>
|
<td><span class="eb ${cls}">${lbl}</span></td>
|
||||||
<td class="text-nowrap">${espMin}</td>
|
<td class="text-nowrap">${espMin}</td>
|
||||||
<td class="text-nowrap">${srvMin}</td>
|
<td class="text-nowrap">${srvMin}</td>
|
||||||
<td>
|
<td class="text-nowrap">
|
||||||
<button class="btn btn-sm btn-outline-secondary py-0 px-2"
|
<button class="btn btn-sm btn-outline-secondary py-0 px-2 me-1"
|
||||||
onclick="toggleDetalle('${rowId}', this, ${t.id})"
|
onclick="toggleDetalle('${rowId}', this, ${t.id})"
|
||||||
title="Ver detalles">
|
title="Ver detalles">
|
||||||
<i class="fas fa-chevron-down" style="font-size:.65rem"></i>
|
<i class="fas fa-chevron-down" style="font-size:.65rem"></i>
|
||||||
</button>
|
</button>
|
||||||
|
<button class="btn btn-sm btn-outline-warning py-0 px-2"
|
||||||
|
onclick="abrirModalEstado(${t.id}, '${t.estado}', '${nombre.replace(/'/g,\'\\\'')}')"
|
||||||
|
title="Cambiar estado">
|
||||||
|
<i class="fas fa-exchange-alt" style="font-size:.65rem"></i>
|
||||||
|
</button>
|
||||||
</td>
|
</td>
|
||||||
</tr>
|
</tr>
|
||||||
<tr id="${rowId}" class="detail-row" style="display:none">
|
<tr id="${rowId}" class="detail-row" style="display:none">
|
||||||
@@ -641,6 +661,82 @@ function exportarCSV() {
|
|||||||
window.open(`${API}export_historial.php?${params}`, '_blank');
|
window.open(`${API}export_historial.php?${params}`, '_blank');
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ── Cambio de estado ─────────────────────────────────────────
|
||||||
|
const ESTADOS_OPCIONES = {
|
||||||
|
espera : [{v:'en_recepcion', l:'En recepción'}, {v:'ausente', l:'Ausente'}, {v:'cancelado', l:'Cancelado'}],
|
||||||
|
en_recepcion : [{v:'en_espera_lugar', l:'Esp. lugar'}, {v:'espera', l:'Espera'}, {v:'ausente', l:'Ausente'}, {v:'cancelado', l:'Cancelado'}],
|
||||||
|
en_espera_lugar : [{v:'en_servicio', l:'En servicio'}, {v:'ausente', l:'Ausente'}, {v:'cancelado', l:'Cancelado'}],
|
||||||
|
en_servicio : [{v:'finalizado', l:'Finalizado'}, {v:'en_espera_lugar', l:'Esp. lugar'}, {v:'ausente', l:'Ausente'}, {v:'cancelado', l:'Cancelado'}],
|
||||||
|
// estados finales: reapertura (admin)
|
||||||
|
finalizado : [{v:'en_servicio', l:'Reabrir → En servicio'}, {v:'en_espera_lugar', l:'Reabrir → Esp. lugar'}, {v:'espera', l:'Reabrir → Espera'}],
|
||||||
|
ausente : [{v:'espera', l:'Reabrir → Espera'}, {v:'en_servicio', l:'Reabrir → En servicio'}],
|
||||||
|
cancelado : [{v:'espera', l:'Reabrir → Espera'}, {v:'en_servicio', l:'Reabrir → En servicio'}],
|
||||||
|
};
|
||||||
|
|
||||||
|
let _modalTurnoId = null;
|
||||||
|
|
||||||
|
function abrirModalEstado(turnoId, estadoActual, nombre) {
|
||||||
|
_modalTurnoId = turnoId;
|
||||||
|
const opciones = ESTADOS_OPCIONES[estadoActual] || [];
|
||||||
|
if (!opciones.length) { alert('No hay transiciones disponibles para este estado.'); return; }
|
||||||
|
|
||||||
|
const sel = opciones.map(o => `<option value="${o.v}">${o.l}</option>`).join('');
|
||||||
|
const esReopetura = ['finalizado','ausente','cancelado'].includes(estadoActual);
|
||||||
|
|
||||||
|
const backdrop = document.createElement('div');
|
||||||
|
backdrop.className = 'estado-modal-backdrop';
|
||||||
|
backdrop.id = 'estadoModalBackdrop';
|
||||||
|
backdrop.innerHTML = `
|
||||||
|
<div class="estado-modal" onclick="event.stopPropagation()">
|
||||||
|
<h5><i class="fas fa-exchange-alt me-2 text-warning"></i>Cambiar estado del turno</h5>
|
||||||
|
<div class="sub">${nombre}${esReopetura ? ' <span class="badge bg-warning text-dark ms-1">Reapertura admin</span>' : ''}</div>
|
||||||
|
<select id="selectNuevoEstado">${sel}</select>
|
||||||
|
<div class="actions">
|
||||||
|
<button class="btn btn-sm btn-outline-secondary" onclick="cerrarModalEstado()">Cancelar</button>
|
||||||
|
<button class="btn btn-sm btn-warning" onclick="confirmarCambioEstado()">
|
||||||
|
<i class="fas fa-check me-1"></i>Confirmar
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
</div>`;
|
||||||
|
backdrop.addEventListener('click', cerrarModalEstado);
|
||||||
|
document.body.appendChild(backdrop);
|
||||||
|
}
|
||||||
|
|
||||||
|
function cerrarModalEstado() {
|
||||||
|
document.getElementById('estadoModalBackdrop')?.remove();
|
||||||
|
_modalTurnoId = null;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function confirmarCambioEstado() {
|
||||||
|
const nuevoEstado = document.getElementById('selectNuevoEstado').value;
|
||||||
|
if (!_modalTurnoId || !nuevoEstado) return;
|
||||||
|
|
||||||
|
const btn = document.querySelector('#estadoModalBackdrop .btn-warning');
|
||||||
|
btn.disabled = true;
|
||||||
|
btn.innerHTML = '<i class="fas fa-spinner fa-spin me-1"></i>Guardando…';
|
||||||
|
|
||||||
|
try {
|
||||||
|
const res = await fetch(`${API}cambiar_estado.php`, {
|
||||||
|
method : 'POST',
|
||||||
|
headers: { 'Content-Type': 'application/json' },
|
||||||
|
body : JSON.stringify({ turno_id: _modalTurnoId, nuevo_estado: nuevoEstado }),
|
||||||
|
});
|
||||||
|
const json = await res.json();
|
||||||
|
if (json.ok) {
|
||||||
|
cerrarModalEstado();
|
||||||
|
buscar(_state.page);
|
||||||
|
} else {
|
||||||
|
alert('Error: ' + (json.error || 'No se pudo cambiar el estado.'));
|
||||||
|
btn.disabled = false;
|
||||||
|
btn.innerHTML = '<i class="fas fa-check me-1"></i>Confirmar';
|
||||||
|
}
|
||||||
|
} catch(e) {
|
||||||
|
alert('Error de conexión');
|
||||||
|
btn.disabled = false;
|
||||||
|
btn.innerHTML = '<i class="fas fa-check me-1"></i>Confirmar';
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// ── Helpers visuales ──────────────────────────────────────────
|
// ── Helpers visuales ──────────────────────────────────────────
|
||||||
function mostrarSpinner() {
|
function mostrarSpinner() {
|
||||||
document.getElementById('tbody').innerHTML =
|
document.getElementById('tbody').innerHTML =
|
||||||
|
|||||||
+111
-56
@@ -321,18 +321,7 @@ require_once __DIR__ . '/../../../shared/components/sidebar.php';
|
|||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<!-- ── Sección: Consentimientos ── -->
|
|
||||||
<!-- ── Formulario embebido (modo=embebido) ── -->
|
|
||||||
<div class="ficha-sec d-none" id="sec-form-embebido">
|
|
||||||
<h6><i class="fas fa-file-alt me-1"></i>Formulario de consentimiento</h6>
|
|
||||||
<div id="form-embebido-loading" class="text-muted small py-2">
|
|
||||||
<i class="fas fa-spinner fa-spin me-1"></i>Cargando formulario...
|
|
||||||
</div>
|
|
||||||
<iframe id="form-embebido-iframe" src="" frameborder="0"
|
|
||||||
style="width:100%;min-height:520px;border-radius:8px;border:1px solid #e2e8f0;display:none"
|
|
||||||
onload="this.style.display='block';document.getElementById('form-embebido-loading').style.display='none'">
|
|
||||||
</iframe>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div class="ficha-sec" id="sec-consent">
|
<div class="ficha-sec" id="sec-consent">
|
||||||
<h6><i class="fas fa-file-signature me-1"></i>Consentimientos informados</h6>
|
<h6><i class="fas fa-file-signature me-1"></i>Consentimientos informados</h6>
|
||||||
@@ -393,6 +382,38 @@ require_once __DIR__ . '/../../../shared/components/sidebar.php';
|
|||||||
</div><!-- /lugar-layout -->
|
</div><!-- /lugar-layout -->
|
||||||
</main>
|
</main>
|
||||||
|
|
||||||
|
<!-- ── Modal de consentimiento embebido ──────────────────────── -->
|
||||||
|
<div id="modal-consentimiento" style="
|
||||||
|
display:none;position:fixed;inset:0;z-index:9000;
|
||||||
|
background:rgba(0,0,0,.55);align-items:center;justify-content:center;padding:16px">
|
||||||
|
<div style="
|
||||||
|
background:#fff;border-radius:14px;box-shadow:0 8px 40px rgba(0,0,0,.25);
|
||||||
|
width:min(96vw,680px);max-height:92vh;
|
||||||
|
display:flex;flex-direction:column;overflow:hidden">
|
||||||
|
<!-- Header modal -->
|
||||||
|
<div style="
|
||||||
|
display:flex;align-items:center;justify-content:space-between;
|
||||||
|
padding:12px 18px;border-bottom:1px solid #e2e8f0;flex-shrink:0">
|
||||||
|
<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">
|
||||||
|
<i class="fas fa-times"></i>
|
||||||
|
</button>
|
||||||
|
</div>
|
||||||
|
<!-- Loading -->
|
||||||
|
<div id="modal-consent-loading" style="padding:32px;text-align:center;color:#64748b;flex-shrink:0">
|
||||||
|
<i class="fas fa-spinner fa-spin fa-2x mb-2 d-block"></i>Cargando formulario…
|
||||||
|
</div>
|
||||||
|
<!-- Iframe -->
|
||||||
|
<iframe id="modal-consent-iframe" src="" frameborder="0"
|
||||||
|
style="flex:1;border:none;display:none;min-height:60vh"
|
||||||
|
onload="document.getElementById('modal-consent-loading').style.display='none';this.style.display='block'">
|
||||||
|
</iframe>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
|
||||||
|
|
||||||
<script>
|
<script>
|
||||||
// ── Estado global ─────────────────────────────────────────────
|
// ── Estado global ─────────────────────────────────────────────
|
||||||
@@ -725,29 +746,40 @@ function renderConsentimientos(lista) {
|
|||||||
const idJs = parseInt(c.id) || 0;
|
const idJs = parseInt(c.id) || 0;
|
||||||
const tId = parseInt(c.turno_id) || (turnoActivo?.id) || 0;
|
const tId = parseInt(c.turno_id) || (turnoActivo?.id) || 0;
|
||||||
|
|
||||||
// Firmar aquí: solo si no completado y hay token
|
// Botón ver (firmado)
|
||||||
const btnFirmar = (!ya && c.token)
|
const btnVer = (ya && c.token)
|
||||||
? `<button class="btn btn-outline-primary" title="Firmar aquí"
|
? `<a href="${BASE_WA}ver_formulario_enviado.php?token=${token}" target="_blank"
|
||||||
onclick="abrirFirmaPresencial('${token}', ${idJs}, ${nomJs.replace(/"/g,'"')})">
|
class="btn btn-outline-secondary" title="Ver firmado">
|
||||||
<i class="fas fa-signature"></i> Firmar
|
<i class="fas fa-eye"></i> Ver
|
||||||
</button>` : '';
|
</a>` : '';
|
||||||
|
|
||||||
// WA / Ver: según estado
|
// Acción principal según modo y estado
|
||||||
const btnWa = ya
|
let btnAccion = '';
|
||||||
? (c.token ? `<a href="${BASE_WA}ver_formulario_enviado.php?token=${token}" target="_blank"
|
if (!ya && c.token) {
|
||||||
class="btn btn-outline-secondary" title="Ver firmado">
|
if (LUGAR_FORM_MODO === 'embebido') {
|
||||||
<i class="fas fa-eye"></i> Ver
|
// Modo embebido: abrir modal directamente
|
||||||
</a>` : '')
|
btnAccion = `<button class="btn btn-primary" title="Abrir formulario"
|
||||||
: `<button class="btn btn-outline-success" title="Reenviar por WhatsApp"
|
onclick="abrirModalConsentimientoPorToken('${token}')">
|
||||||
onclick="reenviarConsentimiento(${tId})">
|
<i class="fas fa-pen me-1"></i>Firmar
|
||||||
<i class="fab fa-whatsapp"></i> WA
|
</button>`;
|
||||||
</button>`;
|
} else {
|
||||||
|
// Modo link: firmar presencial o reenviar WA
|
||||||
|
btnAccion = `<button class="btn btn-outline-primary" title="Firmar aquí"
|
||||||
|
onclick="abrirFirmaPresencial('${token}', ${idJs}, ${nomJs.replace(/"/g,'"')})">
|
||||||
|
<i class="fas fa-signature"></i> Firmar
|
||||||
|
</button>
|
||||||
|
<button class="btn btn-outline-success" title="Reenviar por WhatsApp"
|
||||||
|
onclick="reenviarConsentimiento(${tId})">
|
||||||
|
<i class="fab fa-whatsapp"></i> WA
|
||||||
|
</button>`;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return `<div class="consent-row ${c.estado}" data-consent-id="${c.id}">
|
return `<div class="consent-row ${c.estado}" data-consent-id="${c.id}">
|
||||||
<i class="fas ${ico}"></i>
|
<i class="fas ${ico}"></i>
|
||||||
<span class="nom-form">${escHtml(c.formulario_nombre || 'Consentimiento')}</span>
|
<span class="nom-form">${escHtml(c.formulario_nombre || 'Consentimiento')}</span>
|
||||||
<span class="c-badge">${label}</span>
|
<span class="c-badge">${label}</span>
|
||||||
<div class="acciones-consent">${btnFirmar}${btnWa}</div>
|
<div class="acciones-consent">${btnAccion}${btnVer}</div>
|
||||||
</div>`;
|
</div>`;
|
||||||
}).join('');
|
}).join('');
|
||||||
}
|
}
|
||||||
@@ -858,41 +890,64 @@ async function cambiarEstadoTurno(nuevoEstado) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ── Reset ─────────────────────────────────────────────────────
|
// ── Embebido como modal ───────────────────────────────────────
|
||||||
async function cargarFormEmbebido(turnoId) {
|
let _consentTokenCache = null; // { turnoId, token }
|
||||||
const sec = document.getElementById('sec-form-embebido');
|
|
||||||
const iframe = document.getElementById('form-embebido-iframe');
|
// cargarFormEmbebido ya no necesita hacer nada (el token viene de la fila de consentimiento)
|
||||||
const load = document.getElementById('form-embebido-loading');
|
async function cargarFormEmbebido(turnoId) { /* token llega vía renderConsentimientos */ }
|
||||||
if (!sec || !iframe) return;
|
|
||||||
sec.classList.remove('d-none');
|
function _abrirModalConToken(token) {
|
||||||
|
const modal = document.getElementById('modal-consentimiento');
|
||||||
|
const iframe = document.getElementById('modal-consent-iframe');
|
||||||
|
const load = document.getElementById('modal-consent-loading');
|
||||||
iframe.style.display = 'none';
|
iframe.style.display = 'none';
|
||||||
load.style.display = '';
|
iframe.src = '';
|
||||||
try {
|
load.style.display = '';
|
||||||
const res = await fetch(`${API}get_consent_token.php?turno_id=${turnoId}&lugar_id=${lugarId}`);
|
modal.style.display = 'flex';
|
||||||
const json = await res.json();
|
setTimeout(() => {
|
||||||
if (json.ok && json.token) {
|
iframe.src = `${BASE_WA}ver_formulario_enviado.php?token=${encodeURIComponent(token)}&embed=1`;
|
||||||
iframe.src = `${BASE_WA}ver_formulario_enviado.php?token=${encodeURIComponent(json.token)}`;
|
}, 80);
|
||||||
} else {
|
|
||||||
load.innerHTML = '<span class="text-danger small"><i class="fas fa-exclamation-triangle me-1"></i>' + (json.error || 'No hay formulario configurado') + '</span>';
|
|
||||||
}
|
|
||||||
} catch (e) {
|
|
||||||
load.innerHTML = '<span class="text-danger small"><i class="fas fa-exclamation-triangle me-1"></i>Error al cargar formulario</span>';
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
function abrirModalConsentimiento() {
|
||||||
|
if (!_consentTokenCache?.token) { mostrarError('Token de consentimiento no disponible.'); return; }
|
||||||
|
_abrirModalConToken(_consentTokenCache.token);
|
||||||
|
}
|
||||||
|
|
||||||
|
function abrirModalConsentimientoPorToken(token) {
|
||||||
|
_abrirModalConToken(token);
|
||||||
|
}
|
||||||
|
|
||||||
|
function cerrarModalConsentimiento() {
|
||||||
|
const modal = document.getElementById('modal-consentimiento');
|
||||||
|
const iframe = document.getElementById('modal-consent-iframe');
|
||||||
|
modal.style.display = 'none';
|
||||||
|
iframe.src = '';
|
||||||
|
iframe.style.display = 'none';
|
||||||
|
document.getElementById('modal-consent-loading').style.display = '';
|
||||||
|
// Refrescar estado del consentimiento
|
||||||
|
if (turnoActivo) actualizarConsentimientos(turnoActivo.id);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Cerrar modal si se firma desde el iframe
|
||||||
|
window.addEventListener('message', function(e) {
|
||||||
|
if (e.data && e.data.type === 'turneroFirmado') {
|
||||||
|
cerrarModalConsentimiento();
|
||||||
|
mostrarToast('Consentimiento firmado ✓', 'success', 3000);
|
||||||
|
if (turnoActivo) actualizarConsentimientos(turnoActivo.id);
|
||||||
|
// Ocultar botón de firma
|
||||||
|
const sec = document.getElementById('sec-form-embebido');
|
||||||
|
if (sec) sec.classList.add('d-none');
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
function resetFicha() {
|
function resetFicha() {
|
||||||
clearInterval(pollingConsentId);
|
clearInterval(pollingConsentId);
|
||||||
turnoActivo = null;
|
turnoActivo = null;
|
||||||
tieneConsent = false;
|
tieneConsent = false;
|
||||||
hayPendientes = false;
|
hayPendientes = false;
|
||||||
|
_consentTokenCache = null;
|
||||||
// Limpiar iframe embebido
|
cerrarModalConsentimiento();
|
||||||
const iframe = document.getElementById('form-embebido-iframe');
|
|
||||||
const sec = document.getElementById('sec-form-embebido');
|
|
||||||
const load = document.getElementById('form-embebido-loading');
|
|
||||||
if (iframe) { iframe.src = ''; iframe.style.display = 'none'; }
|
|
||||||
if (sec) { sec.classList.add('d-none'); }
|
|
||||||
if (load) { load.style.display = ''; load.innerHTML = '<i class="fas fa-spinner fa-spin me-1"></i>Cargando formulario...'; }
|
|
||||||
|
|
||||||
document.getElementById('ficha-turno').classList.add('d-none');
|
document.getElementById('ficha-turno').classList.add('d-none');
|
||||||
document.getElementById('ficha-placeholder').classList.remove('d-none');
|
document.getElementById('ficha-placeholder').classList.remove('d-none');
|
||||||
|
|||||||
+98
-30
@@ -1,4 +1,5 @@
|
|||||||
<?php
|
<?php
|
||||||
|
ini_set('display_errors', 1); error_reporting(E_ALL); // ponytail: quitar después de depurar
|
||||||
/**
|
/**
|
||||||
* ver_formulario_enviado.php — Ver respuesta de un formulario (imprimible / PDF)
|
* ver_formulario_enviado.php — Ver respuesta de un formulario (imprimible / PDF)
|
||||||
* Acceso admin/enfermero: ver_formulario_enviado.php?id=ENVIO_ID (requiere sesión)
|
* Acceso admin/enfermero: ver_formulario_enviado.php?id=ENVIO_ID (requiere sesión)
|
||||||
@@ -188,6 +189,10 @@ $datosCliente = json_decode($envio['datos_cliente'] ?? '{}', true) ?? [];
|
|||||||
$datosPrefilled = json_decode($envio['datos_prefilled'] ?? '{}', true) ?? [];
|
$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';
|
||||||
|
// 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 = [];
|
||||||
@@ -298,6 +303,31 @@ function esc2(mixed $v): string {
|
|||||||
.hash-seal-header { background: #000 !important; -webkit-print-color-adjust:exact; print-color-adjust:exact; }
|
.hash-seal-header { background: #000 !important; -webkit-print-color-adjust:exact; print-color-adjust:exact; }
|
||||||
}
|
}
|
||||||
|
|
||||||
|
<?php if ($embebido): ?>
|
||||||
|
/* ── Modo embebido (iframe) ── */
|
||||||
|
body { background: #fff; font-size: 12px; }
|
||||||
|
.action-bar { display: none !important; }
|
||||||
|
.doc-wrap { margin: 0; border-radius: 0; box-shadow: none; max-width: 100%; }
|
||||||
|
.doc-header { padding: 10px 14px 8px; }
|
||||||
|
.doc-header-text h3 { font-size: 14px; }
|
||||||
|
.doc-header-text h4 { font-size: 12px; }
|
||||||
|
.doc-body { padding: 12px 14px; }
|
||||||
|
.doc-footer { display: none; }
|
||||||
|
.section-title { font-size: 10px; margin: 14px 0 8px; }
|
||||||
|
.campo-label { font-size: 11px; }
|
||||||
|
.campo-valor { font-size: 12px; }
|
||||||
|
.campo-edit { margin-bottom: 8px; }
|
||||||
|
.campo-edit label { font-size: 11px; margin-bottom: 2px; }
|
||||||
|
.campo-edit .form-control,
|
||||||
|
.campo-edit .form-select { font-size: 12px; padding: 3px 7px; }
|
||||||
|
.campo-edit .form-check-label { font-size: 12px; }
|
||||||
|
.campo-row { padding: 3px 0; }
|
||||||
|
.turnero-cv { height: 110px !important; }
|
||||||
|
.btn { font-size: 12px; padding: 3px 10px; }
|
||||||
|
.hash-seal { display: none; }
|
||||||
|
.alert { font-size: 12px; padding: 6px 10px; }
|
||||||
|
<?php endif; ?>
|
||||||
|
|
||||||
/* ── Canvas firma profesional ───────────────────── */
|
/* ── Canvas firma profesional ───────────────────── */
|
||||||
.firma-pro-widget { max-width: 520px; margin-top: 8px; }
|
.firma-pro-widget { max-width: 520px; margin-top: 8px; }
|
||||||
.fpw-canvas { border: 2px solid #198754; border-radius: 8px; background: #f8fff9;
|
.fpw-canvas { border: 2px solid #198754; border-radius: 8px; background: #f8fff9;
|
||||||
@@ -456,6 +486,7 @@ function esc2(mixed $v): string {
|
|||||||
$_renderedFirmaProfesional = false;
|
$_renderedFirmaProfesional = false;
|
||||||
$_renderedFirmaPaciente = false;
|
$_renderedFirmaPaciente = false;
|
||||||
$_esquemaTieneFirmaPaciente = false;
|
$_esquemaTieneFirmaPaciente = false;
|
||||||
|
$_esquemaTieneFirmaAlguna = false; // true si hay cualquier campo firma/firma_profesional
|
||||||
$_firmaGlobalPacienteUsada = false; // la firma global solo va al primer campo firma
|
$_firmaGlobalPacienteUsada = false; // la firma global solo va al primer campo firma
|
||||||
|
|
||||||
foreach ($esquema as $campo):
|
foreach ($esquema as $campo):
|
||||||
@@ -470,6 +501,12 @@ function esc2(mixed $v): string {
|
|||||||
if (!$cid) continue;
|
if (!$cid) continue;
|
||||||
$isPro = ($tipo === 'firma_profesional');
|
$isPro = ($tipo === 'firma_profesional');
|
||||||
|
|
||||||
|
// Marcar presencia de campos firma en el esquema
|
||||||
|
$_esquemaTieneFirmaAlguna = true;
|
||||||
|
if (!$isPro) {
|
||||||
|
$_esquemaTieneFirmaPaciente = true;
|
||||||
|
}
|
||||||
|
|
||||||
// La firma profesional global solo se muestra en el PRIMER campo firma_profesional.
|
// La firma profesional global solo se muestra en el PRIMER campo firma_profesional.
|
||||||
// Campos posteriores (ej. desistimiento) solo muestran su propia firma por campo.
|
// Campos posteriores (ej. desistimiento) solo muestran su propia firma por campo.
|
||||||
$fSvg = $isPro
|
$fSvg = $isPro
|
||||||
@@ -481,12 +518,10 @@ function esc2(mixed $v): string {
|
|||||||
$fLabel = htmlspecialchars($campo['label'] ?? ($isPro ? 'Firma profesional' : 'Firma paciente'));
|
$fLabel = htmlspecialchars($campo['label'] ?? ($isPro ? 'Firma profesional' : 'Firma paciente'));
|
||||||
|
|
||||||
// Campo paciente sin firma:
|
// Campo paciente sin firma:
|
||||||
// - En turnero pendiente: todos los campos firma muestran canvas (consentimiento + desistimiento).
|
// - En turnero pendiente: todos los campos firma muestran canvas.
|
||||||
// - En vista normal: omitir campos sin firma.
|
// - En vista normal: omitir campos sin firma.
|
||||||
if (!$fSvg && !$fFoto && !$isPro) {
|
if (!$fSvg && !$fFoto && !$isPro) {
|
||||||
if ($modoTurnero && $modoEditar) {
|
if (!($modoTurnero && $modoEditar)) {
|
||||||
$_esquemaTieneFirmaPaciente = true;
|
|
||||||
} else {
|
|
||||||
continue; // no-turnero: omitir campos sin firma
|
continue; // no-turnero: omitir campos sin firma
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -526,14 +561,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">
|
||||||
@@ -598,7 +638,7 @@ function esc2(mixed $v): string {
|
|||||||
</div>
|
</div>
|
||||||
<?php continue; endif;
|
<?php continue; endif;
|
||||||
if ($tipo === 'radio'):
|
if ($tipo === 'radio'):
|
||||||
$opts = $campo['opciones'] ?? [];
|
$opts = $campo['opciones'] ?? $campo['options'] ?? [];
|
||||||
?>
|
?>
|
||||||
<div class="campo-edit">
|
<div class="campo-edit">
|
||||||
<label><?= $label ?></label>
|
<label><?= $label ?></label>
|
||||||
@@ -613,7 +653,7 @@ function esc2(mixed $v): string {
|
|||||||
</div>
|
</div>
|
||||||
<?php continue; endif;
|
<?php continue; endif;
|
||||||
if ($tipo === 'checkbox' || $tipo === 'lista_marcable'):
|
if ($tipo === 'checkbox' || $tipo === 'lista_marcable'):
|
||||||
$opts = $campo['opciones'] ?? [];
|
$opts = $campo['opciones'] ?? $campo['options'] ?? $campo['items'] ?? [];
|
||||||
$checkedArr = is_array($prefill) ? $prefill
|
$checkedArr = is_array($prefill) ? $prefill
|
||||||
: (is_string($prefill) && $prefill !== '' ? (json_decode($prefill, true) ?: [$prefill]) : []);
|
: (is_string($prefill) && $prefill !== '' ? (json_decode($prefill, true) ?: [$prefill]) : []);
|
||||||
?>
|
?>
|
||||||
@@ -630,7 +670,7 @@ function esc2(mixed $v): string {
|
|||||||
</div>
|
</div>
|
||||||
<?php continue; endif;
|
<?php continue; endif;
|
||||||
if ($tipo === 'select'):
|
if ($tipo === 'select'):
|
||||||
$opts = $campo['opciones'] ?? [];
|
$opts = $campo['opciones'] ?? $campo['options'] ?? [];
|
||||||
?>
|
?>
|
||||||
<div class="campo-edit">
|
<div class="campo-edit">
|
||||||
<label><?= $label ?></label>
|
<label><?= $label ?></label>
|
||||||
@@ -760,8 +800,8 @@ function esc2(mixed $v): string {
|
|||||||
</div>
|
</div>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
|
|
||||||
<!-- ── Widget firma turnero (fallback si el esquema no tiene campo firma) ── -->
|
<!-- ── Widget firma turnero (fallback solo si el esquema no tiene NINGÚN campo firma) ── -->
|
||||||
<?php if ($modoTurnero && $envio['estado'] !== 'firmado' && !$_esquemaTieneFirmaPaciente): ?>
|
<?php if ($modoTurnero && $envio['estado'] !== 'firmado' && !$_esquemaTieneFirmaAlguna): ?>
|
||||||
<div class="mt-4 no-print turnero-firma-item" data-cid="__firma_global">
|
<div class="mt-4 no-print turnero-firma-item" data-cid="__firma_global">
|
||||||
<div class="section-title" style="color:#1565c0">
|
<div class="section-title" style="color:#1565c0">
|
||||||
<i class="fas fa-pen me-1"></i>Firma del paciente / responsable
|
<i class="fas fa-pen me-1"></i>Firma del paciente / responsable
|
||||||
@@ -860,7 +900,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;
|
||||||
@@ -871,12 +910,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, {
|
||||||
@@ -887,18 +946,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