import React, { useState, useEffect } from 'react'; import { Play, Check, ChevronRight, Save, History, Trophy, Flame, Brain, Heart, Target, Sparkles, RefreshCcw, Loader2, Moon, Sun, Volume2, Droplets, BarChart3, HelpCircle, X, Cloud, LogIn, LogOut } from 'lucide-react'; import { initializeApp } from 'firebase/app'; import { getAuth, signInWithCustomToken, signInAnonymously, onAuthStateChanged, signOut, GoogleAuthProvider, signInWithPopup } from 'firebase/auth'; import { getFirestore, doc, setDoc, onSnapshot } from 'firebase/firestore'; const MorningRitualApp = () => { // CONFIGURACIÓN API GEMINI const apiKey = ""; // La clave se inyecta en tiempo de ejecución // --- FIREBASE SETUP --- const [user, setUser] = useState(null); const [authObj, setAuthObj] = useState(null); // Guardamos la instancia de auth const [db, setDb] = useState(null); const [appId, setAppId] = useState(null); const [isSyncing, setIsSyncing] = useState(true); const [authError, setAuthError] = useState(null); useEffect(() => { // 1. Inicializar Firebase const firebaseConfig = JSON.parse(__firebase_config); const app = initializeApp(firebaseConfig); const auth = getAuth(app); const database = getFirestore(app); const currentAppId = typeof __app_id !== 'undefined' ? __app_id : 'default-app-id'; setAuthObj(auth); setDb(database); setAppId(currentAppId); // 2. Autenticación (MANDATORY PATTERN) const initAuth = async () => { try { if (typeof __initial_auth_token !== 'undefined' && __initial_auth_token) { await signInWithCustomToken(auth, __initial_auth_token); } else { // Intentamos inicio anónimo si no hay usuario, onAuthStateChanged lo manejará } } catch (error) { console.error("Auth Error:", error); setAuthError("Error de conexión"); } }; initAuth(); const unsubscribe = onAuthStateChanged(auth, async (currentUser) => { if (currentUser) { setUser(currentUser); } else { // Si no hay usuario, iniciamos anónimo por defecto para permitir que la app funcione try { await signInAnonymously(auth); } catch (anonError) { console.error("Anonymous auth error", anonError); } } }); return () => unsubscribe(); }, []); // --- ESTADOS DE LA APP --- const [step, setStep] = useState(0); const [history, setHistory] = useState([]); const [streak, setStreak] = useState(0); const [showHistory, setShowHistory] = useState(false); const [timerActive, setTimerActive] = useState(false); const [timeLeft, setTimeLeft] = useState(120); const [darkMode, setDarkMode] = useState(true); const [hydrated, setHydrated] = useState(false); // Estado para Ayuda/Ejemplos const [activeHelp, setActiveHelp] = useState(null); const [currentExamples, setCurrentExamples] = useState([]); // Estados para IA const [isLoadingAI, setIsLoadingAI] = useState(false); const [visualizationScript, setVisualizationScript] = useState(''); const [isSpeaking, setIsSpeaking] = useState(false); // Lista de Frases Motivacionales const welcomeQuotes = [ "No reacciones al mundo. Diséñalo.", "Tu mañana es el timón de tu vida.", "La disciplina pesa onzas, el arrepentimiento toneladas.", "Gana la mañana, gana el día.", "Hoy es una nueva oportunidad para ser tu mejor versión.", "Donde va tu atención, va tu energía.", "El éxito es una serie de pequeñas victorias.", "Primero creas tus hábitos, luego tus hábitos te crean a ti.", "La calidad de tu vida depende de la calidad de tus hábitos.", "Actúa hoy como la persona que quieres ser mañana.", "El dolor de la disciplina es temporal, la gloria es para siempre.", "Tu futuro se crea con lo que haces hoy, no mañana." ]; const [currentQuote, setCurrentQuote] = useState(() => welcomeQuotes[Math.floor(Math.random() * welcomeQuotes.length)]); // Formulario const [formData, setFormData] = useState({ identity: '', feeling: '', energy: 5, gratitude1: '', gratitude2: '', gratitude3: '', visualized: false, action1: '', action2: '', action3: '', affirmation: 'Hoy elijo ser imparable. Soy disciplinado y nada me detendrá.' }); // --- SINCRONIZACIÓN DE DATOS (Firestore) --- useEffect(() => { if (!user || !db || !appId) return; // Referencia al documento único del usuario que contiene todo su diario // Estructura: artifacts/{appId}/users/{userId}/data/journal const docRef = doc(db, 'artifacts', appId, 'users', user.uid, 'data', 'journal'); const unsubscribeSnapshot = onSnapshot(docRef, (docSnap) => { setIsSyncing(false); if (docSnap.exists()) { const data = docSnap.data(); if (data.history) setHistory(data.history); if (data.streak !== undefined) setStreak(data.streak); // Verificar fechas para resetear racha si es necesario if (data.lastDate) { const today = new Date().toDateString(); const yesterday = new Date(Date.now() - 86400000).toDateString(); if (data.lastDate !== today && data.lastDate !== yesterday) { // Racha perdida (solo visualmente, se actualiza al guardar) setStreak(0); } } if (data.preferences) { if (data.preferences.darkMode !== undefined) setDarkMode(data.preferences.darkMode); } } else { // Usuario nuevo en la nube } }, (error) => { console.error("Error sincronizando datos:", error); setIsSyncing(false); }); return () => unsubscribeSnapshot(); }, [user, db, appId]); // Función para guardar en Firestore const saveToCloud = async (newHistory, newStreak, lastDate) => { if (!user || !db) return; try { const docRef = doc(db, 'artifacts', appId, 'users', user.uid, 'data', 'journal'); await setDoc(docRef, { history: newHistory, streak: newStreak, lastDate: lastDate, preferences: { darkMode } }, { merge: true }); } catch (e) { console.error("Error guardando:", e); // Fallback silencioso en UI para no interrumpir flujo } }; // Guardar preferencia modo oscuro useEffect(() => { if (user && db && !isSyncing) { // Guardar solo preferencia sin tocar historial const docRef = doc(db, 'artifacts', appId, 'users', user.uid, 'data', 'journal'); setDoc(docRef, { preferences: { darkMode } }, { merge: true }); } }, [darkMode]); // --- FUNCIONES DE AUTH --- const handleGoogleLogin = async () => { if (!authObj) return; try { const provider = new GoogleAuthProvider(); await signInWithPopup(authObj, provider); // El onAuthStateChanged manejará el cambio de usuario y recargará los datos de ese usuario } catch (error) { console.error("Login Error:", error); if (error.code === 'auth/unauthorized-domain') { alert("⚠️ Dominio no autorizado: El inicio de sesión con Google no está disponible en este entorno de vista previa. Por favor, continúa usando el modo invitado (tus datos se guardan en esta sesión)."); } else if (error.code === 'auth/popup-closed-by-user') { // Usuario cerró el popup, no hacemos nada } else { alert("No se pudo iniciar sesión: " + error.message); } } }; const handleLogout = async () => { if (!authObj) return; try { await signOut(authObj); window.location.reload(); // Recargar para limpiar estado } catch (error) { console.error("Logout Error:", error); } }; // --- DATOS Y LÓGICA DE LA APP --- const baseExamplesData = { identity: [ "Un líder estoico y calmado ante el caos", "Un ejecutor implacable enfocado en resultados", "Un padre/madre presente, amoroso y paciente", "Un creador visionario y audaz", "Un guerrero pacífico que domina su ego", "Un aprendiz curioso y humilde", "Una fuerza de la naturaleza imparable", "Alguien que escucha más de lo que habla", "Un solucionador de problemas creativo", "La persona más disciplinada que conozco" ], feeling: [ "Profundamente satisfecho con mi esfuerzo", "Orgulloso de haber superado la pereza", "En paz absoluta y sin ansiedad", "Energético, vibrante y vivo", "Realizado por haber ayudado a otros", "Invencible ante los problemas pequeños", "Con una claridad mental cristalina", "Agradecido por estar vivo un día más", "Confiado en mi capacidad de resolver", "Sereno como el agua en calma" ], gratitude: [ "El sabor del primer café de esta mañana", "Haber dormido en una cama caliente", "La sonrisa de mi hijo/pareja ayer", "Tener salud para levantarme sin dolor", "La lección que aprendí de mi error ayer", "Tener agua potable y comida en la nevera", "El sol entrando por la ventana", "Un mensaje inesperado de un amigo", "La oportunidad de trabajar en lo que quiero", "Mis piernas que me permiten caminar" ], action: [ "Escribir las primeras 500 palabras del informe", "Llamar a ese cliente difícil (El Sapo)", "Hacer 45 minutos de pesas sin mirar el celular", "Revisar y pagar las facturas pendientes", "Tener esa conversación incómoda que evito", "Limpiar y organizar mi escritorio por 20 min", "Leer 10 páginas de un libro formativo", "Planificar el menú saludable de la semana", "Responder todos los emails pendientes en 1 hora", "Meditar 15 minutos sin interrupciones" ] }; // Mezclar ejemplos useEffect(() => { if (activeHelp) { const allExamples = [...baseExamplesData[activeHelp]]; for (let i = allExamples.length - 1; i > 0; i--) { const j = Math.floor(Math.random() * (i + 1)); [allExamples[i], allExamples[j]] = [allExamples[j], allExamples[i]]; } setCurrentExamples(allExamples.slice(0, 6)); } }, [activeHelp]); // API Calls const callGemini = async (prompt, type) => { setIsLoadingAI(true); try { const response = await fetch( `https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash-preview-09-2025:generateContent?key=${apiKey}`, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ contents: [{ parts: [{ text: prompt }] }] }), } ); if (!response.ok) throw new Error('Error en la llamada a Gemini'); const data = await response.json(); const generatedText = data.candidates?.[0]?.content?.parts?.[0]?.text || "No se pudo generar el contenido."; if (type === 'visualization') setVisualizationScript(generatedText); else if (type === 'affirmation') setFormData(prev => ({ ...prev, affirmation: generatedText })); else if (type === 'refine_goal') setFormData(prev => ({ ...prev, action1: generatedText.replace(/"/g, '') })); } catch (error) { console.error("Error AI:", error); alert("Error conectando con IA."); } finally { setIsLoadingAI(false); } }; const generateVisualization = () => { if (!formData.identity) return alert("Completa tu identidad primero."); const prompt = `Actúa como un coach. Usuario: "${formData.identity}", Sentimiento: "${formData.feeling}". Guion visualización corto (max 60 palabras), segunda persona, sensorial, éxito hoy. Español neutro.`; callGemini(prompt, 'visualization'); }; const generateMantra = () => { if (!formData.action1 && !formData.identity) return alert("Completa metas e identidad."); const prompt = `Mantra personal (max 2 frases). Identidad: "${formData.identity}", Meta: "${formData.action1}". Presente, afirmativo. Español neutro.`; callGemini(prompt, 'affirmation'); }; const refineGoal = () => { if (!formData.action1) return alert("Escribe tu meta principal."); const prompt = `Haz esta meta SMART y breve: "${formData.action1}". Devuelve solo la frase.`; callGemini(prompt, 'refine_goal'); } const speakText = (text) => { if ('speechSynthesis' in window) { if (isSpeaking) { window.speechSynthesis.cancel(); setIsSpeaking(false); return; } const utterance = new SpeechSynthesisUtterance(text); utterance.lang = 'es-ES'; utterance.onend = () => setIsSpeaking(false); setIsSpeaking(true); window.speechSynthesis.speak(utterance); } }; // Timer useEffect(() => { let interval = null; if (timerActive && timeLeft > 0) interval = setInterval(() => setTimeLeft((prev) => prev - 1), 1000); else if (timeLeft === 0) { setTimerActive(false); setFormData({...formData, visualized: true}); } return () => clearInterval(interval); }, [timerActive, timeLeft]); const handleChange = (e) => setFormData({ ...formData, [e.target.name]: e.target.value }); const handleSelectExample = (text) => { const newData = { ...formData }; if (activeHelp === 'identity') newData.identity = text; else if (activeHelp === 'feeling') newData.feeling = text; else if (activeHelp === 'gratitude') { if (!newData.gratitude1) newData.gratitude1 = text; else if (!newData.gratitude2) newData.gratitude2 = text; else if (!newData.gratitude3) newData.gratitude3 = text; else newData.gratitude1 = text; } else if (activeHelp === 'action') { if (!newData.action1) newData.action1 = text; else if (!newData.action2) newData.action2 = text; else if (!newData.action3) newData.action3 = text; else newData.action1 = text; } setFormData(newData); setActiveHelp(null); }; const completeRitual = () => { const today = new Date().toDateString(); const newEntry = { date: new Date().toLocaleDateString('es-ES', { weekday: 'short', day: 'numeric', month: 'short' }), fullDate: new Date().toISOString(), data: formData }; // Calcular nueva racha let newStreak = streak; if (streak === 0 || (history.length > 0 && history[0].date !== newEntry.date)) { newStreak = streak + 1; } const newHistory = [newEntry, ...history]; setHistory(newHistory); setStreak(newStreak); // GUARDAR EN NUBE saveToCloud(newHistory, newStreak, today); setStep(6); }; const resetForm = () => { setFormData({ identity: '', feeling: '', energy: 5, gratitude1: '', gratitude2: '', gratitude3: '', visualized: false, action1: '', action2: '', action3: '', affirmation: 'Hoy elijo ser imparable. Soy disciplinado y nada me detendrá.' }); setVisualizationScript(''); // Cambiar frase al reiniciar setCurrentQuote(welcomeQuotes[Math.floor(Math.random() * welcomeQuotes.length)]); setStep(0); setTimeLeft(120); setHydrated(false); }; // --- UI HELPERS --- const bgClass = darkMode ? 'bg-slate-900 text-white' : 'bg-gray-50 text-gray-800'; const cardClass = darkMode ? 'bg-slate-800 border-slate-700 text-white' : 'bg-white border-gray-100 text-gray-800'; const inputClass = darkMode ? 'bg-slate-700 border-slate-600 text-white placeholder-slate-400 focus:ring-blue-400' : 'bg-white border-gray-300 text-gray-900 focus:ring-blue-500'; const subtitleClass = darkMode ? 'text-slate-400' : 'text-gray-500'; const modalClass = darkMode ? 'bg-slate-800 text-white border-slate-700' : 'bg-white text-gray-800 border-gray-200'; const HelpModal = () => { if (!activeHelp) return null; return (
Identidad: {entry.data.identity}
Sapo: {entry.data.action1}
"{currentQuote}"
{user && (
Activación Biológica
Bebe 500ml de agua.
Define tu identidad antes de que el día la defina por ti.
{formData.energy}
3 cosas específicas de las últimas 24h.
"{visualizationScript}"
"Cómete ese sapo": Haz lo difícil primero.
Otras misiones: