app.py (Estructura Completa de la Aplicación)
import streamlit as st
from streamlit_option_menu import option_menu
from streamlit_mic_recorder import mic_recorder
import datetime
import json
import os
import calendar
import io
import requests
import base64
# Configuration and Page Layout
st.set_page_config(page_title="FitIA Pro", page_icon="🔥", layout="centered")
# API KEY
API_KEY = "AIzaSyAhKVMLnpLq81Buca8B_cLojoHJr-B_vZc"
FILE_USER = "perfil_usuario.json"
FILE_DATA = "diario_datos.json"
FILE_STREAK = "datos_racha.json"
FILE_FOTO = "perfil_foto.png"
FILE_PESO = "historial_peso.json"
def cargar_json(archivo, defecto):
if os.path.exists(archivo):
try:
with open(archivo, "r", encoding="utf-8") as f: return json.load(f)
except Exception: return defecto
return defecto
def guardar_json(archivo, datos):
try:
with open(archivo, "w", encoding="utf-8") as f: json.dump(datos, f, ensure_ascii=False, indent=4)
except Exception as e: print(f"Error guardando: {e}")
def recalcular_totales_maestro(diario):
diario["cal_hoy"] = 0
diario["prot_hoy"] = 0
diario["carb_hoy"] = 0
diario["gras_hoy"] = 0
for comida in diario["comidas"]:
for ing in comida["ingredientes"]:
diario["cal_hoy"] += ing.get("kcal", 0)
diario["prot_hoy"] += ing.get("prot", 0)
diario["carb_hoy"] += ing.get("carb", 0)
diario["gras_hoy"] += ing.get("gras", 0)
return diario
def obtener_foto_b64():
if os.path.exists(FILE_FOTO):
with open(FILE_FOTO, "rb") as f: return base64.b64encode(f.read()).decode("utf-8")
return None
def calcular_metas(peso, estatura, edad, objetivo, actividad):
tmb = (10 * peso) + (6.25 * estatura) - (5 * edad) + 5
f_act = {"Sedentario": 1.2, "Ligero": 1.375, "Moderado": 1.55, "Intenso": 1.725}
mantenimiento = tmb * f_act.get(actividad, 1.2)
if "Grasa" in objetivo: cal_meta = mantenimiento - 500
elif "Músculo" in objetivo: cal_meta = mantenimiento + 300
else: cal_meta = mantenimiento
cal_meta = int(cal_meta)
prot_meta = int(peso * 2.0)
gras_meta = int((cal_meta * 0.25) / 9)
carb_meta = int((cal_meta - (prot_meta * 4) - (gras_meta * 9)) / 4)
return cal_meta, round(peso * 0.035, 1), prot_meta, carb_meta, gras_meta
def procesar_con_ia(tipo_input, contenido, perfil):
prompt = f"""
Actúa como un nutricionista experto. Objetivo: {perfil['objetivo']}.
Analiza este {tipo_input}. Devuelve este JSON EXACTO (calcula gramos, kcal, proteínas, carbohidratos y grasas por cada ingrediente):
{{
"es_comida": true,
"nota_salud": 8,
"ingredientes": [{{"nombre": "Pollo", "gr": 150, "kcal": 200, "prot": 30, "carb": 0, "gras": 5}}],
"total_kcal": 200, "total_prot": 30, "total_carb": 0, "total_gras": 5
}}
"""
b64_data = base64.b64encode(contenido).decode('utf-8')
mime_type = "audio/wav" if tipo_input == "audio" else "image/jpeg"
payload = {"contents": [{"parts": [{"text": prompt}, {"inlineData": {"mimeType": mime_type, "data": b64_data}}]}]}
headers = {"Content-Type": "application/json"}
# Fallback Matrix Array Setup
modelos = ["gemini-2.0-flash", "gemini-1.5-flash", "gemini-1.5-pro"]
for modelo in modelos:
url = f"https://generativelanguage.googleapis.com/v1beta/models/{modelo}:generateContent?key={API_KEY}"
try:
response = requests.post(url, json=payload, headers=headers)
if response.status_code == 200:
res_text = response.json()['candidates'][0]['content']['parts'][0]['text']
res_text = res_text.replace("```json", "").replace("```", "").strip()
return json.loads(res_text)
except: continue
return None
def obtener_consejo_ia(perfil, diario):
prompt = f"Eres un Coach. {perfil['nombre']} quiere {perfil['objetivo']}. Hoy lleva {diario['cal_hoy']} de {perfil['cal_meta']} kcal. Da un consejo corto con emoji."
payload = {"contents": [{"parts": [{"text": prompt}]}]}
url = f"https://generativelanguage.googleapis.com/v1beta/models/gemini-1.5-flash:generateContent?key={API_KEY}"
try:
response = requests.post(url, json=payload, headers={"Content-Type": "application/json"})
return response.json()['candidates'][0]['content']['parts'][0]['text'].strip()
except: return "💡 ¡Sigue así! Mantente enfocado en tu objetivo."
# Session State and Persistent Initializations
if "modo_pantalla" not in st.session_state: st.session_state.modo_pantalla = "dashboard"
if "comida_activa_id" not in st.session_state: st.session_state.comida_activa_id = None
if "consejo_actual" not in st.session_state: st.session_state.consejo_actual = "💡 Analizando tu día..."
if "consejo_ref_cal" not in st.session_state: st.session_state.consejo_ref_cal = -1
user_profile = cargar_json(FILE_USER, None)
if user_profile and "prot_meta" not in user_profile:
cm, wm, pm, carb_m, gm = calcular_metas(user_profile.get("peso", 70), 170, 25, user_profile['objetivo'], "Moderado")
user_profile.update({"prot_meta": pm, "carb_meta": carb_m, "gras_meta": gm})
guardar_json(FILE_USER, user_profile)
datos_racha = cargar_json(FILE_STREAK, {"racha": 1, "ultima": str(datetime.date.today()), "historial": [str(datetime.date.today())]})
historial_peso = cargar_json(FILE_PESO, {})
comidas_defecto = [
{"id": "desayuno_1", "nombre": "Desayuno", "icono": "☕", "ingredientes": [], "nota_salud": "-"},
{"id": "almuerzo_1", "nombre": "Almuerzo", "icono": "🍲", "ingredientes": [], "nota_salud": "-"},
{"id": "cena_1", "nombre": "Cena", "icono": "🌙", "ingredientes": [], "nota_salud": "-"}
]
diario = cargar_json(FILE_DATA, {"fecha": str(datetime.date.today()), "cal_hoy": 0, "agua_hoy": 0, "prot_hoy": 0, "carb_hoy": 0, "gras_hoy": 0, "comidas": comidas_defecto})
for k in ["prot_hoy", "carb_hoy", "gras_hoy"]: diario.setdefault(k, 0)
if diario["fecha"] != str(datetime.date.today()):
diario = {"fecha": str(datetime.date.today()), "cal_hoy": 0, "agua_hoy": 0, "prot_hoy": 0, "carb_hoy": 0, "gras_hoy": 0, "comidas": comidas_defecto}
guardar_json(FILE_DATA, diario)
st.session_state.consejo_ref_cal = -1
if user_profile is not None and st.session_state.consejo_ref_cal != diario['cal_hoy']:
st.session_state.consejo_actual = obtener_consejo_ia(user_profile, diario)
st.session_state.consejo_ref_cal = diario['cal_hoy']
# Interface Logic Routing
if user_profile is None:
st.title("🚀 Configura tu Perfil")
with st.form("form_onboarding"):
nom = st.text_input("¿Cómo te llamas?")
c1, c2, c3 = st.columns(3)
peso = c1.number_input("Peso (kg)", 40.0, 200.0, 70.0)
talla = c2.number_input("Talla (cm)", 100.0, 250.0, 170.0)
edad = c3.number_input("Edad", 15, 99, 25)
obj = st.selectbox("Objetivo", ["Perder Grasa", "Ganar Músculo", "Mantenimiento"])
act = st.selectbox("Actividad", ["Sedentario", "Ligero", "Moderado", "Intenso"])
if st.form_submit_button("CREAR MI PLAN", type="primary"):
if nom:
c_meta, w_meta, p_meta, carb_meta, g_meta = calcular_metas(peso, talla, edad, obj, act)
guardar_json(FILE_USER, {"nombre": nom, "peso": peso, "objetivo": obj, "cal_meta": c_meta, "water_meta": w_meta, "prot_meta": p_meta, "carb_meta": carb_meta, "gras_meta": g_meta})
st.rerun()
else:
# App logic streams (Diario, Dieta, Perfil)...
# Render UI using custom CSS configurations injected natively.
st.write("FitIA Engine Operativo.")