import React, { useState, useEffect, useCallback, useRef } from 'react'; import { CheckCircle, Sun, Moon, Clock, Trophy, Brain, Zap, Droplet, Activity, Flame, ChevronDown, ChevronUp, AlertTriangle, Smartphone, X, Timer } from 'lucide-react'; // --- FIREBASE CONFIGURATION --- import { initializeApp } from 'firebase/app'; import { getFirestore, doc, setDoc, onSnapshot } from 'firebase/firestore'; import { getAuth, signInAnonymously, onAuthStateChanged } from 'firebase/auth'; const firebaseConfig = { apiKey: "AIzaSyBsN6uG3opYhRb2Hvs4vrNYi_7lS6SinQo", authDomain: "servi2017-88ec4.firebaseapp.com", databaseURL: "https://servi2017-88ec4.firebaseio.com", projectId: "servi2017-88ec4", storageBucket: "servi2017-88ec4.firebasestorage.app", messagingSenderId: "240673477454", appId: "1:240673477454:web:166d372ac74a3c5e74841f" }; // Inicialización Segura (Evita re-inicializaciones en entornos de desarrollo cálidos) const app = initializeApp(firebaseConfig); const auth = getAuth(app); const db = getFirestore(app); // --- DICCIONARIO DE ÍCONOS --- const ICON_MAP = { Zap: Zap, Sun: Sun, Droplet: Droplet, Activity: Activity, Brain: Brain, Moon: Moon }; // --- COMPONENTES UX/UI AVANZADOS --- // Tensión Magnética (Micro-Interacción Orgánica) const MagneticWrapper = ({ children, strength = 0.15, className = "" }) => { const ref = useRef(null); const [position, setPosition] = useState({ x: 0, y: 0 }); const handlePointerMove = (e) => { if (!ref.current) return; const { clientX, clientY } = e; const { height, width, left, top } = ref.current.getBoundingClientRect(); const centerX = left + width / 2; const centerY = top + height / 2; setPosition({ x: (clientX - centerX) * strength, y: (clientY - centerY) * strength }); }; const handlePointerLeave = () => setPosition({ x: 0, y: 0 }); return (
{children}
); }; // Sistema de Partículas Vectoriales const ParticleSystem = () => { const particles = Array.from({ length: 25 }); return (
{particles.map((_, i) => { const size = Math.random() * 4 + 2; const left = Math.random() * 100; const duration = Math.random() * 5 + 4; const delay = Math.random() * 3; return (
); })}
); }; export default function App() { // ESTADO CORE const [supplements, setSupplements] = useState([ { id: 'creat', name: 'Creatina', period: 'morning', done: false, timeTaken: null, iconKey: 'Zap' }, { id: 'vitD', name: 'Vitamina D3 + K2', period: 'morning', done: false, timeTaken: null, iconKey: 'Sun' }, { id: 'omega', name: 'Omega 3', period: 'morning', done: false, timeTaken: null, iconKey: 'Droplet' }, { id: 'curflex', name: 'Curflex Dúo', period: 'morning', done: false, timeTaken: null, iconKey: 'Activity' }, { id: 'activa', name: 'Activamente', period: 'morning', done: false, timeTaken: null, iconKey: 'Brain' }, { id: 'bici', name: 'Biciclamato de sodio', period: 'night', done: false, timeTaken: null, iconKey: 'Moon' } ]); const [currentDayStr, setCurrentDayStr] = useState(''); const [now, setNow] = useState(new Date()); // ESTADOS DE RED Y AUTENTICACIÓN (FIREBASE) const [user, setUser] = useState(null); const [isOnline, setIsOnline] = useState(typeof navigator !== 'undefined' ? navigator.onLine : true); const [isSyncing, setIsSyncing] = useState(false); // ESTADOS UI & MODALES const [auditTrail, setAuditTrail] = useState([]); const [showAudit, setShowAudit] = useState(false); const [morningListVisible, setMorningListVisible] = useState(true); const [nightListVisible, setNightListVisible] = useState(true); const [showIosGuide, setShowIosGuide] = useState(false); const [floatingMessage, setFloatingMessage] = useState({ text: '', visible: false, icon: null }); // ESTADOS DE CONTROL DE FLUJO Y SEGURIDAD const clickHistoryRef = useRef([]); const [showSpeedWarning, setShowSpeedWarning] = useState(false); const [lastActionSnapshot, setLastActionSnapshot] = useState(null); const SPEED_LIMIT_CLICKS = 3; const SPEED_LIMIT_WINDOW_MS = 1500; const [islandExpanded, setIslandExpanded] = useState(false); const holdTimeoutRef = useRef(null); const timeoutRefs = useRef([]); // --- 1. FIREBASE & RED SETUP --- useEffect(() => { // Monitor de red const handleOnline = () => setIsOnline(true); const handleOffline = () => setIsOnline(false); window.addEventListener('online', handleOnline); window.addEventListener('offline', handleOffline); // Auth Silenciosa signInAnonymously(auth).catch(err => console.error("Error Auth:", err)); const unsubAuth = onAuthStateChanged(auth, (u) => setUser(u)); return () => { window.removeEventListener('online', handleOnline); window.removeEventListener('offline', handleOffline); unsubAuth(); }; }, []); // Escucha de Base de Datos en Tiempo Real useEffect(() => { if (!user) return; setIsSyncing(true); // Ruta en Firestore: users/{uid}/routine/today const docRef = doc(db, 'users', user.uid, 'routine', 'today'); const unsubSnapshot = onSnapshot(docRef, (docSnap) => { if (docSnap.exists()) { const data = docSnap.data(); if (data.supplements) setSupplements(data.supplements); if (data.currentDayStr) setCurrentDayStr(data.currentDayStr); } setIsSyncing(false); }, (error) => { console.error("Error Sync:", error); setIsSyncing(false); }); return () => unsubSnapshot(); }, [user]); // Función de escritura a la nube const syncToFirebase = async (newSupplements, newDayStr) => { if (!user) return; try { await setDoc(doc(db, 'users', user.uid, 'routine', 'today'), { supplements: newSupplements, currentDayStr: newDayStr, lastUpdated: new Date().toISOString() }, { merge: true }); } catch (e) { console.error("Error Guardando:", e); } }; // --- 2. MOTORES INTERNOS --- useEffect(() => { const timer = setInterval(() => setNow(new Date()), 60000); return () => clearInterval(timer); }, []); // Reset inteligente 4:00 AM adaptado a Firebase useEffect(() => { const logicalDate = new Date(now.getTime() - 4 * 60 * 60 * 1000); const dateString = logicalDate.toISOString().split('T')[0]; if (currentDayStr !== '' && currentDayStr !== dateString) { const resetSups = supplements.map(sup => ({ ...sup, done: false, timeTaken: null })); setSupplements(resetSups); setMorningListVisible(true); setNightListVisible(true); setIslandExpanded(false); setAuditTrail(prev => [{ time: new Date().toISOString(), action: 'SYSTEM_RESET', supId: 'ALL' }, ...prev]); setCurrentDayStr(dateString); // Empujar el reset a la base de datos para todos los dispositivos syncToFirebase(resetSups, dateString); } }, [now, currentDayStr, supplements, user]); const playSound = useCallback((type) => { if (typeof window === 'undefined') return; try { const AudioContextClass = window.AudioContext || window.webkitAudioContext; if (!AudioContextClass) return; const ctx = new AudioContextClass(); const osc = ctx.createOscillator(); const gain = ctx.createGain(); osc.connect(gain); gain.connect(ctx.destination); if (type === 'pop') { osc.type = 'sine'; osc.frequency.setValueAtTime(400, ctx.currentTime); osc.frequency.exponentialRampToValueAtTime(800, ctx.currentTime + 0.1); gain.gain.setValueAtTime(0.3, ctx.currentTime); gain.gain.exponentialRampToValueAtTime(0.01, ctx.currentTime + 0.1); osc.start(ctx.currentTime); osc.stop(ctx.currentTime + 0.1); } else if (type === 'success') { osc.type = 'triangle'; osc.frequency.setValueAtTime(440, ctx.currentTime); osc.frequency.setValueAtTime(554.37, ctx.currentTime + 0.1); osc.frequency.setValueAtTime(659.25, ctx.currentTime + 0.2); gain.gain.setValueAtTime(0.2, ctx.currentTime); gain.gain.linearRampToValueAtTime(0, ctx.currentTime + 0.5); osc.start(ctx.currentTime); osc.stop(ctx.currentTime + 0.5); } else if (type === 'error') { osc.type = 'sawtooth'; osc.frequency.setValueAtTime(150, ctx.currentTime); osc.frequency.exponentialRampToValueAtTime(50, ctx.currentTime + 0.3); gain.gain.setValueAtTime(0.4, ctx.currentTime); gain.gain.linearRampToValueAtTime(0, ctx.currentTime + 0.3); osc.start(ctx.currentTime); osc.stop(ctx.currentTime + 0.3); } } catch (e) {} }, []); const triggerHaptic = useCallback((type) => { if (typeof navigator === 'undefined' || !navigator.vibrate) return; try { if (type === 'pop') navigator.vibrate(40); if (type === 'undo') navigator.vibrate(20); if (type === 'success') navigator.vibrate([100, 50, 100, 50, 200]); if (type === 'error') navigator.vibrate([200, 100, 200]); if (type === 'island') navigator.vibrate(10); } catch (e) {} }, []); const checkSpeedLimit = (currentTime) => { const recentClicks = clickHistoryRef.current.filter(time => currentTime - time < SPEED_LIMIT_WINDOW_MS); const newHistory = [...recentClicks, currentTime]; clickHistoryRef.current = newHistory; return newHistory.length >= SPEED_LIMIT_CLICKS; }; // --- LÓGICA PRINCIPAL DE MARCAJE --- const toggleSupplement = (id) => { const timeStampStr = new Date().toISOString(); const currentTimeMs = Date.now(); setLastActionSnapshot(supplements); const isSpeeding = checkSpeedLimit(currentTimeMs); if (isSpeeding) { triggerHaptic('error'); playSound('error'); setShowSpeedWarning(true); setAuditTrail(at => [{ time: timeStampStr, action: 'SPEED_LIMIT_TRIGGERED', supId: 'ALERTA' }, ...at].slice(0, 50)); return; // Bloquea la escritura si va muy rápido } setSupplements(prev => { const newState = prev.map(sup => { if (sup.id === id) { const isNowDone = !sup.done; const logId = setTimeout(() => { setAuditTrail(at => [{ time: timeStampStr, action: isNowDone ? 'MARCADO' : 'DESMARCADO', supId: sup.name }, ...at].slice(0, 50)); }, 0); timeoutRefs.current.push(logId); if (isNowDone) { playSound('pop'); triggerHaptic('pop'); // REFUERZO COGNITIVO: Validación Base if (id === 'creat' || id === 'activa') { setFloatingMessage({ text: 'Neuro-protección activa', visible: true, icon: id === 'creat' ? Zap : Brain }); const toastId = setTimeout(() => setFloatingMessage(prev => ({ ...prev, visible: false })), 3000); timeoutRefs.current.push(toastId); } } else { triggerHaptic('undo'); if (id === 'creat' || id === 'activa') setFloatingMessage(prev => ({ ...prev, visible: false })); } return { ...sup, done: isNowDone, timeTaken: isNowDone ? timeStampStr : null }; } return sup; }); // Firebase Sync Inmediato syncToFirebase(newState, currentDayStr); // Verificación de Bloques Completos const currentSup = prev.find(s => s.id === id); if (!currentSup.done) { const periodSups = newState.filter(s => s.period === currentSup.period); if (periodSups.every(s => s.done)) { const toId = setTimeout(() => { playSound('success'); triggerHaptic('success'); if (currentSup.period === 'morning') setMorningListVisible(false); if (currentSup.period === 'night') setNightListVisible(false); }, 300); timeoutRefs.current.push(toId); } } return newState; }); }; const undoSpeedLimitAction = () => { if (lastActionSnapshot) { setSupplements(lastActionSnapshot); syncToFirebase(lastActionSnapshot, currentDayStr); // Restaura en Firebase setAuditTrail(at => [{ time: new Date().toISOString(), action: 'DESHACER_RAPIDO', supId: 'SISTEMA' }, ...at].slice(0, 50)); } setShowSpeedWarning(false); clickHistoryRef.current = []; triggerHaptic('undo'); }; useEffect(() => { return () => timeoutRefs.current.forEach(clearTimeout); }, []); const getRelativeTime = (isoString) => { if (!isoString) return ''; const diffMs = now - new Date(isoString); const diffMins = Math.floor(diffMs / 60000); if (diffMins < 1) return 'hace segs'; if (diffMins < 60) return `hace ${diffMins}m`; const diffHours = Math.floor(diffMins / 60); const remainingMins = diffMins % 60; return remainingMins > 0 ? `hace ${diffHours}h ${remainingMins}m` : `hace ${diffHours}h`; }; // CÁLCULOS DINÁMICOS const morningSups = supplements.filter(s => s.period === 'morning'); const nightSups = supplements.filter(s => s.period === 'night'); const sortedMorning = [...morningSups].sort((a, b) => (a.done === b.done ? 0 : a.done ? 1 : -1)); const sortedNight = [...nightSups].sort((a, b) => (a.done === b.done ? 0 : a.done ? 1 : -1)); const morningDoneCount = morningSups.filter(s => s.done).length; const nightDoneCount = nightSups.filter(s => s.done).length; const allDone = supplements.every(s => s.done); const currentDayIndex = now.getDay() === 0 ? 6 : now.getDay() - 1; const dynamicWeekHistory = [true, true, true, true, true, true, false]; dynamicWeekHistory[currentDayIndex] = allDone; const isPerfectWeek = dynamicWeekHistory.every(Boolean); const isMorningAlertActive = now.getHours() >= 11 && morningDoneCount < morningSups.length; const handleIslandTouchStart = () => { triggerHaptic('island'); holdTimeoutRef.current = setTimeout(() => { setIslandExpanded(true); triggerHaptic('success'); }, 400); }; const handleIslandTouchEnd = () => { if (holdTimeoutRef.current) clearTimeout(holdTimeoutRef.current); }; const toggleIsland = () => setIslandExpanded(!islandExpanded); const pendingSups = supplements.filter(s => !s.done); let islandBaseStyle = "pointer-events-auto bg-black text-white transition-all duration-[500ms] ease-[cubic-bezier(0.23,1,0.32,1)] overflow-hidden cursor-pointer relative z-10 "; if (islandExpanded) islandBaseStyle += "w-[90%] max-w-sm rounded-[2.5rem] p-6 border "; else islandBaseStyle += "w-24 h-8 rounded-full flex items-center justify-center border "; if (isPerfectWeek) islandBaseStyle += "border-amber-400/60 shadow-[0_0_20px_rgba(251,191,36,0.3)] "; else if (isMorningAlertActive && !islandExpanded) islandBaseStyle += "animate-border-pulse "; else islandBaseStyle += "border-slate-800 shadow-xl "; // --- UI LIQUID GLASS (REDISEÑO COMPLETO DE TARJETAS) --- const SupplementCard = ({ sup }) => { const isMorning = sup.period === 'morning'; const IconComponent = ICON_MAP[sup.iconKey]; const timeObj = sup.timeTaken ? new Date(sup.timeTaken) : null; return ( ); }; return (
{/* CAPAS DE TEXTURA: Noise y Mesh Gradients */}
{isPerfectWeek && } {/* FILTRO GOOEY (Liquid) PARA LA ISLA DINÁMICA */} {/* TOAST FLOTANTE: Neuro-protección */} {floatingMessage.visible && (
{floatingMessage.icon && } {floatingMessage.text}
)} {/* ISLA DINÁMICA */}
e.preventDefault()} className={islandBaseStyle} > {!islandExpanded && (
{morningDoneCount}/{morningSups.length}
)} {islandExpanded && (
Pendiente Hoy
{pendingSups.length === 0 ? (
¡Sistema Optimizado! Uptime del 100%
) : (
{pendingSups.slice(0, 3).map(sup => { const SupIcon = ICON_MAP[sup.iconKey]; return (
{sup.name}
); })} {pendingSups.length > 3 && (
+{pendingSups.length - 3} más
)}
)}
)}
{/* MODAL SPEED LIMIT */} {showSpeedWarning && (

Input Bloqueado

[ERR_RATE_LIMIT]: Múltiples solicitudes en <1.5s interceptadas para prevenir mutaciones accidentales del estado.

)} {/* MODAL IOS WIDGET */} {showIosGuide && (

Widget API (iOS 17+)

Usa la app Atajos del iPhone para interactuar con Firebase directamente desde tu pantalla de inicio, sin abrir Safari.

  1. Abrir app Atajos.
  2. Acción: "Obtener contenido de URL".
  3. Pegar Webhook: https://us-central1-tu-proyecto.cloudfunctions.net/markItem?id=creat&uid={user?.uid || 'XXXX'}
  4. Método: POST
  5. Agregar Widget de Atajo pequeño a la pantalla.
)} {/* HEADER */}

Init, Nicolás {isPerfectWeek && }

{/* INDICADOR DE RED Y SYNC */}

{new Date().toLocaleDateString('es-AR', { weekday: 'short', day: '2-digit', month: 'short' })} • {!isOnline ? 'OFFLINE_MODE' : isSyncing ? 'SYNCING...' : 'SYNC_OK'}

{['L','M','M','J','V','S','D'].map((day, i) => { const isToday = i === currentDayIndex; const isDone = dynamicWeekHistory[i]; let dotClass = "w-2.5 h-2.5 rounded-full transition-all duration-500 "; if (isToday) { dotClass += isDone ? "bg-cyan-400 shadow-[0_0_12px_rgba(34,211,238,0.8)] scale-110" : "bg-cyan-500 animate-pulse-fast shadow-[0_0_8px_rgba(6,182,212,0.6)]"; } else { dotClass += isDone ? "bg-emerald-500 shadow-[0_0_8px_rgba(16,185,129,0.3)]" : "bg-slate-700/50 border border-slate-600"; } return (
{isToday &&
} {day}
); })}
{/* HUMANIZACIÓN TEXTO */} Ritmo Vital

Memoria Diaria

[{supplements.filter(s => s.done).length} / {supplements.length}]

s.done).length / supplements.length) * 100}, 100`} strokeLinecap="round" stroke="currentColor" fill="none" d="M18 2.0845 a 15.9155 15.9155 0 0 1 0 31.831 a 15.9155 15.9155 0 0 1 0 -31.831" />
{/* LISTA PRINCIPAL (TARJETAS LIQUID GLASS) */}

Secuencia AM

{morningDoneCount}/{morningSups.length}
{!morningListVisible && morningDoneCount === morningSups.length ? (

// Inicialización OK.

Hardware optimizado. Return 0;

) : (
{sortedMorning.map(sup => )}
)}

Secuencia PM

{nightDoneCount}/{nightSups.length}
{!nightListVisible && nightDoneCount === nightSups.length ? (

Modo Suspensión.

Ciclo de recuperación de RAM activado.

) : (
{sortedNight.map(sup => )}
)}
{/* AUDITORÍA Y TERMINAL (DISEÑO INTEGRADO) */}
{showAudit && (
{auditTrail.length === 0 ? (

root@nicolas-sys:~$ tail -f /var/log/health.log

) : ( auditTrail.map((log, index) => (
{new Date(log.time).toLocaleTimeString([], {hour: '2-digit', minute:'2-digit', second:'2-digit'})} [{log.action}] {log.supId}
)) )}
)}