// Configuración Supabase
const SUPABASE_URL = "https://fokhpkrzbonieyvcujlw.supabase.co";
const SUPABASE_KEY = "sb_publishable_4vtENh_jt7CcxhYdHk-BJw_H9quDyht";
let dbClient;
try {
if (typeof window.supabase !== 'undefined') {
dbClient = window.supabase.createClient(SUPABASE_URL, SUPABASE_KEY);
} else {
dbClient = supabase.createClient(SUPABASE_URL, SUPABASE_KEY);
}
} catch (e) {
console.error("Supabase init error:", e);
}
// Estado Global
const AppState = {
isAuthenticated: false,
userEmail: null,
theme: localStorage.getItem('theme') || 'light',
notificationsEnabled: localStorage.getItem('sanar_notifications') === 'true',
notificationTime: localStorage.getItem('sanar_notification_time') || '20:00',
lastNotificationDate: localStorage.getItem('sanar_last_notify') || '',
answers: {
paso1: '', paso2: '', paso3: '', paso4: '', paso5_1: '', paso5_2: ''
}
};
const root = document.getElementById('app-root');
// Recordatorios Locales
const NotificationManager = {
requestPermission: async () => {
if (!("Notification" in window)) return false;
if (Notification.permission === "granted") return true;
const p = await Notification.requestPermission();
return p === "granted";
},
trigger: (title, body) => {
const options = { body, icon: 'icon.png', badge: 'icon.png' };
if (navigator.serviceWorker && navigator.serviceWorker.ready) {
navigator.serviceWorker.ready.then(reg => reg.showNotification(title, options)).catch(()=> new Notification(title, options));
} else {
new Notification(title, options);
}
},
check: () => {
if (!AppState.notificationsEnabled) return;
const now = new Date();
const cTime = now.toTimeString().substring(0, 5);
const tDate = now.toDateString();
if (cTime === AppState.notificationTime && AppState.lastNotificationDate !== tDate) {
NotificationManager.trigger("Sanar la Relación", "Tómate un momento para cultivar la amabilidad contigo.");
AppState.lastNotificationDate = tDate;
localStorage.setItem('sanar_last_notify', tDate);
}
},
init: () => {
setInterval(() => NotificationManager.check(), 60000);
setTimeout(() => NotificationManager.check(), 5000);
}
};
NotificationManager.init();
// Gestor de Flujo de Pantallas
const FlowManager = {
currentStep: 0,
init: () => {
const sessionActive = sessionStorage.getItem('sanar_relacion_session');
const email = sessionStorage.getItem('sanar_relacion_email');
if (sessionActive === 'true' && email) {
AppState.isAuthenticated = true;
AppState.userEmail = email;
FlowManager.goToStep(1);
} else {
FlowManager.goToStep(0);
}
},
goToStep: (index) => {
root.style.opacity = '0';
root.style.transform = 'translateY(10px)';
setTimeout(() => {
const views = [
Views.Login(),
Views.Intro(),
Views.Paso1(),
Views.Paso2(),
Views.Paso3(),
Views.Paso4(),
Views.Paso5(),
Views.Cierre(),
Views.Success(),
"" // Index 9: Diario (rendered asynchronously)
];
if (index >= 0 && index < views.length) {
FlowManager.currentStep = index;
root.innerHTML = views[index];
if (window.feather) window.feather.replace();
FlowManager.attachEvents();
FlowManager.renderBottomMenu();
// Celebración Visual (Confeti)
if (index === 8 && window.confetti) {
setTimeout(() => {
confetti({
particleCount: 120,
spread: 80,
origin: { y: 0.6 },
colors: ['#e8a598', '#d68c8c', '#f4c7bb', '#FFD700', '#ffffff'],
disableForReducedMotion: true
});
}, 300);
}
root.style.opacity = '1';
root.style.transform = 'translateY(0)';
}
}, 300);
},
renderBottomMenu: () => {
const menuEl = document.getElementById('bottom-menu-container');
// Ocultar en Login(0) y Success(8). Mostrar en el resto.
if (FlowManager.currentStep === 0 || FlowManager.currentStep === 8) {
menuEl.classList.add('translate-y-full');
setTimeout(() => menuEl.classList.add('hidden'), 500);
} else {
if (menuEl.classList.contains('hidden') || !menuEl.innerHTML.trim()) {
menuEl.innerHTML = Views.BottomNav();
menuEl.classList.remove('hidden');
if (window.feather) window.feather.replace();
FlowManager.attachMenuEvents();
setTimeout(() => menuEl.classList.remove('translate-y-full'), 50);
}
}
},
attachMenuEvents: () => {
// Reset colors
['nav-home', 'nav-diary', 'nav-settings'].forEach(id => {
const el = document.getElementById(id);
if (el) {
el.classList.remove('text-peach-600', 'dark:text-peach-400');
el.classList.add('text-sand-900/50', 'dark:text-sand-50/50');
}
});
// Highlight current
if (FlowManager.currentStep >= 1 && FlowManager.currentStep <= 7) {
document.getElementById('nav-home')?.classList.add('text-peach-600', 'dark:text-peach-400');
document.getElementById('nav-home')?.classList.remove('text-sand-900/50', 'dark:text-sand-50/50');
} else if (FlowManager.currentStep === 9) { // Diario
document.getElementById('nav-diary')?.classList.add('text-peach-600', 'dark:text-peach-400');
document.getElementById('nav-diary')?.classList.remove('text-sand-900/50', 'dark:text-sand-50/50');
}
document.getElementById('nav-home')?.addEventListener('click', () => {
FlowManager.saveCurrentStepData();
if (FlowManager.currentStep !== 1) FlowManager.goToStep(1); // Intro
});
document.getElementById('nav-diary')?.addEventListener('click', () => {
if (FlowManager.currentStep === 9) return;
FlowManager.saveCurrentStepData();
// Transition out
root.style.opacity = '0';
root.style.transform = 'translateY(10px)';
FlowManager.currentStep = 9;
// Re-highlight menu
['nav-home', 'nav-diary', 'nav-settings'].forEach(id => {
const el = document.getElementById(id);
if (el) {
el.classList.remove('text-peach-600', 'dark:text-peach-400');
el.classList.add('text-sand-900/50', 'dark:text-sand-50/50');
}
});
document.getElementById('nav-diary')?.classList.add('text-peach-600', 'dark:text-peach-400');
document.getElementById('nav-diary')?.classList.remove('text-sand-900/50', 'dark:text-sand-50/50');
// After transition, load diary
setTimeout(() => {
root.style.opacity = '1';
root.style.transform = 'translateY(0)';
FlowManager.loadDiary();
}, 310);
});
document.getElementById('nav-settings')?.addEventListener('click', () => {
FlowManager.saveCurrentStepData();
FlowManager.openSettings();
});
},
loadDiary: async () => {
root.innerHTML = `
`;
if (window.feather) window.feather.replace();
try {
const memberId = sessionStorage.getItem('sanar_relacion_member_id');
const { data: entries, error } = await dbClient
.from('sanar_relacion_progress')
.select('*')
.eq('member_id', memberId)
.order('created_at', { ascending: false });
if (error) throw error;
root.innerHTML = Views.Diario(entries);
if (window.feather) window.feather.replace();
// Empty State Button
document.getElementById('btn-start-diary')?.addEventListener('click', () => {
FlowManager.goToStep(1); // Intro
});
} catch(e) {
console.error(e);
root.innerHTML = `Error al cargar tu diario. Por favor, verifica tu conexión a internet.
`;
}
},
openSettings: () => {
if(document.getElementById('settings-overlay')) return;
const overlay = document.createElement('div');
overlay.id = 'settings-overlay';
overlay.innerHTML = Views.Settings();
document.body.appendChild(overlay);
if (window.feather) window.feather.replace();
// Eventos
document.getElementById('btn-close-settings')?.addEventListener('click', () => overlay.remove());
// Theme toggle
document.getElementById('set-theme')?.addEventListener('change', (e) => {
const isDark = e.target.checked;
document.documentElement.classList.toggle('dark', isDark);
localStorage.setItem('theme', isDark ? 'dark' : 'light');
AppState.theme = isDark ? 'dark' : 'light';
});
// Notifications
document.getElementById('set-notify')?.addEventListener('change', async (e) => {
const isEnabled = e.target.checked;
const configDiv = document.getElementById('notify-config');
if (isEnabled) {
const granted = await NotificationManager.requestPermission();
if (!granted) {
alert("Por favor habilita las notificaciones en tu navegador o celular primero.");
e.target.checked = false;
return;
}
configDiv.classList.remove('opacity-30', 'pointer-events-none');
} else {
configDiv.classList.add('opacity-30', 'pointer-events-none');
}
});
document.getElementById('btn-save-settings')?.addEventListener('click', () => {
const notifyEnabled = document.getElementById('set-notify').checked;
const notifyTime = document.getElementById('set-notify-time').value;
localStorage.setItem('sanar_notifications', notifyEnabled);
localStorage.setItem('sanar_notification_time', notifyTime);
AppState.notificationsEnabled = notifyEnabled;
AppState.notificationTime = notifyTime;
overlay.remove();
});
// Change PIN Logic
document.getElementById('btn-change-pin')?.addEventListener('click', async () => {
const newPin = document.getElementById('new-pin').value;
const msgEl = document.getElementById('pin-msg');
const btn = document.getElementById('btn-change-pin');
if (!/^\d{4}$/.test(newPin)) {
msgEl.textContent = "El PIN debe tener 4 números.";
msgEl.className = "text-[10px] text-center text-red-500 mt-1 pb-1";
msgEl.classList.remove('hidden');
return;
}
btn.disabled = true;
btn.innerHTML = '';
try {
const memberId = sessionStorage.getItem('sanar_relacion_member_id');
const { error } = await dbClient
.from('members')
.update({ pin: newPin })
.eq('id', memberId);
if (error) throw error;
msgEl.textContent = "¡PIN actualizado con éxito en todas tus apps!";
msgEl.className = "text-[10px] text-center text-green-500 mt-1 pb-1";
msgEl.classList.remove('hidden');
document.getElementById('new-pin').value = '';
btn.innerHTML = 'Actualizar PIN';
btn.disabled = false;
} catch (err) {
msgEl.textContent = "Error al actualizar el PIN. Intenta de nuevo.";
msgEl.className = "text-[10px] text-center text-red-500 mt-1 pb-1";
msgEl.classList.remove('hidden');
btn.innerHTML = 'Actualizar PIN';
btn.disabled = false;
}
});
// Restart Application Flow
document.getElementById('btn-show-restart')?.addEventListener('click', () => {
document.getElementById('btn-show-restart').classList.add('hidden');
document.getElementById('restart-auth-area').classList.remove('hidden');
document.getElementById('restart-auth-area').classList.add('flex');
});
document.getElementById('btn-confirm-restart')?.addEventListener('click', async () => {
const enteredPin = document.getElementById('restart-pin').value;
const msgEl = document.getElementById('restart-msg');
const btn = document.getElementById('btn-confirm-restart');
if(!enteredPin) return;
btn.disabled = true;
btn.innerHTML = '';
msgEl.classList.add('hidden');
try {
const email = sessionStorage.getItem('sanar_relacion_email');
const { data: member, error } = await dbClient
.from('members')
.select('id')
.eq('email', email)
.eq('pin', enteredPin)
.single();
if (error || !member) throw new Error("PIN de confirmación incorrecto.");
// Borrar todo el historial previo en Supabase
const { error: deleteError } = await dbClient
.from('sanar_relacion_progress')
.delete()
.eq('member_id', member.id);
if (deleteError) {
throw new Error("No se pudo limpiar tu Diario. " + deleteError.message);
}
// Restart app effectively
AppState.answers = { paso1: '', paso2: '', paso3: '', paso4: '', paso5_1: '', paso5_2: '' };
document.querySelectorAll('.input-area').forEach(input => input.value = '');
overlay.remove();
FlowManager.goToStep(1); // Back to Intro
} catch (err) {
msgEl.textContent = err.message;
msgEl.classList.remove('hidden');
btn.innerHTML = 'Confirmar Limpieza';
btn.disabled = false;
}
});
document.getElementById('btn-logout')?.addEventListener('click', () => {
sessionStorage.clear();
AppState.isAuthenticated = false;
AppState.userEmail = null;
overlay.remove();
FlowManager.goToStep(0);
});
},
attachEvents: () => {
const step = FlowManager.currentStep;
if (step === 0) {
// Login
const form = document.getElementById('login-form');
const errorMsg = document.getElementById('login-error');
const btn = document.getElementById('btn-login');
form?.addEventListener('submit', async (e) => {
e.preventDefault();
const email = document.getElementById('email').value.trim();
const pin = document.getElementById('pin').value.trim();
if(!email || !pin) return;
btn.innerHTML = '';
errorMsg.classList.add('hidden');
try {
const { data: member, error } = await dbClient
.from('members')
.select('id, status')
.eq('email', email)
.eq('pin', pin)
.single();
if (error || !member) throw new Error("Credenciales inválidas.");
if (member.status !== 'active') throw new Error("Tu cuenta está deshabilitada.");
sessionStorage.setItem('sanar_relacion_session', 'true');
sessionStorage.setItem('sanar_relacion_email', email);
sessionStorage.setItem('sanar_relacion_member_id', member.id);
AppState.isAuthenticated = true;
AppState.userEmail = email;
FlowManager.goToStep(1);
} catch (err) {
errorMsg.textContent = err.message;
errorMsg.classList.remove('hidden');
btn.innerHTML = 'Iniciar Experiencia ';
if (window.feather) window.feather.replace();
}
});
}
// Navegación Básica
document.getElementById('btn-next')?.addEventListener('click', () => {
FlowManager.saveCurrentStepData();
FlowManager.goToStep(step + 1);
});
document.getElementById('btn-prev')?.addEventListener('click', () => {
FlowManager.saveCurrentStepData();
FlowManager.goToStep(step - 1);
});
// Guardar Progreso
const btnFinish = document.getElementById('btn-finish');
btnFinish?.addEventListener('click', async () => {
btnFinish.innerHTML = ' Guardando...';
try {
const memberId = sessionStorage.getItem('sanar_relacion_member_id');
const { error } = await dbClient.from('sanar_relacion_progress').insert([{
member_id: memberId,
answers: AppState.answers
}]);
if (error) {
console.error("Supabase Error:", error);
throw new Error(error.message);
}
FlowManager.goToStep(8); // Success
} catch(e) {
console.error("Error", e);
btnFinish.innerHTML = 'Finalizar e Integrar ';
alert("Error de Supabase: " + e.message);
}
});
// Final Screen
document.getElementById('btn-restart')?.addEventListener('click', () => {
AppState.answers = { paso1:'', paso2:'', paso3:'', paso4:'', paso5_1:'', paso5_2:'' };
FlowManager.goToStep(1);
});
document.getElementById('btn-logout-final')?.addEventListener('click', () => {
sessionStorage.clear();
AppState.isAuthenticated = false;
FlowManager.goToStep(0);
});
},
saveCurrentStepData: () => {
const step = FlowManager.currentStep;
if (step === 2) AppState.answers.paso1 = document.getElementById('ans-paso1')?.value || '';
if (step === 3) AppState.answers.paso2 = document.getElementById('ans-paso2')?.value || '';
if (step === 4) AppState.answers.paso3 = document.getElementById('ans-paso3')?.value || '';
if (step === 5) AppState.answers.paso4 = document.getElementById('ans-paso4')?.value || '';
if (step === 6) {
AppState.answers.paso5_1 = document.getElementById('ans-paso5-1')?.value || '';
AppState.answers.paso5_2 = document.getElementById('ans-paso5-2')?.value || '';
}
}
};
document.addEventListener('DOMContentLoaded', () => {
FlowManager.init();
});