import React, { useState, useEffect, useMemo } from 'react';
import { initializeApp } from 'firebase/app';
import {
getFirestore, collection, doc, setDoc, getDoc,
onSnapshot, addDoc, updateDoc, query, orderBy
} from 'firebase/firestore';
import {
getAuth, signInAnonymously, onAuthStateChanged, signInWithCustomToken
} from 'firebase/auth';
import {
LineChart, Line, XAxis, YAxis, CartesianGrid, Tooltip, ResponsiveContainer,
BarChart, Bar
} from 'recharts';
import {
CheckCircle, Circle, Utensils, Calendar, TrendingUp, Lock,
User, Droplets, ShoppingCart, MessageCircle, ChevronRight,
ChevronLeft, PlayCircle, ClipboardList, Sparkles, Brain, Search, Send, Info, XCircle, RefreshCw, Settings2, Wrench, RotateCcw, Edit3, ArrowLeft, Rocket, Check
} from 'lucide-react';
// --- CONFIGURACIÓN FIREBASE ---
const firebaseConfig = JSON.parse(__firebase_config);
const app = initializeApp(firebaseConfig);
const auth = getAuth(app);
const db = getFirestore(app);
const appId = typeof __app_id !== 'undefined' ? __app_id : 'programa-21d-v2';
const apiKey = "";
// --- LISTA VERDE (SEGÚN PDF) ---
const GREEN_LIST = {
proteinas: ["Res", "Cerdo", "Cordero", "Chivo", "Hígado", "Corazón", "Riñones", "Pollo", "Gallina", "Pavo", "Pato", "Salmón", "Caballa", "Arenque", "Sardinas", "Anchoas", "Mariscos", "Huevos"],
grasas: ["Ghee", "Mantequilla", "Crema de leche", "Aceite de coco", "Quesos curados", "Manteca de cerdo", "Sebo de res", "Aceitunas", "Aceite de oliva", "Aguacate", "Semillas de Chía", "Semillas de Lino", "Cáñamo"],
vegetales: ["Espinaca", "Lechuga", "Acelga", "Rúcula", "Brócoli", "Coliflor", "Repollo", "Espárragos", "Pepino", "Champiñones", "Chucrut"],
condimentos: ["Sal de mar", "Pimienta", "Ajo", "Cebolla en polvo", "Comino", "Cúrcuma", "Romero", "Tomillo", "Albahaca", "Vinagre de sidra", "Limón"]
};
const WEEKLY_DATA = [
{ week: 1, title: "Primera Semana 1/3", schedule: { breakfast: Array(7).fill("Desayuno"), lunch: Array(7).fill("Almuerzo"), dinner: ["Cena", "Caldo de Huesos", "Cena", "Cena", "Caldo de Huesos", "Cena", "Cena"] } },
{ week: 2, title: "Segunda Semana 2/3", schedule: { breakfast: Array(7).fill("Desayuno"), lunch: Array(7).fill("Almuerzo"), dinner: ["Cena", "Caldo de Huesos", "Caldo de Huesos", "Cena", "Caldo de Huesos", "Caldo de Huesos", "Caldo de Huesos"] } },
{ week: 3, title: "Tercera Semana 3/3", schedule: { breakfast: Array(7).fill("3 Opciones"), lunch: Array(7).fill("Almuerzo"), dinner: Array(7).fill("2 Opciones") } }
];
const PROGRAM_DAYS = Array.from({ length: 21 }, (_, i) => ({
day: i + 1,
title: `Día ${i + 1}: ${i < 7 ? 'Adaptación' : i < 14 ? 'Cetosis Profunda' : 'Consolidación'}`,
tasks: ["Agua con sal y limón", "15 min caminata", "Ayuno 14h"]
}));
const callGemini = async (prompt, systemInstruction = "") => {
const url = `https://generativelanguage.googleapis.com/v1beta/models/gemini-2.5-flash-preview-09-2025:generateContent?key=${apiKey}`;
const payload = {
contents: [{ parts: [{ text: prompt }] }],
systemInstruction: systemInstruction ? { parts: [{ text: systemInstruction }] } : undefined,
generationConfig: { responseMimeType: "application/json" }
};
const fetchWithRetry = async (retries = 5, delay = 1000) => {
try {
const response = await fetch(url, { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify(payload) });
const result = await response.json();
return result.candidates?.[0]?.content?.parts?.[0]?.text;
} catch (error) {
if (retries > 0) {
await new Promise(res => setTimeout(res, delay));
return fetchWithRetry(retries - 1, delay * 2);
}
throw error;
}
};
return await fetchWithRetry();
};
export default function App() {
const [user, setUser] = useState(null);
const [activeTab, setActiveTab] = useState('dashboard');
const [profile, setProfile] = useState({ startDate: null, demoDay: 0 });
const [progress, setProgress] = useState({});
const [measurements, setMeasurements] = useState([]);
const [personalizedPlan, setPersonalizedPlan] = useState(null); // Ahora será { week1: {...}, week2: {...}, week3: {...} }
const [shoppingLists, setShoppingLists] = useState({}); // { week1: {...}, week2: {...} }
const [mealHistory, setMealHistory] = useState({});
const [excludedFoods, setExcludedFoods] = useState([]);
const [loading, setLoading] = useState(true);
const currentDay = useMemo(() => {
if (profile.demoDay > 0) return profile.demoDay;
if (!profile.startDate) return 0;
const start = new Date(profile.startDate);
const today = new Date();
const diffTime = Math.abs(today - start);
const diffDays = Math.ceil(diffTime / (1000 * 60 * 60 * 24));
return diffDays > 21 ? 21 : diffDays;
}, [profile]);
useEffect(() => {
const initAuth = async () => {
if (typeof __initial_auth_token !== 'undefined' && __initial_auth_token) {
await signInWithCustomToken(auth, __initial_auth_token);
} else { await signInAnonymously(auth); }
};
initAuth();
const unsubscribe = onAuthStateChanged(auth, (u) => {
setUser(u);
if (u) setLoading(false);
});
return () => unsubscribe();
}, []);
useEffect(() => {
if (!user) return;
const profileRef = doc(db, 'artifacts', appId, 'users', user.uid, 'data', 'profile');
const unsubProfile = onSnapshot(profileRef, (snap) => {
if (snap.exists()) setProfile(snap.data());
});
const planRef = doc(db, 'artifacts', appId, 'users', user.uid, 'data', 'plan');
const unsubPlan = onSnapshot(planRef, (snap) => {
if (snap.exists()) {
const data = snap.data();
setPersonalizedPlan(data.meals || null);
setExcludedFoods(data.excluded || []);
setMealHistory(data.history || {});
setShoppingLists(data.shoppingLists || {});
}
});
const progressRef = doc(db, 'artifacts', appId, 'users', user.uid, 'data', 'progress');
const unsubProgress = onSnapshot(progressRef, (snap) => {
if (snap.exists()) setProgress(snap.data());
});
const measurementsRef = collection(db, 'artifacts', appId, 'users', user.uid, 'measurements');
const unsubMeasurements = onSnapshot(measurementsRef, (snap) => {
const data = snap.docs.map(d => ({ id: d.id, ...d.data() }));
setMeasurements(data.sort((a, b) => new Date(a.date) - new Date(b.date)));
});
return () => { unsubProfile(); unsubPlan(); unsubProgress(); unsubMeasurements(); };
}, [user]);
const startProgram = async () => {
if (!user) return;
const profileRef = doc(db, 'artifacts', appId, 'users', user.uid, 'data', 'profile');
await setDoc(profileRef, { startDate: new Date().toISOString(), demoDay: 0 });
};
const setDemoDay = async (day) => {
const profileRef = doc(db, 'artifacts', appId, 'users', user.uid, 'data', 'profile');
await setDoc(profileRef, { ...profile, demoDay: day }, { merge: true });
};
const toggleDailyTask = async (day, taskIndex) => {
if (!user) return;
const currentDayData = progress[day] || { completedTasks: [] };
let newTasks = [...currentDayData.completedTasks];
if (newTasks.includes(taskIndex)) newTasks = newTasks.filter(i => i !== taskIndex);
else newTasks.push(taskIndex);
const progressRef = doc(db, 'artifacts', appId, 'users', user.uid, 'data', 'progress');
await setDoc(progressRef, { ...progress, [day]: { ...currentDayData, completedTasks: newTasks } }, { merge: true });
};
const savePlanData = async (meals, excluded, history = mealHistory, sLists = shoppingLists) => {
if (!user) return;
const planRef = doc(db, 'artifacts', appId, 'users', user.uid, 'data', 'plan');
await setDoc(planRef, { meals, excluded, history, shoppingLists: sLists, updatedAt: new Date().toISOString() });
};
if (loading) return
Estandarizando Glucosa...
;
return (
{/* Sidebar */}
{currentDay === 0 ? (
Transforma tu
metabolismo hoy
Inicia tu conteo de 21 días y accede a tu plan inteligente basado en la zona verde.
) : (
<>
{activeTab === 'dashboard' && "Panel de Control"}
{activeTab === 'strategy' && "Estrategia de Ayunos"}
{activeTab === 'my-plan' && "Plan Personalizado"}
{activeTab === 'journal' && "Coach Mental IA"}
{activeTab === 'stats' && "Resultados"}
{activeTab === 'dashboard' && }
{activeTab === 'strategy' && }
{activeTab === 'my-plan' && }
{activeTab === 'journal' && }
{activeTab === 'stats' && {
e.preventDefault();
const weight = e.target.weight.value;
const waist = e.target.waist.value;
addDoc(collection(db, 'artifacts', appId, 'users', user.uid, 'measurements'), {
date: new Date().toISOString().split('T')[0], weight: parseFloat(weight), waist: parseFloat(waist)
});
e.target.reset();
}} />}
>
)}
);
}
function NavItem({ icon, label, active, onClick }) {
return (
);
}
// --- PLAN PERSONALIZADO ---
function PersonalizedPlanView({ plan, excluded, history, shoppingLists, onSave, currentDay }) {
const currentWeekNum = Math.ceil(currentDay / 7);
const [selectedWeek, setSelectedWeek] = useState(currentWeekNum > 3 ? 3 : currentWeekNum);
const [step, setStep] = useState(plan?.[`week${selectedWeek}`] ? 'view' : 'setup');
const [tempExcluded, setTempExcluded] = useState(excluded);
const [isGenerating, setIsGenerating] = useState(false);
const [isSyncingList, setIsSyncingList] = useState(false);
const [activeCellMenu, setActiveCellMenu] = useState(null);
const [manualEdit, setManualEdit] = useState('');
const weekKey = `week${selectedWeek}`;
const currentPlan = plan?.[weekKey] || null;
const currentSList = shoppingLists?.[weekKey] || null;
// Actualizar vista al cambiar de semana
useEffect(() => {
if (plan?.[weekKey]) setStep('view');
else setStep('setup');
}, [selectedWeek, plan]);
const generateFullPlan = async () => {
setIsGenerating(true);
try {
const systemPrompt = `Chef experto. Usa SOLO Zona Verde: ${JSON.stringify(GREEN_LIST)}. EXCLUYE: ${tempExcluded.join(', ')}. Genera platos coherentes para la semana ${selectedWeek}. JSON: { "meals": { "D1": { "breakfast": "Plato", "lunch": "Plato", "dinner": "Plato" }, ... } }. Responde SOLO JSON.`;
const res = await callGemini(`Plan para semana ${selectedWeek}.`, systemPrompt);
const cleaned = res.replace(/```json|```/g, '').trim();
const data = JSON.parse(cleaned);
const newHistory = { ...history };
Object.keys(data.meals).forEach(day => {
['breakfast', 'lunch', 'dinner'].forEach(type => {
newHistory[`${weekKey}-${day}-${type}`] = [data.meals[day][type]];
});
});
const updatedMeals = { ...plan, [weekKey]: data.meals };
await onSave(updatedMeals, tempExcluded, newHistory, shoppingLists);
} catch (e) { console.error(e); }
setIsGenerating(false);
};
const syncShoppingList = async () => {
if (!currentPlan) return;
setIsSyncingList(true);
try {
const mealsStr = Object.keys(currentPlan).map(d => `${d}: ${currentPlan[d].breakfast}, ${currentPlan[d].lunch}, ${currentPlan[d].dinner}`).join(' | ');
const res = await callGemini(`Lista compras categorizada semana ${selectedWeek}: ${mealsStr}. JSON: { "categories": { "Proteínas": [], "Vegetales": [], "Grasas": [], "Otros": [] } }. SOLO JSON.`, "Logística.");
const cleaned = res.replace(/```json|```/g, '').trim();
const data = JSON.parse(cleaned);
await onSave(plan, excluded, history, { ...shoppingLists, [weekKey]: data.categories });
} catch (e) { console.error(e); }
setIsSyncingList(false);
};
const regenerateMeal = async (day, type) => {
setActiveCellMenu(null);
setIsGenerating(true);
try {
const res = await callGemini(`Sustituye "${currentPlan[day][type]}". Zona Verde. JSON: { "newMeal": "" }.`, "Chef Keto.");
const data = JSON.parse(res.replace(/```json|```/g, '').trim());
const key = `${weekKey}-${day}-${type}`;
const newHistory = { ...history, [key]: [...(history[key] || []), data.newMeal] };
const updatedWeek = { ...currentPlan, [day]: { ...currentPlan[day], [type]: data.newMeal } };
await onSave({ ...plan, [weekKey]: updatedWeek }, excluded, newHistory, shoppingLists);
} catch (e) { console.error(e); }
setIsGenerating(false);
};
if (step === 'setup') {
return (
{[1, 2, 3].map(w => {
const isLocked = w > currentWeekNum;
return ;
})}
Semana {selectedWeek}
Configura tus gustos para esta etapa
setTempExcluded(p => p.includes(i) ? p.filter(x => x !== i) : [...p, i])} color="indigo" />
setTempExcluded(p => p.includes(i) ? p.filter(x => x !== i) : [...p, i])} color="amber" />
);
}
return (
{[1, 2, 3].map(w => {
const isLocked = w > currentWeekNum;
const isCompleted = w < currentWeekNum;
return (
);
})}
{selectedWeek < currentWeekNum ? (
Semana Completada
) : selectedWeek === currentWeekNum ? (
Semana en Curso
) : (
Próxima Etapa
)}
| MOMENTO |
{Object.keys(currentPlan).map(day => {day.replace('D', 'Día ')} | )}
{['breakfast', 'lunch', 'dinner'].map((type, rowIndex) => (
|
{type === 'breakfast' ? 'MAÑANA' : type === 'lunch' ? 'TARDE' : 'NOCHE'}
|
{Object.keys(currentPlan).map(day => {
const key = `${weekKey}-${day}-${type}`;
const isMenuOpen = activeCellMenu === key;
const canGoBack = history[key]?.length > 1;
return (
{currentPlan[day][type]}
{isMenuOpen && (
{canGoBack && }
setManualEdit(e.target.value)} onKeyDown={(e) => {
if (e.key === 'Enter' && manualEdit) {
onSave({ ...plan, [weekKey]: { ...currentPlan, [day]: { ...currentPlan[day], [type]: manualEdit } } }, excluded, { ...history, [key]: [...(history[key] || []), manualEdit] }, shoppingLists);
setManualEdit(''); setActiveCellMenu(null);
}
}} />
)}
|
);
})}
))}
{/* CARRITO SEMANAL */}
Mi Carrito Semana {selectedWeek}
Sincronizado con tus platos actuales
{currentSList ?
{Object.keys(currentSList).map(cat =>
{cat}
{currentSList[cat].map((item, i) =>
)}
)}
:
Presiona sincronizar para generar tu lista de la semana {selectedWeek}.
}
Mi Carrito 21D
Enviar directamente a tu WhatsApp
);
}
function IngredientSection({ title, list, tempExcluded, toggleExclude, color }) {
const colorMap = { indigo: "border-indigo-100 bg-indigo-50/20 text-indigo-950 hover:border-indigo-300 shadow-indigo-50/50", amber: "border-amber-100 bg-amber-50/20 text-amber-950 hover:border-amber-300 shadow-amber-50/50", green: "border-green-100 bg-green-50/20 text-green-950 hover:border-green-300 shadow-green-50/50" };
return (
{title}
{list.map(item => {
const isEx = tempExcluded.includes(item);
return ;
})}
);
}
function DashboardView({ progress, toggleTask, currentDay }) {
const dayData = PROGRAM_DAYS[currentDay - 1] || PROGRAM_DAYS[0];
return (
Tareas Día #{currentDay}
{dayData.tasks.map((t, i) => {
const isComp = progress[currentDay]?.completedTasks?.includes(i);
return ;
})}
MOTOR METABÓLICO ACTIVADO
ADAPTACIÓN
CELULAR
{21 - currentDay}Días Restantes
);
}
function StrategyView({ currentMaxDay }) {
const currentWeek = Math.ceil(currentMaxDay / 7);
const [selectedWeek, setSelectedWeek] = useState(currentWeek > 3 ? 3 : currentWeek);
const daysLabels = ["D1 - L", "D2 - M", "D3 - M", "D4 - J", "D5 - V", "D6 - S", "D7 - D"];
return (
{[1, 2, 3].map(w => {
const isLocked = w > currentWeek;
return ;
})}
MATRIZ ESTRATÉGICA
{WEEKLY_DATA[selectedWeek-1].title}
| PERIODO SOLAR | {daysLabels.map((l, i) => {l} | )}
| Mañana7 AM | {WEEKLY_DATA[selectedWeek-1].schedule.breakfast.map((item, idx) => {item} | )}
| Tarde12 - 3 PM | {WEEKLY_DATA[selectedWeek-1].schedule.lunch.map((item, idx) => {item} | )}
| Noche6 PM | {WEEKLY_DATA[selectedWeek-1].schedule.dinner.map((item, idx) => {item} | )}
);
}
function JournalView() { return SOPORTE COGNITIVO
"Tu cuerpo no pide comida, pide nutrientes. Aprende a distinguir el hambre real de la emocional."
; }
function StatsView({ measurements, onAdd }) { return ; }