?
¡Hola! 👋
Tu resumen de hoy
🔥
0
semanas de racha
¡Empieza hoy!
0
puntos acumulados
Último peso (kg)
0/0
Retos completados
🏅 Mis logros
Mi Progreso 📈
Registra tus métricas semanales
Registrar esta semana
✓ ¡Registro guardado! +10 puntos
Introduce al menos el peso.
Evolución del peso
Historial
Hábitos diarios 🌿
Marca tus hábitos de esta semana
Esta semana
Cargando...
Mi Dieta 🥗
Tu plan nutricional personalizado
¿Cómo te encuentras hoy?
✓ Enviado
Cargando...
Mi Entrenamiento 🏋️
Plan de ejercicio personalizado
Cargando...
Recetas 🍽
Biblioteca de recetas saludables
Mensajes 💬
Comunicación con Héctor
Cargando...
Mi Plan 📋
Documentos y recursos personalizados
Cargando...
Buscador de alimentos 🔍
Base de datos con +5.000 alimentos y sus macros
Todos los valores son por 100g o 100ml
Panel Admin 🛠
Salud & Rendimiento · Héctor Serrano
0
Pacientes
0
Mayor racha
0
Sin actividad
0
Pts lider
Estado de pacientes
🏆 Ranking
Añadir paciente
Pacientes — 0
📅 Calendario de citas
📚 Historial de versiones de dieta
Selecciona un paciente
🛒 Lista de la compra semanal
Selecciona un paciente
Publicar reto
Retos activos
Enviar mensaje
Enviados
Asignar documento
Documentos asignados
Constructor de dieta
⚡ Modo rápido
🍽 Recetas guardadas
📥 Importar desde Excel
Formato: Día ; Momento ; Alimento ; Kcal ; Prot ; Carbos ; Grasas
📊 Macros en tiempo real
Selecciona un paciente y añade alimentos
📋 Plan en construcción
Notas y valoraciones de pacientes
Constructor de plan de entrenamiento
Plan de entrenamiento
Añadir receta
Biblioteca — 0
Añadir hábito global
Hábitos activos
🔥 Calculadora de gasto calórico (Mifflin-St Jeor)
TMB (kcal/día)
En reposo total
GET (kcal/día)
Con actividad
Objetivos recomendados:
Déficit
-500 kcal
Manteni-
miento
Superávit
+500 kcal
Definir objetivo
Objetivos activos
🔍 Buscador de alimentos — Base de datos completa
Valores por 100g · Pulsa en un alimento para añadirlo al plan del paciente
📊 Análisis de macros por paciente
📧 Enviar email a paciente
Redactar email
✓ Email enviado correctamente
Error al enviar. Comprueba la configuración de EmailJS.
Historial de emails enviados
Cargando...
// ── SISTEMA DE PERFILES DE PACIENTE ─────────────────── const PERFILES_PREDEFINIDOS = { mujer_deficit: { nombre: 'Mujer · pérdida de grasa', color: '#E24B4A', colorLight: '#FCEBEB', descripcion: 'Mujer sedentaria o moderadamente activa buscando pérdida de grasa corporal. Énfasis en proteínas, desayuno completo, cenas ligeras.', objetivo: 'deficit', macros_base: { prot_pct: 32, carb_pct: 38, fat_pct: 30 }, distribucion_kcal: { Desayuno: 0.22, 'Media mañana': 0.12, Comida: 0.35, Merienda: 0.11, Cena: 0.20 }, caracteristicas: ['Alta en proteínas', 'Sin restricción severa', 'Saciedad prioritaria', 'Apto para TCA leve'], notas_clinicas: 'Evitar déficit >500 kcal. Si tiene TCA activo, consultar antes de aplicar restricción calórica.', variantes: [ { nombre: 'Sedentaria 1500 kcal', kcal: 1500, prot: 120, carb: 140, fat: 50, nota: 'Déficit moderado, sedentaria' }, { nombre: 'Activa 1700 kcal', kcal: 1700, prot: 136, carb: 160, fat: 57, nota: '3-4 días ejercicio/semana' }, { nombre: 'Muy activa 1900 kcal', kcal: 1900, prot: 152, carb: 178, fat: 63, nota: '5+ días ejercicio/semana' } ], plan_estructura: { Lunes: { Desayuno: 'Yogur griego 200g + avena 40g + fruta', 'Media mañana': 'Fruta + puñado frutos secos 20g', Comida: 'Proteína 150g + verdura abundante + carbohidrato 60g crudo', Merienda: 'Queso fresco 100g', Cena: 'Proteína 130g + verdura salteada + ensalada' }, Martes: { Desayuno: 'Tortilla 2 huevos + tostada integral + tomate', 'Media mañana': 'Manzana + té/café', Comida: 'Legumbre 80g seca + verdura + AOVE', Merienda: 'Batido proteína 25g', Cena: 'Pescado 150g + ensalada completa + brócoli vapor' }, Miércoles: { Desayuno: 'Yogur griego + fruta + nueces 15g', 'Media mañana': 'Palitos verdura + hummus 40g', Comida: 'Carne magra 150g + arroz 60g + ensalada', Merienda: 'Yogur + infusión', Cena: 'Revuelto 3 huevos + champiñones + espinacas' }, Jueves: { Desayuno: 'Avena 50g + leche desnatada + plátano', 'Media mañana': 'Fruta + yogur', Comida: 'Pescado 150g + patata cocida 150g + verdura', Merienda: 'Queso fresco + pepino', Cena: 'Pechuga pollo 130g + ensalada grande + AOVE' }, Viernes: { Desayuno: 'Tortitas avena 2 uds + fruta', 'Media mañana': 'Café + onza chocolate negro', Comida: 'Pasta integral 70g + salsa tomate casera + carne 100g', Merienda: 'Yogur griego + fruta', Cena: 'Merluza 150g + verdura plancha + ensalada' }, Sábado: { Desayuno: 'Brunch: huevos revueltos + tostadas + fruta', Comida: 'Comida libre controlada', Merienda: 'Fruta', Cena: 'Cena ligera: sopa o ensalada completa + proteína' }, Domingo: { Desayuno: 'Tostadas aguacate + huevo + café', Comida: 'Comida familiar: proteína + guarnición + postre fruta', Cena: 'Cena ligera + yogur' } } }, hombre_deficit: { nombre: 'Hombre · pérdida de grasa', color: '#378ADD', colorLight: '#E6F1FB', descripcion: 'Hombre con exceso de peso o recomposición corporal. Más volumen de comida, alta proteína, compatible con trabajo y vida social.', objetivo: 'deficit', macros_base: { prot_pct: 30, carb_pct: 40, fat_pct: 30 }, distribucion_kcal: { Desayuno: 0.20, 'Media mañana': 0.10, Comida: 0.38, Merienda: 0.12, Cena: 0.20 }, caracteristicas: ['Más volumen en comida', 'Alta proteína animal', 'Sin pasar hambre', 'Compatible trabajo físico'], notas_clinicas: 'Hombres toleran déficit mayor (hasta -600 kcal). Priorizar proteínas >2g/kg para preservar masa muscular.', variantes: [ { nombre: 'Sedentario 1800 kcal', kcal: 1800, prot: 135, carb: 180, fat: 60, nota: 'Trabajo de oficina' }, { nombre: 'Moderado 2100 kcal', kcal: 2100, prot: 158, carb: 210, fat: 70, nota: 'Ejercicio 3-4 días' }, { nombre: 'Activo 2400 kcal', kcal: 2400, prot: 180, carb: 240, fat: 80, nota: 'Gym 5+ días' } ], plan_estructura: { Lunes: { Desayuno: 'Avena 70g + leche + plátano + café', 'Media mañana': 'Fruta + puñado almendras 25g', Comida: 'Carne magra 200g + arroz 80g + ensalada grande', Merienda: 'Batido proteína 30g + fruta', Cena: 'Pescado 180g + verdura + tortilla 2 huevos' }, Martes: { Desayuno: 'Tortilla 3 huevos + tostadas + tomate', 'Media mañana': 'Yogur griego + café', Comida: 'Legumbre 100g seca + chorizo/morcilla poco + pan', Merienda: 'Sándwich pechuga + queso', Cena: 'Pechuga 180g + ensalada + boniato 150g' }, Miércoles: { Desayuno: 'Pan integral 80g + AOVE + tomate rallado + café', 'Media mañana': 'Fruta grande', Comida: 'Pasta 90g + bolognesa casera + ensalada', Merienda: 'Queso fresco 150g', Cena: 'Salmon 180g + espárragos + patata cocida 100g' }, Jueves: { Desayuno: 'Yogur griego + granola 40g + fruta', 'Media mañana': 'Sándwich pechuga', Comida: 'Arroz 80g + pollo 200g + guacamole', Merienda: 'Batido proteína', Cena: 'Revuelto 4 huevos + champiñones + ensalada' }, Viernes: { Desayuno: 'Tostadas aguacate + huevo + café', 'Media mañana': 'Fruta', Comida: 'Pizza casera masa fina 2 raciones + ensalada', Merienda: 'Yogur + frutos secos', Cena: 'Merluza 200g + verdura plancha' }, Sábado: { Desayuno: 'Desayuno completo + café + zumo', Comida: 'Comida social controlada', Merienda: 'Fruta', Cena: 'Proteína + ensalada + algo de carbohidrato' }, Domingo: { Desayuno: 'Tostadas + huevos + fruta + café', Comida: 'Comida familiar', Cena: 'Cena ligera' } } }, deportista_volumen: { nombre: 'Deportista · ganancia muscular', color: '#1D9E75', colorLight: '#E1F5EE', descripcion: 'Entrenamiento de fuerza 4-5 días/semana. Superávit limpio con alta proteína y carbohidratos estratégicos pre/post entreno.', objetivo: 'superavit', macros_base: { prot_pct: 28, carb_pct: 45, fat_pct: 27 }, distribucion_kcal: { Desayuno: 0.20, 'Media mañana': 0.15, Comida: 0.30, Merienda: 0.20, Cena: 0.15 }, caracteristicas: ['Pre-entreno carbohidratos', 'Post-entreno proteína rápida', 'Sin ganar grasa excesiva', 'Superávit limpio +300-500 kcal'], notas_clinicas: 'Merienda = post-entreno. Ajustar timing de carbohidratos según horario de entrenamiento.', variantes: [ { nombre: 'Volumen limpio 2800 kcal', kcal: 2800, prot: 196, carb: 315, fat: 84, nota: 'GET ~2500 + 300 superávit' }, { nombre: 'Volumen moderado 3200 kcal', kcal: 3200, prot: 224, carb: 360, fat: 96, nota: 'GET ~2800 + 400 superávit' }, { nombre: 'Volumen agresivo 3600 kcal', kcal: 3600, prot: 252, carb: 405, fat: 108, nota: 'Hardgainer / ectomorfo' } ], plan_estructura: { Lunes: { Desayuno: 'Avena 100g + leche entera + plátano + huevos 2', 'Media mañana': 'Arroz con leche casero 200g o batido masas', Comida: 'Pollo 200g + arroz blanco 100g + AOVE + verdura', Merienda: '(Post-entreno) Batido proteína 40g + plátano + avena 40g', Cena: 'Salmón 180g + patata cocida 200g + ensalada' }, Martes: { Desayuno: 'Tortilla 4 huevos + tostadas 80g + zumo naranja', 'Media mañana': 'Sándwich pavo + queso + fruta', Comida: 'Ternera 200g + pasta 100g + tomate', Merienda: 'Yogur griego x2 + nueces + miel', Cena: 'Atún 150g + boniato 200g + brócoli' }, Miércoles: { Desayuno: 'Overnight oats 100g + proteína + frutos rojos', 'Media mañana': 'Queso fresco 200g + fruta', Comida: 'Lentejas 100g + chorizo poco + pan 60g', Merienda: '(Post) Batido proteína + dátiles 4 uds + leche', Cena: 'Pechuga 200g + arroz 80g + verdura' }, Jueves: { Desayuno: 'Pan 100g + jamón york + huevo + café', 'Media mañana': 'Batido masas casero (leche+avena+plátano+cacao)', Comida: 'Carne picada 200g + pasta 100g + queso', Merienda: 'Fruta + frutos secos 30g + yogur', Cena: 'Huevos 4 uds + patatas + espinacas' }, Viernes: { Desayuno: 'Tostadas francesas 3 + miel + fruta + café', 'Media mañana': 'Sándwich grande proteína', Comida: 'Pizza casera 3 raciones + ensalada', Merienda: '(Post) Proteína + plátano', Cena: 'Pescado 200g + arroz 80g' }, Sábado: { Desayuno: 'Desayuno grande libre', Comida: 'Refeed: comida alta en carbohidratos', Merienda: 'Snack proteico', Cena: 'Normal' }, Domingo: { Desayuno: 'Brunch proteico', Comida: 'Comida familiar + postre', Cena: 'Ligera' } } }, deportista_resistencia: { nombre: 'Deportista resistencia', color: '#7F77DD', colorLight: '#EEEDFE', descripcion: 'Corredores, ciclistas, triatletas. Alta demanda de carbohidratos, periodización nutricional según días de entrenamiento.', objetivo: 'mantenimiento', macros_base: { prot_pct: 20, carb_pct: 55, fat_pct: 25 }, distribucion_kcal: { Desayuno: 0.22, 'Media mañana': 0.15, Comida: 0.30, Merienda: 0.18, Cena: 0.15 }, caracteristicas: ['Muy alta en carbohidratos', 'Pre-entreno: carbos rápidos', 'Durante: geles o plátano', 'Recuperación: proteína + carbos'], notas_clinicas: 'Días de entreno largo: aumentar 300-500 kcal en carbohidratos. Días descanso: reducir carbos y mantener proteína.', variantes: [ { nombre: 'Running amateur 2200 kcal', kcal: 2200, prot: 110, carb: 303, fat: 61, nota: '40-60 km/semana' }, { nombre: 'Ciclismo 2600 kcal', kcal: 2600, prot: 130, carb: 358, fat: 72, nota: '8-12h bicicleta/semana' }, { nombre: 'Triatlón 3000 kcal', kcal: 3000, prot: 150, carb: 413, fat: 83, nota: 'Entrenamiento doble sesión' } ], plan_estructura: { Lunes: { Desayuno: '(Pre-entreno) Avena 80g + miel + plátano + café', 'Media mañana': 'Naranja + barrita energética o dátiles 4', Comida: 'Pasta 100g + atún 120g + tomate + AOVE', Merienda: '(Post-entreno) Batido proteína + leche + plátano', Cena: 'Pollo 160g + arroz 80g + ensalada' }, Martes: { Desayuno: 'Tostadas 80g + huevos 2 + zumo naranja', 'Media mañana': 'Yogur + granola 40g', Comida: 'Arroz 100g + salmón 160g + brócoli', Merienda: 'Sándwich + fruta', Cena: 'Lentejas 80g + verdura + huevo' }, Miércoles: { Desayuno: '(Día largo) Pan 100g + AOVE + tomate + café', 'Media mañana': 'Plátano + gel energético durante entrenamiento', Comida: 'Pasta 120g + carne 160g + ensalada', Merienda: 'Batido recuperación (leche+proteína+miel)', Cena: 'Tortilla 3 huevos + boniato 200g' }, Jueves: { Desayuno: 'Avena 60g + leche + fruta (día descanso, menos carbos)', 'Media mañana': 'Fruta + frutos secos 20g', Comida: 'Proteína 180g + ensalada grande + muy pocas patatas', Merienda: 'Yogur + café', Cena: 'Pescado 180g + verdura + pequeña guarnición' }, Viernes: { Desayuno: 'Porridge + proteína + fruta', 'Media mañana': 'Barrita energética + agua', Comida: 'Pasta 100g + bolognesa + ensalada', Merienda: '(Pre-carrera) Plátano + café + gel', Cena: 'Ligera: sopa + pan + tortilla' }, Sábado: { Desayuno: '(Día competición/larga salida) Arroz con leche 300g o pasta + miel', Comida: 'Post-competición: proteína + carbohidratos + fruta', Merienda: 'Recuperación: batido completo', Cena: 'Normal + mayor ración carbohidratos' }, Domingo: { Desayuno: 'Descanso activo: desayuno normal', Comida: 'Comida completa pero sin excesos', Cena: 'Ligera' } } }, poblacion_general: { nombre: 'Población general · mantenimiento', color: '#5F5E5A', colorLight: '#F1EFE8', descripcion: 'Adulto sano sin objetivos deportivos específicos. Alimentación equilibrada, variada y sostenible. Hábitos saludables a largo plazo sin restricciones.', objetivo: 'mantenimiento', macros_base: { prot_pct: 20, carb_pct: 50, fat_pct: 30 }, distribucion_kcal: { Desayuno: 0.20, 'Media mañana': 0.10, Comida: 0.35, Merienda: 0.10, Cena: 0.25 }, caracteristicas: ['Dieta mediterránea', 'Sin restricciones severas', 'Sostenible a largo plazo', 'Educación nutricional'], notas_clinicas: 'Priorizar educación sobre flexibilidad alimentaria. Evitar lenguaje de "prohibidos". La adherencia es más importante que la precisión.', variantes: [ { nombre: 'Mujer adulta 1900 kcal', kcal: 1900, prot: 95, carb: 238, fat: 63, nota: 'Actividad moderada, 30-50 años' }, { nombre: 'Hombre adulto 2300 kcal', kcal: 2300, prot: 115, carb: 288, fat: 77, nota: 'Actividad moderada, 30-50 años' }, { nombre: 'Mayor activo 1800 kcal', kcal: 1800, prot: 108, carb: 203, fat: 60, nota: '+60 años, preservar músculo' } ], plan_estructura: { Lunes: { Desayuno: 'Café con leche + tostadas AOVE + fruta', 'Media mañana': 'Fruta de temporada', Comida: 'Legumbre o arroz + verdura + proteína moderada + pan', Merienda: 'Yogur natural', Cena: 'Pescado o huevos + verdura + ensalada + pan' }, Martes: { Desayuno: 'Café + pan integral + tomate + AOVE', 'Media mañana': 'Puñado almendras o nueces 20g', Comida: 'Pasta o arroz 80g + carne magra + ensalada', Merienda: 'Pieza fruta', Cena: 'Tortilla 2-3 huevos + champiñones + ensalada' }, Miércoles: { Desayuno: 'Yogur griego + fruta + café', 'Media mañana': 'Fruta', Comida: 'Potaje o guiso tradicional (legumbre + verdura + algo de proteína)', Merienda: 'Infusión + frutos secos', Cena: 'Merluza o dorada al horno + patata + brócoli' }, Jueves: { Desayuno: 'Café + tostadas + aguacate o tomate', 'Media mañana': 'Fruta o yogur', Comida: 'Arroz con verduras + pollo o ternera + ensalada', Merienda: 'Yogur + café', Cena: 'Crema de verduras + pan + queso fresco' }, Viernes: { Desayuno: 'Café con leche + cereales integrales + fruta', 'Media mañana': 'Fruta', Comida: 'Paella o fideuá casera + ensalada (comida social)', Merienda: 'Café + pequeño dulce casero (moderado)', Cena: 'Ensalada completa + lata atún + pan' }, Sábado: { Desayuno: 'Desayuno tardío: huevos revueltos + tostadas + zumo', Comida: 'Comida familiar/social con moderación', Merienda: 'Fruta o café', Cena: 'Ligera: sopa + pan o ensalada' }, Domingo: { Desayuno: 'Desayuno completo relajado', Comida: 'Comida tradicional dominical', Cena: 'Muy ligera: yogur + fruta o crema de verduras' } } }, padel_masculino: { nombre: 'Padelista profesional · masculino', color: '#0F6E56', colorLight: '#E1F5EE', descripcion: 'Deportista profesional de pádel masculino. Varios partidos y entrenamientos semanales, torneos. Necesita energía para explosividad, recuperación rápida entre partidos y mantenimiento de composición corporal.', objetivo: 'mantenimiento', macros_base: { prot_pct: 25, carb_pct: 50, fat_pct: 25 }, distribucion_kcal: { Desayuno: 0.20, 'Media mañana': 0.15, Comida: 0.28, Merienda: 0.22, Cena: 0.15 }, caracteristicas: ['Explosividad (carbos de calidad)', 'Recuperación entre partidos', 'Alta proteína para músculo', 'Hidratación estratégica', 'Periodización torneo/semana'], notas_clinicas: 'Partido en mañana: desayuno 90-120min antes, rico en carbos. Partido tarde: merienda pre-partido ligera 60min antes. Día torneo: evitar alimentos nuevos.', variantes: [ { nombre: 'Entreno moderado 2800 kcal', kcal: 2800, prot: 175, carb: 350, fat: 78, nota: '3-4 sesiones/semana' }, { nombre: 'Entreno intenso 3200 kcal', kcal: 3200, prot: 200, carb: 400, fat: 89, nota: '5-6 sesiones + torneos' }, { nombre: 'Torneo/competición 3500 kcal', kcal: 3500, prot: 219, carb: 438, fat: 97, nota: 'Semana de torneo con varios partidos' } ], plan_estructura: { Lunes: { Desayuno: '(Pre-entreno) Avena 90g + leche + miel + plátano + café', 'Media mañana': '(Durante/post entreno) Plátano + bebida isotónica o batido recuperación', Comida: 'Arroz blanco 90g + pollo 200g + verdura + AOVE', Merienda: '(Pre-tarde si hay 2ª sesión) Tostadas + pavo + fruta', Cena: 'Salmón 180g + patata cocida 150g + ensalada' }, Martes: { Desayuno: 'Tortilla 3 huevos + tostadas 80g + zumo naranja natural', 'Media mañana': 'Sándwich pechuga + queso + plátano', Comida: 'Pasta 100g + bolognesa casera + ensalada + pan', Merienda: 'Batido proteína 35g + fruta + frutos secos 25g', Cena: 'Pechuga 200g + arroz 80g + brócoli' }, Miércoles: { Desayuno: '(Descanso) Yogur griego 200g + granola 40g + fruta', 'Media mañana': 'Fruta + café', Comida: 'Legumbre 90g + chorizo poco + verdura + pan', Merienda: 'Queso fresco 150g + nueces + café', Cena: 'Pescado blanco 180g + ensalada + boniato 150g' }, Jueves: { Desayuno: '(Pre-entreno) Pan 90g + AOVE + jamón + café + zumo', 'Media mañana': 'Barrita energética + plátano + agua', Comida: 'Arroz 90g + carne roja 180g + pimientos + ensalada', Merienda: '(Post) Batido recuperación (leche+proteína+miel+avena 30g)', Cena: 'Salmón 160g + patata 130g + verdura vapor' }, Viernes: { Desayuno: '(Pre-partido o entreno) Arroz con leche 300g o tostadas + miel + plátano', 'Media mañana': 'Plátano + gel o bebida deportiva si hay partido', Comida: '(Post-partido) Arroz 100g + pollo 200g + ensalada + pan', Merienda: 'Batido proteína + dátiles + café', Cena: 'Tortilla 3 huevos + boniato 200g + ensalada' }, Sábado: { Desayuno: '(Día torneo) Arroz con leche casero 350g + café + zumo — 2h antes', 'Media mañana': 'Entre partidos: plátano + bebida isotónica + barrita', Comida: 'Arroz blanco + pollo sin especias + poca ensalada (evitar alimentos nuevos)', Merienda: 'Plátano + gel + agua', Cena: '(Post-torneo recuperación) Proteína 200g + carbohidratos moderados + ensalada' }, Domingo: { Desayuno: 'Descanso: desayuno completo sin prisas', Comida: 'Comida normal recuperación', Cena: 'Ligera: proteína + verdura' } } }, padel_femenino: { nombre: 'Padelista profesional · femenino', color: '#993556', colorLight: '#FBEAF0', descripcion: 'Deportista profesional de pádel femenino. Misma demanda técnica que el masculino con consideraciones específicas: ciclo menstrual, mayor riesgo de déficit de hierro, y tendencia a mayor restricción calórica.', objetivo: 'mantenimiento', macros_base: { prot_pct: 28, carb_pct: 47, fat_pct: 25 }, distribucion_kcal: { Desayuno: 0.20, 'Media mañana': 0.15, Comida: 0.28, Merienda: 0.22, Cena: 0.15 }, caracteristicas: ['Alta proteína (riesgo anemia)', 'Hierro y vitamina D prioritarios', 'No restringir en exceso', 'Fase lútea: +150-200 kcal', 'Calcio para salud ósea'], notas_clinicas: 'IMPORTANTE: Vigilar señales de RED-S (Deficiencia Energética en el Deporte). Analítica: ferritina, vitamina D, calcio. Fase lútea del ciclo: mayor apetito normal, no restringir. Si hay amenorrea, derivar a médico.', variantes: [ { nombre: 'Entreno moderado 2400 kcal', kcal: 2400, prot: 168, carb: 282, fat: 67, nota: '3-4 sesiones/semana' }, { nombre: 'Entreno intenso 2700 kcal', kcal: 2700, prot: 189, carb: 317, fat: 75, nota: '5-6 sesiones + torneos' }, { nombre: 'Torneo/competición 3000 kcal', kcal: 3000, prot: 210, carb: 353, fat: 83, nota: 'Semana de torneo' } ], plan_estructura: { Lunes: { Desayuno: '(Pre-entreno) Avena 70g + leche + miel + plátano + café', 'Media mañana': 'Batido recuperación post-entreno (leche+proteína+plátano) o yogur griego + fruta', Comida: 'Arroz 80g + salmón 160g (rico en hierro hemo) + espinacas salteadas + AOVE', Merienda: '(Pre si hay 2ª sesión) Tostadas + pechuga + fruta', Cena: 'Ternera 160g (hierro) + boniato 130g + brócoli + AOVE' }, Martes: { Desayuno: 'Yogur griego 200g + granola 35g + frutos rojos + café', 'Media mañana': 'Sándwich pavo + queso + naranja (vitamina C mejora absorción hierro)', Comida: 'Lentejas 80g + espinacas + zanahoria + huevo duro (hierro + calcio)', Merienda: 'Batido proteína 30g + plátano + leche', Cena: 'Merluza 160g + arroz 70g + ensalada' }, Miércoles: { Desayuno: '(Descanso) Tostadas 70g + aguacate + huevo + café', 'Media mañana': 'Fruta + puñado nueces (omega 3)', Comida: 'Pasta 90g + atún 130g + tomate + AOVE + queso parmesano', Merienda: 'Yogur griego + miel + fruta', Cena: 'Pechuga 160g + quinoa 70g + verdura asada' }, Jueves: { Desayuno: '(Pre-entreno) Pan integral 80g + jamón serrano + zumo naranja + café', 'Media mañana': 'Plátano + bebida deportiva durante entreno', Comida: 'Pollo 170g + arroz 80g + pimientos + ensalada', Merienda: '(Post) Batido recuperación completo + frutos secos 20g', Cena: 'Salmón 160g + patata 120g + espárragos' }, Viernes: { Desayuno: '(Pre-partido o entreno) Arroz con leche 250g + café', 'Media mañana': 'Plátano + gel si hay partido', Comida: '(Post) Pollo 170g + arroz 80g + ensalada + pan', Merienda: 'Batido proteína + dátiles + leche', Cena: 'Tortilla 3 huevos + boniato 150g + espinacas' }, Sábado: { Desayuno: '(Torneo) Arroz con leche 280g + café + zumo — 2h antes', 'Media mañana': 'Entre partidos: plátano + bebida isotónica + barrita sin ingredientes nuevos', Comida: 'Arroz blanco + pechuga + ensalada sencilla (sin fibra excesiva)', Merienda: 'Plátano + agua + gel si hay partido tarde', Cena: '(Recuperación) Proteína 170g + carbohidratos + ensalada + yogur con calcio' }, Domingo: { Desayuno: 'Descanso: desayuno completo y relajado', Comida: 'Comida normal + postre fruta', Cena: 'Ligera: crema verduras + proteína + yogur' } } } }; window.abrirPerfiles = function() { const modal = document.getElementById('modal-perfiles'); modal.style.display = 'flex'; // Mostrar info del paciente seleccionado const pacId = document.getElementById('d-pac').value; const pac = pacientes.find(p => p.id === pacId); const infoEl = document.getElementById('perfil-pac-info'); if (pac) { infoEl.style.display = 'block'; document.getElementById('perfil-pac-nombre').textContent = pac.nombre; const gc = pac.gastoCalórico; document.getElementById('perfil-pac-gcal').innerHTML = gc ? `GET: ${gc.tdee} kcal · Déficit: ${gc.deficit} kcal · Superávit: ${gc.superavit} kcal` : '⚠️ Sin GET calculado — ve a Obj. & Kcal primero para mejores resultados'; } else { infoEl.style.display = 'none'; } mostrarPerfil('mujer_deficit', document.querySelector('.perfil-tab')); }; window.mostrarPerfil = function(perfilKey, btn) { document.querySelectorAll('.perfil-tab').forEach(b => b.style.opacity = '.6'); if (btn) btn.style.opacity = '1'; const p = PERFILES_PREDEFINIDOS[perfilKey]; if (!p) return; // Calcular kcal objetivo desde el paciente si tiene GET const pacId = document.getElementById('d-pac').value; const pac = pacientes.find(px => px.id === pacId); const gc = pac?.gastoCalórico; const kcalSugerida = gc ? (p.objetivo === 'deficit' ? gc.deficit : p.objetivo === 'superavit' ? gc.superavit : gc.tdee) : null; const contenido = document.getElementById('perfil-contenido'); contenido.innerHTML = `
${p.nombre}
${p.descripcion}
${p.caracteristicas.map(c => `${c}`).join('')}
${p.notas_clinicas ? `
⚕️ ${p.notas_clinicas}
` : ''}
📐 Modo estructura
Distribución de kcal por comida + alimentos típicos de referencia. Tú añades los alimentos reales.
Para cuando ya conoces al paciente
✨ Plan completo
Plan de 7 días con alimentos reales, raciones y macros calculados. Lista para ajustar.
Para pacientes nuevos o poco tiempo
Variante calórica ${kcalSugerida ? `— recomendada para ${pac.nombre}: ${kcalSugerida} kcal` : ''}:
${p.variantes.map((v,i) => `
${v.kcal} kcal
${v.nota}
P:${v.prot}g C:${v.carb}g G:${v.fat}g
`).join('')}
✏️
Personalizar
Mis kcal exactas
Distribución calórica por comida
`; // Renderizar distribución _renderDistribucion(perfilKey); }; function _renderDistribucion(perfilKey) { const p = PERFILES_PREDEFINIDOS[perfilKey]; const el = document.getElementById('dist-comidas-' + perfilKey); if (!el) return; const kcalTotal = window._perfilKcalSel || p.variantes[0].kcal; el.innerHTML = Object.entries(p.distribucion_kcal).map(([m, pct]) => { const k = Math.round(kcalTotal * pct); return `
${m.length>8?m.substring(0,7)+'…':m}
${k}
kcal · ${Math.round(pct*100)}%
`; }).join(''); } let _perfilSeleccionado = null; let _perfilVariante = null; let _perfilModo = 'estructura'; window._perfilKcalSel = null; window.selVariante = function(el, perfilKey, idx) { document.querySelectorAll('.variante-card').forEach(c => { c.classList.remove('sel'); c.style.borderColor = 'var(--border)'; c.style.background = 'var(--bg)'; }); const p = PERFILES_PREDEFINIDOS[perfilKey]; el.classList.add('sel'); el.style.borderColor = p.color; el.style.background = p.colorLight; _perfilSeleccionado = perfilKey; _perfilVariante = idx; window._perfilKcalSel = p.variantes[idx].kcal; _renderDistribucion(perfilKey); document.getElementById('btn-aplicar-perfil-wrap').style.display = 'block'; }; window.seleccionarModo = function(modo, perfilKey) { _perfilModo = modo; const p = PERFILES_PREDEFINIDOS[perfilKey]; document.getElementById('modo-estructura-card').style.borderWidth = modo === 'estructura' ? '2px' : '1px'; document.getElementById('modo-completo-card').style.borderWidth = modo === 'completo' ? '2px' : '1px'; }; window.mostrarKcalCustom = function(perfilKey) { const wrap = document.getElementById('kcal-custom-wrap-' + perfilKey); if (wrap) wrap.style.display = 'block'; _perfilSeleccionado = perfilKey; _perfilVariante = 'custom'; document.getElementById('btn-aplicar-perfil-wrap').style.display = 'block'; }; window.aplicarPerfilSeleccionado = async function() { if (!_perfilSeleccionado) { toast('Selecciona una variante primero', false); return; } const p = PERFILES_PREDEFINIDOS[_perfilSeleccionado]; // Obtener kcal y macros let kcal, prot, carb, fat; if (_perfilVariante === 'custom') { kcal = +document.getElementById('kcal-custom-' + _perfilSeleccionado)?.value || p.variantes[0].kcal; prot = +document.getElementById('prot-custom-' + _perfilSeleccionado)?.value || p.variantes[0].prot; carb = +document.getElementById('carb-custom-' + _perfilSeleccionado)?.value || p.variantes[0].carb; fat = +document.getElementById('fat-custom-' + _perfilSeleccionado)?.value || p.variantes[0].fat; } else { const v = p.variantes[_perfilVariante]; kcal = v.kcal; prot = v.prot; carb = v.carb; fat = v.fat; } // Configurar objetivo en el constructor document.getElementById('d-objetivo-tipo').value = p.objetivo; document.getElementById('d-objetivo-kcal').value = kcal; const DIAS = ['Lunes','Martes','Miércoles','Jueves','Viernes','Sábado','Domingo']; const MOMENTOS = ['Desayuno','Media mañana','Comida','Merienda','Cena']; if (_perfilModo === 'estructura') { // Modo estructura: cargar alimentos de referencia con kcal proporcionales dietaB = []; DIAS.forEach(dia => { const planDia = p.plan_estructura[dia] || {}; MOMENTOS.forEach(momento => { if (planDia[momento]) { const kcalMomento = Math.round(kcal * (p.distribucion_kcal[momento] || 0)); dietaB.push({ dia, momento, alimento: planDia[momento], kcal: kcalMomento, proteinas: null, carbohidratos: null, grasas: null, id: Date.now().toString() + Math.random().toString(36).slice(2), esReferencia: true }); } }); }); toast(`✅ Estructura del perfil "${p.nombre}" cargada — ${dietaB.length} comidas de referencia`, 'ok'); } else { // Modo completo: usar IA para generar plan real const pacId = document.getElementById('d-pac').value; const pac = pacientes.find(px => px.id === pacId); if (!pac) { toast('Selecciona un paciente primero para el plan completo', false); return; } document.getElementById('modal-perfiles').style.display = 'none'; toast('⏳ Generando plan completo con IA...', 'ok'); const prompt = `Eres un nutricionista deportivo español experto. Genera un plan nutricional COMPLETO de 7 días. PERFIL: ${p.nombre} DESCRIPCIÓN: ${p.descripcion} PACIENTE: ${pac.nombre} OBJETIVO CALÓRICO: ${kcal} kcal/día (${p.objetivo}) MACROS: Proteínas ${prot}g · Carbohidratos ${carb}g · Grasas ${fat}g DISTRIBUCIÓN POR COMIDAS: ${Object.entries(p.distribucion_kcal).map(([m,pct]) => `- ${m}: ${Math.round(kcal*pct)} kcal (${Math.round(pct*100)}%)`).join('\n')} CARACTERÍSTICAS DEL PERFIL: ${p.caracteristicas.join(', ')} REGLAS IMPORTANTES: 1. Usa alimentos ESPAÑOLES reales con gramajes concretos en el nombre del alimento 2. Varía los platos entre días, no repitas el mismo plato seguido 3. Los macros de cada comida deben ser coherentes con el alimento descrito 4. NO repitas exactamente los mismos desayunos más de 2 veces seguidas 5. Los sábados y domingos pueden ser más flexibles y sociales RESPONDE SOLO CON JSON VÁLIDO (sin texto, sin markdown): {"resumen":{"kcal_media":${kcal},"proteinas_media":${prot},"carbohidratos_media":${carb},"grasas_media":${fat}},"dias":[{"dia":"Lunes","comidas":[{"momento":"Desayuno","alimento":"descripción concreta con gramaje","kcal":0,"proteinas":0,"carbohidratos":0,"grasas":0}]}]}`; try { const response = await fetch('/.netlify/functions/claude-proxy', { method: 'POST', headers: { 'Content-Type': 'application/json' }, body: JSON.stringify({ model: 'claude-sonnet-4-20250514', max_tokens: 4000, messages: [{ role: 'user', content: prompt }] }) }); const data = await response.json(); const texto = data.content?.[0]?.text || ''; const plan = JSON.parse(texto.replace(/```json|```/g,'').trim()); dietaB = []; (plan.dias || []).forEach(d => { (d.comidas || []).forEach(c => { dietaB.push({ dia: d.dia, momento: c.momento, alimento: c.alimento, kcal: c.kcal||null, proteinas: c.proteinas||null, carbohidratos: c.carbohidratos||null, grasas: c.grasas||null, id: Date.now().toString() + Math.random().toString(36).slice(2) }); }); }); toast(`✅ Plan completo "${p.nombre}" generado — ${dietaB.length} comidas`, 'ok'); } catch(e) { // Si falla la IA, cargar estructura igualmente console.error('IA error:', e); toast('⚠️ IA no disponible — cargando estructura de referencia', false); _perfilModo = 'estructura'; aplicarPerfilSeleccionado(); return; } } // Configurar tipo de plan en el constructor document.getElementById('d-tipo').value = 'semana'; if (window.toggleDTipo) toggleDTipo(); renderDietaPrev(); document.getElementById('modal-perfiles').style.display = 'none'; // Scroll al constructor setTimeout(() => { document.getElementById('d-preview')?.scrollIntoView({behavior:'smooth', block:'start'}); }, 300); }; window.guardarComoPlantilla = async function(perfilKey) { const p = PERFILES_PREDEFINIDOS[perfilKey]; const v = _perfilVariante !== null && _perfilVariante !== 'custom' ? p.variantes[_perfilVariante] : p.variantes[0]; const nombre = `${p.nombre} — ${v.kcal} kcal`; await addDoc(collection(db, 'plantillas_dieta'), { nombre, tipo: p.objetivo, descripcion: p.descripcion, kcal: v.kcal, proteinas: v.prot, carbohidratos: v.carb, grasas: v.fat, perfil: perfilKey, usos: 0, creadaEn: new Date().toISOString() }); toast('✅ Guardado como plantilla reutilizable', 'ok'); }; // ── VISTA DE PLANIFICACIÓN SEMANAL/MENSUAL ───────────── let _planModo = 'semana'; // 'semana' | 'mes' let _planOffset = 0; // semanas/meses desde hoy let _planPacId = null; let _planDietaCache = null; // alimentos cargados del paciente window.toggleVistaPlan = function() { const panel = document.getElementById('panel-planificacion'); const btn = document.getElementById('btn-vista-plan'); const visible = panel.style.display !== 'none'; panel.style.display = visible ? 'none' : 'block'; btn.style.background = visible ? 'var(--brand)' : 'var(--purple)'; btn.textContent = visible ? '📅 Vista planificación' : '✕ Cerrar planificación'; if (!visible) { // Sincronizar paciente seleccionado en el constructor const dPac = document.getElementById('d-pac').value; if (dPac) { document.getElementById('plan-view-pac').value = dPac; _planPacId = dPac; } _planOffset = 0; cargarPlanificacion(); } }; window.setPlanModo = function(modo) { _planModo = modo; _planOffset = 0; document.getElementById('plan-modo-semana').style.background = modo === 'semana' ? 'var(--brand)' : 'none'; document.getElementById('plan-modo-semana').style.color = modo === 'semana' ? 'white' : 'var(--text2)'; document.getElementById('plan-modo-mes').style.background = modo === 'mes' ? 'var(--brand)' : 'none'; document.getElementById('plan-modo-mes').style.color = modo === 'mes' ? 'white' : 'var(--text2)'; cargarPlanificacion(); }; window.navPlan = function(delta) { _planOffset = delta === 0 ? 0 : _planOffset + delta; cargarPlanificacion(); }; window.cargarPlanificacion = async function() { const pacId = document.getElementById('plan-view-pac').value; _planPacId = pacId || null; const tablaEl = document.getElementById('plan-tabla'); const resumenEl = document.getElementById('plan-resumen'); if (!pacId) { tablaEl.innerHTML = '
Selecciona un paciente para ver su planificación
'; resumenEl.innerHTML = ''; return; } tablaEl.innerHTML = '
Cargando...
'; // Cargar dieta del paciente desde Firestore try { const snap = await getDoc(doc(db, 'dietas', pacId)); _planDietaCache = snap.exists() ? (snap.data().alimentos || []) : []; } catch(e) { _planDietaCache = []; } // Si hay alimentos en dietaB (construcción activa), usarlos también const alimentos = dietaB.length > 0 ? dietaB : _planDietaCache; const pac = pacientes.find(p => p.id === pacId); const target = parseInt(document.getElementById('d-objetivo-kcal')?.value) || pac?.gastoCalórico?.tdee || null; if (_planModo === 'semana') { _renderVistaSemana(alimentos, target, pac); } else { _renderVistaMes(alimentos, target, pac); } }; // ── VISTA SEMANAL ───────────────────────────────────── function _renderVistaSemana(alimentos, target, pac) { const tablaEl = document.getElementById('plan-tabla'); const resumenEl = document.getElementById('plan-resumen'); const DIAS = ['Lunes','Martes','Miércoles','Jueves','Viernes','Sábado','Domingo']; const MOMENTOS = ['Desayuno','Media mañana','Comida','Merienda','Cena']; // Calcular semana actual + offset const hoy = new Date(); const dow = hoy.getDay() === 0 ? 6 : hoy.getDay() - 1; // 0=lunes const lunes = new Date(hoy); lunes.setDate(hoy.getDate() - dow + _planOffset * 7); const domingo = new Date(lunes); domingo.setDate(lunes.getDate() + 6); const fmt = d => d.toLocaleDateString('es-ES', {day:'numeric', month:'short'}); document.getElementById('plan-periodo-label').textContent = `${fmt(lunes)} – ${fmt(domingo)}`; // Agrupar alimentos por día const porDia = {}; DIAS.forEach(d => porDia[d] = {}); MOMENTOS.forEach(m => DIAS.forEach(d => porDia[d][m] = [])); alimentos.forEach(a => { const d = a.dia || 'Lunes'; const m = a.momento || 'Desayuno'; if (!porDia[d]) porDia[d] = {}; if (!porDia[d][m]) porDia[d][m] = []; porDia[d][m].push(a); }); // Totales por día const kcalPorDia = {}; DIAS.forEach(d => { kcalPorDia[d] = alimentos.filter(a => (a.dia||'Lunes') === d).reduce((s,a) => s+(parseInt(a.kcal)||0), 0); }); // ── TABLA SEMANAL ── // Cabecera días let html = ``; DIAS.forEach(d => { const k = kcalPorDia[d]; const pct = target && k ? Math.round(k/target*100) : null; const semColor = !k ? 'var(--border)' : !pct ? 'var(--border)' : pct>=90&&pct<=110 ? '#1D9E75' : pct<90 ? '#BA7517' : '#E24B4A'; const semBg = !k ? 'var(--bg)' : !pct ? 'var(--bg)' : pct>=90&&pct<=110 ? '#E1F5EE' : pct<90 ? '#FAEEDA' : '#FCEBEB'; const semTxt = !k ? '' : !pct ? k+'k' : `${k}k (${pct}%)`; html += ``; }); html += ``; // Filas por momento MOMENTOS.forEach((m, mi) => { const rowBg = mi % 2 === 0 ? 'var(--bg)' : 'var(--card)'; html += ``; DIAS.forEach(d => { const items = porDia[d][m] || []; const mKcal = items.reduce((s,a) => s+(parseInt(a.kcal)||0), 0); html += ``; }); html += ``; }); // Fila de totales html += ``; let semTotal = 0, diasConDatos = 0; DIAS.forEach(d => { const k = kcalPorDia[d]; semTotal += k; if (k > 0) diasConDatos++; const pct = target && k ? Math.round(k/target*100) : null; const col = !k ? 'var(--text3)' : !pct ? 'var(--brand)' : pct>=90&&pct<=110 ? '#1D9E75' : pct<90 ? '#BA7517' : '#E24B4A'; html += ``; }); html += `
Comida
${d}
${semTxt||'–'}
${target && k ? `
` : ''}
${m.length>8?m.substring(0,7)+'…':m}`; if (!items.length) { // Celda vacía — click para añadir html += `
+
`; } else { items.forEach(a => { const nombre = (a.alimento||'').length > 28 ? (a.alimento||'').substring(0,26)+'…' : (a.alimento||''); html += `
${esc(nombre)}
${a.kcal ? `
${a.kcal} kcal
` : ''}
`; }); // Añadir más html += `
+ añadir
`; } if (mKcal > 0) { html += `
${mKcal}k
`; } html += `
TOTAL
${k?k+' kcal':'—'}
${pct?`
${pct}%
`:''}
`; tablaEl.innerHTML = html; // Resumen semanal const avgKcal = diasConDatos > 0 ? Math.round(semTotal/diasConDatos) : 0; const semPct = target && avgKcal ? Math.round(avgKcal/target*100) : null; const semCol = !semPct ? 'var(--brand)' : semPct>=90&&semPct<=110 ? '#1D9E75' : semPct<90 ? '#BA7517' : '#E24B4A'; const totProt = alimentos.reduce((s,a)=>s+(parseInt(a.proteinas)||0),0); const totCarb = alimentos.reduce((s,a)=>s+(parseInt(a.carbohidratos)||0),0); const totFat = alimentos.reduce((s,a)=>s+(parseInt(a.grasas)||0),0); const avgProt = diasConDatos>0?Math.round(totProt/diasConDatos):0; const avgCarb = diasConDatos>0?Math.round(totCarb/diasConDatos):0; const avgFat = diasConDatos>0?Math.round(totFat/diasConDatos):0; resumenEl.innerHTML = `
${avgKcal}
kcal media/día
${target?`
${semPct}% del objetivo
`:''} ${target?`
Objetivo: ${target} kcal
`:''}
${avgProt}g
proteínas/día
${target?`
Obj: ~${Math.round(target*0.30/4)}g
`:''}
${avgCarb}g
carbos/día
${target?`
Obj: ~${Math.round(target*0.40/4)}g
`:''}
${avgFat}g
grasas/día
${target?`
Obj: ~${Math.round(target*0.30/9)}g
`:''}
${semTotal}
kcal total semana
${diasConDatos} día${diasConDatos!==1?'s':''} con datos
${diasConDatos < 7 ? `
⚠️
${7-diasConDatos} día${7-diasConDatos!==1?'s':''} sin planificar
Faltan alimentos en el plan
` : `
Semana completa
7 días planificados
`}
`; } // ── VISTA MENSUAL ───────────────────────────────────── function _renderVistaMes(alimentos, target, pac) { const tablaEl = document.getElementById('plan-tabla'); const resumenEl = document.getElementById('plan-resumen'); const DIAS_SEMANA = ['L','M','X','J','V','S','D']; const DIAS_NOMBRE = ['Lunes','Martes','Miércoles','Jueves','Viernes','Sábado','Domingo']; const MESES = ['Enero','Febrero','Marzo','Abril','Mayo','Junio','Julio','Agosto','Septiembre','Octubre','Noviembre','Diciembre']; const hoy = new Date(); const fecha = new Date(hoy.getFullYear(), hoy.getMonth() + _planOffset, 1); const año = fecha.getFullYear(); const mes = fecha.getMonth(); document.getElementById('plan-periodo-label').textContent = `${MESES[mes]} ${año}`; // Mapa de kcal por nombre de día (el plan es semanal, se repite) const kcalPorNomDia = {}; DIAS_NOMBRE.forEach(d => { kcalPorNomDia[d] = alimentos.filter(a=>(a.dia||'Lunes')===d).reduce((s,a)=>s+(parseInt(a.kcal)||0),0); }); // Construir mes let primerDia = new Date(año, mes, 1).getDay(); primerDia = primerDia === 0 ? 6 : primerDia - 1; const totalDias = new Date(año, mes+1, 0).getDate(); const hoyStr = hoy.toDateString(); let html = `
`; // Cabecera días DIAS_SEMANA.forEach(d => { html += `
${d}
`; }); // Espacios vacíos al inicio for (let i = 0; i < primerDia; i++) { html += `
`; } // Días del mes for (let d = 1; d <= totalDias; d++) { const fechaDia = new Date(año, mes, d); const nomDia = DIAS_NOMBRE[fechaDia.getDay()===0?6:fechaDia.getDay()-1]; const k = kcalPorNomDia[nomDia] || 0; const pct = target && k ? Math.round(k/target*100) : null; const esHoy = fechaDia.toDateString() === hoyStr; let bg = 'var(--bg)'; let border = '1px solid var(--border)'; let kcalColor = 'var(--text3)'; let kcalBg = 'transparent'; if (k > 0) { if (pct >= 90 && pct <= 110) { bg = '#E1F5EE'; border = '1px solid #1D9E75'; kcalColor = '#085041'; kcalBg = '#1D9E75'; } else if (pct < 90) { bg = '#FAEEDA'; border = '1px solid #BA7517'; kcalColor = '#633806'; kcalBg = '#BA7517'; } else { bg = '#FCEBEB'; border = '1px solid #E24B4A'; kcalColor = '#791F1F'; kcalBg = '#E24B4A'; } } if (esHoy) border = '2px solid var(--brand)'; // Contar comidas de ese día const nComidas = new Set(alimentos.filter(a=>(a.dia||'Lunes')===nomDia).map(a=>a.momento)).size; html += `
${d} ${nomDia.substring(0,3)}
${k > 0 ? `
${k} kcal
${pct ? `
${pct}%
` : ''}
${nComidas} comida${nComidas!==1?'s':''}
` : `
sin datos
`}
`; } html += `
`; tablaEl.innerHTML = html; // Resumen mensual const diasPlanificados = DIAS_NOMBRE.filter(d => kcalPorNomDia[d] > 0).length; const kcalMedia = diasPlanificados > 0 ? Math.round(Object.values(kcalPorNomDia).filter(k=>k>0).reduce((s,k)=>s+k,0) / diasPlanificados) : 0; const semPct = target && kcalMedia ? Math.round(kcalMedia/target*100) : null; const semCol = !semPct ? 'var(--brand)' : semPct>=90&&semPct<=110 ? '#1D9E75' : semPct<90 ? '#BA7517' : '#E24B4A'; resumenEl.innerHTML = `
${kcalMedia}
kcal media/día
${semPct?`
${semPct}% objetivo
`:''}
${diasPlanificados}/7
días con plan
${target?`
${target}
kcal objetivo/día
`:''}
`; } // Click en celda vacía → selecciona ese día+momento en el modo rápido window.celdaClickPlan = function(dia, momento) { // Cambiar selector de día const diaFast = document.getElementById('d-dia-fast'); if (diaFast) diaFast.value = dia; const drDia = document.getElementById('dr-dia'); if (drDia) drDia.value = dia; document.getElementById('d-dia')?.setAttribute && (document.getElementById('d-dia').value = dia); // Activar momento document.querySelectorAll('.btn-momento').forEach(b => { if (b.dataset.momento === momento) { selMomento(momento, b); } }); // Hacer scroll al buscador const search = document.getElementById('fast-search'); if (search) { search.scrollIntoView({behavior:'smooth', block:'center'}); setTimeout(() => search.focus(), 400); } toast(`📅 ${dia} · ${momento} — ahora busca el alimento`, 'ok'); }; window.celdaClickPlanMes = function(nomDia) { celdaClickPlan(nomDia, 'Comida'); }; // Actualizar planificación cuando cambia dietaB const _origRenderPrev = renderDietaPrev; renderDietaPrev = function() { _origRenderPrev(); const panel = document.getElementById('panel-planificacion'); if (panel && panel.style.display !== 'none' && _planPacId) { const pac = pacientes.find(p => p.id === _planPacId); const target = parseInt(document.getElementById('d-objetivo-kcal')?.value) || pac?.gastoCalórico?.tdee || null; if (_planModo === 'semana') _renderVistaSemana(dietaB, target, pac); else _renderVistaMes(dietaB, target, pac); } }; // Sync plan-view-pac con el selector principal del constructor const _origLoadPacGcal2 = window.loadPacGcal; window.loadPacGcal = function() { _origLoadPacGcal2(); const dPac = document.getElementById('d-pac').value; const planPac = document.getElementById('plan-view-pac'); if (planPac && dPac) { planPac.value = dPac; _planPacId = dPac; const panel = document.getElementById('panel-planificacion'); if (panel && panel.style.display !== 'none') cargarPlanificacion(); } }; // Rellenar selector plan-view-pac const _origUpdateAdminSelects2 = window.updateAdminSelects; window.updateAdminSelects = function() { _origUpdateAdminSelects2(); const el = document.getElementById('plan-view-pac'); if (!el) return; const v = el.value; el.innerHTML = '' + pacientes.map(p => ``).join(''); }; // ── HOOK showFTab para nuevas pestañas ───────────────── const _origShowFTab=window.showFTab; window.showFTab=function(tab,btn){ _origShowFTab(tab,btn); if(!window.currentFichaPac)return; const id=window.currentFichaPac.id; if(tab==='timeline')loadTimeline(id); if(tab==='smart')loadSmart(id); if(tab==='msgpac')loadMsgPac(id); }; // ── HOOK showAP para nuevas pestañas admin ───────────── const _origShowAP=window.showAP; window.showAP=function(name,btn){ _origShowAP(name,btn); if(name==='calendario')renderCalendario(); if(name==='histdieta')updateAdminSelects(); if(name==='compra')updateAdminSelects(); }; // ── TIMELINE ─────────────────────────────────────────── window.loadTimeline=async function(pacId){ const el=document.getElementById('f-timeline-content'); el.innerHTML='
Cargando timeline...
'; try{ const [sR,sRg,sM]=await Promise.all([ getDocs(query(collection(db,'revisiones'),where('pacienteId','==',pacId),orderBy('fecha','desc'))), getDocs(query(collection(db,`reg_${pacId}`),orderBy('fecha','desc'))), getDocs(query(collection(db,'mensajes'),where('pacienteId','==',pacId),orderBy('createdAt','desc'))) ]); const items=[]; sR.docs.forEach(d=>{const r=d.data();items.push({tipo:'revision',fecha:r.fecha||r.createdAt?.split('T')[0]||'',data:r,id:d.id});}); sRg.docs.forEach(d=>{const r=d.data();items.push({tipo:'registro',fecha:r.fecha?.split('T')[0]||r.createdAt?.split('T')[0]||'',data:r});}); sM.docs.forEach(d=>{const r=d.data();items.push({tipo:'mensaje',fecha:r.createdAt?.split('T')[0]||'',data:r});}); items.sort((a,b)=>b.fecha.localeCompare(a.fecha)); if(!items.length){el.innerHTML='
Sin actividad registrada.
';return;} const ic={revision:'📋',registro:'⚖️',mensaje:'💬'}; const co={revision:'var(--purple)',registro:'var(--brand)',mensaje:'var(--green)'}; const et={revision:'Revisión clínica',registro:'Registro',mensaje:'Mensaje'}; el.innerHTML=`
${items.map(it=>{ let det=''; if(it.tipo==='revision'){const r=it.data;det=`
${r.peso?`⚖️ ${r.peso}kg`:''} ${r.cintura?`📏 ${r.cintura}cm`:''} ${r.grasa?`🔥 ${r.grasa}%`:''} ${r.adherencia?`📊 Adh ${r.adherencia}/10`:''}
${r.notas?`
"${esc(r.notas.substring(0,100))}"
`:''}`;} else if(it.tipo==='registro'){const r=it.data;det=`
${r.peso?'Peso: '+r.peso+'kg ':''} ${r.cintura?'· Cintura: '+r.cintura+'cm ':''} ${r.grasa?'· Grasa: '+r.grasa+'%':''}
`;} else{det=`
${esc((it.data.texto||'').substring(0,120))}
`;} return`
${ic[it.tipo]} ${et[it.tipo]}
${it.fecha}
${det}
`; }).join('')}
`; }catch(e){el.innerHTML='
Error al cargar timeline.
';} }; // ── OBJETIVOS SMART ──────────────────────────────────── window.loadSmart=async function(pacId){ const el=document.getElementById('f-smart-content'); el.innerHTML='
Cargando...
'; try{ const snap=await getDocs(query(collection(db,'smart_goals'),where('pacienteId','==',pacId),orderBy('createdAt','desc'))); const goals=snap.docs.map(d=>({id:d.id,...d.data()})); el.innerHTML=`
🎯 Nuevo objetivo SMART
${goals.length?goals.map(g=>{ const ini=parseFloat(g.inicial)||0,meta=parseFloat(g.meta)||0,actual=parseFloat(g.actual||g.inicial)||0; const total=Math.abs(meta-ini)||1; const prog=Math.min(100,Math.max(0,Math.round(Math.abs(actual-ini)/total*100))); const done=new Date(g.fechaLimite)g.inicial?actual>=meta:actual<=meta; return`
${esc(g.objetivo)}
${g.categoria||''} · Límite: ${g.fechaLimite||'—'}
${logrado?'✓ Logrado':done?'Vencido':''}
${prog}%
Inicio: ${g.inicial}${g.unidad||''} Meta: ${g.meta}${g.unidad||''} Actual: ${g.actual||g.inicial}${g.unidad||''}
${g.motivacion?`
"${esc(g.motivacion)}"
`:''}
`; }).join(''):'
Sin objetivos SMART definidos aún.
'}`; }catch(e){el.innerHTML='
Error al cargar.
';} }; window.guardarSmart=async function(pacId){ const objetivo=document.getElementById('sm-objetivo').value.trim(); if(!objetivo){toast('Escribe el objetivo',false);return;} await addDoc(collection(db,'smart_goals'),{ pacienteId:pacId,objetivo, inicial:document.getElementById('sm-inicial').value||null, meta:document.getElementById('sm-meta').value||null, unidad:document.getElementById('sm-unidad').value||'', fechaLimite:document.getElementById('sm-fecha').value||null, categoria:document.getElementById('sm-categoria').value, motivacion:document.getElementById('sm-motivacion').value.trim(), actual:document.getElementById('sm-inicial').value||null, createdAt:new Date().toISOString() }); toast('✓ Objetivo guardado','ok');loadSmart(pacId); }; window.actualizarSmart=async function(pacId,goalId){ const val=document.getElementById('sm-act-'+goalId).value; if(!val){toast('Introduce el valor actual',false);return;} await updateDoc(doc(db,'smart_goals',goalId),{actual:val,updatedAt:new Date().toISOString()}); toast('✓ Progreso actualizado','ok');loadSmart(pacId); }; window.borrarSmart=async function(pacId,id){ if(!confirm('¿Eliminar este objetivo?'))return; await deleteDoc(doc(db,'smart_goals',id));toast('Eliminado');loadSmart(pacId); }; // ── MENSAJES POR PACIENTE (desde ficha) ─────────────── window.loadMsgPac=async function(pacId){ const pac=pacientes.find(p=>p.id===pacId); const el=document.getElementById('f-msgpac-content'); const [snapEnv,snapProg]=await Promise.all([ getDocs(query(collection(db,'mensajes'),where('pacienteId','==',pacId),orderBy('createdAt','desc'))), getDocs(query(collection(db,'mensajes_prog'),where('pacienteId','==',pacId),orderBy('fechaEnvio','desc'))) ]); const enviados=snapEnv.docs.map(d=>({id:d.id,...d.data()})); const prog=snapProg.docs.map(d=>({id:d.id,...d.data()})).filter(m=>m.fechaEnvio>new Date().toISOString()); el.innerHTML=`
💬 Mensaje para ${esc(pac?.nombre||'')}
${prog.length?`
🕐 Programados pendientes (${prog.length})
${prog.map(m=>`
🕐 ${m.fechaEnvio?.replace('T',' ').substring(0,16)}
${esc(m.texto)}
`).join('')}`:''}
💬 Enviados (${enviados.length})
${enviados.slice(0,8).map(m=>`
✓ Enviado${m.categoria?' · '+m.categoria:''}${fmt(m.createdAt)}
${esc(m.texto)}
`).join('')||'
Sin mensajes aún.
'}`; }; window.enviarMsgPac=async function(pacId){ const texto=document.getElementById('mp-texto').value.trim(); if(!texto){toast('Escribe el mensaje',false);return;} const tipo=document.getElementById('mp-tipo').value; const categoria=document.getElementById('mp-cat').value; if(tipo==='prog'){ const fecha=document.getElementById('mp-fecha').value; if(!fecha){toast('Selecciona fecha',false);return;} await addDoc(collection(db,'mensajes_prog'),{pacienteId:pacId,texto,categoria,fechaEnvio:new Date(fecha).toISOString(),createdAt:new Date().toISOString()}); toast('✓ Mensaje programado','ok'); }else{ await addDoc(collection(db,'mensajes'),{pacienteId:pacId,texto,categoria,createdAt:new Date().toISOString()}); toast('✓ Mensaje enviado','ok'); } loadMsgPac(pacId); }; window.cancelarMsgProg=async function(pacId,id){ if(!confirm('¿Cancelar mensaje?'))return; await deleteDoc(doc(db,'mensajes_prog',id));toast('Cancelado');loadMsgPac(pacId); }; // ── EMAILS AUTOMÁTICOS ───────────────────────────────── function _ejsSend(para,nombre,asunto,cuerpo){ const cfg={service:'service_tpmegyj',template:'template_c6zzcjh',pubkey:'yyMglfvH2NNOr4IyD'}; emailjs.init(cfg.pubkey); return emailjs.send(cfg.service,cfg.template,{to_email:para,to_name:nombre,from_name:'Héctor Serrano — Salud & Rendimiento',subject:asunto,message:cuerpo}); } window.enviarEmailPaciente=async function(pacId){ const pac=pacientes.find(p=>p.id===pacId); if(!pac?.email){toast('El paciente no tiene email registrado',false);return;} const texto=document.getElementById('mp-texto')?.value.trim(); if(!texto){toast('Escribe primero el mensaje',false);return;} try{ await _ejsSend(pac.email,pac.nombre,'Mensaje de tu nutricionista',texto); await addDoc(collection(db,'emails_enviados'),{pacienteId:pacId,to:pac.email,asunto:'Mensaje de tu nutricionista',cuerpo:texto,tipo:'manual',createdAt:new Date().toISOString()}); toast('✓ Email enviado a '+pac.email,'ok'); }catch(e){toast('Error al enviar email: '+e.message,false);} }; window.enviarBienvenida=async function(pacId){ const pac=pacientes.find(p=>p.id===pacId); if(!pac?.email){toast('El paciente no tiene email registrado',false);return;} const cuerpo=`Hola ${pac.nombre},\n\nBienvenid@ a Salud & Rendimiento. Estoy encantado de acompañarte en este proceso.\n\nTu código de acceso a la app es: ${pac.codigo}\n\nEntra en https://hectornutricion.pages.dev y usa tu código para ver tu plan personalizado.\n\nCualquier duda, escríbeme directamente.\n\nUn saludo,\nHéctor Serrano`; try{ await _ejsSend(pac.email,pac.nombre,'¡Bienvenid@ a Salud & Rendimiento!',cuerpo); await addDoc(collection(db,'emails_enviados'),{pacienteId:pacId,to:pac.email,asunto:'Bienvenida',tipo:'bienvenida',createdAt:new Date().toISOString()}); toast('✓ Email de bienvenida enviado','ok'); }catch(e){toast('Error: '+e.message,false);} }; window.enviarRecordatorioCita=async function(pacId,fechaCita){ const pac=pacientes.find(p=>p.id===pacId); if(!pac?.email){toast('El paciente no tiene email',false);return;} const cuerpo=`Hola ${pac.nombre},\n\nTe recuerdo que tienes una revisión programada el ${fechaCita}.\n\nRecuerda venir con tus últimos registros de peso y cualquier duda sobre tu plan.\n\nHasta pronto,\nHéctor Serrano`; try{ await _ejsSend(pac.email,pac.nombre,`Recordatorio de cita — ${fechaCita}`,cuerpo); toast('✓ Recordatorio enviado a '+pac.email,'ok'); }catch(e){toast('Error: '+e.message,false);} }; window.enviarResumenSemanal=async function(pacId){ const pac=pacientes.find(p=>p.id===pacId); if(!pac?.email){toast('El paciente no tiene email',false);return;} try{ const [snapReg,snapRevs]=await Promise.all([ getDocs(query(collection(db,`reg_${pacId}`),orderBy('fecha','desc'))), getDocs(query(collection(db,'revisiones'),where('pacienteId','==',pacId),orderBy('fecha','desc'))) ]); const regs=snapReg.docs.map(d=>d.data()); const ultimoReg=regs[0]; const ultimaRev=snapRevs.docs[0]?.data(); const cuerpo=`Hola ${pac.nombre},\n\nAquí tienes tu resumen de esta semana:\n\n${ultimoReg?`Último peso registrado: ${ultimoReg.peso||'—'}kg (${fmt(ultimoReg.fecha)})\n`:''}${ultimaRev?`Última revisión: ${ultimaRev.fecha} — ${ultimaRev.notas||'Sin notas'}\n`:''}\nSigue así, cada pequeño paso cuenta. Si tienes alguna duda o necesitas un ajuste en el plan, no dudes en escribirme.\n\n¡Buen finde!\nHéctor Serrano`; await _ejsSend(pac.email,pac.nombre,'Tu resumen semanal — Salud & Rendimiento',cuerpo); toast('✓ Resumen semanal enviado','ok'); }catch(e){toast('Error: '+e.message,false);} }; // Añadir botones de email en el resumen de la ficha const _origRenderFichaResumen=window.renderFichaResumen; window.renderFichaResumen=function(pac){ _origRenderFichaResumen(pac); const el=document.getElementById('f-resumen-content'); if(pac.email){ el.innerHTML+=`
`; } else { el.innerHTML+=`
⚠️ Sin email — añade uno en la anamnesis para activar emails automáticos
`; } }; // ── CALENDARIO DE CITAS ───────────────────────────────── let _calFecha=new Date(); window.calMes=function(delta){ _calFecha=new Date(_calFecha.getFullYear(),_calFecha.getMonth()+delta,1); renderCalendario(); }; window.renderCalendario=async function(){ const tit=document.getElementById('cal-titulo'); const grid=document.getElementById('cal-grid'); const det=document.getElementById('cal-detalle'); if(!tit||!grid)return; const meses=['Enero','Febrero','Marzo','Abril','Mayo','Junio','Julio','Agosto','Septiembre','Octubre','Noviembre','Diciembre']; const dias=['L','M','X','J','V','S','D']; const año=_calFecha.getFullYear(),mes=_calFecha.getMonth(); tit.textContent=`${meses[mes]} ${año}`; // Cargar revisiones del mes const inicio=new Date(año,mes,1).toISOString().split('T')[0]; const fin=new Date(año,mes+1,0).toISOString().split('T')[0]; const snap=await getDocs(query(collection(db,'revisiones'),where('fecha','>=',inicio),where('fecha','<=',fin))); const revs=snap.docs.map(d=>d.data()); const snapProg=await getDocs(query(collection(db,'revisiones'),where('proxima','>=',inicio),where('proxima','<=',fin))); const prox=snapProg.docs.map(d=>d.data()); // Mapa de fechas con citas const citasPorFecha={}; revs.forEach(r=>{if(r.fecha){if(!citasPorFecha[r.fecha])citasPorFecha[r.fecha]=[];citasPorFecha[r.fecha].push({tipo:'revision',pacId:r.pacienteId});}}); prox.forEach(r=>{if(r.proxima){if(!citasPorFecha[r.proxima])citasPorFecha[r.proxima]=[];citasPorFecha[r.proxima].push({tipo:'proxima',pacId:r.pacienteId});}}); // Primer día del mes (ajustar a Lunes=0) let primerDia=new Date(año,mes,1).getDay(); primerDia=primerDia===0?6:primerDia-1; const totalDias=new Date(año,mes+1,0).getDate(); const hoy=new Date().toISOString().split('T')[0]; let html=dias.map(d=>`
${d}
`).join(''); for(let i=0;i`; for(let d=1;d<=totalDias;d++){ const fecha=`${año}-${String(mes+1).padStart(2,'0')}-${String(d).padStart(2,'0')}`; const citas=citasPorFecha[fecha]||[]; const esHoy=fecha===hoy; const tieneCitas=citas.length>0; html+=`
${d}${tieneCitas?`
`:''}
`; } grid.innerHTML=html; det.innerHTML=''; }; window.mostrarDiaCal=async function(fecha){ const det=document.getElementById('cal-detalle'); const snap=await getDocs(query(collection(db,'revisiones'),where('fecha','==',fecha))); const snapP=await getDocs(query(collection(db,'revisiones'),where('proxima','==',fecha))); const revs=snap.docs.map(d=>({...d.data(),tipo:'Revisión realizada'})); const prox=snapP.docs.map(d=>({...d.data(),tipo:'Próxima cita'})); const todas=[...revs,...prox]; if(!todas.length){det.innerHTML=`
Sin citas el ${fecha}
`;return;} det.innerHTML=`
${fecha}
`+ await Promise.all(todas.map(async r=>{ const pac=pacientes.find(p=>p.id===r.pacienteId); return`
${(pac?.nombre||'?').substring(0,2).toUpperCase()}
${esc(pac?.nombre||r.pacienteId)}
${r.tipo}
${pac?.email?``:''}
`; })).then(arr=>arr.join('')); }; // ── HISTORIAL DE VERSIONES DE DIETA ──────────────────── window.cargarHistDieta=async function(){ const pacId=document.getElementById('hd-pac').value; const el=document.getElementById('hd-content'); if(!pacId){el.innerHTML='
Selecciona un paciente
';return;} el.innerHTML='
Cargando...
'; try{ const snap=await getDocs(query(collection(db,'dietas_hist'),where('pacienteId','==',pacId),orderBy('guardadaEn','desc'))); const hist=snap.docs.map(d=>({id:d.id,...d.data()})); const snapActual=await getDoc(doc(db,'dietas',pacId)); const pac=pacientes.find(p=>p.id===pacId); el.innerHTML=`
${esc(pac?.nombre||'')} — ${hist.length} versión${hist.length!==1?'es':''} guardada${hist.length!==1?'s':''}
${snapActual.exists()?`
Plan actual: ${(snapActual.data().alimentos||[]).length} alimentos · actualizado ${fmt(snapActual.data().updatedAt||snapActual.data().guardadaEn||'')}
`:''} ${hist.length?hist.map(h=>`
Versión ${fmt(h.guardadaEn)}
${(h.alimentos||[]).length} alimentos
${h.notas?`
"${esc(h.notas)}"
`:''}
`).join(''):'
Sin versiones archivadas. Pulsa "Archivar versión actual" para guardar la dieta actual.
'}`; }catch(e){el.innerHTML='
Error al cargar.
';} }; window.archivarDietaActual=async function(pacId){ const snap=await getDoc(doc(db,'dietas',pacId)); if(!snap.exists()||!(snap.data().alimentos?.length)){toast('No hay plan actual para archivar',false);return;} const notas=prompt('Nota para esta versión (opcional):',''); await addDoc(collection(db,'dietas_hist'),{ pacienteId:pacId, alimentos:snap.data().alimentos||[], tipo:snap.data().tipo||'semana', notas:notas||'', guardadaEn:new Date().toISOString() }); toast('✓ Versión archivada','ok');cargarHistDieta(); }; window.restaurarDieta=async function(pacId,histId){ if(!confirm('¿Restaurar esta versión? El plan actual se sobrescribirá.'))return; const snap=await getDoc(doc(db,'dietas_hist',histId)); const h=snap.data(); await setDoc(doc(db,'dietas',pacId),{alimentos:h.alimentos,tipo:h.tipo||'semana',updatedAt:new Date().toISOString()},{merge:true}); toast('✓ Dieta restaurada','ok');cargarHistDieta(); }; window.borrarHistDieta=async function(pacId,id){ if(!confirm('¿Eliminar esta versión?'))return; await deleteDoc(doc(db,'dietas_hist',id));toast('Versión eliminada');cargarHistDieta(); }; // ── LISTA DE LA COMPRA ────────────────────────────────── window.generarListaCompra=async function(){ const pacId=document.getElementById('lc-pac').value; const filtroDia=document.getElementById('lc-dias').value; const el=document.getElementById('lc-content'); if(!pacId){el.innerHTML='
Selecciona un paciente
';return;} el.innerHTML='
Generando lista...
'; try{ const snap=await getDoc(doc(db,'dietas',pacId)); if(!snap.exists()||!(snap.data().alimentos?.length)){el.innerHTML='
Este paciente no tiene plan nutricional guardado
';return;} let alimentos=snap.data().alimentos||[]; if(filtroDia!=='todos')alimentos=alimentos.filter(a=>a.dia===filtroDia); // Extraer ingredientes agrupados por categoría const CATEGORIAS={ proteinas:['pollo','pechuga','ternera','carne','merluza','salmón','atún','huevo','huevos','sardina','gambas','langostinos','bacalao','lubina','dorada'], lacteos:['leche','yogur','queso','requesón','kefir'], cereales:['arroz','pasta','pan','avena','quinoa','maíz','espagueti','macarrón','tortilla de trigo'], frutas:['manzana','plátano','naranja','pera','kiwi','fresa','uva','melón','sandía','melocotón','mandarina'], verduras:['tomate','lechuga','espinaca','zanahoria','pimiento','cebolla','ajo','brócoli','calabacín','berenjena','pepino'], grasas:['aceite','aguacate','almendra','nuez','anacardo'], otros:[] }; const grupos={proteinas:[],lacteos:[],cereales:[],frutas:[],verduras:[],grasas:[],otros:[]}; alimentos.forEach(a=>{ const nombre=(a.alimento||'').toLowerCase(); let asignado=false; for(const [cat,palabras] of Object.entries(CATEGORIAS)){ if(cat==='otros')continue; if(palabras.some(p=>nombre.includes(p))){ grupos[cat].push(a.alimento); asignado=true;break; } } if(!asignado)grupos.otros.push(a.alimento); }); const labels={proteinas:'🥩 Proteínas',lacteos:'🥛 Lácteos',cereales:'🌾 Cereales y carbohidratos',frutas:'🍎 Frutas',verduras:'🥦 Verduras',grasas:'🥑 Grasas saludables',otros:'🛒 Otros'}; const pac=pacientes.find(p=>p.id===pacId); let html=`
${esc(pac?.nombre||'')} — ${filtroDia==='todos'?'Semana completa':filtroDia}
`; let listaTxt='🛒 Lista de la compra\n\n'; Object.entries(grupos).forEach(([cat,items])=>{ if(!items.length)return; const uniq=[...new Set(items)]; html+=`
${labels[cat]}
`; listaTxt+=labels[cat]+'\n'; html+=uniq.map(i=>`
${esc(i)}
`).join(''); listaTxt+=uniq.map(i=>'□ '+i).join('\n')+'\n\n'; html+='
'; }); window._listaTxt=listaTxt; el.innerHTML=html; }catch(e){el.innerHTML='
Error al generar lista.
';} }; window.copiarListaCompra=function(){ if(!window._listaTxt)return; navigator.clipboard.writeText(window._listaTxt).then(()=>toast('✓ Lista copiada al portapapeles','ok')).catch(()=>toast('Error al copiar',false)); }; // ── INTERCAMBIOS DE ALIMENTOS ────────────────────────── let _ixActual=null,_ixModo='pac',_ixTol=10; window.abrirIntercambio=async function(alimento,modo){ _ixActual=alimento;_ixModo=modo;_ixTol=10; document.getElementById('modal-intercambio').style.display='flex'; document.getElementById('ix-original-nombre').textContent=alimento.alimento||'—'; _ixRenderBadges('ix-original-macros',alimento); document.querySelectorAll('.ix-tol-btn').forEach((b,i)=>{b.style.background=i===0?'var(--brand)':'none';b.style.color=i===0?'white':'var(--text2)';}); if(!alimentosCache.length){ document.getElementById('ix-resultados').innerHTML='
Cargando base de datos...
'; const {getDocs:gd,collection:col}=await import('https://www.gstatic.com/firebasejs/10.12.0/firebase-firestore.js'); const snap=await gd(col(db,'alimentos_db')); alimentosCache=snap.docs.map(d=>({id:d.id,...d.data()})); } _ixBuscar(); }; window.setIxTol=function(pct,btn){ _ixTol=pct; document.querySelectorAll('.ix-tol-btn').forEach(b=>{b.style.background='none';b.style.color='var(--text2)';}); btn.style.background='var(--brand)';btn.style.color='white'; _ixBuscar(); }; function _ixBuscar(){ const a=_ixActual,tol=_ixTol/100; const el=document.getElementById('ix-resultados'),cnt=document.getElementById('ix-conteo'); const kcal=parseFloat(a.kcal)||0,prot=parseFloat(a.proteinas)||0,carb=parseFloat(a.carbohidratos)||0,gras=parseFloat(a.grasas)||0; if(!kcal&&!prot){ const palabras=(a.alimento||'').toLowerCase().split(' ').filter(p=>p.length>3); const sim=alimentosCache.filter(x=>palabras.some(p=>(x.nombre||'').toLowerCase().includes(p))).slice(0,6); cnt.textContent=sim.length?sim.length+' similares':''; el.innerHTML=sim.length?`
Sin macros de referencia — resultados por nombre similar
`+sim.map(x=>_ixCard(x,100)).join(''):'
Sin resultados
';return; } const alts=alimentosCache.filter(x=>{ if(!x.kcal)return false; const f=kcal/(x.kcal||1);const g=f*100; if(g<10||g>600)return false; if(prot>0&&Math.abs((x.proteinas||0)*f-prot)/prot>tol)return false; const t2=tol*1.5; if(carb>5&&Math.abs((x.carbohidratos||0)*f-carb)/carb>t2)return false; if(gras>3&&Math.abs((x.grasas||0)*f-gras)/gras>t2)return false; return true; }).sort((x,y)=>{ const fx=kcal/(x.kcal||1),fy=kcal/(y.kcal||1); return Math.abs((x.proteinas||0)*fx-prot)-Math.abs((y.proteinas||0)*fy-prot); }).slice(0,8); cnt.textContent=alts.length?alts.length+' alternativas encontradas':''; el.innerHTML=alts.length?alts.map(x=>{ const f=kcal/(x.kcal||1);return _ixCard(x,Math.round(f*100)); }).join(''):`
Sin alternativas con ±${_ixTol}%.
Prueba aumentando la tolerancia.
`; } function _ixCard(item,gramos){ const f=gramos/100; const pa=Math.round((item.proteinas||0)*f*10)/10,ca=Math.round((item.carbohidratos||0)*f*10)/10,ga=Math.round((item.grasas||0)*f*10)/10,ka=Math.round((item.kcal||0)*f); const nuevo={id:Date.now().toString()+Math.random().toString(36).slice(2),alimento:item.nombre+' '+gramos+'g',kcal:ka,proteinas:pa,carbohidratos:ca,grasas:ga,dia:_ixActual.dia,momento:_ixActual.momento}; return`
${esc(item.nombre)}
${gramos}g
${ka} kcal
P: ${pa}gC: ${ca}gG: ${ga}g
Pulsa para sustituir →
`; } function _ixRenderBadges(elId,a){ const el=document.getElementById(elId);if(!el)return; const m=[[a.kcal,'kcal','var(--brand)'],[a.proteinas,'Prot','#2563eb'],[a.carbohidratos,'CH','#d97706'],[a.grasas,'Grasas','#dc2626']].filter(([v])=>v); el.innerHTML=m.map(([v,l,c])=>`${l}: ${v}${l==='kcal'?' kcal':'g'}`).join('')||'Sin macros'; } window.selIx=async function(nuevoAlimento){ if(_ixModo==='adm'){ const idx=dietaB.findIndex(a=>a.id===_ixActual.id); if(idx!==-1)dietaB[idx]={...nuevoAlimento,id:_ixActual.id};else dietaB.push(nuevoAlimento); renderDietaPrev();toast('✅ Sustituido por '+nuevoAlimento.alimento,'ok'); }else{ try{ const snap=await getDoc(doc(db,'dietas',cu.id)); if(!snap.exists())return; const alims=snap.data().alimentos||[]; const idx=alims.findIndex(a=>a.dia===_ixActual.dia&&a.momento===_ixActual.momento&&a.alimento===_ixActual.alimento); if(idx!==-1){alims[idx]={...alims[idx],...nuevoAlimento};await setDoc(doc(db,'dietas',cu.id),{alimentos:alims,updatedAt:new Date().toISOString()},{merge:true});toast('✅ '+nuevoAlimento.alimento,'ok');loadDietaPac();} }catch(e){toast('Error al guardar',false);} } document.getElementById('modal-intercambio').style.display='none';_ixActual=null; }; window.cerrarIx=function(){document.getElementById('modal-intercambio').style.display='none';_ixActual=null;}; // ── COPIAR DIETA ENTRE PACIENTES ─────────────────────── window.abrirCopiarDieta=function(){ const destId=document.getElementById('d-pac').value; const opts=''+pacientes.map(p=>``).join(''); document.getElementById('cd-origen').innerHTML=opts; document.getElementById('cd-destino').innerHTML=opts; if(destId)document.getElementById('cd-destino').value=destId; document.getElementById('cd-preview-origen').style.display='none'; document.getElementById('cd-sin-dieta').style.display='none'; document.getElementById('cd-btn-cargar').disabled=true; document.getElementById('modal-copiar-dieta').style.display='flex'; }; document.addEventListener('change',async function(e){ if(e.target.id!=='cd-origen')return; const origenId=e.target.value; const prev=document.getElementById('cd-preview-origen'),sinD=document.getElementById('cd-sin-dieta'),btn=document.getElementById('cd-btn-cargar'); prev.style.display='none';sinD.style.display='none';btn.disabled=true; if(!origenId)return; try{ const snap=await getDoc(doc(db,'dietas',origenId)); if(!snap.exists()||!(snap.data().alimentos||[]).length){sinD.style.display='block';return;} const alims=snap.data().alimentos||[]; const byDia={};alims.forEach(a=>{const d=a.dia||'—';if(!byDia[d])byDia[d]={};const m=a.momento||'—';if(!byDia[d][m])byDia[d][m]=[];byDia[d][m].push(a);}); const dias=Object.keys(byDia).length;const total=alims.reduce((s,a)=>s+(parseInt(a.kcal)||0),0); document.getElementById('cd-preview-contenido').innerHTML=`
${alims.length} alimentos · ${dias} día${dias>1?'s':''} · ~${Math.round(total/Math.max(dias,1))} kcal/día
`+ Object.entries(byDia).map(([d,m])=>`
${d}
${Object.entries(m).map(([mom,its])=>`
${mom}: ${its.map(i=>esc(i.alimento)).join(', ')}
`).join('')}
`).join(''); prev.style.display='block';_cdCheck(); }catch(e){toast('Error cargando dieta',false);} }); document.addEventListener('change',function(e){if(e.target.id==='cd-destino')_cdCheck();}); function _cdCheck(){ const o=document.getElementById('cd-origen').value,d=document.getElementById('cd-destino').value; const pv=document.getElementById('cd-preview-origen').style.display!=='none'; const btn=document.getElementById('cd-btn-cargar'); if(o&&d&&o===d){btn.disabled=true;toast('El origen y destino no pueden ser iguales',false);return;} btn.disabled=!(o&&d&&pv); } window.ejecutarCopiarDieta=async function(){ const o=document.getElementById('cd-origen').value,d=document.getElementById('cd-destino').value; if(!o||!d)return; const snap=await getDoc(doc(db,'dietas',o)); if(!snap.exists()){toast('No se encontró la dieta',false);return;} const alims=snap.data().alimentos||[]; dietaB=alims.map(a=>({...a,id:Date.now().toString()+Math.random().toString(36).slice(2)})); const dPac=document.getElementById('d-pac');if(dPac){dPac.value=d;dPac.dispatchEvent(new Event('change'));} document.getElementById('modal-copiar-dieta').style.display='none'; renderDietaPrev(); const po=pacientes.find(p=>p.id===o),pd=pacientes.find(p=>p.id===d); toast(`✅ Dieta de ${po?.nombre||''} cargada para ${pd?.nombre||''} `, 'ok'); setTimeout(()=>{const pv=document.getElementById('d-preview');if(pv)pv.scrollIntoView({behavior:'smooth',block:'start'});},200); }; // ── VALORAR RECETAS ──────────────────────────────────── window.valorarReceta=async function(recId,estrellas){ if(!cu){toast('Debes iniciar sesión',false);return;} try{ const snap=await getDoc(doc(db,'recetas',recId)); if(!snap.exists())return; const data=snap.data(); const valoraciones=data.valoraciones||[]; // Guardar valoración del paciente (una por paciente) const idx=valoraciones.findIndex(v=>v.pacId===cu.id); if(idx!==-1)valoraciones[idx]={pacId:cu.id,val:estrellas}; else valoraciones.push({pacId:cu.id,val:estrellas}); await updateDoc(doc(db,'recetas',recId),{valoraciones:valoraciones.map(v=>v.val)}); recetas=recetas.map(r=>r.id===recId?{...r,valoraciones:valoraciones.map(v=>v.val)}:r); renderRecetasPac(); toast(`✓ Valorada con ${estrellas} estrella${estrellas>1?'s':''} `,'ok'); }catch(e){toast('Error al valorar',false);} };