/* Plans — calendario semanal real (parseado desde el markdown del LLM) + * nutrición (todavía MOCK) + form de generación. * * Antes el calendario era 100% MOCK (`MOCK.planWeek`). Ahora: * 1. `screen-plans.jsx` lista los planes guardados (`/plans/`). * 2. Si hay alguno tipo "training", carga el detalle (`/plans/{id}`), * pasa `plan.content.answer` (markdown) por `parseTrainingPlan` * (definido en `utils-plan-parser.jsx`) y dibuja el calendario. * 3. Si el parser devuelve `null` (LLM no respetó el formato esperado), * mostramos el markdown crudo con `MarkdownRender` y un eyebrow * explicativo. La opción "pedir reformat al coach" queda como TODO. * 4. El generador (`NewPlanForm`) post-procesa la respuesta inmediata y * muestra preview con el calendario antes de guardar; si falla parser, * muestra el markdown + botón "Reintentar" con instrucciones reforzadas. * * MOCK que sigue: * - Pantalla "Nutrición" entera (`NutritionPlan`): no estamos parseando * `/agents/plan/nutrition` aún (formato del LLM nutricionista no * consistente; ver README §"MOCK que siguen pendientes"). * - Stats agregadas (`carga sem.`, `volumen`, `25%`) del header del plan * activo: el parser todavía no calcula esto (lo añadimos cuando el LLM * devuelva `load` por sesión de forma confiable). Ahora derivamos * `weeks count` y `sessions/sem promedio` de la estructura parseada. */ const Plans = ({ density, dataState, setRoute, inDemo }) => { const [view, setView] = React.useState("training"); const [selectedSession, setSelectedSession] = React.useState(null); // null = "no cargado", [] = "cargado vacío", [{...}] = lista. const [plans, setPlans] = React.useState(null); const [activePlan, setActivePlan] = React.useState(null); // detalle (con content) const [loadError, setLoadError] = React.useState(null); // Cargar lista de planes y detalle del primero "training". React.useEffect(() => { if (inDemo) return; let alive = true; (async () => { try { const res = await API.listPlans(); const items = Array.isArray(res) ? res : (res?.plans || res?.items || []); if (!alive) return; setPlans(items); const trainingPlan = items.find((p) => (p.type || p.plan_type) === "training"); if (trainingPlan) { try { const detail = await API.getPlan(trainingPlan.id); if (alive) setActivePlan(detail); } catch (e) { if (alive && e.status !== 401) setLoadError(e.message); } } } catch (e) { if (e.status === 401 || e.status === 403 || e.status === 422) return; if (alive) setLoadError(e.message); } })(); return () => { alive = false; }; }, [inDemo]); return (