// storymals-secure.js - VERSIÓN 1.6.0 - SOPORTE COMPLETO PARA iOS
(function(){
window.storymals = window.storymals || {};
storymals.setToken = function(t){
try { localStorage.setItem('storymals_token', t); } catch(e) { console.error(e); }
};
storymals.getToken = function(){ try { return localStorage.getItem('storymals_token'); } catch(e){ return null; } };
storymals.clearToken = function(){ try { localStorage.removeItem('storymals_token'); } catch(e){} };
let isInstalling = false;
let installTimeout = null;
let countdownInterval = null;
// ========== DETECCIÓN DE DISPOSITIVO ==========
const isIOS = /iPad|iPhone|iPod/.test(navigator.userAgent) && !window.MSStream;
const isStandalone = window.matchMedia('(display-mode: standalone)').matches ||
window.navigator.standalone ||
document.referrer.includes('android-app://');
const hasKey = window.location.search.includes('key=') || window.location.search.includes('sm_token=');
// ========== SERVICE WORKER ==========
if ('serviceWorker' in navigator) {
const isCuentosPage = window.location.href.includes('/cuento/') ||
window.location.href.includes('/biblioteca/') ||
window.location.href.includes('/categoria/') ||
document.querySelector('.cuento-wrapper') ||
document.querySelector('.cuentos-miniaturas');
const isPwaPage = window.location.search.includes('storymals_pwa=1') ||
document.body.classList.contains('storymals-pwa-page');
if (isPwaPage && !isCuentosPage) {
console.log('🟢 Registrando Service Worker');
window.addEventListener('load', () => {
navigator.serviceWorker.register('/wp-content/plugins/storymals-secure/service-worker.js')
.then(reg => console.log('✅ ServiceWorker registrado'))
.catch(err => console.log('❌ ServiceWorker error:', err));
});
} else {
navigator.serviceWorker.getRegistrations().then(registrations => {
for (let reg of registrations) {
if (reg.active && reg.active.scriptURL.includes('storymals-secure')) {
console.log('🔴 Desregistrando Service Worker');
reg.unregister();
}
}
});
}
}
// ========== FUNCIONES PARA iOS ==========
function storymals_initIOS() {
if (!isIOS) return; // Solo para iOS
console.log('🍎 Inicializando funciones para iOS');
// Si está en modo standalone, no hacer nada
if (isStandalone) {
console.log('✅ iOS en modo standalone (PWA instalada)');
document.cookie = 'storymals_standalone=1; path=/; max-age=3600';
hideAllPwaBanners();
return;
}
// Si tiene key y no está en standalone, mostrar guía de instalación
if (hasKey) {
console.log('📱 iOS detectado con key - mostrando guía de instalación');
document.cookie = 'storymals_standalone=0; path=/; max-age=300';
localStorage.setItem('storymals_ios_pending_url', window.location.href);
localStorage.setItem('storymals_ios_timestamp', Date.now());
// Intentar abrir en PWA si ya está instalada (por si acaso)
setTimeout(function() {
var pwaUrl = window.location.href.replace(/^https?:\/\//, 'storymals://');
var iframe = document.createElement('iframe');
iframe.style.display = 'none';
iframe.src = pwaUrl;
document.body.appendChild(iframe);
}, 300);
// Mostrar overlay después de 1.5 segundos
setTimeout(function() {
if (!document.getElementById('storymals-ios-overlay')) {
showIOSInstallOverlay();
}
}, 1500);
}
}
function showIOSInstallOverlay() {
var overlay = document.createElement('div');
overlay.id = 'storymals-ios-overlay';
overlay.style.cssText = 'position:fixed; top:0; left:0; right:0; bottom:0; background:rgba(0,0,0,0.85); z-index:9999999; display:flex; align-items:center; justify-content:center; flex-direction:column; padding:20px;';
overlay.innerHTML = `
📚
¡Instalar StoryMals!
Para una mejor experiencia, instalá StoryMals en tu iPhone/iPad.
1. Tocá el botón Compartir ⎙
2. Seleccioná "Añadir a pantalla de inicio"
3. Tocá "Añadir"
Continuar en Safari
¿Ya tenés la app instalada?
Abrir ahora
`;
document.body.appendChild(overlay);
}
function showIOSRedirectOverlay() {
var overlay = document.createElement('div');
overlay.id = 'storymals-ios-redirect-overlay';
overlay.style.cssText = 'position:fixed; top:0; left:0; right:0; bottom:0; background:rgba(0,0,0,0.85); z-index:9999999; display:flex; align-items:center; justify-content:center; flex-direction:column; padding:20px;';
overlay.innerHTML = `
📚
¡Abrir en StoryMals!
Ya tenés la app instalada. Abrila para continuar tu aventura.
📱 Abrir StoryMals
Continuar en Safari
`;
document.body.appendChild(overlay);
// Intentar abrir automáticamente después de 1 segundo
setTimeout(function() {
window.location.href = window.location.href.replace(/^https?:\/\//, 'storymals://');
}, 1000);
}
// ========== BANNERS PWA ==========
let deferredPrompt;
window.addEventListener('beforeinstallprompt', (e) => {
console.log('✅ beforeinstallprompt event fired');
deferredPrompt = e;
if (!isInstalling && !isIOS) {
showAllPwaBanners();
}
});
window.addEventListener('load', () => {
if (isStandalone) {
hideAllPwaBanners();
} else if (isIOS && !isInstalling) {
// En iOS solo mostrar banners si no está instalado
if (!isStandalone) {
showAllPwaBanners();
}
}
storymals_initModernBanners();
storymals_initIOS();
// Si es iOS y tiene key, mostrar redirección después de 3 segundos
if (isIOS && hasKey && !isStandalone) {
setTimeout(function() {
if (!document.getElementById('storymals-ios-redirect-overlay') &&
!document.getElementById('storymals-ios-overlay')) {
// Verificar si ya está instalado (por si acaso)
var checkPWA = window.matchMedia('(display-mode: standalone)').matches ||
window.navigator.standalone;
if (checkPWA) {
showIOSRedirectOverlay();
}
}
}, 3000);
}
});
function storymals_isPWAInstalled() {
return window.matchMedia('(display-mode: standalone)').matches ||
window.navigator.standalone ||
document.referrer.includes('android-app://');
}
function storymals_hideBannersIfInstalled() {
if (storymals_isPWAInstalled()) {
hideAllPwaBanners();
hideModernBanners();
const bannerModern = document.getElementById('storymals-pwa-banner-modern');
const notification = document.getElementById('storymals-pwa-notification');
const legacyBanner = document.getElementById('storymals-pwa-banner');
if (bannerModern) bannerModern.style.display = 'none';
if (notification) notification.style.display = 'none';
if (legacyBanner) legacyBanner.style.display = 'none';
}
}
function showAllPwaBanners() {
if (storymals_isPWAInstalled() || isInstalling) {
hideAllPwaBanners();
return;
}
// En iOS solo mostrar si no está instalado
if (isIOS && isStandalone) {
hideAllPwaBanners();
return;
}
showPwaBanner();
showModernBanners();
}
function hideAllPwaBanners() {
hidePwaBanner();
hideModernBanners();
const bannerModern = document.getElementById('storymals-pwa-banner-modern');
const notification = document.getElementById('storymals-pwa-notification');
if (bannerModern) bannerModern.style.display = 'none';
if (notification) notification.style.display = 'none';
}
function showPwaBanner() {
if (storymals_isPWAInstalled() || isInstalling) {
hidePwaBanner();
return;
}
if (isIOS && isStandalone) {
hidePwaBanner();
return;
}
var b = document.getElementById('storymals-pwa-banner');
if(b) {
applyBannerStyles();
b.style.display = 'block';
const autoHide = getOption('storymals_pwa_auto_hide', 0);
if (autoHide > 0) {
setTimeout(() => hidePwaBanner(), autoHide * 1000);
}
}
}
function hidePwaBanner() {
var b = document.getElementById('storymals-pwa-banner');
if(b) b.style.display = 'none';
}
function showModernBanners() {
if (storymals_isPWAInstalled() || isInstalling) {
hideModernBanners();
return;
}
if (isIOS && isStandalone) {
hideModernBanners();
return;
}
const banner = document.getElementById('storymals-pwa-banner-modern');
const notification = document.getElementById('storymals-pwa-notification');
if (banner && !localStorage.getItem('storymals_banner_dismissed')) {
banner.style.display = 'flex';
}
if (notification && !localStorage.getItem('storymals_notification_dismissed')) {
notification.style.display = 'flex';
}
}
function hideModernBanners() {
const banner = document.getElementById('storymals-pwa-banner-modern');
const notification = document.getElementById('storymals-pwa-notification');
if (banner) banner.style.display = 'none';
if (notification) notification.style.display = 'none';
}
function applyBannerStyles() {
var b = document.getElementById('storymals-pwa-banner');
if(!b) return;
const bgColor = getOption('storymals_pwa_bg_color', '#ffffff');
const textColor = getOption('storymals_pwa_text_color', '#000000');
const btnColor = getOption('storymals_pwa_btn_color', '#7c3aed');
const btnTextColor = getOption('storymals_pwa_btn_text_color', '#ffffff');
const position = getOption('storymals_pwa_banner_position', 'bottom');
b.style.backgroundColor = bgColor;
b.style.color = textColor;
const button = b.querySelector('button');
if(button) {
button.style.backgroundColor = btnColor;
button.style.color = btnTextColor;
button.id = 'storymals-install-btn';
}
b.style.position = 'fixed';
b.style.left = '0';
b.style.right = '0';
b.style.zIndex = '2147483647';
if (position === 'top') {
b.style.top = '0';
} else if (position === 'center') {
b.style.top = '50%';
b.style.transform = 'translateY(-50%)';
} else {
b.style.bottom = '0';
}
}
function getOption(option, defaultValue) {
try {
const element = document.getElementById('storymals-pwa-settings');
if (element) {
const settings = JSON.parse(element.textContent);
return settings[option] || defaultValue;
}
} catch(e) {}
return defaultValue;
}
function storymals_dismissBanner() {
const banner = document.getElementById('storymals-pwa-banner-modern');
if(banner) {
banner.style.display = 'none';
localStorage.setItem('storymals_banner_dismissed', 'true');
localStorage.setItem('storymals_banner_dismissed_date', new Date().toISOString());
}
}
function storymals_dismissNotification() {
const notification = document.getElementById('storymals-pwa-notification');
if(notification) {
notification.style.display = 'none';
localStorage.setItem('storymals_notification_dismissed', 'true');
localStorage.setItem('storymals_notification_dismissed_date', new Date().toISOString());
}
}
function storymals_initModernBanners() {
storymals_hideBannersIfInstalled();
if (storymals_isPWAInstalled() || isInstalling) return;
if (isIOS && isStandalone) return;
const banner = document.getElementById('storymals-pwa-banner-modern');
if(banner) {
const autoShow = banner.getAttribute('data-auto-show') === 'true';
const delay = parseInt(banner.getAttribute('data-delay')) * 1000;
const position = banner.getAttribute('data-position');
const dismissed = localStorage.getItem('storymals_banner_dismissed');
const dismissedDate = localStorage.getItem('storymals_banner_dismissed_date');
if(dismissed && dismissedDate) {
const diffDays = Math.ceil((new Date() - new Date(dismissedDate)) / (1000 * 60 * 60 * 24));
if(diffDays > 30) {
localStorage.removeItem('storymals_banner_dismissed');
localStorage.removeItem('storymals_banner_dismissed_date');
}
}
if(autoShow && !dismissed) {
setTimeout(() => {
if(deferredPrompt && !storymals_isPWAInstalled() && !isInstalling) {
banner.style.display = 'flex';
applyBannerPosition(banner, position);
}
}, delay);
}
}
const notification = document.getElementById('storymals-pwa-notification');
if(notification) {
const autoShow = notification.getAttribute('data-auto-show') === 'true';
const delay = parseInt(notification.getAttribute('data-delay')) * 1000;
const position = notification.getAttribute('data-position');
const dismissed = localStorage.getItem('storymals_notification_dismissed');
const dismissedDate = localStorage.getItem('storymals_notification_dismissed_date');
if(dismissed && dismissedDate) {
const diffDays = Math.ceil((new Date() - new Date(dismissedDate)) / (1000 * 60 * 60 * 24));
if(diffDays > 30) {
localStorage.removeItem('storymals_notification_dismissed');
localStorage.removeItem('storymals_notification_dismissed_date');
}
}
if(autoShow && !dismissed) {
setTimeout(() => {
if(deferredPrompt && !storymals_isPWAInstalled() && !isInstalling) {
notification.style.display = 'flex';
applyBannerPosition(notification, position);
}
}, delay);
}
}
}
function applyBannerPosition(element, position) {
element.style.position = 'fixed';
element.style.left = '0';
element.style.right = '0';
element.style.zIndex = '2147483647';
if(position === 'top') {
element.style.top = '0';
element.style.bottom = 'auto';
} else {
element.style.bottom = '0';
element.style.top = 'auto';
}
}
// ========== MODAL INSTALACIÓN EN PROGRESO ==========
let installingMessageDiv = null;
function showInstallingMessage() {
if (document.getElementById('storymals-installing-message')) return;
hideAllPwaBanners();
storymals_hideBannersIfInstalled();
const overlay = document.createElement('div');
overlay.id = 'storymals-installing-message';
overlay.style.cssText = 'position:fixed; top:0; left:0; width:100%; height:100%; background:rgba(0,0,0,0.8); z-index:2147483647; margin:0; padding:0;';
const card = document.createElement('div');
card.style.cssText = 'position:absolute; bottom:50px; left:50%; transform:translateX(-50%); background:#2d3748; padding:30px; border-radius:16px; max-width:90%; width:350px; text-align:center; color:white; font-family:sans-serif; box-sizing:border-box;';
const spinner = document.createElement('div');
spinner.className = 'storymals-loading-spinner';
spinner.style.cssText = 'margin:0 auto 20px auto; width:48px; height:48px; border:5px solid #f3f3f3; border-top:5px solid #ce846b; border-radius:50%; animation: spin 1s linear infinite;';
card.appendChild(spinner);
const title = document.createElement('h3');
title.textContent = 'Instalando StoryMals...';
title.style.cssText = 'color:#fff; margin-bottom:20px; font-size:1.2rem;';
card.appendChild(title);
const spacer1 = document.createElement('div');
spacer1.style.cssText = 'height: 20px;';
card.appendChild(spacer1);
const desc = document.createElement('h2');
desc.textContent = 'Esto puede tomar unos segundos. Por favor, espera.';
desc.style.cssText = 'color:#cbd5e1; font-size:14px; margin-bottom:15px;';
card.appendChild(desc);
const spacerDesc = document.createElement('div');
spacerDesc.style.cssText = 'height: 20px;';
card.appendChild(spacerDesc);
const countdownContainer = document.createElement('div');
countdownContainer.style.cssText = 'margin:15px 0; text-align:center;';
const countdownText = document.createElement('span');
countdownText.id = 'storymals-countdown-text';
countdownText.textContent = '30';
countdownText.style.cssText = 'color:#fff; font-size:18px; font-weight:bold; font-family:"Fredoka", sans-serif;';
const countdownLabel = document.createElement('span');
countdownLabel.textContent = ' segundos restantes';
countdownLabel.style.cssText = 'color:#fff; font-size:14px; font-family:"Fredoka", sans-serif;';
countdownContainer.appendChild(countdownText);
countdownContainer.appendChild(countdownLabel);
card.appendChild(countdownContainer);
const progressContainer = document.createElement('div');
progressContainer.style.cssText = 'width:100%; background-color:#4a5568; border-radius:10px; overflow:hidden; margin-top:10px;';
const progressBar = document.createElement('div');
progressBar.id = 'storymals-progress-bar';
progressBar.style.cssText = 'width:0%; height:8px; background-color:#ce846b; transition:width 0.2s linear;';
progressContainer.appendChild(progressBar);
card.appendChild(progressContainer);
overlay.appendChild(card);
document.body.appendChild(overlay);
installingMessageDiv = overlay;
let remainingSeconds = 30;
if (countdownInterval) clearInterval(countdownInterval);
countdownInterval = setInterval(() => {
remainingSeconds--;
if (remainingSeconds >= 0) {
countdownText.textContent = remainingSeconds;
const percent = ((30 - remainingSeconds) / 30) * 100;
progressBar.style.width = percent + '%';
}
if (remainingSeconds <= 0) {
clearInterval(countdownInterval);
countdownInterval = null;
}
}, 1000);
if (!document.querySelector('#storymals-spin-style')) {
const style = document.createElement('style');
style.id = 'storymals-spin-style';
style.textContent = '@keyframes spin { 0% { transform: rotate(0deg); } 100% { transform: rotate(360deg); } }';
document.head.appendChild(style);
}
}
function hideInstallingMessage() {
if (countdownInterval) {
clearInterval(countdownInterval);
countdownInterval = null;
}
if (installingMessageDiv) {
installingMessageDiv.remove();
installingMessageDiv = null;
}
}
// ========== MODAL INSTALACIÓN EXITOSA ==========
function showInstallSuccessModal() {
if (document.getElementById('storymals-install-modal')) return;
hideAllPwaBanners();
storymals_hideBannersIfInstalled();
const overlay = document.createElement('div');
overlay.id = 'storymals-install-modal';
overlay.style.cssText = 'position:fixed; top:0; left:0; width:100%; height:100%; background:rgba(0,0,0,0.9); z-index:2147483647; margin:0; padding:0;';
const card = document.createElement('div');
card.style.cssText = 'position:absolute; bottom:50px; left:50%; transform:translateX(-50%); background:#2d3748; padding:30px; border-radius:16px; max-width:90%; width:320px; text-align:center; color:white; font-family:sans-serif; box-sizing:border-box;';
const checkIcon = document.createElement('div');
checkIcon.textContent = '✓';
checkIcon.style.cssText = 'margin:0 auto 20px auto; width:48px; height:48px; background:#4ade80; border-radius:50%; display:flex; align-items:center; justify-content:center; font-size:32px; font-weight:bold; color:#fff;';
card.appendChild(checkIcon);
const title = document.createElement('h2');
title.textContent = '¡Instalación exitosa!';
title.style.cssText = 'color:#fff; margin-bottom:10px; font-size:1.5rem;';
card.appendChild(title);
const spacerTitle = document.createElement('div');
spacerTitle.style.cssText = 'height: 20px;';
card.appendChild(spacerTitle);
const message = document.createElement('h3');
message.textContent = 'Acerca tu muñeco StoryMals al dispositivo o escanea el código QR';
message.style.cssText = 'color:#e2e8f0; font-size:14px; margin-bottom:20px;';
card.appendChild(message);
const hint = document.createElement('h3');
hint.innerHTML = 'Para descubrir cuentos mágicos y vivir divertidas aventuras.';
hint.style.cssText = 'color:#cbd5e1; font-size:14px; margin-bottom:0;';
card.appendChild(hint);
const spacer2 = document.createElement('div');
spacer2.style.cssText = 'height: 20px;';
card.appendChild(spacer2);
overlay.appendChild(card);
document.body.appendChild(overlay);
}
// ========== INSTALACIÓN PWA ==========
window.storymals_installPWA = function(){
if (storymals_isPWAInstalled()) {
return;
}
// iOS: mostrar instrucciones
if (isIOS) {
if (!document.getElementById('storymals-ios-overlay')) {
showIOSInstallOverlay();
}
return;
}
if(deferredPrompt && !isInstalling){
deferredPrompt.prompt();
deferredPrompt.userChoice.then((choiceResult) => {
if (choiceResult.outcome === 'accepted') {
console.log('✅ Usuario aceptó la instalación PWA');
isInstalling = true;
hideAllPwaBanners();
storymals_hideBannersIfInstalled();
showInstallingMessage();
installTimeout = setTimeout(() => {
console.log('⏰ Mostrando modal de éxito después de 30 segundos');
hideInstallingMessage();
showInstallSuccessModal();
isInstalling = false;
installTimeout = null;
}, 30000);
try {
fetch(storymals_ajax.ajax_url + '?action=storymals_pwa_installed', {
method: 'POST',
credentials: 'same-origin'
});
} catch(e) {}
} else {
console.log('❌ Usuario canceló la instalación');
}
deferredPrompt = null;
});
} else {
alert('Para instalar StoryMals:\n\n1. Abre el menú de tu navegador (tres puntos)\n2. Selecciona "Añadir a pantalla de inicio"\n3. Confirma la instalación');
}
};
window.addEventListener('beforeunload', function() {
if (installTimeout) clearTimeout(installTimeout);
if (countdownInterval) clearInterval(countdownInterval);
});
// ========== FUNCIÓN PARA ABRIR KEY (MODIFICADA PARA iOS) ==========
storymals.openKey = async function(key){
try {
const resp = await fetch(storymals_ajax.ajax_url + '?action=' + storymals_ajax.open_action, {
method:'POST',
credentials:'same-origin',
headers:{'Content-Type':'application/json'},
body: JSON.stringify({key:key})
});
const data = await resp.json();
if(!data.success){
if(resp.status === 403 || data.data === 'login_required' || data.data === 'forbidden'){
alert('Por favor, iniciá sesión con tu cuenta (Google o Email).');
window.location.href = '/';
return;
}
alert('Error: ' + (data.data || 'Llavero inválido'));
return;
}
if(data.data.token) {
storymals.setToken(data.data.token);
// Si es iOS y tiene redirección especial
if (data.data.is_ios && data.data.ios_redirect) {
window.location.href = data.data.ios_redirect;
return;
}
if(data.data.url) {
const url = new URL(data.data.url, window.location.origin);
url.searchParams.set('sm_token', data.data.token);
window.location.href = url.toString();
}
}
} catch(e) {
console.error(e);
alert('Error de conexión.');
}
};
// ========== VALIDACIÓN DE TOKEN ==========
storymals.validateTokenOnPage = async function(){
let token = storymals.getToken();
if(!token) return {ok:false,reason:'no_token'};
try {
const resp = await fetch(storymals_ajax.ajax_url + '?action=' + storymals_ajax.validate_action, {
method:'POST',
credentials:'same-origin',
headers:{'Content-Type':'application/json'},
body: JSON.stringify({token: token, url: window.location.pathname})
});
const data = await resp.json();
if(data.success && data.data && data.data.renewed && data.data.new_token) {
storymals.setToken(data.data.new_token);
}
return data;
} catch(e){ console.error(e); return {ok:false,reason:'network'}; }
};
// ========== EXPORTAR FUNCIONES GLOBALES ==========
window.storymals_dismissBanner = storymals_dismissBanner;
window.storymals_dismissNotification = storymals_dismissNotification;
// ========== INICIALIZACIÓN ==========
window.addEventListener('DOMContentLoaded', function(){
var auto = document.body.getAttribute('data-storymals-validate');
if(auto === '1'){
storymals.validateTokenOnPage().then(function(res){
if(res && !res.success){
console.warn('Token inválido o expirado', res);
}
});
}
storymals_hideBannersIfInstalled();
storymals_initModernBanners();
storymals_initIOS();
});
})();
Storymals – Storymals
Ir al contenido
Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Suspendisse et justo. Praesent mattis commodo augue. Morbi fringilla congue libero, ac malesuada vulputate pharetra.
Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Suspendisse et justo. Praesent mattis commodo augue.
Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Suspendisse et justo. Praesent mattis commodo augue.
Lorem ipsum dolor sit amet, consectetuer adipiscing elit. Suspendisse et justo. Praesent mattis commodo augue.
Etiam erat lectus, finibus eget commodo quis, tincidunt eget leo. Nullam quis vulputate orci, ac accumsan quam. Morbi fringilla congue libero.