M¡!ADIÓS®

Esquela con Foto

Cree una esquela personalizada con fotografía del fallecido. Elija entre 8 estilos de marco, ajuste la foto y descargue en alta calidad.

edit_document Esquela Clásica add_photo_alternate Esquela con Foto

add_photo_alternate Fotografía

add_photo_alternate

Haga clic para seleccionar una foto

JPG, PNG, WEBP — recomendado retrato

PequeñoMedianoGrande

crop_free Estilo de Marco

Clásico
Barroco
Dorado
Vintage
Moderno
Elegante
✿
✿
✿
✿
Floral
Oscuro
Prensa

edit_note Datos de la Esquela

Opciones de descarga privada
Vista Previa en Tiempo Real
In Memoriam
person Foto

NOMBRE Y APELLIDOS

Falleció a los X años de edad

Lugar, Fecha

D.E.P.

Sus familiares y amigos...

RUEGAN una oración por su alma y la asistencia a la ceremonia religiosa

miadios.com

El documento se descargará en alta resolución

Gestionado en la Unión Europea/I Dislexia
Utilizamos cookies propias y de terceros para mejorar su experiencia. Política de Privacidad.
policy

Aviso de Privacidad

Este generador de esquelas con foto es una herramienta gratuita para uso personal. Las imágenes que suba no se envían a ningún servidor — se procesan únicamente en su dispositivo.

Información Importante:

  • El usuario es responsable de la veracidad de los datos.
  • Las fotos permanecen en su dispositivo en todo momento.
  • No se publicará nada automáticamente sin su consentimiento.

Compartir Esquela

Mantén pulsado para guardar la imagen

'); w.document.close(); } } else { var a = document.createElement('a'); a.download = filename; a.href = dataUrl; document.body.appendChild(a); a.click(); document.body.removeChild(a); } } // ===== STATE ===== var photoDataUrl = null; var currentFrame = 'frame-classic'; var currentShape = 'shape-circle'; var currentSize = 'foto-size-md'; var currentEsquelaId = null; var shareBlob = null; var sharePdfDoc = null; var sizeLabels = ['Pequeño', 'Mediano', 'Grande']; var sizeClasses = ['foto-size-sm', 'foto-size-md', 'foto-size-lg']; // ===== DOM REFS ===== var esquelaInner = document.getElementById('esquelaFotoInner'); var fotoWrapper = document.getElementById('fotoWrapper'); var fotoPlaceholder = document.getElementById('fotoPlaceholder'); var captureArea = document.getElementById('captureArea'); var brandingFooter = document.getElementById('brandingFooter'); var sinPromocion = document.getElementById('sinPromocion'); var sinMensajeChk = document.getElementById('sinMensaje'); var sinCeremoniaChk = document.getElementById('sinCeremonia'); var saveBtn = document.getElementById('saveEsquelaBtn'); var shareModal = document.getElementById('shareModal'); var shareClose = document.getElementById('shareClose'); var shareThumb = document.getElementById('shareThumb'); var copyFeedback = document.getElementById('shareCopyFeedback'); var inp = { nombre: document.getElementById('nombre'), subtitulo: document.getElementById('subtitulo'), edad: document.getElementById('edad'), fecha: document.getElementById('fecha'), simbolo: document.getElementById('simbolo'), mensaje: document.getElementById('mensaje'), familia: document.getElementById('familia'), ceremonia: document.getElementById('ceremonia'), googleMapsUrl: document.getElementById('googleMapsUrl') }; var prev = { nombre: document.getElementById('previewNombre'), subtitulo: document.getElementById('previewSubtitulo'), edad: document.getElementById('previewEdad'), fecha: document.getElementById('previewFecha'), simbolo: document.getElementById('previewSimbolo'), cruzImg: document.getElementById('previewCruzImg'), simText: document.getElementById('previewSimboloText'), mensaje: document.getElementById('previewMensaje'), familia: document.getElementById('previewFamilia'), ceremonia: document.getElementById('previewCeremonia'), mapContainer: document.getElementById('previewMapContainer'), mapQr: document.getElementById('previewMapQr') }; // ===== PHOTO UPLOAD ===== var fotoInput = document.getElementById('fotoInput'); var uploadZone = document.getElementById('uploadZone'); var uploadPlaceholder = document.getElementById('uploadPlaceholder'); var uploadPreviewWrap = document.getElementById('uploadPreviewWrap'); var uploadThumb = document.getElementById('uploadThumb'); // Compress image to max 800px and JPEG 0.75 so it fits in localStorage (~5MB limit) function compressAndSetPhoto(dataUrl) { var img = new Image(); img.onload = function () { var MAX = 800; var w = img.width, h = img.height; if (w > MAX || h > MAX) { if (w > h) { h = Math.round(h * MAX / w); w = MAX; } else { w = Math.round(w * MAX / h); h = MAX; } } var canvas = document.createElement('canvas'); canvas.width = w; canvas.height = h; canvas.getContext('2d').drawImage(img, 0, 0, w, h); photoDataUrl = canvas.toDataURL('image/jpeg', 0.75); uploadThumb.src = photoDataUrl; uploadPlaceholder.classList.add('hidden'); uploadPreviewWrap.classList.remove('hidden'); uploadPreviewWrap.classList.add('flex'); renderFotoInPreview(); }; img.src = dataUrl; } // ── Elegir foto de mi galería (subida antes desde Configuración en el panel) ── function openGalleryPicker() { var user = null; try { user = JSON.parse(sessionStorage.getItem('memorial_user')); } catch (e) { } if (!user || !user.id) { alert('Sesión expirada o inválida. Por favor, inicia sesión de nuevo.'); return; } var grid = document.getElementById('galleryPickerGrid'); grid.innerHTML = '

Cargando...

'; document.getElementById('modalGalleryPicker').classList.remove('hidden'); fetch('api/user_gallery.php?user_id=' + encodeURIComponent(user.id)) .then(function (r) { return r.json(); }) .then(function (data) { var fotos = (data.success && data.fotos) ? data.fotos : []; if (!fotos.length) { grid.innerHTML = '

No tienes fotos guardadas todavía. Añádelas desde el panel → ⚙ Configuración → Mis Fotos.

'; return; } grid.innerHTML = fotos.map(function (f) { return ''; }).join(''); }) .catch(function () { grid.innerHTML = '

Error al cargar la galería.

'; }); } fotoInput.addEventListener('change', function (e) { var file = e.target.files[0]; if (!file) return; var reader = new FileReader(); reader.onload = function (ev) { compressAndSetPhoto(ev.target.result); }; reader.readAsDataURL(file); }); // Drag and drop uploadZone.addEventListener('dragover', function (e) { e.preventDefault(); uploadZone.classList.add('drag-over'); }); uploadZone.addEventListener('dragleave', function () { uploadZone.classList.remove('drag-over'); }); uploadZone.addEventListener('drop', function (e) { e.preventDefault(); uploadZone.classList.remove('drag-over'); var file = e.dataTransfer.files[0]; if (!file || !file.type.startsWith('image/')) return; var reader = new FileReader(); reader.onload = function (ev) { compressAndSetPhoto(ev.target.result); }; reader.readAsDataURL(file); }); function quitarFotoFn() { photoDataUrl = null; fotoInput.value = ''; uploadThumb.src = ''; uploadPreviewWrap.classList.add('hidden'); uploadPreviewWrap.classList.remove('flex'); uploadPlaceholder.classList.remove('hidden'); renderFotoInPreview(); } window.quitarFotoFn = quitarFotoFn; function renderFotoInPreview() { // Remove existing img if any var existingImg = document.getElementById('previewFoto'); if (existingImg) existingImg.remove(); if (photoDataUrl) { fotoPlaceholder.style.display = 'none'; var img = document.createElement('img'); img.id = 'previewFoto'; img.src = photoDataUrl; img.alt = 'Foto del fallecido'; img.style.cssText = 'width:100%;height:100%;object-fit:cover;display:block;'; fotoWrapper.appendChild(img); } else { fotoPlaceholder.style.display = 'flex'; } } // ===== PHOTO SHAPE ===== document.querySelectorAll('.shape-btn').forEach(function (btn) { btn.addEventListener('click', function () { document.querySelectorAll('.shape-btn').forEach(function (b) { b.classList.remove('active'); }); btn.classList.add('active'); currentShape = btn.dataset.shape; applyFotoShape(); }); }); function applyFotoShape() { fotoWrapper.classList.remove('shape-circle', 'shape-oval', 'shape-square', 'shape-rounded'); fotoWrapper.classList.add(currentShape); } // ===== PHOTO SIZE ===== function updateFotoSize(val) { val = parseInt(val); document.getElementById('sizeLabel').textContent = sizeLabels[val]; // Update track fill for visual slider var pct = val === 0 ? '0%' : val === 1 ? '50%' : '100%'; document.getElementById('fotoSize').style.setProperty('--track-fill', pct); esquelaInner.classList.remove('foto-size-sm', 'foto-size-md', 'foto-size-lg'); currentSize = sizeClasses[val]; esquelaInner.classList.add(currentSize); } // ===== FRAME SELECTOR ===== document.querySelectorAll('.frame-thumb').forEach(function (thumb) { thumb.addEventListener('click', function () { document.querySelectorAll('.frame-thumb').forEach(function (t) { t.classList.remove('active'); }); thumb.classList.add('active'); currentFrame = thumb.dataset.frame; applyFrame(); }); }); function applyFrame() { esquelaInner.classList.remove( 'frame-classic', 'frame-barroco', 'frame-dorado', 'frame-vintage', 'frame-moderno', 'frame-elegante', 'frame-floral', 'frame-oscuro', 'frame-prensa' ); esquelaInner.classList.add(currentFrame); // Corner ornaments — only visible in floral var cornersVisible = currentFrame === 'frame-floral'; document.querySelectorAll('.corner-ornament').forEach(function (el) { el.style.display = cornersVisible ? 'block' : 'none'; }); // Prensa header var prensaHeader = esquelaInner.querySelector('.prensa-header'); if (prensaHeader) { prensaHeader.style.display = currentFrame === 'frame-prensa' ? 'block' : 'none'; } // For prensa frame — rearrange layout restructureForFrame(); } function restructureForFrame() { // Ensure fotoWrapper is inside the correct container depending on frame var prensaBody = esquelaInner.querySelector('.prensa-body'); if (currentFrame === 'frame-prensa') { // Build prensa layout if not already built if (!prensaBody) { prensaBody = document.createElement('div'); prensaBody.className = 'prensa-body'; var fotoCol = document.createElement('div'); fotoCol.className = 'prensa-foto-col'; var textCol = document.createElement('div'); textCol.className = 'prensa-text-col'; // Move fotoWrapper into fotoCol fotoCol.appendChild(fotoWrapper); // Move text elements into textCol var toMove = [ prev.simbolo, prev.nombre, prev.edad, prev.fecha, prev.mensaje, esquelaInner.querySelector('.esquela-info-section') ]; toMove.forEach(function (el) { if (el) textCol.appendChild(el); }); prensaBody.appendChild(fotoCol); prensaBody.appendChild(textCol); // Insert prensaBody after prensa-header var prensaHeader = esquelaInner.querySelector('.prensa-header'); if (prensaHeader) { prensaHeader.insertAdjacentElement('afterend', prensaBody); } else { // Insert before branding var bf = document.getElementById('brandingFooter'); esquelaInner.insertBefore(prensaBody, bf); } } } else { // Restore normal layout if previously in prensa mode if (prensaBody) { var fotoCol = prensaBody.querySelector('.prensa-foto-col'); var textCol = prensaBody.querySelector('.prensa-text-col'); // Re-attach fotoWrapper at top of esquelaInner (before simbolo) var cornersEnd = null; var corners = esquelaInner.querySelectorAll('.corner-ornament'); if (corners.length > 0) { cornersEnd = corners[corners.length - 1]; } var prensaHeader2 = esquelaInner.querySelector('.prensa-header'); var insertRef = prensaHeader2 ? prensaHeader2.nextSibling : (cornersEnd ? cornersEnd.nextSibling : esquelaInner.firstChild); // Move fotoWrapper back esquelaInner.insertBefore(fotoWrapper, insertRef); // Move text elements back var bf2 = document.getElementById('brandingFooter'); if (textCol) { Array.from(textCol.children).forEach(function (child) { esquelaInner.insertBefore(child, bf2); }); } prensaBody.remove(); } } } // ===== PREVIEW UPDATE ===== function updatePreview() { prev.nombre.textContent = inp.nombre.value || 'NOMBRE Y APELLIDOS'; if (prev.subtitulo) prev.subtitulo.textContent = inp.subtitulo ? inp.subtitulo.value : ''; prev.edad.textContent = inp.edad.value ? 'Falleció a los ' + inp.edad.value + ' de edad' : 'Falleció a los X años de edad'; prev.fecha.textContent = inp.fecha.value || 'Lugar, Fecha'; if (sinMensajeChk && sinMensajeChk.checked) { prev.mensaje.innerHTML = ''; } else if (inp.mensaje.value) { prev.mensaje.textContent = inp.mensaje.value; } else { prev.mensaje.innerHTML = 'D.E.P.'; } prev.familia.textContent = inp.familia.value || 'Sus familiares y amigos...'; if (sinCeremoniaChk && sinCeremoniaChk.checked) { prev.ceremonia.textContent = ''; } else { prev.ceremonia.textContent = inp.ceremonia.value || 'RUEGAN una oración por su alma y la asistencia a la ceremonia religiosa'; } // Symbol var val = inp.simbolo.value; if (val === 'none') { prev.simbolo.style.display = 'none'; } else if (val.indexOf('img:') === 0) { prev.simbolo.style.display = 'flex'; prev.cruzImg.style.display = 'block'; prev.simText.textContent = ''; var cruceSrc = val.replace('img:', ''); prev.cruzImg.src = CRUCES_SVG[cruceSrc] || cruceSrc; } else { prev.simbolo.style.display = 'flex'; prev.cruzImg.style.display = 'none'; prev.simText.textContent = val; prev.simText.style.fontSize = '2.2rem'; prev.simText.style.fontFamily = "'Playfair Display', serif"; } // Map / QR var mapUrl = inp.googleMapsUrl.value.trim(); if (mapUrl) { prev.mapContainer.classList.remove('hidden'); prev.mapContainer.classList.add('flex'); prev.mapQr.src = generarQrDataUrl(mapUrl, 120) || ''; } else { prev.mapContainer.classList.add('hidden'); prev.mapContainer.classList.remove('flex'); prev.mapQr.src = ''; } if (typeof scalePreview === 'function') { scalePreview(); } else if (typeof window.scaleEsquelaPreview === 'function') { window.scaleEsquelaPreview(); } } // Bind all text inputs Object.values(inp).forEach(function (el) { el.addEventListener('input', updatePreview); }); // Sin promoción sinPromocion.addEventListener('change', function () { brandingFooter.style.display = sinPromocion.checked ? 'none' : ''; }); if (sinMensajeChk) { sinMensajeChk.addEventListener('change', updatePreview); } if (sinCeremoniaChk) { sinCeremoniaChk.addEventListener('change', updatePreview); } // Clear document.getElementById('clearFields').addEventListener('click', function () { Object.values(inp).forEach(function (el) { el.value = ''; }); sinPromocion.checked = false; brandingFooter.style.display = ''; quitarFotoFn(); updatePreview(); }); // ===== CAPTURE ===== function capturarEsquela(scale, callback, onError) { if (_isMobile) { _capturarMovil(scale, callback, onError); } else { _capturarDesktop(scale, callback, onError); } } // --- Captura escritorio --- function _capturarDesktop(scale, callback, onError) { var scaler = captureArea.parentElement; var origCapStyle = captureArea.getAttribute('style') || ''; var origScalerStyle = scaler ? scaler.getAttribute('style') || '' : ''; captureArea.setAttribute('style', origCapStyle + ';width:520px!important;min-width:520px!important;max-width:520px!important;' + 'transform:none!important;box-shadow:none!important;margin:0!important;'); if (scaler) { scaler.setAttribute('style', origScalerStyle + ';width:520px!important;min-width:520px!important;' + 'overflow:visible!important;height:auto!important;'); } captureArea.getBoundingClientRect(); function restore() { if (origCapStyle) captureArea.setAttribute('style', origCapStyle); else captureArea.removeAttribute('style'); if (scaler) { if (origScalerStyle) scaler.setAttribute('style', origScalerStyle); else scaler.removeAttribute('style'); } } setTimeout(function () { var captureHeight = Math.max( captureArea.scrollHeight || 0, captureArea.offsetHeight || 0 ) + 2; html2canvas(captureArea, { scale: scale, useCORS: true, allowTaint: false, backgroundColor: '#ffffff', width: 520, height: captureHeight, windowWidth: 900, logging: false }).then(function (canvas) { restore(); callback(canvas); }) .catch(function (err) { restore(); if (onError) onError(err); }); }, 100); } // --- Captura móvil: iframe oculto a exactamente 520px --- function _capturarMovil(scale, callback, onError) { // Recopilar todos los estilos, incluyendo dinámicos (Tailwind) var cssText = ''; for (var si = 0; si < document.styleSheets.length; si++) { try { var sheet = document.styleSheets[si]; var rules = sheet.cssRules || sheet.rules; if (rules) { for (var ri = 0; ri < rules.length; ri++) { cssText += rules[ri].cssText + '\n'; } } } catch (e) { // CORS de estilos de terceros (se copian como link) } } if (!cssText.trim()) { var styleEls = document.querySelectorAll('style'); for (var sidx = 0; sidx < styleEls.length; sidx++) { cssText += styleEls[sidx].textContent; } } var linksText = ''; var linkEls = document.querySelectorAll('link[rel="stylesheet"]'); for (var li = 0; li < linkEls.length; li++) { linksText += linkEls[li].outerHTML; } var cloneHtml = captureArea.outerHTML.replace(/id="captureArea"/, 'id="capClone"'); var iframe = document.createElement('iframe'); iframe.setAttribute('scrolling', 'no'); iframe.style.cssText = 'position:fixed;left:0;top:0;width:520px;height:800px;' + 'border:none;opacity:0;pointer-events:none;z-index:-9999;'; document.body.appendChild(iframe); iframe.srcdoc = '' + '' + linksText + '' + ' ' + cloneHtml + ''; iframe.onload = function () { var iDoc = iframe.contentDocument || iframe.contentWindow.document; var target = iDoc.getElementById('capClone'); if (!target) { if (document.body.contains(iframe)) document.body.removeChild(iframe); if (onError) onError(new Error('Clon no encontrado')); return; } // Esperar a que carguen todas las imágenes (fotos, códigos QR, cruces, etc.) var imgs = target.querySelectorAll('img'); var loadedCount = 0; var totalImgs = imgs.length; function checkDone() { loadedCount++; if (loadedCount >= totalImgs) { runCapture(); } } if (totalImgs === 0) { runCapture(); } else { for (var ii = 0; ii < totalImgs; ii++) { var img = imgs[ii]; if (img.complete || img.naturalWidth > 0) { checkDone(); } else { img.onload = checkDone; img.onerror = checkDone; } } } function runCapture() { setTimeout(function () { // Ajustar altura del iframe al contenido real var iH = Math.max(target.scrollHeight || 0, target.offsetHeight || 0) + 2; iframe.style.height = iH + 'px'; html2canvas(target, { scale: scale, useCORS: true, allowTaint: false, backgroundColor: '#ffffff', windowWidth: 520, height: iH, logging: false }).then(function (canvas) { if (document.body.contains(iframe)) document.body.removeChild(iframe); callback(canvas); }).catch(function (err) { if (document.body.contains(iframe)) document.body.removeChild(iframe); if (onError) onError(err); }); }, 150); } }; } // Reusable function to trigger PDF download function triggerPdfDownloadFoto(btn) { var origHtml = btn.innerHTML; btn.innerHTML = 'Generando PDF...'; btn.disabled = true; var modalOpen = shareModal && shareModal.classList.contains('open'); if (modalOpen) { shareModal.classList.remove('open'); } var _mobileWin = null; if (_isIOS) { _mobileWin = window.open('', '_blank'); if (_mobileWin) { _mobileWin.document.write('Generando PDF...'); _mobileWin.document.close(); } } capturarEsquela(_isMobile ? 1.5 : 2, function (canvas) { try { var imgData = canvas.toDataURL('image/jpeg', 0.92); var Lib = (window.jspdf && window.jspdf.jsPDF) || window.jsPDF; // Generar siempre una sola hoja A4 con fondo blanco var pdf = new Lib({ orientation: 'p', unit: 'mm', format: 'a4' }); pdf.setFillColor(255, 255, 255); pdf.rect(0, 0, 210, 297, 'F'); var pw = 210, ph = 297; var margin = 15; // 15mm margin var maxW = pw - (margin * 2); var maxH = ph - (margin * 2); var ratio = canvas.height / canvas.width; var dw = maxW, dh = maxW * ratio; if (dh > maxH) { dh = maxH; dw = maxH / ratio; } var ox = margin + (maxW - dw) / 2; var oy = margin + (maxH - dh) / 2; pdf.addImage(imgData, 'JPEG', ox, oy, dw, dh); if (_mobileWin) { var pdfUrl = pdf.output('bloburl'); _mobileWin.location.href = pdfUrl; } else { pdf.save('esquela.pdf'); } } catch (e) { if (_mobileWin) _mobileWin.close(); alert('Error al generar PDF: ' + (e.message || e)); } finally { if (modalOpen && shareModal) { shareModal.classList.add('open'); } btn.innerHTML = origHtml; btn.disabled = false; } }, function (e) { if (_mobileWin) _mobileWin.close(); if (modalOpen && shareModal) { shareModal.classList.add('open'); } alert('Error al procesar imagen: ' + (e && e.message ? e.message : e)); btn.innerHTML = origHtml; btn.disabled = false; }); } // ===== PDF ===== document.getElementById('downloadPdf').addEventListener('click', function () { triggerPdfDownloadFoto(this); }); // ===== JPG ===== document.getElementById('downloadJpg').addEventListener('click', function () { var btn = this; var origHtml = btn.innerHTML; btn.innerHTML = 'Generando...'; btn.disabled = true; capturarEsquela(_isMobile ? 2 : 3, function (canvas) { descargarArchivo(canvas.toDataURL('image/jpeg', 0.95), 'esquela.jpg'); btn.innerHTML = origHtml; btn.disabled = false; }, function (e) { alert('Error al generar imagen: ' + (e && e.message ? e.message : e)); btn.innerHTML = origHtml; btn.disabled = false; }); }); // ===== COMPARTIR ===== function openShareModal() { shareModal.classList.add('open'); copyFeedback.textContent = ''; shareThumb.src = ''; capturarEsquela(2, function (canvas) { var imgData = canvas.toDataURL('image/jpeg', 0.9); shareThumb.src = imgData; }); } document.getElementById('shareEsquela').addEventListener('click', openShareModal); shareClose.addEventListener('click', function () { shareModal.classList.remove('open'); }); shareModal.addEventListener('click', function (e) { if (e.target === shareModal) shareModal.classList.remove('open'); }); document.getElementById('shareImgUnified').addEventListener('click', function () { if (!shareThumb.src) { copyFeedback.textContent = 'Espera, generando imagen...'; return; } descargarArchivo(shareThumb.src, 'esquela.jpg'); copyFeedback.textContent = '✓ Foto descargada. Compártela desde tu carpeta de descargas.'; setTimeout(function () { copyFeedback.textContent = ''; }, 6000); }); var sharePdfBtn = document.getElementById('sharePdfUnified'); if (sharePdfBtn) { sharePdfBtn.addEventListener('click', function () { var mainPdfBtn = document.getElementById('downloadPdf'); if (mainPdfBtn) { mainPdfBtn.click(); } else { triggerPdfDownloadFoto(this); } }); } // ===== GUARDAR EN PANEL ===== saveBtn.addEventListener('click', async function () { var user = null; try { user = JSON.parse(sessionStorage.getItem('memorial_user')); } catch (e) { } if (!user || !user.session_token) { alert('Sesión expirada o inválida. Por favor, inicia sesión de nuevo.'); window.location.href = 'registro.html'; return; } saveBtn.disabled = true; var origHTML = saveBtn.innerHTML; saveBtn.innerHTML = 'hourglass_empty Guardando...'; var payload = { user_id: user.id, esquela_id: currentEsquelaId || null, nombre: inp.nombre.value ? inp.nombre.value.substring(0, 150) : 'Sin nombre', edad: inp.edad.value ? inp.edad.value.substring(0, 100) : '', modelo: 'foto-' + currentFrame, simbolo: inp.simbolo.value, fecha: inp.fecha.value ? inp.fecha.value.substring(0, 100) : '', mensaje: inp.mensaje.value ? inp.mensaje.value.substring(0, 1500) : '', familia: inp.familia.value ? inp.familia.value.substring(0, 1500) : '', ceremonia: inp.ceremonia.value ? inp.ceremonia.value.substring(0, 1500) : '', google_maps_url: inp.googleMapsUrl.value ? inp.googleMapsUrl.value.substring(0, 480) : '', sin_promocion: sinPromocion.checked, imagen_url: '' }; // Always save to localStorage var dataStore = JSON.parse(localStorage.getItem('mando_' + user.id)) || { esquelas: [], voluntades: [], compromisos: [], logs: [] }; if (!dataStore.logs) dataStore.logs = []; if (!dataStore.esquelas) dataStore.esquelas = []; // Embed photo into the local esquela object (compressed, ~60KB — safe for localStorage) if (photoDataUrl) payload.foto_local = photoDataUrl; if (currentEsquelaId) { var idx = -1; for (var k = 0; k < dataStore.esquelas.length; k++) { if (dataStore.esquelas[k].id === currentEsquelaId) { idx = k; break; } } if (idx !== -1) { for (var key in payload) { dataStore.esquelas[idx][key] = payload[key]; } } else { payload.id = currentEsquelaId; dataStore.esquelas.unshift(payload); } } else { var newId = 'EQF_' + Date.now(); payload.id = newId; payload.esquela_id = newId; if (!payload.fecha) payload.fecha = new Date().toLocaleDateString(); dataStore.esquelas.unshift(payload); currentEsquelaId = newId; } dataStore.logs.unshift({ id: Date.now(), text: 'esquela con foto "' + payload.nombre + '" guardada.', time: new Date().toLocaleTimeString(), type: 'save' }); try { localStorage.setItem('mando_' + user.id, JSON.stringify(dataStore)); } catch (qe) { // Quota exceeded: strip foto_local from the correct esquela and use separate key var qIdx = dataStore.esquelas.findIndex(function (e) { return e.id === currentEsquelaId; }); var fotoBackup = qIdx !== -1 ? dataStore.esquelas[qIdx].foto_local : null; if (qIdx !== -1) delete dataStore.esquelas[qIdx].foto_local; localStorage.setItem('mando_' + user.id, JSON.stringify(dataStore)); if (fotoBackup) { try { localStorage.setItem('foto_esquela_' + currentEsquelaId, fotoBackup); } catch (e2) { } } } // Try server — foto_local se envía al servidor como foto_local (esquelas.php lo guarda en imagen_url MEDIUMTEXT) var serverPayload = Object.assign({}, payload); try { if (window.location.protocol !== 'file:') { var res = await fetch('api/esquelas.php', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(serverPayload) }); if (!res.ok) { showToast('Guardado en este dispositivo (no se pudo sincronizar con el servidor).', 'warning'); } else { showToast('¡Esquela guardada correctamente!'); } } else { showToast('¡Esquela guardada localmente!'); } } catch (e) { showToast('Error de red, guardado solo localmente.', 'warning'); } saveBtn.disabled = false; saveBtn.innerHTML = origHTML; setTimeout(function () { window.location.href = 'panel.html'; }, 2500); }); // ===== TOAST ===== function showToast(msg, type) { type = type || 'success'; var bg = type === 'warning' ? '#f59e0b' : (type === 'error' ? '#ef4444' : '#16a34a'); var icon = type === 'warning' ? 'warning' : (type === 'error' ? 'error' : 'verified'); var el = document.createElement('div'); el.style.cssText = 'position:fixed;bottom:24px;right:24px;z-index:99999;background:' + bg + ';color:#fff;padding:16px 24px;border-radius:16px;box-shadow:0 20px 40px rgba(0,0,0,0.3);display:flex;align-items:center;gap:12px;font-weight:700;font-size:13px;text-transform:uppercase;letter-spacing:0.1em;'; el.innerHTML = '' + icon + ' ' + msg; document.body.appendChild(el); setTimeout(function () { if (el.parentNode) el.remove(); }, 4000); } // ===== COOKIES ===== window.acceptCookies = function () { var banner = document.getElementById('cookiesBanner'); if (banner) { banner.style.display = 'none'; localStorage.setItem('cookies_accepted', 'true'); } }; // ===== INIT ===== (function init() { // Show disclaimer var discModal = document.getElementById('disclaimerModal'); var acceptDisc = document.getElementById('acceptDisclaimer'); if (discModal && !sessionStorage.getItem('esquelas_foto_disclaimer_ok')) { discModal.style.display = 'flex'; } if (acceptDisc) { acceptDisc.addEventListener('click', function () { discModal.style.display = 'none'; sessionStorage.setItem('esquelas_foto_disclaimer_ok', 'true'); }); } // Cookies banner var cookiesBanner = document.getElementById('cookiesBanner'); if (cookiesBanner && !localStorage.getItem('cookies_accepted')) { cookiesBanner.style.display = 'block'; } // Show panel button if logged in var user = null; try { user = JSON.parse(sessionStorage.getItem('memorial_user')); } catch (e) { } if (user && user.session_token) { var btnPanel = document.getElementById('btnVolverPanel'); if (btnPanel) { btnPanel.classList.remove('hidden'); btnPanel.classList.add('inline-flex'); } } // URL params: preload from panel edit link var urlParams = new URLSearchParams(window.location.search); var idToLoad = urlParams.get('id'); if (idToLoad && user) { var dataStore = JSON.parse(localStorage.getItem('mando_' + user.id)) || { esquelas: [] }; for (var j = 0; j < dataStore.esquelas.length; j++) { var local = dataStore.esquelas[j]; if (local.id === idToLoad) { currentEsquelaId = idToLoad; inp.nombre.value = local.nombre || ''; inp.edad.value = local.edad || ''; inp.simbolo.value = local.simbolo || 'img:cruces/1.svg'; inp.fecha.value = local.fecha || ''; inp.mensaje.value = local.mensaje || ''; inp.familia.value = local.familia || ''; inp.ceremonia.value = local.ceremonia || ''; if (inp.googleMapsUrl && local.google_maps_url) inp.googleMapsUrl.value = local.google_maps_url; sinPromocion.checked = !!local.sin_promocion; if (sinPromocion.checked) brandingFooter.style.display = 'none'; // Restore frame from modelo field if (local.modelo && local.modelo.startsWith('foto-')) { var frameName = local.modelo.replace('foto-', ''); var matchThumb = document.querySelector('[data-frame="' + frameName + '"]'); if (matchThumb) { document.querySelectorAll('.frame-thumb').forEach(function (t) { t.classList.remove('active'); }); matchThumb.classList.add('active'); currentFrame = frameName; } } // Restore photo: try foto_local field first, then separate localStorage key var savedPhoto = local.foto_local || localStorage.getItem('foto_esquela_' + idToLoad); if (savedPhoto) { photoDataUrl = savedPhoto; uploadThumb.src = savedPhoto; uploadPlaceholder.classList.add('hidden'); uploadPreviewWrap.classList.remove('hidden'); uploadPreviewWrap.classList.add('flex'); renderFotoInPreview(); } saveBtn.innerHTML = 'update
Actualizar en PanelActualiza los cambios en tu panel personal
'; break; } } } // Apply initial state applyFrame(); applyFotoShape(); esquelaInner.classList.add(currentSize); updatePreview(); // Initially hide corners (only visible for floral) document.querySelectorAll('.corner-ornament').forEach(function (el) { el.style.display = 'none'; }); // Hide prensa header initially var ph = esquelaInner.querySelector('.prensa-header'); if (ph) ph.style.display = 'none'; })();