cambios
This commit is contained in:
+192
-5
@@ -50,7 +50,11 @@ $token = trim($_GET['t'] ?? '');
|
|||||||
cursor:crosshair; touch-action:none;
|
cursor:crosshair; touch-action:none;
|
||||||
background:#fff; display:block; width:100%; height:150px; }
|
background:#fff; display:block; width:100%; height:150px; }
|
||||||
#firma-canvas.empty { border-style:dashed; border-color:#adb5bd; }
|
#firma-canvas.empty { border-style:dashed; border-color:#adb5bd; }
|
||||||
|
/* ── Firma inline por campo ──────────────────────────────── */
|
||||||
|
.fw-canvas { cursor:crosshair; touch-action:none;
|
||||||
|
background:#fff; display:block; width:100%; height:140px;
|
||||||
|
border-radius:8px; }
|
||||||
|
.fw-canvas.empty { border-style:dashed !important; }
|
||||||
/* ── Firma modos ─────────────────────────────── */
|
/* ── Firma modos ─────────────────────────────── */
|
||||||
.firma-modo-btns { display:flex; gap:8px; margin-bottom:12px; }
|
.firma-modo-btns { display:flex; gap:8px; margin-bottom:12px; }
|
||||||
.firma-modo-btn { flex:1; padding:7px 0; border:2px solid #dee2e6; border-radius:8px;
|
.firma-modo-btn { flex:1; padding:7px 0; border:2px solid #dee2e6; border-radius:8px;
|
||||||
@@ -433,7 +437,10 @@ function renderCampos(esquema, prefilled) {
|
|||||||
</div>`;
|
</div>`;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (c.tipo === 'firma') return; // se maneja aparte
|
if (c.tipo === 'firma' || c.tipo === 'firma_profesional') {
|
||||||
|
html += renderFirmaInline(c.id, c.modos || ['canvas', 'foto'], c.label, c.required, c.tipo === 'firma_profesional');
|
||||||
|
return;
|
||||||
|
}
|
||||||
if (c.tipo === 'parrafo') {
|
if (c.tipo === 'parrafo') {
|
||||||
const ws = c.flujoLibre ? 'normal' : 'pre-wrap';
|
const ws = c.flujoLibre ? 'normal' : 'pre-wrap';
|
||||||
html += `<div class="mb-3" style="font-size:.88rem;line-height:1.75;color:#222;text-align:justify;white-space:${ws}">${esc(c.contenido||'')}</div>`;
|
html += `<div class="mb-3" style="font-size:.88rem;line-height:1.75;color:#222;text-align:justify;white-space:${ws}">${esc(c.contenido||'')}</div>`;
|
||||||
@@ -538,6 +545,158 @@ function renderCampos(esquema, prefilled) {
|
|||||||
cont.innerHTML = html;
|
cont.innerHTML = html;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ══════════════════════════════════════════════════════════════════════
|
||||||
|
// FIRMAS INLINE (por campo)
|
||||||
|
// ══════════════════════════════════════════════════════════════════════
|
||||||
|
const _fw = {}; // { fieldId: { canvas, ctx, hasFirma, foto, drawing, lastX, lastY } }
|
||||||
|
|
||||||
|
function renderFirmaInline(fid, modos, label, required, isPro) {
|
||||||
|
const hasCanvas = (modos||[]).includes('canvas');
|
||||||
|
const hasFoto = (modos||[]).includes('foto');
|
||||||
|
const borderC = isPro ? '#198754' : '#1565c0';
|
||||||
|
const bgC = isPro ? '#f0fff4' : '#fff';
|
||||||
|
const iconC = isPro ? 'fa-user-md' : 'fa-signature';
|
||||||
|
const badge = isPro
|
||||||
|
? `<span class="badge ms-1" style="background:#198754;font-size:.65rem">Profesional</span>`
|
||||||
|
: `<span class="badge ms-1" style="background:#1565c0;font-size:.65rem">Paciente</span>`;
|
||||||
|
const star = required ? '<span class="text-danger ms-1">*</span>' : '';
|
||||||
|
const lbl = `<label class="form-label fw-semibold">
|
||||||
|
<i class="fas ${iconC} me-1" style="color:${borderC}"></i>${esc(label||'')}${star}${badge}
|
||||||
|
</label>`;
|
||||||
|
let html = `<div class="mb-4" id="fw-${fid}"><hr class="mb-3">${lbl}`;
|
||||||
|
|
||||||
|
if (hasCanvas) {
|
||||||
|
html += `<div id="fw-${fid}-canvas-area">
|
||||||
|
<p class="text-muted small mb-2">✍️ Dibuja la firma con el dedo o el mouse.</p>
|
||||||
|
<canvas id="fw-${fid}-canvas" class="fw-canvas empty"
|
||||||
|
style="border:2px dashed ${borderC};background:${bgC};"></canvas>
|
||||||
|
<div class="d-flex gap-2 mt-2">
|
||||||
|
<button type="button" class="btn btn-outline-secondary btn-sm"
|
||||||
|
onclick="firmaWidgetLimpiar('${fid}')">
|
||||||
|
<i class="fas fa-eraser me-1"></i>Limpiar
|
||||||
|
</button>
|
||||||
|
<span id="fw-${fid}-status" class="small text-muted align-self-center">Sin firma</span>
|
||||||
|
</div>
|
||||||
|
</div>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (hasFoto) {
|
||||||
|
html += `<div id="fw-${fid}-foto-area" class="${hasCanvas ? 'mt-3' : ''}">
|
||||||
|
<p class="text-muted small mb-2">📷 Adjunta una foto de la firma.</p>
|
||||||
|
<label class="firma-foto-drop" id="fw-${fid}-foto-drop" for="fw-${fid}-foto-input">
|
||||||
|
<i class="fas fa-camera fa-2x d-block mb-2"></i>
|
||||||
|
Toca para abrir cámara o galería
|
||||||
|
</label>
|
||||||
|
<input type="file" id="fw-${fid}-foto-input" accept="image/*" capture="environment"
|
||||||
|
class="d-none" onchange="firmaWidgetFoto('${fid}', this)">
|
||||||
|
<img id="fw-${fid}-foto-preview" src="" alt="Firma"
|
||||||
|
style="max-width:100%;max-height:160px;border-radius:8px;border:2px solid ${borderC};display:none;margin-top:8px;object-fit:contain;">
|
||||||
|
<div class="d-flex gap-2 mt-2">
|
||||||
|
<button type="button" class="btn btn-outline-secondary btn-sm"
|
||||||
|
onclick="firmaWidgetLimpiarFoto('${fid}')">
|
||||||
|
<i class="fas fa-eraser me-1"></i>Quitar foto
|
||||||
|
</button>
|
||||||
|
<span id="fw-${fid}-foto-status" class="small text-muted align-self-center">Sin foto</span>
|
||||||
|
</div>
|
||||||
|
</div>`;
|
||||||
|
}
|
||||||
|
|
||||||
|
html += `</div>`;
|
||||||
|
return html;
|
||||||
|
}
|
||||||
|
|
||||||
|
function firmaWidgetInit(fid) {
|
||||||
|
const canvas = document.getElementById('fw-' + fid + '-canvas');
|
||||||
|
if (!canvas) return;
|
||||||
|
const ratio = window.devicePixelRatio || 1;
|
||||||
|
canvas.width = canvas.offsetWidth * ratio;
|
||||||
|
canvas.height = canvas.offsetHeight * ratio;
|
||||||
|
const ctx = canvas.getContext('2d');
|
||||||
|
ctx.scale(ratio, ratio);
|
||||||
|
ctx.strokeStyle = '#1a1a2e';
|
||||||
|
ctx.lineWidth = 2.5;
|
||||||
|
ctx.lineCap = 'round';
|
||||||
|
_fw[fid] = { canvas, ctx, drawing: false, hasFirma: false, foto: null, lastX: 0, lastY: 0 };
|
||||||
|
|
||||||
|
function pos(e) {
|
||||||
|
const r = canvas.getBoundingClientRect();
|
||||||
|
const src = e.touches?.[0] ?? e;
|
||||||
|
return { x: src.clientX - r.left, y: src.clientY - r.top };
|
||||||
|
}
|
||||||
|
function start(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
const s = _fw[fid]; s.drawing = true;
|
||||||
|
const p = pos(e); s.lastX = p.x; s.lastY = p.y;
|
||||||
|
}
|
||||||
|
function move(e) {
|
||||||
|
e.preventDefault();
|
||||||
|
const s = _fw[fid];
|
||||||
|
if (!s.drawing) return;
|
||||||
|
const p = pos(e);
|
||||||
|
s.ctx.beginPath(); s.ctx.moveTo(s.lastX, s.lastY);
|
||||||
|
s.ctx.lineTo(p.x, p.y); s.ctx.stroke();
|
||||||
|
s.lastX = p.x; s.lastY = p.y;
|
||||||
|
if (!s.hasFirma) {
|
||||||
|
s.hasFirma = true;
|
||||||
|
canvas.classList.remove('empty');
|
||||||
|
canvas.style.borderStyle = 'solid';
|
||||||
|
const st = document.getElementById('fw-' + fid + '-status');
|
||||||
|
if (st) { st.textContent = '✅ Firmado'; st.className = 'small text-success align-self-center fw-semibold'; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
function end() { if (_fw[fid]) _fw[fid].drawing = false; }
|
||||||
|
canvas.addEventListener('mousedown', start);
|
||||||
|
canvas.addEventListener('mousemove', move);
|
||||||
|
canvas.addEventListener('mouseup', end);
|
||||||
|
canvas.addEventListener('touchstart', start, { passive: false });
|
||||||
|
canvas.addEventListener('touchmove', move, { passive: false });
|
||||||
|
canvas.addEventListener('touchend', end);
|
||||||
|
}
|
||||||
|
|
||||||
|
function firmaWidgetLimpiar(fid) {
|
||||||
|
const s = _fw[fid];
|
||||||
|
if (!s?.canvas) return;
|
||||||
|
const r = window.devicePixelRatio || 1;
|
||||||
|
s.ctx.clearRect(0, 0, s.canvas.width / r, s.canvas.height / r);
|
||||||
|
s.hasFirma = false;
|
||||||
|
s.canvas.classList.add('empty');
|
||||||
|
s.canvas.style.borderStyle = 'dashed';
|
||||||
|
const st = document.getElementById('fw-' + fid + '-status');
|
||||||
|
if (st) { st.textContent = 'Sin firma'; st.className = 'small text-muted align-self-center'; }
|
||||||
|
}
|
||||||
|
|
||||||
|
function firmaWidgetFoto(fid, input) {
|
||||||
|
const file = input.files?.[0];
|
||||||
|
if (!file) return;
|
||||||
|
const reader = new FileReader();
|
||||||
|
reader.onload = e => {
|
||||||
|
if (!_fw[fid]) _fw[fid] = {};
|
||||||
|
_fw[fid].foto = e.target.result;
|
||||||
|
const p = document.getElementById('fw-' + fid + '-foto-preview');
|
||||||
|
const d = document.getElementById('fw-' + fid + '-foto-drop');
|
||||||
|
const s = document.getElementById('fw-' + fid + '-foto-status');
|
||||||
|
if (p) { p.src = e.target.result; p.style.display = 'block'; }
|
||||||
|
if (d) d.style.display = 'none';
|
||||||
|
if (s) { s.textContent = '✅ Foto lista'; s.className = 'small text-success align-self-center fw-semibold'; }
|
||||||
|
};
|
||||||
|
reader.readAsDataURL(file);
|
||||||
|
}
|
||||||
|
|
||||||
|
function firmaWidgetLimpiarFoto(fid) {
|
||||||
|
if (_fw[fid]) _fw[fid].foto = null;
|
||||||
|
const p = document.getElementById('fw-' + fid + '-foto-preview');
|
||||||
|
const d = document.getElementById('fw-' + fid + '-foto-drop');
|
||||||
|
const s = document.getElementById('fw-' + fid + '-foto-status');
|
||||||
|
const i = document.getElementById('fw-' + fid + '-foto-input');
|
||||||
|
if (p) { p.src = ''; p.style.display = 'none'; }
|
||||||
|
if (d) d.style.display = '';
|
||||||
|
if (s) { s.textContent = 'Sin foto'; s.className = 'small text-muted align-self-center'; }
|
||||||
|
if (i) i.value = '';
|
||||||
|
}
|
||||||
|
|
||||||
|
function firmaWidgetObtenerSVG(fid) { return _fw[fid]?.hasFirma ? _fw[fid].canvas.toDataURL('image/png') : null; }
|
||||||
|
function firmaWidgetObtenerFoto(fid) { return _fw[fid]?.foto || null; }
|
||||||
|
|
||||||
// ══════════════════════════════════════════════════════════════════════
|
// ══════════════════════════════════════════════════════════════════════
|
||||||
// formCliente
|
// formCliente
|
||||||
// ══════════════════════════════════════════════════════════════════════
|
// ══════════════════════════════════════════════════════════════════════
|
||||||
@@ -706,7 +865,18 @@ const formCliente = {
|
|||||||
|
|
||||||
if (form.permite_firma) {
|
if (form.permite_firma) {
|
||||||
// Pequeño delay para asegurar que el layout ya renderizó
|
// Pequeño delay para asegurar que el layout ya renderizó
|
||||||
requestAnimationFrame(() => requestAnimationFrame(() => firma.init()));
|
requestAnimationFrame(() => requestAnimationFrame(() => {
|
||||||
|
firma.init();
|
||||||
|
form.esquema_decoded.forEach(c => {
|
||||||
|
if (c.tipo === 'firma' || c.tipo === 'firma_profesional') firmaWidgetInit(c.id);
|
||||||
|
});
|
||||||
|
}));
|
||||||
|
} else {
|
||||||
|
requestAnimationFrame(() => requestAnimationFrame(() => {
|
||||||
|
form.esquema_decoded.forEach(c => {
|
||||||
|
if (c.tipo === 'firma' || c.tipo === 'firma_profesional') firmaWidgetInit(c.id);
|
||||||
|
});
|
||||||
|
}));
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -716,7 +886,7 @@ const formCliente = {
|
|||||||
const campos = _data.formulario.esquema_decoded;
|
const campos = _data.formulario.esquema_decoded;
|
||||||
|
|
||||||
campos.forEach(c => {
|
campos.forEach(c => {
|
||||||
if (c.tipo === 'separador' || c.tipo === 'firma' || c.tipo === 'parrafo') return;
|
if (c.tipo === 'separador' || c.tipo === 'firma' || c.tipo === 'firma_profesional' || c.tipo === 'parrafo') return;
|
||||||
if (c.tipo === 'linked') { return; } // linked viene del prefilled
|
if (c.tipo === 'linked') { return; } // linked viene del prefilled
|
||||||
|
|
||||||
// parrafo_inline: recolectar solo los inputs editables (los que el usuario llenó)
|
// parrafo_inline: recolectar solo los inputs editables (los que el usuario llenó)
|
||||||
@@ -739,6 +909,14 @@ const formCliente = {
|
|||||||
datos[c.id] = el ? el.value : '';
|
datos[c.id] = el ? el.value : '';
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
// Recopilar firmas inline (firma y firma_profesional por campo)
|
||||||
|
campos.forEach(c => {
|
||||||
|
if (c.tipo !== 'firma' && c.tipo !== 'firma_profesional') return;
|
||||||
|
const svg = firmaWidgetObtenerSVG(c.id);
|
||||||
|
const foto = firmaWidgetObtenerFoto(c.id);
|
||||||
|
if (svg) datos[c.id + '_svg'] = svg;
|
||||||
|
if (foto) datos[c.id + '_foto'] = foto;
|
||||||
|
});
|
||||||
return datos;
|
return datos;
|
||||||
},
|
},
|
||||||
|
|
||||||
@@ -746,7 +924,7 @@ const formCliente = {
|
|||||||
const campos = _data.formulario.esquema_decoded;
|
const campos = _data.formulario.esquema_decoded;
|
||||||
for (const c of campos) {
|
for (const c of campos) {
|
||||||
if (!c.required) continue;
|
if (!c.required) continue;
|
||||||
if (c.tipo === 'separador' || c.tipo === 'linked' || c.tipo === 'firma' || c.tipo === 'parrafo' || c.tipo === 'parrafo_inline') continue;
|
if (c.tipo === 'separador' || c.tipo === 'linked' || c.tipo === 'firma' || c.tipo === 'firma_profesional' || c.tipo === 'parrafo' || c.tipo === 'parrafo_inline') continue;
|
||||||
const v = datos[c.id];
|
const v = datos[c.id];
|
||||||
const vacio = Array.isArray(v) ? v.length === 0 : !v || !String(v).trim();
|
const vacio = Array.isArray(v) ? v.length === 0 : !v || !String(v).trim();
|
||||||
if (vacio) {
|
if (vacio) {
|
||||||
@@ -759,6 +937,15 @@ const formCliente = {
|
|||||||
$('firma-container').scrollIntoView({block:'center', behavior:'smooth'});
|
$('firma-container').scrollIntoView({block:'center', behavior:'smooth'});
|
||||||
return 'La firma es obligatoria en este formulario.';
|
return 'La firma es obligatoria en este formulario.';
|
||||||
}
|
}
|
||||||
|
// Validar firmas inline requeridas
|
||||||
|
for (const c of campos) {
|
||||||
|
if (!c.required || (c.tipo !== 'firma' && c.tipo !== 'firma_profesional')) continue;
|
||||||
|
if (!firmaWidgetObtenerSVG(c.id) && !firmaWidgetObtenerFoto(c.id)) {
|
||||||
|
const el = document.getElementById('fw-' + c.id);
|
||||||
|
if (el) el.scrollIntoView({ block: 'center', behavior: 'smooth' });
|
||||||
|
return `El campo "${c.label}" requiere firma.`;
|
||||||
|
}
|
||||||
|
}
|
||||||
return null;
|
return null;
|
||||||
},
|
},
|
||||||
|
|
||||||
|
|||||||
@@ -471,7 +471,8 @@ const TIPOS_CAMPO = [
|
|||||||
{ tipo:'select', label:'Lista desplegable', icon:'fa-list' },
|
{ tipo:'select', label:'Lista desplegable', icon:'fa-list' },
|
||||||
{ tipo:'radio', label:'Selección única', icon:'fa-dot-circle' },
|
{ tipo:'radio', label:'Selección única', icon:'fa-dot-circle' },
|
||||||
{ tipo:'checkbox', label:'Múltiple opción', icon:'fa-check-square' },
|
{ tipo:'checkbox', label:'Múltiple opción', icon:'fa-check-square' },
|
||||||
{ tipo:'firma', label:'Firma digital', icon:'fa-signature' },
|
{ tipo:'firma', label:'Firma del paciente', icon:'fa-signature' },
|
||||||
|
{ tipo:'firma_profesional', label:'Firma del profesional', icon:'fa-user-md' },
|
||||||
{ tipo:'separador', label:'Separador / Título', icon:'fa-minus' },
|
{ tipo:'separador', label:'Separador / Título', icon:'fa-minus' },
|
||||||
{ tipo:'parrafo', label:'Párrafo de texto', icon:'fa-paragraph' },
|
{ tipo:'parrafo', label:'Párrafo de texto', icon:'fa-paragraph' },
|
||||||
{ tipo:'parrafo_inline', label:'Párrafo con campos', icon:'fa-align-left' },
|
{ tipo:'parrafo_inline', label:'Párrafo con campos', icon:'fa-align-left' },
|
||||||
@@ -584,8 +585,9 @@ function agregarCampo({ tipo, linked }) {
|
|||||||
} else if (['select','radio','checkbox'].includes(tipo)) {
|
} else if (['select','radio','checkbox'].includes(tipo)) {
|
||||||
campo = { id, tipo, label:'Campo sin título', options:['Opción 1','Opción 2'], required:false };
|
campo = { id, tipo, label:'Campo sin título', options:['Opción 1','Opción 2'], required:false };
|
||||||
} else if (tipo === 'firma') {
|
} else if (tipo === 'firma') {
|
||||||
const _mc = []; if($('tb-firma-canvas').checked)_mc.push('canvas'); if($('tb-firma-foto').checked)_mc.push('foto');
|
campo = { id, tipo:'firma', label:'Firma del paciente', modos: ['canvas','foto'] };
|
||||||
campo = { id, tipo:'firma', label:'Firma del paciente', modos: _mc.length ? _mc : ['canvas','foto'] };
|
} else if (tipo === 'firma_profesional') {
|
||||||
|
campo = { id, tipo:'firma_profesional', label:'Firma del profesional', modos: ['canvas'] };
|
||||||
} else if (tipo === 'parrafo') {
|
} else if (tipo === 'parrafo') {
|
||||||
campo = { id, tipo:'parrafo', contenido:'Escribe aquí el texto del párrafo...', flujoLibre:false };
|
campo = { id, tipo:'parrafo', contenido:'Escribe aquí el texto del párrafo...', flujoLibre:false };
|
||||||
} else if (tipo === 'parrafo_inline') {
|
} else if (tipo === 'parrafo_inline') {
|
||||||
@@ -606,7 +608,7 @@ function agregarCampo({ tipo, linked }) {
|
|||||||
const TIPO_ICON = {
|
const TIPO_ICON = {
|
||||||
texto:'fa-font', textarea:'fa-align-left', numero:'fa-hashtag',
|
texto:'fa-font', textarea:'fa-align-left', numero:'fa-hashtag',
|
||||||
fecha:'fa-calendar', hora:'fa-clock', select:'fa-list', radio:'fa-dot-circle',
|
fecha:'fa-calendar', hora:'fa-clock', select:'fa-list', radio:'fa-dot-circle',
|
||||||
checkbox:'fa-check-square', firma:'fa-signature', separador:'fa-minus', linked:'fa-link',
|
checkbox:'fa-check-square', firma:'fa-signature', firma_profesional:'fa-user-md', separador:'fa-minus', linked:'fa-link',
|
||||||
parrafo:'fa-paragraph', parrafo_inline:'fa-align-left', lista_marcable:'fa-list-ol'
|
parrafo:'fa-paragraph', parrafo_inline:'fa-align-left', lista_marcable:'fa-list-ol'
|
||||||
};
|
};
|
||||||
|
|
||||||
@@ -732,7 +734,11 @@ function renderCampoPreview(c) {
|
|||||||
}
|
}
|
||||||
if (c.tipo === 'firma') {
|
if (c.tipo === 'firma') {
|
||||||
return `<div class="preview-field">${lbl}
|
return `<div class="preview-field">${lbl}
|
||||||
<div style="border:1px solid #dee2e6;border-radius:5px;height:50px;display:flex;align-items:center;justify-content:center;font-size:11px;color:#aaa;background:#fafafa">✍️ Firma</div></div>`;
|
<div style="border:1px solid #1565c0;border-radius:5px;height:50px;display:flex;align-items:center;justify-content:center;font-size:11px;color:#1565c0;background:#e8f0fe">✍️ Firma del paciente</div></div>`;
|
||||||
|
}
|
||||||
|
if (c.tipo === 'firma_profesional') {
|
||||||
|
return `<div class="preview-field">${lbl}
|
||||||
|
<div style="border:1px solid #198754;border-radius:5px;height:50px;display:flex;align-items:center;justify-content:center;font-size:11px;color:#198754;background:#f0fff4">✍️ Firma del profesional</div></div>`;
|
||||||
}
|
}
|
||||||
if (c.tipo === 'parrafo') {
|
if (c.tipo === 'parrafo') {
|
||||||
const ws = c.flujoLibre ? 'normal' : 'pre-wrap';
|
const ws = c.flujoLibre ? 'normal' : 'pre-wrap';
|
||||||
@@ -837,6 +843,21 @@ function abrirEditorCampo(idx) {
|
|||||||
<input class="form-check-input" type="checkbox" id="ce-required" ${c.required?'checked':''}>
|
<input class="form-check-input" type="checkbox" id="ce-required" ${c.required?'checked':''}>
|
||||||
<label class="form-check-label small" for="ce-required">Requiere al menos una selección</label>
|
<label class="form-check-label small" for="ce-required">Requiere al menos una selección</label>
|
||||||
</div>`;
|
</div>`;
|
||||||
|
} else if (c.tipo === 'firma' || c.tipo === 'firma_profesional') {
|
||||||
|
const cm = c.modos || ['canvas'];
|
||||||
|
html += `<div class="mb-2">
|
||||||
|
<label class="form-label small fw-semibold">Modos de firma disponibles</label>
|
||||||
|
<div class="d-flex gap-4">
|
||||||
|
<div class="form-check">
|
||||||
|
<input type="checkbox" class="form-check-input" id="ce-modo-canvas" ${cm.includes('canvas')?'checked':''}>
|
||||||
|
<label class="form-check-label small" for="ce-modo-canvas">✍️ Dibujar (canvas)</label>
|
||||||
|
</div>
|
||||||
|
<div class="form-check">
|
||||||
|
<input type="checkbox" class="form-check-input" id="ce-modo-foto" ${cm.includes('foto')?'checked':''}>
|
||||||
|
<label class="form-check-label small" for="ce-modo-foto">📷 Foto</label>
|
||||||
|
</div>
|
||||||
|
</div>
|
||||||
|
</div>`;
|
||||||
}
|
}
|
||||||
|
|
||||||
$('campo-editor-body').innerHTML = html;
|
$('campo-editor-body').innerHTML = html;
|
||||||
@@ -882,6 +903,15 @@ function aplicarCampo() {
|
|||||||
const items = document.getElementById('ce-items');
|
const items = document.getElementById('ce-items');
|
||||||
if (items) c.items = items.value.split('\n').map(s=>s.trim()).filter(Boolean);
|
if (items) c.items = items.value.split('\n').map(s=>s.trim()).filter(Boolean);
|
||||||
|
|
||||||
|
const modoCanvas = document.getElementById('ce-modo-canvas');
|
||||||
|
const modoFoto = document.getElementById('ce-modo-foto');
|
||||||
|
if (modoCanvas !== null || modoFoto !== null) {
|
||||||
|
const mc = [];
|
||||||
|
if (modoCanvas?.checked) mc.push('canvas');
|
||||||
|
if (modoFoto?.checked) mc.push('foto');
|
||||||
|
if (mc.length) c.modos = mc;
|
||||||
|
}
|
||||||
|
|
||||||
_bsCampo.hide();
|
_bsCampo.hide();
|
||||||
renderCanvas();
|
renderCanvas();
|
||||||
renderPreview();
|
renderPreview();
|
||||||
@@ -988,7 +1018,6 @@ async function guardar() {
|
|||||||
const usarGlobal = $('tb-usar-global').checked;
|
const usarGlobal = $('tb-usar-global').checked;
|
||||||
// Sincronizar modos ANTES de serializar el esquema
|
// Sincronizar modos ANTES de serializar el esquema
|
||||||
const _modos = []; if($('tb-firma-canvas').checked)_modos.push('canvas'); if($('tb-firma-foto').checked)_modos.push('foto');
|
const _modos = []; if($('tb-firma-canvas').checked)_modos.push('canvas'); if($('tb-firma-foto').checked)_modos.push('foto');
|
||||||
_campos = _campos.map(c => c.tipo === 'firma' ? { ...c, modos: _modos.length ? _modos : ['canvas'] } : c);
|
|
||||||
const datos = {
|
const datos = {
|
||||||
nombre,
|
nombre,
|
||||||
descripcion: $('tb-descripcion').value.trim() || null,
|
descripcion: $('tb-descripcion').value.trim() || null,
|
||||||
|
|||||||
+6
-1
@@ -747,7 +747,12 @@ function renderCampoPreview(c, valores) {
|
|||||||
if (c.tipo === 'firma') {
|
if (c.tipo === 'firma') {
|
||||||
return `<div class="preview-field">${lbl}
|
return `<div class="preview-field">${lbl}
|
||||||
<div class="border rounded p-2 text-center text-muted small bg-light" style="height:70px;line-height:50px">
|
<div class="border rounded p-2 text-center text-muted small bg-light" style="height:70px;line-height:50px">
|
||||||
✍️ Área de firma</div></div>`;
|
✍️ Área de firma (paciente)</div></div>`;
|
||||||
|
}
|
||||||
|
if (c.tipo === 'firma_profesional') {
|
||||||
|
return `<div class="preview-field">${lbl}
|
||||||
|
<div class="border rounded p-2 text-center small fw-semibold" style="height:70px;line-height:50px;color:#198754;background:#f0fff4;border-color:#198754 !important">
|
||||||
|
✍️ Área de firma (profesional)</div></div>`;
|
||||||
}
|
}
|
||||||
if (c.tipo === 'parrafo') {
|
if (c.tipo === 'parrafo') {
|
||||||
const txt = (c.contenido || '');
|
const txt = (c.contenido || '');
|
||||||
|
|||||||
@@ -277,6 +277,7 @@ function esc2(mixed $v): string {
|
|||||||
<div class="esquema-sep"><?= esc2($campo['label'] ?? '') ?></div>
|
<div class="esquema-sep"><?= esc2($campo['label'] ?? '') ?></div>
|
||||||
<?php continue; endif;
|
<?php continue; endif;
|
||||||
if ($tipo === 'firma') continue;
|
if ($tipo === 'firma') continue;
|
||||||
|
if ($tipo === 'firma_profesional') continue;
|
||||||
|
|
||||||
// ── Parrafo estático ──────────────────────────────────
|
// ── Parrafo estático ──────────────────────────────────
|
||||||
if ($tipo === 'parrafo'):
|
if ($tipo === 'parrafo'):
|
||||||
@@ -334,6 +335,35 @@ function esc2(mixed $v): string {
|
|||||||
</div>
|
</div>
|
||||||
<?php endif; ?>
|
<?php endif; ?>
|
||||||
|
|
||||||
|
<!-- Firmas inline (firma_paciente y firma_profesional por campo) -->
|
||||||
|
<?php foreach ($esquema as $campo):
|
||||||
|
$tipo = $campo['tipo'] ?? '';
|
||||||
|
if ($tipo !== 'firma' && $tipo !== 'firma_profesional') continue;
|
||||||
|
$cid = $campo['id'] ?? null;
|
||||||
|
if (!$cid) continue;
|
||||||
|
$fSvg = $datosCliente[$cid . '_svg'] ?? null;
|
||||||
|
$fFoto = $datosCliente[$cid . '_foto'] ?? null;
|
||||||
|
if (!$fSvg && !$fFoto) continue;
|
||||||
|
$isPro = ($tipo === 'firma_profesional');
|
||||||
|
$icon = $isPro ? 'fa-user-md' : 'fa-signature';
|
||||||
|
$color = $isPro ? '#198754' : '#1565c0';
|
||||||
|
$titLabel = htmlspecialchars($campo['label'] ?? ($isPro ? 'Firma profesional' : 'Firma paciente'));
|
||||||
|
?>
|
||||||
|
<div class="section-title mt-4" style="color:<?= $color ?>">
|
||||||
|
<i class="fas <?= $icon ?> me-1"></i><?= $titLabel ?>
|
||||||
|
</div>
|
||||||
|
<?php if ($fSvg): ?>
|
||||||
|
<div class="firma-box" style="border-color:<?= $color ?>">
|
||||||
|
<img src="<?= htmlspecialchars($fSvg) ?>" alt="<?= $titLabel ?>">
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
<?php if ($fFoto): ?>
|
||||||
|
<div class="firma-box mt-2" style="border-color:<?= $color ?>">
|
||||||
|
<img src="<?= htmlspecialchars($fFoto) ?>" alt="Foto - <?= $titLabel ?>">
|
||||||
|
</div>
|
||||||
|
<?php endif; ?>
|
||||||
|
<?php endforeach; ?>
|
||||||
|
|
||||||
<!-- Info del envío -->
|
<!-- Info del envío -->
|
||||||
<div class="section-title mt-4"><i class="fas fa-clock me-1"></i>Información del envío</div>
|
<div class="section-title mt-4"><i class="fas fa-clock me-1"></i>Información del envío</div>
|
||||||
<div class="campo-row">
|
<div class="campo-row">
|
||||||
|
|||||||
Reference in New Issue
Block a user