import { useState, useCallback, useEffect, useRef, useMemo } from "react";

// ─────────────────────────────────────────────────────────────────────────────
// CONSTANTS
// ─────────────────────────────────────────────────────────────────────────────
const COLORS = [
  { label:"Amber · Seguridad",     val:"#F59E0B" },
  { label:"Sky · Tecnología",      val:"#0EA5E9" },
  { label:"Emerald · Salud",       val:"#10B981" },
  { label:"Red · Emergencias",     val:"#EF4444" },
  { label:"Violet · Negocios",     val:"#8B5CF6" },
  { label:"Orange · Construcción", val:"#F97316" },
];

const QUICK_INSERTS = [
  { label:"💡 Tip",       code:`<div class="callout tip">💡 <strong>Consejo:</strong> Escribe tu consejo aquí.</div>` },
  { label:"⚠️ Atención",  code:`<div class="callout warning">⚠️ <strong>Atención:</strong> Escribe tu advertencia aquí.</div>` },
  { label:"🚫 Peligro",   code:`<div class="callout danger">🚫 <strong>Peligro:</strong> Describe el riesgo aquí.</div>` },
  { label:"📋 Norma",     code:`<div class="callout law">📋 <strong>Normativa:</strong> Texto de la norma o regulación.</div>` },
  { label:"ℹ️ Info",      code:`<div class="callout info">ℹ️ <strong>Dato importante:</strong> Escribe la información aquí.</div>` },
  { label:"📊 Tabla",     code:`<table class="data-table"><thead><tr><th>Columna 1</th><th>Columna 2</th><th>Columna 3</th></tr></thead><tbody><tr><td>Dato A</td><td>Dato B</td><td>Dato C</td></tr><tr><td>Dato D</td><td>Dato E</td><td>Dato F</td></tr></tbody></table>` },
  { label:"🔢 Pasos",     code:`<div class="steps"><div class="step"><div class="step-n">1</div><div class="step-body"><strong>Primer paso</strong><p>Descripción del primer paso.</p></div></div><div class="step"><div class="step-n">2</div><div class="step-body"><strong>Segundo paso</strong><p>Descripción del segundo paso.</p></div></div><div class="step"><div class="step-n">3</div><div class="step-body"><strong>Tercer paso</strong><p>Descripción del tercer paso.</p></div></div></div>` },
  { label:"🔽 Acordeón",  code:`<div class="accordion"><div class="acc-item"><div class="acc-head" onclick="toggleAcc(this)">▶ Pregunta o título expandible</div><div class="acc-body">Contenido que se despliega al hacer click. Puedes poner texto, listas o cualquier HTML aquí.</div></div><div class="acc-item"><div class="acc-head" onclick="toggleAcc(this)">▶ Segundo ítem del acordeón</div><div class="acc-body">Más contenido oculto.</div></div></div>` },
  { label:"↔️ Comparar",  code:`<div class="compare"><div class="compare-col good"><div class="compare-label">✅ Correcto / Buenas prácticas</div><ul><li>Práctica correcta 1</li><li>Práctica correcta 2</li></ul></div><div class="compare-col bad"><div class="compare-label">❌ Incorrecto / Errores comunes</div><ul><li>Error común 1</li><li>Error común 2</li></ul></div></div>` },
  { label:"🔢 Stats",     code:`<div class="stat-row"><div class="stat"><div class="stat-n">85%</div><div class="stat-l">Descripción del dato estadístico</div></div><div class="stat"><div class="stat-n">3x</div><div class="stat-l">Otro indicador importante</div></div><div class="stat"><div class="stat-n">+40</div><div class="stat-l">Un tercer dato relevante</div></div></div>` },
  { label:"🔲 Tarjetas",  code:`<div class="icon-grid"><div class="icon-item"><div class="icon-circle">🔧</div><strong>Elemento 1</strong><p>Descripción breve</p></div><div class="icon-item"><div class="icon-circle">⚡</div><strong>Elemento 2</strong><p>Descripción breve</p></div><div class="icon-item"><div class="icon-circle">🎯</div><strong>Elemento 3</strong><p>Descripción breve</p></div><div class="icon-item"><div class="icon-circle">📌</div><strong>Elemento 4</strong><p>Descripción breve</p></div></div>` },
  { label:"💬 Cita",      code:`<blockquote class="quote">Escribe aquí la cita, norma o referencia legal que quieres destacar. — <cite>Fuente o autor</cite></blockquote>` },
  { label:"📦 Concepto",  code:`<div class="concept accent"><h4>Título del concepto</h4><p>Explicación detallada del concepto. Puedes escribir varios párrafos aquí con toda la información necesaria.</p></div>` },
  { label:"🖼️ Imagen",    code:`<div class="img-placeholder"><div class="img-ph-ico">🖼️</div><div class="img-ph-label">Reemplaza este bloque con una imagen real</div><div class="img-ph-hint">Edita el HTML y usa: &lt;img src="data:..." style="max-width:100%;border-radius:8px"&gt;</div></div>` },
  { label:"🎮 Botón",     code:`<div style="text-align:center;margin:16px 0"><button class="btn-inline" onclick="alert('Acción del botón')">🎯 Texto del botón</button></div>` },
];

const SCORM_API = `var API=null;
function findAPI(w){var t=0;while(!w.API&&w.parent&&w.parent!==w&&t++<10)w=w.parent;return w.API||null;}
window.onload=function(){API=findAPI(window);if(API){API.LMSInitialize("");API.LMSSetValue("cmi.core.lesson_status","incomplete");}};
window.onunload=function(){if(API){API.LMSCommit("");API.LMSFinish("");}};`;

const CSS_REFERENCE = `CLASES CSS DISPONIBLES:
concept/concept accent, steps/step/step-n/step-body, callout tip/warning/danger/info/law,
icon-grid/icon-item/icon-circle, data-table, hl/hl danger/hl success, section-title,
accordion/acc-item/acc-head(onclick="toggleAcc(this)")/acc-body, compare/compare-col good/bad,
blockquote.quote, icon-list/li-icon, stat-row/stat/stat-n/stat-l, img-placeholder, btn-inline`;

// ─────────────────────────────────────────────────────────────────────────────
// UTILS
// ─────────────────────────────────────────────────────────────────────────────
function esc(s){ return String(s||"").replace(/&/g,"&amp;").replace(/</g,"&lt;").replace(/>/g,"&gt;").replace(/"/g,"&quot;").replace(/'/g,"&#39;"); }
function hexRgb(h){ return [parseInt(h.slice(1,3),16),parseInt(h.slice(3,5),16),parseInt(h.slice(5,7),16)].join(","); }

async function extractPPTX(file) {
  const JSZip = window.JSZip;
  if (!JSZip) throw new Error("JSZip no disponible");
  const zip = await JSZip.loadAsync(file);
  const keys = Object.keys(zip.files).filter(k=>/^ppt\/slides\/slide\d+\.xml$/.test(k))
    .sort((a,b)=>parseInt(a.match(/\d+/)[0])-parseInt(b.match(/\d+/)[0]));
  const slides = [];
  for (const k of keys) {
    const xml = await zip.files[k].async("string");
    const texts=[], rx=/<a:t(?:\s[^>]*)?>([^<]+)<\/a:t>/g; let m;
    while((m=rx.exec(xml))!==null){ const t=m[1].trim(); if(t)texts.push(t); }
    if(texts.length) slides.push(texts.join(" | "));
  }
  return slides;
}

async function readB64(file) {
  return new Promise((res,rej)=>{ const r=new FileReader(); r.onload=()=>res(r.result.split(",")[1]); r.onerror=rej; r.readAsDataURL(file); });
}

async function extractPDF(file) {
  if (!window.pdfjsLib) {
    await new Promise((res, rej) => {
      const sc = document.createElement("script");
      sc.src = "https://cdnjs.cloudflare.com/ajax/libs/pdf.js/3.11.174/pdf.min.js";
      sc.onload = res; sc.onerror = rej;
      document.head.appendChild(sc);
    });
    window.pdfjsLib.GlobalWorkerOptions.workerSrc =
      "https://cdnjs.cloudflare.com/ajax/libs/pdf.js/3.11.174/pdf.worker.min.js";
  }
  const ab = await file.arrayBuffer();
  const pdf = await window.pdfjsLib.getDocument({ data: ab }).promise;
  const pages = [];
  for (let i = 1; i <= Math.min(pdf.numPages, 60); i++) {
    const page = await pdf.getPage(i);
    const tc = await page.getTextContent();
    const text = tc.items.map(it => it.str).join(" ").trim();
    if (text) pages.push("[Pagina " + i + "]: " + text);
  }
  return pages;
}

// ─────────────────────────────────────────────────────────────────────────────
// CLAUDE API
// ─────────────────────────────────────────────────────────────────────────────
async function callClaude(messages, sys, maxTok=1000) {
  // Proxy does not accept `system` param — prepend as context in first user message
  const msgsWithSys = sys
    ? [{ role: "user", content: "INSTRUCCIONES DEL SISTEMA:\n" + sys + "\n\n---\nAhora procede con la tarea:" },
       { role: "assistant", content: "Entendido. Procedo." },
       ...messages]
    : messages;

  let r, d;
  try {
    r = await fetch("https://api.anthropic.com/v1/messages", {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({
        model: "claude-sonnet-4-20250514",
        max_tokens: maxTok,
        messages: msgsWithSys,
      }),
    });
  } catch(netErr) {
    throw new Error("Sin conexion a internet: " + netErr.message);
  }
  try { d = await r.json(); } catch(_) {
    throw new Error("HTTP " + r.status + " — respuesta invalida del servidor.");
  }
  if (!r.ok || d.error) {
    const msg = (d.error && (d.error.message || JSON.stringify(d.error))) || ("Error HTTP " + r.status);
    const type = (d.error && d.error.type) || "";
    if (type === "overloaded_error") throw new Error("API saturada. Espera e intenta de nuevo.");
    if (r.status === 401) throw new Error("API key invalida (401).");
    if (r.status === 429) throw new Error("Demasiadas solicitudes (429). Espera un momento.");
    throw new Error(msg);
  }
  if (!d.content || !d.content.length) throw new Error("Respuesta vacia. Intenta de nuevo.");
  return d.content.filter(function(b){ return b.type === "text"; }).map(function(b){ return b.text; }).join("");
}

function safeJSON(raw) {
  if(!raw) throw new Error("Respuesta vacía");
  let s = raw.replace(/^```[\w]*\s*/m,"").replace(/\s*```\s*$/m,"").trim();
  const st=s.indexOf("{"), en=s.lastIndexOf("}");
  if(st===-1||en===-1) throw new Error("No hay JSON en la respuesta");
  s=s.slice(st,en+1);
  try{ return JSON.parse(s); }catch(_){}
  s=s.replace(/,(\s*[}\]])/g,"$1");
  s=s.replace(/"((?:[^"\\]|\\.)*)"/g,m=>m.replace(/\n/g,"\\n").replace(/\r/g,"").replace(/\t/g," "));
  try{ return JSON.parse(s); }catch(_){}
  const title=(s.match(/"title"\s*:\s*"([^"]+)"/))||[,"Sin título"];
  const modNames=[...s.matchAll(/"name"\s*:\s*"([^"]+)"/g)].map(m=>m[1]).slice(0,8);
  const modDescs=[...s.matchAll(/"desc"\s*:\s*"([^"]+)"/g)].map(m=>m[1]);
  if(modNames.length===0) throw new Error("No se pudo extraer la estructura. Intenta de nuevo.");
  const modules=modNames.map((name,i)=>({id:i+1,name,desc:modDescs[i]||name}));
  const terms=[...s.matchAll(/"term"\s*:\s*"([^"]+)"/g)].map(m=>m[1]);
  const defs=[...s.matchAll(/"def"\s*:\s*"([^"]+)"/g)].map(m=>m[1]);
  return { title:title[1], audience:"General", duration:"4 horas", modules, glossary:terms.map((t,i)=>({term:t,def:defs[i]||t})), quizzes:[] };
}

// ─────────────────────────────────────────────────────────────────────────────
// PASS 1 — Outline
// ─────────────────────────────────────────────────────────────────────────────
async function getOutline(file, courseName) {
  const isPDF = file.name.endsWith(".pdf");
  let messages;
  if(isPDF){
    const pages = await extractPDF(file);
    const text = pages.join("\n").slice(0, 12000);
    messages=[{role:"user",content:"Extrae estructura del curso" + (courseName ? ` \"${courseName}\"` : "") + ". Devuelve SOLO JSON.\n\nCONTENIDO:\n" + text}];
  } else {
    const slides=await extractPPTX(file);
    messages=[{role:"user",content:"Extrae estructura del curso" + (courseName ? ` \"${courseName}\"` : "") + ". Devuelve SOLO JSON:\n\n" + slides.map((s,i)=>"[" + (i+1) + "]: " + s).join("\n").slice(0,12000)}];
  }
  const sys=`Extrae estructura de curso e-learning. Devuelve ÚNICAMENTE JSON empezando con {. Sin markdown. Sin apostrofes ni comillas dentro de strings.
{"title":"Titulo","audience":"Publico objetivo","duration":"X horas","modules":[{"id":1,"name":"Nombre","desc":"1 linea"}],"glossary":[{"term":"Termino","def":"Definicion"}],"quizzes":[{"mod":1,"qs":[{"q":"Pregunta?","opts":["A","B","C","D"],"c":0}]}]}
4-7 módulos, 6-10 glosario, 3 preguntas por módulo. Todo en español.`;
  const raw=await callClaude(messages,sys);
  return safeJSON(raw);
}

// ─────────────────────────────────────────────────────────────────────────────
// PASS 2 — Rich HTML per module
// ─────────────────────────────────────────────────────────────────────────────
async function generateModuleHTML(file, outline, mod, pptxSlides) {
  const otherMods = outline.modules.filter(m=>m.id!==mod.id).map(m=>m.name).join(", ");
  const prompt = `Genera el HTML COMPLETO y RICO para el MÓDULO ${mod.id}: "${mod.name}" del curso "${outline.title}".
Público: ${outline.audience}. Descripción: ${mod.desc}. Otros módulos (NO los desarrolles): ${otherMods}.

REQUISITOS:
- Usa el contenido REAL del documento fuente
- Mínimo 500 palabras específicas del tema
- Usa MUCHOS componentes distintos: callouts, steps, tables, icon-grid, accordion, compare, stats, quotes, etc.
- Devuelve SOLO HTML puro, sin html/head/body/style/script. Empieza directo con el contenido.`;

  const docCtx = (pptxSlides||[]).map((s,i)=>"[" + (i+1) + "]: " + s).join("\n").slice(0,10000);
  const messages=[{role:"user",content:"DOCUMENTO:\n" + docCtx + "\n\n---\n" + prompt}];
  const sys=`Eres experto en diseño instruccional e-learning. Generas HTML rico para cursos SCORM.
${CSS_REFERENCE}
Devuelve ÚNICAMENTE HTML del contenido usando las clases indicadas. Contenido real, específico y abundante.`;
  const raw = await callClaude(messages,sys);
  return raw.replace(/^```[\w]*\s*/m,"").replace(/\s*```\s*$/m,"").trim();
}

// ─────────────────────────────────────────────────────────────────────────────
// AI EDIT — Modify existing module HTML
// ─────────────────────────────────────────────────────────────────────────────
async function aiEditModule(currentHTML, instruction, moduleName, courseTitle) {
  const sys=`Eres experto en diseño instruccional e-learning. Editas HTML de módulos SCORM.

${CSS_REFERENCE}

REGLAS CRÍTICAS:
1. Devuelve ÚNICAMENTE el HTML del contenido del módulo. SIN etiquetas html/head/body.
2. Puedes (y debes) usar <style> y <script> embebidos cuando el usuario pide algo interactivo.
3. Cuando el usuario pide calculadoras, simuladores, quizzes, formularios, sliders u otros elementos interactivos: IMPLEMÉNTALOS con HTML+CSS+JS real y funcional. NO los reemplaces con texto.
4. Para calculadoras: usa <input type="number">, botones con onclick, y muestra resultados dinámicos.
5. Para sliders: usa <input type="range"> con actualización en tiempo real via oninput.
6. Usa IDs únicos para evitar conflictos (ej: "calc-hh-${Date.now()}").
7. Estilos inline o en <style> scoped. Diseño moderno y consistente con el curso.
8. Conserva el contenido existente salvo que se pida eliminarlo.

EJEMPLOS DE LO QUE DEBES GENERAR:
- "agrega una calculadora de HH" → <div> con inputs numéricos, botón calcular, resultado visible
- "agrega un quiz" → preguntas con radio buttons y feedback inmediato
- "agrega un slider" → <input type="range"> con valor visible que cambia en tiempo real`;

  const messages=[{role:"user",content:`Módulo: "${moduleName}" — Curso: "${courseTitle}"

HTML ACTUAL:
${currentHTML}

INSTRUCCIÓN:
${instruction}

Devuelve el HTML completo del módulo con los cambios aplicados. Si la instrucción pide algo interactivo, impleméntalo completamente con JS funcional.`}];
  const raw = await callClaude(messages, sys, 4000);
  return raw.replace(/^```[\w]*\s*/,"").replace(/\s*```\s*$/,"").trim();
}

// ─────────────────────────────────────────────────────────────────────────────
// BUILD SCORM HTML
// ─────────────────────────────────────────────────────────────────────────────
function buildSCORM(outline, moduleHTMLs, accentColor, withEval) {
  const {title,audience,duration,modules=[],glossary=[],quizzes=[]}=outline;
  const ac=accentColor, rgb=hexRgb(ac);
  const screens=["cover",...modules.map(m=>`m${m.id}`),...(withEval?modules.map(m=>`m${m.id}q`):[]),"gloss"];

  const sideItems=modules.map(m=>`
    <div class="sb-item" id="nav-m${m.id}" onclick="nav('m${m.id}')">
      <div class="sb-n">${m.id}</div><span>${esc(m.name)}</span><span class="ck">&#10003;</span>
    </div>
    ${withEval?`<div class="sb-item" id="nav-m${m.id}q" onclick="nav('m${m.id}q')">
      <div class="sb-n">&#128221;</div><span>Quiz M${m.id}</span><span class="ck">&#10003;</span>
    </div>`:""}`).join("");

  const modCards=modules.map(m=>`
    <div class="mcard" onclick="nav('m${m.id}')">
      <div class="mn">${m.id}</div>
      <div><div class="mt">${esc(m.name)}</div><div class="md">${esc(m.desc)}</div></div>
    </div>`).join("");

  const modScreens=modules.map(m=>{
    const nextId=withEval?`m${m.id}q`:(m.id<modules.length?`m${m.id+1}`:"gloss");
    return `
<div class="screen" id="screen-m${m.id}">
  <div class="mhdr"><div class="mhdr-n">${m.id}</div><div><h2>${esc(m.name)}</h2><p>${esc(m.desc)}</p></div></div>
  <div class="module-content">${moduleHTMLs[m.id]||`<p>${esc(m.desc)}</p>`}</div>
  <div class="nav-row">
    ${m.id>1?`<button class="btn btn-o" onclick="nav('m${m.id-1}')">&#8592; Anterior</button>`:"<div></div>"}
    <button class="btn btn-p" onclick="nav('${nextId}')">Continuar &#8594;</button>
  </div>
</div>`}).join("\n");

  const quizScreens=withEval?modules.map(m=>{
    const qdata=quizzes.find(q=>q.mod===m.id)||{qs:[]};
    const nextId=m.id<modules.length?`m${m.id+1}`:"gloss";
    const qSafe=encodeURIComponent(JSON.stringify(qdata.qs||[]));
    return `
<div class="screen" id="screen-m${m.id}q">
  <div class="qhdr"><div class="qi">&#128221;</div><div><h2>Evaluaci&#243;n &#8212; ${esc(m.name)}</h2><p>Selecciona la respuesta correcta</p></div></div>
  <div id="qc-${m.id}"></div>
  <script>(function(){
    var Q=JSON.parse(decodeURIComponent('${qSafe}'));
    var ST={sel:{},a:{},done:false};
    function R(){
      var c=document.getElementById('qc-${m.id}');if(!c)return;
      var h='';
      Q.forEach(function(q,qi){
        h+='<div class="qcard"><p class="qn">Pregunta '+(qi+1)+' de '+Q.length+'</p><p class="qt">'+q.q+'</p><div class="opts">';
        q.opts.forEach(function(o,oi){
          var cls='opt';
          if(ST.done){cls+=' locked';if(oi===q.c)cls+=' ok';else if(oi===ST.a[qi]&&ST.a[qi]!==q.c)cls+=' ng';}
          else if(ST.sel[qi]===oi)cls+=' sel';
          h+='<div class="'+cls+'" onclick="pk${m.id}('+qi+','+oi+')"><span class="ol">'+['A','B','C','D'][oi]+'</span><span>'+o+'</span></div>';
        });h+='</div></div>';
      });
      if(!ST.done){
        var ans=Object.keys(ST.sel).length;
        h+='<div class="quiz-footer"><span class="quiz-ct">'+ans+'/'+Q.length+' respondidas</span><button class="btn btn-p" onclick="sb${m.id}()" '+(ans<Q.length?'disabled':'')+'>Enviar</button></div>';
      } else {
        var cor=Q.filter(function(q,qi){return ST.a[qi]===q.c;}).length;
        var pct=Math.round(cor/Q.length*100),pass=pct>=60;
        h+='<div class="qres '+(pass?'pass':'fail')+'"><div class="qres-ico">'+(pass?'&#9989;':'&#10060;')+'</div>';
        h+='<div class="qres-t">'+(pass?'Aprobado':'No aprobado')+'</div><div class="qres-p">'+pct+'% ('+cor+'/'+Q.length+')</div>';
        if(pass)h+='<button class="btn btn-p" onclick="nav(\'${nextId}\')">Continuar &#8594;</button>';
        else h+='<button class="btn btn-o" onclick="rt${m.id}()">Reintentar &#8635;</button>';
        h+='</div>';
      }
      c.innerHTML=h;
    }
    window.pk${m.id}=function(qi,oi){if(ST.done)return;ST.sel[qi]=oi;R();};
    window.sb${m.id}=function(){ST.a=Object.assign({},ST.sel);ST.done=true;R();};
    window.rt${m.id}=function(){ST={sel:{},a:{},done:false};R();};
    if(document.readyState==='loading'){document.addEventListener('DOMContentLoaded',R);}else{setTimeout(R,0);}
  })()\<\/script>
</div>`}).join("\n"):"";

  const glossCards=(glossary||[]).map(g=>`<div class="gloss-card"><div class="gloss-term">${esc(g.term)}</div><div class="gloss-def">${esc(g.def)}</div></div>`).join("\n");
  const TI=[`"cover":"Portada"`,`"gloss":"Glosario"`,...modules.map(m=>`"m${m.id}":${JSON.stringify(m.name)}`),...(withEval?modules.map(m=>`"m${m.id}q":${JSON.stringify("Quiz — "+m.name)}`):[])].join(",");

  return `<!DOCTYPE html>
<html lang="es"><head>
<meta charset="UTF-8"><meta name="viewport" content="width=device-width,initial-scale=1">
<title>${esc(title)}</title>
<link href="https://fonts.googleapis.com/css2?family=DM+Sans:ital,wght@0,400;0,500;0,600;0,700;1,400&display=swap" rel="stylesheet">
<script src="scorm_api.js"><\/script>
<style>
:root{--ac:${ac};--rgb:${rgb};--s9:#0F172A;--s8:#1E293B;--s7:#334155;--s6:#475569;--s5:#64748B;--s4:#94A3B8;--s3:#CBD5E1;--s2:#E2E8F0;--s1:#F1F5F9;--s0:#F8FAFC;--w:#fff;--r:10px;--shadow:0 1px 6px rgba(0,0,0,.07),0 4px 16px rgba(0,0,0,.05)}
*{box-sizing:border-box;margin:0;padding:0}
body{font-family:'DM Sans',sans-serif;background:var(--s0);color:var(--s8);height:100vh;overflow:hidden}
.app{display:flex;height:100vh;overflow:hidden}
.sb{width:265px;background:var(--s9);display:flex;flex-direction:column;flex-shrink:0;overflow-y:auto}
.sb-brand{padding:15px 15px 11px;border-bottom:1px solid rgba(255,255,255,.07)}
.sb-logo{font-size:12px;font-weight:700;color:var(--w);line-height:1.4}
.sb-sub{font-size:11px;color:var(--s4);margin-top:2px}
.sb-sec{padding:9px 15px 4px;font-size:10px;font-weight:700;color:var(--s5);text-transform:uppercase;letter-spacing:1px}
.sb-item{display:flex;align-items:center;gap:8px;padding:7px 15px;cursor:pointer;color:var(--s4);font-size:12px;font-weight:500;transition:background .12s}
.sb-item:hover,.sb-item.active{background:rgba(255,255,255,.06);color:var(--w)}
.sb-item.done .ck{color:var(--ac);opacity:1}
.sb-n{width:20px;height:20px;border-radius:5px;background:rgba(255,255,255,.07);display:flex;align-items:center;justify-content:center;font-size:10px;font-weight:700;flex-shrink:0}
.sb-item.active .sb-n{background:var(--ac);color:var(--w)}
.sb-item>span:nth-child(2){flex:1;line-height:1.3}
.ck{opacity:0;font-size:11px}
.sb-prog{padding:11px 15px;border-top:1px solid rgba(255,255,255,.07);margin-top:auto}
.sp-lbl{font-size:11px;color:var(--s4);display:flex;justify-content:space-between;margin-bottom:4px}
.sp-bar{height:3px;background:rgba(255,255,255,.1);border-radius:2px}
.sp-fill{height:100%;background:var(--ac);border-radius:2px;transition:width .5s}
.main-area{flex:1;display:flex;flex-direction:column;overflow:hidden}
.topbar{height:50px;background:var(--w);border-bottom:1px solid var(--s2);display:flex;align-items:center;gap:9px;padding:0 18px;flex-shrink:0;box-shadow:0 1px 3px rgba(0,0,0,.04)}
.tb-title{flex:1;font-size:13px;font-weight:600;color:var(--s7);overflow:hidden;text-overflow:ellipsis;white-space:nowrap}
.btn{padding:7px 13px;border-radius:7px;font-size:12px;font-weight:600;cursor:pointer;border:none;font-family:'DM Sans',sans-serif;transition:all .15s;line-height:1}
.btn-p{background:var(--ac);color:#fff}.btn-p:hover{opacity:.9}.btn-p:disabled{opacity:.35;cursor:not-allowed}
.btn-o{background:transparent;border:1.5px solid var(--ac);color:var(--ac)}.btn-o:hover{background:rgba(var(--rgb),.07)}
.content{flex:1;overflow-y:auto;padding:20px 26px}
.screen{display:none;animation:fi .2s ease;max-width:860px}.screen.active{display:block}
@keyframes fi{from{opacity:0;transform:translateY(5px)}to{opacity:1;transform:none}}
.chero{background:linear-gradient(135deg,var(--s9),var(--s8));border-radius:13px;padding:32px 28px;margin-bottom:16px;color:#fff;position:relative;overflow:hidden}
.chero::before{content:'';position:absolute;right:-30px;top:-30px;width:180px;height:180px;background:rgba(var(--rgb),.15);border-radius:50%}
.ctitle{font-size:24px;font-weight:700;line-height:1.2;margin-bottom:7px;position:relative}
.csub{font-size:13px;color:var(--s4);margin-bottom:18px;position:relative}
.cmeta{display:flex;gap:9px;flex-wrap:wrap;position:relative}
.chip{background:rgba(255,255,255,.1);border-radius:6px;padding:5px 10px;font-size:12px}
.chip span{color:var(--ac);font-weight:600}
.mgrid{display:grid;grid-template-columns:repeat(auto-fill,minmax(210px,1fr));gap:9px}
.mcard{background:var(--w);border:1px solid var(--s2);border-radius:var(--r);padding:12px 13px;display:flex;align-items:flex-start;gap:9px;cursor:pointer;transition:all .15s;box-shadow:var(--shadow)}
.mcard:hover{box-shadow:0 4px 20px rgba(var(--rgb),.2);border-color:rgba(var(--rgb),.4);transform:translateY(-1px)}
.mn{width:27px;height:27px;border-radius:7px;background:var(--ac);color:#fff;display:flex;align-items:center;justify-content:center;font-weight:700;font-size:12px;flex-shrink:0}
.mt{font-size:12px;font-weight:600;margin-bottom:2px;line-height:1.3}.md{font-size:11px;color:var(--s4);line-height:1.4}
.mhdr{background:linear-gradient(135deg,var(--s9),var(--s8));border-radius:11px;padding:18px 20px;display:flex;align-items:center;gap:13px;margin-bottom:16px;color:#fff}
.mhdr-n{width:44px;height:44px;border-radius:9px;background:var(--ac);color:#fff;display:flex;align-items:center;justify-content:center;font-size:19px;font-weight:700;flex-shrink:0}
.mhdr h2{font-size:17px;font-weight:700;margin-bottom:3px}.mhdr p{font-size:11px;color:var(--s4)}
.nav-row{display:flex;justify-content:space-between;align-items:center;margin-top:22px;padding-top:14px;border-top:1px solid var(--s2)}
.module-content{font-size:14px;line-height:1.7;color:var(--s7)}
.module-content h3{font-size:17px;font-weight:700;color:var(--s8);margin:20px 0 9px}
.module-content h4{font-size:15px;font-weight:600;color:var(--s8);margin:16px 0 7px}
.module-content p{margin-bottom:11px}
.module-content ul,.module-content ol{margin:0 0 13px 18px}
.module-content li{margin-bottom:4px}
.concept{background:var(--w);border:1px solid var(--s2);border-radius:var(--r);padding:14px 16px;margin:12px 0;box-shadow:var(--shadow)}
.concept h4{margin-top:0;margin-bottom:6px;color:var(--s8)}
.concept.accent{border-left:3px solid var(--ac);background:rgba(var(--rgb),.04)}
.concept.accent h4{color:var(--ac)}
.steps{margin:12px 0;display:flex;flex-direction:column;gap:9px}
.step{display:flex;gap:11px;align-items:flex-start}
.step-n{width:26px;height:26px;border-radius:50%;background:var(--ac);color:#fff;display:flex;align-items:center;justify-content:center;font-weight:700;font-size:12px;flex-shrink:0;margin-top:1px}
.step-body{flex:1}.step-body strong{display:block;font-size:13px;color:var(--s8);margin-bottom:2px}
.step-body p{margin:0;color:var(--s6);font-size:13px}
.callout{border-radius:8px;padding:11px 13px;margin:11px 0;font-size:13px;line-height:1.6}
.callout.tip{background:#F0FDF4;border:1px solid #BBF7D0;color:#166534}
.callout.warning{background:#FFFBEB;border:1px solid #FDE68A;color:#92400E}
.callout.danger{background:#FEF2F2;border:1px solid #FECACA;color:#991B1B}
.callout.info{background:#EFF6FF;border:1px solid #BFDBFE;color:#1E40AF}
.callout.law{background:#F5F3FF;border:1px solid #DDD6FE;color:#4C1D95}
.icon-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(140px,1fr));gap:9px;margin:12px 0}
.icon-item{background:var(--w);border:1px solid var(--s2);border-radius:9px;padding:13px 11px;text-align:center;box-shadow:var(--shadow)}
.icon-circle{font-size:26px;margin-bottom:7px}
.icon-item strong{display:block;font-size:12px;color:var(--s8);margin-bottom:3px}
.icon-item p{font-size:11px;color:var(--s5);line-height:1.4;margin:0}
.data-table{width:100%;border-collapse:collapse;margin:12px 0;font-size:13px;box-shadow:var(--shadow);border-radius:var(--r);overflow:hidden}
.data-table th{background:var(--s8);color:var(--w);padding:8px 11px;text-align:left;font-size:11px;font-weight:600;text-transform:uppercase;letter-spacing:.4px}
.data-table td{padding:8px 11px;border-bottom:1px solid var(--s2);color:var(--s7)}
.data-table tr:last-child td{border-bottom:none}
.data-table tr:nth-child(even) td{background:var(--s1)}
.hl{background:rgba(var(--rgb),.15);color:var(--ac);padding:1px 5px;border-radius:4px;font-weight:600;font-size:13px}
.hl.danger{background:#FEE2E2;color:#991B1B}.hl.success{background:#D1FAE5;color:#065F46}
.section-title{font-size:15px;font-weight:700;color:var(--s8);border-bottom:2px solid var(--ac);padding-bottom:5px;margin:18px 0 11px}
.accordion{margin:12px 0;display:flex;flex-direction:column;gap:5px}
.acc-item{background:var(--w);border:1px solid var(--s2);border-radius:var(--r);overflow:hidden}
.acc-head{padding:10px 13px;cursor:pointer;font-size:13px;font-weight:600;color:var(--s8);user-select:none;transition:background .15s}
.acc-head:hover{background:var(--s1)}.acc-head.open{background:rgba(var(--rgb),.06);color:var(--ac)}
.acc-body{max-height:0;overflow:hidden;transition:max-height .3s ease,padding .3s;padding:0 13px;font-size:13px;color:var(--s6);line-height:1.6}
.acc-body.open{max-height:600px;padding:9px 13px}
.compare{display:grid;grid-template-columns:1fr 1fr;gap:9px;margin:12px 0}
.compare-col{border-radius:var(--r);padding:13px 14px}
.compare-col.good{background:#F0FDF4;border:1px solid #BBF7D0}
.compare-col.bad{background:#FEF2F2;border:1px solid #FECACA}
.compare-label{font-size:12px;font-weight:700;margin-bottom:7px}
.compare-col.good .compare-label{color:#166534}.compare-col.bad .compare-label{color:#991B1B}
.compare-col ul{margin-left:14px;font-size:12px;color:var(--s7)}
.quote{border-left:3px solid var(--ac);background:rgba(var(--rgb),.05);padding:11px 14px;border-radius:0 var(--r) var(--r) 0;margin:12px 0;font-style:italic;font-size:13px;color:var(--s7);line-height:1.7}
.quote cite{display:block;margin-top:5px;font-style:normal;font-weight:600;font-size:12px;color:var(--s5)}
.icon-list{list-style:none;margin:0 0 13px;display:flex;flex-direction:column;gap:5px}
.icon-list li{display:flex;align-items:flex-start;gap:7px;font-size:13px;color:var(--s7);line-height:1.5}
.li-icon{flex-shrink:0;font-size:14px;width:18px;text-align:center}
.stat-row{display:flex;gap:9px;flex-wrap:wrap;margin:12px 0}
.stat{background:var(--w);border:1px solid var(--s2);border-radius:var(--r);padding:13px 16px;flex:1;min-width:110px;text-align:center;box-shadow:var(--shadow)}
.stat-n{font-size:26px;font-weight:700;color:var(--ac);line-height:1}
.stat-l{font-size:11px;color:var(--s5);margin-top:3px;line-height:1.3}
.img-placeholder{background:var(--s1);border:2px dashed var(--s3);border-radius:var(--r);padding:28px;text-align:center;margin:12px 0}
.img-ph-ico{font-size:32px;margin-bottom:7px}
.img-ph-label{font-size:13px;font-weight:600;color:var(--s5);margin-bottom:4px}
.img-ph-hint{font-size:11px;color:var(--s4)}
.btn-inline{background:var(--ac);color:#fff;border:none;padding:9px 20px;border-radius:8px;font-size:13px;font-weight:600;cursor:pointer;font-family:inherit;transition:opacity .15s}
.btn-inline:hover{opacity:.85}
.qhdr{background:linear-gradient(135deg,#1E3A8A,#1E40AF);border-radius:11px;padding:15px 18px;display:flex;gap:11px;align-items:center;margin-bottom:14px;color:#fff}
.qi{width:38px;height:38px;background:rgba(255,255,255,.15);border-radius:8px;display:flex;align-items:center;justify-content:center;font-size:18px;flex-shrink:0}
.qhdr h2{font-size:14px;font-weight:700;margin-bottom:2px}.qhdr p{font-size:11px;color:#93C5FD}
.qcard{background:var(--w);border:1px solid var(--s2);border-radius:var(--r);padding:13px 15px;margin-bottom:8px;box-shadow:var(--shadow)}
.qn{font-size:10px;font-weight:700;color:var(--ac);text-transform:uppercase;letter-spacing:.5px;margin-bottom:3px}
.qt{font-size:13px;font-weight:500;margin-bottom:10px;line-height:1.5}
.opts{display:flex;flex-direction:column;gap:5px}
.opt{display:flex;align-items:center;gap:7px;padding:7px 10px;border-radius:6px;border:1.5px solid var(--s2);cursor:pointer;font-size:12px;transition:all .12s;color:var(--s7)}
.opt:hover:not(.locked){border-color:rgba(var(--rgb),.5);background:rgba(var(--rgb),.05)}
.opt.sel{border-color:var(--ac);background:rgba(var(--rgb),.08);color:var(--s8);font-weight:500}
.opt.ok{border-color:#16A34A;background:#F0FDF4;color:#166534;font-weight:500}
.opt.ng{border-color:#DC2626;background:#FEF2F2;color:#991B1B}
.opt.locked{cursor:default}
.ol{width:18px;height:18px;border-radius:4px;background:var(--s1);display:flex;align-items:center;justify-content:center;font-size:10px;font-weight:700;flex-shrink:0}
.opt.ok .ol{background:#DCFCE7;color:#166534}.opt.ng .ol{background:#FEE2E2;color:#991B1B}.opt.sel .ol{background:var(--ac);color:#fff}
.quiz-footer{display:flex;align-items:center;justify-content:space-between;margin-top:14px;padding-top:11px;border-top:1px solid var(--s2)}
.quiz-ct{font-size:12px;color:var(--s5)}
.qres{text-align:center;padding:24px;background:var(--w);border:1px solid var(--s2);border-radius:var(--r);box-shadow:var(--shadow)}
.qres.pass{border-color:#16A34A;background:#F0FDF4}.qres.fail{border-color:#DC2626;background:#FEF2F2}
.qres-ico{font-size:36px;margin-bottom:7px}
.qres-t{font-size:18px;font-weight:700;margin-bottom:3px}
.qres.pass .qres-t{color:#166534}.qres.fail .qres-t{color:#991B1B}
.qres-p{font-size:13px;color:var(--s5);margin-bottom:14px}
.gloss-header{font-size:18px;font-weight:700;margin-bottom:14px}
.gloss-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(260px,1fr));gap:9px}
.gloss-card{background:var(--w);border:1px solid var(--s2);border-radius:var(--r);padding:13px 14px;box-shadow:var(--shadow)}
.gloss-term{font-size:12px;font-weight:700;color:var(--ac);margin-bottom:4px}
.gloss-def{font-size:12px;color:var(--s6);line-height:1.5}
.toast{position:fixed;bottom:13px;right:13px;background:var(--s9);color:#fff;padding:7px 13px;border-radius:7px;font-size:12px;opacity:0;transition:opacity .3s;pointer-events:none;z-index:100;border-left:3px solid var(--ac)}
.toast.show{opacity:1}
</style></head><body>
<div class="app">
  <div class="sb">
    <div class="sb-brand"><div class="sb-logo">${esc(title)}</div><div class="sb-sub">${esc(audience||"")} &#183; ${esc(duration||"")}</div></div>
    <div class="sb-sec">M&#243;dulos</div>
    <div class="sb-item active" id="nav-cover" onclick="nav('cover')"><div class="sb-n">&#127968;</div><span>Portada</span><span class="ck">&#10003;</span></div>
    ${sideItems}
    <div class="sb-sec">Recursos</div>
    <div class="sb-item" id="nav-gloss" onclick="nav('gloss')"><div class="sb-n">&#128218;</div><span>Glosario</span><span class="ck">&#10003;</span></div>
    <div class="sb-prog"><div class="sp-lbl"><span>Progreso</span><span id="pp">0%</span></div><div class="sp-bar"><div class="sp-fill" id="pf" style="width:0%"></div></div></div>
  </div>
  <div class="main-area">
    <div class="topbar">
      <div class="tb-title" id="tt">${esc(title)}</div>
      <button class="btn btn-o" id="bprev" onclick="prevS()" disabled>&#8592;</button>
      <button class="btn btn-p" id="bnext" onclick="nextS()">Siguiente &#8594;</button>
    </div>
    <div class="content" id="cnt">
      <div class="screen active" id="screen-cover">
        <div class="chero"><div class="ctitle">${esc(title)}</div><div class="csub">${esc(audience||"")}</div>
          <div class="cmeta"><div class="chip"><span>${modules.length}</span> m&#243;dulos</div><div class="chip"><span>${esc(duration||"")}</span></div>${withEval?`<div class="chip"><span>Con</span> evaluaciones</div>`:""}</div></div>
        <div class="mgrid">${modCards}</div>
        <div style="margin-top:20px;text-align:center"><button class="btn btn-p" style="padding:10px 26px;font-size:14px" onclick="nav('m1')">Iniciar curso &#8594;</button></div>
      </div>
      ${modScreens}
      ${quizScreens}
      <div class="screen" id="screen-gloss"><div class="gloss-header">&#128218; Glosario T&#233;cnico</div><div class="gloss-grid">${glossCards}</div></div>
    </div>
  </div>
</div>
<div class="toast" id="tst"></div>
<script>
var SC=${JSON.stringify(screens)};
var cur='cover',done=new Set(['cover']);
var TI={${TI}};
function nav(id){var el=document.getElementById('screen-'+id);if(!el)return;var p=document.querySelector('.screen.active');if(p)p.classList.remove('active');el.classList.add('active');var pn=document.querySelector('.sb-item.active');if(pn)pn.classList.remove('active');var ni=document.getElementById('nav-'+id);if(ni){ni.classList.add('active');ni.classList.add('done');}cur=id;done.add(id);document.getElementById('tt').textContent=TI[id]||id;document.getElementById('cnt').scrollTop=0;upd();}
function nextS(){var i=SC.indexOf(cur);if(i<SC.length-1)nav(SC[i+1]);}
function prevS(){var i=SC.indexOf(cur);if(i>0)nav(SC[i-1]);}
function upd(){var pct=Math.round(done.size/SC.length*100);document.getElementById('pf').style.width=pct+'%';document.getElementById('pp').textContent=pct+'%';var i=SC.indexOf(cur);document.getElementById('bprev').disabled=i<=0;document.getElementById('bnext').disabled=i>=SC.length-1;if(window.API){try{API.LMSSetValue('cmi.core.score.raw',String(pct));API.LMSSetValue('cmi.core.lesson_status',pct>=100?'passed':'incomplete');API.LMSCommit('');}catch(e){}}}
function toggleAcc(el){el.classList.toggle('open');var b=el.nextElementSibling;if(b)b.classList.toggle('open');}
upd();
<\/script>
</body></html>`;
}

function buildManifest(title,id){
  const s=title.replace(/[<>&"]/g," ");
  return `<?xml version="1.0" encoding="UTF-8"?><manifest identifier="${id}" version="1.0" xmlns="http://www.imsproject.org/xsd/imscp_rootv1p1p2" xmlns:adlcp="http://www.adlnet.org/xsd/adlcp_rootv1p2" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.imsproject.org/xsd/imscp_rootv1p1p2 imscp_rootv1p1p2.xsd"><metadata><schema>ADL SCORM</schema><schemaversion>1.2</schemaversion></metadata><organizations default="ORG_1"><organization identifier="ORG_1"><title>${s}</title><item identifier="ITEM_1" identifierref="RES_1"><title>${s}</title><adlcp:masteryscore>70</adlcp:masteryscore></item></organization></organizations><resources><resource identifier="RES_1" type="webcontent" adlcp:scormtype="sco" href="index.html"><file href="index.html"/><file href="scorm_api.js"/></resource></resources></manifest>`;
}

async function packageZIP(outline, moduleHTMLs, color, withEval) {
  const JSZip = window.JSZip;
  if (!JSZip) throw new Error("JSZip no cargado");
  const html = buildSCORM(outline, moduleHTMLs, color, withEval);
  const zip = new JSZip();
  const id = "SCORM_"+Date.now();
  zip.file("imsmanifest.xml", buildManifest(outline.title, id));
  zip.file("index.html", html);
  zip.file("scorm_api.js", SCORM_API);
  const blob = await zip.generateAsync({ type:"blob", compression:"DEFLATE" });
  const url = URL.createObjectURL(blob);
  const fname = outline.title.replace(/\s+/g,"_").replace(/[^a-zA-Z0-9_]/g,"").slice(0,40)+"_SCORM.zip";
  return { html, url, fname };
}

// ─────────────────────────────────────────────────────────────────────────────
// BLOCK SPLITTER — splits HTML string into top-level block elements
// ─────────────────────────────────────────────────────────────────────────────
function splitIntoBlocks(html) {
  if (typeof document === 'undefined') return [html];
  const tmp = document.createElement('div');
  tmp.innerHTML = html || '';
  const BLOCK = /^(DIV|P|H[1-6]|UL|OL|TABLE|BLOCKQUOTE|SECTION|ARTICLE|FIGURE|PRE|HR)$/;
  const blocks = [];
  let inlineGroup = null;
  const flushInline = () => {
    if (inlineGroup) { blocks.push(inlineGroup.outerHTML); inlineGroup = null; }
  };
  tmp.childNodes.forEach(n => {
    if (n.nodeType === 3) {
      if (!n.textContent.trim()) return;
      if (!inlineGroup) inlineGroup = document.createElement('p');
      inlineGroup.appendChild(n.cloneNode(true));
    } else if (n.nodeType === 1) {
      if (BLOCK.test(n.tagName)) {
        flushInline();
        blocks.push(n.outerHTML);
      } else {
        if (!inlineGroup) inlineGroup = document.createElement('p');
        inlineGroup.appendChild(n.cloneNode(true));
      }
    }
  });
  flushInline();
  return blocks.length ? blocks : [html];
}

// ─────────────────────────────────────────────────────────────────────────────
// COMPONENT PICKER — block-aware insert UI (no iframe involvement)
// ─────────────────────────────────────────────────────────────────────────────
function ComponentPicker({ code, onInsert, onDragStart, onDragEnd }) {
  const [inserted, setInserted] = useState(null);
  const [localDragging, setLocalDragging] = useState(null);

  const blocks = useMemo(() => splitIntoBlocks(code), [code]);

  return (
    <div style={{flex:1,display:"flex",flexDirection:"column",overflow:"hidden"}}>
      <div style={{padding:"10px 12px",borderBottom:"1px solid rgba(255,255,255,.06)",flexShrink:0}}>
        <p style={{fontSize:"10px",color:localDragging!==null?"#7DD3FC":"#475569",fontWeight:700,textTransform:"uppercase",letterSpacing:".6px",marginBottom:"8px",transition:"color .2s"}}>
          {localDragging!==null ? "⬇ Suelta en el módulo →" : "Arrastra al módulo →"}
        </p>
        <div style={{display:"grid",gridTemplateColumns:"1fr 1fr",gap:"5px"}}>
          {QUICK_INSERTS.map((q,i)=>(
            <div key={i} draggable
              onDragStart={e=>{
                e.dataTransfer.effectAllowed="copy";
                e.dataTransfer.setData("text/plain", String(i));
                setLocalDragging(i);
                onDragStart(i);
              }}
              onDragEnd={()=>{ setLocalDragging(null); onDragEnd(); }}
              style={{
                background: localDragging===i?"rgba(14,165,233,.2)":inserted===i?"rgba(16,185,129,.18)":"rgba(255,255,255,.04)",
                border:`2px solid ${localDragging===i?"#0EA5E9":inserted===i?"rgba(16,185,129,.5)":"rgba(255,255,255,.08)"}`,
                borderRadius:"8px",padding:"8px 10px",cursor:"grab",userSelect:"none",
                color:localDragging===i?"#7DD3FC":inserted===i?"#6EE7B7":"#94A3B8",
                fontSize:"11px",fontFamily:"'DM Sans',sans-serif",textAlign:"left",
                transition:"all .15s",lineHeight:1.3,
                display:"flex",alignItems:"center",gap:"6px",
                opacity:localDragging!==null&&localDragging!==i?0.35:1,
                transform:localDragging===i?"scale(0.96)":"scale(1)",
              }}>
              <span style={{fontSize:"10px",opacity:.4}}>⠿</span>
              {inserted===i?"✓ Listo":q.label}
            </div>
          ))}
        </div>
      </div>
      <div style={{flex:1,overflow:"auto",padding:"10px 12px"}}>
        <div style={{textAlign:"center",padding:"16px 10px",color:"#334155",fontSize:"11px",lineHeight:1.8}}>
          <div style={{fontSize:"22px",marginBottom:"6px"}}>→</div>
          Arrastra un componente y suéltalo<br/>directamente en el módulo de la derecha.
        </div>
      </div>
    </div>
  );
}

// ── BlockDropCanvas: renders real module blocks + drop zones in React ──
function BlockDropCanvas({ code, dragging, outline, onDrop }) {
  const [overZone, setOverZone] = useState(null);
  const blocks = useMemo(() => splitIntoBlocks(code), [code]);
  const accentColor = outline?._color || "#F59E0B";

  return (
    <div style={{flex:1,overflow:"auto",background:"#F8FAFC",padding:"20px 24px"}}
      onDragOver={e=>e.preventDefault()}
    >
      <style>{`
        .bdc-wrap{font-family:'DM Sans',sans-serif;font-size:14px;line-height:1.7;color:#334155}
        .bdc-wrap *{box-sizing:border-box;margin:0;padding:0}
        .bdc-wrap p{margin-bottom:11px}
        .bdc-wrap h1,.bdc-wrap h2{font-size:22px;font-weight:800;color:#1E293B;margin:0 0 14px}
        .bdc-wrap h3{font-size:17px;font-weight:700;margin:18px 0 8px;color:#1E293B}
        .bdc-wrap h4{font-size:15px;font-weight:600;margin:14px 0 6px;color:#1E293B}
        .bdc-wrap ul,.bdc-wrap ol{margin:0 0 13px 18px}
        .bdc-wrap li{margin-bottom:4px}
        .bdc-wrap .concept{background:#fff;border:1px solid #E2E8F0;border-radius:10px;padding:14px 16px;margin:12px 0;box-shadow:0 1px 6px rgba(0,0,0,.07)}
        .bdc-wrap .concept h4{margin-top:0;margin-bottom:6px;color:#1E293B}
        .bdc-wrap .concept.accent{border-left:3px solid ${accentColor};background:rgba(0,0,0,.02)}
        .bdc-wrap .concept.accent h4{color:${accentColor}}
        .bdc-wrap .callout{border-radius:10px;padding:13px 15px;margin:12px 0;display:flex;gap:10px;align-items:flex-start}
        .bdc-wrap .callout.tip{background:#F0FDF4;border:1px solid #BBF7D0;color:#166534}
        .bdc-wrap .callout.warning{background:#FFFBEB;border:1px solid #FDE68A;color:#92400E}
        .bdc-wrap .callout.danger{background:#FFF1F2;border:1px solid #FECDD3;color:#9F1239}
        .bdc-wrap .callout.info{background:#EFF6FF;border:1px solid #BFDBFE;color:#1E40AF}
        .bdc-wrap .callout.law{background:#FAF5FF;border:1px solid #E9D5FF;color:#6B21A8}
        .bdc-wrap .callout-ico{font-size:16px;flex-shrink:0;margin-top:2px}
        .bdc-wrap .callout strong{font-size:12px;font-weight:700;display:block;margin-bottom:3px}
        .bdc-wrap .steps{margin:12px 0;display:flex;flex-direction:column;gap:9px}
        .bdc-wrap .step{display:flex;gap:11px;align-items:flex-start}
        .bdc-wrap .step-n{width:24px;height:24px;border-radius:50%;background:${accentColor};color:#fff;font-size:12px;font-weight:700;display:flex;align-items:center;justify-content:center;flex-shrink:0;margin-top:2px}
        .bdc-wrap .step-body strong{display:block;font-size:13px;color:#1E293B;margin-bottom:2px}
        .bdc-wrap .step-body p{margin:0;font-size:13px;color:#475569}
        .bdc-wrap .data-table{width:100%;border-collapse:collapse;margin:12px 0;font-size:13px}
        .bdc-wrap .data-table th{background:#1E293B;color:#fff;padding:9px 12px;text-align:left;font-weight:600}
        .bdc-wrap .data-table td{padding:8px 12px;border-bottom:1px solid #E2E8F0}
        .bdc-wrap .data-table tr:nth-child(even) td{background:#F8FAFC}
        .bdc-wrap .hl{background:${accentColor}22;border-left:3px solid ${accentColor};padding:8px 12px;border-radius:0 6px 6px 0;margin:8px 0;font-weight:600;color:#1E293B}
        .bdc-wrap .section-title{text-align:center;padding:16px;background:linear-gradient(135deg,#1E293B,#334155);color:#fff;border-radius:10px;margin:16px 0}
        .bdc-wrap .section-title h2{color:#fff;font-size:18px;margin:0 0 4px}
        .bdc-wrap .section-title p{color:rgba(255,255,255,.7);margin:0;font-size:12px}
        .bdc-wrap .stat-row{display:flex;gap:12px;margin:12px 0;flex-wrap:wrap}
        .bdc-wrap .stat{flex:1;min-width:80px;background:#fff;border:1px solid #E2E8F0;border-radius:10px;padding:12px;text-align:center}
        .bdc-wrap .stat-n{font-size:26px;font-weight:700;color:${accentColor};line-height:1}
        .bdc-wrap .stat-l{font-size:11px;color:#64748B;margin-top:3px}
        .bdc-wrap .img-placeholder{background:#F1F5F9;border:2px dashed #CBD5E1;border-radius:10px;padding:28px;text-align:center;margin:12px 0}
        .bdc-wrap .accordion{margin:12px 0}
        .bdc-wrap .acc-item{border:1px solid #E2E8F0;border-radius:8px;margin-bottom:6px;overflow:hidden}
        .bdc-wrap .acc-head{padding:11px 14px;background:#F8FAFC;font-weight:600;cursor:pointer;color:#1E293B;font-size:13px}
        .bdc-wrap .acc-body{padding:11px 14px;font-size:13px;color:#475569;background:#fff}
        .bdc-wrap .compare{display:grid;grid-template-columns:1fr 1fr;gap:12px;margin:12px 0}
        .bdc-wrap .compare-col{border-radius:10px;padding:14px}
        .bdc-wrap .compare-col.good{background:#F0FDF4;border:1px solid #BBF7D0}
        .bdc-wrap .compare-col.bad{background:#FFF1F2;border:1px solid #FECDD3}
        .bdc-wrap .compare-col h4{margin:0 0 8px;font-size:13px}
        .bdc-wrap .icon-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(110px,1fr));gap:10px;margin:12px 0}
        .bdc-wrap .icon-item{background:#fff;border:1px solid #E2E8F0;border-radius:10px;padding:14px 10px;text-align:center}
        .bdc-wrap .icon-item .ico,.bdc-wrap .icon-item .icon-circle{font-size:26px;margin-bottom:6px}
        .bdc-wrap .icon-item strong{display:block;font-size:12px;font-weight:700;color:#1E293B;margin-bottom:3px}
        .bdc-wrap .icon-item p{font-size:11px;color:#64748B;margin:0}
        .bdc-wrap blockquote.quote{border-left:4px solid ${accentColor};background:#FFFBEB;padding:12px 16px;margin:12px 0;border-radius:0 8px 8px 0;font-style:italic;color:#78350F}
      `}</style>
      {/* Drop zone before first block */}
      <DropZone index={0} over={overZone===0} active={dragging!==null}
        onDrop={()=>onDrop(0)} onOver={()=>setOverZone(0)} onLeave={()=>setOverZone(null)} />

      {blocks.map((b,i)=>(
        <div key={i}>
          <div className="bdc-wrap" dangerouslySetInnerHTML={{__html: b}} />
          <DropZone index={i+1} over={overZone===i+1} active={dragging!==null}
            onDrop={()=>onDrop(i+1)} onOver={()=>setOverZone(i+1)} onLeave={()=>setOverZone(null)} />
        </div>
      ))}

      {blocks.length===0 && (
        <DropZone index={0} over={overZone===0} active={dragging!==null}
          onDrop={()=>onDrop(0)} onOver={()=>setOverZone(0)} onLeave={()=>setOverZone(null)}
          empty />
      )}
    </div>
  );
}

function DropZone({ index, over, active, onDrop, onOver, onLeave, empty }) {
  return (
    <div
      onDragOver={e=>{ e.preventDefault(); onOver(); }}
      onDragLeave={onLeave}
      onDrop={e=>{ e.preventDefault(); onDrop(); }}
      style={{
        height: empty ? "120px" : over ? "52px" : active ? "24px" : "8px",
        display:"flex", alignItems:"center", justifyContent:"center", gap:"6px",
        border: over ? "2px solid #0EA5E9" : active ? "2px dashed rgba(14,165,233,.4)" : "2px solid transparent",
        borderRadius:"10px", margin:"4px 0",
        background: over ? "rgba(14,165,233,.1)" : "transparent",
        color: over ? "#0EA5E9" : "rgba(14,165,233,.4)",
        fontSize:"12px", fontWeight:700,
        transition:"all .18s ease",
        cursor: active ? "copy" : "default",
        overflow:"hidden",
      }}>
      {(over || empty) && (
        <><span style={{fontSize:"18px",lineHeight:1}}>＋</span>
        <span>{over ? "Soltar aquí" : "Arrastra aquí"}</span></>
      )}
    </div>
  );
}

function BlockRow({ html }) {
  const tmp = document.createElement('div');
  tmp.innerHTML = html;
  const text = (tmp.textContent||'').trim().slice(0,55) || '(bloque vacío)';
  const tag = (html.match(/^<([a-zA-Z][a-zA-Z0-9]*)/)||[])[1]?.toUpperCase()||'DIV';
  const tagColor = {P:"#334155",DIV:"#1E40AF",H1:"#7C3AED",H2:"#7C3AED",H3:"#7C3AED",TABLE:"#065F46"}[tag]||"#334155";
  return (
    <div style={{display:"flex",alignItems:"center",gap:"7px",padding:"6px 9px",borderRadius:"6px",background:"rgba(255,255,255,.03)",border:"1px solid rgba(255,255,255,.06)",margin:"1px 0"}}>
      <span style={{fontSize:"9px",fontWeight:700,color:tagColor,background:"rgba(255,255,255,.06)",padding:"2px 5px",borderRadius:"3px",flexShrink:0,fontFamily:"monospace",minWidth:"28px",textAlign:"center"}}>{tag}</span>
      <span style={{fontSize:"11px",color:"#475569",overflow:"hidden",textOverflow:"ellipsis",whiteSpace:"nowrap",flex:1}}>{text}</span>
    </div>
  );
}

// ─────────────────────────────────────────────────────────────────────────────
// EDITOR PANEL COMPONENT
// ─────────────────────────────────────────────────────────────────────────────
function EditorPanel({ module, html, outline, onSave, onClose }) {
  const [tab, setTab] = useState("ai");
  const [code, setCode] = useState(html);
  const [aiPrompt, setAiPrompt] = useState("");
  const [aiLoading, setAiLoading] = useState(false);
  const [aiError, setAiError] = useState("");
  const [previewKey, setPreviewKey] = useState(0);
  const [insertedIdx, setInsertedIdx] = useState(null);
  const [draggingComp, setDraggingComp] = useState(null);
  const [hasSelection, setHasSelection] = useState(false);
  const textareaRef = useRef(null);
  const cursorRef = useRef({ start: 0, end: 0 });
  const iframeRef = useRef(null);
  const isIframeEdit = useRef(false);
  // Undo/redo history
  const historyRef = useRef([html]);
  const histIdxRef = useRef(0);

  // Save cursor position whenever textarea is interacted with
  const saveCursor = () => {
    const ta = textareaRef.current;
    if (ta) cursorRef.current = { start: ta.selectionStart, end: ta.selectionEnd };
  };

  // Push a new state into history (called whenever code changes meaningfully)
  const pushHistory = (newCode) => {
    const hist = historyRef.current;
    const idx = histIdxRef.current;
    // Discard any redo states ahead
    const newHist = hist.slice(0, idx + 1);
    newHist.push(newCode);
    // Keep max 50 states
    if (newHist.length > 50) newHist.shift();
    historyRef.current = newHist;
    histIdxRef.current = newHist.length - 1;
  };

  const canUndo = histIdxRef.current > 0;
  const canRedo = histIdxRef.current < historyRef.current.length - 1;

  const undo = () => {
    if (!canUndo) return;
    histIdxRef.current -= 1;
    const prev = historyRef.current[histIdxRef.current];
    isIframeEdit.current = false;
    setCode(prev);
    setPreviewKey(k => k + 1);
  };

  const redo = () => {
    if (!canRedo) return;
    histIdxRef.current += 1;
    const next = historyRef.current[histIdxRef.current];
    isIframeEdit.current = false;
    setCode(next);
    setPreviewKey(k => k + 1);
  };

  // Listen for contenteditable changes coming from the preview iframe
  useEffect(() => {
    const handler = (e) => {
      if (e.data?.type === "previewEdit") {
        isIframeEdit.current = true;
        setCode(e.data.html);
        pushHistory(e.data.html);
      }
      if (e.data?.type === "hasSelection") {
        setHasSelection(e.data.value);
      }
    };
    window.addEventListener("message", handler);
    return () => window.removeEventListener("message", handler);
  }, []);

  // Static shell — code injected via onLoad directly into contentDocument
  const previewShell = `<!DOCTYPE html><html><head><meta charset="UTF-8">
<link href="https://fonts.googleapis.com/css2?family=DM+Sans:wght@400;500;600;700&display=swap" rel="stylesheet">
<style>
*{box-sizing:border-box;margin:0;padding:0}body{font-family:'DM Sans',sans-serif;padding:20px;font-size:14px;line-height:1.7;color:#334155;background:#F8FAFC;outline:none}
.edit-hint{position:fixed;top:8px;right:8px;background:rgba(14,165,233,.9);color:#fff;font-size:10px;font-weight:600;padding:3px 8px;border-radius:5px;pointer-events:none;font-family:'DM Sans',sans-serif;letter-spacing:.3px}
[contenteditable]:hover{outline:1px dashed rgba(14,165,233,.5);border-radius:3px}
[contenteditable]:focus{outline:2px solid rgba(14,165,233,.7);border-radius:3px}
:root{--ac:${outline._color||"#F59E0B"};--rgb:${hexRgb(outline._color||"#F59E0B")}}
.concept{background:#fff;border:1px solid #E2E8F0;border-radius:10px;padding:14px 16px;margin:12px 0;box-shadow:0 1px 6px rgba(0,0,0,.07)}
.concept h4{margin-top:0;margin-bottom:6px;color:#1E293B}.concept.accent{border-left:3px solid var(--ac);background:rgba(var(--rgb),.04)}.concept.accent h4{color:var(--ac)}
.steps{margin:12px 0;display:flex;flex-direction:column;gap:9px}.step{display:flex;gap:11px;align-items:flex-start}
.step-n{width:26px;height:26px;border-radius:50%;background:var(--ac);color:#fff;display:flex;align-items:center;justify-content:center;font-weight:700;font-size:12px;flex-shrink:0}
.step-body{flex:1}.step-body strong{display:block;font-size:13px;color:#1E293B;margin-bottom:2px}.step-body p{margin:0;color:#475569;font-size:13px}
.callout{border-radius:8px;padding:11px 13px;margin:11px 0;font-size:13px;line-height:1.6}
.callout.tip{background:#F0FDF4;border:1px solid #BBF7D0;color:#166534}
.callout.warning{background:#FFFBEB;border:1px solid #FDE68A;color:#92400E}
.callout.danger{background:#FEF2F2;border:1px solid #FECACA;color:#991B1B}
.callout.info{background:#EFF6FF;border:1px solid #BFDBFE;color:#1E40AF}
.callout.law{background:#F5F3FF;border:1px solid #DDD6FE;color:#4C1D95}
.icon-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(140px,1fr));gap:9px;margin:12px 0}
.icon-item{background:#fff;border:1px solid #E2E8F0;border-radius:9px;padding:13px 11px;text-align:center}.icon-circle{font-size:26px;margin-bottom:7px}
.icon-item strong{display:block;font-size:12px;margin-bottom:3px}.icon-item p{font-size:11px;color:#64748B;margin:0}
.data-table{width:100%;border-collapse:collapse;margin:12px 0;font-size:13px;border-radius:10px;overflow:hidden}
.data-table th{background:#1E293B;color:#fff;padding:8px 11px;text-align:left;font-size:11px;font-weight:600}
.data-table td{padding:8px 11px;border-bottom:1px solid #E2E8F0}.data-table tr:nth-child(even) td{background:#F1F5F9}
.hl{background:rgba(var(--rgb),.15);color:var(--ac);padding:1px 5px;border-radius:4px;font-weight:600;font-size:13px}
.hl.danger{background:#FEE2E2;color:#991B1B}.hl.success{background:#D1FAE5;color:#065F46}
.section-title{font-size:15px;font-weight:700;border-bottom:2px solid var(--ac);padding-bottom:5px;margin:18px 0 11px}
.accordion{margin:12px 0;display:flex;flex-direction:column;gap:5px}
.acc-item{background:#fff;border:1px solid #E2E8F0;border-radius:10px;overflow:hidden}
.acc-head{padding:10px 13px;cursor:pointer;font-size:13px;font-weight:600;user-select:none}
.acc-head.open{background:rgba(var(--rgb),.06);color:var(--ac)}
.acc-body{max-height:0;overflow:hidden;transition:max-height .3s,padding .3s;padding:0 13px;font-size:13px;line-height:1.6}
.acc-body.open{max-height:600px;padding:9px 13px}
.compare{display:grid;grid-template-columns:1fr 1fr;gap:9px;margin:12px 0}
.compare-col{border-radius:10px;padding:13px 14px}
.compare-col.good{background:#F0FDF4;border:1px solid #BBF7D0}.compare-col.bad{background:#FEF2F2;border:1px solid #FECACA}
.compare-label{font-size:12px;font-weight:700;margin-bottom:7px}
.compare-col.good .compare-label{color:#166534}.compare-col.bad .compare-label{color:#991B1B}
.compare-col ul{margin-left:14px;font-size:12px}
.quote{border-left:3px solid var(--ac);background:rgba(var(--rgb),.05);padding:11px 14px;border-radius:0 10px 10px 0;margin:12px 0;font-style:italic;font-size:13px;line-height:1.7}
.quote cite{display:block;margin-top:5px;font-style:normal;font-weight:600;font-size:12px;color:#64748B}
.icon-list{list-style:none;margin:0 0 13px;display:flex;flex-direction:column;gap:5px}
.icon-list li{display:flex;align-items:flex-start;gap:7px;font-size:13px;line-height:1.5}
.li-icon{flex-shrink:0;font-size:14px;width:18px;text-align:center}
.stat-row{display:flex;gap:9px;flex-wrap:wrap;margin:12px 0}
.stat{background:#fff;border:1px solid #E2E8F0;border-radius:10px;padding:13px 16px;flex:1;min-width:110px;text-align:center}
.stat-n{font-size:26px;font-weight:700;color:var(--ac);line-height:1}.stat-l{font-size:11px;color:#64748B;margin-top:3px}
.img-placeholder{background:#F1F5F9;border:2px dashed #CBD5E1;border-radius:10px;padding:28px;text-align:center;margin:12px 0}
.img-ph-ico{font-size:32px;margin-bottom:7px}.img-ph-label{font-size:13px;font-weight:600;color:#64748B;margin-bottom:4px}.img-ph-hint{font-size:11px;color:#94A3B8}
.btn-inline{background:var(--ac);color:#fff;border:none;padding:9px 20px;border-radius:8px;font-size:13px;font-weight:600;cursor:pointer;font-family:inherit;transition:opacity .15s}.btn-inline:hover{opacity:.85}
.section-title{text-align:center;padding:16px;background:linear-gradient(135deg,#1E293B,#334155);color:#fff;border-radius:10px;margin:16px 0}
.section-title h2{color:#fff;font-size:18px;font-weight:800;margin:0 0 4px}.section-title p{color:rgba(255,255,255,.7);margin:0;font-size:12px}
.mhdr{background:linear-gradient(135deg,var(--ac),rgba(var(--rgb),.7));border-radius:11px;padding:16px 18px;display:flex;gap:11px;align-items:center;margin-bottom:16px;color:#fff}
.mhdr-n{width:36px;height:36px;background:rgba(255,255,255,.25);border-radius:8px;display:flex;align-items:center;justify-content:center;font-size:16px;font-weight:800;flex-shrink:0}
.mhdr h2{font-size:16px;font-weight:700;color:#fff;margin:0 0 2px}.mhdr p{font-size:12px;color:rgba(255,255,255,.8);margin:0}
h1,h2{font-size:20px;font-weight:800;color:#1E293B;margin:0 0 12px;line-height:1.3}
h3{font-size:17px;font-weight:700;color:#1E293B;margin:20px 0 9px}h4{font-size:15px;font-weight:600;color:#1E293B;margin:16px 0 7px}p{margin-bottom:11px}
ul,ol{margin:0 0 13px 18px}li{margin-bottom:4px}
/* Text formatting toolbar */
#fmt-bar{position:fixed;z-index:9999;background:#1E293B;border:1px solid rgba(255,255,255,.15);border-radius:10px;padding:5px 8px;display:none;gap:4px;align-items:center;box-shadow:0 8px 24px rgba(0,0,0,.4);flex-wrap:wrap;max-width:360px}
#fmt-bar button{background:rgba(255,255,255,.07);border:1px solid rgba(255,255,255,.1);color:#CBD5E1;border-radius:5px;padding:3px 7px;cursor:pointer;font-size:12px;font-family:'DM Sans',sans-serif;transition:all .12s;white-space:nowrap}
#fmt-bar button:hover,#fmt-bar button.active{background:rgba(14,165,233,.25);border-color:#0EA5E9;color:#7DD3FC}
#fmt-bar select{background:#0F172A;border:1px solid rgba(255,255,255,.1);color:#CBD5E1;border-radius:5px;padding:3px 5px;font-size:11px;cursor:pointer}
#fmt-bar .sep{width:1px;height:18px;background:rgba(255,255,255,.1);flex-shrink:0}
#fmt-bar input[type=color]{width:24px;height:24px;border:none;border-radius:4px;cursor:pointer;padding:0}
</style>
<script>function toggleAcc(el){el.classList.toggle('open');var b=el.nextElementSibling;if(b)b.classList.toggle('open');}<\/script>
</head><body>
<div style="position:fixed;top:8px;right:8px;background:rgba(14,165,233,.9);color:#fff;font-size:10px;font-weight:700;padding:3px 9px;border-radius:20px;pointer-events:none;font-family:sans-serif;z-index:99;display:flex;align-items:center;gap:4px"><span>✏️</span><span>Click para editar</span></div>
<div id="ec" contenteditable="true" style="outline:none;min-height:200px;padding-bottom:40px"></div>
<script>
var ec=document.getElementById('ec');
var syncT=null;
function scheduleSync(){clearTimeout(syncT);syncT=setTimeout(function(){window.parent.postMessage({type:'previewEdit',html:ec.innerHTML},'*');},400);}
ec.addEventListener('input',scheduleSync);
ec.addEventListener('blur',function(){window.parent.postMessage({type:'previewEdit',html:ec.innerHTML},'*');});
document.addEventListener('selectionchange',function(){
  var sel=window.getSelection();
  var ok=sel&&!sel.isCollapsed&&sel.rangeCount&&ec.contains(sel.getRangeAt(0).commonAncestorContainer);
  window.parent.postMessage({type:'hasSelection',value:!!ok},'*');
});
window.addEventListener('message',function(e){
  if(!e.data)return;
  if(e.data.type==='setContent'){
    ec.innerHTML=e.data.html;
    ec.querySelectorAll('script').forEach(function(os){var ns=document.createElement('script');Array.from(os.attributes).forEach(function(a){ns.setAttribute(a.name,a.value);});ns.textContent=os.textContent;os.parentNode.replaceChild(ns,os);});
  }
  if(e.data.type==='execFormat'){document.execCommand(e.data.cmd,false,e.data.val||null);ec.focus();scheduleSync();}
});
<\/script>
</body></html>`;

  const execFormat = (cmd, val) => {
    const fr = iframeRef.current;
    if (fr && fr.contentWindow) fr.contentWindow.postMessage({type:'execFormat', cmd, val: val||null}, '*');
  };

  // Called when iframe loads — send content via postMessage (zero escaping issues)
  const injectContent = () => {
    const fr = iframeRef.current;
    if (fr && fr.contentWindow) {
      setTimeout(() => {
        fr.contentWindow.postMessage({type:'setContent', html: code}, '*');
      }, 50);
    }
  };

  const insertAt = (snippet) => {
    // Insert at cursor position, defaulting to end of code
    const pos = cursorRef.current;
    const insertPos = (pos && pos.end > 0) ? pos.end : code.length;
    const newVal = code.slice(0, insertPos) + "\n" + snippet + "\n" + code.slice(insertPos);
    const newCursorPos = insertPos + snippet.length + 2;
    cursorRef.current = { start: newCursorPos, end: newCursorPos };
    isIframeEdit.current = false;
    setCode(newVal);
    pushHistory(newVal);
    setPreviewKey(k => k + 1);
    // Switch to HTML tab so user can see the result
    setTab("html");
    setTimeout(() => {
      const ta = textareaRef.current;
      if (ta) {
        ta.selectionStart = ta.selectionEnd = newCursorPos;
        ta.focus();
        ta.scrollTop = ta.scrollHeight;
      }
    }, 60);
  };

  const runAI = async () => {
    if (!aiPrompt.trim()) return;
    setAiLoading(true); setAiError("");
    try {
      const newHTML = await aiEditModule(code, aiPrompt, module.name, outline.title);
      isIframeEdit.current = false;
      setCode(newHTML);
      pushHistory(newHTML);
      setPreviewKey(k => k+1);
      setAiPrompt("");
    } catch(e) { setAiError(e.message); }
    finally { setAiLoading(false); }
  };

  const AI_SUGGESTIONS = [
    "Agrega más ejemplos prácticos y casos reales",
    "Divide el contenido en pasos numerados con explicaciones detalladas",
    "Agrega una tabla comparativa con datos específicos del tema",
    "Incluye un acordeón con las preguntas frecuentes sobre este módulo",
    "Agrega callouts de advertencia y consejos de seguridad",
    "Crea una sección de estadísticas con datos numéricos relevantes",
    "Agrega una comparación de buenas vs malas prácticas",
    "Incluye citas de la normativa o regulación correspondiente",
    "Agrega tarjetas icónicas para resumir los conceptos clave",
    "Crea botones interactivos con ejercicios de reflexión",
  ];

  return (
    <div style={{position:"fixed",inset:0,zIndex:9999,display:"flex",flexDirection:"column",background:"#07090F"}}>
      {/* Header */}
      <div style={{height:"50px",background:"#0F172A",borderBottom:"1px solid rgba(255,255,255,.08)",display:"flex",alignItems:"center",gap:"10px",padding:"0 16px",flexShrink:0}}>
        <div style={{width:"6px",height:"6px",borderRadius:"50%",background:"#0EA5E9",flexShrink:0}}/>
        <span style={{fontSize:"13px",fontWeight:600,color:"#E2E8F0"}}>Editando: <em style={{fontStyle:"normal",color:"#7DD3FC"}}>{module.name}</em></span>
        <div style={{flex:1}}/>
        <button onClick={()=>{onSave(code);}} style={{background:"linear-gradient(135deg,#10B981,#0EA5E9)",color:"#fff",border:"none",padding:"6px 16px",borderRadius:"7px",cursor:"pointer",fontFamily:"'DM Sans',sans-serif",fontSize:"12px",fontWeight:700}}>✓ Guardar módulo</button>
        <button onClick={onClose} style={{background:"rgba(255,255,255,.06)",border:"1px solid rgba(255,255,255,.1)",color:"#94A3B8",padding:"6px 12px",borderRadius:"7px",cursor:"pointer",fontFamily:"'DM Sans',sans-serif",fontSize:"12px",marginLeft:"4px"}}>✕ Cerrar</button>
      </div>

      {/* Body: left editor + right preview */}
      <div style={{flex:1,display:"flex",overflow:"hidden"}}>

        {/* LEFT: Editor */}
        <div style={{width:"460px",flexShrink:0,display:"flex",flexDirection:"column",borderRight:"1px solid rgba(255,255,255,.07)",background:"#0B0E17"}}>
          {/* Tabs */}
          <div style={{display:"flex",borderBottom:"1px solid rgba(255,255,255,.07)",flexShrink:0}}>
            {["ai","human","components"].map(t=>(
              <button key={t} onClick={()=>setTab(t)} style={{flex:1,padding:"10px",background:tab===t?"rgba(14,165,233,.1)":"transparent",border:"none",borderBottom:tab===t?"2px solid #0EA5E9":"2px solid transparent",color:tab===t?"#7DD3FC":"#475569",fontSize:"12px",fontWeight:600,cursor:"pointer",fontFamily:"'DM Sans',sans-serif",transition:"all .15s"}}>
                {t==="ai"?"🤖 IA" : t==="human"?"✏️ HTML" : "🧩 Componentes"}
              </button>
            ))}
          </div>

          {/* AI TAB */}
          {tab==="ai" && (
            <div style={{flex:1,overflow:"auto",padding:"14px"}}>
              <p style={{fontSize:"12px",color:"#475569",marginBottom:"12px",lineHeight:1.5}}>Describe qué quieres cambiar o agregar. Claude editará el módulo completo.</p>
              <textarea
                value={aiPrompt}
                onChange={e=>setAiPrompt(e.target.value)}
                placeholder="Ej: Agrega un acordeón con las 5 causas más comunes de accidentes en izaje, y una tabla con los valores de SWL según diámetro de cable..."
                style={{width:"100%",height:"100px",background:"rgba(255,255,255,.05)",border:"1px solid rgba(255,255,255,.1)",borderRadius:"8px",padding:"10px",fontSize:"13px",color:"#E2E8F0",resize:"vertical",outline:"none",fontFamily:"'DM Sans',sans-serif",lineHeight:1.5}}
              />
              {aiError && <p style={{color:"#FCA5A5",fontSize:"11px",margin:"6px 0",lineHeight:1.4}}>{aiError}</p>}
              <button onClick={runAI} disabled={aiLoading||!aiPrompt.trim()} style={{width:"100%",marginTop:"8px",background:aiLoading?"rgba(14,165,233,.3)":"linear-gradient(135deg,#0EA5E9,#6366F1)",color:aiLoading?"#475569":"#fff",border:"none",padding:"9px",borderRadius:"8px",cursor:aiLoading?"not-allowed":"pointer",fontFamily:"'DM Sans',sans-serif",fontSize:"13px",fontWeight:600,display:"flex",alignItems:"center",justifyContent:"center",gap:"6px"}}>
                {aiLoading ? <>⟳ Generando...</> : <>✨ Aplicar con IA</>}
              </button>
              <div style={{marginTop:"16px"}}>
                <p style={{fontSize:"11px",color:"#334155",marginBottom:"8px",textTransform:"uppercase",letterSpacing:".5px",fontWeight:700}}>Sugerencias rápidas</p>
                {AI_SUGGESTIONS.map((s,i)=>(
                  <button key={i} onClick={()=>setAiPrompt(s)} style={{display:"block",width:"100%",textAlign:"left",background:"rgba(255,255,255,.03)",border:"1px solid rgba(255,255,255,.06)",borderRadius:"6px",padding:"7px 10px",marginBottom:"5px",color:"#475569",fontSize:"12px",cursor:"pointer",fontFamily:"'DM Sans',sans-serif",lineHeight:1.4,transition:"all .15s"}}
                    onMouseOver={e=>e.currentTarget.style.background="rgba(14,165,233,.08)"}
                    onMouseOut={e=>e.currentTarget.style.background="rgba(255,255,255,.03)"}>
                    {s}
                  </button>
                ))}
              </div>
            </div>
          )}

          {/* HTML TAB */}
          {tab==="human" && (
            <div style={{flex:1,display:"flex",flexDirection:"column",overflow:"hidden"}}>
              <div style={{padding:"8px 12px",borderBottom:"1px solid rgba(255,255,255,.05)",display:"flex",gap:"6px",flexShrink:0,alignItems:"center"}}>
                <button onClick={undo} disabled={!canUndo} title="Deshacer (Ctrl+Z)"
                  style={{background:canUndo?"rgba(255,255,255,.07)":"rgba(255,255,255,.02)",border:"1px solid rgba(255,255,255,.08)",color:canUndo?"#94A3B8":"#1E293B",padding:"4px 10px",borderRadius:"5px",cursor:canUndo?"pointer":"default",fontFamily:"'DM Sans',sans-serif",fontSize:"12px",fontWeight:600,transition:"all .15s"}}>
                  ↩ Deshacer
                </button>
                <button onClick={redo} disabled={!canRedo} title="Rehacer (Ctrl+Y)"
                  style={{background:canRedo?"rgba(255,255,255,.07)":"rgba(255,255,255,.02)",border:"1px solid rgba(255,255,255,.08)",color:canRedo?"#94A3B8":"#1E293B",padding:"4px 10px",borderRadius:"5px",cursor:canRedo?"pointer":"default",fontFamily:"'DM Sans',sans-serif",fontSize:"12px",fontWeight:600,transition:"all .15s"}}>
                  ↪ Rehacer
                </button>
                <div style={{flex:1}}/>
                <button onClick={()=>{setPreviewKey(k=>k+1);}} style={{background:"rgba(14,165,233,.12)",border:"1px solid rgba(14,165,233,.2)",color:"#7DD3FC",padding:"4px 10px",borderRadius:"5px",cursor:"pointer",fontFamily:"'DM Sans',sans-serif",fontSize:"11px"}}>↻ Preview</button>
              </div>
              <textarea
                ref={textareaRef}
                value={code}
                onChange={e=>{setCode(e.target.value); isIframeEdit.current=false; setPreviewKey(k=>k+1); pushHistory(e.target.value);}}
                onKeyDown={e=>{
                  if((e.ctrlKey||e.metaKey)&&e.key==="z"&&!e.shiftKey){e.preventDefault();undo();}
                  if((e.ctrlKey||e.metaKey)&&(e.key==="y"||(e.key==="z"&&e.shiftKey))){e.preventDefault();redo();}
                }}
                onKeyUp={saveCursor}
                onMouseUp={saveCursor}
                onClick={saveCursor}
                onBlur={saveCursor}
                spellCheck={false}
                style={{flex:1,background:"#090D16",border:"none",padding:"12px",fontSize:"12px",color:"#A5B4FC",resize:"none",outline:"none",fontFamily:"'DM Mono', 'Courier New', monospace",lineHeight:1.6,tabSize:2}}
              />
            </div>
          )}

          {/* COMPONENTS TAB */}
          {tab==="components" && (
            <ComponentPicker
              code={code}
              onInsert={(newCode)=>{
                isIframeEdit.current=false;
                setCode(newCode);
                pushHistory(newCode);
                setPreviewKey(k=>k+1);
              }}
              onDragStart={(i)=>setDraggingComp(i)}
              onDragEnd={()=>setDraggingComp(null)}
            />
          )}
        </div>

        {/* RIGHT: Live Preview or Block Drop Canvas */}
        <div style={{flex:1,display:"flex",flexDirection:"column",overflow:"hidden",position:"relative"}}>
          <div style={{height:"36px",background:"#0F172A",borderBottom:"1px solid rgba(255,255,255,.05)",display:"flex",alignItems:"center",padding:"0 14px",flexShrink:0,gap:"8px"}}>
            <div style={{width:"8px",height:"8px",borderRadius:"50%",background:"rgba(16,185,129,.6)"}}/>
            <span style={{fontSize:"11px",color:"#334155"}}>{tab==="components" ? "⬇ Arrastra el componente sobre el módulo" : "Preview en tiempo real"}</span>
            {aiLoading && <span style={{fontSize:"11px",color:"#0EA5E9",marginLeft:"8px",animation:"pulse 1s infinite"}}>⟳ Claude está editando...</span>}
          </div>
          {/* Floating format toolbar — shows when text is selected in preview */}
          {hasSelection && tab !== "components" && (
            <div style={{
              position:"absolute", top:"44px", left:"50%", transform:"translateX(-50%)",
              zIndex:100, background:"#1E293B", border:"1px solid rgba(255,255,255,.15)",
              borderRadius:"10px", padding:"5px 8px", display:"flex", gap:"4px",
              alignItems:"center", boxShadow:"0 8px 24px rgba(0,0,0,.6)",
              flexWrap:"wrap", pointerEvents:"all",
            }}>
              {[
                {label:<b>B</b>, cmd:"bold"},
                {label:<i>I</i>, cmd:"italic"},
                {label:<u>U</u>, cmd:"underline"},
              ].map((t,i)=>(
                <button key={i} onMouseDown={e=>{e.preventDefault();execFormat(t.cmd);}}
                  style={{background:"rgba(255,255,255,.07)",border:"1px solid rgba(255,255,255,.1)",color:"#CBD5E1",borderRadius:"5px",padding:"3px 9px",cursor:"pointer",fontSize:"13px",fontFamily:"inherit"}}>
                  {t.label}
                </button>
              ))}
              <div style={{width:1,height:16,background:"rgba(255,255,255,.15)"}}/>
              {[
                {label:"←", cmd:"justifyLeft", title:"Izquierda"},
                {label:"≡", cmd:"justifyCenter", title:"Centro"},
                {label:"→", cmd:"justifyRight", title:"Derecha"},
              ].map((t,i)=>(
                <button key={i} onMouseDown={e=>{e.preventDefault();execFormat(t.cmd);}}
                  title={t.title}
                  style={{background:"rgba(255,255,255,.07)",border:"1px solid rgba(255,255,255,.1)",color:"#CBD5E1",borderRadius:"5px",padding:"3px 8px",cursor:"pointer",fontSize:"12px"}}>
                  {t.label}
                </button>
              ))}
              <div style={{width:1,height:16,background:"rgba(255,255,255,.15)"}}/>
              <select onMouseDown={e=>e.stopPropagation()} onChange={e=>{execFormat('fontSize',e.target.value);e.target.value='';}}
                style={{background:"#0F172A",border:"1px solid rgba(255,255,255,.12)",color:"#CBD5E1",borderRadius:"5px",padding:"2px 4px",fontSize:"11px",cursor:"pointer"}}>
                <option value="">↕ Tam</option>
                {["1:Chico","3:Normal","4:Medio","5:Grande","6:XL","7:XXL"].map(o=>{const[v,l]=o.split(':');return <option key={v} value={v}>{l}</option>;})}
              </select>
              <div style={{width:1,height:16,background:"rgba(255,255,255,.15)"}}/>
              <label title="Color texto" style={{display:"flex",alignItems:"center",gap:2,cursor:"pointer",color:"#94A3B8",fontSize:"10px",fontWeight:700}}>
                A<input type="color" defaultValue="#1E293B"
                  onChange={e=>execFormat('foreColor',e.target.value)}
                  style={{width:18,height:18,border:"none",borderRadius:3,cursor:"pointer",padding:0}}/>
              </label>
              <label title="Resaltado" style={{display:"flex",alignItems:"center",gap:2,cursor:"pointer",color:"#94A3B8",fontSize:"10px",fontWeight:700}}>
                M<input type="color" defaultValue="#FEF08A"
                  onChange={e=>execFormat('hiliteColor',e.target.value)}
                  style={{width:18,height:18,border:"none",borderRadius:3,cursor:"pointer",padding:0}}/>
              </label>
              <div style={{width:1,height:16,background:"rgba(255,255,255,.15)"}}/>
              <button onMouseDown={e=>{e.preventDefault();execFormat('removeFormat');}}
                title="Quitar formato"
                style={{background:"rgba(239,68,68,.12)",border:"1px solid rgba(239,68,68,.2)",color:"#FCA5A5",borderRadius:"5px",padding:"3px 7px",cursor:"pointer",fontSize:"11px"}}>
                ✕
              </button>
            </div>
          )}
          {tab==="components"
            ? <BlockDropCanvas
                code={code}
                dragging={draggingComp}
                outline={outline}
                onDrop={(blockIdx)=>{
                  if(draggingComp===null)return;
                  const snippet=QUICK_INSERTS[draggingComp].code;
                  const nb=[...splitIntoBlocks(code)];
                  nb.splice(blockIdx,0,snippet);
                  const newCode=nb.join("\n");
                  isIframeEdit.current=false;
                  setCode(newCode);
                  pushHistory(newCode);
                  setPreviewKey(k=>k+1);
                }}
              />
            : <iframe
                ref={iframeRef}
                key={previewKey}
                title="Module Preview"
                srcDoc={previewShell}
                onLoad={injectContent}
                style={{flex:1,border:"none",background:"#F8FAFC"}}
                sandbox="allow-scripts allow-same-origin"
              />
          }
        </div>
      </div>
    </div>
  );
}


// ─────────────────────────────────────────────────────────────────────────────
// PROJECT STORAGE
// ─────────────────────────────────────────────────────────────────────────────
async function listProjects() {
  try {
    const r = await window.storage.list("proj:");
    if (!r?.keys) return [];
    const projects = [];
    for (const k of r.keys) {
      try {
        const d = await window.storage.get(k);
        if (d?.value) projects.push(JSON.parse(d.value));
      } catch(_) {}
    }
    return projects.sort((a,b) => b.savedAt - a.savedAt);
  } catch(_) { return []; }
}

async function saveProject(outline, moduleHTMLs, cfg, html) {
  const id = outline._id || ("proj_" + Date.now());
  outline._id = id;
  const proj = {
    id,
    title: outline.title,
    color: cfg.color,
    withEval: cfg.withEval,
    modules: outline.modules,
    savedAt: Date.now(),
    outline,
    moduleHTMLs,
    cfg,
    html,
  };
  try {
    await window.storage.set("proj:" + id, JSON.stringify(proj));
  } catch(e) { console.error("Save error:", e); }
  return id;
}

async function deleteProject(id) {
  try { await window.storage.delete("proj:" + id); } catch(_) {}
}

function timeAgo(ts) {
  const s = Math.floor((Date.now() - ts) / 1000);
  if (s < 60) return "ahora mismo";
  if (s < 3600) return `hace ${Math.floor(s/60)} min`;
  if (s < 86400) return `hace ${Math.floor(s/3600)} h`;
  return `hace ${Math.floor(s/86400)} días`;
}

// ─────────────────────────────────────────────────────────────────────────────
// MAIN APP
// ─────────────────────────────────────────────────────────────────────────────
export default function App() {
  const [phase, setPhase] = useState("home");
  const [file, setFile] = useState(null);
  const [drag, setDrag] = useState(false);
  const [cfg, setCfg] = useState({ name:"", color:COLORS[0].val, withEval:false });
  const [genSteps, setGenSteps] = useState([]);
  const [outline, setOutline] = useState(null);
  const [moduleHTMLs, setModuleHTMLs] = useState({});
  const [result, setResult] = useState(null);
  const [preview, setPreview] = useState(false);
  const [editingModule, setEditingModule] = useState(null);
  const [packaging, setPackaging] = useState(false);
  const [err, setErr] = useState("");
  const [projects, setProjects] = useState([]);
  const [projLoading, setProjLoading] = useState(true);
  const [deleteConfirm, setDeleteConfirm] = useState(null);
  const fRef = useRef(null);

  // Load JSZip + pdf.js on mount
  useEffect(() => {
    if (!window.JSZip) {
      const sc = document.createElement("script");
      sc.src = "https://cdnjs.cloudflare.com/ajax/libs/jszip/3.10.1/jszip.min.js";
      document.head.appendChild(sc);
    }
    if (!window.pdfjsLib) {
      const sc2 = document.createElement("script");
      sc2.src = "https://cdnjs.cloudflare.com/ajax/libs/pdf.js/3.11.174/pdf.min.js";
      sc2.onload = () => { window.pdfjsLib.GlobalWorkerOptions.workerSrc = "https://cdnjs.cloudflare.com/ajax/libs/pdf.js/3.11.174/pdf.worker.min.js"; };
      document.head.appendChild(sc2);
    }
    listProjects().then(p => { setProjects(p); setProjLoading(false); });
  }, []);

  const refreshProjects = async () => {
    const p = await listProjects();
    setProjects(p);
  };

  const handleFile = useCallback((f) => {
    if (!f) return;
    if (!f.name.endsWith(".pdf") && !f.name.endsWith(".pptx")) { setErr("Solo PDF o PPTX"); return; }
    setErr("");
    setFile(f);
    const auto = f.name.replace(/\.(pdf|pptx)$/i,"").replace(/[-_]/g," ").replace(/\b\w/g,c=>c.toUpperCase());
    setCfg(c=>({...c, name:c.name||auto}));
    setPhase("config");
  }, []);

  const onDrop = useCallback((e) => { e.preventDefault(); setDrag(false); handleFile(e.dataTransfer.files[0]); }, [handleFile]);

  const run = async () => {
    const isPDF = file.name.endsWith(".pdf");
    // Warn about large files
    if (file.size > 8 * 1024 * 1024) {
      setErr("El archivo es demasiado grande (más de 8 MB). Reduce el tamaño del PDF e intenta de nuevo.");
      return;
    }
    setGenSteps([{ label:"Analizando estructura del documento", s:"running" }]);
    setPhase("generating");
    try {
      const ol = await getOutline(file, cfg.name);
      if (cfg.name) ol.title = cfg.name;
      ol._color = cfg.color;

      const allSteps = [
        { label:"Estructura analizada ✓", s:"done" },
        ...ol.modules.map(m=>({ label:`Generando M${m.id}: ${m.name}`, s:"pending" })),
        { label:"Empaquetando SCORM", s:"pending" },
      ];
      setGenSteps(allSteps);

      let pptxSlides = null;
      if (isPDF) {
        setGenSteps(prev => [{label:"Extrayendo texto del PDF...", s:"running"}].concat(prev.slice(1)));
        pptxSlides = await extractPDF(file);
        setGenSteps(prev => [{label:"PDF extraido correctamente", s:"done"}].concat(prev.slice(1)));
      } else {
        pptxSlides = await extractPPTX(file);
      }

      const htmls = {};
      for (let i=0; i < ol.modules.length; i++) {
        const m = ol.modules[i];
        setGenSteps(prev => prev.map((x,idx) => idx===i+1 ? {...x,s:"running"} : x));
        htmls[m.id] = await generateModuleHTML(file, ol, m, pptxSlides);
        setGenSteps(prev => prev.map((x,idx) => idx===i+1 ? {...x,s:"done"} : x));
      }

      const packIdx = ol.modules.length+1;
      setGenSteps(prev => prev.map((x,idx) => idx===packIdx ? {...x,s:"running"} : x));
      const { html, url, fname } = await packageZIP(ol, htmls, cfg.color, cfg.withEval);
      setGenSteps(prev => prev.map((x,idx) => idx===packIdx ? {...x,s:"done"} : x));

      setOutline(ol);
      setModuleHTMLs(htmls);
      setResult({ url, fname, html });
      // Auto-save project to storage
      await saveProject(ol, htmls, cfg, html);
      await refreshProjects();
      await new Promise(r=>setTimeout(r,400));
      setPhase("done");
    } catch(e) { setErr(e.message); setPhase("error"); }
  };

  const repackage = async (newHTMLs) => {
    setPackaging(true);
    try {
      const { html, url, fname } = await packageZIP(outline, newHTMLs, cfg.color, cfg.withEval);
      setResult({ url, fname, html });
    } catch(e) { console.error(e); }
    setPackaging(false);
  };

  const saveModule = async (modId, newHTML) => {
    const newHTMLs = { ...moduleHTMLs, [modId]: newHTML };
    setModuleHTMLs(newHTMLs);
    setEditingModule(null);
    await repackage(newHTMLs);
    // Also persist updated module to storage
    if (outline) {
      const { html: updHtml } = await packageZIP(outline, newHTMLs, cfg.color, cfg.withEval).catch(()=>({html:""}));
      await saveProject(outline, newHTMLs, cfg, updHtml);
      await refreshProjects();
    }
  };

  const reset = () => {
    setPhase("home"); setFile(null); setCfg({ name:"", color:COLORS[0].val, withEval:false });
    setOutline(null); setModuleHTMLs({}); setResult(null); setPreview(false);
    setEditingModule(null); setErr(""); setGenSteps([]);
  };

  const loadProject = async (proj) => {
    const ol = proj.outline;
    const htmls = proj.moduleHTMLs;
    const c = proj.cfg;
    setCfg(c);
    setOutline(ol);
    setModuleHTMLs(htmls);
    // Re-generate the blob URL (can't be stored)
    try {
      const { html, url, fname } = await packageZIP(ol, htmls, c.color, c.withEval);
      setResult({ url, fname, html });
    } catch(e) {
      setResult({ url:"", fname: ol.title+"_SCORM.zip", html: proj.html || "" });
    }
    setPhase("done");
  };

  const handleDeleteProject = async (id) => {
    await deleteProject(id);
    await refreshProjects();
    setDeleteConfirm(null);
  };

  const ico = { pending:"○", running:"⟳", done:"✓" };

  const css = `
    @import url('https://fonts.googleapis.com/css2?family=Syne:wght@700;800&family=DM+Sans:wght@400;500;600&display=swap');
    *{box-sizing:border-box;margin:0;padding:0}
    body{font-family:'DM Sans',sans-serif;background:#07090F;color:#E2E8F0;min-height:100vh}
    .app{min-height:100vh;display:flex;flex-direction:column;background:radial-gradient(ellipse 80% 50% at 50% -20%,rgba(14,165,233,.1),transparent)}
    .hdr{padding:13px 26px;border-bottom:1px solid rgba(255,255,255,.06);display:flex;align-items:center;gap:10px}
    .logo{width:29px;height:29px;background:linear-gradient(135deg,#0EA5E9,#6366F1);border-radius:7px;display:flex;align-items:center;justify-content:center;font-size:13px}
    .brand{font-family:'Syne',sans-serif;font-weight:800;font-size:16px;letter-spacing:-.3px}
    .brand em{color:#0EA5E9;font-style:normal}
    .beta{margin-left:auto;background:rgba(14,165,233,.12);border:1px solid rgba(14,165,233,.25);color:#7DD3FC;font-size:11px;padding:3px 9px;border-radius:20px;font-weight:600}
    .main{flex:1;display:flex;flex-direction:column;align-items:center;padding:22px 14px}.main.centered{justify-content:center}
    .glass{background:rgba(255,255,255,.04);border:1px solid rgba(255,255,255,.08);border-radius:16px;backdrop-filter:blur(8px)}
    .uw{width:100%;max-width:470px;text-align:center}
    .uh{font-family:'Syne',sans-serif;font-size:27px;font-weight:800;line-height:1.15;margin-bottom:9px;background:linear-gradient(135deg,#F8FAFC 40%,#64748B);-webkit-background-clip:text;-webkit-text-fill-color:transparent}
    .us{color:#475569;margin-bottom:24px;font-size:13px;line-height:1.6}
    .drop{border:2px dashed rgba(14,165,233,.28);border-radius:13px;padding:34px 20px;cursor:pointer;transition:all .2s;background:rgba(14,165,233,.02)}
    .drop:hover,.drop.drag{border-color:#0EA5E9;background:rgba(14,165,233,.07)}
    .drop-ico{font-size:34px;margin-bottom:10px}
    .drop-t{font-size:13px;color:#94A3B8;margin-bottom:4px}.drop-s{font-size:12px;color:#334155}
    .bup{margin-top:12px;background:linear-gradient(135deg,#0EA5E9,#6366F1);color:#fff;border:none;padding:8px 20px;border-radius:8px;font-size:13px;font-weight:600;cursor:pointer;font-family:'DM Sans',sans-serif}
    .fmts{display:flex;gap:5px;justify-content:center;margin-top:10px}
    .fmt{background:rgba(255,255,255,.04);border:1px solid rgba(255,255,255,.07);border-radius:5px;padding:2px 8px;font-size:11px;color:#334155}
    .cw{width:100%;max-width:490px}
    .ch{font-family:'Syne',sans-serif;font-size:21px;font-weight:800;margin-bottom:4px}
    .cs{color:#475569;margin-bottom:16px;font-size:12px}
    .fpill{display:flex;align-items:center;gap:8px;background:rgba(16,185,129,.09);border:1px solid rgba(16,185,129,.2);border-radius:8px;padding:7px 11px;margin-bottom:13px;font-size:12px;color:#6EE7B7}
    .fg{margin-bottom:13px}
    .fl{font-size:10px;font-weight:700;color:#475569;text-transform:uppercase;letter-spacing:.7px;margin-bottom:5px}
    .fi{width:100%;background:rgba(255,255,255,.05);border:1px solid rgba(255,255,255,.09);border-radius:8px;padding:8px 11px;font-size:13px;color:#F1F5F9;font-family:'DM Sans',sans-serif;outline:none;transition:border .2s}
    .fi:focus{border-color:#0EA5E9}
    .cgrid{display:grid;grid-template-columns:repeat(3,1fr);gap:5px}
    .cbtn{display:flex;align-items:center;gap:6px;background:rgba(255,255,255,.04);border:1px solid rgba(255,255,255,.07);border-radius:7px;padding:6px 8px;cursor:pointer;font-size:11px;color:#475569;width:100%;transition:all .15s}
    .cbtn.on{border-color:rgba(255,255,255,.25);background:rgba(255,255,255,.08);color:#E2E8F0}
    .cdot{width:12px;height:12px;border-radius:50%;flex-shrink:0}
    .tr{display:flex;align-items:center;gap:9px;cursor:pointer}
    .tog{width:36px;height:20px;border-radius:10px;background:rgba(255,255,255,.08);border:1px solid rgba(255,255,255,.1);position:relative;transition:background .2s;flex-shrink:0}
    .tog.on{background:#0EA5E9;border-color:#0EA5E9}
    .tog::after{content:'';position:absolute;width:14px;height:14px;background:#fff;border-radius:50%;top:2px;left:2px;transition:left .2s}
    .tog.on::after{left:18px}
    .tl{font-size:12px;color:#475569}
    .rb{display:flex;gap:7px;margin-top:16px}
    .bback{flex:1;background:rgba(255,255,255,.04);border:1px solid rgba(255,255,255,.08);color:#475569;padding:9px;border-radius:8px;cursor:pointer;font-family:'DM Sans',sans-serif;font-size:12px}
    .bgo{flex:2;background:linear-gradient(135deg,#0EA5E9,#6366F1);color:#fff;border:none;padding:9px;border-radius:8px;cursor:pointer;font-family:'DM Sans',sans-serif;font-size:13px;font-weight:600}
    .bgo:hover{opacity:.9}
    .gw{width:100%;max-width:440px}
    .gh{font-family:'Syne',sans-serif;font-size:19px;font-weight:800;margin-bottom:5px;text-align:center}
    .gs{color:#475569;font-size:12px;margin-bottom:20px;line-height:1.6;text-align:center}
    .step-list{display:flex;flex-direction:column;max-height:52vh;overflow-y:auto}
    .si{display:flex;align-items:center;gap:10px;padding:9px 13px;border-bottom:1px solid rgba(255,255,255,.04)}
    .si:last-child{border:none}
    .sico{width:26px;height:26px;border-radius:6px;display:flex;align-items:center;justify-content:center;flex-shrink:0;font-size:12px;font-weight:700}
    .sico.pending{background:rgba(255,255,255,.04);color:#1E293B}
    .sico.running{background:rgba(14,165,233,.18);color:#0EA5E9;animation:pulse 1s infinite}
    .sico.done{background:rgba(16,185,129,.18);color:#10B981}
    .slbl{font-size:12px}.slbl.pending{color:#1E293B}.slbl.running{color:#7DD3FC}.slbl.done{color:#6EE7B7}
    @keyframes pulse{0%,100%{opacity:1}50%{opacity:.4}}
    /* DONE */
    .dw{width:100%;max-width:580px}
    .dh{font-family:'Syne',sans-serif;font-size:20px;font-weight:800;margin-bottom:3px}
    .ds{color:#475569;font-size:12px;margin-bottom:14px}
    .mods-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(200px,1fr));gap:8px;margin-bottom:14px}
    .mod-edit-card{background:rgba(255,255,255,.04);border:1px solid rgba(255,255,255,.08);border-radius:10px;padding:11px 13px;display:flex;align-items:center;gap:10px;transition:all .15s}
    .mod-edit-card:hover{border-color:rgba(14,165,233,.3);background:rgba(14,165,233,.05)}
    .mec-num{width:24px;height:24px;border-radius:6px;background:rgba(14,165,233,.15);color:#7DD3FC;display:flex;align-items:center;justify-content:center;font-size:11px;font-weight:700;flex-shrink:0}
    .mec-name{flex:1;font-size:12px;color:#94A3B8;line-height:1.3}
    .mec-btn{background:rgba(255,255,255,.06);border:1px solid rgba(255,255,255,.1);color:#64748B;padding:4px 9px;border-radius:5px;cursor:pointer;font-size:11px;font-weight:600;font-family:'DM Sans',sans-serif;transition:all .15s;white-space:nowrap}
    .mec-btn:hover{background:rgba(14,165,233,.15);border-color:rgba(14,165,233,.3);color:#7DD3FC}
    .actions{display:flex;gap:7px;flex-wrap:wrap}
    .bdl{flex:2;background:linear-gradient(135deg,#10B981,#0EA5E9);color:#fff;border:none;padding:10px;border-radius:9px;font-size:13px;font-weight:700;cursor:pointer;font-family:'DM Sans',sans-serif;display:flex;align-items:center;justify-content:center;gap:6px}
    .bdl:hover{opacity:.9}
    .bview{flex:1;background:rgba(14,165,233,.12);border:1px solid rgba(14,165,233,.28);color:#7DD3FC;padding:10px;border-radius:9px;font-size:12px;font-weight:600;cursor:pointer;font-family:'DM Sans',sans-serif;display:flex;align-items:center;justify-content:center;gap:5px}
    .bview:hover{background:rgba(14,165,233,.2)}
    .bnew{background:rgba(255,255,255,.04);border:1px solid rgba(255,255,255,.08);color:#475569;padding:10px 16px;border-radius:9px;font-size:12px;cursor:pointer;font-family:'DM Sans',sans-serif}
    .pkg-badge{display:inline-flex;align-items:center;gap:5px;background:rgba(245,158,11,.1);border:1px solid rgba(245,158,11,.25);color:#FCD34D;padding:3px 9px;border-radius:6px;font-size:11px;font-weight:600;margin-bottom:10px}
    .ew{max-width:420px;text-align:center}
    .eico{font-size:36px;margin-bottom:10px}
    .eh{font-family:'Syne',sans-serif;font-size:18px;font-weight:800;margin-bottom:5px;color:#FCA5A5}
    .em{color:#475569;font-size:12px;margin-bottom:13px;background:rgba(239,68,68,.09);border:1px solid rgba(239,68,68,.18);border-radius:8px;padding:9px 12px;word-break:break-word;line-height:1.6}
    .br{background:rgba(239,68,68,.12);border:1px solid rgba(239,68,68,.25);color:#FCA5A5;padding:8px 16px;border-radius:7px;cursor:pointer;font-family:'DM Sans',sans-serif;font-size:13px;font-weight:500}
    .br2{background:rgba(255,255,255,.04);border:1px solid rgba(255,255,255,.08);color:#475569;padding:8px 16px;border-radius:7px;cursor:pointer;font-family:'DM Sans',sans-serif;font-size:13px;margin-left:6px}
    /* HOME / PROJECTS */
    .hw{width:100%;max-width:800px;padding-top:10px}
    .hw-top{display:flex;align-items:center;gap:10px;margin-bottom:20px}
    .hw-title{font-family:'Syne',sans-serif;font-size:22px;font-weight:800;flex:1}
    .hw-new{background:linear-gradient(135deg,#0EA5E9,#6366F1);color:#fff;border:none;padding:8px 18px;border-radius:8px;cursor:pointer;font-family:'DM Sans',sans-serif;font-size:13px;font-weight:600;display:flex;align-items:center;gap:6px;transition:opacity .15s}
    .hw-new:hover{opacity:.9}
    .proj-grid{display:grid;grid-template-columns:repeat(auto-fill,minmax(290px,1fr));gap:10px}
    .proj-card{background:rgba(255,255,255,.04);border:1px solid rgba(255,255,255,.08);border-radius:12px;padding:14px 16px;cursor:pointer;transition:all .18s;position:relative;overflow:hidden}
    .proj-card:hover{border-color:rgba(255,255,255,.2);background:rgba(255,255,255,.07);transform:translateY(-1px)}
    .proj-accent{position:absolute;top:0;left:0;right:0;height:3px;border-radius:12px 12px 0 0}
    .proj-title{font-size:14px;font-weight:700;color:#E2E8F0;margin-bottom:4px;line-height:1.3;padding-top:6px}
    .proj-meta{display:flex;align-items:center;gap:8px;margin-bottom:10px;flex-wrap:wrap}
    .proj-tag{font-size:10px;color:#475569;background:rgba(255,255,255,.05);border:1px solid rgba(255,255,255,.07);padding:2px 7px;border-radius:20px}
    .proj-time{font-size:10px;color:#334155;margin-left:auto}
    .proj-mods{display:flex;flex-wrap:wrap;gap:4px;margin-bottom:11px}
    .proj-mod{font-size:10px;color:#475569;background:rgba(255,255,255,.04);border:1px solid rgba(255,255,255,.06);padding:2px 7px;border-radius:4px;white-space:nowrap;overflow:hidden;text-overflow:ellipsis;max-width:130px}
    .proj-actions{display:flex;gap:6px}
    .proj-open{flex:1;background:rgba(14,165,233,.12);border:1px solid rgba(14,165,233,.25);color:#7DD3FC;padding:6px;border-radius:7px;cursor:pointer;font-family:'DM Sans',sans-serif;font-size:12px;font-weight:600;text-align:center;transition:background .15s}
    .proj-open:hover{background:rgba(14,165,233,.25)}
    .proj-del{background:rgba(239,68,68,.08);border:1px solid rgba(239,68,68,.15);color:#475569;padding:6px 10px;border-radius:7px;cursor:pointer;font-family:'DM Sans',sans-serif;font-size:12px;transition:all .15s}
    .proj-del:hover{background:rgba(239,68,68,.18);border-color:rgba(239,68,68,.35);color:#FCA5A5}
    .proj-empty{text-align:center;padding:48px 20px;color:#334155}
    .proj-empty-ico{font-size:40px;margin-bottom:12px}
    .proj-empty-t{font-size:15px;font-weight:700;color:#475569;margin-bottom:6px}
    .proj-empty-s{font-size:13px;color:#334155;margin-bottom:20px;line-height:1.5}
    .del-confirm{position:fixed;inset:0;background:rgba(0,0,0,.7);z-index:10000;display:flex;align-items:center;justify-content:center;backdrop-filter:blur(4px)}
    .del-box{background:#0F172A;border:1px solid rgba(239,68,68,.3);border-radius:14px;padding:24px 28px;max-width:340px;text-align:center}
    .del-ico{font-size:32px;margin-bottom:10px}
    .del-title{font-size:16px;font-weight:700;margin-bottom:6px;color:#FCA5A5}
    .del-sub{font-size:13px;color:#475569;margin-bottom:18px;line-height:1.5}
    .del-btns{display:flex;gap:8px}
    .del-cancel{flex:1;background:rgba(255,255,255,.05);border:1px solid rgba(255,255,255,.1);color:#475569;padding:9px;border-radius:8px;cursor:pointer;font-family:'DM Sans',sans-serif;font-size:13px}
    .del-ok{flex:1;background:rgba(239,68,68,.2);border:1px solid rgba(239,68,68,.4);color:#FCA5A5;padding:9px;border-radius:8px;cursor:pointer;font-family:'DM Sans',sans-serif;font-size:13px;font-weight:600}
    .hdr-proj-btn{background:rgba(255,255,255,.05);border:1px solid rgba(255,255,255,.09);color:#64748B;padding:5px 12px;border-radius:7px;cursor:pointer;font-family:'DM Sans',sans-serif;font-size:12px;font-weight:500;display:flex;align-items:center;gap:5px;transition:all .15s;margin-left:6px}
    .hdr-proj-btn:hover{background:rgba(255,255,255,.09);color:#94A3B8}
  `;

  return (<>
    <style>{css}</style>
    <div className="app">
      <header className="hdr">
        <div className="logo" style={{cursor:"pointer"}} onClick={reset}>⚡</div>
        <div className="brand" style={{cursor:"pointer"}} onClick={reset}>SCORM<em>forge</em></div>
        <div className="beta">Beta</div>
        <div style={{marginLeft:"auto",display:"flex",alignItems:"center",gap:"6px"}}>
          {phase!=="home" && (
            <button className="hdr-proj-btn" onClick={reset}>
              📁 Mis proyectos
              {projects.length>0 && <span style={{background:"rgba(14,165,233,.2)",color:"#7DD3FC",borderRadius:"20px",padding:"0 5px",fontSize:"10px",fontWeight:700}}>{projects.length}</span>}
            </button>
          )}
          {phase!=="home" && phase!=="upload" && phase!=="generating" && (
            <button className="hdr-proj-btn" onClick={()=>{reset();setTimeout(()=>setPhase("upload"),0);}}>
              + Nuevo
            </button>
          )}
        </div>
      </header>
      <main className={"main" + (phase==="home" ? "" : " centered")}>

        {phase==="home" && (
          <div className="hw">
            <div className="hw-top">
              <h2 className="hw-title">📁 Mis proyectos</h2>
              <button className="hw-new" onClick={()=>setPhase("upload")}>
                ⚡ Nuevo curso
              </button>
            </div>

            {projLoading ? (
              <div style={{textAlign:"center",padding:"40px",color:"#334155",fontSize:"13px"}}>Cargando proyectos...</div>
            ) : projects.length === 0 ? (
              <div className="glass proj-empty">
                <div className="proj-empty-ico">📚</div>
                <div className="proj-empty-t">No tienes proyectos guardados</div>
                <div className="proj-empty-s">Sube un PDF o PPTX y convierte<br/>tu primer documento en un curso SCORM.</div>
                <button className="hw-new" onClick={()=>setPhase("upload")}>⚡ Crear primer curso</button>
              </div>
            ) : (
              <div className="proj-grid">
                {projects.map(proj => (
                  <div key={proj.id} className="proj-card" onClick={()=>loadProject(proj)}>
                    <div className="proj-accent" style={{background:proj.color||"#F59E0B"}}/>
                    <div className="proj-title">{proj.title}</div>
                    <div className="proj-meta">
                      <span className="proj-tag">{proj.modules?.length||"?"} módulos</span>
                      {proj.withEval && <span className="proj-tag">📝 Con eval</span>}
                      <span className="proj-time">{timeAgo(proj.savedAt)}</span>
                    </div>
                    <div className="proj-mods">
                      {(proj.modules||[]).slice(0,4).map(m=>(
                        <span key={m.id} className="proj-mod">M{m.id} {m.name}</span>
                      ))}
                      {(proj.modules||[]).length>4 && <span className="proj-mod">+{proj.modules.length-4} más</span>}
                    </div>
                    <div className="proj-actions" onClick={e=>e.stopPropagation()}>
                      <div className="proj-open" onClick={()=>loadProject(proj)}>Abrir →</div>
                      <button className="proj-del" onClick={e=>{e.stopPropagation();setDeleteConfirm(proj);}}>🗑</button>
                    </div>
                  </div>
                ))}
              </div>
            )}
          </div>
        )}

        {phase==="upload" && (
          <div className="uw">
            <h1 className="uh">Convierte tu documento<br/>en un curso interactivo</h1>
            <p className="us">PDF o PPTX → SCORM con contenido rico, navegación,<br/>evaluaciones y editor por módulo.</p>
            <div className={`drop${drag?" drag":""}`} onDrop={onDrop} onDragOver={e=>{e.preventDefault();setDrag(true);}} onDragLeave={()=>setDrag(false)} onClick={()=>fRef.current?.click()}>
              <div className="drop-ico">📄</div>
              <p className="drop-t">Arrastra tu archivo aquí</p>
              <p className="drop-s">o haz clic para buscar</p>
              <button className="bup" onClick={e=>{e.stopPropagation();fRef.current?.click();}}>Seleccionar archivo</button>
              <div className="fmts"><span className="fmt">PDF</span><span className="fmt">PPTX</span></div>
            </div>
            <input ref={fRef} type="file" accept=".pdf,.pptx" style={{display:"none"}} onChange={e=>handleFile(e.target.files[0])} />
            {err && <p style={{color:"#FCA5A5",marginTop:9,fontSize:12}}>{err}</p>}
          </div>
        )}

        {phase==="config" && (
          <div className="cw">
            <h2 className="ch">Configura el curso</h2>
            <p className="cs">Claude generará contenido rico por módulo. Podrás editarlo después.</p>
            <div className="fpill">
              <span>{file?.name?.endsWith(".pdf")?"📄":"📊"}</span>
              <span style={{flex:1}}>{file?.name}</span>
              <span style={{color:"#334155",fontSize:11}}>{(file?.size/1024).toFixed(0)} KB</span>
            </div>
            <div className="fg"><div className="fl">Nombre del curso</div>
              <input className="fi" placeholder="Ej: Seguridad en Izaje de Cargas" value={cfg.name} onChange={e=>setCfg(c=>({...c,name:e.target.value}))} /></div>
            <div className="fg"><div className="fl">Color principal</div>
              <div className="cgrid">{COLORS.map(col=>(
                <button key={col.val} className={`cbtn${cfg.color===col.val?" on":""}`} onClick={()=>setCfg(c=>({...c,color:col.val}))}>
                  <div className="cdot" style={{background:col.val}}/><span>{col.label}</span>
                </button>
              ))}</div></div>
            <div className="fg"><div className="fl">Evaluaciones</div>
              <div className="tr" onClick={()=>setCfg(c=>({...c,withEval:!c.withEval}))}>
                <div className={`tog${cfg.withEval?" on":""}`}/>
                <span className="tl">{cfg.withEval?"Con quizzes por módulo":"Solo contenido (sin evaluaciones)"}</span>
              </div></div>
            <div className="rb">
              <button className="bback" onClick={reset}>← Proyectos</button>
              <button className="bgo" onClick={run}>Generar SCORM →</button>
            </div>
          </div>
        )}

        {phase==="generating" && (
          <div className="gw">
            <h2 className="gh">Generando curso...</h2>
            <p className="gs">Claude lee tu documento y escribe contenido rico<br/>para cada módulo individualmente.</p>
            <div className="glass step-list">
              {genSteps.map((st,i)=>(
                <div key={i} className="si">
                  <div className={`sico ${st.s}`}>{ico[st.s]}</div>
                  <span className={`slbl ${st.s}`}>{st.label}</span>
                </div>
              ))}
            </div>
          </div>
        )}

        {phase==="done" && result && outline && (
          <div className="dw">
            <div style={{display:"flex",alignItems:"center",gap:"10px",marginBottom:"6px"}}>
              <span style={{fontSize:"22px"}}>🎉</span>
              <h2 className="dh">Curso listo — ahora puedes editarlo</h2>
            </div>
            <p className="ds">Edita cada módulo con IA o manualmente, luego descarga el ZIP actualizado.</p>
            {packaging && <div className="pkg-badge">⟳ Re-empaquetando SCORM...</div>}
            <div className="mods-grid">
              {outline.modules.map(m=>(
                <div key={m.id} className="mod-edit-card">
                  <div className="mec-num">{m.id}</div>
                  <div className="mec-name">{m.name}</div>
                  <button className="mec-btn" onClick={()=>setEditingModule(m)}>✏️ Editar</button>
                </div>
              ))}
            </div>
            <div className="actions">
              <button className="bview" onClick={()=>setPreview(true)}>👁 Preview</button>
              <button className="bdl" disabled={packaging} onClick={()=>{const a=document.createElement("a");a.href=result.url;a.download=result.fname;a.click();}}>⬇ Descargar ZIP</button>
              <button className="bnew" onClick={reset}>Nuevo</button>
            </div>
          </div>
        )}

        {phase==="error" && (
          <div className="ew">
            <div className="eico">⚠️</div>
            <h2 className="eh">Error al generar</h2>
            <p className="em">{err}</p>
            <p style={{fontSize:"11px",color:"#334155",marginBottom:"13px",lineHeight:1.5}}>
              Si el error persiste, usa el diagnóstico para ver el error exacto.
            </p>
            <div style={{display:"flex",gap:"7px",justifyContent:"center",flexWrap:"wrap"}}>
              <button className="br" onClick={()=>setPhase("config")}>Intentar de nuevo</button>
              <button className="br2" onClick={reset}>Volver a proyectos</button>
            </div>
            <button onClick={async()=>{
              const results = [];
              const tests = [
                {
                  label:"Test 1: modelo claude-sonnet-4-20250514",
                  body:{model:"claude-sonnet-4-20250514",max_tokens:1000,messages:[{role:"user",content:"di hola"}]}
                },
                {
                  label:"Test 2: modelo claude-haiku-4-5-20251001",
                  body:{model:"claude-haiku-4-5-20251001",max_tokens:1000,messages:[{role:"user",content:"di hola"}]}
                },
                {
                  label:"Test 3: sin model field",
                  body:{max_tokens:1000,messages:[{role:"user",content:"di hola"}]}
                },
              ];
              for(const t of tests){
                try{
                  const r=await fetch("https://api.anthropic.com/v1/messages",{
                    method:"POST",
                    headers:{"Content-Type":"application/json"},
                    body:JSON.stringify(t.body)
                  });
                  const txt=await r.text();
                  results.push(t.label+": HTTP "+r.status+" → "+txt.slice(0,120));
                }catch(e){
                  results.push(t.label+": CATCH "+e.name+": "+e.message);
                }
              }
              setErr(results.join("\n\n"));
            }} style={{marginTop:"10px",background:"rgba(99,102,241,.15)",border:"1px solid rgba(99,102,241,.3)",color:"#A5B4FC",padding:"7px 16px",borderRadius:"7px",cursor:"pointer",fontFamily:"'DM Sans',sans-serif",fontSize:"12px",width:"100%"}}>
              🔍 Diagnóstico completo (3 tests)
            </button>
          </div>
        )}

      </main>
    </div>

    {/* EDITOR */}
    {editingModule && (
      <EditorPanel
        module={editingModule}
        html={moduleHTMLs[editingModule.id]||""}
        outline={outline}
        onSave={(newHTML)=>saveModule(editingModule.id, newHTML)}
        onClose={()=>setEditingModule(null)}
      />
    )}

    {/* PREVIEW */}
    {preview && result && outline && (
      <div style={{position:"fixed",inset:0,zIndex:9998,display:"flex",flexDirection:"column",background:"#07090F"}}>
        <div style={{height:"50px",background:"#0F172A",borderBottom:"1px solid rgba(255,255,255,.08)",display:"flex",alignItems:"center",gap:"8px",padding:"0 12px",flexShrink:0}}>
          <div style={{width:"24px",height:"24px",background:"linear-gradient(135deg,#0EA5E9,#6366F1)",borderRadius:"6px",display:"flex",alignItems:"center",justifyContent:"center",fontSize:"11px"}}>⚡</div>
          <span style={{fontFamily:"'Syne',sans-serif",fontWeight:800,fontSize:"13px"}}>SCORM<em style={{color:"#0EA5E9",fontStyle:"normal"}}>forge</em></span>
          <div style={{width:"1px",height:"20px",background:"rgba(255,255,255,.1)",margin:"0 4px"}}/>
          {/* Module edit buttons */}
          <div style={{display:"flex",gap:"5px",overflowX:"auto",flex:1}}>
            {outline.modules.map(m=>(
              <button key={m.id} onClick={()=>{setPreview(false);setEditingModule(m);}}
                style={{background:"rgba(14,165,233,.1)",border:"1px solid rgba(14,165,233,.2)",color:"#7DD3FC",padding:"4px 10px",borderRadius:"6px",cursor:"pointer",fontFamily:"'DM Sans',sans-serif",fontSize:"11px",fontWeight:600,whiteSpace:"nowrap",flexShrink:0,transition:"all .15s"}}
                onMouseOver={e=>{e.currentTarget.style.background="rgba(14,165,233,.25)";}}
                onMouseOut={e=>{e.currentTarget.style.background="rgba(14,165,233,.1)";}}>
                ✏️ M{m.id}
              </button>
            ))}
          </div>
          <button onClick={()=>{const a=document.createElement("a");a.href=result.url;a.download=result.fname;a.click();}}
            style={{background:"rgba(16,185,129,.15)",border:"1px solid rgba(16,185,129,.3)",color:"#6EE7B7",padding:"5px 11px",borderRadius:"6px",cursor:"pointer",fontFamily:"'DM Sans',sans-serif",fontSize:"11px",fontWeight:600,flexShrink:0}}>⬇</button>
          <button onClick={()=>setPreview(false)}
            style={{background:"rgba(255,255,255,.06)",border:"1px solid rgba(255,255,255,.1)",color:"#94A3B8",padding:"5px 11px",borderRadius:"6px",cursor:"pointer",fontFamily:"'DM Sans',sans-serif",fontSize:"11px",fontWeight:600,flexShrink:0}}>✕ Cerrar</button>
        </div>
        <iframe title="SCORM Preview" srcDoc={result.html} style={{flex:1,border:"none"}} sandbox="allow-scripts allow-same-origin allow-forms" />
      </div>
    )}
    {/* DELETE CONFIRM */}
    {deleteConfirm && (
      <div className="del-confirm" onClick={()=>setDeleteConfirm(null)}>
        <div className="del-box" onClick={e=>e.stopPropagation()}>
          <div className="del-ico">🗑️</div>
          <div className="del-title">Eliminar proyecto</div>
          <div className="del-sub">¿Eliminar <strong style={{color:"#E2E8F0"}}>{deleteConfirm.title}</strong>?<br/>Esta acción no se puede deshacer.</div>
          <div className="del-btns">
            <button className="del-cancel" onClick={()=>setDeleteConfirm(null)}>Cancelar</button>
            <button className="del-ok" onClick={()=>handleDeleteProject(deleteConfirm.id)}>Sí, eliminar</button>
          </div>
        </div>
      </div>
    )}
  </>);
}
