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