diff --git a/form_cliente.php b/form_cliente.php
index 9b2eea1..0b0c088 100644
--- a/form_cliente.php
+++ b/form_cliente.php
@@ -301,6 +301,8 @@ const API = 'api/lab/submit_formulario.php';
let _data = null; // respuesta de verificar token
let _cfg = {}; // config de diseño del laboratorio
let _firmaDibujada = false;
+let _firmaModosGlobal = null; // modos del topbar (columna firma_modos en DB)
+let _hasInlineFirma = false; // true si el esquema tiene campos tipo firma/firma_profesional
// ══════════════════════════════════════════════════════════════════════
// HELPERS
@@ -357,6 +359,20 @@ const firma = (() => {
canvas.addEventListener('touchstart', e => { e.preventDefault(); drawing=true; const p=getPos(e); lastX=p.x; lastY=p.y; }, {passive:false});
canvas.addEventListener('touchmove', e => { e.preventDefault(); if (!drawing) return; trazo(getPos(e)); }, {passive:false});
canvas.addEventListener('touchend', ()=> { drawing=false; ctx.beginPath(); });
+
+ // Drag & drop en área de foto global
+ const fotoDrop = $('firma-foto-drop');
+ if (fotoDrop) {
+ fotoDrop.addEventListener('dragover', e => { e.preventDefault(); fotoDrop.classList.add('dragover'); });
+ fotoDrop.addEventListener('dragleave', () => fotoDrop.classList.remove('dragover'));
+ fotoDrop.addEventListener('drop', e => {
+ e.preventDefault();
+ fotoDrop.classList.remove('dragover');
+ const file = e.dataTransfer.files?.[0];
+ if (!file || !file.type.startsWith('image/')) return;
+ _onFotoChange({ files: [file] });
+ });
+ }
}
function trazo(p) {
@@ -438,7 +454,11 @@ function renderCampos(esquema, prefilled) {
return;
}
if (c.tipo === 'firma' || c.tipo === 'firma_profesional') {
- html += renderFirmaInline(c.id, c.modos || ['canvas', 'foto'], c.label, c.required, c.tipo === 'firma_profesional');
+ // firma del paciente usa firma_modos global (topbar); profesional usa sus propios modos
+ const modos = (c.tipo === 'firma' && _firmaModosGlobal)
+ ? _firmaModosGlobal
+ : (c.modos || ['canvas']);
+ html += renderFirmaInline(c.id, modos, c.label, c.required, c.tipo === 'firma_profesional');
return;
}
if (c.tipo === 'parrafo') {
@@ -553,6 +573,7 @@ const _fw = {}; // { fieldId: { canvas, ctx, hasFirma, foto, drawing, lastX, la
function renderFirmaInline(fid, modos, label, required, isPro) {
const hasCanvas = (modos||[]).includes('canvas');
const hasFoto = (modos||[]).includes('foto');
+ const hasBoth = hasCanvas && hasFoto;
const borderC = isPro ? '#198754' : '#1565c0';
const bgC = isPro ? '#f0fff4' : '#fff';
const iconC = isPro ? 'fa-user-md' : 'fa-signature';
@@ -565,6 +586,16 @@ function renderFirmaInline(fid, modos, label, required, isPro) {
`;
let html = `
${lbl}`;
+ // Tabs cuando ambos modos están activos
+ if (hasBoth) {
+ html += `
+
+
+
`;
+ }
+
if (hasCanvas) {
html += `
✍️ Dibuja la firma con el dedo o el mouse.
@@ -581,11 +612,11 @@ function renderFirmaInline(fid, modos, label, required, isPro) {
}
if (hasFoto) {
- html += `
-
📷 Adjunta una foto de la firma.
+ html += `
+
📷 Adjunta una foto de la firma o arrastra la imagen aquí.
@@ -651,6 +682,31 @@ function firmaWidgetInit(fid) {
canvas.addEventListener('touchstart', start, { passive: false });
canvas.addEventListener('touchmove', move, { passive: false });
canvas.addEventListener('touchend', end);
+
+ // Drag & drop en área de foto del widget
+ const fotoDrop = document.getElementById('fw-' + fid + '-foto-drop');
+ if (fotoDrop) {
+ fotoDrop.addEventListener('dragover', e => { e.preventDefault(); fotoDrop.classList.add('dragover'); });
+ fotoDrop.addEventListener('dragleave', () => fotoDrop.classList.remove('dragover'));
+ fotoDrop.addEventListener('drop', e => {
+ e.preventDefault();
+ fotoDrop.classList.remove('dragover');
+ const file = e.dataTransfer.files?.[0];
+ if (!file || !file.type.startsWith('image/')) return;
+ firmaWidgetFoto(fid, { files: [file] });
+ });
+ }
+}
+
+function firmaWidgetTab(fid, tab) {
+ const ca = document.getElementById('fw-' + fid + '-canvas-area');
+ const fa = document.getElementById('fw-' + fid + '-foto-area');
+ const bt = document.getElementById('fw-' + fid + '-tab-canvas');
+ const bf = document.getElementById('fw-' + fid + '-tab-foto');
+ if (ca) ca.style.display = tab === 'canvas' ? '' : 'none';
+ if (fa) fa.style.display = tab === 'foto' ? '' : 'none';
+ if (bt) bt.classList.toggle('activo', tab === 'canvas');
+ if (bf) bf.classList.toggle('activo', tab === 'foto');
}
function firmaWidgetLimpiar(fid) {
@@ -845,33 +901,32 @@ const formCliente = {
}
// Campos
+ // firma_modos global: autoridad para campos tipo 'firma' (topbar del builder)
+ const fModosStr = form.firma_modos || '';
+ _firmaModosGlobal = fModosStr
+ ? fModosStr.split(',').map(m => m.trim()).filter(Boolean)
+ : null;
+
+ const hasInlineFirma = form.esquema_decoded.some(c => c.tipo === 'firma' || c.tipo === 'firma_profesional');
+ _hasInlineFirma = hasInlineFirma;
renderCampos(form.esquema_decoded, prefilled);
- // Firma (se inicializa DESPUÉS de mostrar el form para que offsetWidth sea correcto)
- if (form.permite_firma) {
+ // Firma global (solo si NO hay firmas inline y permite_firma está activo)
+ if (form.permite_firma && !hasInlineFirma) {
show('firma-container');
if (form.requiere_firma) show('firma-req-badge');
- // Configurar modos de firma: primero columna firma_modos, luego campo en esquema
- const firmaCampo = form.esquema_decoded.find(c => c.tipo === 'firma');
- const fModosStr = form.firma_modos || '';
- const firmaModosArr = fModosStr
- ? fModosStr.split(',').map(m => m.trim()).filter(Boolean)
- : (firmaCampo?.modos ?? null);
- firma.setModos(firmaModosArr);
+ firma.setModos(_firmaModosGlobal);
}
hide('loading-screen');
show('form-screen');
- if (form.permite_firma) {
- // Pequeño delay para asegurar que el layout ya renderizó
+ if (form.permite_firma && !hasInlineFirma) {
requestAnimationFrame(() => requestAnimationFrame(() => {
firma.init();
- form.esquema_decoded.forEach(c => {
- if (c.tipo === 'firma' || c.tipo === 'firma_profesional') firmaWidgetInit(c.id);
- });
}));
- } else {
+ }
+ if (hasInlineFirma) {
requestAnimationFrame(() => requestAnimationFrame(() => {
form.esquema_decoded.forEach(c => {
if (c.tipo === 'firma' || c.tipo === 'firma_profesional') firmaWidgetInit(c.id);
@@ -933,7 +988,7 @@ const formCliente = {
return `El campo "${c.label}" es obligatorio.`;
}
}
- if (_data.formulario.requiere_firma && !firma.obtenerSVG() && !firma.obtenerFoto()) {
+ if (_data.formulario.requiere_firma && !_hasInlineFirma && !firma.obtenerSVG() && !firma.obtenerFoto()) {
$('firma-container').scrollIntoView({block:'center', behavior:'smooth'});
return 'La firma es obligatoria en este formulario.';
}
diff --git a/lab_formulario_builder.php b/lab_formulario_builder.php
index 5b408e6..a402822 100644
--- a/lab_formulario_builder.php
+++ b/lab_formulario_builder.php
@@ -844,20 +844,28 @@ function abrirEditorCampo(idx) {
`;
} else if (c.tipo === 'firma' || c.tipo === 'firma_profesional') {
- const cm = c.modos || ['canvas'];
- html += `
-
-
`;
+ } else {
+ html += `
+
+ Los modos del paciente (✍️ Dibujar / 📷 Foto) se configuran
+ desde el panel superior ↑ (sección Firma del paciente).
+
`;
+ }
}
$('campo-editor-body').innerHTML = html;