// Atlas Builder v7 — muscle priority system

// ── Design tokens ─────────────────────────────────────────────────────────────
const BD = {
  page:    '#060D18',
  panel:   '#0A1422',
  card:    '#0E1A2C',
  hov:     '#132134',
  border:  'rgba(255,255,255,0.07)',
  text:    '#E8EDF8',
  sub:     'rgba(232,237,248,0.55)',
  muted:   'rgba(232,237,248,0.55)',
  blue:    '#3B82F6',
  blueDim: 'rgba(59,130,246,0.18)',
  green:   '#22C55E',
  amber:   '#F59E0B',
  red:     '#EF4444',
};

// ── 19-zone muscle map ────────────────────────────────────────────────────────
// view: 'front' | 'back' | 'both'
// group: functional category for grouping in UI
const MUSCLES = {
  // ─ Front ─
  pecho:      { label: 'Pecho',              view: 'front', group: 'torso'   },
  delt_ant:   { label: 'Deltoides anterior', view: 'front', group: 'hombros' },
  delt_lat:   { label: 'Deltoides lateral',  view: 'both',  group: 'hombros' },
  biceps:     { label: 'Bíceps',             view: 'front', group: 'brazos'  },
  antebrazo:  { label: 'Antebrazo',          view: 'both',  group: 'brazos'  },
  core:       { label: 'Abdominales',        view: 'both',  group: 'core'    },
  oblicuos:   { label: 'Oblicuos',           view: 'front', group: 'core'    },
  cuadriceps: { label: 'Cuádriceps',         view: 'front', group: 'piernas' },
  aductores:  { label: 'Aductores',          view: 'front', group: 'piernas' },
  gemelos:    { label: 'Gemelos',            view: 'both',  group: 'piernas' },
  tibial:     { label: 'Tibial anterior',    view: 'front', group: 'piernas' },
  // ─ Back ─
  trapecio:   { label: 'Trapecio',           view: 'back',  group: 'torso'   },
  dorsal:     { label: 'Dorsal ancho',       view: 'back',  group: 'torso'   },
  delt_post:  { label: 'Deltoides posterior',view: 'back',  group: 'hombros' },
  triceps:    { label: 'Tríceps',            view: 'back',  group: 'brazos'  },
  lumbar:     { label: 'Lumbar',             view: 'back',  group: 'core'    },
  gluteos:    { label: 'Glúteos',            view: 'back',  group: 'gluteos' },
  isquio:     { label: 'Isquiotibiales',     view: 'back',  group: 'piernas' },
  abductores: { label: 'Abductores',         view: 'back',  group: 'piernas' },
  erectores:  { label: 'Erectores espinales',view: 'back',  group: 'core'    },
};

// Two-level muscle navigation: six big groups → the specific muscles inside.
// Built from the existing MUSCLES ids (no new categories). Every muscle above
// belongs to exactly one group; all 20 are covered.
const MUSCLE_GROUPS = [
  { id: 'g_pecho',   label: 'Pecho',          ids: ['pecho'] },
  { id: 'g_espalda', label: 'Espalda',        ids: ['dorsal', 'trapecio', 'lumbar', 'erectores'] },
  { id: 'g_hombro',  label: 'Hombro',         ids: ['delt_ant', 'delt_lat', 'delt_post'] },
  { id: 'g_brazo',   label: 'Brazo',          ids: ['biceps', 'triceps', 'antebrazo'] },
  { id: 'g_pierna',  label: 'Pierna',         ids: ['cuadriceps', 'isquio', 'gluteos', 'gemelos', 'aductores', 'abductores', 'tibial'] },
  { id: 'g_core',    label: 'Core / Abdomen', ids: ['core', 'oblicuos'] },
];

// ── Body-map muscle mapping ──────────────────────────────────────────────────
// Map react-muscle-highlighter slugs (window.MUSCLE_MAP) → Atlas muscle ids,
// per view. The library merges the three deltoid heads into one "deltoids"
// region, so the map selects the head that dominates each view (anterior/
// lateral from the front, posterior from the back); the other heads, plus
// erectores and abductores, stay reachable from the two-level chip picker.
const MMAP_FRONT = {
  chest:'pecho', obliques:'oblicuos', abs:'core', biceps:'biceps',
  triceps:'triceps', trapezius:'trapecio', deltoids:'delt_lat',
  adductors:'aductores', quadriceps:'cuadriceps', tibialis:'tibial',
  calves:'gemelos', forearm:'antebrazo',
};
const MMAP_BACK = {
  trapezius:'trapecio', deltoids:'delt_post', triceps:'triceps',
  'upper-back':'dorsal', 'lower-back':'lumbar', forearm:'antebrazo',
  gluteal:'gluteos', adductors:'aductores', hamstring:'isquio', calves:'gemelos',
};
// Smaller regions get a slight nearest-center bias so a big neighbour can't
// steal their taps once every catch area is enlarged.
const MM_SMALL = new Set(['antebrazo','tibial','gemelos','biceps','triceps','delt_lat','delt_post','oblicuos','aductores']);

// ── Science data (Israetel MEV/MAV/MRV — sets per week) ──────────────────────
const MUSCLE_SCIENCE = {
  pecho:      { mev: 10, mav: 16, mrv: 22, freq: 2, sti: 0.80 },
  delt_ant:   { mev: 4,  mav: 8,  mrv: 14, freq: 2, sti: 0.70 },
  delt_lat:   { mev: 8,  mav: 14, mrv: 20, freq: 2, sti: 0.75 },
  delt_post:  { mev: 10, mav: 16, mrv: 22, freq: 2, sti: 0.70 },
  biceps:     { mev: 8,  mav: 14, mrv: 20, freq: 2, sti: 0.65 },
  antebrazo:  { mev: 6,  mav: 12, mrv: 18, freq: 3, sti: 0.60 },
  triceps:    { mev: 8,  mav: 14, mrv: 20, freq: 2, sti: 0.75 },
  core:       { mev: 8,  mav: 16, mrv: 24, freq: 3, sti: 0.65 },
  oblicuos:   { mev: 6,  mav: 12, mrv: 18, freq: 2, sti: 0.60 },
  trapecio:   { mev: 8,  mav: 14, mrv: 20, freq: 2, sti: 0.70 },
  dorsal:     { mev: 10, mav: 16, mrv: 22, freq: 2, sti: 0.80 },
  lumbar:     { mev: 6,  mav: 10, mrv: 14, freq: 2, sti: 0.70 },
  cuadriceps: { mev: 8,  mav: 14, mrv: 20, freq: 2, sti: 0.80 },
  aductores:  { mev: 6,  mav: 10, mrv: 16, freq: 2, sti: 0.60 },
  gluteos:    { mev: 8,  mav: 16, mrv: 24, freq: 2, sti: 0.75 },
  isquio:     { mev: 8,  mav: 14, mrv: 20, freq: 2, sti: 0.75 },
  gemelos:    { mev: 10, mav: 16, mrv: 22, freq: 3, sti: 0.60 },
  abductores: { mev: 6,  mav: 10, mrv: 16, freq: 2, sti: 0.60 },
  tibial:     { mev: 4,  mav: 8,  mrv: 12, freq: 3, sti: 0.50 },
  erectores:  { mev: 6,  mav: 10, mrv: 14, freq: 2, sti: 0.70 },
};

// ── Pares antagonistas para análisis de equilibrio ────────────────────────────
const BALANCE_PAIRS = [
  {
    id: 'push-pull',
    labelA: 'Pecho + Empuje', idsA: ['pecho', 'delt_ant', 'triceps'],
    labelB: 'Espalda + Tracción', idsB: ['dorsal', 'trapecio', 'delt_post', 'biceps'],
    maxRatio: 1.35,
    advice: 'Dominio de empuje sobre tracción. Añade remos, jalones o face pulls. La ratio ideal es ~1:1 o más tracción.',
  },
  {
    id: 'quad-post',
    labelA: 'Cuádriceps', idsA: ['cuadriceps'],
    labelB: 'Isquio + Glúteos', idsB: ['isquio', 'gluteos'],
    maxRatio: 2.2,
    advice: 'Cuádriceps mucho más trabajado que la cadena posterior. Incluye sentadillas rumanas, peso muerto y hip thrust para proteger la rodilla.',
  },
  {
    id: 'biceps-triceps',
    labelA: 'Bíceps', idsA: ['biceps'],
    labelB: 'Tríceps', idsB: ['triceps'],
    maxRatio: 2.5, minRatio: 0.4,
    adviceHigh: 'Mucho más bíceps que tríceps. El tríceps constituye 2/3 del volumen del brazo.',
    adviceLow: 'Mucho más tríceps que bíceps. Añade trabajo de curls para equilibrar.',
  },
  {
    id: 'delt-ant-post',
    labelA: 'Deltoides anterior', idsA: ['delt_ant'],
    labelB: 'Deltoides posterior', idsB: ['delt_post'],
    maxRatio: 1.8,
    advice: 'Deltoides anterior dominante. El deltoides posterior —el más olvidado— es clave para la salud del manguito rotador.',
  },
];

// ── Equipment display ─────────────────────────────────────────────────────────
const EQ_META = {
  barra:      { label: 'Barra',      color: '#F59E0B', bg: 'rgba(245,158,11,0.14)' },
  mancuernas: { label: 'Mancuernas', color: '#3B82F6', bg: 'rgba(59,130,246,0.14)' },
  bodyweight: { label: 'Bodyweight', color: '#22C55E', bg: 'rgba(34,197,94,0.14)'  },
  polea:      { label: 'Polea',      color: '#A855F7', bg: 'rgba(168,85,247,0.14)' },
  máquina:    { label: 'Máquina',    color: '#94A3B8', bg: 'rgba(148,163,184,0.14)'},
  kettlebell: { label: 'Kettlebell', color: '#EF4444', bg: 'rgba(239,68,68,0.14)'  },
  disco:      { label: 'Disco',      color: '#3B82F6', bg: 'rgba(59,130,246,0.14)' },
  multipower: { label: 'Multipower', color: '#94A3B8', bg: 'rgba(148,163,184,0.14)'},
  'barra-hexagonal': { label: 'Barra hexag.', color: '#F59E0B', bg: 'rgba(245,158,11,0.14)' },
  'barra-ez': { label: 'Barra EZ',   color: '#F59E0B', bg: 'rgba(245,158,11,0.14)' },
  landmine:   { label: 'Landmine',   color: '#F59E0B', bg: 'rgba(245,158,11,0.14)' },
  suspension: { label: 'Suspensión', color: '#22C55E', bg: 'rgba(34,197,94,0.14)'  },
};

const LVL_COLOR = { principiante:'#22C55E', intermedio:'#F59E0B', avanzado:'#EF4444' };
const LVL_LABEL = { principiante:'Básico', intermedio:'Intermedio', avanzado:'Avanzado' };

// ── Helpers ───────────────────────────────────────────────────────────────────
function exEquipment(ex) {
  const eq = ex.equipment;
  if (!eq) return [];
  return Array.isArray(eq) ? eq : [eq];
}

// Maps the DB `muscleGroup` (campo nuevo, separado del patrón) al vocabulario
// granular del Builder/mapa corporal.
const BUILDER_MG_MAP = { espalda: 'dorsal', 'espalda-baja': 'erectores', isquios: 'isquio' };

function exGroup(ex) {
  if (ex.group) return ex.group;
  if (ex.muscleGroup) return BUILDER_MG_MAP[ex.muscleGroup] || ex.muscleGroup;
  const p   = ex.pattern || '';
  // muscles can be {primary:[...]} (DB format) or a plain array (log format)
  const pm0 = ((Array.isArray(ex.muscles) ? ex.muscles[0] : ex.muscles?.primary?.[0]) || '').toLowerCase();

  if (p === 'empuje-horizontal') {
    if (pm0.includes('tríceps') || pm0.includes('triceps')) return 'triceps';
    return 'pecho';
  }
  if (p === 'empuje-vertical') {
    if (pm0.includes('medial') || pm0.includes('lateral')) return 'delt_lat';
    if (pm0.includes('tríceps') || pm0.includes('triceps')) return 'triceps';
    return 'delt_ant';
  }
  if (p === 'traccion-horizontal') {
    if (pm0.includes('post')) return 'delt_post';
    if (pm0.includes('trapecio')) return 'trapecio';
    return 'dorsal';
  }
  if (p === 'traccion-vertical') {
    if (pm0.includes('bíceps') || pm0.includes('braquial')) return 'biceps';
    return 'dorsal';
  }
  if (p === 'sentadilla') return 'cuadriceps';
  if (p === 'bisagra')    return 'isquio';
  if (p === 'aislamiento-pantorrilla') return 'gemelos';
  if (p.startsWith('core')) return 'core';
  return 'core';
}

// Lower-cased primary muscle names of an exercise (DB format {primary:[...]}).
function exPrimaryNames(ex) {
  return (ex.muscles?.primary || []).map(m => (m || '').toLowerCase());
}
function primaryMatches(ex, re) {
  return exPrimaryNames(ex).some(m => re.test(m));
}

function exsForMuscle(id, all) {
  const g = id;
  // Special cases with multiple patterns
  if (id === 'dorsal')     return all.filter(e => exGroup(e) === 'dorsal');
  if (id === 'gluteos')    return all.filter(e => exGroup(e) === 'gluteos');
  if (id === 'isquio')     return all.filter(e => exGroup(e) === 'isquio' && e.group !== 'gluteos');
  // Muscles the pattern/group heuristic never routes to — resolve them by the
  // exercise's own primary muscle names instead (see coverage audit). Without
  // these, tapping oblicuos / erectores / lumbar returned 0 exercises.
  if (id === 'oblicuos')   return all.filter(e => primaryMatches(e, /oblicu/));
  if (id === 'erectores')  return all.filter(e => primaryMatches(e, /erectores/));
  if (id === 'lumbar')     return all.filter(e => primaryMatches(e, /lumbar|erectores/));
  // Core = rectus/transverse/flexion work; keep oblique-led moves out of it so
  // they live under "oblicuos" rather than cluttering "Abdominales".
  if (id === 'core')       return all.filter(e => exGroup(e) === 'core' && e.group !== 'oblicuos' && !(exPrimaryNames(e)[0] || '').startsWith('oblicu'));
  if (id === 'cuadriceps') return all.filter(e => exGroup(e) === 'cuadriceps');
  return all.filter(e => exGroup(e) === g);
}

function sessionDuration(workout) {
  const sets = workout.reduce((s, e) => s + e.sets.length, 0);
  return Math.round((workout.length ? 5 : 0) + sets * 2.5);
}

function useWidth() {
  const [w, setW] = React.useState(window.innerWidth);
  React.useEffect(() => {
    const h = () => setW(window.innerWidth);
    window.addEventListener('resize', h);
    return () => window.removeEventListener('resize', h);
  }, []);
  return w;
}

// ── Weekly volume helper ──────────────────────────────────────────────────────
function setsThisWeek(muscleId, log) {
  const now  = Date.now();
  const week = 7 * 24 * 60 * 60 * 1000;
  let total  = 0;
  for (const session of (log || [])) {
    if (now - (session.dateTs || 0) > week) continue;
    for (const ex of (session.exercises || [])) {
      if (ex.group === muscleId || exGroup(ex) === muscleId) {
        total += (ex.sets || []).length;
      }
    }
  }
  return total;
}

// ── Mapa corporal anatómico (react-muscle-highlighter, MIT) ──────────────────
// Renders the licensed anatomical SVG paths (window.MUSCLE_MAP). Muscle regions
// are display-only; tap resolution happens at the SVG root by nearest muscle
// centre, with centres measured from the real shapes (getBBox) so touch targets
// match the anatomy and stay reliable for small muscles on mobile.
function BodyMap({ view, selected, onPick, priorities }) {
  const [hov, setHov] = React.useState(null);
  const [pressing, setPressing] = React.useState(null);
  const svgRef = React.useRef(null);
  const centersRef = React.useRef([]);

  const data    = typeof window !== 'undefined' ? window.MUSCLE_MAP : null;
  const slugMap = view === 'front' ? MMAP_FRONT : MMAP_BACK;
  const parts   = data ? (view === 'front' ? data.front : data.back) : [];
  const viewBox = data ? (view === 'front' ? data.viewBoxFront : data.viewBoxBack) : '0 0 724 1448';
  const outline = data ? (view === 'front' ? data.outlineFront : data.outlineBack) : '';
  const originX = view === 'front' ? 0 : 724;

  // Measure a centre for every interactive muscle side from its rendered shape,
  // so hit-testing tracks the anatomy without any hand-coded coordinates.
  React.useLayoutEffect(() => {
    const svg = svgRef.current;
    if (!svg) return;
    const list = [];
    svg.querySelectorAll('[data-mid]').forEach(el => {
      let bb;
      try { bb = el.getBBox(); } catch (_) { return; }
      if (!bb || (!bb.width && !bb.height)) return;
      const id = el.getAttribute('data-mid');
      list.push({ id, cx: bb.x + bb.width / 2, cy: bb.y + bb.height / 2, small: MM_SMALL.has(id) });
    });
    centersRef.current = list;
  }, [view]);

  // Map a client point to viewBox coords, then to the nearest muscle centre.
  function pickAt(clientX, clientY) {
    const svg = svgRef.current;
    if (!svg || !svg.getScreenCTM) return null;
    const ctm = svg.getScreenCTM();
    if (!ctm) return null;
    const pt = svg.createSVGPoint(); pt.x = clientX; pt.y = clientY;
    const p = pt.matrixTransform(ctm.inverse());
    let best = null, bestEff = Infinity, bestRaw = Infinity;
    for (const c of centersRef.current) {
      const raw = Math.hypot(p.x - c.cx, p.y - c.cy);
      const eff = raw * (c.small ? 0.8 : 1);   // small-muscle bias
      if (eff < bestEff) { bestEff = eff; best = c.id; bestRaw = raw; }
    }
    return (best && bestRaw <= 320) ? best : null;   // ignore taps far off the body
  }

  const B = '#3B82F6';

  // Fill / glow for a muscle id given the current selection & priority state.
  function styleFor(id) {
    const sel = selected === id, press = pressing === id, hovr = hov === id;
    const prio = priorities[id] === 'priority';
    const maint = priorities[id] === 'maintain';
    const red = priorities[id] === 'reducir';
    if (sel || press) return { fill: B,        opacity: 1,    filter: 'url(#mm-sel)' };
    if (hovr)         return { fill: B,        opacity: 0.9,  filter: 'url(#mm-hov)' };
    if (prio)         return { fill: B,        opacity: 0.78, filter: 'url(#mm-prio)' };
    if (maint)        return { fill: '#6ea8fe', opacity: 0.55 };
    if (red)          return { fill: '#6ea8fe', opacity: 0.34 };
    return { fill: '#41506e', opacity: 0.92 };   // idle sculpted muscle
  }

  return (
    <div>
      <svg ref={svgRef} viewBox={viewBox} preserveAspectRatio="xMidYMid meet"
        onClick={e => { const id = pickAt(e.clientX, e.clientY); if (id) onPick(id); }}
        onPointerDown={e => { const id = pickAt(e.clientX, e.clientY); setPressing(id); setHov(id); }}
        onPointerMove={e => { if (e.pointerType === 'mouse') setHov(pickAt(e.clientX, e.clientY)); }}
        onPointerUp={() => setPressing(null)}
        onPointerCancel={() => setPressing(null)}
        onPointerLeave={() => { setPressing(null); setHov(null); }}
        style={{ width:'100%', maxWidth:360, display:'block', margin:'0 auto', overflow:'visible', cursor:'pointer', touchAction:'manipulation' }}>
        {/* Transparent capture over the whole view so any tap resolves */}
        <rect x={originX} y="0" width="724" height="1448" fill="#000" fillOpacity="0" />
        <defs>
          <linearGradient id="mm-body" x1="0" y1="0" x2="0" y2="1">
            <stop offset="0%"   stopColor="#232c44" />
            <stop offset="100%" stopColor="#121828" />
          </linearGradient>
          <filter id="mm-hov"  x="-20%" y="-20%" width="140%" height="140%">
            <feGaussianBlur in="SourceGraphic" stdDeviation="6" result="b" />
            <feMerge><feMergeNode in="b"/><feMergeNode in="SourceGraphic"/></feMerge>
          </filter>
          <filter id="mm-sel"  x="-30%" y="-30%" width="160%" height="160%">
            <feGaussianBlur in="SourceGraphic" stdDeviation="12" result="b" />
            <feMerge><feMergeNode in="b"/><feMergeNode in="SourceGraphic"/></feMerge>
          </filter>
          <filter id="mm-prio" x="-15%" y="-15%" width="130%" height="130%">
            <feGaussianBlur in="SourceGraphic" stdDeviation="5" result="b" />
            <feMerge><feMergeNode in="b"/><feMergeNode in="SourceGraphic"/></feMerge>
          </filter>
        </defs>

        {/* Body silhouette outline (fills the figure under the muscles) */}
        {outline && (
          <path d={outline} fill="url(#mm-body)"
            stroke="rgba(120,150,210,0.22)" strokeWidth="2"
            style={{ pointerEvents:'none', vectorEffect:'non-scaling-stroke' }} />
        )}

        {/* Muscle & body-part regions */}
        {parts.map((part, pi) => {
          const id = slugMap[part.slug];
          const path = part.path || {};
          const sides = ['common', 'left', 'right'].filter(s => Array.isArray(path[s]) && path[s].length);
          if (!id) {
            // Non-muscle body part (head, hands, feet…) — inert body fill.
            return (
              <g key={pi} fill="#2b3450" opacity="0.9" style={{ pointerEvents:'none' }}>
                {sides.flatMap(s => path[s].map((d, i) => <path key={s + i} d={d} />))}
              </g>
            );
          }
          const st = styleFor(id);
          return sides.map(s => (
            <g key={pi + s} data-mid={id}
              fill={st.fill} opacity={st.opacity} filter={st.filter}
              stroke="rgba(10,16,30,0.55)" strokeWidth="1"
              style={{ pointerEvents:'none', transition:'fill .16s ease, opacity .16s ease' }}>
              {path[s].map((d, i) => <path key={i} d={d} />)}
            </g>
          ));
        })}

        {/* Floating label */}
        {(hov || selected) && (
          <text x={originX + 362} y="1436" textAnchor="middle"
            fill="rgba(232,237,248,0.6)" fontSize="34"
            fontFamily="Inter,system-ui" fontWeight="700" letterSpacing="4">
            {MUSCLES[hov || selected]?.label.toUpperCase()}
          </text>
        )}
      </svg>

      <div style={{ display:'flex', gap:'8px 14px', marginTop:10, justifyContent:'center', flexWrap:'wrap' }}>
        {[['#41506e','Sin prioridad'],['rgba(59,130,246,0.85)','Prioridad activa']].map(([c,l]) => (
          <span key={l} style={{ display:'flex', alignItems:'center', gap:5 }}>
            <span style={{ width:7, height:7, borderRadius:2, background:c, display:'inline-block' }} />
            <span style={{ fontFamily:'Inter,system-ui', fontSize:10, color:BD.muted }}>{l}</span>
          </span>
        ))}
      </div>
    </div>
  );
}

// ── Panel de volumen semanal ──────────────────────────────────────────────────
function WeeklyVolumePanel({ priorities, log, sessionSets }) {
  const activeMuscles = Object.entries(priorities).filter(
    ([, s]) => s === 'priority' || s === 'maintain' || s === 'reducir'
  );
  if (activeMuscles.length === 0) return null;

  return (
    <div style={{ background:BD.card, borderRadius:14, padding:'14px 16px', marginBottom:20,
      border:`1px solid ${BD.border}` }}>
      <div style={{ fontFamily:'ui-monospace,Menlo,monospace', fontSize:8.5, fontWeight:700,
        color:BD.muted, letterSpacing:1.5, marginBottom:12 }}>OBJETIVOS SEMANALES</div>
      {activeMuscles.map(([id, state]) => {
        const def    = MUSCLES[id];
        const sci    = MUSCLE_SCIENCE[id];
        if (!def || !sci) return null;
        const isPrio  = state === 'priority';
        const isMaint = state === 'maintain';
        const isRed   = state === 'reducir';
        // Priority→MAV, Mantener→MEV, Reducir→50% MEV (deload)
        const target  = isPrio ? sci.mav : isMaint ? sci.mev : Math.round(sci.mev * 0.5);
        const done    = setsThisWeek(id, log) + (sessionSets[id] || 0);
        const pct     = Math.min(100, Math.round((done / target) * 100));
        // Para reducir: colores invertidos (menos = mejor)
        const color   = isRed
          ? (done <= target ? BD.green : done <= sci.mev ? BD.amber : BD.red)
          : pct >= 100 ? BD.green : pct >= 60 ? BD.amber : BD.blue;
        const badgeBg    = isPrio ? 'rgba(59,130,246,0.18)'  : isMaint ? 'rgba(34,197,94,0.12)'  : 'rgba(245,158,11,0.12)';
        const badgeColor = isPrio ? '#93C5FD'                : isMaint ? BD.green                 : BD.amber;
        const badgeLabel = isPrio ? 'PRIORIDAD'              : isMaint ? 'MANTENER'               : 'REDUCIR';
        return (
          <div key={id} style={{ marginBottom:10 }}>
            <div style={{ display:'flex', justifyContent:'space-between', alignItems:'center', marginBottom:4 }}>
              <div style={{ display:'flex', alignItems:'center', gap:6 }}>
                <span style={{ fontFamily:'Inter,system-ui', fontSize:11, fontWeight:700, color:BD.text }}>
                  {def.label}
                </span>
                <span style={{ fontSize:8, padding:'1px 5px', borderRadius:999, fontWeight:700,
                  background:badgeBg, color:badgeColor, fontFamily:'Inter,system-ui' }}>
                  {badgeLabel}
                </span>
              </div>
              <span style={{ fontFamily:'ui-monospace,Menlo,monospace', fontSize:10, color }}>
                {done}/{target} sets · {sci.freq}×/sem
              </span>
            </div>
            <div style={{ height:4, borderRadius:999, background:'rgba(255,255,255,0.06)', overflow:'hidden' }}>
              <div style={{ height:'100%', width:`${pct}%`, borderRadius:999, background:color,
                transition:'width .4s ease', boxShadow: pct >= 100 ? `0 0 8px ${color}60` : 'none' }} />
            </div>
          </div>
        );
      })}
      <div style={{ marginTop:8, fontFamily:'Inter,system-ui', fontSize:10, color:BD.muted }}>
        Basado en protocolos de hipertrofia (Israetel / Schoenfeld)
      </div>
    </div>
  );
}

// ── Panel de recomendación científica ────────────────────────────────────────
function SciencePanel({ muscleId, priorities, log, sessionSets, onTogglePriority }) {
  // Science detail is collapsed by default so the panel doesn't overwhelm; the
  // full MEV/MAV/MRV breakdown stays one tap away behind "Ver detalles".
  const [showDetail, setShowDetail] = React.useState(false);
  // Reset to collapsed whenever the selected muscle changes.
  React.useEffect(() => { setShowDetail(false); }, [muscleId]);
  if (!muscleId) return null;
  const def = MUSCLES[muscleId];
  const sci = MUSCLE_SCIENCE[muscleId];
  if (!def || !sci) return null;

  const prio = priorities[muscleId];
  const done = setsThisWeek(muscleId, log) + (sessionSets[muscleId] || 0);

  const statusColor = done === 0       ? BD.muted
                    : done < sci.mev   ? BD.amber
                    : done < sci.mav   ? BD.green
                    : done <= sci.mrv  ? BD.blue
                    : BD.red;

  const statusText = done === 0        ? 'Sin series esta semana'
                   : done < sci.mev    ? 'Por debajo del volumen mínimo efectivo'
                   : done < sci.mav    ? 'En rango óptimo · sigue así'
                   : done <= sci.mrv   ? 'Volumen elevado · monitoriza recuperación'
                   :                    'Superando el máximo recuperable';

  const ROWS = [
    { key:'MEV', val:sci.mev, color:BD.amber, title:'Mínimo efectivo',
      body:'Series mínimas por semana para provocar adaptación. Por debajo no hay progreso real.' },
    { key:'MAV', val:sci.mav, color:BD.green, title:'Volumen óptimo',
      body:'Rango donde la hipertrofia se maximiza sin comprometer la recuperación semanal.' },
    { key:'MRV', val:sci.mrv, color:BD.red,   title:'Máximo recuperable',
      body:'Límite superior. Superarlo de forma sostenida acumula fatiga y frena el progreso.' },
  ];

  return (
    <div style={{ background:BD.card, borderRadius:14, padding:'14px 16px',
      border:`1px solid ${BD.border}`, animation:'fadeIn .2s ease' }}>

      {/* Cabecera */}
      <div style={{ display:'flex', alignItems:'center', justifyContent:'space-between', marginBottom:14 }}>
        <div>
          <div style={{ fontFamily:'ui-monospace,Menlo,monospace', fontSize:8, fontWeight:700,
            color:BD.muted, letterSpacing:1.5, marginBottom:3 }}>RECOMENDACIÓN CIENTÍFICA</div>
          <div style={{ fontFamily:'"Space Grotesk",system-ui', fontSize:15, fontWeight:700, color:BD.text }}>
            {def.label}
          </div>
        </div>
        <button onClick={() => onTogglePriority(muscleId)}
          style={{ padding:'5px 12px', borderRadius:999, border:'none', cursor:'pointer',
            background: prio === 'priority' ? BD.blue
                       : prio === 'maintain' ? 'rgba(34,197,94,0.15)'
                       : prio === 'reducir'  ? 'rgba(245,158,11,0.12)'
                       : 'rgba(255,255,255,0.06)',
            color: prio === 'priority' ? '#fff'
                  : prio === 'maintain' ? BD.green
                  : prio === 'reducir'  ? BD.amber
                  : BD.muted,
            fontFamily:'Inter,system-ui', fontSize:10, fontWeight:700, transition:'all .14s',
            boxShadow: prio === 'priority' ? '0 3px 12px -3px rgba(59,130,246,0.45)' : 'none',
            whiteSpace:'nowrap' }}>
          {prio === 'priority' ? '🎯 Prioridad'
           : prio === 'maintain' ? '✓ Mantener'
           : prio === 'reducir'  ? '↓ Reducir'
           : '+ Priorizar'}
        </button>
      </div>

      {/* Estado compacto siempre visible + toggle de detalle científico */}
      <div style={{ display:'flex', alignItems:'center', gap:10, justifyContent:'space-between' }}>
        <div style={{ display:'flex', alignItems:'center', gap:8, minWidth:0 }}>
          <span style={{ flexShrink:0, fontFamily:'ui-monospace,Menlo,monospace', fontSize:10,
            fontWeight:700, color:statusColor }}>{done} sets</span>
          <span style={{ fontFamily:'Inter,system-ui', fontSize:10.5, color:statusColor,
            fontWeight:600, overflow:'hidden', textOverflow:'ellipsis', whiteSpace:'nowrap' }}>
            {statusText}
          </span>
        </div>
        <button onClick={() => setShowDetail(v => !v)}
          style={{ flexShrink:0, background:'none', border:'none', cursor:'pointer', padding:'2px 0',
            fontFamily:'Inter,system-ui', fontSize:10.5, fontWeight:700, color:BD.blue,
            display:'flex', alignItems:'center', gap:3 }}>
          {showDetail ? 'Ocultar' : 'Ver detalles'}
          <span style={{ fontSize:9, transition:'transform .18s ease',
            display:'inline-block', transform: showDetail ? 'rotate(90deg)' : 'none' }}>▸</span>
        </button>
      </div>

      {!showDetail && (
      <div style={{ marginTop:8, height:4, borderRadius:999, background:'rgba(255,255,255,0.06)',
        position:'relative', overflow:'hidden' }}>
        <div style={{ height:'100%', borderRadius:999, transition:'width .4s ease',
          width:`${Math.min(100, (done / sci.mrv) * 100)}%`, background:statusColor }} />
      </div>
      )}

      {showDetail && (
      <div style={{ marginTop:14, animation:'fadeIn .18s ease' }}>

      {/* Filas MEV / MAV / MRV */}
      {ROWS.map(({ key, val, color, title, body }) => (
        <div key={key} style={{ display:'flex', gap:10, marginBottom:10, alignItems:'flex-start' }}>
          <div style={{ minWidth:36, flexShrink:0, textAlign:'center' }}>
            <div style={{ display:'inline-block', padding:'2px 5px', borderRadius:5,
              background:`${color}1A`, fontFamily:'ui-monospace,Menlo,monospace',
              fontSize:8, fontWeight:700, color, marginBottom:3 }}>{key}</div>
            <div style={{ fontFamily:'ui-monospace,Menlo,monospace', fontSize:11,
              color, fontWeight:700 }}>{val}</div>
          </div>
          <div style={{ flex:1 }}>
            <div style={{ fontFamily:'Inter,system-ui', fontSize:11, fontWeight:700,
              color:BD.sub, marginBottom:2 }}>{title}</div>
            <div style={{ fontFamily:'Inter,system-ui', fontSize:10, color:BD.muted, lineHeight:1.5 }}>
              {body}
            </div>
          </div>
        </div>
      ))}

      {/* Barra de volumen con marcadores MEV / MAV */}
      <div style={{ marginTop:12, marginBottom:4 }}>
        <div style={{ display:'flex', justifyContent:'space-between', marginBottom:5 }}>
          <span style={{ fontFamily:'Inter,system-ui', fontSize:9.5, color:BD.muted }}>
            Esta semana · {sci.freq}× recomendado
          </span>
          <span style={{ fontFamily:'ui-monospace,Menlo,monospace', fontSize:10,
            color:statusColor, fontWeight:700 }}>{done} sets</span>
        </div>
        <div style={{ position:'relative', height:6, borderRadius:999, background:'rgba(255,255,255,0.06)' }}>
          <div style={{ position:'absolute', inset:0, borderRadius:999, overflow:'hidden' }}>
            <div style={{ height:'100%', borderRadius:999, transition:'width .4s ease',
              width:`${Math.min(100, (done / sci.mrv) * 100)}%`, background:statusColor }} />
          </div>
          {/* Marcador MEV */}
          <div style={{ position:'absolute', top:0, bottom:0, width:1.5,
            left:`${(sci.mev / sci.mrv) * 100}%`, background:BD.amber,
            transform:'translateX(-50%)' }} />
          {/* Marcador MAV */}
          <div style={{ position:'absolute', top:0, bottom:0, width:1.5,
            left:`${(sci.mav / sci.mrv) * 100}%`, background:BD.green,
            transform:'translateX(-50%)' }} />
        </div>
        <div style={{ display:'flex', justifyContent:'space-between', marginTop:4 }}>
          <span style={{ fontFamily:'ui-monospace,Menlo,monospace', fontSize:8, color:BD.amber }}>MEV {sci.mev}</span>
          <span style={{ fontFamily:'ui-monospace,Menlo,monospace', fontSize:8, color:BD.green }}>MAV {sci.mav}</span>
          <span style={{ fontFamily:'ui-monospace,Menlo,monospace', fontSize:8, color:BD.muted }}>MRV {sci.mrv}</span>
        </div>
      </div>

      </div>
      )}
    </div>
  );
}

// ── Panel de equilibrio muscular ──────────────────────────────────────────────
function BalancePanel({ log, sessionSets }) {
  const imbalances = BALANCE_PAIRS.map(pair => {
    const setsA = pair.idsA.reduce((acc, id) => acc + setsThisWeek(id, log) + (sessionSets[id] || 0), 0);
    const setsB = pair.idsB.reduce((acc, id) => acc + setsThisWeek(id, log) + (sessionSets[id] || 0), 0);
    const totalSets = setsA + setsB;
    if (totalSets < 4) return null;
    if (setsB === 0 && setsA === 0) return null;

    const ratio = setsB === 0 ? (setsA > 0 ? Infinity : 1) : setsA / setsB;

    let alert = null;
    if (pair.maxRatio && ratio > pair.maxRatio) {
      alert = { label: pair.labelA, dominator: true, advice: pair.adviceHigh || pair.advice, ratio };
    } else if (pair.minRatio && ratio < pair.minRatio) {
      alert = { label: pair.labelB, dominator: false, advice: pair.adviceLow || pair.advice, ratio: setsB / setsA };
    }

    return alert ? { ...alert, setsA, setsB, pair } : null;
  }).filter(Boolean);

  if (imbalances.length === 0) return null;

  return (
    <div style={{ marginTop:12 }}>
      {imbalances.map(({ pair, setsA, setsB, advice, ratio }) => {
        const ratioStr = isFinite(ratio) ? ratio.toFixed(1) + ':1' : '∞:1';
        return (
          <div key={pair.id} style={{
            padding:'10px 12px', borderRadius:10, marginBottom:8,
            background:'rgba(245,158,11,0.07)',
            border:'1px solid rgba(245,158,11,0.22)',
          }}>
            <div style={{ display:'flex', alignItems:'center', gap:6, marginBottom:5 }}>
              <svg width="13" height="13" viewBox="0 0 24 24" fill="none" style={{ flexShrink:0 }}>
                <path d="M12 2L2 19h20L12 2z" stroke={BD.amber} strokeWidth="2" strokeLinejoin="round"/>
                <path d="M12 9v5M12 16v1" stroke={BD.amber} strokeWidth="2" strokeLinecap="round"/>
              </svg>
              <span style={{ fontFamily:'Inter,system-ui', fontSize:10, fontWeight:700, color:BD.amber, letterSpacing:'0.05em' }}>
                DESEQUILIBRIO POTENCIAL
              </span>
            </div>
            <div style={{ display:'flex', gap:8, marginBottom:6, alignItems:'center' }}>
              <span style={{ fontFamily:'ui-monospace,Menlo,monospace', fontSize:9, color:BD.sub,
                padding:'2px 6px', borderRadius:4, background:'rgba(255,255,255,0.05)' }}>
                {pair.labelA}: {setsA} series
              </span>
              <span style={{ color:BD.muted, fontSize:9 }}>vs</span>
              <span style={{ fontFamily:'ui-monospace,Menlo,monospace', fontSize:9, color:BD.sub,
                padding:'2px 6px', borderRadius:4, background:'rgba(255,255,255,0.05)' }}>
                {pair.labelB}: {setsB} series
              </span>
              <span style={{ fontFamily:'ui-monospace,Menlo,monospace', fontSize:9,
                color:BD.amber, marginLeft:'auto' }}>
                {ratioStr}
              </span>
            </div>
            {/* Mini ratio bar */}
            <div style={{ height:4, borderRadius:999, background:'rgba(255,255,255,0.06)', marginBottom:6, overflow:'hidden' }}>
              <div style={{
                height:'100%', borderRadius:999,
                width:`${Math.min(100, (setsA / (setsA + setsB)) * 100)}%`,
                background:`linear-gradient(90deg, ${BD.blue}, ${BD.amber})`,
                transition:'width .4s ease',
              }} />
            </div>
            <p style={{ fontFamily:'Inter,system-ui', fontSize:10, color:BD.sub, lineHeight:1.5, margin:0 }}>
              {advice}
            </p>
          </div>
        );
      })}
    </div>
  );
}

// ── Panel: Recomendación Atlas Coach ─────────────────────────────────────────
function CoachRecommendationPanel({ priorities, log, sessionSets }) {
  const [open,    setOpen]    = React.useState(true);
  const [dayOpen, setDayOpen] = React.useState(false);
  const { navigate } = useRoute();

  const plan = React.useMemo(() => {
    if (!window.AtlasEngine) return null;
    const hasAny = Object.values(priorities).some(s => s === 'priority' || s === 'maintain' || s === 'reducir');
    if (!hasAny) return null;
    return window.AtlasEngine.exportBuilderPlan(priorities, 4);
  }, [priorities]);

  if (!plan || !plan.volumePlan.length) return null;

  const split = plan.split;
  const splitStyle = {
    fullbody:    { color:BD.green,  bg:'rgba(34,197,94,0.10)',   border:'rgba(34,197,94,0.22)'   },
    upper_lower: { color:BD.blue,   bg:'rgba(59,130,246,0.10)',  border:'rgba(59,130,246,0.22)'  },
    ppl:         { color:BD.amber,  bg:'rgba(245,158,11,0.10)',  border:'rgba(245,158,11,0.22)'  },
  }[split.key] || { color:BD.blue, bg:'rgba(59,130,246,0.10)', border:'rgba(59,130,246,0.22)' };

  function handleSendToCoach() {
    window.AtlasEngine.saveBuilderPlan(plan);
    navigate('/coach');
  }

  return (
    <div style={{ background:BD.card, borderRadius:14, padding:'14px 16px',
      border:`1px solid ${BD.border}`, marginBottom:14 }}>

      {/* Header */}
      <div style={{ display:'flex', alignItems:'center', justifyContent:'space-between', marginBottom: open ? 12 : 0 }}>
        <span style={{ fontFamily:'ui-monospace,Menlo,monospace', fontSize:7.5,
          fontWeight:700, color:BD.muted, letterSpacing:1.6 }}>RECOMENDACIÓN ATLAS COACH</span>
        <button onClick={() => setOpen(o=>!o)}
          style={{ background:'none', border:'none', cursor:'pointer', color:BD.muted, padding:'4px 2px', lineHeight:0 }}>
          <svg width="13" height="13" viewBox="0 0 24 24" fill="none"
            stroke="currentColor" strokeWidth="2.5" strokeLinecap="round">
            {open ? <path d="M18 15l-6-6-6 6"/> : <path d="M6 9l6 6 6-6"/>}
          </svg>
        </button>
      </div>

      {open && (
        <>
          {/* Split badge */}
          <div style={{ padding:'10px 12px', borderRadius:10, marginBottom:12,
            background:splitStyle.bg, border:`1px solid ${splitStyle.border}` }}>
            <div style={{ display:'flex', alignItems:'baseline', gap:8, marginBottom:4 }}>
              <span style={{ fontFamily:'"Space Grotesk",system-ui', fontSize:15, fontWeight:700, color:splitStyle.color }}>
                {split.name}
              </span>
              <span style={{ fontFamily:'ui-monospace,Menlo,monospace', fontSize:8.5, color:BD.muted }}>
                {split.sessionsPerWeek}×/sem · {plan.totalSets} series totales
              </span>
            </div>
            <p style={{ fontFamily:'Inter,system-ui', fontSize:10, color:BD.sub, lineHeight:1.55, margin:0 }}>
              {split.reason}
            </p>
          </div>

          {/* Volume table */}
          <div style={{ marginBottom:10 }}>
            {plan.volumePlan.map(m => {
              const done    = setsThisWeek(m.id, log) + (sessionSets[m.id] || 0);
              const pct     = m.targetSets > 0 ? Math.min(100, (done / m.targetSets) * 100) : 0;
              const prioC   = m.state === 'priority' ? BD.blue
                            : m.state === 'maintain'  ? BD.green : BD.amber;
              const badge   = m.state === 'priority' ? 'MAV'
                            : m.state === 'maintain'  ? 'MEV' : 'DLD';
              return (
                <div key={m.id} style={{ marginBottom:8 }}>
                  <div style={{ display:'flex', justifyContent:'space-between', alignItems:'center', marginBottom:3 }}>
                    <div style={{ display:'flex', alignItems:'center', gap:5 }}>
                      <span style={{ fontFamily:'Inter,system-ui', fontSize:10.5, color:BD.sub, fontWeight:600 }}>{m.name}</span>
                      <span style={{ fontFamily:'ui-monospace,Menlo,monospace', fontSize:7.5, padding:'1px 5px',
                        borderRadius:999, background:`${prioC}1A`, color:prioC, fontWeight:700 }}>{badge}</span>
                    </div>
                    <span style={{ fontFamily:'ui-monospace,Menlo,monospace', fontSize:9.5, color:BD.muted }}>
                      {done}/{m.targetSets} · {m.freq}×/sem
                    </span>
                  </div>
                  <div style={{ height:3, borderRadius:999, background:'rgba(255,255,255,0.06)', overflow:'hidden' }}>
                    <div style={{ height:'100%', width:`${pct}%`, background:prioC, borderRadius:999, transition:'width .4s ease' }} />
                  </div>
                </div>
              );
            })}
          </div>

          {/* Day structure toggle */}
          <button onClick={() => setDayOpen(o=>!o)}
            style={{ width:'100%', background:'rgba(255,255,255,0.03)', border:`1px solid ${BD.border}`,
              borderRadius:8, padding:'7px 12px', cursor:'pointer', color:BD.muted,
              fontFamily:'Inter,system-ui', fontSize:10, display:'flex', justifyContent:'space-between',
              marginBottom: dayOpen ? 8 : 10 }}>
            <span>Estructura de sesiones</span>
            <svg width="11" height="11" viewBox="0 0 24 24" fill="none" stroke="currentColor"
              strokeWidth="2.5" strokeLinecap="round" style={{ transform: dayOpen ? 'rotate(180deg)':'none', transition:'transform .2s' }}>
              <path d="M6 9l6 6 6-6"/>
            </svg>
          </button>

          {dayOpen && (
            <div style={{ marginBottom:10 }}>
              {plan.days.map((day, i) => (
                <div key={i} style={{ marginBottom:6, padding:'8px 10px', borderRadius:8,
                  background:'rgba(255,255,255,0.02)', border:`1px solid ${BD.border}` }}>
                  <div style={{ fontFamily:'ui-monospace,Menlo,monospace', fontSize:8.5,
                    fontWeight:700, color:BD.muted, marginBottom:5, letterSpacing:0.5 }}>
                    {day.dayLabel.toUpperCase()} · {day.totalSets} series
                  </div>
                  <div style={{ display:'flex', flexWrap:'wrap', gap:4 }}>
                    {day.muscles.map(m => (
                      <span key={m.id} style={{ fontFamily:'Inter,system-ui', fontSize:9.5,
                        padding:'2px 7px', borderRadius:999,
                        background:'rgba(59,130,246,0.10)', color:'#93C5FD' }}>
                        {m.name} · {m.setsThisDay}s
                      </span>
                    ))}
                  </div>
                </div>
              ))}
              <p style={{ fontFamily:'Inter,system-ui', fontSize:9.5, color:BD.muted,
                lineHeight:1.5, margin:'6px 0 0' }}>{split.science}</p>
            </div>
          )}

          {/* CTA */}
          <button onClick={handleSendToCoach}
            style={{ width:'100%', padding:'10px 0', borderRadius:8, cursor:'pointer',
              border:'1px solid rgba(59,130,246,0.28)', background:'rgba(59,130,246,0.08)',
              color:'#93C5FD', fontFamily:'Inter,system-ui', fontSize:11.5, fontWeight:700,
              transition:'all .15s' }}>
            Enviar al Coach →
          </button>
        </>
      )}
    </div>
  );
}

// ── Atlas Score ───────────────────────────────────────────────────────────────
function computeAtlasScore(priorities, log, sessionSets) {
  const active = Object.entries(priorities).filter(([,s]) => s === 'priority' || s === 'maintain');

  // 1. Equilibrio (25 pts) — balance antagonista
  const pairsEval = BALANCE_PAIRS.filter(p => {
    const a = p.idsA.reduce((n,id) => n + setsThisWeek(id,log) + (sessionSets[id]||0), 0);
    const b = p.idsB.reduce((n,id) => n + setsThisWeek(id,log) + (sessionSets[id]||0), 0);
    return (a + b) >= 4;
  });
  let imbalancedPairs = [];
  pairsEval.forEach(p => {
    const a = p.idsA.reduce((n,id) => n + setsThisWeek(id,log) + (sessionSets[id]||0), 0);
    const b = p.idsB.reduce((n,id) => n + setsThisWeek(id,log) + (sessionSets[id]||0), 0);
    const r = b === 0 ? (a > 0 ? Infinity : 1) : a / b;
    if ((p.maxRatio && r > p.maxRatio) || (p.minRatio && r < p.minRatio)) imbalancedPairs.push(p);
  });
  const eqPts = pairsEval.length === 0
    ? 18
    : Math.max(0, 25 - imbalancedPairs.length * 9);

  // 2. Volumen óptimo (30 pts) — series en rango MEV–MAV
  let volPts = 0;
  if (active.length > 0) {
    let sum = 0;
    active.forEach(([id, state]) => {
      const sci = MUSCLE_SCIENCE[id]; if (!sci) return;
      const done = setsThisWeek(id,log) + (sessionSets[id]||0);
      if (done === 0)           sum += 0;
      else if (done < sci.mev)  sum += 0.35 * (done / sci.mev);
      else if (done <= sci.mav) sum += 0.35 + 0.65 * ((done - sci.mev) / Math.max(1, sci.mav - sci.mev));
      else if (done <= sci.mrv) sum += 1.0;
      else                      sum += 0.70;
    });
    volPts = Math.round(30 * (sum / active.length));
  }

  // 3. Sin excesos (20 pts) — penaliza por superar MRV
  let excesoPts = 20;
  const excesoMuscles = [];
  Object.entries(MUSCLE_SCIENCE).forEach(([id, sci]) => {
    const done = setsThisWeek(id,log) + (sessionSets[id]||0);
    if (done > sci.mrv) { excesoPts -= 5; excesoMuscles.push(MUSCLES[id]?.label || id); }
  });
  excesoPts = Math.max(0, excesoPts);

  // 4. Cobertura muscular (15 pts) — prioridades en grupos principales
  const CGROUPS = ['torso', 'hombros', 'brazos', 'piernas', 'core'];
  const coveredCount = CGROUPS.filter(g =>
    active.some(([id]) => MUSCLES[id]?.group === g || (g === 'piernas' && MUSCLES[id]?.group === 'gluteos'))
  ).length;
  const coverPts = Math.round(15 * (coveredCount / CGROUPS.length));

  // 5. Frecuencia semanal (10 pts) — músculos prioritarios activos esta semana
  const freqPts = active.length === 0 ? 0
    : Math.round(10 * (active.filter(([id]) => setsThisWeek(id,log) + (sessionSets[id]||0) > 0).length / active.length));

  const total = eqPts + volPts + excesoPts + coverPts + freqPts;

  // Insight principal
  let insight;
  if (active.length === 0) {
    insight = 'Selecciona músculos en el mapa para empezar a evaluar tu entrenamiento.';
  } else if (excesoMuscles.length > 0) {
    insight = `${excesoMuscles[0]} supera el MRV. El exceso sostenido frena el progreso y acumula fatiga.`;
  } else if (imbalancedPairs.length > 0) {
    const p = imbalancedPairs[0];
    insight = (p.adviceHigh || p.advice).split('.')[0] + '.';
  } else if (volPts < 12) {
    insight = 'El volumen semanal está aún por debajo del mínimo efectivo en varios grupos prioritarios.';
  } else if (coverPts < 9) {
    insight = 'Diversifica las prioridades. Un programa completo cubre torso, tren inferior, brazos y core.';
  } else if (total >= 85) {
    insight = 'Entrenamiento muy bien estructurado. Volumen, equilibrio y frecuencia en zona óptima.';
  } else if (total >= 70) {
    insight = 'Buen programa. Ajusta el volumen de los grupos prioritarios para acercarte al rango MAV.';
  } else {
    insight = 'Programa en desarrollo. Acumula series y distribuye el volumen entre grupos antagonistas.';
  }

  return {
    total,
    insight,
    pillars: [
      { key:'equilibrio', label:'Equilibrio muscular', pts:eqPts,     max:25, icon:'⚖' },
      { key:'volumen',    label:'Volumen adecuado',    pts:volPts,    max:30, icon:'📊' },
      { key:'exceso',     label:'Sin excesos',         pts:excesoPts, max:20, icon:'🛡' },
      { key:'cobertura',  label:'Cobertura',           pts:coverPts,  max:15, icon:'🗺' },
      { key:'frecuencia', label:'Frecuencia',          pts:freqPts,   max:10, icon:'📅' },
    ],
  };
}

function AtlasScoreCard({ priorities, log, sessionSets }) {
  const [open, setOpen] = React.useState(false);
  const sc = computeAtlasScore(priorities, log, sessionSets);

  const color = sc.total >= 80 ? BD.green
              : sc.total >= 65 ? BD.amber
              : sc.total >= 45 ? '#F97316'
              : BD.red;

  const qualLabel = sc.total >= 80 ? 'Excelente'
                  : sc.total >= 65 ? 'Bueno'
                  : sc.total >= 45 ? 'Mejorable'
                  : 'Atención';

  const R  = 30;
  const C  = 2 * Math.PI * R;
  const offset = C * (1 - sc.total / 100);

  return (
    <div style={{ background:BD.card, borderRadius:14, padding:'14px 16px',
      border:`1px solid ${BD.border}`, marginBottom:14 }}>

      <div style={{ display:'flex', alignItems:'center', gap:14 }}>

        {/* Anillo SVG */}
        <div style={{ position:'relative', width:68, height:68, flexShrink:0 }}>
          <svg width="68" height="68" viewBox="0 0 76 76">
            <circle cx="38" cy="38" r={R} fill="none"
              stroke="rgba(255,255,255,0.06)" strokeWidth="6" />
            <circle cx="38" cy="38" r={R} fill="none"
              stroke={color} strokeWidth="6" strokeLinecap="round"
              strokeDasharray={C} strokeDashoffset={offset}
              transform="rotate(-90 38 38)"
              style={{ transition:'stroke-dashoffset .9s ease, stroke .4s' }} />
          </svg>
          <div style={{ position:'absolute', inset:0, display:'flex',
            flexDirection:'column', alignItems:'center', justifyContent:'center' }}>
            <span style={{ fontFamily:'"Space Grotesk",system-ui', fontSize:19,
              fontWeight:800, color, lineHeight:1 }}>{sc.total}</span>
            <span style={{ fontFamily:'ui-monospace,Menlo,monospace', fontSize:7.5,
              color:BD.muted, marginTop:1 }}>/100</span>
          </div>
        </div>

        {/* Texto */}
        <div style={{ flex:1, minWidth:0 }}>
          <div style={{ display:'flex', alignItems:'center', gap:7, marginBottom:5 }}>
            <span style={{ fontFamily:'ui-monospace,Menlo,monospace', fontSize:7.5,
              fontWeight:700, color:BD.muted, letterSpacing:1.6 }}>ATLAS SCORE</span>
            <span style={{ padding:'2px 7px', borderRadius:999,
              background:`${color}1A`, color,
              fontFamily:'Inter,system-ui', fontSize:9, fontWeight:700 }}>{qualLabel}</span>
          </div>
          <p style={{ fontFamily:'Inter,system-ui', fontSize:11, color:BD.sub,
            lineHeight:1.55, margin:0 }}>{sc.insight}</p>
        </div>

        <button onClick={() => setOpen(o => !o)}
          style={{ background:'none', border:'none', cursor:'pointer',
            color:BD.muted, padding:'4px 2px', flexShrink:0, lineHeight:0 }}>
          <svg width="13" height="13" viewBox="0 0 24 24" fill="none"
            stroke="currentColor" strokeWidth="2.5" strokeLinecap="round">
            {open ? <path d="M18 15l-6-6-6 6"/> : <path d="M6 9l6 6 6-6"/>}
          </svg>
        </button>
      </div>

      {open && (
        <div style={{ marginTop:12, paddingTop:12, borderTop:`1px solid ${BD.border}` }}>
          {sc.pillars.map(p => {
            const pct   = p.pts / p.max;
            const pc    = pct >= 0.8 ? BD.green : pct >= 0.5 ? BD.amber : BD.red;
            return (
              <div key={p.key} style={{ marginBottom:9 }}>
                <div style={{ display:'flex', justifyContent:'space-between',
                  alignItems:'center', marginBottom:3 }}>
                  <span style={{ fontFamily:'Inter,system-ui', fontSize:10.5, color:BD.sub }}>
                    {p.label}
                  </span>
                  <span style={{ fontFamily:'ui-monospace,Menlo,monospace', fontSize:9, color:pc }}>
                    {p.pts}/{p.max}
                  </span>
                </div>
                <div style={{ height:3, borderRadius:999,
                  background:'rgba(255,255,255,0.06)', overflow:'hidden' }}>
                  <div style={{ height:'100%', borderRadius:999,
                    width:`${pct * 100}%`, background:pc,
                    transition:'width .5s ease' }} />
                </div>
              </div>
            );
          })}
          <div style={{ marginTop:10, fontFamily:'Inter,system-ui', fontSize:10,
            color:BD.muted, textAlign:'right' }}>
            Basado en protocolos Israetel · Schoenfeld
          </div>
        </div>
      )}
    </div>
  );
}

// ── Modal: Generar Plan ───────────────────────────────────────────────────────
function PlanModal({ priorities, log, sessionSets, workout, onClose, onGoLab }) {
  const sc    = computeAtlasScore(priorities, log, sessionSets);
  const today = new Date().toLocaleDateString('es-ES', { year:'numeric', month:'long', day:'numeric' });
  const active = Object.entries(priorities).filter(([,s]) => s);

  // Paleta clara para impresión
  const PT = {
    bg:'#F8F9FC', card:'#FFFFFF', border:'#E5E7EB',
    text:'#0F1A2E', sub:'#374151', muted:'#6B7280',
    blue:'#2563EB', green:'#16A34A', amber:'#D97706', red:'#DC2626',
  };

  const scoreColor = sc.total >= 80 ? PT.green
                   : sc.total >= 65 ? PT.amber
                   : sc.total >= 45 ? '#EA580C' : PT.red;
  const qualLabel  = sc.total >= 80 ? 'Excelente' : sc.total >= 65 ? 'Bueno'
                   : sc.total >= 45 ? 'Mejorable' : 'Atención';

  function handlePrint() {
    const s = document.createElement('style');
    s.dataset.ap = '1';
    s.textContent = `@media print{body *{visibility:hidden!important}#atlas-plan-print,#atlas-plan-print *{visibility:visible!important}#atlas-plan-print{position:fixed;inset:0;overflow:visible!important;padding:40px!important;border-radius:0!important;max-height:none!important}.atlas-noprint{display:none!important}}`;
    document.head.appendChild(s);
    window.print();
    window.addEventListener('afterprint', () =>
      document.querySelectorAll('[data-ap]').forEach(e => e.remove()), { once:true });
  }

  return ReactDOM.createPortal(
    <div style={{ position:'fixed', inset:0, zIndex:960, overflowY:'auto',
      display:'flex', alignItems:'flex-start', justifyContent:'center',
      padding:'calc(4vh + env(safe-area-inset-top)) 16px calc(6vh + env(safe-area-inset-bottom))' }}>

      {/* Fondo oscuro */}
      <div onClick={onClose}
        style={{ position:'fixed', inset:0, background:'rgba(4,10,20,0.86)', zIndex:0 }} />

      {/* Tarjeta del plan — fondo claro para impresión */}
      <div id="atlas-plan-print"
        style={{ position:'relative', zIndex:1, width:'min(680px, 100%)',
          background:PT.bg, borderRadius:20, padding:'44px 48px',
          color:PT.text, fontFamily:'Inter,system-ui',
          boxShadow:'0 32px 80px rgba(0,0,0,0.55)' }}>

        {/* ── Cabecera ── */}
        <div style={{ display:'flex', justifyContent:'space-between', alignItems:'flex-start',
          marginBottom:28, paddingBottom:22, borderBottom:`1.5px solid ${PT.border}` }}>
          <div>
            <div style={{ fontFamily:'"Space Grotesk",system-ui', fontWeight:800,
              fontSize:22, color:PT.text, letterSpacing:-0.5, marginBottom:3 }}>
              Atlas Method
            </div>
            <div style={{ fontFamily:'ui-monospace,Menlo,monospace', fontSize:9.5,
              color:PT.muted, letterSpacing:0.5 }}>
              PLAN DE ENTRENAMIENTO · {today.toUpperCase()}
            </div>
          </div>
          <button onClick={onClose} className="atlas-noprint"
            style={{ background:'none', border:'none', cursor:'pointer',
              color:PT.muted, fontSize:22, lineHeight:1, padding:'0 4px' }}>
            ×
          </button>
        </div>

        {/* ── Atlas Score ── */}
        <div style={{ display:'flex', gap:32, marginBottom:30, alignItems:'center' }}>
          <div style={{ textAlign:'center', flexShrink:0, minWidth:84 }}>
            <div style={{ fontFamily:'"Space Grotesk",system-ui', fontSize:58,
              fontWeight:800, color:scoreColor, lineHeight:1 }}>
              {sc.total}
            </div>
            <div style={{ fontFamily:'Inter,system-ui', fontSize:10, color:PT.muted, marginTop:2 }}>/100</div>
            <span style={{ display:'inline-block', marginTop:7, padding:'3px 10px',
              borderRadius:999, background:`${scoreColor}18`, color:scoreColor,
              fontFamily:'Inter,system-ui', fontSize:10, fontWeight:700 }}>
              {qualLabel}
            </span>
          </div>
          <div style={{ flex:1 }}>
            <div style={{ fontFamily:'ui-monospace,Menlo,monospace', fontSize:8.5,
              color:PT.muted, letterSpacing:1.5, marginBottom:10 }}>ATLAS SCORE</div>
            {sc.pillars.map(p => {
              const pct = p.pts / p.max;
              const c   = pct >= 0.8 ? PT.green : pct >= 0.5 ? PT.amber : PT.red;
              return (
                <div key={p.key} style={{ marginBottom:7 }}>
                  <div style={{ display:'flex', justifyContent:'space-between', marginBottom:3 }}>
                    <span style={{ fontFamily:'Inter,system-ui', fontSize:10.5, color:PT.sub }}>{p.label}</span>
                    <span style={{ fontFamily:'ui-monospace,Menlo,monospace', fontSize:9.5,
                      color:c, fontWeight:700 }}>{p.pts}/{p.max}</span>
                  </div>
                  <div style={{ height:4, borderRadius:999, background:'#E5E7EB', overflow:'hidden' }}>
                    <div style={{ height:'100%', width:`${pct*100}%`, background:c, borderRadius:999 }} />
                  </div>
                </div>
              );
            })}
          </div>
        </div>

        {/* ── Músculos priorizados ── */}
        {active.length > 0 && (
          <div style={{ marginBottom:26 }}>
            <div style={{ fontFamily:'ui-monospace,Menlo,monospace', fontSize:8.5,
              color:PT.muted, letterSpacing:1.5, marginBottom:12 }}>MÚSCULOS PRIORIZADOS</div>
            <div style={{ borderRadius:10, overflow:'hidden', border:`1px solid ${PT.border}` }}>
              <div style={{ display:'grid', gridTemplateColumns:'1fr 96px 100px 68px',
                background:'#F3F4F6', padding:'7px 14px' }}>
                {['Músculo','Prioridad','Series/sem','Frec.'].map(h => (
                  <span key={h} style={{ fontFamily:'Inter,system-ui', fontSize:9,
                    fontWeight:700, color:PT.muted, letterSpacing:0.5 }}>{h}</span>
                ))}
              </div>
              {active.map(([id, state], i) => {
                const def  = MUSCLES[id];
                const sci2 = MUSCLE_SCIENCE[id];
                if (!def || !sci2) return null;
                const done   = setsThisWeek(id, log) + (sessionSets[id] || 0);
                const target = state === 'priority' ? sci2.mav
                             : state === 'maintain'  ? sci2.mev
                             : Math.round(sci2.mev * 0.5);
                const sc2c   = state === 'priority' ? PT.blue
                             : state === 'maintain'  ? PT.green : PT.amber;
                const slabel = state === 'priority' ? 'Prioridad'
                             : state === 'maintain'  ? 'Mantener' : 'Reducir';
                return (
                  <div key={id} style={{ display:'grid', gridTemplateColumns:'1fr 96px 100px 68px',
                    padding:'9px 14px', borderTop:`1px solid ${PT.border}`,
                    background: i%2===0 ? PT.card : '#FAFAFA' }}>
                    <span style={{ fontFamily:'Inter,system-ui', fontSize:11, fontWeight:600, color:PT.text }}>{def.label}</span>
                    <span style={{ fontFamily:'Inter,system-ui', fontSize:10.5, color:sc2c, fontWeight:700 }}>{slabel}</span>
                    <span style={{ fontFamily:'ui-monospace,Menlo,monospace', fontSize:10, color:PT.text }}>
                      {done}<span style={{ color:PT.muted }}>/{target} sets</span>
                    </span>
                    <span style={{ fontFamily:'ui-monospace,Menlo,monospace', fontSize:10, color:PT.sub }}>{sci2.freq}×/sem</span>
                  </div>
                );
              })}
            </div>
          </div>
        )}

        {/* ── Insight ── */}
        <div style={{ background:'#EFF6FF', border:'1px solid #BFDBFE',
          borderRadius:10, padding:'12px 16px', marginBottom:26 }}>
          <span style={{ fontFamily:'Inter,system-ui', fontSize:11, fontWeight:700,
            color:'#1D4ED8' }}>Recomendación · </span>
          <span style={{ fontFamily:'Inter,system-ui', fontSize:11, color:'#1E40AF',
            lineHeight:1.65 }}>{sc.insight}</span>
        </div>

        {/* ── Footer acciones ── */}
        <div style={{ borderTop:`1px solid ${PT.border}`, paddingTop:18,
          display:'flex', alignItems:'center', justifyContent:'space-between',
          flexWrap:'wrap', gap:10 }}>
          <div style={{ fontFamily:'Inter,system-ui', fontSize:10, color:PT.muted }}>
            Atlas Method · <span style={{ fontWeight:700, color:PT.blue }}>atlasmethod.com</span>
          </div>
          <div className="atlas-noprint" style={{ display:'flex', gap:8, flexWrap:'wrap' }}>
            <button onClick={onClose}
              style={{ padding:'8px 14px', borderRadius:8, border:`1px solid ${PT.border}`,
                background:'transparent', fontFamily:'Inter,system-ui', fontSize:11,
                color:PT.sub, cursor:'pointer', fontWeight:600 }}>
              Cerrar
            </button>
            <button onClick={onGoLab}
              style={{ padding:'8px 16px', borderRadius:8, border:`1px solid ${PT.blue}`,
                background:'transparent', fontFamily:'Inter,system-ui', fontSize:11,
                color:PT.blue, cursor:'pointer', fontWeight:700 }}>
              Ir al Laboratorio →
            </button>
            <button onClick={handlePrint}
              style={{ padding:'8px 18px', borderRadius:8, border:'none',
                background:PT.blue, fontFamily:'Inter,system-ui', fontSize:11,
                color:'white', cursor:'pointer', fontWeight:700,
                boxShadow:'0 2px 8px rgba(37,99,235,0.35)' }}>
              Descargar PDF
            </button>
          </div>
        </div>
      </div>
    </div>,
    document.body
  );
}

// ── Barra de búsqueda global ──────────────────────────────────────────────────
function SearchBar({ query, onQuery, muscle, onMuscle, priorities = {} }) {
  const [focused, setFocused] = React.useState(false);
  const [openG, setOpenG] = React.useState(null);
  const mobile = useIsMobile();
  const PRIO = s => s === 'priority' || s === 'maintain' || s === 'reducir';
  // The visible sub-list follows the tapped group, or the group of the active muscle.
  const activeGroup = MUSCLE_GROUPS.find(g => g.ids.includes(muscle));
  const shownGroup  = MUSCLE_GROUPS.find(g => g.id === openG) || activeGroup || null;
  return (
    <div id="builder-search" style={{ marginBottom:22, scrollMarginTop: 70 }}>
      <div style={{ position:'relative' }}>
        <svg width="15" height="15" viewBox="0 0 24 24" fill="none"
          style={{ position:'absolute', left:14, top:'50%', transform:'translateY(-50%)',
            stroke: focused ? '#93C5FD' : BD.muted, strokeWidth:2.5, pointerEvents:'none', transition:'stroke .15s' }}>
          <circle cx="11" cy="11" r="8"/><path d="m21 21-4.35-4.35"/>
        </svg>
        <input id="builder-search-input" aria-label="Buscar ejercicios" value={query} onChange={e => onQuery(e.target.value)}
          onFocus={() => setFocused(true)} onBlur={() => setFocused(false)}
          placeholder="Buscar ejercicios, músculos, patrones..."
          style={{ width:'100%', padding: mobile ? '14px 42px' : '13px 42px', borderRadius:14, boxSizing:'border-box',
            background:'rgba(255,255,255,0.04)',
            border:`1.5px solid ${focused ? 'rgba(59,130,246,0.50)' : 'rgba(255,255,255,0.09)'}`,
            fontFamily:'Inter,system-ui', fontSize: mobile ? 16 : 14, color:BD.text, transition:'border-color .15s', outline:'none' }} />
        {query && (
          <button onClick={() => onQuery('')}
            style={{ position:'absolute', right:10, top:'50%', transform:'translateY(-50%)',
              width:26, height:26, borderRadius:8, border:'none',
              background:'rgba(255,255,255,0.08)', cursor:'pointer',
              color:BD.muted, fontSize:12, display:'flex', alignItems:'center', justifyContent:'center' }}>
            ✕
          </button>
        )}
      </div>
      {/* Level 1 — six big muscle groups (clean entry) */}
      <div style={{ display:'flex', gap:6, marginTop:10, flexWrap:'wrap' }}>
        {MUSCLE_GROUPS.map(g => {
          const isOpen  = shownGroup?.id === g.id;
          const hasPrio = g.ids.some(id => PRIO(priorities[id]));
          return (
            <button key={g.id} onClick={() => setOpenG(isOpen ? null : g.id)}
              style={{ padding: mobile ? '9px 15px' : '6px 14px', minHeight: mobile ? 38 : undefined, borderRadius:999, border:'none', cursor:'pointer',
                display:'inline-flex', alignItems:'center', gap:6,
                background: isOpen ? BD.blue : 'rgba(255,255,255,0.06)',
                color: isOpen ? '#fff' : BD.sub,
                fontFamily:'Inter,system-ui', fontSize: mobile ? 13 : 12, fontWeight:700, transition:'background .14s, color .14s' }}>
              {g.label}
              {hasPrio && <span style={{ width:6, height:6, borderRadius:'50%', background: isOpen ? '#fff' : '#93C5FD' }} />}
            </button>
          );
        })}
      </div>

      {/* Level 2 — the specific muscles of the open group */}
      {shownGroup && (
        <div style={{ display:'flex', gap:6, marginTop:8, flexWrap:'wrap',
          padding:'10px 12px', borderRadius:12, background:'rgba(255,255,255,0.03)', border:`1px solid ${BD.border}`,
          animation:'fadeIn .15s ease' }}>
          {shownGroup.ids.map(id => {
            const def = MUSCLES[id]; if (!def) return null;
            const active = muscle === id;
            const prio = priorities[id];
            return (
              <button key={id} onClick={() => onMuscle(active ? null : id)}
                style={{ padding: mobile ? '8px 13px' : '5px 12px', minHeight: mobile ? 36 : undefined, borderRadius:999, cursor:'pointer',
                  background: active ? BD.blue
                             : prio === 'priority' ? BD.blueDim
                             : prio === 'maintain' ? 'rgba(34,197,94,0.10)'
                             : prio === 'reducir'  ? 'rgba(245,158,11,0.10)'
                             : 'rgba(255,255,255,0.05)',
                  border: active ? 'none' : `1px solid ${prio === 'priority' ? 'rgba(59,130,246,0.40)' : BD.border}`,
                  color: active ? '#fff'
                        : prio === 'priority' ? '#93C5FD'
                        : prio === 'maintain' ? BD.green
                        : prio === 'reducir'  ? BD.amber
                        : BD.muted,
                  fontFamily:'Inter,system-ui', fontSize: mobile ? 12.5 : 11.5, fontWeight:600, transition:'all .12s' }}>
                {prio === 'priority' ? '🎯 ' : prio === 'maintain' ? '✓ ' : prio === 'reducir' ? '↓ ' : ''}{def.label}
              </button>
            );
          })}
        </div>
      )}
    </div>
  );
}

// ── Tarjeta ejercicio premium ─────────────────────────────────────────────────
function ExerciseCard({ ex, inSession, onDetail, onQuickAdd }) {
  const [hov, setHov] = React.useState(false);
  const g = exGroup(ex);
  const eq = exEquipment(ex)[0];
  const eqMeta = EQ_META[eq];
  const lvlColor = LVL_COLOR[ex.level] || BD.muted;

  return (
    <div onMouseEnter={() => setHov(true)} onMouseLeave={() => setHov(false)}
      onClick={onDetail}
      style={{ borderRadius:14, overflow:'hidden', cursor:'pointer', position:'relative',
        border:`1.5px solid ${inSession ? 'rgba(34,197,94,0.32)' : hov ? 'rgba(59,130,246,0.42)' : BD.border}`,
        background: inSession ? 'rgba(34,197,94,0.04)' : BD.card,
        transform: hov ? 'translateY(-2px)' : 'none',
        transition:'border-color .14s, transform .14s, background .14s' }}>
      <div style={{ position:'relative' }}>
        <ExerciseMedia.Thumbnail exercise={ex} group={g} isAdded={inSession} height={96} />
        <button onClick={e => { e.stopPropagation(); onQuickAdd(); }}
          style={{ position:'absolute', top:7, right:7, width:28, height:28, borderRadius:'50%',
            border:'none', cursor:'pointer',
            background: inSession ? 'rgba(34,197,94,0.88)' : 'rgba(59,130,246,0.88)',
            color:'#fff', fontSize:13, fontWeight:700,
            display:'flex', alignItems:'center', justifyContent:'center',
            backdropFilter:'blur(8px)',
            boxShadow: inSession ? '0 2px 10px rgba(34,197,94,0.45)' : '0 2px 10px rgba(59,130,246,0.45)',
            transition:'transform .12s' }}
          onMouseEnter={e => e.currentTarget.style.transform='scale(1.12)'}
          onMouseLeave={e => e.currentTarget.style.transform='scale(1)'}>
          {inSession ? '✓' : '+'}
        </button>
        <div style={{ position:'absolute', bottom:7, left:8, width:7, height:7, borderRadius:'50%',
          background:lvlColor, boxShadow:`0 0 5px ${lvlColor}88` }} />
      </div>
      <div style={{ padding:'8px 10px 10px' }}>
        <div style={{ fontFamily:'Inter,system-ui', fontSize:11.5, fontWeight:700, color:BD.text,
          display:'-webkit-box', WebkitLineClamp:2, WebkitBoxOrient:'vertical',
          overflow:'hidden', lineHeight:1.35, marginBottom:5 }}>{ex.name}</div>
        <div style={{ display:'flex', gap:5, flexWrap:'wrap' }}>
          {eqMeta && (
            <span style={{ padding:'2px 7px', borderRadius:999,
              background:eqMeta.bg, color:eqMeta.color,
              fontFamily:'Inter,system-ui', fontSize:9, fontWeight:700 }}>{eqMeta.label}</span>
          )}
          {ex.compound && (
            <span style={{ padding:'2px 7px', borderRadius:999,
              background:'rgba(245,158,11,0.12)', color:'#FCD34D',
              fontFamily:'Inter,system-ui', fontSize:9, fontWeight:700 }}>Comp.</span>
          )}
        </div>
      </div>
    </div>
  );
}

// ── Detalle de ejercicio ──────────────────────────────────────────────────────
function ExerciseDetail({ ex, inSession, onAdd, onBack }) {
  const g    = exGroup(ex);
  const cues = ex.cues || [];
  return (
    <div style={{ animation:'fadeIn .18s ease', display:'flex', flexDirection:'column', height:'100%' }}>
      <button onClick={onBack}
        style={{ display:'flex', alignItems:'center', gap:6, background:'none', border:'none',
          cursor:'pointer', padding:'4px 0', marginBottom:16,
          fontFamily:'Inter,system-ui', fontSize:12, color:BD.sub, fontWeight:600, flexShrink:0 }}>
        ← Volver
      </button>
      <div style={{ flex:1, overflowY:'auto', paddingBottom:8 }}>
        <div style={{ borderRadius:14, overflow:'hidden', marginBottom:16 }}>
          <ExerciseMedia.Thumbnail exercise={ex} group={g} isAdded={false} height={160} animate />
        </div>
        <div style={{ marginBottom:14 }}>
          <div style={{ fontFamily:'"Space Grotesk",system-ui', fontSize:22, fontWeight:700,
            color:BD.text, lineHeight:1.2, marginBottom:10 }}>{ex.name}</div>
          <div style={{ display:'flex', flexWrap:'wrap', gap:6 }}>
            {exEquipment(ex).map(eq => {
              const m = EQ_META[eq];
              if (!m) return null;
              return <span key={eq} style={{ padding:'4px 10px', borderRadius:999,
                background:m.bg, color:m.color, fontFamily:'Inter,system-ui', fontSize:10, fontWeight:700 }}>{m.label}</span>;
            })}
            {ex.level && (
              <span style={{ padding:'4px 10px', borderRadius:999,
                background:'rgba(255,255,255,0.06)', color:LVL_COLOR[ex.level] || BD.muted,
                fontFamily:'Inter,system-ui', fontSize:10, fontWeight:700 }}>
                {LVL_LABEL[ex.level]}
              </span>
            )}
            {ex.compound && (
              <span style={{ padding:'4px 10px', borderRadius:999,
                background:'rgba(245,158,11,0.12)', color:'#FCD34D',
                fontFamily:'Inter,system-ui', fontSize:10, fontWeight:700 }}>⚡ Compuesto</span>
            )}
          </div>
        </div>
        <div style={{ marginBottom:18 }}>
          <div style={{ fontFamily:'ui-monospace,Menlo,monospace', fontSize:8.5, fontWeight:700,
            color:BD.muted, letterSpacing:1.5, marginBottom:8 }}>MÚSCULOS</div>
          <div style={{ display:'flex', flexWrap:'wrap', gap:6 }}>
            {(ex.muscles.primary || []).map(m => (
              <span key={m} style={{ padding:'4px 10px', borderRadius:999,
                background:BD.blueDim, color:'#93C5FD',
                fontFamily:'Inter,system-ui', fontSize:10, fontWeight:600 }}>{m}</span>
            ))}
            {(ex.muscles.secondary || []).map(m => (
              <span key={m} style={{ padding:'4px 10px', borderRadius:999,
                background:'rgba(255,255,255,0.05)', color:BD.muted,
                fontFamily:'Inter,system-ui', fontSize:10 }}>{m}</span>
            ))}
          </div>
        </div>
        {cues.length > 0 && (
          <div style={{ marginBottom:22 }}>
            <div style={{ fontFamily:'ui-monospace,Menlo,monospace', fontSize:8.5, fontWeight:700,
              color:BD.muted, letterSpacing:1.5, marginBottom:10 }}>TÉCNICA</div>
            {cues.map((cue, i) => (
              <div key={i} style={{ display:'flex', gap:10, marginBottom:9 }}>
                <span style={{ width:20, height:20, borderRadius:7, background:BD.blueDim,
                  color:'#93C5FD', fontFamily:'ui-monospace,Menlo,monospace', fontSize:9, fontWeight:700,
                  display:'flex', alignItems:'center', justifyContent:'center', flexShrink:0, marginTop:1 }}>{i+1}</span>
                <span style={{ fontFamily:'Inter,system-ui', fontSize:12.5, color:BD.sub, lineHeight:1.55 }}>{cue}</span>
              </div>
            ))}
          </div>
        )}
        {(ex.variants || []).length > 0 && (
          <div style={{ marginBottom:18 }}>
            <div style={{ fontFamily:'ui-monospace,Menlo,monospace', fontSize:8.5, fontWeight:700,
              color:BD.muted, letterSpacing:1.5, marginBottom:8 }}>VARIANTES</div>
            <div style={{ display:'flex', flexWrap:'wrap', gap:6 }}>
              {ex.variants.map(v => (
                <span key={v} style={{ padding:'4px 10px', borderRadius:999,
                  background:'rgba(255,255,255,0.05)', color:BD.sub,
                  fontFamily:'Inter,system-ui', fontSize:10 }}>{v}</span>
              ))}
            </div>
          </div>
        )}
      </div>
      <div style={{ paddingTop:14, flexShrink:0 }}>
        <button onClick={onAdd}
          style={{ width:'100%', padding:'14px 20px', borderRadius:13, cursor:'pointer',
            background: inSession ? 'rgba(34,197,94,0.12)' : BD.blue,
            color: inSession ? BD.green : '#fff', border:'none',
            fontFamily:'Inter,system-ui', fontSize:14, fontWeight:700, letterSpacing:-0.2,
            boxShadow: inSession ? 'none' : '0 8px 28px -8px rgba(59,130,246,0.50)',
            transition:'all .15s' }}>
          {inSession ? '✓ Editar series →' : 'Añadir a sesión →'}
        </button>
      </div>
    </div>
  );
}

// ── Panel de resultados ───────────────────────────────────────────────────────
function ResultsPanel({ filteredExs, suggestions, sessionIds, workout, muscle, query, priorities, onDetail, onQuickAdd, onRemoveEx, onEditEx, onBack, onTogglePriority, mobile }) {
  // On mobile the added exercises live in the always-visible MobileWorkoutBlock,
  // so don't repeat them here.
  const sessionExsInView = mobile ? [] : workout.filter(e => filteredExs.some(f => f.id === e.id));
  const remainingExs     = filteredExs.filter(e => !sessionIds.has(e.id));
  const prio             = muscle ? priorities[muscle] : null;

  return (
    <div style={{ animation:'fadeIn .18s ease', display:'flex', flexDirection:'column', height:'100%' }}>
      <div style={{ marginBottom:16, flexShrink:0 }}>
        {/* Volver a la selección general — nunca dejar al usuario atrapado aquí */}
        {onBack && (
          <button onClick={onBack}
            style={{ display:'inline-flex', alignItems:'center', gap:6, marginBottom:12,
              padding:'6px 12px 6px 9px', borderRadius:999, cursor:'pointer',
              background:'rgba(255,255,255,0.05)', border:`1px solid ${BD.border}`,
              color:BD.sub, fontFamily:'Inter,system-ui', fontSize:11.5, fontWeight:700,
              transition:'all .14s' }}>
            <span style={{ fontSize:14, lineHeight:1 }}>←</span> Volver
          </button>
        )}
        <div style={{ display:'flex', alignItems:'flex-start', justifyContent:'space-between', gap:12, flexWrap:'wrap' }}>
          <div>
            <div style={{ fontFamily:'Inter,system-ui', fontSize:26, fontWeight:900,
              color:BD.text, letterSpacing:-1.5, lineHeight:1 }}>
              {muscle ? MUSCLES[muscle].label : 'Resultados'}
            </div>
            <div style={{ fontFamily:'Inter,system-ui', fontSize:11, color:BD.muted, marginTop:4 }}>
              {filteredExs.length} ejercicio{filteredExs.length !== 1 ? 's' : ''}
              {query.trim() ? ` · "${query.trim()}"` : ''}
            </div>
          </div>
          {muscle && (
            <button onClick={() => onTogglePriority(muscle)}
              style={{ flexShrink:0, padding:'7px 14px', borderRadius:999, border:'none', cursor:'pointer',
                background: prio === 'priority' ? BD.blue
                           : prio === 'maintain' ? 'rgba(34,197,94,0.15)'
                           : prio === 'reducir'  ? 'rgba(245,158,11,0.12)'
                           : 'rgba(255,255,255,0.06)',
                color: prio === 'priority' ? '#fff'
                      : prio === 'maintain' ? BD.green
                      : prio === 'reducir'  ? BD.amber
                      : BD.muted,
                fontFamily:'Inter,system-ui', fontSize:11, fontWeight:700, transition:'all .15s',
                boxShadow: prio === 'priority' ? '0 4px 16px -4px rgba(59,130,246,0.50)' : 'none' }}>
              {prio === 'priority' ? '🎯 Prioridad'
               : prio === 'maintain' ? '✓ Mantener'
               : prio === 'reducir'  ? '↓ Reducir'
               : '+ Priorizar'}
            </button>
          )}
        </div>
      </div>

      <div style={{ flex:1, overflowY:'auto', paddingBottom:8 }}>
        {sessionExsInView.length > 0 && (
          <div style={{ marginBottom:20 }}>
            <div style={{ fontFamily:'ui-monospace,Menlo,monospace', fontSize:8.5, fontWeight:700,
              color:BD.muted, letterSpacing:1, marginBottom:10 }}>EN TU SESIÓN</div>
            {sessionExsInView.map(ex => (
              <ExRow key={ex.id} ex={ex} onRemove={() => onRemoveEx(ex.id)} onEdit={() => onEditEx(ex)} />
            ))}
          </div>
        )}
        {remainingExs.length > 0 && (
          <div>
            {sessionExsInView.length > 0 && (
              <div style={{ fontFamily:'ui-monospace,Menlo,monospace', fontSize:8.5, fontWeight:700,
                color:BD.muted, letterSpacing:1, marginBottom:10 }}>DISPONIBLES</div>
            )}
            <div style={{ display:'grid', gridTemplateColumns:'repeat(2, 1fr)', gap:10 }}>
              {remainingExs.map(ex => (
                <ExerciseCard key={ex.id} ex={ex} inSession={false}
                  onDetail={() => onDetail(ex)}
                  onQuickAdd={() => onQuickAdd(ex)} />
              ))}
            </div>
          </div>
        )}
        {filteredExs.length === 0 && (
          (suggestions && suggestions.length > 0) ? (
            <div>
              <div style={{ padding:'8px 0 12px', textAlign:'center', fontFamily:'Inter,system-ui', fontSize:13, color:BD.sub }}>
                Sin coincidencias exactas para <b style={{ color:BD.text }}>“{query.trim()}”</b>
                <div style={{ fontFamily:'ui-monospace,Menlo,monospace', fontSize:9, fontWeight:700, letterSpacing:1, color:BD.muted, marginTop:10 }}>¿QUISISTE DECIR…?</div>
              </div>
              <div style={{ display:'grid', gridTemplateColumns:'repeat(2, 1fr)', gap:10 }}>
                {suggestions.map(ex => (
                  <ExerciseCard key={ex.id} ex={ex} inSession={sessionIds.has(ex.id)}
                    onDetail={() => onDetail(ex)} onQuickAdd={() => onQuickAdd(ex)} />
                ))}
              </div>
            </div>
          ) : (
            <div style={{ padding:'52px 0', textAlign:'center',
              fontFamily:'Inter,system-ui', fontSize:13, color:BD.muted, lineHeight:1.65 }}>
              Sin resultados<br/>
              <span style={{ fontSize:11 }}>Prueba con otro término o grupo muscular</span>
            </div>
          )
        )}
      </div>
    </div>
  );
}

// ── Fila ejercicio en sesión ──────────────────────────────────────────────────
function ExRow({ ex, onRemove, onEdit }) {
  const [hov, setHov] = React.useState(false);
  const g = exGroup(ex);
  return (
    <div onMouseEnter={() => setHov(true)} onMouseLeave={() => setHov(false)}
      onClick={onEdit}
      style={{ display:'flex', alignItems:'center', borderRadius:12, overflow:'hidden',
        background: hov ? BD.hov : BD.card, border:`1px solid ${BD.border}`,
        marginBottom:8, cursor:'pointer', transition:'background .12s' }}>
      <div style={{ width:54, flexShrink:0 }}>
        <ExerciseMedia.Thumbnail exercise={ex} group={g} isAdded={false} height={54} />
      </div>
      <div style={{ flex:1, padding:'0 11px', minWidth:0 }}>
        <div style={{ fontFamily:'Inter,system-ui', fontSize:12, fontWeight:700, color:BD.text,
          overflow:'hidden', textOverflow:'ellipsis', whiteSpace:'nowrap' }}>{ex.name}</div>
        <div style={{ fontFamily:'Inter,system-ui', fontSize:10, color:BD.muted, marginTop:2 }}>
          {ex.sets.length} serie{ex.sets.length !== 1 ? 's' : ''}
          {ex.sets[0]?.kg ? ` · ${ex.sets[0].kg} kg` : ''}
        </div>
      </div>
      <div style={{ display:'flex', alignItems:'center', gap:8, paddingRight:10 }}>
        <span style={{ fontSize:11, color:BD.green }}>✓</span>
        <button onClick={e => { e.stopPropagation(); onRemove(); }}
          style={{ width:24, height:24, borderRadius:7, border:'none',
            background:'rgba(239,68,68,0.14)', color:'rgba(239,68,68,0.80)',
            cursor:'pointer', fontSize:11, display:'flex', alignItems:'center', justifyContent:'center' }}>
          ✕
        </button>
      </div>
    </div>
  );
}

// ── Configurar series ─────────────────────────────────────────────────────────
function SetConfig({ ex, initSets, onConfirm, onBack }) {
  const [sets, setSets] = React.useState(() =>
    initSets && initSets.length > 0
      ? initSets.map(s => ({ kg: s.kg || '', reps: s.reps || '10', rir: s.rir || '' }))
      : [{ kg:'', reps:'10', rir:'' }, { kg:'', reps:'10', rir:'' }, { kg:'', reps:'10', rir:'' }]
  );
  const upd = (i,f,v) => setSets(p => p.map((s,idx) => idx===i ? {...s,[f]:v} : s));
  const add = () => setSets(p => [...p, { kg:'', reps:'10', rir:'' }]);
  const rem = i => setSets(p => p.filter((_,idx) => idx!==i));
  const g = exGroup(ex);
  const inp = {
    type:'number', min:0,
    style:{ width:'100%', padding:'10px 6px', borderRadius:9, boxSizing:'border-box',
      border:`1px solid ${BD.border}`, background:'rgba(255,255,255,0.04)',
      fontFamily:'ui-monospace,Menlo,monospace', fontSize:14, color:BD.text, textAlign:'center' },
  };
  return (
    <div style={{ animation:'fadeIn .18s ease', display:'flex', flexDirection:'column', height:'100%' }}>
      <button onClick={onBack}
        style={{ display:'flex', alignItems:'center', gap:6, background:'none', border:'none',
          cursor:'pointer', padding:'4px 0', marginBottom:14,
          fontFamily:'Inter,system-ui', fontSize:12, color:BD.sub, fontWeight:600, flexShrink:0 }}>
        ← Volver
      </button>
      <div style={{ borderRadius:14, overflow:'hidden', marginBottom:18, flexShrink:0 }}>
        <ExerciseMedia.Thumbnail exercise={ex} group={g} isAdded={false} height={120} />
        <div style={{ padding:'11px 13px', background:BD.card }}>
          <div style={{ fontFamily:'Inter,system-ui', fontSize:15, fontWeight:700, color:BD.text }}>{ex.name}</div>
          <div style={{ fontFamily:'Inter,system-ui', fontSize:11, color:BD.muted, marginTop:2 }}>
            {ex.muscles.primary.join(' · ')}
          </div>
        </div>
      </div>
      <div style={{ flex:1, overflowY:'auto' }}>
        <div style={{ display:'grid', gridTemplateColumns:'20px 1fr 1fr 48px 20px', gap:6, marginBottom:8 }}>
          {['','Kg','Reps','RIR',''].map((h,i) => (
            <span key={i} style={{ fontFamily:'ui-monospace,Menlo,monospace', fontSize:8,
              color:BD.muted, fontWeight:700, textAlign:'center', letterSpacing:0.5 }}>{h}</span>
          ))}
        </div>
        {sets.map((set, i) => (
          <div key={i} style={{ display:'grid', gridTemplateColumns:'20px 1fr 1fr 48px 20px', gap:6, alignItems:'center', marginBottom:7 }}>
            <span style={{ fontFamily:'ui-monospace,Menlo,monospace', fontSize:10, color:BD.muted, textAlign:'center' }}>{i+1}</span>
            <input {...inp} value={set.kg}   placeholder="—"  step={0.5} onChange={e => upd(i,'kg',  e.target.value)} />
            <input {...inp} value={set.reps} placeholder="10" step={1}   onChange={e => upd(i,'reps',e.target.value)} />
            <input {...inp} value={set.rir || ''} placeholder="—" min={0} max={5} step={1} onChange={e => upd(i,'rir',e.target.value)} />
            {sets.length > 1
              ? <button onClick={() => rem(i)} aria-label={`Quitar serie ${i+1}`} title="Quitar serie" style={{ width:24, height:24, border:'none', background:'rgba(239,68,68,0.14)',
                  color:'rgba(239,68,68,0.90)', borderRadius:5, cursor:'pointer', fontSize:11,
                  display:'flex', alignItems:'center', justifyContent:'center', padding:0 }}>✕</button>
              : <div />}
          </div>
        ))}
        <button onClick={add}
          style={{ marginTop:4, marginBottom:18, padding:'7px 12px', borderRadius:9,
            border:'1px dashed rgba(255,255,255,0.15)', background:'transparent',
            cursor:'pointer', fontFamily:'Inter,system-ui', fontSize:11, fontWeight:600, color:BD.sub }}>
          + Serie
        </button>
        <button onClick={() => onConfirm(ex, sets)}
          style={{ width:'100%', padding:'14px 20px', borderRadius:13, cursor:'pointer',
            background:'#2563EB', color:'#fff', border:'none',
            fontFamily:'Inter,system-ui', fontSize:14, fontWeight:700, letterSpacing:-0.2,
            boxShadow:'0 8px 28px -8px rgba(59,130,246,0.50)' }}>
          Añadir · {sets.length} serie{sets.length !== 1 ? 's' : ''} →
        </button>
      </div>
    </div>
  );
}

// ── Panel vacío ───────────────────────────────────────────────────────────────
// Scroll to + focus the search so "＋ Añadir ejercicio" always leads somewhere.
function focusBuilderSearch() {
  const el  = document.getElementById('builder-search');
  const inp = document.getElementById('builder-search-input');
  if (el) el.scrollIntoView({ behavior: 'smooth', block: 'start' });
  setTimeout(() => { try { if (inp) inp.focus(); } catch (e) {} }, 220);
}

// Mobile: the routine-in-construction is ALWAYS visible while building, with a
// persistent "＋ Añadir ejercicio" that returns to the search/selector. This is
// what makes adding the 2nd, 3rd, Nth exercise identical to the 1st.
function MobileWorkoutBlock({ workout, duration, onEdit, onRemove, onAdd }) {
  return (
    <div style={{ marginBottom:18, borderRadius:16, border:`1px solid ${BD.border}`, background:BD.card, padding:'14px 14px 16px' }}>
      <div style={{ display:'flex', alignItems:'baseline', justifyContent:'space-between', marginBottom:12 }}>
        <div style={{ fontFamily:'ui-monospace,Menlo,monospace', fontSize:9, fontWeight:700, color:BD.muted, letterSpacing:1.2 }}>TU RUTINA</div>
        <div style={{ fontFamily:'Inter,system-ui', fontSize:12, color:BD.sub, fontWeight:600 }}>
          {workout.length} ejercicio{workout.length !== 1 ? 's' : ''} · ~{duration} min
        </div>
      </div>
      <div style={{ display:'flex', flexDirection:'column', gap:8 }}>
        {workout.map(ex => (
          <ExRow key={ex.id} ex={ex} onRemove={() => onRemove(ex.id)} onEdit={() => onEdit(ex)} />
        ))}
      </div>
      <button onClick={onAdd}
        style={{ width:'100%', marginTop:12, minHeight:48, borderRadius:12, cursor:'pointer',
          border:'1px dashed rgba(59,130,246,0.45)', background:'rgba(59,130,246,0.08)', color:'#93C5FD',
          fontFamily:'Inter,system-ui', fontSize:15, fontWeight:800, display:'flex', alignItems:'center', justifyContent:'center', gap:8 }}>
        <span style={{ fontSize:19, lineHeight:1 }}>＋</span> Añadir ejercicio
      </button>
    </div>
  );
}

function EmptyPanel({ onPick, priorities }) {
  const hasPrios = Object.values(priorities).some(v => v === 'priority');
  return (
    <div style={{ paddingTop:8 }}>
      <div style={{ fontFamily:'"Space Grotesk",system-ui', fontWeight:600,
        fontSize:22, color:BD.sub, marginBottom:10, lineHeight:1.2 }}>
        {hasPrios ? 'Tu plan de desarrollo' : 'Construye tu físico'}
      </div>
      <p style={{ fontFamily:'Inter,system-ui', fontSize:13, color:BD.muted,
        lineHeight:1.65, margin:'0 0 4px', maxWidth:320 }}>
        {hasPrios
          ? 'Elige un grupo muscular arriba (o toca el mapa) para ver sus músculos, o busca un ejercicio directamente. Tu perfil de prioridades guía las recomendaciones.'
          : 'Elige un grupo muscular arriba (Pecho, Espalda, Hombro…) o toca el mapa para ver sus músculos. Después busca o añade ejercicios.'}
      </p>
    </div>
  );
}

// ── Barra de sesión sticky ────────────────────────────────────────────────────
function BookmarkIcon({ filled, color }) {
  return (
    <svg width="14" height="14" viewBox="0 0 24 24" fill={filled ? color : 'none'} stroke={color} strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" style={{ flexShrink:0 }}>
      <path d="M6 3h12a1 1 0 0 1 1 1v17l-7-4-7 4V4a1 1 0 0 1 1-1z" />
    </svg>
  );
}

function WorkoutBar({ workout, saved, routineSaved, duration, onSave, onSaveRoutine, onStart, mobile }) {
  // Save-to-Mis-rutinas (template) — bookmark, clearly distinct from "register session"
  const saveRoutineBtn = (
    <button onClick={onSaveRoutine}
      title="Guardar como plantilla reutilizable en Mis rutinas"
      style={{ flex: mobile ? 1 : '0 0 auto', minHeight: mobile ? 46 : undefined, padding:'11px 16px', borderRadius:12, cursor:'pointer',
        background: routineSaved ? 'rgba(34,197,94,0.15)' : 'transparent',
        color: routineSaved ? BD.green : '#93C5FD', border:`1px solid ${routineSaved ? 'rgba(34,197,94,0.3)' : 'rgba(59,130,246,0.28)'}`,
        fontFamily:'Inter,system-ui', fontSize:13, fontWeight:700, transition:'all .25s', whiteSpace:'nowrap',
        display:'inline-flex', alignItems:'center', justifyContent:'center', gap:7 }}>
      {routineSaved ? '✓ Guardada' : <><BookmarkIcon color="#93C5FD" />{mobile ? 'Guardar rutina' : 'Guardar en Mis rutinas'}</>}
    </button>
  );
  // Register today's session (awards gems) — clearly a log action, not a template
  const registerBtn = (
    <button onClick={onSave}
      title="Registrar el entrenamiento de hoy en tu historial"
      style={{ flex: mobile ? 1 : '0 0 auto', minHeight: mobile ? 46 : undefined, padding:'11px 18px', borderRadius:12, border:'none', cursor:'pointer',
        background: saved ? 'rgba(34,197,94,0.15)' : BD.blue,
        color: saved ? BD.green : '#fff',
        fontFamily:'Inter,system-ui', fontSize:13, fontWeight:700, transition:'all .25s', whiteSpace:'nowrap' }}>
      {saved ? '✓ Registrada' : 'Registrar sesión'}
    </button>
  );
  const startBtn = (
    <button onClick={onStart}
      style={{ flexShrink:0, minHeight: mobile ? 46 : undefined, padding:'11px 18px', borderRadius:12, border:'none', cursor:'pointer',
        background:'#22C55E', color:'#fff',
        fontFamily:'Inter,system-ui', fontSize:13, fontWeight:700, transition:'all .25s', whiteSpace:'nowrap',
        boxShadow:'0 4px 16px rgba(34,197,94,0.35)' }}>
      ▶ Iniciar
    </button>
  );

  const wrap = {
    position:'fixed', left:0, right:0, zIndex:200,
    // Mobile: sit just above the fixed bottom tab bar so neither is covered
    bottom: mobile ? 'calc(56px + env(safe-area-inset-bottom))' : 0,
    background:'rgba(6,13,24,0.97)', backdropFilter:'blur(24px)',
    borderTop:'1px solid rgba(255,255,255,0.08)',
    padding: mobile ? '10px 14px' : '12px 32px',
  };

  // Mobile: two rows so the two save buttons never truncate
  if (mobile) {
    return (
      <div style={{ ...wrap, display:'flex', flexDirection:'column', gap:8 }}>
        <div style={{ display:'flex', alignItems:'center', gap:10 }}>
          <span style={{ flex:1, fontFamily:'Inter,system-ui', fontSize:12, fontWeight:600, color:BD.sub, whiteSpace:'nowrap', overflow:'hidden', textOverflow:'ellipsis' }}>
            {workout.length} ejercicio{workout.length !== 1 ? 's' : ''} · ~{duration} min
          </span>
          {startBtn}
        </div>
        <div style={{ display:'flex', gap:8 }}>
          {saveRoutineBtn}
          {registerBtn}
        </div>
      </div>
    );
  }

  return (
    <div style={{ ...wrap, display:'flex', alignItems:'center', gap:12 }}>
      <div style={{ flex:1, display:'flex', gap:6, overflow:'hidden' }}>
        {workout.slice(0, 5).map(ex => {
          const gs = ExerciseMedia.GROUP_STYLE[exGroup(ex)] || ExerciseMedia.GROUP_STYLE.core;
          return (
            <span key={ex.id} style={{ display:'flex', alignItems:'center', gap:6,
              padding:'5px 10px', borderRadius:999, flexShrink:0,
              background:'rgba(255,255,255,0.06)', border:`1px solid ${BD.border}`,
              fontFamily:'Inter,system-ui', fontSize:11, fontWeight:600, color:BD.sub,
              maxWidth:140, overflow:'hidden' }}>
              <span style={{ width:6, height:6, borderRadius:'50%', background:gs?.to||BD.blue, flexShrink:0 }} />
              <span style={{ overflow:'hidden', textOverflow:'ellipsis', whiteSpace:'nowrap' }}>{ex.name}</span>
            </span>
          );
        })}
        {workout.length > 5 && (
          <span style={{ padding:'5px 10px', borderRadius:999, flexShrink:0,
            background:'rgba(255,255,255,0.04)',
            fontFamily:'Inter,system-ui', fontSize:11, color:BD.muted }}>
            +{workout.length - 5}
          </span>
        )}
      </div>
      <span style={{ fontFamily:'ui-monospace,Menlo,monospace', fontSize:11, color:BD.muted, flexShrink:0 }}>
        ~{duration} min
      </span>
      {startBtn}
      {saveRoutineBtn}
      {registerBtn}
    </div>
  );
}

// ── Componente principal ──────────────────────────────────────────────────────
function BuilderSection() {
  const { state, actions } = useStore();
  const { navigate }       = useRoute();
  const mobile             = useWidth() < 680;

  const [view,       setView]      = React.useState('front');
  const [muscle,     setMuscle]    = React.useState(null);
  const [query,      setQuery]     = React.useState('');
  const [mode,       setMode]      = React.useState('empty');
  const [detailEx,   setDetailEx]  = React.useState(null);
  const [cfgEx,      setCfgEx]     = React.useState(null);
  const [cfgSets,    setCfgSets]   = React.useState(null);
  const [cfgFromDet, setCfgFromDet]= React.useState(false);
  const [workout,    setWorkout]   = React.useState([]);
  const [saved,      setSaved]     = React.useState(false);
  const [routineSaved, setRoutineSaved] = React.useState(false);
  const [showPlan,   setShowPlan]  = React.useState(false);

  // Toast: { msg, link?: {label, to}, action?: {label, onClick} }
  const [toast, setToast] = React.useState(null);
  const toastTimer = React.useRef(null);
  function showToast(t, ms = 4500) {
    setToast(t);
    if (toastTimer.current) clearTimeout(toastTimer.current);
    toastTimer.current = setTimeout(() => setToast(null), ms);
  }

  // Priorities persist in localStorage
  const [priorities, setPrioritiesRaw] = React.useState(() => {
    try { return JSON.parse(localStorage.getItem('atlas.priorities') || '{}'); } catch { return {}; }
  });
  function setPriorities(next) {
    setPrioritiesRaw(next);
    try { localStorage.setItem('atlas.priorities', JSON.stringify(next)); } catch {}
  }

  function togglePriority(id) {
    setPriorities(prev => {
      const cur = prev[id];
      const next = cur === 'priority' ? 'maintain'
                 : cur === 'maintain' ? 'reducir'
                 : cur === 'reducir'  ? null
                 : 'priority';
      const copy = { ...prev };
      if (next === null) delete copy[id]; else copy[id] = next;
      return copy;
    });
  }

  const allExs = React.useMemo(() => ExerciseService.getAll(), []);

  const [coachBanner, setCoachBanner] = React.useState(null);

  React.useEffect(() => {
    const raw  = localStorage.getItem('atlas.pendingWorkout');
    const meta = (() => { try { return JSON.parse(localStorage.getItem('atlas.pendingWorkoutMeta') || 'null'); } catch { return null; } })();
    if (!raw) return;
    try {
      const exs = JSON.parse(raw);
      if (Array.isArray(exs) && exs.length > 0) {
        localStorage.removeItem('atlas.pendingWorkout');
        if (meta) localStorage.removeItem('atlas.pendingWorkoutMeta');
        // Builder only ever *loads* a workout into the canvas; training itself
        // runs in the unified player (/player).
        setWorkout(exs);
        if (meta) setCoachBanner(meta);
      }
    } catch {}
  }, []);

  const filteredExs = React.useMemo(() => {
    let exs = allExs;
    if (muscle) exs = exsForMuscle(muscle, exs);
    const q = query.trim();
    // Fuzzy, accent/word-order/typo-tolerant search over name·músculos·patrón·equipo.
    if (q) exs = ExerciseService.searchIn(exs, q);
    return exs;
  }, [allExs, muscle, query]);

  // "¿Quisiste decir…?" — near matches when the exact search finds nothing.
  const searchSuggestions = React.useMemo(() => {
    const q = query.trim();
    if (!q || filteredExs.length > 0) return [];
    return ExerciseService.suggest(q, 6);
  }, [query, filteredExs.length]);

  // Sets per muscle in current workout session
  const sessionSets = React.useMemo(() => {
    const acc = {};
    for (const ex of workout) {
      const g = exGroup(ex);
      acc[g] = (acc[g] || 0) + (ex.sets?.length || 0);
    }
    return acc;
  }, [workout]);

  const sessionIds = React.useMemo(() => new Set(workout.map(e => e.id)), [workout]);
  const duration   = React.useMemo(() => sessionDuration(workout), [workout]);
  const hasFilter  = muscle !== null || query.trim() !== '';

  // keepView: when the muscle was picked from the body map the user is already
  // looking at the right side, so don't flip the view out from under them (the
  // anatomical map draws several muscles on both sides). Chip picks still switch
  // to the canonical view so the selected muscle is visible.
  function pickMuscle(id, keepView) {
    setMuscle(id);
    setMode('results');
    if (keepView) return;
    const def = MUSCLES[id];
    if (def?.view === 'back')  setView('back');
    if (def?.view === 'front') setView('front');
  }

  // Leave a muscle's exercise view and return to the general selection, so the
  // user is never stranded inside one muscle without a way out.
  function backToSelection() {
    setMuscle(null);
    setQuery('');
    setMode('empty');
  }

  function handleMuscleChip(id) {
    if (id === null) {
      setMuscle(null);
      if (mode !== 'detail' && mode !== 'config') setMode(query.trim() ? 'results' : 'empty');
    } else {
      pickMuscle(id);
    }
  }

  function handleQuery(q) {
    setQuery(q);
    if (mode !== 'detail' && mode !== 'config') {
      setMode(q.trim() || muscle ? 'results' : 'empty');
    }
  }

  function openDetail(ex) { setDetailEx(ex); setMode('detail'); }

  function openConfig(ex, fromDetail) {
    setCfgEx(ex);
    setCfgSets(workout.find(e => e.id === ex.id)?.sets || null);
    setCfgFromDet(!!fromDetail);
    setMode('config');
  }

  function confirmSets(ex, sets) {
    setWorkout(prev =>
      prev.find(e => e.id === ex.id)
        ? prev.map(e => e.id === ex.id ? { ...e, sets } : e)
        : [...prev, { ...ex, sets }]
    );
    setMode(hasFilter ? 'results' : 'empty');
  }

  function removeEx(id) { setWorkout(prev => prev.filter(e => e.id !== id)); }
  function editEx(ex) { openConfig(ex, false); }

  // Register today's session in the history/log. This counts toward the weekly
  // goal and achievements but awards NO gems — session rewards are earned only by
  // completing a tracked session in the player. Saving a reusable template is a
  // separate, persistent action in the bottom bar ("Guardar rutina").
  function save() {
    if (!workout.length) return;
    const snapshot = workout;
    actions.logSession(snapshot.map(ex => ({
      id: ex.id, name: ex.name, group: exGroup(ex),
      muscles: ex.muscles.primary, sets: ex.sets,
    })));
    setSaved(true);
    showToast({ msg: 'Sesión registrada en tu historial' }, 3000);
    setTimeout(() => { setSaved(false); setWorkout([]); setMuscle(null); setQuery(''); setMode('empty'); }, 3000);
  }

  // Save a workout as a reusable routine template ("Mis rutinas"). Accepts an
  // optional snapshot so it can be triggered from the post-registration toast
  // even after the live workout has been cleared.
  function saveAsRoutine(wkt) {
    const src = (wkt && wkt.length) ? wkt : workout;
    if (!src.length) return;
    const muscles = [...new Set(src.flatMap(ex =>
      Array.isArray(ex.muscles) ? ex.muscles : (ex.muscles?.primary || [])))].slice(0, 4);
    const totalSets = src.reduce((t, ex) => t + (ex.sets?.length || 0), 0);
    const dateLabel = new Date().toLocaleDateString('es-ES', { day: 'numeric', month: 'short' });
    const name = coachBanner?.routineName
      || (muscles.length ? `Rutina ${muscles.slice(0, 2).join(' + ')}` : `Rutina ${dateLabel}`);
    const routine = {
      id: `routine-${Date.now()}`,
      name,
      objective: null,
      source: 'builder',
      createdAt: Date.now(),
      sessions: [{
        name: coachBanner?.sessionName || 'Sesión 1',
        exercises: src.map(ex => ({
          id: ex.id, name: ex.name, group: exGroup(ex),
          muscles: Array.isArray(ex.muscles) ? { primary: ex.muscles, secondary: [] } : (ex.muscles || { primary: [], secondary: [] }),
          pattern: ex.pattern,
          sets: ex.sets,
          setsCount: ex.sets?.length,
          repsRange: ex.repsRange || (ex.sets?.[0]?.reps ? String(ex.sets[0].reps) : '8-12'),
          rir: ex.rir ?? 2,
          rest: ex.rest || '90s',
        })),
        totalSets,
        muscles,
      }],
    };
    actions.saveRoutine(routine);
    setRoutineSaved(true);
    showToast({ msg: 'Guardada en Mis rutinas ✓', link: { label: 'Ver', to: '/rutinas' } });
    setTimeout(() => setRoutineSaved(false), 2800);
  }

  function loadRoutineDay(dayIndex) {
    const routine = arLoad();
    if (!routine?.sessions?.[dayIndex]) return;
    const session = routine.sessions[dayIndex];
    const exs = session.exercises || [];
    if (!exs.length) return;
    setWorkout(exs);
    setCoachBanner(prev => prev ? { ...prev, sessionIndex: dayIndex, sessionName: session.name } : null);
  }

  // Single training player: hand the session to the immersive player at /player
  // (same flow as the Coach) instead of a separate in-Builder execution view.
  function startExecution() {
    if (!workout.length) return;
    const exercises = workout.map(ex => ({
      id: ex.id, name: ex.name, group: exGroup(ex),
      muscles: Array.isArray(ex.muscles) ? { primary: ex.muscles, secondary: [] } : (ex.muscles || { primary: [], secondary: [] }),
      pattern: ex.pattern,
      sets: ex.sets,
      setsCount: ex.sets?.length,
      repsRange: ex.repsRange || (ex.sets?.[0]?.reps ? String(ex.sets[0].reps) : '8-12'),
      rir: ex.rir ?? 2,
      rest: ex.rest || '90s',
    }));
    try {
      localStorage.setItem('atlas.pendingWorkout', JSON.stringify(exercises));
      localStorage.setItem('atlas.pendingWorkoutMeta', JSON.stringify({ ...(coachBanner || {}), mode: 'player' }));
    } catch (e) {}
    setCoachBanner(null);
    navigate('/player');
  }

  const renderMode = mode === 'empty' && hasFilter ? 'results' : mode;

  // Extra bottom padding so the fixed WorkoutBar (2 rows on mobile) never covers
  // the last content — e.g. the "Generar Plan →" button — when a workout is loaded.
  const contentPadBottom = mobile ? (workout.length > 0 ? 210 : 120) : (workout.length > 0 ? 140 : 120);

  return (
    <section style={{ minHeight:'100vh', background:BD.page }}>
      <div style={{ maxWidth:1060, margin:'0 auto', padding: mobile ? `48px 16px ${contentPadBottom}px` : `64px 28px ${contentPadBottom}px` }}>

        {coachBanner && (
          <div style={{
            padding:'12px 18px', borderRadius:12,
            background:'rgba(37,99,235,0.07)', border:'1px solid rgba(37,99,235,0.16)',
            marginBottom:20, animation:'fadeIn .25s ease',
          }}>
            {/* Row 1: label, name, actions */}
            <div style={{ display:'flex', alignItems:'center', gap:8 }}>
              <span style={{ fontFamily:'ui-monospace,Menlo,monospace', fontSize:9, fontWeight:700, color:'#93C5FD', letterSpacing:1.1, flexShrink:0 }}>ATLAS COACH</span>
              <span style={{ fontFamily:'Inter,system-ui', fontSize:12, fontWeight:600, color:'rgba(147,197,253,0.80)', flex:1, overflow:'hidden', textOverflow:'ellipsis', whiteSpace:'nowrap' }}>
                {coachBanner.routineName || 'Plan generado'}
                {!coachBanner.totalSessions || coachBanner.totalSessions <= 1
                  ? (coachBanner.sessionName ? ` · ${coachBanner.sessionName}` : '')
                  : ` · ${coachBanner.sessionName || `Día ${(coachBanner.sessionIndex || 0) + 1}`}`}
              </span>
              {workout.length > 0 && !mobile && (
                <button onClick={startExecution} style={{ background:'#22C55E', border:'none', borderRadius:8, color:'#fff', padding:'6px 13px', cursor:'pointer', fontSize:12, fontWeight:700, fontFamily:'Inter,system-ui', flexShrink:0, boxShadow:'0 3px 12px rgba(34,197,94,0.30)' }}>
                  ▶ Iniciar
                </button>
              )}
              <button
                title="Descargar rutina completa en PDF"
                onClick={() => {
                  if (typeof exportRoutinePDF === 'undefined') return;
                  try {
                    const full = JSON.parse(localStorage.getItem('atlas.routine.active.v1') || 'null');
                    if (full && Array.isArray(full.sessions) && full.sessions.length) {
                      exportRoutinePDF(full);
                    } else {
                      exportRoutinePDF({ name: coachBanner.routineName || 'Rutina', sessions: [{ name: coachBanner.sessionName || 'Día 1', exercises: workout }] });
                    }
                  } catch { exportRoutinePDF({ name: coachBanner.routineName || 'Rutina', sessions: [{ name: coachBanner.sessionName || 'Día 1', exercises: workout }] }); }
                }}
                style={{ padding:'5px 10px', borderRadius:7, border:'1px solid rgba(59,130,246,0.25)', background:'rgba(59,130,246,0.07)', color:'#93C5FD', fontFamily:'Inter,system-ui', fontSize:11, fontWeight:700, cursor:'pointer', flexShrink:0 }}
              >↓ PDF</button>
              <button onClick={() => setCoachBanner(null)} style={{ background:'none', border:'none', cursor:'pointer', color:'rgba(147,197,253,0.35)', fontSize:14, padding:'2px 6px', flexShrink:0 }}>✕</button>
            </div>
            {/* Mobile: prominent full-width start button */}
            {workout.length > 0 && mobile && (
              <button onClick={startExecution} style={{ width:'100%', marginTop:12, background:'#22C55E', border:'none', borderRadius:11, color:'#fff', padding:'14px', cursor:'pointer', fontSize:16, fontWeight:800, fontFamily:'Inter,system-ui', boxShadow:'0 4px 16px rgba(34,197,94,0.32)' }}>
                ▶ Iniciar entrenamiento
              </button>
            )}
            {/* Row 2: day tabs for multi-session plans */}
            {coachBanner.totalSessions > 1 && (
              <div style={{ display:'flex', gap:6, marginTop:10, flexWrap:'wrap' }}>
                {Array.from({ length: coachBanner.totalSessions }, (_, i) => {
                  const isActive = i === (coachBanner.sessionIndex || 0);
                  return (
                    <button key={i} onClick={() => loadRoutineDay(i)} style={{
                      padding:'4px 12px', borderRadius:7,
                      border:`1px solid ${isActive ? 'rgba(59,130,246,0.45)' : 'rgba(59,130,246,0.15)'}`,
                      background: isActive ? 'rgba(59,130,246,0.14)' : 'transparent',
                      color: isActive ? '#93C5FD' : 'rgba(147,197,253,0.40)',
                      fontFamily:'Inter,system-ui', fontSize:11, fontWeight:700,
                      cursor: isActive ? 'default' : 'pointer', transition:'all .15s',
                    }}>
                      Día {i + 1}
                    </button>
                  );
                })}
              </div>
            )}
          </div>
        )}

        {toast && (
          <div style={{ position:'fixed', zIndex:400,
            left: mobile ? 16 : 'auto', right: mobile ? 16 : 24,
            bottom: mobile ? 'calc(178px + env(safe-area-inset-bottom))' : 96,
            background:'#0F1A2E', color:BD.text, padding:'12px 16px',
            borderRadius:14, fontFamily:'Inter,system-ui', fontSize:13, fontWeight:700,
            animation:'fadeIn .25s ease', boxShadow:'0 12px 40px rgba(0,0,0,0.55)', border:'1px solid rgba(255,255,255,0.10)',
            display:'flex', alignItems:'center', gap:14, justifyContent:'space-between' }}>
            <span>{toast.msg}</span>
            {toast.link && (
              <button onClick={() => { setToast(null); navigate(toast.link.to); }}
                style={{ flexShrink:0, border:'none', cursor:'pointer', background:'rgba(59,130,246,0.16)', color:'#93C5FD', borderRadius:9, padding:'7px 14px', fontFamily:'Inter,system-ui', fontSize:13, fontWeight:800 }}>
                {toast.link.label} →
              </button>
            )}
            {toast.action && (
              <button onClick={() => { toast.action.onClick(); }}
                style={{ flexShrink:0, border:'1px solid rgba(59,130,246,0.35)', cursor:'pointer', background:'transparent', color:'#93C5FD', borderRadius:9, padding:'7px 14px', fontFamily:'Inter,system-ui', fontSize:13, fontWeight:800 }}>
                {toast.action.label}
              </button>
            )}
          </div>
        )}

        <div style={{ marginBottom: mobile ? 20 : 28 }}>
          <h1 style={{ fontFamily:'Inter,system-ui', fontWeight:900,
            fontSize: mobile ? 26 : 38, color:BD.text, letterSpacing:-2,
            lineHeight:1, margin:0 }}>
            Construye tu físico.{' '}
            <span style={{ fontFamily:'"Space Grotesk",system-ui', fontWeight:400, color:BD.sub, letterSpacing:-0.5 }}>
              De forma inteligente.
            </span>
          </h1>
        </div>

        <SearchBar query={query} onQuery={handleQuery} muscle={muscle} onMuscle={handleMuscleChip} priorities={priorities} />

        <div style={{ display:'flex', gap: mobile ? 0 : 44,
          alignItems:'flex-start', flexDirection: mobile ? 'column' : 'row' }}>

          {/* ── Mapa corporal ── (below the add-panel on mobile) */}
          <div style={{ width: mobile ? '100%' : 320, flexShrink:0, marginBottom: mobile ? 28 : 0, order: mobile ? 2 : 0 }}>

            {/* Atlas Score lives in Progreso (it needs real training data); the
                build screen stays focused on choosing exercises. */}

            <div style={{ display:'flex', gap:3, marginBottom:16,
              background:'rgba(255,255,255,0.04)', borderRadius:10, padding:3 }}>
              {[['front','Frontal'],['back','Posterior']].map(([v,lbl]) => (
                <button key={v} onClick={() => setView(v)}
                  style={{ flex:1, padding:'8px 0', borderRadius:7, border:'none', cursor:'pointer',
                    background: view === v ? BD.blue : 'transparent',
                    color: view === v ? '#fff' : BD.muted,
                    fontFamily:'Inter,system-ui', fontSize:11, fontWeight:700, transition:'all .14s' }}>
                  {lbl}
                </button>
              ))}
            </div>
            <div style={{ maxWidth: mobile ? 320 : 'none', margin: mobile ? '0 auto' : '0' }}>
              <BodyMap view={view} selected={muscle} onPick={id => pickMuscle(id, true)} priorities={priorities} />
            </div>

            {/* Panel científico — aparece cuando hay músculo seleccionado */}
            {muscle && (
              <div style={{ marginTop:12 }}>
                <SciencePanel
                  muscleId={muscle}
                  priorities={priorities}
                  log={state.log}
                  sessionSets={sessionSets}
                  onTogglePriority={togglePriority}
                />
              </div>
            )}

            {/* Volume panel — resumen global de todas las prioridades */}
            <div style={{ marginTop:12 }}>
              <WeeklyVolumePanel priorities={priorities} log={state.log} sessionSets={sessionSets} />
            </div>

            {/* Balance panel — desequilibrios musculares */}
            <BalancePanel log={state.log} sessionSets={sessionSets} />

            {/* Coach recommendation — split + volumen + estructura científica */}
            <CoachRecommendationPanel
              priorities={priorities}
              log={state.log}
              sessionSets={sessionSets}
            />

            {(Object.keys(priorities).length > 0 || workout.length > 0) && (
              <div style={{ marginTop:12, display:'flex', flexDirection:'column', gap:6 }}>
                <button onClick={() => setShowPlan(true)}
                  style={{ width:'100%', padding:'12px 0', borderRadius:10, border:'none',
                    cursor:'pointer',
                    background:'linear-gradient(135deg, #1D4ED8 0%, #2563EB 100%)',
                    color:'white', fontFamily:'Inter,system-ui', fontSize:12.5, fontWeight:700,
                    letterSpacing:0.2, boxShadow:'0 4px 20px rgba(37,99,235,0.40)',
                    transition:'opacity .15s' }}>
                  Generar Plan →
                </button>
                {workout.length > 0 && (
                  <button onClick={() => navigate('/coach')}
                    style={{ width:'100%', padding:'9px 0', borderRadius:10,
                      border:'1px solid rgba(59,130,246,0.22)', background:'rgba(59,130,246,0.06)',
                      color:'#93C5FD', fontFamily:'Inter,system-ui', fontSize:11.5, fontWeight:700,
                      cursor:'pointer' }}>
                    Analizar con Coach →
                  </button>
                )}
              </div>
            )}
          </div>

          {/* ── Panel contextual ── (above the map on mobile) */}
          <div style={{ flex:1, minWidth:0, width: mobile ? '100%' : undefined, order: mobile ? 1 : 0,
            maxHeight: mobile ? 'none' : 700, overflowY: mobile ? 'visible' : 'auto' }}>

            {/* Mobile: routine-in-construction always visible with a persistent add button */}
            {mobile && workout.length > 0 && renderMode !== 'config' && renderMode !== 'detail' && (
              <MobileWorkoutBlock
                workout={workout}
                duration={duration}
                onEdit={editEx}
                onRemove={removeEx}
                onAdd={() => { handleQuery(''); focusBuilderSearch(); }}
              />
            )}

            {/* Welcome panel only when NOT already building (otherwise it's a dead end) */}
            {renderMode === 'empty' && !(mobile && workout.length > 0) && (
              <EmptyPanel onPick={pickMuscle} priorities={priorities} />
            )}

            {renderMode === 'results' && (
              <ResultsPanel
                filteredExs={filteredExs}
                suggestions={searchSuggestions}
                sessionIds={sessionIds}
                workout={workout}
                muscle={muscle}
                query={query}
                priorities={priorities}
                onDetail={openDetail}
                onQuickAdd={ex => openConfig(ex, false)}
                onRemoveEx={removeEx}
                onEditEx={editEx}
                onBack={backToSelection}
                onTogglePriority={togglePriority}
                mobile={mobile}
              />
            )}

            {renderMode === 'detail' && detailEx && (
              <ExerciseDetail
                ex={detailEx}
                inSession={sessionIds.has(detailEx.id)}
                onAdd={() => openConfig(detailEx, true)}
                onBack={() => setMode(hasFilter ? 'results' : 'empty')}
              />
            )}

            {renderMode === 'config' && cfgEx && (
              <SetConfig
                key={cfgEx.id}
                ex={cfgEx}
                initSets={cfgSets}
                onConfirm={confirmSets}
                onBack={() => {
                  if (cfgFromDet && detailEx) setMode('detail');
                  else setMode(hasFilter ? 'results' : 'empty');
                }}
              />
            )}
          </div>
        </div>
      </div>

      {workout.length > 0 && (
        <WorkoutBar
          workout={workout} saved={saved} routineSaved={routineSaved} duration={duration}
          onSave={save} onSaveRoutine={saveAsRoutine} onStart={startExecution} mobile={mobile}
        />
      )}

      {showPlan && (
        <PlanModal
          priorities={priorities}
          log={state.log}
          sessionSets={sessionSets}
          workout={workout}
          onClose={() => setShowPlan(false)}
          onGoLab={() => { setShowPlan(false); navigate('/laboratorio'); }}
        />
      )}
    </section>
  );
}

Object.assign(window, { BuilderSection });
