/* Portal O-Lab — app del Aplicante. Expone window.AplicanteApp. */
(function () {
  const { useState, useEffect, useRef } = React;
  const { C, FONT, Icon, Avatar, Bar, Pill, Btn, Card, MatchRing, computeMatch, matchColor, useIsMobile } = window.PORTAL_UI;
  const B = () => window.PORTAL_BACKEND;
  const SEED = () => window.PORTAL_SEED;

  // "A Juan y 3 más les gusta" — resumen de quiénes reaccionaron.
  function likesLabel(names, count) {
    names = names || []; count = count || 0;
    if (!count) return "Sé el primero en reaccionar";
    const verb = count === 1 ? "le gusta" : "les gusta";
    if (names.length <= 2) return "A " + names.join(" y ") + " " + verb;
    return "A " + names.slice(0, 2).join(", ") + " y " + (count - 2) + " más " + verb;
  }

  function Section({ title, sub, children, right }) {
    return (
      <div style={{ display: "flex", flexDirection: "column", gap: 12 }}>
        <div style={{ display: "flex", alignItems: "flex-end", justifyContent: "space-between", gap: 12 }}>
          <div>
            <div style={{ fontSize: 22, fontWeight: 800, color: C.ink, letterSpacing: "-0.02em" }}>{title}</div>
            {sub && <div style={{ fontSize: 13, fontWeight: 600, color: C.mut, marginTop: 4 }}>{sub}</div>}
          </div>
          {right}
        </div>
        {children}
      </div>
    );
  }

  /* --------------------------- RETOS ------------------------------ */
  const RETO_ACCENTS = {
    orange: { grad: "linear-gradient(150deg,#FF6600,#FF3D7F)", solid: C.orange, dk: C.orangeDk, soft: C.orangeSoft, softBd: C.orangeSoftBd },
    violet: { grad: "linear-gradient(150deg,#6C4CF1,#12C2E9)", solid: C.violet, dk: C.violetDk, soft: C.violetSoft, softBd: C.violetSoftBd },
    cyan: { grad: "linear-gradient(150deg,#12C2E9,#0E8F5B)", solid: C.cyan, dk: C.cyanDk, soft: C.cyanSoft, softBd: "#B4E9F6" },
    green: { grad: "linear-gradient(150deg,#0E8F5B,#C6F24E)", solid: C.green, dk: "#0B6B44", soft: C.greenBg, softBd: C.greenBd },
  };
  // Los 4 retos base que todo aplicante debe completar (se rastrean solos con su perfil).
  function baseRetos(me) {
    const labsDone = Object.values(me.myLabs || {}).filter((l) => l.status === "completado").length;
    const testsDone = Object.keys(me.testResults || {}).length;
    const appliedDone = Object.keys(me.appliedJobs || {}).length;
    const profileImproved = !!me.photoUrl && !!(me.bio && String(me.bio).trim()) && (me.desiredRoles || []).length > 0;
    return [
      { key: "lab", title: "Completa tu primer Lab", desc: "Abre y termina al menos 1 Lab para demostrar lo que aprendes.", cur: Math.min(labsDone, 1), target: 1, done: labsDone >= 1, xp: 150, accent: "orange", cta: "Ir a Labs", goTab: "labs" },
      { key: "test", title: "Haz tu primer Test", desc: "Descubre tus habilidades resolviendo al menos 1 test.", cur: Math.min(testsDone, 1), target: 1, done: testsDone >= 1, xp: 150, accent: "violet", cta: "Ir a Tests", goTab: "tests" },
      { key: "perfil", title: "Mejora tu perfil", desc: "Sube tu foto, cuéntanos sobre ti y elige tus roles para que los empleadores te descubran.", cur: profileImproved ? 1 : 0, target: 1, done: profileImproved, xp: 100, accent: "cyan", cta: "Ir a Perfil", goTab: "perfil" },
      { key: "aplica", title: "Aplica a tu primera vacante", desc: "Postúlate a por lo menos 1 vacante que haga match contigo.", cur: Math.min(appliedDone, 1), target: 1, done: appliedDone >= 1, xp: 150, accent: "green", cta: "Ver vacantes", goTab: "vacantes" },
    ];
  }

  const RETO_ICON = { lab: "flask", test: "test", perfil: "user", aplica: "briefcase" };
  function RetoCard({ r, claimed, onClaim, goTab }) {
    const a = RETO_ACCENTS[r.accent] || RETO_ACCENTS.orange;
    const pct = Math.round((r.cur / r.target) * 100);
    const done = r.done;
    return (
      <div style={{ position: "relative", flex: "0 0 224px", borderRadius: 18, padding: 14, overflow: "hidden", display: "flex", flexDirection: "column",
        background: done ? a.grad : "#fff", color: done ? "#fff" : C.ink,
        border: done ? "2px solid rgba(255,255,255,0.4)" : `2px solid ${a.softBd}`,
        boxShadow: done ? "0 12px 26px rgba(0,0,0,0.22)" : "0 6px 16px rgba(23,19,31,0.07)",
        animation: done ? "olabGlow 2.8s ease-in-out infinite" : "none" }}>
        <div style={{ display: "flex", alignItems: "center", gap: 10, marginBottom: 9 }}>
          <div style={{ width: 42, height: 42, borderRadius: 13, flexShrink: 0, display: "flex", alignItems: "center", justifyContent: "center",
            background: done ? "rgba(255,255,255,0.22)" : a.grad, boxShadow: done ? "none" : `0 6px 15px ${a.solid}55`,
            animation: done ? "olabFloat 3s ease-in-out infinite" : "none" }}>
            <Icon name={done ? "trophy" : (RETO_ICON[r.key] || "star")} size={23} color="#fff" />
          </div>
          <div style={{ fontSize: 9, fontWeight: 900, letterSpacing: "0.13em", color: done ? "rgba(255,255,255,0.95)" : a.dk }}>{done ? "✓ COMPLETADO" : "MISIÓN"}</div>
        </div>
        <div style={{ fontSize: 14, fontWeight: 900, lineHeight: 1.2, letterSpacing: "-0.01em" }}>{r.title}</div>
        <div style={{ fontSize: 11, fontWeight: 600, lineHeight: 1.35, marginTop: 5, color: done ? "rgba(255,255,255,0.85)" : C.mut2, flex: 1 }}>{r.desc}</div>
        <div style={{ position: "relative", height: 9, borderRadius: 999, marginTop: 12, background: done ? "rgba(255,255,255,0.3)" : C.sand2, overflow: "hidden" }}>
          <div style={{ width: pct + "%", height: "100%", borderRadius: 999, background: done ? "#fff" : a.grad }} />
          <div style={{ position: "absolute", top: 0, left: 0, height: "100%", width: "38%", background: "linear-gradient(90deg,transparent,rgba(255,255,255,0.65),transparent)", animation: "olabSweep 2.6s ease-in-out infinite" }} />
        </div>
        <div style={{ display: "flex", justifyContent: "space-between", alignItems: "center", marginTop: 9 }}>
          <span style={{ fontSize: 11, fontWeight: 800, color: done ? "rgba(255,255,255,0.9)" : C.mut }}>{r.cur}/{r.target}</span>
          <span style={{ display: "flex", alignItems: "center", gap: 4, fontSize: 12, fontWeight: 900, padding: "3px 9px", borderRadius: 999, background: done ? "rgba(23,19,31,0.3)" : "#17131F", color: "#fff" }}><Icon name="coin" size={13} color={C.lime} />+{r.xp}</span>
        </div>
        <div style={{ marginTop: 11 }}>
          {done
            ? (claimed
                ? <div style={{ fontSize: 12, fontWeight: 900, color: "#fff", textAlign: "center", letterSpacing: "0.02em" }}>🎉 +{r.xp} XP RECLAMADOS</div>
                : <button onClick={onClaim} style={{ width: "100%", border: 0, background: C.lime, color: C.ink, fontFamily: FONT, fontSize: 12.5, fontWeight: 900, padding: "10px 0", borderRadius: 12, cursor: "pointer", letterSpacing: "0.03em", boxShadow: "0 4px 0 #9FCB2E", animation: "olabPulse 1.8s ease-in-out infinite" }}>RECLAMAR +{r.xp} XP</button>)
            : <button onClick={() => goTab && goTab(r.goTab)} style={{ width: "100%", border: 0, background: a.grad, color: "#fff", fontFamily: FONT, fontSize: 12.5, fontWeight: 900, padding: "10px 0", borderRadius: 12, cursor: "pointer", letterSpacing: "0.02em", boxShadow: `0 5px 14px ${a.solid}66` }}>{r.cta} →</button>}
        </div>
      </div>
    );
  }

  function CustomRetoCard({ c, isAdmin }) {
    async function del() { if (window.confirm("¿Eliminar este reto?")) await B().deleteChallenge(c.id); }
    const xp = String(c.xp || "").replace(/[^0-9]/g, "");
    return (
      <div style={{ position: "relative", flex: "0 0 224px", borderRadius: 18, padding: 14, overflow: "hidden", display: "flex", flexDirection: "column",
        background: "linear-gradient(160deg,#241C3B,#17131F)", color: "#fff", border: "2px solid rgba(198,242,78,0.35)", boxShadow: "0 10px 24px rgba(23,19,31,0.35)" }}>
        {isAdmin && <button onClick={del} title="Eliminar reto" style={{ position: "absolute", top: 8, right: 8, border: 0, background: "rgba(255,255,255,0.16)", color: "#fff", borderRadius: 999, width: 22, height: 22, cursor: "pointer", fontSize: 11, zIndex: 2 }}>✕</button>}
        <div style={{ display: "flex", alignItems: "center", gap: 10, marginBottom: 9 }}>
          <div style={{ width: 42, height: 42, borderRadius: 13, flexShrink: 0, display: "flex", alignItems: "center", justifyContent: "center", background: "linear-gradient(135deg,#C6F24E,#0E8F5B)", boxShadow: "0 6px 15px rgba(198,242,78,0.4)", animation: "olabFloat 3s ease-in-out infinite" }}><Icon name="star" size={23} color="#17131F" /></div>
          <div style={{ fontSize: 9, fontWeight: 900, letterSpacing: "0.12em", color: C.lime }}>RETO ESPECIAL</div>
        </div>
        <div style={{ fontSize: 14, fontWeight: 900, lineHeight: 1.2, paddingRight: isAdmin ? 18 : 0 }}>{c.title}</div>
        {c.description && <div style={{ fontSize: 11, fontWeight: 600, lineHeight: 1.35, marginTop: 5, color: "rgba(255,255,255,0.78)", flex: 1 }}>{c.description}</div>}
        <div style={{ display: "flex", alignItems: "center", justifyContent: "space-between", marginTop: 12, gap: 8 }}>
          {xp ? <span style={{ display: "flex", alignItems: "center", gap: 4, fontSize: 12, fontWeight: 900, padding: "3px 9px", borderRadius: 999, background: "rgba(198,242,78,0.16)", color: C.lime }}><Icon name="coin" size={13} color={C.lime} />+{xp} XP</span> : <span />}
          {c.link && <a href={c.link} target="_blank" rel="noopener" style={{ fontSize: 12.5, fontWeight: 900, color: C.ink, background: C.lime, padding: "8px 13px", borderRadius: 11, textDecoration: "none", boxShadow: "0 4px 0 #9FCB2E" }}>JUGAR →</a>}
        </div>
      </div>
    );
  }

  function RetoForm({ onClose }) {
    const [f, setF] = useState({ title: "", description: "", xp: "", link: "" });
    const [busy, setBusy] = useState(false);
    const set = (k) => (e) => setF((p) => ({ ...p, [k]: e.target.value }));
    async function submit() {
      if (!f.title.trim()) return;
      setBusy(true);
      try { await B().publishChallenge(f); onClose(); } catch (e) { console.warn(e); } finally { setBusy(false); }
    }
    const inp = { width: "100%", fontFamily: FONT, fontSize: 13, fontWeight: 600, color: C.ink, background: C.sand, border: `1px solid ${C.line2}`, borderRadius: 12, padding: "10px 12px", boxSizing: "border-box" };
    return (
      <Card style={{ padding: 14, display: "flex", flexDirection: "column", gap: 10 }}>
        <div style={{ display: "flex", alignItems: "center", gap: 8 }}><Pill color={C.violetDk} bg={C.violetSoft}>ADMIN</Pill><span style={{ fontSize: 13, fontWeight: 800, color: C.ink }}>Nuevo reto de la semana</span></div>
        <input value={f.title} onChange={set("title")} placeholder="Título del reto (ej: Completa 2 Labs de logística)" style={inp} />
        <textarea value={f.description} onChange={set("description")} rows={2} placeholder="Descripción o instrucciones (opcional)" style={{ ...inp, resize: "vertical" }} />
        <div style={{ display: "flex", gap: 8 }}>
          <input value={f.xp} onChange={set("xp")} placeholder="XP (ej: 300)" style={{ ...inp, flex: 1 }} />
          <input value={f.link} onChange={set("link")} placeholder="Link (opcional)" style={{ ...inp, flex: 2 }} />
        </div>
        <div style={{ display: "flex", justifyContent: "flex-end", gap: 8 }}>
          <Btn kind="ghost" onClick={onClose} style={{ padding: "9px 16px" }}>Cancelar</Btn>
          <Btn onClick={submit} style={{ padding: "9px 16px", opacity: busy ? 0.6 : 1 }}>{busy ? "Publicando…" : "Publicar reto"}</Btn>
        </div>
      </Card>
    );
  }

  function RetosSemana({ me, isAdmin, update, goTab }) {
    const [custom, setCustom] = useState(null);
    const [showForm, setShowForm] = useState(false);
    const [open, setOpen] = useState(true);
    useEffect(() => B().subscribeChallenges((l) => setCustom(l)), []);
    const retos = baseRetos(me);
    const claimed = me.retosClaimed || {};
    const doneCount = retos.filter((r) => r.done).length;
    const customList = Array.isArray(custom) ? custom : [];
    function claim(r) {
      if (!r.done || claimed[r.key]) return;
      update({ xp: (Number(me.xp) || 0) + r.xp, coins: (Number(me.coins) || 0) + Math.round(r.xp / 3), retosClaimed: { ...claimed, [r.key]: true } });
    }
    const hudBtn = { border: "1px solid rgba(255,255,255,0.22)", background: "rgba(255,255,255,0.09)", color: "#fff", fontFamily: FONT, fontSize: 12, fontWeight: 800, padding: "6px 11px", borderRadius: 999, cursor: "pointer", display: "flex", alignItems: "center", gap: 5 };
    return (
      <div style={{ position: "relative", borderRadius: 22, padding: open ? "14px 14px 4px" : "12px 14px", overflow: "hidden",
        background: "radial-gradient(1200px 400px at 8% -30%, #3a2170 0%, transparent 60%), radial-gradient(900px 500px at 100% 0%, #5a2500 0%, transparent 55%), #17131F",
        border: "1px solid rgba(255,255,255,0.08)", boxShadow: "0 16px 34px rgba(23,19,31,0.30)" }}>
        <div style={{ position: "absolute", inset: 0, pointerEvents: "none", opacity: 0.55, backgroundImage: "radial-gradient(1.5px 1.5px at 22px 30px, rgba(255,255,255,0.4), transparent), radial-gradient(1.5px 1.5px at 84px 62px, rgba(198,242,78,0.45), transparent), radial-gradient(1.5px 1.5px at 140px 24px, rgba(255,102,0,0.4), transparent)", backgroundSize: "120px 80px, 90px 70px, 160px 90px", animation: "olabStars 16s linear infinite" }} />
        <div style={{ position: "relative", display: "flex", alignItems: "center", justifyContent: "space-between", gap: 10, flexWrap: "wrap" }}>
          <div style={{ display: "flex", alignItems: "center", gap: 11 }}>
            <div style={{ width: 36, height: 36, borderRadius: 11, background: "linear-gradient(135deg,#FF6600,#FF3D7F)", display: "flex", alignItems: "center", justifyContent: "center", boxShadow: "0 6px 15px rgba(255,102,0,0.45)", animation: "olabFloat 3.4s ease-in-out infinite" }}><Icon name="trophy" size={20} color="#fff" /></div>
            <div>
              <div style={{ fontSize: 15, fontWeight: 900, color: "#fff", letterSpacing: "0.03em" }}>MISIONES DE LA SEMANA</div>
              <div style={{ fontSize: 11, fontWeight: 700, color: "rgba(255,255,255,0.62)" }}>{doneCount}/{retos.length} completadas · reclama tu XP 🪙</div>
            </div>
          </div>
          <div style={{ display: "flex", alignItems: "center", gap: 8 }}>
            {isAdmin && <button onClick={() => setShowForm((s) => !s)} style={hudBtn}><Icon name="plus" size={13} color="#fff" />Nuevo reto</button>}
            <button onClick={() => setOpen((o) => !o)} title={open ? "Ocultar" : "Mostrar"} style={{ ...hudBtn, fontWeight: 900, padding: "6px 12px" }}>{open ? "▾" : "▸"}</button>
          </div>
        </div>
        <div style={{ position: "relative", display: "flex", gap: 5, marginTop: 12 }}>
          {retos.map((r, i) => <div key={i} style={{ flex: 1, height: 7, borderRadius: 999, background: r.done ? "linear-gradient(90deg,#C6F24E,#0E8F5B)" : "rgba(255,255,255,0.14)", boxShadow: r.done ? "0 0 10px rgba(198,242,78,0.6)" : "none", transition: "background .3s" }} />)}
        </div>
        {open && (
          <React.Fragment>
            {isAdmin && showForm && <div style={{ position: "relative", marginTop: 12 }}><RetoForm onClose={() => setShowForm(false)} /></div>}
            <div style={{ position: "relative", display: "flex", gap: 10, overflowX: "auto", padding: "14px 0 12px", alignItems: "stretch" }}>
              {retos.map((r) => <RetoCard key={r.key} r={r} claimed={!!claimed[r.key]} onClaim={() => claim(r)} goTab={goTab} />)}
              {customList.map((c) => <CustomRetoCard key={c.id} c={c} isAdmin={isAdmin} />)}
            </div>
          </React.Fragment>
        )}
      </div>
    );
  }

  /* ----------------------------- FEED ----------------------------- */
  function Feed({ me, openRedirect, isAdmin, update, goTab }) {
    const [liked, setLiked] = useState(false);
    const [posts, setPosts] = useState(null);
    const [text, setText] = useState("");
    const [busy, setBusy] = useState(false);
    const [adminOpen, setAdminOpen] = useState(false);
    const [image, setImage] = useState("");
    const [imgBusy, setImgBusy] = useState(false);
    const imgRef = useRef(null);
    const [category, setCategory] = useState("");
    const [title, setTitle] = useState("");
    const [link, setLink] = useState("");
    const [deadline, setDeadline] = useState("");
    const [audience, setAudience] = useState("todos");
    const [aliases, setAliases] = useState({});
    const [alias, setAlias] = useState("");
    const myEmail = (me.email || "").toLowerCase();
    useEffect(() => B().subscribeFeed((list) => setPosts(list)), []);
    useEffect(() => B().subscribeAliases(setAliases), []);
    useEffect(() => { setAlias(aliases[myEmail] || ""); }, [aliases, myEmail]);
    const livePosts = (Array.isArray(posts) ? posts : []).filter((p) => !p.audience || p.audience === "todos" || p.audience === "aplicantes");
    async function onPickImage(e) { const file = e.target.files && e.target.files[0]; e.target.value = ""; if (!file) return; setImgBusy(true); try { const url = await B().uploadFeedImage(file); setImage(url); } catch (err) { console.warn(err); } finally { setImgBusy(false); } }
    const CAT_DATED = category === "lab" || category === "test" || category === "evento";
    const missingTitle = CAT_DATED && !title.trim();
    const missingBody = CAT_DATED && !text.trim();
    async function publish() { if (!text.trim() && !image) return; if (missingTitle || missingBody) return; setBusy(true); try { await B().publishPost({ text, title, image, category, link, audience, alias, deadline }); if ((aliases[myEmail] || "") !== alias) B().setAlias(alias); setText(""); setTitle(""); setImage(""); setCategory(""); setLink(""); setDeadline(""); setAudience("todos"); } catch (e) { console.warn(e); } finally { setBusy(false); } }
    const CATS = [["", "General", C.mut3], ["lab", "Lab", C.violet], ["test", "Test", C.orange], ["evento", "Evento", C.green], ["vacante", "Vacante", C.cyanDk]];
    const AUDS = [["todos", "Todos"], ["aplicantes", "Solo aplicantes"], ["empresas", "Solo empresas"]];
    return (
      <div style={{ display: "flex", flexDirection: "column", gap: 16 }}>
        {isAdmin ? (
          <Card style={{ padding: 14 }}>
            <div style={{ display: "flex", alignItems: "center", justifyContent: "space-between", marginBottom: 10 }}>
              <div style={{ display: "flex", alignItems: "center", gap: 8 }}><Pill color={C.violetDk} bg={C.violetSoft}>ADMIN</Pill><span style={{ fontSize: 13, fontWeight: 800, color: C.ink }}>Publicar en el feed</span></div>
              <button onClick={() => setAdminOpen(true)} style={{ border: `1px solid ${C.line2}`, background: "#fff", color: C.mut3, fontFamily: FONT, fontSize: 12, fontWeight: 800, padding: "6px 11px", borderRadius: 999, cursor: "pointer" }}>Gestionar admins</button>
            </div>
            <div style={{ display: "flex", gap: 10, alignItems: "flex-start" }}>
              <Avatar name={me.name} bg="linear-gradient(135deg,#FF6600,#FF3D7F)" size={34} />
              <textarea value={text} onChange={(e) => setText(e.target.value)} rows={2} placeholder="Comparte un anuncio, reto o consejo con la comunidad…" style={{ flex: 1, fontFamily: FONT, fontSize: 13, fontWeight: 500, color: C.ink, background: C.sand, border: `1px solid ${C.line2}`, borderRadius: 14, padding: "10px 12px", resize: "vertical" }} />
            </div>
            <div style={{ marginTop: 10 }}>
              <div style={{ fontSize: 10, fontWeight: 800, letterSpacing: "0.06em", color: C.mut4, marginBottom: 6 }}>CATEGORÍA</div>
              <div style={{ display: "flex", gap: 6, flexWrap: "wrap" }}>
                {CATS.map(([k, label, col]) => <button key={k || "gen"} onClick={() => setCategory(k)} style={{ border: `1px solid ${category === k ? col : C.line2}`, background: category === k ? col : "#fff", color: category === k ? "#fff" : C.mut3, fontFamily: FONT, fontSize: 12, fontWeight: 800, padding: "6px 12px", borderRadius: 999, cursor: "pointer" }}>{label}</button>)}
              </div>
              {CAT_DATED && (
                <div style={{ marginTop: 10 }}>
                  <div style={{ fontSize: 10, fontWeight: 800, letterSpacing: "0.06em", color: C.mut4, marginBottom: 6 }}>NOMBRE CORTO {category === "lab" ? "DEL LAB" : category === "test" ? "DEL TEST" : "DEL EVENTO"} <span style={{ color: "#C0392B" }}>*</span></div>
                  <input value={title} onChange={(e) => setTitle(e.target.value)} placeholder={category === "lab" ? "Ej: Lab de Desarrollo Web" : category === "test" ? "Ej: Test de Análisis de Datos" : "Ej: Feria de empleo 2026"} style={{ width: "100%", boxSizing: "border-box", fontFamily: FONT, fontSize: 13.5, fontWeight: 800, color: C.ink, background: C.sand, border: `1px solid ${missingTitle ? "#E8A6A0" : title ? C.violet : C.line2}`, borderRadius: 12, padding: "9px 12px" }} />
                  <div style={{ fontSize: 11, fontWeight: 600, color: missingTitle ? "#C0392B" : C.mut, marginTop: 5 }}>{missingTitle ? "⚠️ Escribe un nombre corto: es el que se ve grande en la tarjeta de " + (category === "lab" ? "Labs" : category === "test" ? "Tests" : "Eventos") + "." : "El texto de arriba es la descripción larga (solo se ve en el feed)."}</div>
                </div>
              )}
              <div style={{ marginTop: 10 }}>
                <div style={{ fontSize: 10, fontWeight: 800, letterSpacing: "0.06em", color: C.mut4, marginBottom: 6 }}>LINK (HIPERVÍNCULO) — opcional</div>
                <input value={link} onChange={(e) => setLink(e.target.value)} placeholder="https://… (al tocar la miniatura lleva a este link)" style={{ width: "100%", boxSizing: "border-box", fontFamily: FONT, fontSize: 12, fontWeight: 600, color: C.ink, background: C.sand, border: `1px solid ${C.line2}`, borderRadius: 12, padding: "9px 12px" }} />
                <div style={{ fontSize: 11, fontWeight: 600, color: C.mut, marginTop: 5 }}>{category && category !== "vacante" ? <span style={{ color: C.violetDk, fontWeight: 700 }}>✨ Además aparecerá en la sección de {category === "lab" ? "Labs" : category === "test" ? "Tests" : "Eventos"}.</span> : "La miniatura de la publicación se vuelve clickeable y abre este enlace."}</div>
              </div>
              {CAT_DATED && (
                <div style={{ marginTop: 10 }}>
                  <div style={{ fontSize: 10, fontWeight: 800, letterSpacing: "0.06em", color: C.mut4, marginBottom: 6 }}>⏳ FECHA LÍMITE PARA COMPLETARLO</div>
                  <input type="date" value={deadline} onChange={(e) => setDeadline(e.target.value)} style={{ width: "100%", boxSizing: "border-box", fontFamily: FONT, fontSize: 13, fontWeight: 700, color: C.ink, background: C.sand, border: `1px solid ${deadline ? C.orange : C.line2}`, borderRadius: 12, padding: "9px 12px" }} />
                  <div style={{ fontSize: 11, fontWeight: 600, color: C.mut, marginTop: 5 }}>Se mostrará en el post un <b style={{ color: C.orangeDk }}>contador gamificado</b> con los días que quedan para completarlo. Déjalo vacío si no vence.</div>
                </div>
              )}
              <div style={{ marginTop: 10 }}>
                <div style={{ fontSize: 10, fontWeight: 800, letterSpacing: "0.06em", color: C.mut4, marginBottom: 6 }}>¿QUIÉN LO VE?</div>
                <div style={{ display: "flex", gap: 6, flexWrap: "wrap" }}>
                  {AUDS.map(([k, label]) => <button key={k} onClick={() => setAudience(k)} style={{ border: `1px solid ${audience === k ? C.ink : C.line2}`, background: audience === k ? C.ink : "#fff", color: audience === k ? "#fff" : C.mut3, fontFamily: FONT, fontSize: 12, fontWeight: 800, padding: "6px 12px", borderRadius: 999, cursor: "pointer" }}>{label}</button>)}
                </div>
              </div>
              <div style={{ marginTop: 10 }}>
                <div style={{ fontSize: 10, fontWeight: 800, letterSpacing: "0.06em", color: C.mut4, marginBottom: 6 }}>PUBLICAR COMO (opcional)</div>
                <input value={alias} onChange={(e) => setAlias(e.target.value)} placeholder="Ej: Fundación El Origen (deja vacío para usar tu nombre)" style={{ width: "100%", boxSizing: "border-box", fontFamily: FONT, fontSize: 13, fontWeight: 600, color: C.ink, background: C.sand, border: `1px solid ${C.line2}`, borderRadius: 12, padding: "9px 12px" }} />
                <div style={{ fontSize: 11, fontWeight: 600, color: C.mut, marginTop: 5 }}>Si escribes un alias, tus publicaciones saldrán con ese nombre en vez del tuyo. Se recuerda para las próximas.</div>
              </div>
            </div>
            {image && (
              <div style={{ position: "relative", marginTop: 10 }}>
                <img src={image} alt="" style={{ width: "100%", maxHeight: 220, objectFit: "cover", borderRadius: 12 }} />
                <button onClick={() => setImage("")} style={{ position: "absolute", top: 8, right: 8, border: 0, background: "rgba(23,19,31,0.7)", color: "#fff", borderRadius: 999, width: 26, height: 26, cursor: "pointer" }}>✕</button>
              </div>
            )}
            <div style={{ display: "flex", alignItems: "center", justifyContent: "space-between", marginTop: 10 }}>
              <label style={{ display: "flex", alignItems: "center", gap: 7, border: `1px solid ${C.line2}`, background: "#fff", color: C.mut3, fontFamily: FONT, fontSize: 12, fontWeight: 800, padding: "8px 12px", borderRadius: 999, cursor: "pointer" }}>
                <Icon name="plus" size={14} color={C.mut3} />{imgBusy ? "Subiendo…" : image ? "Cambiar miniatura" : "Miniatura"}
                <input ref={imgRef} type="file" accept="image/*" onChange={onPickImage} style={{ display: "none" }} />
              </label>
              <Btn onClick={publish} style={{ padding: "9px 16px", fontSize: 13, opacity: (busy || missingTitle || missingBody) ? 0.5 : 1, cursor: (missingTitle || missingBody) ? "not-allowed" : "pointer" }}>{busy ? "Publicando…" : "Publicar"}</Btn>
            </div>
            {(missingTitle || missingBody) && <div style={{ fontSize: 11, fontWeight: 700, color: "#C0392B", marginTop: 8, textAlign: "right" }}>{missingTitle && missingBody ? "Faltan el nombre corto y la descripción." : missingTitle ? "Falta el nombre corto." : "Falta la descripción."}</div>}
          </Card>
        ) : (
          <div style={{ background: C.sand, border: `1px solid ${C.line}`, borderRadius: 16, padding: "12px 14px", display: "flex", alignItems: "center", gap: 10 }}>
            <Icon name="chat" color={C.mut} size={18} />
            <span style={{ fontSize: 12, fontWeight: 700, color: C.mut2 }}>Solo los administradores publican. Puedes reaccionar a las publicaciones. 💬</span>
          </div>
        )}

        {livePosts.map((p) => <PostCard key={p.id} post={p} me={me} isAdmin={isAdmin} />)}

        {adminOpen && <AdminModal onClose={() => setAdminOpen(false)} />}

        {livePosts.length === 0 && (<React.Fragment>
        {/* Post insignia */}
        <Card style={{ overflow: "hidden", animation: "olabRise .4s ease both" }}>
          <div style={{ padding: "14px 14px 0", display: "flex", gap: 10, alignItems: "center" }}>
            <Avatar name="Juan Camilo" bg="linear-gradient(135deg,#12C2E9,#6C4CF1)" />
            <div style={{ flex: 1 }}>
              <div style={{ fontSize: 13, fontWeight: 800, color: C.ink }}>Juan Camilo Ospina <span style={{ fontSize: 11, fontWeight: 700, color: C.mut }}>· Nv. 9</span></div>
              <div style={{ fontSize: 11, color: C.mut, fontWeight: 600 }}>Comunidad Logística y Bodega · hace 2 h</div>
            </div>
          </div>
          <div style={{ margin: "12px 14px 0", borderRadius: 18, padding: "18px 14px", background: "linear-gradient(150deg,#FFF6E8,#FFE9F1)", border: "1px solid #FFD9B8", display: "flex", alignItems: "center", gap: 14 }}>
            <div style={{ width: 62, height: 62, borderRadius: 18, background: "linear-gradient(140deg,#FF6600,#FFB347)", display: "flex", alignItems: "center", justifyContent: "center", flexShrink: 0, boxShadow: "0 8px 18px rgba(255,102,0,0.35)", animation: "olabPulse 2.6s ease-in-out infinite" }}>
              <Icon name="trophy" color="#fff" size={30} />
            </div>
            <div>
              <div style={{ fontSize: 10, fontWeight: 800, letterSpacing: "0.1em", color: C.orangeDk }}>INSIGNIA DESBLOQUEADA</div>
              <div style={{ fontSize: 16, fontWeight: 800, color: C.ink, lineHeight: 1.2, marginTop: 3 }}>Operación de Bodega · Nivel 2</div>
              <div style={{ fontSize: 12, color: C.mut2, fontWeight: 600, marginTop: 4 }}>3 Labs + 1 test validados en O-Lab</div>
            </div>
          </div>
          <div style={{ display: "flex", alignItems: "center", gap: 14, padding: "12px 14px 14px" }}>
            <button onClick={() => setLiked(!liked)} style={{ border: 0, background: "transparent", padding: 0, display: "flex", alignItems: "center", gap: 6, cursor: "pointer", fontFamily: FONT }}>
              <Icon name="heart" size={19} color={liked ? C.pink : C.mut3} fill={liked ? C.pink : "none"} />
              <span style={{ fontSize: 12, fontWeight: 700, color: C.mut3 }}>{liked ? 48 : 47}</span>
            </button>
            <button onClick={openRedirect} style={{ border: 0, background: "transparent", padding: 0, display: "flex", alignItems: "center", gap: 6, cursor: "pointer", fontFamily: FONT }}>
              <Icon name="chat" size={19} color={C.mut3} /><span style={{ fontSize: 12, fontWeight: 700, color: C.mut3 }}>9</span>
            </button>
            <span style={{ flex: 1 }} />
            <button onClick={() => setLiked(true)} style={{ border: `1px solid ${C.orangeSoftBd}`, background: C.orangeSoft, color: C.orangeDk, fontFamily: FONT, fontSize: 12, fontWeight: 800, padding: "7px 12px", borderRadius: 999, cursor: "pointer" }}>Felicitar +5 XP</button>
          </div>
        </Card>

        {/* Post empresa (vacante-match) */}
        <Card style={{ overflow: "hidden" }}>
          <div style={{ padding: 14, display: "flex", gap: 10, alignItems: "center" }}>
            <div style={{ width: 38, height: 38, borderRadius: 12, background: C.ink, display: "flex", alignItems: "center", justifyContent: "center", fontSize: 11, fontWeight: 800, color: C.lime }}>AL</div>
            <div style={{ flex: 1 }}>
              <div style={{ fontSize: 13, fontWeight: 800, color: C.ink }}>Andina Logistics <span style={{ fontSize: 10, fontWeight: 800, color: C.green, background: C.greenBg, padding: "2px 6px", borderRadius: 6, marginLeft: 2 }}>EMPRESA</span></div>
              <div style={{ fontSize: 11, color: C.mut, fontWeight: 600 }}>Está buscando talento · hace 5 h</div>
            </div>
          </div>
          <div style={{ padding: "0 14px 14px" }}>
            <div style={{ borderRadius: 18, border: `1px solid ${C.line}`, overflow: "hidden" }}>
              <div style={{ padding: 14, background: "linear-gradient(150deg,#F4F0FF,#EAF9FE)" }}>
                <div style={{ display: "flex", justifyContent: "space-between", gap: 10, alignItems: "flex-start" }}>
                  <div>
                    <div style={{ fontSize: 16, fontWeight: 800, color: C.ink, lineHeight: 1.2 }}>Operario de bodega — 3 vacantes</div>
                    <div style={{ fontSize: 12, fontWeight: 600, color: C.mut2, marginTop: 4 }}>Medellín · Turnos rotativos · $1.6M – $1.9M</div>
                  </div>
                  <MatchRing pct="92%" />
                </div>
                <div style={{ display: "flex", flexWrap: "wrap", gap: 6, marginTop: 12 }}>
                  <Pill color={C.green} bg={C.greenBg} bd={C.greenBd}>✓ Inventarios</Pill>
                  <Pill color={C.green} bg={C.greenBg} bd={C.greenBd}>✓ Seguridad industrial</Pill>
                  <span style={{ fontSize: 11, fontWeight: 700, color: C.mut, background: C.sand, border: "1px dashed #D6CEC4", padding: "5px 9px", borderRadius: 999 }}>Montacargas — falta test</span>
                </div>
              </div>
              <div style={{ display: "flex", gap: 8, padding: "12px 14px", background: "#fff", borderTop: `1px solid ${C.line}` }}>
                <Btn onClick={openRedirect} style={{ flex: 1, padding: 11 }}>Ver vacante</Btn>
                <Btn kind="ghost" onClick={openRedirect} style={{ padding: "11px 13px" }}>Referir</Btn>
              </div>
            </div>
          </div>
        </Card>
        </React.Fragment>)}
      </div>
    );
  }

  function linkifyText(text) {
    return String(text || "").split(/(https?:\/\/[^\s]+)/g).map((p, i) => /^https?:\/\//i.test(p)
      ? <a key={i} href={p} target="_blank" rel="noopener noreferrer" style={{ color: C.orange, fontWeight: 700, wordBreak: "break-all" }}>{p}</a>
      : <React.Fragment key={i}>{p}</React.Fragment>);
  }
  function firstUrl(text) { const m = String(text || "").match(/https?:\/\/[^\s]+/); return m ? m[0] : ""; }

  function LinkPreview({ url }) {
    const [prev, setPrev] = useState(null);
    useEffect(() => { let ok = true; B().linkPreview(url).then((p) => { if (ok) setPrev(p); }); return () => { ok = false; }; }, [url]);
    if (!prev || (!prev.image && !prev.title)) return null;
    return (
      <a href={url} target="_blank" rel="noopener noreferrer" style={{ display: "block", margin: "0 14px 12px", border: `1px solid ${C.line}`, borderRadius: 16, overflow: "hidden", textDecoration: "none", background: "#fff" }}>
        {prev.image && <div style={{ height: 168, background: "#F0EAE2 url(" + prev.image + ") center/cover no-repeat" }} />}
        <div style={{ padding: 12 }}>
          <div style={{ fontSize: 10, fontWeight: 800, color: C.mut4, letterSpacing: "0.04em", textTransform: "uppercase" }}>{prev.domain || "enlace"}</div>
          {prev.title && <div style={{ fontSize: 14, fontWeight: 800, color: C.ink, marginTop: 3, lineHeight: 1.3 }}>{prev.title}</div>}
          {prev.description && <div style={{ fontSize: 12, fontWeight: 500, color: C.mut2, marginTop: 4, lineHeight: 1.4 }}>{prev.description}</div>}
        </div>
      </a>
    );
  }

  // Contador gamificado grande para posts con fecha límite (labs/tests/eventos).
  // Muestra días/horas/min restantes y cambia de color según urgencia.
  function CountdownBanner({ deadline, category }) {
    const [, tick] = useState(0);
    useEffect(() => { const t = setInterval(() => tick((x) => x + 1), 60000); return () => clearInterval(t); }, []);
    const end = new Date(deadline + "T23:59:59");
    if (isNaN(end.getTime())) return null;
    const diff = end.getTime() - Date.now();
    const label = end.toLocaleDateString("es-CO", { weekday: "long", day: "2-digit", month: "long", year: "numeric" });
    const expired = diff <= 0;
    const days = Math.floor(diff / 86400000);
    const hours = Math.floor((diff % 86400000) / 3600000);
    const mins = Math.floor((diff % 3600000) / 60000);
    // Estados de urgencia → paleta.
    let g1 = "#6C4CF1", g2 = "#12C2E9", accent = "#fff", pulse = false, tone = "Tienes tiempo";
    if (expired) { g1 = "#3A3442"; g2 = "#17131F"; tone = "Acceso cerrado"; }
    else if (days <= 1) { g1 = "#FF3D7F"; g2 = "#FF6600"; pulse = true; tone = "¡Último momento!"; }
    else if (days <= 3) { g1 = "#FF6600"; g2 = "#FF3D7F"; tone = "¡Corre, se cierra pronto!"; }
    else if (days <= 7) { g1 = "#FF8A00"; g2 = "#FFB020"; tone = "Esta semana vence"; }
    const catWord = category === "lab" ? "este Lab" : category === "test" ? "este Test" : category === "evento" ? "este Evento" : "esto";
    const Unit = ({ n, u }) => (
      <div style={{ textAlign: "center", minWidth: 46 }}>
        <div style={{ fontSize: 30, fontWeight: 900, color: "#fff", lineHeight: 1, fontVariantNumeric: "tabular-nums", textShadow: "0 2px 8px rgba(0,0,0,0.25)" }}>{String(Math.max(0, n)).padStart(2, "0")}</div>
        <div style={{ fontSize: 9, fontWeight: 800, color: "rgba(255,255,255,0.82)", letterSpacing: "0.1em", marginTop: 4 }}>{u}</div>
      </div>
    );
    const Sep = () => <div style={{ fontSize: 24, fontWeight: 900, color: "rgba(255,255,255,0.5)", lineHeight: 1, paddingBottom: 12 }}>:</div>;
    return (
      <div style={{ margin: "0 14px 12px", borderRadius: 16, padding: "13px 15px", background: `linear-gradient(120deg, ${g1}, ${g2})`, boxShadow: "0 8px 22px -10px " + g1, position: "relative", overflow: "hidden", animation: pulse ? "olabPulse 1.6s ease-in-out infinite" : "none" }}>
        <div style={{ position: "absolute", right: -18, top: -18, width: 90, height: 90, borderRadius: 999, background: "rgba(255,255,255,0.10)" }} />
        <div style={{ display: "flex", alignItems: "center", justifyContent: "space-between", gap: 12, position: "relative" }}>
          <div style={{ minWidth: 0 }}>
            <div style={{ display: "flex", alignItems: "center", gap: 6 }}>
              <span style={{ fontSize: 15 }}>{expired ? "🔒" : "⏳"}</span>
              <span style={{ fontSize: 11, fontWeight: 900, color: "#fff", letterSpacing: "0.04em", textTransform: "uppercase" }}>{tone}</span>
            </div>
            <div style={{ fontSize: 12, fontWeight: 700, color: "rgba(255,255,255,0.92)", marginTop: 4 }}>
              {expired ? "Cerró el " + label : "Completa " + catWord + " antes del"}
            </div>
            {!expired && <div style={{ fontSize: 12.5, fontWeight: 900, color: "#fff", marginTop: 1, textTransform: "capitalize" }}>{label}</div>}
          </div>
          {expired ? (
            <div style={{ fontSize: 13, fontWeight: 900, color: "#fff", background: "rgba(0,0,0,0.22)", padding: "8px 12px", borderRadius: 12, whiteSpace: "nowrap" }}>Vencido</div>
          ) : (
            <div style={{ display: "flex", alignItems: "flex-start", gap: 8, flexShrink: 0 }}>
              {days > 0 && <React.Fragment><Unit n={days} u="DÍAS" /><Sep /></React.Fragment>}
              <Unit n={hours} u="HORAS" /><Sep /><Unit n={mins} u="MIN" />
            </div>
          )}
        </div>
      </div>
    );
  }

  function PostCard({ post, me, isAdmin }) {
    const [rx, setRx] = useState({ count: 0, mine: false });
    const [editing, setEditing] = useState(false);
    const [draft, setDraft] = useState({ text: "", title: "", link: "" });
    const [busyE, setBusyE] = useState(false);
    useEffect(() => B().subscribeReactions(post.id, setRx), [post.id]);
    const when = post.createdAt && post.createdAt.toDate ? post.createdAt.toDate() : null;
    const ago = when ? relTime(when) : "ahora";
    const url = firstUrl(post.text);
    const isContent = post.category === "lab" || post.category === "test" || post.category === "evento";
    function startEdit() {
      let t = post.title || "";
      // Posts de Lab/Test/Evento publicados antes del campo "título" no lo traen:
      // sugerimos uno con la primera frase del texto para no bloquear el guardado.
      if (!t && isContent) {
        const body = String(post.text || "").trim().replace(/^[^\wÁÉÍÓÚÑáéíóúñ¿¡"']+/, "");
        const firstLine = body.split("\n")[0] || body;
        const m = firstLine.match(/^.{0,58}?[.?!]/);
        t = (m ? m[0] : firstLine.slice(0, 58)).replace(/[.?!]+$/, "").trim();
      }
      setDraft({ text: post.text || "", title: t, link: post.link || "" });
      setEditing(true);
    }
    const editMissingTitle = isContent && !String(draft.title || "").trim();
    const editMissingBody = isContent && !String(draft.text || "").trim();
    async function saveEdit() { if (editMissingTitle || editMissingBody) return; setBusyE(true); try { await B().editPost(post.id, { text: draft.text, title: draft.title, link: draft.link }); setEditing(false); } catch (e) { console.warn(e); } finally { setBusyE(false); } }
    const einp = { width: "100%", boxSizing: "border-box", fontFamily: FONT, fontSize: 13, fontWeight: 600, color: C.ink, background: C.sand, border: `1px solid ${C.line2}`, borderRadius: 12, padding: "10px 12px" };
    return (
      <Card style={{ overflow: "hidden", animation: "olabRise .4s ease both" }}>
        <div style={{ padding: 14, display: "flex", gap: 10, alignItems: "center" }}>
          {post.authorPhoto ? <img src={post.authorPhoto} alt="" style={{ width: 38, height: 38, borderRadius: 999, objectFit: "cover" }} /> : <Avatar name={post.authorName} bg="linear-gradient(135deg,#6C4CF1,#12C2E9)" />}
          <div style={{ flex: 1, minWidth: 0 }}>
            <div style={{ fontSize: 13, fontWeight: 800, color: C.ink }}>{post.authorName} {post.byApplicant ? <Pill color="#0B7A3B" bg={C.greenBg} style={{ fontSize: 9, padding: "2px 6px" }}>LOGRO</Pill> : (!post.authorAlias && <Pill color={C.violetDk} bg={C.violetSoft} style={{ fontSize: 9, padding: "2px 6px" }}>ADMIN</Pill>)}{post.category && (() => { const CB = { lab: ["LAB", C.violetDk, C.violetSoft], test: ["TEST", C.orangeDk, C.orangeSoft], evento: ["EVENTO", C.green, C.greenBg], vacante: ["VACANTE", C.cyanDk, C.cyanSoft] }[post.category]; return CB ? <Pill color={CB[1]} bg={CB[2]} style={{ fontSize: 9, padding: "2px 6px", marginLeft: 4 }}>{CB[0]}</Pill> : null; })()}</div>
            <div style={{ fontSize: 11, color: C.mut, fontWeight: 600 }}>O-Lab · {ago}{post.editedAt ? " · editado" : ""}</div>
          </div>
          {isAdmin && !editing && <button onClick={startEdit} title="Editar" style={{ border: 0, background: "transparent", cursor: "pointer", color: C.mut3, marginRight: 2 }}><Icon name="test" size={16} /></button>}
          {isAdmin && <button onClick={() => B().deletePost(post.id)} title="Eliminar" style={{ border: 0, background: "transparent", cursor: "pointer", color: C.mut4 }}><Icon name="close" size={16} /></button>}
        </div>
        {editing ? (
          <div style={{ padding: "0 14px 12px", display: "flex", flexDirection: "column", gap: 8 }}>
            {isContent && <input value={draft.title} onChange={(e) => setDraft((p) => ({ ...p, title: e.target.value }))} placeholder={"Nombre corto del " + (post.category === "lab" ? "Lab" : post.category === "test" ? "Test" : "Evento") + " *"} style={{ ...einp, fontWeight: 800, border: `1px solid ${editMissingTitle ? "#E8A6A0" : C.line2}` }} />}
            <textarea value={draft.text} onChange={(e) => setDraft((p) => ({ ...p, text: e.target.value }))} rows={4} placeholder={isContent ? "Descripción larga (se ve en el feed)" : "Descripción"} style={{ ...einp, resize: "vertical", border: `1px solid ${editMissingBody ? "#E8A6A0" : C.line2}` }} />
            <input value={draft.link} onChange={(e) => setDraft((p) => ({ ...p, link: e.target.value }))} placeholder="Link (opcional, al tocar la imagen lleva ahí)" style={einp} />
            {(editMissingTitle || editMissingBody) && <div style={{ fontSize: 11, fontWeight: 700, color: "#C0392B" }}>{editMissingTitle && editMissingBody ? "Faltan el nombre corto y la descripción." : editMissingTitle ? "Falta el nombre corto." : "Falta la descripción."}</div>}
            <div style={{ display: "flex", gap: 8, justifyContent: "flex-end" }}>
              <Btn kind="ghost" onClick={() => setEditing(false)} style={{ padding: "8px 14px", fontSize: 13 }}>Cancelar</Btn>
              <Btn kind="dark" onClick={saveEdit} style={{ padding: "8px 16px", fontSize: 13, opacity: (busyE || editMissingTitle || editMissingBody) ? 0.5 : 1, cursor: (editMissingTitle || editMissingBody) ? "not-allowed" : "pointer" }}>{busyE ? "Guardando…" : "Guardar"}</Btn>
            </div>
          </div>
        ) : (
        <div style={{ padding: "0 14px 12px" }}>
          {post.title && <div style={{ fontSize: 16, fontWeight: 900, color: C.ink, lineHeight: 1.3, marginBottom: 5 }}>{post.title}</div>}
          <div style={{ fontSize: 14, fontWeight: 500, color: "#3D3548", lineHeight: 1.55, whiteSpace: "pre-wrap" }}>{linkifyText(post.text)}</div>
        </div>
        )}
        {post.image
          ? <a href={post.link || url || post.image} target="_blank" rel="noopener noreferrer" style={{ display: "block", margin: "0 14px 12px" }}><img src={post.image} alt="" style={{ width: "100%", height: "auto", display: "block", objectFit: "contain", borderRadius: 14, background: C.sand }} /></a>
          : url ? <LinkPreview url={url} /> : null}
        {post.deadline && <CountdownBanner deadline={post.deadline} category={post.category} />}
        <div style={{ display: "flex", alignItems: "center", gap: 14, padding: "10px 14px 14px", borderTop: `1px solid ${C.line}` }}>
          <button onClick={() => B().reactPost(post.id, !rx.mine)} style={{ border: 0, background: "transparent", padding: 0, display: "flex", alignItems: "center", gap: 6, cursor: "pointer", fontFamily: FONT }}>
            <Icon name="heart" size={19} color={rx.mine ? C.pink : C.mut3} fill={rx.mine ? C.pink : "none"} />
            <span style={{ fontSize: 12, fontWeight: 700, color: C.mut3 }}>{rx.count}</span>
          </button>
          <span title={(rx.names || []).join(", ")} style={{ fontSize: 12, fontWeight: 700, color: C.mut, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>{likesLabel(rx.names, rx.count)}</span>
        </div>
      </Card>
    );
  }

  function relTime(d) {
    const s = Math.floor((Date.now() - d.getTime()) / 1000);
    if (s < 60) return "ahora"; 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";
  }

  function topCounts(arr, n) {
    const m = {};
    arr.forEach((v) => { const k = String(v || "").trim(); if (k) m[k] = (m[k] || 0) + 1; });
    return Object.keys(m).map((k) => ({ label: k, count: m[k] })).sort((a, b) => b.count - a.count).slice(0, n || 6);
  }
  function BarList({ title, rows, color }) {
    const max = Math.max(1, ...rows.map((r) => r.count));
    return (
      <Card style={{ padding: 15 }}>
        <div style={{ fontSize: 13, fontWeight: 800, color: C.ink, marginBottom: 12 }}>{title}</div>
        {rows.length === 0 ? <div style={{ fontSize: 12, fontWeight: 600, color: C.mut }}>Sin datos aún.</div> : (
          <div style={{ display: "flex", flexDirection: "column", gap: 10 }}>
            {rows.map((r, i) => (
              <div key={i}>
                <div style={{ display: "flex", justifyContent: "space-between", marginBottom: 4 }}><span style={{ fontSize: 12, fontWeight: 700, color: C.mut3 }}>{r.label}</span><span style={{ fontSize: 12, fontWeight: 800, color: color || C.orange }}>{r.count}</span></div>
                <Bar pct={Math.round((r.count / max) * 100) + "%"} color={color || C.orange} h={7} />
              </div>
            ))}
          </div>
        )}
      </Card>
    );
  }
  function StatTile({ label, value, color }) {
    return (
      <Card style={{ padding: "14px 12px", textAlign: "center", borderRadius: 16 }}>
        <div style={{ fontSize: 22, fontWeight: 800, color: color }}>{value}</div>
        <div style={{ fontSize: 9, fontWeight: 800, color: C.mut, letterSpacing: "0.04em", marginTop: 3 }}>{label}</div>
      </Card>
    );
  }

  // Panel de administración del portal (superadmin): Insights · Publicar · Admins.
  function AdminModal({ onClose }) {
    const [tab, setTab] = useState("solicitudes");
    const tabs = [["solicitudes", "Solicitudes", "user"], ["contenido", "Contenido", "flask"], ["insights", "Insights", "test"], ["publicar", "Publicar", "chat"], ["admins", "Admins", "star"]];
    return (
      <div onClick={onClose} style={{ position: "fixed", inset: 0, zIndex: 65, background: "rgba(23,19,31,0.62)", display: "flex", alignItems: "flex-start", justifyContent: "center", padding: 20, overflowY: "auto" }}>
        <div onClick={(e) => e.stopPropagation()} style={{ width: "100%", maxWidth: 760, background: C.cream, borderRadius: 24, padding: 22, animation: "olabRise .25s ease both", marginBottom: 40 }}>
          <div style={{ display: "flex", justifyContent: "space-between", alignItems: "center" }}>
            <div style={{ display: "flex", alignItems: "center", gap: 8 }}><Icon name="star" color={C.violet} size={20} /><div style={{ fontSize: 20, fontWeight: 800, color: C.ink }}>Panel de administración</div></div>
            <button onClick={onClose} style={{ border: `1px solid ${C.line2}`, background: "#fff", borderRadius: 999, width: 32, height: 32, cursor: "pointer", color: C.mut3 }}><Icon name="close" size={16} /></button>
          </div>
          <div style={{ display: "flex", gap: 6, marginTop: 14 }}>
            {tabs.map(([k, label, icon]) => {
              const on = tab === k;
              return <button key={k} onClick={() => setTab(k)} style={{ border: `1px solid ${on ? C.ink : C.line2}`, background: on ? C.ink : "#fff", color: on ? "#fff" : C.mut3, fontFamily: FONT, fontSize: 12, fontWeight: 800, padding: "8px 14px", borderRadius: 999, cursor: "pointer", display: "flex", alignItems: "center", gap: 6 }}><Icon name={icon} size={14} color={on ? "#fff" : C.mut3} />{label}</button>;
            })}
          </div>
          <div style={{ marginTop: 16 }}>
            {tab === "solicitudes" && <AdminSolicitudes />}
            {tab === "contenido" && <AdminContenido />}
            {tab === "insights" && <AdminInsights />}
            {tab === "publicar" && <AdminPublicar />}
            {tab === "admins" && <AdminAdmins />}
          </div>
        </div>
      </div>
    );
  }

  function AdminContenido() {
    const [type, setType] = useState("lab");
    const [form, setForm] = useState({});
    const [image, setImage] = useState("");
    const [busy, setBusy] = useState(false);
    const [imgBusy, setImgBusy] = useState(false);
    const [items, setItems] = useState(null);
    useEffect(() => B().subscribeContent((l) => setItems(l || [])), []);
    const set = (k) => (e) => setForm({ ...form, [k]: e.target.value });
    async function onImg(e) { const f = e.target.files && e.target.files[0]; e.target.value = ""; if (!f) return; setImgBusy(true); try { const url = await B().uploadFeedImage(f); setImage(url); } catch (err) { console.warn(err); } finally { setImgBusy(false); } }
    async function publish() { if (!form.title || (type !== "ad" && !form.link)) return; setBusy(true); try { await B().publishContent({ type, title: form.title, description: form.description, link: form.link, xp: form.xp, area: form.area, image, deadline: form.deadline, competencias: form.competencias }); setForm({}); setImage(""); } catch (e) { console.warn(e); } finally { setBusy(false); } }
    const live = items || [];
    const TL = { lab: ["Lab", C.violet], test: ["Test", C.orange], evento: ["Evento", C.green], ad: ["ADs", C.pink] };
    const inp = { fontFamily: FONT, fontSize: 13, fontWeight: 600, color: C.ink, background: C.sand, border: `1px solid ${C.line2}`, borderRadius: 12, padding: "11px 12px", width: "100%" };
    const cap = { fontSize: 10, fontWeight: 800, letterSpacing: "0.08em", color: C.mut4, marginBottom: 5, display: "block" };
    return (
      <div style={{ display: "flex", flexDirection: "column", gap: 14 }}>
        <Card style={{ padding: 16 }}>
          <div style={{ fontSize: 14, fontWeight: 800, color: C.ink, marginBottom: 10 }}>Publicar Lab, Test, Evento o Publicidad</div>
          <div style={{ display: "flex", gap: 8, marginBottom: 12, flexWrap: "wrap" }}>
            {["lab", "test", "evento", "ad"].map((t) => <button key={t} onClick={() => setType(t)} style={{ border: `1px solid ${type === t ? TL[t][1] : C.line2}`, background: type === t ? TL[t][1] : "#fff", color: type === t ? "#fff" : C.mut3, fontFamily: FONT, fontSize: 12, fontWeight: 800, padding: "8px 14px", borderRadius: 999, cursor: "pointer" }}>{TL[t][0]}</button>)}
          </div>
          {type === "ad" && <div style={{ fontSize: 12, fontWeight: 600, color: C.mut, marginBottom: 10, background: C.orangeSoft, border: `1px solid ${C.orangeSoftBd}`, borderRadius: 10, padding: "8px 11px" }}>📣 Las publicidades aparecen como banners a los lados del Feed, con la etiqueta <b>ADs</b>. Ideal para destacar nuevos labs, tests, eventos o vacantes.</div>}
          <div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: 10 }}>
            <div style={{ gridColumn: "1 / -1" }}><label style={cap}>TÍTULO</label><input value={form.title || ""} onChange={set("title")} placeholder={type === "ad" ? "¡Nuevo Lab de IA disponible!" : type === "evento" ? "Feria de empleo Medellín" : "¿Cómo auditar una bodega?"} style={inp} /></div>
            <div style={{ gridColumn: "1 / -1" }}><label style={cap}>{type === "ad" ? "LINK DE DESTINO (opcional)" : "LINK DE ACCESO"}</label><input value={form.link || ""} onChange={set("link")} placeholder={type === "ad" ? "https://… (a dónde lleva el banner)" : "https://u.o-lab.app/_hCR-X"} style={inp} /></div>
            <div style={{ gridColumn: "1 / -1" }}><label style={cap}>DESCRIPCIÓN</label><textarea value={form.description || ""} onChange={set("description")} rows={2} placeholder="Breve descripción…" style={{ ...inp, resize: "vertical" }} /></div>
            <div><label style={cap}>{type === "evento" ? "FECHA / LUGAR" : "ÁREA"}</label><input value={form.area || ""} onChange={set("area")} placeholder={type === "evento" ? "18 ago · Medellín" : "Logística"} style={inp} /></div>
            <div><label style={cap}>XP (opcional)</label><input value={form.xp || ""} onChange={set("xp")} placeholder="+450" style={inp} /></div>
            <div><label style={cap}>DEADLINE DE ACCESO (opcional)</label><input type="date" value={form.deadline || ""} onChange={set("deadline")} style={inp} /></div>
            {type !== "evento" && type !== "ad" && <div><label style={cap}>COMPETENCIAS A DESARROLLAR</label><input value={form.competencias || ""} onChange={set("competencias")} placeholder="Ej: Operación de bodega, Seguridad industrial" style={inp} /></div>}
          </div>
          {type !== "evento" && type !== "ad" && <div style={{ fontSize: 11, fontWeight: 600, color: C.mut, marginTop: -2 }}>Separa las competencias con comas. Cuando un aplicante complete este {type === "lab" ? "Lab" : "Test"}, se sumarán a su perfil.</div>}
          {image && <div style={{ position: "relative", marginTop: 10 }}><img src={image} alt="" style={{ width: "100%", maxHeight: 180, objectFit: "cover", borderRadius: 12 }} /><button onClick={() => setImage("")} style={{ position: "absolute", top: 8, right: 8, border: 0, background: "rgba(23,19,31,0.7)", color: "#fff", borderRadius: 999, width: 26, height: 26, cursor: "pointer" }}>✕</button></div>}
          <div style={{ display: "flex", alignItems: "center", justifyContent: "space-between", marginTop: 12 }}>
            <label style={{ display: "flex", alignItems: "center", gap: 7, border: `1px solid ${C.line2}`, background: "#fff", color: C.mut3, fontFamily: FONT, fontSize: 12, fontWeight: 800, padding: "8px 12px", borderRadius: 999, cursor: "pointer" }}><Icon name="plus" size={14} color={C.mut3} />{imgBusy ? "Subiendo…" : image ? "Cambiar miniatura" : "Miniatura"}<input type="file" accept="image/*" onChange={onImg} style={{ display: "none" }} /></label>
            <Btn onClick={publish} style={{ padding: "9px 16px", fontSize: 13, opacity: busy ? 0.6 : 1 }}>{busy ? "Publicando…" : "Publicar"}</Btn>
          </div>
        </Card>
        <div style={{ fontSize: 12, fontWeight: 800, color: C.mut4, letterSpacing: "0.06em" }}>CONTENIDO PUBLICADO ({live.length})</div>
        {items === null && <div style={{ fontSize: 12, color: C.mut, fontWeight: 600 }}>Cargando…</div>}
        {live.map((it) => (
          <Card key={it.id} style={{ padding: 12, display: "flex", gap: 12, alignItems: "center" }}>
            <div style={{ width: 54, height: 54, borderRadius: 12, flexShrink: 0, background: it.image ? "#F0EAE2 url(" + it.image + ") center/cover" : (TL[it.type] ? TL[it.type][1] : C.mut), display: "flex", alignItems: "center", justifyContent: "center" }}>{!it.image && <Icon name={it.type === "evento" ? "calendar" : it.type === "test" ? "test" : it.type === "ad" ? "star" : "flask"} color="#fff" size={22} />}</div>
            <div style={{ flex: 1, minWidth: 0 }}>
              <div style={{ display: "flex", alignItems: "center", gap: 6 }}><Pill color="#fff" bg={TL[it.type] ? TL[it.type][1] : C.mut} style={{ fontSize: 9 }}>{TL[it.type] ? TL[it.type][0] : it.type}</Pill><span style={{ fontSize: 13, fontWeight: 800, color: C.ink, overflow: "hidden", textOverflow: "ellipsis", whiteSpace: "nowrap" }}>{it.title}</span></div>
              <a href={it.link} target="_blank" rel="noopener noreferrer" style={{ fontSize: 11, fontWeight: 700, color: C.orange, wordBreak: "break-all" }}>{it.link}</a>
            </div>
            <button onClick={() => B().deleteContent(it.id)} style={{ border: `1px solid ${C.line2}`, background: "#fff", color: C.mut3, fontFamily: FONT, fontSize: 11, fontWeight: 800, padding: "6px 10px", borderRadius: 999, cursor: "pointer" }}>Eliminar</button>
          </Card>
        ))}
      </div>
    );
  }

  // Editor de perfil de CUALQUIER aplicante, para superadmins ("Editar como admin").
  function AdminEditProfile({ uid, name, email, onClose }) {
    const [prof, setProf] = useState(null);
    useEffect(() => {
      let alive = true;
      B().adminGetProfile(uid).then((p) => { if (alive) setProf(p || { name: name || "", email: email || "" }); });
      return () => { alive = false; };
    }, [uid]);
    const saveFn = (patch) => B().adminSaveProfile(uid, patch);
    return (
      <div onClick={onClose} style={{ position: "fixed", inset: 0, zIndex: 80, background: "rgba(23,19,31,0.62)", display: "flex", alignItems: "flex-start", justifyContent: "center", padding: 20, overflowY: "auto" }}>
        <div onClick={(e) => e.stopPropagation()} style={{ width: "100%", maxWidth: 760, marginBottom: 40 }}>
          <div style={{ display: "flex", justifyContent: "space-between", alignItems: "center", marginBottom: 12, color: "#fff" }}>
            <div><div style={{ fontSize: 18, fontWeight: 900 }}>Editar hoja de vida (admin)</div><div style={{ fontSize: 12, fontWeight: 700, opacity: 0.85 }}>{email}</div></div>
            <button onClick={onClose} style={{ border: "1px solid rgba(255,255,255,0.3)", background: "rgba(255,255,255,0.1)", borderRadius: 999, width: 34, height: 34, cursor: "pointer", color: "#fff" }}><Icon name="close" size={16} /></button>
          </div>
          {prof === null
            ? <div style={{ background: "#fff", borderRadius: 16, padding: 30, textAlign: "center", color: C.mut, fontWeight: 700 }}>Cargando perfil…</div>
            : <CvEditor me={prof} update={saveFn} isAdmin={true} />}
          <div style={{ display: "flex", justifyContent: "flex-end", marginTop: 12 }}>
            <Btn kind="dark" onClick={onClose} style={{ padding: "10px 20px" }}>Cerrar</Btn>
          </div>
        </div>
      </div>
    );
  }

  function AdminSolicitudes() {
    const [reqs, setReqs] = useState(null);
    const [busy, setBusy] = useState("");
    const [filter, setFilter] = useState("pending");
    const [roleF, setRoleF] = useState("all");
    const [q, setQ] = useState("");
    const [openProg, setOpenProg] = useState("");
    const [pwOpen, setPwOpen] = useState("");
    const [pw, setPw] = useState("");
    const [pwMsg, setPwMsg] = useState(null);
    const [expOpen, setExpOpen] = useState("");
    const [expDate, setExpDate] = useState("");
    const [editUser, setEditUser] = useState(null);
    async function saveExpiry(uid, date) { setBusy(uid + "-exp"); try { await B().setAccessExpiry(uid, date); setReqs((rs) => (rs || []).map((r) => r.uid === uid ? { ...r, accessExpiry: date } : r)); setExpOpen(""); } catch (e) { console.warn(e); } finally { setBusy(""); } }
    async function savePw(uid) {
      if ((pw || "").length < 6) { setPwMsg({ uid, kind: "err", text: "Mínimo 6 caracteres." }); return; }
      setBusy(uid + "-pw");
      try {
        const r = await B().setUserPassword(uid, pw);
        if (r && r.ok) { setPwMsg({ uid, kind: "ok", text: "Contraseña actualizada ✓" }); setPw(""); setTimeout(() => { setPwOpen(""); setPwMsg(null); }, 1600); }
        else setPwMsg({ uid, kind: "err", text: (r && r.error) || "No se pudo." });
      } catch (e) { setPwMsg({ uid, kind: "err", text: "Error de conexión." }); } finally { setBusy(""); }
    }
    function load() { setReqs(null); B().listRequests().then((r) => setReqs(r || [])); }
    useEffect(() => { load(); }, []);
    async function decide(uid, status) { setBusy(uid); try { await B().decideRequest(uid, status); setReqs((rs) => (rs || []).map((r) => r.uid === uid ? { ...r, status } : r)); } catch (e) { console.warn(e); } finally { setBusy(""); } }
    const row = (label, val) => val ? <div style={{ fontSize: 12, fontWeight: 600, color: C.mut2 }}><span style={{ color: C.mut4, fontWeight: 800 }}>{label}:</span> {Array.isArray(val) ? val.join(", ") : String(val)}</div> : null;
    if (reqs === null) return <div style={{ padding: 30, textAlign: "center", color: C.mut, fontWeight: 700 }}>Cargando solicitudes…</div>;
    const counts = { pending: 0, approved: 0, rejected: 0 };
    reqs.forEach((r) => { counts[r.status || "pending"] = (counts[r.status || "pending"] || 0) + 1; });
    const roleOf = (r) => (r.role === "empresa" ? "empresa" : "aplicante");
    const roleCounts = { aplicante: 0, empresa: 0 };
    reqs.forEach((r) => { roleCounts[roleOf(r)] += 1; });
    const nq = q.trim().toLowerCase();
    const matchQ = (r) => !nq || [r.name, r.email, r.phone, r.city, r.numDoc, r.institucion, r.contactName, r.razonSocial, r.nit].some((v) => String(v || "").toLowerCase().includes(nq));
    const list = reqs
      .filter((r) => nq ? true : (filter === "all" ? true : (r.status || "pending") === filter))
      .filter((r) => nq ? true : (roleF === "all" ? true : roleOf(r) === roleF))
      .filter(matchQ)
      .sort((a, b) => (b.createdAt && b.createdAt.seconds || 0) - (a.createdAt && a.createdAt.seconds || 0));
    const stCol = { pending: [C.orangeDk, C.orangeSoft], approved: [C.green, C.greenBg], rejected: ["#C0392B", "#FDECEA"] };
    const chips = [["pending", "Pendientes (" + counts.pending + ")"], ["approved", "Aprobadas (" + counts.approved + ")"], ["rejected", "Rechazadas (" + counts.rejected + ")"], ["all", "Todas (" + reqs.length + ")"]];
    const roleChips = [["all", "Todos", C.ink], ["aplicante", "👤 Aplicantes (" + roleCounts.aplicante + ")", C.orange], ["empresa", "💼 Empresas (" + roleCounts.empresa + ")", C.violet]];
    return (
      <div style={{ display: "flex", flexDirection: "column", gap: 12 }}>
        <div style={{ display: "flex", alignItems: "center", justifyContent: "space-between", gap: 8, flexWrap: "wrap" }}>
          <div style={{ display: "flex", gap: 6, flexWrap: "wrap" }}>
            {chips.map(([k, label]) => <button key={k} onClick={() => setFilter(k)} style={{ border: `1px solid ${filter === k ? C.ink : C.line2}`, background: filter === k ? C.ink : "#fff", color: filter === k ? "#fff" : C.mut3, fontFamily: FONT, fontSize: 12, fontWeight: 800, padding: "7px 12px", borderRadius: 999, cursor: "pointer" }}>{label}</button>)}
          </div>
          <button onClick={load} style={{ border: `1px solid ${C.line2}`, background: "#fff", color: C.mut3, fontFamily: FONT, fontSize: 12, fontWeight: 800, padding: "7px 11px", borderRadius: 999, cursor: "pointer" }}>↻ Actualizar</button>
        </div>
        <div style={{ position: "relative" }}>
          <span style={{ position: "absolute", left: 14, top: "50%", transform: "translateY(-50%)", fontSize: 15, color: C.mut3, pointerEvents: "none" }}>🔍</span>
          <input value={q} onChange={(e) => setQ(e.target.value)} placeholder="Buscar por nombre, correo, celular, ciudad o documento…" style={{ width: "100%", boxSizing: "border-box", fontFamily: FONT, fontSize: 13.5, fontWeight: 600, color: C.ink, background: "#fff", border: `1.5px solid ${q ? C.orange : C.line2}`, borderRadius: 999, padding: "11px 38px 11px 40px" }} />
          {q && <button onClick={() => setQ("")} title="Limpiar" style={{ position: "absolute", right: 10, top: "50%", transform: "translateY(-50%)", border: 0, background: C.sand, color: C.mut3, borderRadius: 999, width: 24, height: 24, cursor: "pointer", fontSize: 12, fontWeight: 800 }}>✕</button>}
        </div>
        {nq && <div style={{ fontSize: 11.5, fontWeight: 700, color: C.mut2, marginTop: -4 }}>{list.length} resultado{list.length === 1 ? "" : "s"} para "{q.trim()}" · buscando en todas las solicitudes</div>}
        <div style={{ display: "flex", gap: 6, flexWrap: "wrap" }}>
          {roleChips.map(([k, label, col]) => <button key={k} onClick={() => setRoleF(k)} style={{ border: `1px solid ${roleF === k ? col : C.line2}`, background: roleF === k ? col : "#fff", color: roleF === k ? "#fff" : C.mut3, fontFamily: FONT, fontSize: 12, fontWeight: 800, padding: "6px 12px", borderRadius: 999, cursor: "pointer", opacity: nq ? 0.5 : 1 }}>{label}</button>)}
        </div>
        {list.length === 0 && <div style={{ background: C.sand, borderRadius: 14, padding: 24, textAlign: "center", fontSize: 13, fontWeight: 600, color: C.mut }}>No hay solicitudes aquí.</div>}
        {list.map((r) => {
          const emp = r.role === "empresa";
          const status = r.status || "pending";
          const sc = stCol[status] || stCol.pending;
          const prog = r.programa;
          return (
            <Card key={r.uid} style={{ padding: 16 }}>
              <div style={{ display: "flex", alignItems: "center", gap: 10, marginBottom: 10, flexWrap: "wrap" }}>
                <div style={{ width: 40, height: 40, borderRadius: 12, background: emp ? C.violet : C.orange, display: "flex", alignItems: "center", justifyContent: "center" }}><Icon name={emp ? "briefcase" : "user"} color="#fff" size={20} /></div>
                <div style={{ flex: 1, minWidth: 140 }}>
                  <div style={{ fontSize: 15, fontWeight: 800, color: C.ink }}>{r.name || r.contactName || r.email}</div>
                  <div style={{ fontSize: 12, fontWeight: 700, color: C.mut }}>{r.email}</div>
                </div>
                {r.programaClasesACarreras && <Pill color={C.cyanDk} bg={C.cyanSoft} bd="#B4E9F6">🎓 Clases a Carreras</Pill>}
                <Pill color={emp ? C.violetDk : C.orangeDk} bg={emp ? C.violetSoft : C.orangeSoft}>{emp ? "EMPRESA" : "APLICANTE"}</Pill>
                <Pill color={sc[0]} bg={sc[1]}>{status === "pending" ? "Pendiente" : status === "approved" ? "✓ Aprobada" : "Rechazada"}</Pill>
              </div>
              <div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: "4px 14px", background: C.sand, borderRadius: 12, padding: "11px 13px" }}>
                {emp ? (
                  <React.Fragment>
                    {row("Razón social", r.razonSocial || r.empresa)}{row("NIT", r.nit)}{row("Sector", r.sector)}{row("Tamaño", r.tamano)}
                    {row("Contacto", r.contactName)}{row("Cargo", r.cargo)}{row("Correo", r.email)}{row("Teléfono", r.phone)}
                    {row("Ciudad", r.ciudad || r.ubicacion)}{row("LinkedIn", r.linkedin)}{row("Instagram", r.instagram)}{row("Facebook", r.facebook)}
                    {row("Vacantes activas", r.vacantesActivas)}{row("Cuántas", r.cuantasVacantes)}{row("Roles", r.roles)}{row("Modalidad", r.modalidad)}
                    {row("Desea candidatos", r.deseaCandidatos)}{row("Comparte vacantes", r.comparteVacantes)}{row("Cómo nos escuchó", r.comoNosEscucho)}
                  </React.Fragment>
                ) : (
                  <React.Fragment>
                    {row("Edad", r.edad)}{row("Ciudad", r.city)}{row("Celular", r.phone)}{row("Situación", r.situacion)}
                    {row("Institución", r.institucion)}{row("Cómo nos escuchó", r.comoNosEscucho)}
                  </React.Fragment>
                )}
              </div>
              {prog && (
                <div style={{ marginTop: 8 }}>
                  <button onClick={() => setOpenProg(openProg === r.uid ? "" : r.uid)} style={{ border: `1px solid ${C.cyanSoft}`, background: C.cyanSoft, color: C.cyanDk, fontFamily: FONT, fontSize: 12, fontWeight: 800, padding: "8px 12px", borderRadius: 10, cursor: "pointer", width: "100%", textAlign: "left" }}>{openProg === r.uid ? "▾" : "▸"} Formulario "De Clases a Carreras" completo</button>
                  {openProg === r.uid && (
                    <div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: "4px 14px", background: "#fff", border: `1px solid ${C.line}`, borderRadius: 12, padding: "11px 13px", marginTop: 6 }}>
                      {row("Nombre completo", prog.nombreCompleto)}{row("Tipo doc", prog.tipoDoc)}{row("N° documento", prog.numDoc)}{row("Fecha nac.", prog.fechaNac)}{row("Edad", prog.edadProg)}{row("Sexo", prog.sexo)}{row("Género", prog.genero)}{row("Orientación", prog.orientacion)}
                      {row("País", prog.pais)}{row("Departamento", prog.departamento)}{row("Municipio", prog.municipio)}{row("Zona", prog.zona)}
                      {row("Vive con", prog.viveCon)}{row("Resp. económica", prog.responsabilidadEconomica)}{row("Tiempo cuidado", prog.tiempoCuidado)}
                      {row("Grupos", prog.grupos)}{row("Situación migratoria", prog.situacionMigratoria)}{row("Lengua", prog.lengua)}{row("Discapacidad", prog.discapacidad)}
                      {row("Nivel educativo", prog.nivelEducativo)}{row("Estudia", prog.estudiaActualmente)}{row("Institución", prog.institucionProg)}{row("Institución (otra)", prog.institucionOtra)}{row("Carrera", prog.carrera)}{row("Perfiles interés", prog.perfilesInteres)}{row("Inglés", prog.nivelIngles)}
                      {row("Situación laboral", prog.situacionLaboral)}{row("Ha trabajado", prog.haTrabajado)}
                      {row("Ingreso mensual", prog.ingresoMensual)}{row("Fuente ingresos", prog.fuenteIngresos)}{row("Sisbén", prog.sisben)}{row("Estrato", prog.estrato)}
                      {row("Dispositivos", prog.dispositivos)}{row("Internet", prog.internet)}
                    </div>
                  )}
                </div>
              )}
              <div style={{ display: "flex", gap: 8, marginTop: 12 }}>
                {status !== "approved" && <button onClick={() => decide(r.uid, "approved")} disabled={busy === r.uid} style={{ flex: 1, border: 0, background: C.green, color: "#fff", fontFamily: FONT, fontSize: 13, fontWeight: 800, padding: 11, borderRadius: 13, cursor: "pointer", opacity: busy === r.uid ? 0.6 : 1 }}>✓ {status === "rejected" ? "Aprobar acceso" : "Aprobar acceso"}</button>}
                {status !== "rejected" && <button onClick={() => decide(r.uid, "rejected")} disabled={busy === r.uid} style={{ border: `1px solid ${C.line2}`, background: "#fff", color: "#C0392B", fontFamily: FONT, fontSize: 13, fontWeight: 800, padding: "11px 15px", borderRadius: 13, cursor: "pointer" }}>Rechazar</button>}
                {status === "approved" && <span style={{ flex: 1, textAlign: "center", fontSize: 12, fontWeight: 700, color: C.green, alignSelf: "center" }}>Acceso concedido ✓</span>}
                {!emp && <button onClick={() => setEditUser({ uid: r.uid, name: r.name, email: r.email })} title="Editar perfil del aplicante" style={{ border: `1px solid ${C.violetSoftBd}`, background: C.violetSoft, color: C.violetDk, fontFamily: FONT, fontSize: 13, fontWeight: 800, padding: "11px 13px", borderRadius: 13, cursor: "pointer", display: "flex", alignItems: "center", gap: 5 }}><Icon name="test" size={14} color={C.violetDk} />Editar perfil</button>}
                <button onClick={() => { setExpOpen(expOpen === r.uid ? "" : r.uid); setExpDate(r.accessExpiry || ""); }} title="Tiempo límite de acceso" style={{ border: `1px solid ${r.accessExpiry ? C.orange : C.line2}`, background: r.accessExpiry ? C.orangeSoft : "#fff", color: r.accessExpiry ? C.orangeDk : C.mut3, fontFamily: FONT, fontSize: 13, fontWeight: 800, padding: "11px 13px", borderRadius: 13, cursor: "pointer" }}>⏳</button>
                <button onClick={() => { setPwOpen(pwOpen === r.uid ? "" : r.uid); setPw(""); setPwMsg(null); }} title="Cambiar contraseña" style={{ border: `1px solid ${C.line2}`, background: "#fff", color: C.mut3, fontFamily: FONT, fontSize: 13, fontWeight: 800, padding: "11px 13px", borderRadius: 13, cursor: "pointer" }}>🔑</button>
              </div>
              {expOpen === r.uid && (
                <div style={{ marginTop: 10, background: C.sand, borderRadius: 12, padding: 12 }}>
                  <div style={{ fontSize: 11, fontWeight: 800, letterSpacing: "0.06em", color: C.mut4, marginBottom: 6 }}>ACCESO AL PORTAL HABILITADO HASTA</div>
                  <div style={{ display: "flex", gap: 8, flexWrap: "wrap" }}>
                    <input type="date" value={expDate} onChange={(e) => setExpDate(e.target.value)} style={{ flex: 1, minWidth: 150, fontFamily: FONT, fontSize: 13, fontWeight: 600, color: C.ink, background: "#fff", border: `1px solid ${C.line2}`, borderRadius: 10, padding: "10px 12px" }} />
                    <button onClick={() => saveExpiry(r.uid, expDate)} disabled={busy === r.uid + "-exp"} style={{ border: 0, background: C.ink, color: "#fff", fontFamily: FONT, fontSize: 13, fontWeight: 800, padding: "10px 15px", borderRadius: 10, cursor: "pointer", opacity: busy === r.uid + "-exp" ? 0.6 : 1 }}>Guardar</button>
                    {r.accessExpiry && <button onClick={() => saveExpiry(r.uid, "")} style={{ border: `1px solid ${C.line2}`, background: "#fff", color: "#C0392B", fontFamily: FONT, fontSize: 13, fontWeight: 800, padding: "10px 13px", borderRadius: 10, cursor: "pointer" }}>Quitar</button>}
                  </div>
                  <div style={{ fontSize: 11, fontWeight: 600, color: C.mut, marginTop: 6 }}>Deja vacío (o “Quitar”) para acceso sin vencimiento. Tras esta fecha, el usuario no podrá ingresar.</div>
                </div>
              )}
              {pwOpen === r.uid && (
                <div style={{ marginTop: 10, background: C.sand, borderRadius: 12, padding: 12 }}>
                  <div style={{ fontSize: 11, fontWeight: 800, letterSpacing: "0.06em", color: C.mut4, marginBottom: 6 }}>NUEVA CONTRASEÑA PARA {(r.email || "").toUpperCase()}</div>
                  <div style={{ display: "flex", gap: 8 }}>
                    <input type="text" value={pw} onChange={(e) => setPw(e.target.value)} placeholder="Mínimo 6 caracteres" autoComplete="new-password" style={{ flex: 1, fontFamily: FONT, fontSize: 13, fontWeight: 600, color: C.ink, background: "#fff", border: `1px solid ${C.line2}`, borderRadius: 10, padding: "10px 12px" }} />
                    <button onClick={() => savePw(r.uid)} disabled={busy === r.uid + "-pw"} style={{ border: 0, background: C.ink, color: "#fff", fontFamily: FONT, fontSize: 13, fontWeight: 800, padding: "10px 15px", borderRadius: 10, cursor: "pointer", opacity: busy === r.uid + "-pw" ? 0.6 : 1 }}>Guardar</button>
                  </div>
                  {pwMsg && pwMsg.uid === r.uid && <div style={{ fontSize: 12, fontWeight: 700, color: pwMsg.kind === "ok" ? C.green : "#C0392B", marginTop: 6 }}>{pwMsg.text}</div>}
                </div>
              )}
            </Card>
          );
        })}
        {editUser && <AdminEditProfile uid={editUser.uid} name={editUser.name} email={editUser.email} onClose={() => setEditUser(null)} />}
      </div>
    );
  }

  function AdminInsights() {
    const [vac, setVac] = useState([]);
    const [cands, setCands] = useState([]);
    const [posts, setPosts] = useState([]);
    useEffect(() => {
      B().vacantes().then((r) => setVac(r.vacantes || []));
      B().candidateDirectory().then((r) => setCands(r.candidates || []));
      return B().subscribeFeed((l) => setPosts(l || []));
    }, []);
    const empresas = new Set(vac.map((v) => v.empresa || v.company).filter(Boolean)).size;
    const postulantes = cands.filter((c) => c.origin === "postulacion").length;
    const perfiles = cands.length - postulantes;
    const byArea = topCounts(vac.map((v) => v.area));
    const byMode = topCounts(vac.map((v) => v.modalidad));
    const byCity = topCounts(cands.map((c) => c.city));
    return (
      <div style={{ display: "flex", flexDirection: "column", gap: 14 }}>
        <div style={{ display: "grid", gridTemplateColumns: "repeat(auto-fit,minmax(110px,1fr))", gap: 8 }}>
          <StatTile label="VACANTES" value={vac.length} color={C.orange} />
          <StatTile label="EMPRESAS" value={empresas} color={C.violet} />
          <StatTile label="PERFILES" value={perfiles} color={C.cyan} />
          <StatTile label="POSTULANTES" value={postulantes} color={C.green} />
          <StatTile label="PUBLICACIONES" value={posts.length} color={C.pink} />
        </div>
        <div style={{ display: "grid", gridTemplateColumns: "repeat(auto-fit,minmax(220px,1fr))", gap: 12 }}>
          <BarList title="Vacantes por área" rows={byArea} color={C.orange} />
          <BarList title="Vacantes por modalidad" rows={byMode} color={C.violet} />
          <BarList title="Talento por ciudad" rows={byCity} color={C.cyan} />
        </div>
      </div>
    );
  }

  function AdminPublicar() {
    const [text, setText] = useState("");
    const [busy, setBusy] = useState(false);
    const [posts, setPosts] = useState([]);
    useEffect(() => B().subscribeFeed((l) => setPosts(l || [])), []);
    async function publish() { if (!text.trim()) return; setBusy(true); try { await B().publishPost(text); setText(""); } catch (e) { console.warn(e); } finally { setBusy(false); } }
    return (
      <div style={{ display: "flex", flexDirection: "column", gap: 12 }}>
        <Card style={{ padding: 14 }}>
          <div style={{ fontSize: 13, fontWeight: 800, color: C.ink, marginBottom: 8 }}>Nueva publicación en el feed</div>
          <textarea value={text} onChange={(e) => setText(e.target.value)} rows={3} placeholder="Comparte un anuncio, reto u oportunidad con la comunidad…" style={{ width: "100%", fontFamily: FONT, fontSize: 13, fontWeight: 500, color: C.ink, background: C.sand, border: `1px solid ${C.line2}`, borderRadius: 14, padding: "10px 12px", resize: "vertical" }} />
          <div style={{ display: "flex", justifyContent: "flex-end", marginTop: 10 }}><Btn onClick={publish} style={{ padding: "9px 16px", opacity: busy ? 0.6 : 1 }}>{busy ? "Publicando…" : "Publicar"}</Btn></div>
        </Card>
        <div style={{ fontSize: 12, fontWeight: 800, color: C.mut4, letterSpacing: "0.06em" }}>PUBLICACIONES RECIENTES</div>
        {posts.length === 0 && <div style={{ fontSize: 12, fontWeight: 600, color: C.mut }}>Aún no hay publicaciones.</div>}
        {posts.map((p) => (
          <Card key={p.id} style={{ padding: 13, display: "flex", gap: 10, alignItems: "flex-start" }}>
            <div style={{ flex: 1 }}>
              <div style={{ fontSize: 12, fontWeight: 800, color: C.ink }}>{p.authorName}</div>
              <div style={{ fontSize: 13, fontWeight: 500, color: "#3D3548", marginTop: 3, whiteSpace: "pre-wrap" }}>{p.text}</div>
            </div>
            <button onClick={() => B().deletePost(p.id)} style={{ border: `1px solid ${C.line2}`, background: "#fff", color: C.mut3, fontFamily: FONT, fontSize: 11, fontWeight: 800, padding: "6px 10px", borderRadius: 999, cursor: "pointer" }}>Eliminar</button>
          </Card>
        ))}
      </div>
    );
  }

  function AdminAdmins() {
    const [list, setList] = useState(B().adminList());
    const [email, setEmail] = useState("");
    const [busy, setBusy] = useState(false);
    useEffect(() => { const t = setInterval(() => setList(B().adminList()), 1200); return () => clearInterval(t); }, []);
    async function add() { const e = email.trim().toLowerCase(); if (!e) return; setBusy(true); try { await B().addAdmin(e); setEmail(""); } catch (err) { console.warn(err); } finally { setBusy(false); } }
    async function remove(e) { setBusy(true); try { await B().removeAdmin(e); } catch (err) { console.warn(err); } finally { setBusy(false); } }
    const inp = { fontFamily: FONT, fontSize: 14, fontWeight: 600, color: C.ink, background: C.sand, border: `1px solid ${C.line2}`, borderRadius: 13, padding: "12px 13px", width: "100%" };
    return (
      <div>
        <div style={{ fontSize: 12, fontWeight: 600, color: C.mut }}>Los admins pueden publicar en el feed. Agrega el correo de un usuario de Projects.</div>
        <div style={{ display: "flex", gap: 8, marginTop: 14 }}>
          <input value={email} onChange={(e) => setEmail(e.target.value)} type="email" placeholder="correo@o-lab.ai" style={inp} />
          <Btn onClick={add} style={{ padding: "12px 16px", opacity: busy ? 0.6 : 1 }}>Agregar</Btn>
        </div>
        <div style={{ display: "flex", flexDirection: "column", gap: 8, marginTop: 16 }}>
          <div style={{ display: "flex", alignItems: "center", gap: 10, background: C.violetSoft, borderRadius: 12, padding: "10px 12px" }}>
            <Icon name="star" color={C.violet} size={16} /><span style={{ flex: 1, fontSize: 13, fontWeight: 700, color: C.violetDk }}>tania@o-lab.ai</span><Pill color={C.violetDk} bg="#fff">Maestro</Pill>
          </div>
          {list.filter((e) => e !== "tania@o-lab.ai").map((e) => (
            <div key={e} style={{ display: "flex", alignItems: "center", gap: 10, background: C.sand, borderRadius: 12, padding: "10px 12px" }}>
              <Icon name="user" color={C.mut} size={16} /><span style={{ flex: 1, fontSize: 13, fontWeight: 700, color: C.ink }}>{e}</span>
              <button onClick={() => remove(e)} style={{ border: `1px solid ${C.line2}`, background: "#fff", color: C.mut3, fontFamily: FONT, fontSize: 12, fontWeight: 800, padding: "6px 10px", borderRadius: 999, cursor: "pointer" }}>Quitar</button>
            </div>
          ))}
          {list.filter((e) => e !== "tania@o-lab.ai").length === 0 && <div style={{ fontSize: 12, fontWeight: 600, color: C.mut, textAlign: "center", padding: 8 }}>Aún no has agregado otros admins.</div>}
        </div>
      </div>
    );
  }

  /* ----------------------------- VACANTES ----------------------------- */
  /* ------------------------- FAVORITOS ---------------------------- */
  function FavHeart({ kind, id, me, update, meta, size, float }) {
    const favs = (me && me.favorites) || {};
    const on = !!(favs[kind] || {})[id];
    function toggle(e) {
      if (e) e.stopPropagation();
      const bucket = { ...(favs[kind] || {}) };
      if (bucket[id]) delete bucket[id]; else bucket[id] = { at: Date.now(), ...(meta || {}) };
      update({ favorites: { ...favs, [kind]: bucket } });
    }
    const base = { border: 0, background: float ? "rgba(255,255,255,0.92)" : "transparent", cursor: "pointer", padding: float ? 6 : 4, display: "flex", alignItems: "center", justifyContent: "center", borderRadius: 999, lineHeight: 0 };
    if (float) Object.assign(base, { position: "absolute", top: 10, right: 10, boxShadow: "0 2px 8px rgba(0,0,0,0.18)" });
    return (
      <button onClick={toggle} title={on ? "Quitar de favoritas" : "Me encanta"} style={base}>
        <Icon name="heart" size={size || 20} color={on ? C.pink : C.mut4} fill={on ? C.pink : "none"} />
      </button>
    );
  }
  function FavToggle({ on, setOn, count }) {
    return (
      <button onClick={() => setOn(!on)} style={{ border: `1px solid ${on ? C.pink : C.line2}`, background: on ? "#FFE9F1" : "#fff", color: on ? "#B0176B" : C.mut3, fontFamily: FONT, fontSize: 12, fontWeight: 800, padding: "8px 13px", borderRadius: 999, cursor: "pointer", whiteSpace: "nowrap", display: "flex", alignItems: "center", gap: 6 }}>
        <Icon name="heart" size={14} color={on ? C.pink : C.mut3} fill={on ? C.pink : "none"} />Favoritas{count != null ? " (" + count + ")" : ""}
      </button>
    );
  }
  const favSet = (me, kind) => ((me && me.favorites && me.favorites[kind]) || {});

  // Botón de admin para subir/cambiar la miniatura de un lab/test (override en el portal).
  function ThumbButton({ id, current }) {
    const [busy, setBusy] = useState(false);
    const ref = useRef(null);
    async function onFile(e) { const f = e.target.files && e.target.files[0]; e.target.value = ""; if (!f) return; setBusy(true); try { const url = await B().uploadFeedImage(f); await B().setThumbnail(id, url); } catch (err) { console.warn(err); } finally { setBusy(false); } }
    return (
      <React.Fragment>
        <button onClick={(e) => { e.stopPropagation(); ref.current && ref.current.click(); }} title="Miniatura del contenido" style={{ position: "absolute", bottom: 8, right: 8, border: 0, background: "rgba(23,19,31,0.74)", color: "#fff", fontFamily: FONT, fontSize: 11, fontWeight: 800, padding: "6px 10px", borderRadius: 999, cursor: "pointer", display: "flex", alignItems: "center", gap: 5, zIndex: 3 }}>
          <Icon name="plus" size={12} color="#fff" />{busy ? "Subiendo…" : (current ? "Cambiar miniatura" : "Miniatura")}
        </button>
        <input ref={ref} type="file" accept="image/*" onChange={onFile} style={{ display: "none" }} />
      </React.Fragment>
    );
  }

  function Vacantes({ openRedirect, goLabs, me, update, onApply }) {
    const applied = me.appliedJobs || {};
    const [chip, setChip] = useState("todas");
    const [rawJobs, setRawJobs] = useState(null); // null = cargando
    const [src, setSrc] = useState("loading");
    useEffect(() => { B().vacantes().then((r) => { setSrc(r.source); setRawJobs(r.source === "live" ? mapLive(r.vacantes) : SEED().jobs); }); }, []);
    function mapLive(list) {
      return list.map((v, i) => ({
        id: v.codigo || "v" + i, initials: (v.empresa || "?").slice(0, 2).toUpperCase(), title: v.cargo || "Vacante",
        company: v.empresa || "", meta: [v.ciudad, v.modalidad, v.tipo].filter(Boolean).join(" · "),
        near: true, junior: true, gap: v.requisitos || v.perfil || "", codigo: v.codigo, area: v.area || "",
        modalidad: v.modalidad || "", tipo: v.tipo || "", requisitos: v.requisitos, cargo: v.cargo, link: v.link || "", ciudad: v.ciudad || "",
      }));
    }
    const [fArea, setFArea] = useState("Todas");
    const [fMode, setFMode] = useState("Todas");
    const [favOnly, setFavOnly] = useState(false);
    if (rawJobs === null) return <Section title="Vacantes con match" sub="Cargando vacantes de Comunidad Talentos…"><div style={{ padding: 40, textAlign: "center", color: C.mut, fontWeight: 700 }}>Un momento…</div></Section>;
    // Match AUTOMÁTICO por perfil: se recalcula para cada vacante con computeMatch.
    const jobs = rawJobs.map((j) => { const n = computeMatch(me, j); return { ...j, n, match: n + "%", matchColor: matchColor(n) }; }).sort((a, b) => b.n - a.n);
    const areas = Array.from(new Set(jobs.map((j) => j.area).filter(Boolean))).sort();
    const modes = Array.from(new Set(jobs.map((j) => j.modalidad).filter(Boolean))).sort();
    const favV = favSet(me, "vacantes");
    const filtered = jobs.filter((j) => {
      if (chip === "match" && j.n < 78) return false;
      if (fArea !== "Todas" && j.area !== fArea) return false;
      if (fMode !== "Todas" && j.modalidad !== fMode) return false;
      if (favOnly && !favV[j.id]) return false;
      return true;
    });
    const chips = [["todas", "Todas"], ["match", "Match alto"]];
    return (
      <Section title="Vacantes con match" sub={(src === "live" ? "En vivo desde Projects · Comunidad Talentos · " : "Ejemplos (demo) · ") + "Match calculado automáticamente con tu perfil"}>
        {src === "live" && jobs.length === 0 && (
          <div style={{ background: "#fff", border: "1px dashed #D6CEC4", borderRadius: 22, padding: 32, textAlign: "center" }}>
            <div style={{ fontSize: 16, fontWeight: 800, color: C.ink }}>Aún no hay vacantes publicadas</div>
            <div style={{ fontSize: 13, fontWeight: 600, color: C.mut, marginTop: 6 }}>Cuando el equipo publique vacantes en Comunidad Talentos, aparecerán aquí.</div>
          </div>
        )}
        <div style={{ background: C.ink, borderRadius: 20, padding: 14, display: "flex", alignItems: "center", gap: 12 }}>
          <div style={{ width: 44, height: 44, borderRadius: 14, background: "rgba(198,242,78,0.16)", display: "flex", alignItems: "center", justifyContent: "center", flexShrink: 0 }}><Icon name="clock" color={C.lime} /></div>
          <div style={{ flex: 1 }}>
            <div style={{ fontSize: 13, fontWeight: 800, color: "#fff" }}>3 empresas vieron tu perfil</div>
            <div style={{ fontSize: 11, color: "rgba(255,255,255,0.6)", fontWeight: 600, marginTop: 2 }}>Tu perfil está visible para reclutadores</div>
          </div>
          <Pill color={C.ink} bg={C.lime}>ON</Pill>
        </div>
        <div style={{ display: "flex", gap: 8, flexWrap: "wrap", alignItems: "center" }}>
          {chips.map(([k, label]) => (
            <button key={k} onClick={() => setChip(k)} style={{ border: `1px solid ${chip === k ? C.orange : C.line2}`, background: chip === k ? C.orangeSoft : "#fff", color: chip === k ? C.orangeDk : C.mut3, fontFamily: FONT, fontSize: 12, fontWeight: 800, padding: "8px 13px", borderRadius: 999, cursor: "pointer", whiteSpace: "nowrap" }}>{label}</button>
          ))}
          <FavToggle on={favOnly} setOn={setFavOnly} count={Object.keys(favV).length} />
          {areas.length > 0 && (
            <select value={fArea} onChange={(e) => setFArea(e.target.value)} style={{ fontFamily: FONT, fontSize: 12, fontWeight: 800, color: fArea === "Todas" ? C.mut3 : C.ink, background: "#fff", border: `1px solid ${fArea === "Todas" ? C.line2 : C.orange}`, borderRadius: 999, padding: "8px 12px", cursor: "pointer" }}>
              <option value="Todas">Todas las áreas</option>
              {areas.map((a) => <option key={a} value={a}>{a}</option>)}
            </select>
          )}
          {modes.length > 0 && (
            <select value={fMode} onChange={(e) => setFMode(e.target.value)} style={{ fontFamily: FONT, fontSize: 12, fontWeight: 800, color: fMode === "Todas" ? C.mut3 : C.ink, background: "#fff", border: `1px solid ${fMode === "Todas" ? C.line2 : C.orange}`, borderRadius: 999, padding: "8px 12px", cursor: "pointer" }}>
              <option value="Todas">Toda modalidad</option>
              {modes.map((m) => <option key={m} value={m}>{m}</option>)}
            </select>
          )}
          <span style={{ flex: 1 }} />
          <span style={{ fontSize: 12, fontWeight: 700, color: C.mut }}>{filtered.length} vacantes</span>
        </div>
        <div style={{ display: "grid", gridTemplateColumns: "repeat(auto-fill,minmax(280px,1fr))", gap: 12 }}>
          {filtered.map((job) => (
            <Card key={job.id} style={{ padding: 14 }}>
              <div style={{ display: "flex", gap: 12, alignItems: "flex-start" }}>
                <div style={{ width: 44, height: 44, borderRadius: 13, background: C.sand, border: `1px solid ${C.line}`, display: "flex", alignItems: "center", justifyContent: "center", fontSize: 12, fontWeight: 800, color: C.ink, flexShrink: 0 }}>{job.initials}</div>
                <div style={{ flex: 1, minWidth: 0 }}>
                  <div style={{ fontSize: 15, fontWeight: 800, color: C.ink, lineHeight: 1.25 }}>{job.title}</div>
                  <div style={{ fontSize: 12, fontWeight: 600, color: C.mut2, marginTop: 3 }}>{job.company}</div>
                  <div style={{ fontSize: 12, fontWeight: 600, color: C.mut, marginTop: 2 }}>{job.meta}</div>
                </div>
                <div style={{ flexShrink: 0, textAlign: "center" }}>
                  <div style={{ fontSize: 18, fontWeight: 800, color: job.matchColor, lineHeight: 1 }}>{job.match}</div>
                  <div style={{ fontSize: 8, fontWeight: 800, color: C.mut, letterSpacing: "0.08em" }}>MATCH</div>
                  <FavHeart kind="vacantes" id={job.id} me={me} update={update} meta={{ title: job.title, company: job.company }} size={18} />
                </div>
              </div>
              {job.n > 0 && <div style={{ marginTop: 12 }}><Bar pct={job.match} color={job.matchColor} h={6} /></div>}
              {job.gap && <div style={{ fontSize: 11, fontWeight: 700, color: C.mut2, marginTop: 9, lineHeight: 1.45 }}>{job.gap}</div>}
              <div style={{ display: "flex", gap: 8, marginTop: 12 }}>
                {applied[job.id]
                  ? <Btn kind="lime" onClick={() => job.link && window.open(job.link, "_blank", "noopener")} style={{ flex: 1, padding: 11 }}>✓ Postulada{job.link ? " · abrir ↗" : ""}</Btn>
                  : <Btn onClick={() => onApply(job)} style={{ flex: 1, padding: 11 }}>{job.link ? "Aplicar ↗" : "Postular"}</Btn>}
                <Btn kind="ghost" onClick={goLabs} style={{ padding: "11px 13px" }}>Subir match</Btn>
              </div>
            </Card>
          ))}
        </div>
      </Section>
    );
  }

  /* ----------------------------- LABS ----------------------------- */
  function Labs({ openRedirect, me, update, published, isAdmin, thumbs, hiddenMap }) {
    thumbs = thumbs || {}; hiddenMap = hiddenMap || {};
    // Ids de labs publicados por el portal (no "proj-") → esos se borran de verdad.
    const contentIdSet = new Set((Array.isArray(published) ? published : []).filter((p) => String(p.id).indexOf("proj-") !== 0).map((p) => p.id));
    function delLab(l, e) {
      if (e) e.stopPropagation();
      if (!window.confirm('¿Quitar el lab "' + (l.title || "") + '" del portal?')) return;
      B().setHidden(l.id, true);
      if (contentIdSet.has(l.id)) B().deleteContent(l.id);
    }
    const pub = Array.isArray(published) ? published : [];
    // Labs publicados por admins (con link real + miniatura) primero; luego el catálogo base.
    const pubLabs = pub.map((l) => ({ id: l.id, title: l.title || "Lab", area: l.area || "General", acts: l.acts || 0, xp: parseInt(l.xp, 10) || 300, tag: l.tag || "LAB", url: l.link || "", image: l.image || "", competencias: Array.isArray(l.competencias) ? l.competencias : [], deadline: l.deadline || "" }));
    const labs = [...pubLabs, ...SEED().labsCatalog.filter((c) => !pubLabs.some((p) => p.id === c.id)).map((c) => ({ ...c, url: "", image: "" }))];
    const [mine, setMine] = useState(() => me.myLabs || {});
    const [favOnly, setFavOnly] = useState(false);
    const [evItem, setEvItem] = useState(null);
    const [remindLab, setRemindLab] = useState(null);
    const favL = favSet(me, "labs");
    function open(lab) { const next = { ...mine, [lab.id]: { ...(mine[lab.id] || {}), title: lab.title, area: lab.area, acts: lab.acts, competencias: lab.competencias || [], status: (mine[lab.id] && mine[lab.id].status === "completado") ? "completado" : "en progreso", at: Date.now() } }; setMine(next); B().recordLab(lab, next[lab.id].status); if (lab.url) { try { window.open(lab.url, "_blank", "noopener"); } catch (e) { openRedirect(); } } else openRedirect(); }
    function complete(lab, e) { if (e && e.stopPropagation) e.stopPropagation(); const next = { ...mine, [lab.id]: { ...(mine[lab.id] || { title: lab.title, area: lab.area, acts: lab.acts }), competencias: lab.competencias || (mine[lab.id] && mine[lab.id].competencias) || [], status: "completado", at: Date.now() } }; setMine(next); B().recordLab(lab, "completado"); }
    const started = Object.keys(mine).length;
    const done = Object.values(mine).filter((l) => l.status === "completado").length;
    const shownLabs = (favOnly ? labs.filter((l) => favL[l.id]) : labs).filter((l) => !hiddenMap[l.id]);
    return (
      <Section title="Labs" sub="Contenido de O-Lab. Al abrir un Lab se agrega a tu perfil; complétalo, sube tu evidencia y gana puntos.">
        {evItem && <EvidenceModal me={me} kind="lab" item={evItem} onClose={() => setEvItem(null)} onDone={() => complete(evItem)} />}
        {remindLab && (
          <div onClick={() => setRemindLab(null)} style={{ position: "fixed", inset: 0, zIndex: 80, background: "rgba(23,19,31,0.62)", display: "flex", alignItems: "center", justifyContent: "center", padding: 18 }}>
            <div onClick={(e) => e.stopPropagation()} style={{ width: "100%", maxWidth: 430, background: C.cream, borderRadius: 22, padding: 22, animation: "olabRise .25s ease both", textAlign: "center" }}>
              <div style={{ fontSize: 44 }}>📸</div>
              <div style={{ fontSize: 18, fontWeight: 900, color: C.ink, marginTop: 6 }}>Antes de empezar…</div>
              <div style={{ fontSize: 13.5, fontWeight: 600, color: C.mut2, lineHeight: 1.6, marginTop: 10, textAlign: "left", background: "#fff", border: `1px solid ${C.line}`, borderRadius: 14, padding: "13px 15px" }}>
                Recuerda <b style={{ color: C.ink }}>tomar una foto como evidencia</b> mientras haces el laboratorio y/o con la <b style={{ color: C.ink }}>calificación final</b>.<br /><br />
                Al finalizar deberás subir esta foto en <b style={{ color: C.orangeDk }}>“📸 Subir evidencia”</b> para poder <b style={{ color: C.ink }}>avanzar a la siguiente fase</b>.
              </div>
              <button onClick={() => { const l = remindLab; setRemindLab(null); open(l); }} style={{ width: "100%", marginTop: 16, border: 0, background: "linear-gradient(135deg,#FF6600,#FF3D7F)", color: "#fff", fontFamily: FONT, fontSize: 15, fontWeight: 900, padding: "14px 0", borderRadius: 14, cursor: "pointer", boxShadow: "0 10px 22px rgba(255,102,0,0.32)" }}>Entendido, empezar ↗</button>
              <button onClick={() => setRemindLab(null)} style={{ marginTop: 8, border: 0, background: "transparent", color: C.mut3, fontFamily: FONT, fontSize: 13, fontWeight: 800, cursor: "pointer" }}>Cancelar</button>
            </div>
          </div>
        )}
        <div style={{ borderRadius: 22, padding: 16, background: "linear-gradient(150deg,#6C4CF1,#12C2E9)", color: "#fff", display: "flex", alignItems: "center", gap: 14, flexWrap: "wrap" }}>
          <div style={{ flex: 1, minWidth: 180 }}>
            <div style={{ fontSize: 10, fontWeight: 800, letterSpacing: "0.1em", opacity: 0.85 }}>TU PROGRESO EN LABS</div>
            <div style={{ fontSize: 18, fontWeight: 800, marginTop: 4 }}>{done} completados · {started} en tu ruta</div>
            <div style={{ marginTop: 10 }}><Bar pct={(labs.length ? Math.round((done / labs.length) * 100) : 0) + "%"} color={C.lime} track="rgba(255,255,255,0.28)" /></div>
          </div>
          <span style={{ fontSize: 11, fontWeight: 700, opacity: 0.9 }}>Se registran automáticamente al abrirlos</span>
        </div>
        <div style={{ display: "flex", justifyContent: "flex-end" }}><FavToggle on={favOnly} setOn={setFavOnly} count={Object.keys(favL).length} /></div>
        <div style={{ display: "grid", gridTemplateColumns: "repeat(auto-fill,minmax(260px,1fr))", gap: 12 }}>
          {shownLabs.map((l) => {
            const st = mine[l.id];
            const done = st && st.status === "completado";
            const img = thumbs[l.id] || l.image;
            return (
              <Card key={l.id} style={{ overflow: "hidden" }}>
                <div style={{ position: "relative", height: 130, background: img ? ("#2B1B4D url(" + img + ") center/cover no-repeat") : "linear-gradient(135deg,#2B1B4D,#4A1F00)" }}>
                  <span style={{ position: "absolute", top: 10, left: 10, fontSize: 10, fontWeight: 800, color: "#fff", background: "rgba(23,19,31,0.72)", padding: "5px 9px", borderRadius: 999 }}>{l.tag}{l.acts ? " · " + l.acts + " ACT." : ""}</span>
                  {st && <span style={{ position: "absolute", top: 10, right: 44, fontSize: 10, fontWeight: 800, color: done ? C.ink : "#fff", background: done ? C.lime : "rgba(255,102,0,0.9)", padding: "5px 9px", borderRadius: 999 }}>{done ? "✓ Completado" : "En progreso"}</span>}
                  <FavHeart kind="labs" id={l.id} me={me} update={update} meta={{ title: l.title, area: l.area }} size={17} float />
                  {!img && <div style={{ position: "absolute", inset: 0, display: "flex", alignItems: "center", justifyContent: "center", opacity: 0.35 }}><Icon name="flask" color="#fff" size={44} /></div>}
                  {isAdmin && <ThumbButton id={l.id} current={img} />}
                  {isAdmin && <button onClick={(e) => delLab(l, e)} title="Quitar del portal" style={{ position: "absolute", bottom: 8, left: 8, border: 0, background: "rgba(192,57,43,0.92)", color: "#fff", fontFamily: FONT, fontSize: 11, fontWeight: 800, padding: "6px 10px", borderRadius: 999, cursor: "pointer", display: "flex", alignItems: "center", gap: 5, zIndex: 3 }}><Icon name="close" size={12} color="#fff" />Borrar</button>}
                </div>
                <div style={{ padding: 13 }}>
                  <span style={{ fontSize: 10, fontWeight: 800, color: C.violetDk, background: C.violetSoft, padding: "3px 8px", borderRadius: 999 }}>{l.area}</span>
                  <div style={{ fontSize: 14, fontWeight: 800, color: C.ink, lineHeight: 1.3, minHeight: 38, marginTop: 8, display: "-webkit-box", WebkitLineClamp: 2, WebkitBoxOrient: "vertical", overflow: "hidden" }}>{l.title}</div>
                  <div style={{ display: "flex", alignItems: "center", gap: 8, marginTop: 11 }}>
                    <Btn onClick={() => setRemindLab(l)} style={{ flex: 1, padding: 10, borderRadius: 13 }}>{st ? "Continuar ↗" : "Empezar ↗"}</Btn>
                    {done ? <Pill color={C.green} bg={C.greenBg}>+{l.xp} XP</Pill>
                      : <button onClick={(e) => complete(l, e)} title="Marcar completado" style={{ border: `1px solid ${C.line2}`, background: "#fff", borderRadius: 11, padding: "9px 10px", cursor: "pointer", color: C.mut3 }}><Icon name="check" size={16} /></button>}
                  </div>
                  <button onClick={() => setEvItem(l)} style={{ width: "100%", marginTop: 8, border: `1px solid ${C.orangeSoftBd}`, background: C.orangeSoft, color: C.orangeDk, fontFamily: FONT, fontSize: 12.5, fontWeight: 800, padding: "9px 0", borderRadius: 12, cursor: "pointer", display: "flex", alignItems: "center", justifyContent: "center", gap: 6 }}>📸 Subir evidencia +50 XP</button>
                </div>
              </Card>
            );
          })}
        </div>
      </Section>
    );
  }

  /* ----------------------------- TESTS ----------------------------- */
  // Muestra el deadline de acceso de un contenido (si ya venció, lo marca).
  function deadlineInfo(deadline) {
    if (!deadline) return null;
    const d = new Date(deadline + "T23:59:59");
    if (isNaN(d.getTime())) return null;
    const today = new Date(); today.setHours(0, 0, 0, 0);
    const dd = new Date(deadline + "T00:00:00");
    const expired = dd < today;
    const label = d.toLocaleDateString("es-CO", { day: "2-digit", month: "short", year: "numeric" });
    return { expired, label };
  }
  function PublishedCard({ item, color, kind, me, update, isAdmin, thumbs, onEvidence }) {
    const dl = deadlineInfo(item.deadline);
    const img = (thumbs && thumbs[item.id]) || item.image;
    function delItem(e) { if (e) e.stopPropagation(); if (!window.confirm('¿Quitar "' + (item.title || "") + '" del portal?')) return; B().setHidden(item.id, true); B().deleteContent(item.id); }
    return (
      <Card style={{ overflow: "hidden", opacity: dl && dl.expired ? 0.75 : 1 }}>
        <div style={{ position: "relative", height: 130, background: img ? ("#2B1B4D url(" + img + ") center/cover no-repeat") : ("linear-gradient(135deg," + color + ",#2B1B4D)") }}>
          {item.tag && <span style={{ position: "absolute", top: 10, left: 10, fontSize: 10, fontWeight: 800, color: "#fff", background: "rgba(23,19,31,0.72)", padding: "5px 9px", borderRadius: 999 }}>{item.tag}</span>}
          {kind && me && update && <FavHeart kind={kind} id={item.id} me={me} update={update} meta={{ title: item.title }} size={17} float />}
          {isAdmin && <ThumbButton id={item.id} current={img} />}
          {isAdmin && <button onClick={delItem} title="Quitar del portal" style={{ position: "absolute", bottom: 8, left: 8, border: 0, background: "rgba(192,57,43,0.92)", color: "#fff", fontFamily: FONT, fontSize: 11, fontWeight: 800, padding: "6px 10px", borderRadius: 999, cursor: "pointer", display: "flex", alignItems: "center", gap: 5, zIndex: 3 }}><Icon name="close" size={12} color="#fff" />Borrar</button>}
        </div>
        <div style={{ padding: 13 }}>
          {item.area && <span style={{ fontSize: 10, fontWeight: 800, color: C.violetDk, background: C.violetSoft, padding: "3px 8px", borderRadius: 999 }}>{item.area}</span>}
          <div style={{ fontSize: 14, fontWeight: 800, color: C.ink, lineHeight: 1.3, marginTop: 8, display: "-webkit-box", WebkitLineClamp: 2, WebkitBoxOrient: "vertical", overflow: "hidden" }}>{item.title}</div>
          {dl && <div style={{ fontSize: 11, fontWeight: 800, color: dl.expired ? "#C0392B" : C.orangeDk, marginTop: 7, display: "flex", alignItems: "center", gap: 5 }}><Icon name="clock" size={13} color={dl.expired ? "#C0392B" : C.orangeDk} />{dl.expired ? "Acceso cerrado · " + dl.label : "Acceso hasta " + dl.label}</div>}
          <div style={{ display: "flex", alignItems: "center", gap: 8, marginTop: 11 }}>
            <Btn onClick={() => !(dl && dl.expired) && item.link && window.open(item.link, "_blank", "noopener")} style={{ flex: 1, padding: 10, borderRadius: 13, opacity: dl && dl.expired ? 0.5 : 1, cursor: dl && dl.expired ? "not-allowed" : "pointer" }}>{dl && dl.expired ? "Cerrado" : "Empezar ↗"}</Btn>
            {item.xp ? <Pill color={C.violetDk} bg={C.violetSoft}>+{String(item.xp).replace(/^\+/, "")} XP</Pill> : null}
          </div>
          {onEvidence && <button onClick={onEvidence} style={{ width: "100%", marginTop: 8, border: `1px solid ${C.orangeSoftBd}`, background: C.orangeSoft, color: C.orangeDk, fontFamily: FONT, fontSize: 12.5, fontWeight: 800, padding: "9px 0", borderRadius: 12, cursor: "pointer", display: "flex", alignItems: "center", justifyContent: "center", gap: 6 }}>📸 Subir evidencia +40 XP</button>}
        </div>
      </Card>
    );
  }

  function Tests({ me, update, published, isAdmin, thumbs }) {
    const defs = SEED().testDefs;
    const [favOnly, setFavOnly] = useState(false);
    const favT = favSet(me, "tests");
    const pubAll = Array.isArray(published) ? published : [];
    const pubTests = favOnly ? pubAll.filter((t) => favT[t.id]) : pubAll;
    const [running, setRunning] = useState(null);
    const [results, setResults] = useState(() => me.testResults || {});
    const [evItem, setEvItem] = useState(null);
    function done(testId, result) {
      setResults((r) => ({ ...r, [testId]: { ...result, at: Date.now() } }));
      B().saveTestResult(testId, result);
      setRunning(null);
      // Al terminar, invita a subir la evidencia para ganar puntos.
      const t = defs.find((d) => d.id === testId);
      if (t) setEvItem({ id: t.id, title: t.name });
    }
    return (
      <Section title="Tests de competencias" sub="Se responden aquí mismo. Haz el test, sube tu evidencia y gana puntos." right={pubAll.length > 0 ? <FavToggle on={favOnly} setOn={setFavOnly} count={Object.keys(favT).length} /> : null}>
        {evItem && <EvidenceModal me={me} kind="test" item={evItem} onClose={() => setEvItem(null)} />}
        {pubTests.length > 0 && (
          <div>
            <div style={{ fontSize: 14, fontWeight: 800, color: C.ink, marginBottom: 10 }}>Tests de O-Lab</div>
            <div style={{ display: "grid", gridTemplateColumns: "repeat(auto-fill,minmax(260px,1fr))", gap: 12 }}>
              {pubTests.map((t) => <PublishedCard key={t.id} item={t} color={C.orange} kind="tests" me={me} update={update} isAdmin={isAdmin} thumbs={thumbs} onEvidence={() => setEvItem({ id: t.id, title: t.title })} />)}
            </div>
          </div>
        )}
        <div style={{ display: "grid", gridTemplateColumns: "repeat(auto-fill,minmax(300px,1fr))", gap: 12 }}>
          {defs.map((t) => {
            const res = results[t.id];
            return (
              <Card key={t.id} style={{ padding: 16, display: "flex", flexDirection: "column", gap: 12 }}>
                <div style={{ display: "flex", alignItems: "center", gap: 12 }}>
                  <div style={{ width: 46, height: 46, borderRadius: 14, background: t.color, display: "flex", alignItems: "center", justifyContent: "center", flexShrink: 0 }}><Icon name={t.icon} color="#fff" size={22} /></div>
                  <div style={{ flex: 1 }}>
                    <div style={{ fontSize: 15, fontWeight: 800, color: C.ink }}>{t.name}</div>
                    <div style={{ fontSize: 11, fontWeight: 700, color: C.mut, marginTop: 2 }}>{t.questions.length} preguntas · {t.minutes} min</div>
                  </div>
                  <Pill color={t.color} bg="#F5F2EE" style={{ fontSize: 10 }}>{t.tag}</Pill>
                </div>
                <div style={{ fontSize: 12, fontWeight: 600, color: C.mut2, lineHeight: 1.5 }}>{t.intro}</div>
                {res && (
                  <div style={{ background: C.greenBg, border: `1px solid ${C.greenBd}`, borderRadius: 12, padding: "9px 12px", display: "flex", alignItems: "center", gap: 8 }}>
                    <Icon name="check" color={C.green} size={16} />
                    <span style={{ fontSize: 12, fontWeight: 800, color: C.green }}>{res.label}{res.pct != null ? " · " + res.pct + "/100" : ""}</span>
                  </div>
                )}
                <Btn onClick={() => setRunning(t)} kind={res ? "ghost" : "primary"} style={{ padding: 11 }}>{res ? "Repetir test" : "Hacer test"}</Btn>
                {res && <button onClick={() => setEvItem({ id: t.id, title: t.name })} style={{ border: `1px solid ${C.orangeSoftBd}`, background: C.orangeSoft, color: C.orangeDk, fontFamily: FONT, fontSize: 12.5, fontWeight: 800, padding: "9px 0", borderRadius: 12, cursor: "pointer", display: "flex", alignItems: "center", justifyContent: "center", gap: 6 }}>📸 Subir evidencia +40 XP</button>}
              </Card>
            );
          })}
        </div>
        {running && <TestRunner test={running} onClose={() => setRunning(null)} onDone={done} />}
      </Section>
    );
  }

  function TestRunner({ test, onClose, onDone }) {
    const [i, setI] = useState(0);
    const [answers, setAnswers] = useState([]);
    const [result, setResult] = useState(null);
    const total = test.questions.length;
    function choose(idx) {
      const next = answers.concat(idx);
      if (i + 1 < total) { setAnswers(next); setI(i + 1); }
      else finish(next);
    }
    function finish(all) {
      let res;
      if (test.kind === "perfil") {
        const tally = {};
        all.forEach((ai, qi) => { const t = test.questions[qi].opts[ai].t; tally[t] = (tally[t] || 0) + 1; });
        const top = Object.keys(tally).sort((a, b) => tally[b] - tally[a])[0];
        const o = test.outcomes[top];
        res = { label: o.label, summary: o.summary, skill: o.skill, pct: 82, color: test.color, kind: "perfil" };
      } else {
        const correct = all.reduce((n, ai, qi) => n + (ai === test.questions[qi].correct ? 1 : 0), 0);
        const pct = Math.round((correct / total) * 100);
        res = { label: pct >= 70 ? "Aprobado" : "Sigue practicando", pct, skill: test.skill, color: test.color, kind: "score", correct, total };
      }
      setResult(res);
    }
    const q = test.questions[i];
    return (
      <div onClick={onClose} style={{ position: "fixed", inset: 0, zIndex: 65, background: "rgba(23,19,31,0.62)", display: "flex", alignItems: "center", justifyContent: "center", padding: 20 }}>
        <div onClick={(e) => e.stopPropagation()} style={{ width: "100%", maxWidth: 480, background: "#fff", borderRadius: 24, padding: 22, animation: "olabRise .25s ease both" }}>
          {!result ? (
            <div>
              <div style={{ display: "flex", alignItems: "center", justifyContent: "space-between" }}>
                <Pill color={test.color} bg="#F5F2EE">{test.tag}</Pill>
                <span style={{ fontSize: 12, fontWeight: 800, color: C.mut }}>{i + 1} / {total}</span>
              </div>
              <div style={{ marginTop: 12 }}><Bar pct={Math.round(((i) / total) * 100) + "%"} color={test.color} h={6} /></div>
              <div style={{ fontSize: 18, fontWeight: 800, color: C.ink, lineHeight: 1.3, marginTop: 16 }}>{q.q}</div>
              <div style={{ display: "flex", flexDirection: "column", gap: 9, marginTop: 16 }}>
                {q.opts.map((o, idx) => (
                  <button key={idx} onClick={() => choose(idx)} style={{ border: `1px solid ${C.line2}`, background: C.cream, borderRadius: 14, padding: "13px 14px", cursor: "pointer", fontFamily: FONT, fontSize: 14, fontWeight: 700, color: C.ink, textAlign: "left" }}>{o.label != null ? o.label : o}</button>
                ))}
              </div>
              <button onClick={onClose} style={{ border: 0, background: "transparent", color: C.mut, fontFamily: FONT, fontSize: 13, fontWeight: 700, cursor: "pointer", marginTop: 14 }}>Cancelar</button>
            </div>
          ) : (
            <div style={{ textAlign: "center" }}>
              <div style={{ width: 64, height: 64, borderRadius: 999, margin: "0 auto", background: test.color, display: "flex", alignItems: "center", justifyContent: "center", boxShadow: `0 10px 24px ${test.color}55` }}><Icon name={result.kind === "perfil" ? "star" : "check"} color="#fff" size={30} /></div>
              <div style={{ fontSize: 12, fontWeight: 800, letterSpacing: "0.1em", color: C.mut4, marginTop: 14 }}>RESULTADO</div>
              <div style={{ fontSize: 22, fontWeight: 800, color: C.ink, marginTop: 4 }}>{result.label}{result.pct != null && result.kind === "score" ? " · " + result.pct + "/100" : ""}</div>
              <div style={{ fontSize: 13, fontWeight: 600, color: C.mut2, marginTop: 8, lineHeight: 1.5 }}>{result.summary || (result.kind === "score" ? "Sumaste “" + result.skill + "” a tu perfil (" + result.pct + "%). Las empresas lo verán en tu match." : "")}</div>
              <div style={{ display: "flex", gap: 8, marginTop: 20 }}>
                <Btn kind="ghost" onClick={onClose} style={{ flex: 1, padding: 13 }}>Cerrar</Btn>
                <Btn onClick={() => onDone(test.id, result)} style={{ flex: 1.4, padding: 13 }}>Guardar en mi perfil</Btn>
              </div>
            </div>
          )}
        </div>
      </div>
    );
  }

  /* ----------------------------- EVENTOS ----------------------------- */
  // Sesiones agendables (por ahora fijas). host = responsable que ve las agendas en Projects.
  const SESSIONS = [
    { id: "ses-marca-match", title: "Tu Marca, tu Match", host: "Dayanna", grad: "linear-gradient(150deg,#6C4CF1,#12C2E9)", icon: "star",
      desc: "Construye tu marca personal para conectar con las empresas correctas y destacar tu talento." },
    { id: "ses-speech", title: "El Speech que abre puertas", host: "Dayanna", grad: "linear-gradient(150deg,#FF6600,#FF3D7F)", icon: "chat",
      desc: "Aprende a presentarte en 30 segundos y abrir puertas en entrevistas y networking." },
  ];

  const slotLabel = (sl) => {
    let d = sl.date;
    try { d = new Date(sl.date + "T00:00:00").toLocaleDateString("es-CO", { weekday: "short", day: "2-digit", month: "short" }); } catch (e) {}
    return d + (sl.time ? " · " + sl.time : "");
  };
  function BookingModal({ session, onClose }) {
    const fixed = session.mode === "fijo" && Array.isArray(session.slots) && session.slots.length > 0;
    const [f, setF] = useState({ date: "", time: "", note: "" });
    const [slotId, setSlotId] = useState("");
    const [busy, setBusy] = useState(false);
    const [ok, setOk] = useState(false);
    const set = (k) => (e) => setF((p) => ({ ...p, [k]: e.target.value }));
    const inp = { fontFamily: FONT, fontSize: 14, fontWeight: 600, color: C.ink, background: C.sand, border: `1px solid ${C.line2}`, borderRadius: 12, padding: "12px 13px", width: "100%", boxSizing: "border-box" };
    const cap = { fontSize: 10, fontWeight: 800, letterSpacing: "0.06em", color: C.mut4, marginBottom: 5, display: "block" };
    const chosen = fixed ? (session.slots.find((s) => s.id === slotId) || null) : { date: f.date, time: f.time };
    const canConfirm = fixed ? !!slotId : !!f.date;
    async function confirm() {
      if (!canConfirm) return; setBusy(true);
      const date = chosen ? chosen.date : ""; const time = chosen ? chosen.time : "";
      try { await B().bookSession({ sessionId: session.id, sessionTitle: session.title, host: session.host, date, time, note: f.note }); setOk(true); setTimeout(onClose, 1500); }
      catch (e) { console.warn(e); } finally { setBusy(false); }
    }
    return (
      <div onClick={onClose} style={{ position: "fixed", inset: 0, zIndex: 65, background: "rgba(23,19,31,0.62)", display: "flex", alignItems: "center", justifyContent: "center", padding: 20 }}>
        <div onClick={(e) => e.stopPropagation()} style={{ width: "100%", maxWidth: 440, background: "#fff", borderRadius: 24, padding: 24, animation: "olabRise .25s ease both", maxHeight: "90vh", overflowY: "auto" }}>
          {ok ? (
            <div style={{ textAlign: "center", padding: "16px 0" }}>
              <div style={{ width: 60, height: 60, borderRadius: 999, margin: "0 auto", background: C.greenBg, display: "flex", alignItems: "center", justifyContent: "center" }}><Icon name="check" size={30} color={C.green} /></div>
              <div style={{ fontSize: 19, fontWeight: 800, color: C.ink, marginTop: 14 }}>¡Agendado! 🎉</div>
              <div style={{ fontSize: 13, fontWeight: 600, color: C.mut2, marginTop: 6, lineHeight: 1.5 }}>{session.host} recibirá tu agenda en Projects y coordinará contigo.</div>
            </div>
          ) : (
            <React.Fragment>
              <div style={{ display: "flex", justifyContent: "space-between", alignItems: "center" }}>
                <div style={{ fontSize: 19, fontWeight: 800, color: C.ink }}>Agendar sesión</div>
                <button onClick={onClose} style={{ border: `1px solid ${C.line2}`, background: "#fff", borderRadius: 999, width: 32, height: 32, cursor: "pointer", color: C.mut3 }}><Icon name="close" size={16} /></button>
              </div>
              <div style={{ fontSize: 13, fontWeight: 700, color: C.violetDk, marginTop: 6 }}>{session.title}</div>
              <div style={{ fontSize: 12, fontWeight: 600, color: C.mut, marginTop: 2 }}>Responsable: {session.host} · {fixed ? "elige uno de los horarios disponibles." : "elige el día y la hora que te sirvan."}</div>
              <div style={{ display: "flex", flexDirection: "column", gap: 12, marginTop: 16 }}>
                {fixed ? (
                  <div>
                    <label style={cap}>HORARIOS DISPONIBLES</label>
                    <div style={{ display: "flex", flexDirection: "column", gap: 8 }}>
                      {session.slots.map((sl) => {
                        const on = slotId === sl.id;
                        return (
                          <button key={sl.id} onClick={() => setSlotId(sl.id)} style={{ textAlign: "left", border: `1.5px solid ${on ? C.violet : C.line2}`, background: on ? C.violetSoft : "#fff", color: on ? C.violetDk : C.ink, fontFamily: FONT, fontSize: 13, fontWeight: 800, padding: "12px 14px", borderRadius: 12, cursor: "pointer", display: "flex", alignItems: "center", gap: 9 }}>
                            <Icon name={on ? "check" : "calendar"} size={16} color={on ? C.violet : C.mut3} />{slotLabel(sl)}
                          </button>
                        );
                      })}
                    </div>
                  </div>
                ) : (
                  <div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: 12 }}>
                    <div><label style={cap}>FECHA</label><input type="date" value={f.date} onChange={set("date")} style={inp} /></div>
                    <div><label style={cap}>HORA</label><input type="time" value={f.time} onChange={set("time")} style={inp} /></div>
                  </div>
                )}
                <div><label style={cap}>NOTA (opcional)</label><textarea value={f.note} onChange={set("note")} rows={2} placeholder="¿Algo que quieras contarle a la responsable?" style={{ ...inp, resize: "vertical" }} /></div>
              </div>
              <div style={{ display: "flex", gap: 8, marginTop: 18 }}>
                <Btn kind="ghost" onClick={onClose} style={{ flex: 1, padding: 13 }}>Cancelar</Btn>
                <Btn onClick={confirm} style={{ flex: 1.4, padding: 13, opacity: (!canConfirm || busy) ? 0.6 : 1 }}>{busy ? "Agendando…" : "Confirmar agenda"}</Btn>
              </div>
            </React.Fragment>
          )}
        </div>
      </div>
    );
  }

  function AdminSlots({ session }) {
    const [open, setOpen] = useState(false);
    const [mode, setMode] = useState(session.mode || "flexible");
    const [slots, setSlots] = useState(Array.isArray(session.slots) ? session.slots : []);
    const [nd, setNd] = useState(""); const [nt, setNt] = useState("");
    const [busy, setBusy] = useState(false);
    const inp = { fontFamily: FONT, fontSize: 12, fontWeight: 600, color: C.ink, background: "#fff", border: `1px solid ${C.line2}`, borderRadius: 10, padding: "8px 10px" };
    function addSlot() { if (!nd) return; setSlots((s) => s.concat({ id: "sl" + Date.now() + Math.floor(Math.random() * 999), date: nd, time: nt })); setNd(""); setNt(""); }
    async function save() { setBusy(true); try { await B().saveSession(session.id, { mode, slots }); setOpen(false); } catch (e) { console.warn(e); } finally { setBusy(false); } }
    return (
      <div style={{ marginTop: 10, borderTop: `1px solid ${C.line}`, paddingTop: 10 }}>
        <button onClick={() => setOpen((o) => !o)} style={{ border: 0, background: "transparent", color: C.orangeDk, fontFamily: FONT, fontSize: 12, fontWeight: 800, cursor: "pointer", padding: 0 }}>{open ? "▾" : "▸"} Configurar horarios (admin)</button>
        {open && (
          <div style={{ marginTop: 10, background: C.sand, borderRadius: 12, padding: 12 }}>
            <div style={{ display: "flex", gap: 6, marginBottom: 10 }}>
              {[["flexible", "Flexible (ellos eligen)"], ["fijo", "Horarios fijos"]].map(([k, label]) => <button key={k} onClick={() => setMode(k)} style={{ border: `1px solid ${mode === k ? C.ink : C.line2}`, background: mode === k ? C.ink : "#fff", color: mode === k ? "#fff" : C.mut3, fontFamily: FONT, fontSize: 11, fontWeight: 800, padding: "6px 10px", borderRadius: 999, cursor: "pointer" }}>{label}</button>)}
            </div>
            {mode === "fijo" && (
              <React.Fragment>
                <div style={{ display: "flex", flexDirection: "column", gap: 6, marginBottom: 8 }}>
                  {slots.length === 0 && <div style={{ fontSize: 11, fontWeight: 600, color: C.mut }}>Aún no hay horarios. Agrega abajo 👇</div>}
                  {slots.map((sl) => (
                    <div key={sl.id} style={{ display: "flex", alignItems: "center", gap: 8, background: "#fff", borderRadius: 9, padding: "6px 10px" }}>
                      <Icon name="calendar" size={13} color={C.violet} /><span style={{ flex: 1, fontSize: 12, fontWeight: 700, color: C.ink }}>{slotLabel(sl)}</span>
                      <button onClick={() => setSlots((s) => s.filter((x) => x.id !== sl.id))} style={{ border: 0, background: "transparent", color: "#C0392B", fontSize: 12, fontWeight: 800, cursor: "pointer" }}>✕</button>
                    </div>
                  ))}
                </div>
                <div style={{ display: "flex", gap: 6 }}>
                  <input type="date" value={nd} onChange={(e) => setNd(e.target.value)} style={{ ...inp, flex: 1 }} />
                  <input type="time" value={nt} onChange={(e) => setNt(e.target.value)} style={{ ...inp, width: 100 }} />
                  <button onClick={addSlot} style={{ border: 0, background: C.violet, color: "#fff", fontFamily: FONT, fontSize: 12, fontWeight: 800, padding: "8px 12px", borderRadius: 10, cursor: "pointer" }}>+ Agregar</button>
                </div>
              </React.Fragment>
            )}
            <div style={{ display: "flex", justifyContent: "flex-end", marginTop: 10 }}>
              <button onClick={save} disabled={busy} style={{ border: 0, background: C.ink, color: "#fff", fontFamily: FONT, fontSize: 12, fontWeight: 800, padding: "8px 15px", borderRadius: 10, cursor: "pointer", opacity: busy ? 0.6 : 1 }}>{busy ? "Guardando…" : "Guardar horarios"}</button>
            </div>
          </div>
        )}
      </div>
    );
  }
  function SessionCard({ session, myBooking, onBook, onCancel, isAdmin }) {
    const [who, setWho] = useState(null);
    const [showWho, setShowWho] = useState(false);
    useEffect(() => { if (isAdmin) return B().subscribeSessionBookings(session.id, setWho); }, [isAdmin]);
    const count = Array.isArray(who) ? who.length : null;
    const fmt = (b) => [b.date, b.time].filter(Boolean).join(" · ");
    const fixed = session.mode === "fijo" && Array.isArray(session.slots) && session.slots.length > 0;
    return (
      <Card style={{ overflow: "hidden", display: "flex", flexDirection: "column" }}>
        <div style={{ position: "relative", height: 96, background: session.grad, display: "flex", alignItems: "center", justifyContent: "center" }}>
          <Icon name={session.icon} size={40} color="#fff" />
          <span style={{ position: "absolute", top: 10, left: 10, fontSize: 10, fontWeight: 900, letterSpacing: "0.08em", color: "#fff", background: "rgba(23,19,31,0.4)", padding: "4px 9px", borderRadius: 999 }}>SESIÓN</span>
        </div>
        <div style={{ padding: 15, display: "flex", flexDirection: "column", flex: 1 }}>
          <div style={{ fontSize: 16, fontWeight: 800, color: C.ink, lineHeight: 1.25 }}>{session.title}</div>
          <div style={{ fontSize: 12, fontWeight: 700, color: C.mut, marginTop: 3, display: "flex", alignItems: "center", gap: 5 }}><Icon name="user" size={13} color={C.mut} />Con {session.host}</div>
          <div style={{ fontSize: 12.5, fontWeight: 600, color: C.mut2, lineHeight: 1.5, marginTop: 8, flex: 1 }}>{session.desc}</div>
          <div style={{ fontSize: 11, fontWeight: 800, color: C.violetDk, marginTop: 8, display: "flex", alignItems: "center", gap: 5 }}><Icon name="clock" size={13} color={C.violetDk} />{fixed ? session.slots.length + " horario" + (session.slots.length > 1 ? "s" : "") + " disponible" + (session.slots.length > 1 ? "s" : "") : "Horario flexible · tú eliges"}</div>
          {myBooking ? (
            <div style={{ marginTop: 13, background: C.greenBg, border: `1px solid ${C.greenBd}`, borderRadius: 13, padding: "11px 13px" }}>
              <div style={{ fontSize: 12, fontWeight: 800, color: C.green, display: "flex", alignItems: "center", gap: 6 }}><Icon name="check" size={15} color={C.green} />Agendaste {fmt(myBooking) ? "· " + fmt(myBooking) : ""}</div>
              <button onClick={() => onCancel(myBooking.id)} style={{ border: 0, background: "transparent", color: "#C0392B", fontFamily: FONT, fontSize: 11, fontWeight: 800, cursor: "pointer", padding: "6px 0 0", marginTop: 2 }}>Cancelar agenda</button>
            </div>
          ) : (
            <Btn onClick={onBook} style={{ marginTop: 13, padding: 12, borderRadius: 13, display: "flex", alignItems: "center", justifyContent: "center", gap: 7 }}><Icon name="calendar" size={16} color="#fff" />Agendar mi cupo</Btn>
          )}
          {isAdmin && <AdminSlots session={session} />}
          {isAdmin && (
            <div style={{ marginTop: 10, borderTop: `1px solid ${C.line}`, paddingTop: 10 }}>
              <button onClick={() => setShowWho((s) => !s)} style={{ border: 0, background: "transparent", color: C.violetDk, fontFamily: FONT, fontSize: 12, fontWeight: 800, cursor: "pointer", padding: 0 }}>{showWho ? "▾" : "▸"} Agendados{count != null ? " (" + count + ")" : ""}</button>
              {showWho && Array.isArray(who) && (
                who.length === 0
                  ? <div style={{ fontSize: 12, fontWeight: 600, color: C.mut, marginTop: 6 }}>Nadie se ha agendado aún.</div>
                  : <div style={{ display: "flex", flexDirection: "column", gap: 6, marginTop: 8 }}>{who.map((b) => (
                      <div key={b.id} style={{ fontSize: 12, fontWeight: 600, color: C.mut2, background: C.sand, borderRadius: 10, padding: "7px 10px" }}><b style={{ color: C.ink }}>{b.userName || b.userEmail}</b>{fmt(b) ? " · " + fmt(b) : ""}{b.userPhone ? " · " + b.userPhone : ""}</div>
                    ))}</div>
              )}
            </div>
          )}
        </div>
      </Card>
    );
  }

  function Eventos({ openRedirect, me, update, published, isAdmin }) {
    const [bookings, setBookings] = useState([]);
    const [cfg, setCfg] = useState({});
    const [modalSes, setModalSes] = useState(null);
    useEffect(() => B().subscribeMyBookings(setBookings), []);
    useEffect(() => B().subscribeSessions(setCfg), []);
    const bookingFor = (sid) => bookings.find((b) => b.sessionId === sid);
    // Mezcla la config del admin (modo/horarios) con la sesión base.
    const sessions = SESSIONS.map((s) => ({ ...s, ...(cfg[s.id] || {}) }));
    return (
      <Section title="Sesiones" sub="Agenda tu cupo con el equipo O-Lab. La responsable coordina contigo desde Projects.">
        <div style={{ display: "grid", gridTemplateColumns: "repeat(auto-fill,minmax(300px,1fr))", gap: 14 }}>
          {sessions.map((s) => <SessionCard key={s.id} session={s} myBooking={bookingFor(s.id)} onBook={() => setModalSes(s)} onCancel={(id) => B().cancelBooking(id)} isAdmin={isAdmin} />)}
        </div>
        {modalSes && <BookingModal session={modalSes} onClose={() => setModalSes(null)} />}
      </Section>
    );
  }

  /* --------- Hoja de vida descargable (ventana imprimible → PDF) ---------- */
  function escapeHtml(s) { return String(s == null ? "" : s).replace(/[&<>"']/g, (c) => ({ "&": "&amp;", "<": "&lt;", ">": "&gt;", '"': "&quot;", "'": "&#39;" }[c])); }
  // Genera la hoja de vida con el diseño "Aurora" del CV Builder (sidebar oscuro + naranja).
  function buildCvHtml(me) {
    const A = "#FF6600";
    const photo = me.photoUrl
      ? `<img class="photo" src="${escapeHtml(me.photoUrl)}"/>`
      : `<div class="photo ph">${escapeHtml((me.name || "?").split(" ").filter(Boolean).slice(0, 2).map((w) => w[0]).join("").toUpperCase() || "?")}</div>`;
    const contact = [
      me.email ? ["EMAIL", me.email] : null,
      me.phone ? ["TELÉFONO", me.phone] : null,
      (me.city || me.edad) ? ["UBICACIÓN", [me.city, me.edad ? me.edad + " años" : ""].filter(Boolean).join(" · ")] : null,
      me.linkedin ? ["LINKEDIN", me.linkedin] : null,
      me.institucion ? ["INSTITUCIÓN", me.institucion] : null,
    ].filter(Boolean).map(([l, v]) => `<div class="ci"><div class="cl">${l}</div><div class="cv">${escapeHtml(v)}</div></div>`).join("");
    const valArr = Array.isArray(me.validations) ? me.validations : [];
    const isValidated = (type, name) => valArr.some((v) => v.type === type && String(v.name || "").toLowerCase() === String(name || "").toLowerCase());
    const skills = (me.skills || []).map((s) => `<span class="chip">${escapeHtml(s.name)}</span>`).join("")
      + (me.desiredRoles || []).map((r) => `<span class="chip${isValidated("competencia", r) ? " ok" : ""}">${isValidated("competencia", r) ? "✓ " : ""}${escapeHtml(r)}</span>`).join("");
    const VLABEL = { competencia: "Competencia", lab: "Lab", test: "Test", evento: "Evento" };
    const validados = valArr.length
      ? `<h2>Validado por O-Lab</h2><ul class="ol seal">${valArr.map((v) => `<li><span class="sealtag">✓ o-lab.ai</span> <b>${escapeHtml(v.name)}</b> · ${escapeHtml(VLABEL[v.type] || "Logro")}</li>`).join("")}</ul>`
      : "";
    const langs = (Array.isArray(me.languages) && me.languages.length ? me.languages : [{ lang: "Español", level: "Nativo" }])
      .map((l) => `<div class="lang"><b>${escapeHtml(l.lang || "")}</b> — ${escapeHtml(l.level || "")}</div>`).join("");
    const exp = (Array.isArray(me.experience) ? me.experience : []).map((e) => `
      <div class="item"><div class="it">${escapeHtml(e.cargo || "")}</div>
      <div class="im">${[escapeHtml(e.empresa || ""), escapeHtml(e.periodo || ""), escapeHtml(e.ciudad || "")].filter(Boolean).join(" · ")}</div>
      ${Array.isArray(e.bullets) && e.bullets.length ? `<ul>${e.bullets.filter(Boolean).map((b) => `<li>${escapeHtml(b)}</li>`).join("")}</ul>` : ""}</div>`).join("");
    const edu = (Array.isArray(me.education) && me.education.length ? me.education : (me.institucion ? [{ titulo: me.situacion || "Estudios", institucion: me.institucion, periodo: "" }] : []))
      .map((e) => `<div class="item"><div class="it">${escapeHtml(e.titulo || "")}</div><div class="im">${[escapeHtml(e.institucion || ""), escapeHtml(e.periodo || "")].filter(Boolean).join(" · ")}</div></div>`).join("");
    const labsDone = Object.values(me.myLabs || {}).filter((l) => l.status === "completado");
    const formacion = (labsDone.length || Object.keys(me.testResults || {}).length)
      ? `<h2>Formación O-Lab</h2><ul class="ol">${labsDone.map((l) => `<li>✓ <b>${escapeHtml(l.title)}</b>${l.area ? " · " + escapeHtml(l.area) : ""}</li>`).join("")}${Object.values(me.testResults || {}).map((t) => `<li>🧪 <b>${escapeHtml(t.name || t.skill || "Test")}</b>${t.pct != null ? " · " + escapeHtml(t.pct) + "%" : ""}</li>`).join("")}</ul>` : "";
    return `<!doctype html><html lang="es"><head><meta charset="utf-8"><title>Hoja de vida · ${escapeHtml(me.name || "")}</title>
<style>
*{box-sizing:border-box;margin:0;padding:0;font-family:'Plus Jakarta Sans',system-ui,Arial,sans-serif}
body{color:#17131F;background:#fff}
.cv{display:flex;min-height:100vh;max-width:900px;margin:0 auto}
.side{width:300px;flex:0 0 300px;background:#17131F;color:#fff;padding:34px 26px}
.main{flex:1;padding:36px 34px}
.photo{width:120px;height:120px;border-radius:999px;object-fit:cover;display:block;margin:0 auto 18px;border:3px solid rgba(255,255,255,.15)}
.photo.ph{background:${A};color:#fff;display:flex;align-items:center;justify-content:center;font-size:44px;font-weight:800}
.nm{font-size:26px;font-weight:800;line-height:1.1;letter-spacing:-.02em}
.rl{color:${A};font-weight:800;font-size:14px;margin-top:6px}
.sh{color:${A};font-size:12px;font-weight:900;letter-spacing:.12em;margin:22px 0 10px}
.ci{margin-bottom:11px}.cl{font-size:9.5px;font-weight:800;letter-spacing:.08em;color:rgba(255,255,255,.5)}.cv{font-size:12.5px;font-weight:600;color:#fff;word-break:break-word;margin-top:2px}
.chip{display:inline-block;background:rgba(255,255,255,.1);border:1px solid rgba(255,255,255,.18);color:#fff;border-radius:999px;padding:5px 11px;font-size:11.5px;font-weight:700;margin:0 6px 7px 0}
.chip.ok{background:rgba(198,242,78,.16);border-color:rgba(198,242,78,.55);color:#EAFFC2}
.ol.seal li{display:flex;align-items:center;gap:8px}.sealtag{flex:0 0 auto;font-size:9px;font-weight:900;color:#0B7A3B;background:#DCF7A6;border-radius:999px;padding:2px 7px;letter-spacing:.03em}
.lang{font-size:13px;margin-bottom:6px}.lang b{font-weight:800}
h2{font-size:14px;font-weight:900;letter-spacing:.08em;text-transform:uppercase;color:${A};margin:26px 0 12px}h2:first-child{margin-top:0}
.about{font-size:13.5px;line-height:1.65;color:#3D3548}
.item{margin-bottom:16px}.it{font-size:15px;font-weight:800;color:#17131F}.im{font-size:12.5px;font-weight:700;color:${A};margin-top:2px}
.item ul{margin:8px 0 0 2px;list-style:none}.item li{font-size:12.5px;color:#3D3548;line-height:1.5;padding:2px 0 2px 16px;position:relative}.item li::before{content:"•";color:${A};position:absolute;left:2px;font-weight:900}
.ol{list-style:none}.ol li{font-size:12.5px;color:#3D3548;padding:4px 0;border-bottom:1px solid #EDE6DD}
.foot{margin-top:26px;font-size:10.5px;color:#B0A7BB}
@media print{@page{margin:0}.side{-webkit-print-color-adjust:exact;print-color-adjust:exact}}
</style></head><body>
<div class="cv">
  <div class="side">
    ${photo}
    <div class="nm">${escapeHtml(me.name || "Nombre Apellido")}</div>
    <div class="rl">${escapeHtml(me.role || "Tu cargo o profesión")}</div>
    <div class="sh">Contacto</div>${contact}
    ${skills ? `<div class="sh">Skills</div>${skills}` : ""}
    <div class="sh">Idiomas</div>${langs}
  </div>
  <div class="main">
    ${me.bio ? `<h2>Perfil</h2><div class="about">${escapeHtml(me.bio)}</div>` : ""}
    ${exp ? `<h2>Experiencia</h2>${exp}` : ""}
    ${edu ? `<h2>Educación</h2>${edu}` : ""}
    ${validados}
    ${formacion}
    <div class="foot">Hoja de vida generada desde Portal O-Lab · Tu talento mueve el futuro</div>
  </div>
</div>
</body></html>`;
  }
  function downloadCV(me) {
    const w = window.open("", "_blank");
    if (!w) { alert("Permite las ventanas emergentes para descargar tu hoja de vida."); return; }
    w.document.write(buildCvHtml(me));
    w.document.close();
    setTimeout(() => { try { w.focus(); w.print(); } catch (e) {} }, 400);
  }

  // Modal para subir la foto de evidencia de un Lab/Test y ganar puntos.
  // Guarda la foto en el perfil y, solo para mayores de edad, ofrece publicarla en el feed.
  function EvidenceModal({ me, kind, item, onClose, onDone }) {
    const [file, setFile] = useState(null);
    const [preview, setPreview] = useState("");
    const [note, setNote] = useState("");
    const [pubFeed, setPubFeed] = useState(false);
    const [busy, setBusy] = useState(false);
    const [result, setResult] = useState(null);
    const fileRef = useRef(null);
    const adult = (Number(me.edad) || 0) >= 18;
    const [err, setErr] = useState("");
    const kindWord = kind === "test" ? "test" : kind === "libre" ? "avance" : "lab";
    const xpAmt = kind === "test" ? 40 : kind === "libre" ? 20 : 50;
    const coinAmt = kind === "test" ? 20 : kind === "libre" ? 10 : 25;
    function pick(e) { const f = e.target.files && e.target.files[0]; e.target.value = ""; if (!f) return; setFile(f); const r = new FileReader(); r.onload = () => setPreview(r.result); r.readAsDataURL(f); }
    async function submit() {
      if (!file || busy) return; setBusy(true); setErr("");
      try {
        const res = await B().uploadEvidence(file, { note, kind, labId: (item && item.id) || "", labTitle: (item && item.title) || "", publishFeed: pubFeed && adult });
        setResult(res || { xpGain: 0 }); if (onDone) onDone(res);
      } catch (e) {
        console.warn(e);
        setErr((e && (e.code === "storage/unauthorized" || e.code === "permission-denied")) ? "No se pudo guardar. Recarga la página e inténtalo de nuevo." : "No se pudo subir la foto. Revisa tu conexión e inténtalo otra vez.");
      } finally { setBusy(false); }
    }
    const inp = { width: "100%", boxSizing: "border-box", fontFamily: FONT, fontSize: 13, fontWeight: 600, color: C.ink, background: C.sand, border: `1px solid ${C.line2}`, borderRadius: 12, padding: "10px 12px" };
    return (
      <div onClick={onClose} style={{ position: "fixed", inset: 0, zIndex: 80, background: "rgba(23,19,31,0.62)", display: "flex", alignItems: "center", justifyContent: "center", padding: 18 }}>
        <div onClick={(e) => e.stopPropagation()} style={{ width: "100%", maxWidth: 440, background: C.cream, borderRadius: 22, padding: 20, animation: "olabRise .25s ease both", maxHeight: "90vh", overflowY: "auto" }}>
          {!result ? (
            <React.Fragment>
              <div style={{ display: "flex", justifyContent: "space-between", alignItems: "flex-start", gap: 8 }}>
                <div>
                  <div style={{ fontSize: 17, fontWeight: 900, color: C.ink }}>📸 Sube tu evidencia</div>
                  <div style={{ fontSize: 12, fontWeight: 600, color: C.mut2, marginTop: 2 }}>{item && item.title ? "De: " + item.title : "Una selfie con el " + kindWord + " detrás o una captura de tu avance."}</div>
                </div>
                <button onClick={onClose} style={{ border: `1px solid ${C.line2}`, background: "#fff", borderRadius: 999, width: 30, height: 30, minWidth: 30, cursor: "pointer", color: C.mut3 }}>✕</button>
              </div>
              <div style={{ margintop: 6, display: "inline-flex", alignItems: "center", gap: 6, marginTop: 10 }}>
                <Pill color={C.orangeDk} bg={C.orangeSoft} bd={C.orangeSoftBd}>+{xpAmt} XP</Pill>
                <Pill color="#0B7A3B" bg={C.greenBg}>+{coinAmt} O-Coins</Pill>
              </div>
              <div onClick={() => fileRef.current && fileRef.current.click()} style={{ marginTop: 12, border: `2px dashed ${preview ? C.orange : C.line2}`, borderRadius: 16, padding: preview ? 0 : "26px 14px", textAlign: "center", cursor: "pointer", background: "#fff", overflow: "hidden" }}>
                {preview ? <img src={preview} alt="" style={{ width: "100%", maxHeight: 240, objectFit: "cover", display: "block" }} />
                  : <div><div style={{ fontSize: 30 }}>🤳</div><div style={{ fontSize: 13, fontWeight: 800, color: C.ink, marginTop: 6 }}>Toca para tomar o elegir tu foto</div><div style={{ fontSize: 11, fontWeight: 600, color: C.mut, marginTop: 2 }}>Cámara o galería</div></div>}
              </div>
              <input ref={fileRef} type="file" accept="image/*" onChange={pick} style={{ display: "none" }} />
              {preview && <button onClick={() => fileRef.current && fileRef.current.click()} style={{ border: 0, background: "transparent", color: C.orangeDk, fontFamily: FONT, fontSize: 12, fontWeight: 800, cursor: "pointer", marginTop: 6 }}>Cambiar foto</button>}
              <input value={note} onChange={(e) => setNote(e.target.value)} placeholder="Nota (ej: Terminé el Lab de montacargas)" style={{ ...inp, marginTop: 10 }} />
              {adult ? (
                <label style={{ display: "flex", alignItems: "flex-start", gap: 9, marginTop: 12, background: "#fff", border: `1px solid ${pubFeed ? C.orange : C.line2}`, borderRadius: 12, padding: "11px 12px", cursor: "pointer" }}>
                  <input type="checkbox" checked={pubFeed} onChange={(e) => setPubFeed(e.target.checked)} style={{ marginTop: 2, width: 16, height: 16, accentColor: C.orange }} />
                  <span><span style={{ fontSize: 13, fontWeight: 800, color: C.ink }}>Publicar también en el feed 🎉</span><br /><span style={{ fontSize: 11, fontWeight: 600, color: C.mut2 }}>Tu logro se comparte con la comunidad.</span></span>
                </label>
              ) : (
                <div style={{ marginTop: 12, background: C.sand, border: `1px solid ${C.line}`, borderRadius: 12, padding: "10px 12px", fontSize: 11.5, fontWeight: 600, color: C.mut2 }}>🔒 Publicar en el feed está disponible solo para mayores de edad. Tu evidencia se guarda en tu perfil y suma puntos igual.</div>
              )}
              {err && <div style={{ fontSize: 12, fontWeight: 700, color: "#C0392B", background: "#FDECEA", border: "1px solid #F5C6C0", borderRadius: 10, padding: "9px 11px", marginTop: 12 }}>{err}</div>}
              <button onClick={submit} disabled={!file || busy} style={{ width: "100%", marginTop: 14, border: 0, background: !file ? C.line2 : "linear-gradient(135deg,#FF6600,#FF3D7F)", color: "#fff", fontFamily: FONT, fontSize: 15, fontWeight: 900, padding: "14px 0", borderRadius: 14, cursor: !file ? "not-allowed" : "pointer", boxShadow: !file ? "none" : "0 10px 22px rgba(255,102,0,0.32)" }}>{busy ? "Subiendo…" : "Subir y ganar +" + xpAmt + " XP"}</button>
            </React.Fragment>
          ) : (
            <div style={{ textAlign: "center", padding: "10px 4px" }}>
              <div style={{ fontSize: 46, animation: "olabPulse 1.4s ease-in-out infinite" }}>🎉</div>
              <div style={{ fontSize: 20, fontWeight: 900, color: C.ink, marginTop: 6 }}>¡Evidencia subida!</div>
              {result.xpGain > 0
                ? <div style={{ fontSize: 13.5, fontWeight: 700, color: C.mut2, marginTop: 4 }}>Ganaste <b style={{ color: C.orangeDk }}>+{result.xpGain} XP</b> y <b style={{ color: "#0B7A3B" }}>+{result.coinGain} O-Coins</b>.</div>
                : <div style={{ fontSize: 13, fontWeight: 700, color: C.mut2, marginTop: 4 }}>Guardada en tu perfil. Ya habías ganado los puntos de este {kindWord}.</div>}
              {result.published && <div style={{ fontSize: 12.5, fontWeight: 800, color: C.violetDk, marginTop: 8 }}>✓ Publicada en el feed</div>}
              {result.blockedMinor && <div style={{ fontSize: 11.5, fontWeight: 600, color: C.mut2, marginTop: 8 }}>🔒 No se publicó en el feed (solo mayores de edad), pero quedó en tu perfil.</div>}
              <button onClick={onClose} style={{ width: "100%", marginTop: 16, border: 0, background: C.ink, color: "#fff", fontFamily: FONT, fontSize: 14, fontWeight: 800, padding: 13, borderRadius: 13, cursor: "pointer" }}>Listo</button>
            </div>
          )}
        </div>
      </div>
    );
  }

  function EvidenceCard({ me, update }) {
    const [open, setOpen] = useState(false);
    const evidence = Array.isArray(me.evidence) ? me.evidence : [];
    return (
      <Card style={{ padding: 15 }}>
        {open && <EvidenceModal me={me} kind="libre" item={null} onClose={() => setOpen(false)} />}
        <div style={{ display: "flex", alignItems: "baseline", justifyContent: "space-between", gap: 8 }}>
          <span style={{ fontSize: 14, fontWeight: 800, color: C.ink }}>Evidencias de estudio</span>
          <Pill color={C.orangeDk} bg={C.orangeSoft} bd={C.orangeSoftBd}>+XP y O-Coins</Pill>
        </div>
        <div style={{ fontSize: 12, fontWeight: 600, color: C.mut, marginTop: 3 }}>Sube una selfie con el Lab detrás o una captura de tu avance. Sumas puntos y las empresas ven que sí lo estás haciendo.</div>
        <div style={{ display: "flex", gap: 8, marginTop: 12, flexWrap: "wrap" }}>
          <button onClick={() => setOpen(true)} style={{ border: 0, background: C.ink, color: "#fff", fontFamily: FONT, fontSize: 13, fontWeight: 800, padding: "10px 15px", borderRadius: 12, cursor: "pointer", display: "flex", alignItems: "center", gap: 6 }}><Icon name="plus" size={14} color="#fff" />📸 Subir evidencia</button>
        </div>
        {evidence.length > 0 && (
          <div style={{ display: "grid", gridTemplateColumns: "repeat(auto-fill, minmax(120px,1fr))", gap: 10, marginTop: 13 }}>
            {evidence.map((ev) => (
              <div key={ev.at} style={{ borderRadius: 14, overflow: "hidden", border: `1px solid ${C.line}`, background: C.sand, position: "relative" }}>
                <img src={ev.url} alt="evidencia" style={{ width: "100%", height: 96, objectFit: "cover", display: "block" }} />
                <button onClick={() => B().deleteEvidence(ev.at)} title="Eliminar" style={{ position: "absolute", top: 5, right: 5, border: 0, background: "rgba(23,19,31,0.65)", color: "#fff", borderRadius: 999, width: 22, height: 22, cursor: "pointer", fontSize: 11 }}>✕</button>
                {ev.status && <div style={{ position: "absolute", top: 5, left: 5 }}><Pill color={ev.status === "confirmada" ? C.green : C.orangeDk} bg={ev.status === "confirmada" ? C.greenBg : C.orangeSoft} style={{ fontSize: 9 }}>{ev.status === "confirmada" ? "✓ Validada" : "Pendiente"}</Pill></div>}
                {ev.note && <div style={{ padding: "7px 9px", fontSize: 11, fontWeight: 600, color: C.mut2, lineHeight: 1.3 }}>{ev.note}</div>}
              </div>
            ))}
          </div>
        )}
      </Card>
    );
  }

  /* --------- Completitud del perfil (gate para descargar HV) ---------- */
  const PROFILE_REQUIRED = [
    { k: "photoUrl", label: "Foto de perfil" },
    { k: "name", label: "Nombre completo" },
    { k: "edad", label: "Edad" },
    { k: "city", label: "Ciudad" },
    { k: "phone", label: "WhatsApp" },
    { k: "situacion", label: "Situación" },
    { k: "institucion", label: "Institución" },
    { k: "bio", label: "Sobre mí" },
    { k: "role", label: "Título / rol" },
    { k: "desiredRoles", label: "Roles que buscas", arr: true },
    { k: "experience", label: "Experiencia", arr: true },
    { k: "education", label: "Educación", arr: true },
  ];
  function profileCompleteness(me) {
    const missing = [];
    PROFILE_REQUIRED.forEach((f) => {
      const v = me[f.k];
      const ok = f.arr ? (Array.isArray(v) && v.length > 0) : !!(v && String(v).trim());
      if (!ok) missing.push(f.label);
    });
    const done = PROFILE_REQUIRED.length - missing.length;
    return { pct: Math.round((done / PROFILE_REQUIRED.length) * 100), done, total: PROFILE_REQUIRED.length, missing, complete: missing.length === 0 };
  }

  // Tarjeta de datos personales editable EN LÍNEA (sin modal aparte).
  function PersonalInfoCard({ me, update }) {
    const [edit, setEdit] = useState(false);
    const [v, setV] = useState({});
    const set = (k) => (e) => setV((p) => ({ ...p, [k]: e.target.value }));
    function start() { setV({ name: me.name || "", role: me.role || "", edad: me.edad || "", city: me.city || "", phone: me.phone || "", situacion: me.situacion || "", institucion: me.institucion || "", comoNosEscucho: me.comoNosEscucho || "", bio: me.bio || "" }); setEdit(true); }
    function save() { update(v); setEdit(false); }
    const inp = { fontFamily: FONT, fontSize: 13, fontWeight: 600, color: C.ink, background: C.sand, border: `1px solid ${C.line2}`, borderRadius: 11, padding: "10px 11px", width: "100%", boxSizing: "border-box" };
    const cap = { fontSize: 10, fontWeight: 800, letterSpacing: "0.06em", color: C.mut4, marginBottom: 5, display: "block" };
    const rows = [["Nombre", me.name], ["Título / rol", me.role], ["Edad", me.edad], ["Ciudad", me.city], ["WhatsApp", me.phone], ["Situación", me.situacion], ["Institución", me.institucion], ["Cómo nos escuchó", me.comoNosEscucho]];
    return (
      <Card style={{ padding: 15 }}>
        <div style={{ display: "flex", alignItems: "center", justifyContent: "space-between", gap: 8 }}>
          <div style={{ display: "flex", alignItems: "center", gap: 8 }}><Icon name="user" size={17} color={C.ink} /><span style={{ fontSize: 14, fontWeight: 800, color: C.ink }}>Datos personales</span></div>
          {edit
            ? <div style={{ display: "flex", gap: 6 }}><button onClick={() => setEdit(false)} style={{ border: `1px solid ${C.line2}`, background: "#fff", color: C.mut3, fontFamily: FONT, fontSize: 12, fontWeight: 800, padding: "7px 12px", borderRadius: 999, cursor: "pointer" }}>Cancelar</button><button onClick={save} style={{ border: 0, background: C.ink, color: "#fff", fontFamily: FONT, fontSize: 12, fontWeight: 800, padding: "7px 14px", borderRadius: 999, cursor: "pointer" }}>Guardar</button></div>
            : <button onClick={start} style={{ border: `1px solid ${C.orangeSoftBd}`, background: C.orangeSoft, color: C.orangeDk, fontFamily: FONT, fontSize: 12, fontWeight: 800, padding: "7px 13px", borderRadius: 999, cursor: "pointer", display: "flex", alignItems: "center", gap: 6 }}><Icon name="test" size={13} color={C.orangeDk} />Editar</button>}
        </div>
        {edit ? (
          <div style={{ display: "grid", gridTemplateColumns: "repeat(auto-fit,minmax(150px,1fr))", gap: 10, marginTop: 14 }}>
            <div style={{ gridColumn: "1 / -1" }}><label style={cap}>NOMBRE COMPLETO</label><input value={v.name} onChange={set("name")} style={inp} /></div>
            <div><label style={cap}>TÍTULO / ROL ACTUAL</label><input value={v.role} onChange={set("role")} placeholder="Auxiliar de bodega" style={inp} /></div>
            <div><label style={cap}>EDAD</label><input value={v.edad} onChange={set("edad")} type="number" placeholder="24" style={inp} /></div>
            <div><label style={cap}>CIUDAD</label><input value={v.city} onChange={set("city")} placeholder="Medellín" style={inp} /></div>
            <div><label style={cap}>WHATSAPP</label><input value={v.phone} onChange={set("phone")} placeholder="+57 300 000 0000" style={inp} /></div>
            <div><label style={cap}>SITUACIÓN</label><select value={v.situacion} onChange={set("situacion")} style={{ ...inp, cursor: "pointer" }}><option value="">Selecciona…</option>{SITUACION_OPTS.map((o) => <option key={o} value={o}>{o}</option>)}</select></div>
            <div><label style={cap}>INSTITUCIÓN</label><input value={v.institucion} onChange={set("institucion")} placeholder="Universidad / SENA…" style={inp} /></div>
            <div style={{ gridColumn: "1 / -1" }}><label style={cap}>¿DE DÓNDE NOS ESCUCHASTE?</label><input value={v.comoNosEscucho} onChange={set("comoNosEscucho")} placeholder="LinkedIn, un amigo…" style={inp} /></div>
            <div style={{ gridColumn: "1 / -1" }}><label style={cap}>SOBRE MÍ</label><textarea value={v.bio} onChange={set("bio")} rows={3} placeholder="Cuéntale a las empresas quién eres y qué buscas…" style={{ ...inp, resize: "vertical" }} /></div>
          </div>
        ) : (
          <React.Fragment>
            <div style={{ display: "grid", gridTemplateColumns: "repeat(auto-fit,minmax(150px,1fr))", gap: "10px 14px", marginTop: 14 }}>
              {rows.map(([label, val]) => (
                <div key={label}>
                  <div style={{ fontSize: 10, fontWeight: 800, letterSpacing: "0.05em", color: C.mut4 }}>{label.toUpperCase()}</div>
                  {val ? <div style={{ fontSize: 13, fontWeight: 700, color: C.ink, marginTop: 2 }}>{val}</div> : <div style={{ fontSize: 12, fontWeight: 800, color: C.orange, marginTop: 2 }}>Falta ⚠️</div>}
                </div>
              ))}
            </div>
            <div style={{ marginTop: 12 }}>
              <div style={{ fontSize: 10, fontWeight: 800, letterSpacing: "0.05em", color: C.mut4 }}>SOBRE MÍ</div>
              {me.bio ? <div style={{ fontSize: 13, fontWeight: 600, color: "#3D3548", marginTop: 3, lineHeight: 1.5 }}>{me.bio}</div> : <div style={{ fontSize: 12, fontWeight: 800, color: C.orange, marginTop: 3 }}>Falta — cuéntale a las empresas quién eres ⚠️</div>}
            </div>
          </React.Fragment>
        )}
      </Card>
    );
  }

  // Editor de las secciones tipo CV: Experiencia, Educación, Idiomas, LinkedIn.
  function CvSectionsCard({ me, update }) {
    const [edit, setEdit] = useState(false);
    const [exp, setExp] = useState([]);
    const [edu, setEdu] = useState([]);
    const [langs, setLangs] = useState([]);
    const [linkedin, setLinkedin] = useState("");
    function start() {
      setExp((me.experience || []).map((e) => ({ cargo: e.cargo || "", empresa: e.empresa || "", periodo: e.periodo || "", ciudad: e.ciudad || "", bullets: (e.bullets || []).join("\n") })));
      setEdu((me.education || []).map((e) => ({ titulo: e.titulo || "", institucion: e.institucion || "", periodo: e.periodo || "" })));
      setLangs((me.languages && me.languages.length ? me.languages : [{ lang: "Español", level: "Nativo" }]).map((l) => ({ lang: l.lang || "", level: l.level || "" })));
      setLinkedin(me.linkedin || ""); setEdit(true);
    }
    function save() {
      update({
        experience: exp.filter((e) => e.cargo || e.empresa).map((e) => ({ cargo: e.cargo || "", empresa: e.empresa || "", periodo: e.periodo || "", ciudad: e.ciudad || "", bullets: String(e.bullets || "").split("\n").map((x) => x.trim()).filter(Boolean) })),
        education: edu.filter((e) => e.titulo || e.institucion).map((e) => ({ titulo: e.titulo || "", institucion: e.institucion || "", periodo: e.periodo || "" })),
        languages: langs.filter((l) => l.lang).map((l) => ({ lang: l.lang || "", level: l.level || "" })),
        linkedin,
      });
      setEdit(false);
    }
    const inp = { fontFamily: FONT, fontSize: 13, fontWeight: 600, color: C.ink, background: C.sand, border: `1px solid ${C.line2}`, borderRadius: 10, padding: "9px 11px", width: "100%", boxSizing: "border-box" };
    const cap = { fontSize: 10, fontWeight: 800, letterSpacing: "0.05em", color: C.mut4, marginBottom: 4, display: "block" };
    const setRow = (arr, setArr, i, k) => (e) => setArr(arr.map((x, j) => j === i ? { ...x, [k]: e.target.value } : x));
    const expList = me.experience || [], eduList = me.education || [], langList = me.languages || [];
    return (
      <Card style={{ padding: 15 }}>
        <div style={{ display: "flex", alignItems: "center", justifyContent: "space-between", gap: 8 }}>
          <div style={{ display: "flex", alignItems: "center", gap: 8 }}><Icon name="briefcase" size={17} color={C.ink} /><span style={{ fontSize: 14, fontWeight: 800, color: C.ink }}>Tu hoja de vida</span></div>
          {edit
            ? <div style={{ display: "flex", gap: 6 }}><button onClick={() => setEdit(false)} style={{ border: `1px solid ${C.line2}`, background: "#fff", color: C.mut3, fontFamily: FONT, fontSize: 12, fontWeight: 800, padding: "7px 12px", borderRadius: 999, cursor: "pointer" }}>Cancelar</button><button onClick={save} style={{ border: 0, background: C.ink, color: "#fff", fontFamily: FONT, fontSize: 12, fontWeight: 800, padding: "7px 14px", borderRadius: 999, cursor: "pointer" }}>Guardar</button></div>
            : <button onClick={start} style={{ border: `1px solid ${C.orangeSoftBd}`, background: C.orangeSoft, color: C.orangeDk, fontFamily: FONT, fontSize: 12, fontWeight: 800, padding: "7px 13px", borderRadius: 999, cursor: "pointer", display: "flex", alignItems: "center", gap: 6 }}><Icon name="test" size={13} color={C.orangeDk} />Editar</button>}
        </div>
        {edit ? (
          <div style={{ marginTop: 14, display: "flex", flexDirection: "column", gap: 16 }}>
            <div><label style={cap}>LINKEDIN (opcional)</label><input value={linkedin} onChange={(e) => setLinkedin(e.target.value)} placeholder="linkedin.com/in/tuperfil" style={inp} /></div>
            <div>
              <div style={{ fontSize: 12, fontWeight: 800, color: C.orangeDk, marginBottom: 8 }}>EXPERIENCIA</div>
              {exp.map((e, i) => (
                <div key={i} style={{ background: C.sand, borderRadius: 12, padding: 11, marginBottom: 8, display: "grid", gridTemplateColumns: "1fr 1fr", gap: 8, position: "relative" }}>
                  <button onClick={() => setExp(exp.filter((_, j) => j !== i))} style={{ position: "absolute", top: 6, right: 6, border: 0, background: "transparent", color: "#C0392B", fontWeight: 900, cursor: "pointer" }}>✕</button>
                  <div style={{ gridColumn: "1 / -1" }}><input value={e.cargo} onChange={setRow(exp, setExp, i, "cargo")} placeholder="Cargo" style={inp} /></div>
                  <input value={e.empresa} onChange={setRow(exp, setExp, i, "empresa")} placeholder="Empresa" style={inp} />
                  <input value={e.periodo} onChange={setRow(exp, setExp, i, "periodo")} placeholder="2022 — Presente" style={inp} />
                  <input value={e.ciudad} onChange={setRow(exp, setExp, i, "ciudad")} placeholder="Ciudad" style={{ ...inp, gridColumn: "1 / -1" }} />
                  <textarea value={e.bullets} onChange={setRow(exp, setExp, i, "bullets")} rows={2} placeholder="Un logro por línea (ej: aumenté ventas 20%)" style={{ ...inp, gridColumn: "1 / -1", resize: "vertical" }} />
                </div>
              ))}
              <button onClick={() => setExp(exp.concat({ cargo: "", empresa: "", periodo: "", ciudad: "", bullets: "" }))} style={{ border: `1px dashed ${C.line2}`, background: "#fff", color: C.mut3, fontFamily: FONT, fontSize: 12, fontWeight: 800, padding: "8px 12px", borderRadius: 10, cursor: "pointer" }}>+ Experiencia</button>
            </div>
            <div>
              <div style={{ fontSize: 12, fontWeight: 800, color: C.orangeDk, marginBottom: 8 }}>EDUCACIÓN</div>
              {edu.map((e, i) => (
                <div key={i} style={{ background: C.sand, borderRadius: 12, padding: 11, marginBottom: 8, display: "grid", gridTemplateColumns: "1fr 1fr", gap: 8, position: "relative" }}>
                  <button onClick={() => setEdu(edu.filter((_, j) => j !== i))} style={{ position: "absolute", top: 6, right: 6, border: 0, background: "transparent", color: "#C0392B", fontWeight: 900, cursor: "pointer" }}>✕</button>
                  <div style={{ gridColumn: "1 / -1" }}><input value={e.titulo} onChange={setRow(edu, setEdu, i, "titulo")} placeholder="Título / programa" style={inp} /></div>
                  <input value={e.institucion} onChange={setRow(edu, setEdu, i, "institucion")} placeholder="Institución" style={inp} />
                  <input value={e.periodo} onChange={setRow(edu, setEdu, i, "periodo")} placeholder="2017 — 2019" style={inp} />
                </div>
              ))}
              <button onClick={() => setEdu(edu.concat({ titulo: "", institucion: "", periodo: "" }))} style={{ border: `1px dashed ${C.line2}`, background: "#fff", color: C.mut3, fontFamily: FONT, fontSize: 12, fontWeight: 800, padding: "8px 12px", borderRadius: 10, cursor: "pointer" }}>+ Educación</button>
            </div>
            <div>
              <div style={{ fontSize: 12, fontWeight: 800, color: C.orangeDk, marginBottom: 8 }}>IDIOMAS</div>
              {langs.map((l, i) => (
                <div key={i} style={{ display: "flex", gap: 8, marginBottom: 8 }}>
                  <input value={l.lang} onChange={setRow(langs, setLangs, i, "lang")} placeholder="Idioma" style={{ ...inp, flex: 1 }} />
                  <input value={l.level} onChange={setRow(langs, setLangs, i, "level")} placeholder="Nivel" style={{ ...inp, flex: 1 }} />
                  <button onClick={() => setLangs(langs.filter((_, j) => j !== i))} style={{ border: `1px solid ${C.line2}`, background: "#fff", color: "#C0392B", borderRadius: 10, padding: "0 12px", fontWeight: 900, cursor: "pointer" }}>✕</button>
                </div>
              ))}
              <button onClick={() => setLangs(langs.concat({ lang: "", level: "" }))} style={{ border: `1px dashed ${C.line2}`, background: "#fff", color: C.mut3, fontFamily: FONT, fontSize: 12, fontWeight: 800, padding: "8px 12px", borderRadius: 10, cursor: "pointer" }}>+ Idioma</button>
            </div>
          </div>
        ) : (
          <div style={{ marginTop: 12, display: "flex", flexDirection: "column", gap: 12 }}>
            <div>
              <div style={{ fontSize: 11, fontWeight: 800, color: C.orangeDk, marginBottom: 5 }}>EXPERIENCIA</div>
              {expList.length ? expList.map((e, i) => <div key={i} style={{ marginBottom: 6 }}><div style={{ fontSize: 13, fontWeight: 800, color: C.ink }}>{e.cargo}</div><div style={{ fontSize: 11, fontWeight: 700, color: C.mut }}>{[e.empresa, e.periodo].filter(Boolean).join(" · ")}</div></div>) : <div style={{ fontSize: 12, fontWeight: 700, color: C.orange }}>Agrega tu experiencia ⚠️</div>}
            </div>
            <div>
              <div style={{ fontSize: 11, fontWeight: 800, color: C.orangeDk, marginBottom: 5 }}>EDUCACIÓN</div>
              {eduList.length ? eduList.map((e, i) => <div key={i} style={{ marginBottom: 6 }}><div style={{ fontSize: 13, fontWeight: 800, color: C.ink }}>{e.titulo}</div><div style={{ fontSize: 11, fontWeight: 700, color: C.mut }}>{[e.institucion, e.periodo].filter(Boolean).join(" · ")}</div></div>) : <div style={{ fontSize: 12, fontWeight: 700, color: C.orange }}>Agrega tu educación ⚠️</div>}
            </div>
            {langList.length > 0 && <div><div style={{ fontSize: 11, fontWeight: 800, color: C.orangeDk, marginBottom: 5 }}>IDIOMAS</div><div style={{ fontSize: 12, fontWeight: 600, color: C.mut2 }}>{langList.map((l) => l.lang + " — " + l.level).join(" · ")}</div></div>}
          </div>
        )}
      </Card>
    );
  }

  // Editor de perfil que SE VE como la hoja de vida (diseño Aurora del PDF),
  // para llenarla directo. Autoguarda con debounce.
  function CvEditor({ me, update, isAdmin }) {
    const [aiBusy, setAiBusy] = useState("");
    const [d, setD] = useState(() => ({
      name: me.name || "", role: me.role || "", edad: me.edad || "", city: me.city || "", phone: me.phone || "", email: me.email || "", linkedin: me.linkedin || "", bio: me.bio || "",
      desiredRoles: (me.desiredRoles || []).slice(),
      languages: (me.languages && me.languages.length ? me.languages : [{ lang: "Español", level: "Nativo" }]).map((l) => ({ lang: l.lang || "", level: l.level || "" })),
      experience: (me.experience || []).map((e) => ({ cargo: e.cargo || "", empresa: e.empresa || "", periodo: e.periodo || "", ciudad: e.ciudad || "", bullets: (e.bullets || []).join("\n") })),
      education: (me.education || []).map((e) => ({ titulo: e.titulo || "", institucion: e.institucion || "", periodo: e.periodo || "" })),
    }));
    const timer = useRef(null);
    const [saved, setSaved] = useState(true);
    const [newSkill, setNewSkill] = useState("");
    const [uploading, setUploading] = useState(false);
    const fileRef = useRef(null);
    // Sellos de validación O-Lab (solo el admin los otorga; todos los ven).
    const [vals, setVals] = useState(Array.isArray(me.validations) ? me.validations : []);
    const [valType, setValType] = useState("competencia");
    const [valName, setValName] = useState("");
    const isVal = (type, name) => vals.some((v) => v.type === type && String(v.name || "").toLowerCase() === String(name || "").toLowerCase());
    function addVal(type, name) {
      name = String(name || "").trim(); if (!name || isVal(type, name)) return;
      const next = vals.concat({ type, name, at: Date.now(), by: "o-lab.ai" });
      setVals(next); update({ validations: next });
    }
    function removeVal(i) { const next = vals.filter((_, j) => j !== i); setVals(next); update({ validations: next }); }
    function persist(next) {
      setSaved(false); clearTimeout(timer.current);
      timer.current = setTimeout(() => {
        update({
          name: next.name, role: next.role, edad: next.edad, city: next.city, phone: next.phone, linkedin: next.linkedin, bio: next.bio,
          desiredRoles: next.desiredRoles.filter(Boolean),
          languages: next.languages.filter((l) => l.lang).map((l) => ({ lang: l.lang, level: l.level || "" })),
          experience: next.experience.filter((e) => e.cargo || e.empresa).map((e) => ({ cargo: e.cargo || "", empresa: e.empresa || "", periodo: e.periodo || "", ciudad: e.ciudad || "", bullets: String(e.bullets || "").split("\n").map((x) => x.trim()).filter(Boolean) })),
          education: next.education.filter((e) => e.titulo || e.institucion).map((e) => ({ titulo: e.titulo || "", institucion: e.institucion || "", periodo: e.periodo || "" })),
        });
        setSaved(true);
      }, 700);
    }
    function upd(patch) { setD((prev) => { const next = { ...prev, ...patch }; persist(next); return next; }); }
    const setArr = (key, i, k) => (e) => upd({ [key]: d[key].map((x, j) => j === i ? { ...x, [k]: e.target.value } : x) });
    async function onPhoto(e) { const f = e.target.files && e.target.files[0]; e.target.value = ""; if (!f) return; setUploading(true); try { const url = await B().uploadPhoto(f); update({ photoUrl: url }); } catch (err) { console.warn(err); } finally { setUploading(false); } }
    async function improveAI(field, value, apply) {
      if (!String(value || "").trim()) return; setAiBusy(field);
      try { const t = await B().improveTextAI(value, field === "bio" ? "Perfil profesional de hoja de vida" : "Logros de experiencia laboral (uno por línea)"); if (t) apply(t); } catch (e) { console.warn(e); } finally { setAiBusy(""); }
    }
    const aiBtn = { border: 0, background: "linear-gradient(135deg,#6C4CF1,#12C2E9)", color: "#fff", fontFamily: FONT, fontSize: 11, fontWeight: 800, padding: "5px 10px", borderRadius: 999, cursor: "pointer", display: "inline-flex", alignItems: "center", gap: 5 };
    // Inputs que se ven como texto de CV (sin caja).
    const sIn = { background: "transparent", border: 0, borderBottom: "1px solid rgba(255,255,255,0.16)", color: "#fff", fontFamily: FONT, width: "100%", padding: "3px 0", outline: "none", boxSizing: "border-box" };
    const mIn = { background: "transparent", border: 0, borderBottom: "1px solid #EDE6DD", color: C.ink, fontFamily: FONT, width: "100%", padding: "4px 0", outline: "none", boxSizing: "border-box" };
    const sh = { color: C.orange, fontSize: 11, fontWeight: 900, letterSpacing: "0.12em", margin: "20px 0 8px" };
    const h2 = { color: C.orange, fontSize: 13, fontWeight: 900, letterSpacing: "0.08em", textTransform: "uppercase", margin: "22px 0 10px" };
    const addBtn = { border: `1px dashed ${C.line2}`, background: "#fff", color: C.mut3, fontFamily: FONT, fontSize: 12, fontWeight: 800, padding: "7px 12px", borderRadius: 9, cursor: "pointer", marginTop: 6 };
    const delX = { border: 0, background: "transparent", color: "#C0392B", fontWeight: 900, cursor: "pointer", fontSize: 13 };
    return (
      <Card style={{ padding: 0, overflow: "hidden" }}>
        <div style={{ padding: "11px 15px", background: C.sand, borderBottom: `1px solid ${C.line}`, display: "flex", justifyContent: "space-between", alignItems: "center", gap: 8, flexWrap: "wrap" }}>
          <span style={{ fontSize: 13, fontWeight: 800, color: C.ink }}>📄 Tu hoja de vida — llénala aquí (así se descarga)</span>
          <span style={{ fontSize: 11, fontWeight: 800, color: saved ? C.green : C.orange }}>{saved ? "Guardado ✓" : "Guardando…"}</span>
        </div>
        <div style={{ display: "flex", flexWrap: "wrap" }}>
          {/* SIDEBAR oscuro */}
          <div style={{ flex: "1 1 260px", minWidth: 0, background: "#17131F", color: "#fff", padding: "24px 20px" }}>
            <div style={{ position: "relative", width: 108, height: 108, margin: "0 auto 16px" }}>
              {me.photoUrl ? <img src={me.photoUrl} alt="" style={{ width: "100%", height: "100%", borderRadius: 999, objectFit: "cover", border: "3px solid rgba(255,255,255,0.15)" }} /> : <div style={{ width: "100%", height: "100%", borderRadius: 999, background: "linear-gradient(135deg,#FF6600,#FF3D7F)", display: "flex", alignItems: "center", justifyContent: "center", fontSize: 40, fontWeight: 900, color: "#fff" }}>{(d.name || "?").slice(0, 1).toUpperCase()}</div>}
              <button onClick={() => fileRef.current && fileRef.current.click()} title="Subir foto" style={{ position: "absolute", right: 0, bottom: 2, width: 32, height: 32, borderRadius: 999, background: C.lime, border: "3px solid #17131F", cursor: "pointer", color: C.ink, display: "flex", alignItems: "center", justifyContent: "center" }}>{uploading ? "…" : <Icon name="plus" size={16} color={C.ink} />}</button>
              <input ref={fileRef} type="file" accept="image/*" onChange={onPhoto} style={{ display: "none" }} />
            </div>
            <input value={d.name} onChange={(e) => upd({ name: e.target.value })} placeholder="Nombre Apellido" style={{ ...sIn, fontSize: 21, fontWeight: 900, borderBottom: 0, textAlign: "center" }} />
            <input value={d.role} onChange={(e) => upd({ role: e.target.value })} placeholder="Tu cargo o profesión" style={{ ...sIn, color: C.orange, fontWeight: 800, fontSize: 13, borderBottom: 0, textAlign: "center" }} />
            <div style={sh}>CONTACTO</div>
            {[["EMAIL", "email", "tucorreo@email.com"], ["TELÉFONO", "phone", "+57 300 000 0000"], ["CIUDAD", "city", "Ciudad, País"], ["EDAD", "edad", "Edad"], ["LINKEDIN", "linkedin", "linkedin.com/in/…"]].map(([lb, k, ph]) => (
              <div key={k} style={{ marginBottom: 9 }}>
                <div style={{ fontSize: 9, fontWeight: 800, letterSpacing: "0.06em", color: "rgba(255,255,255,0.45)" }}>{lb}</div>
                <input value={d[k]} onChange={(e) => upd({ [k]: e.target.value })} placeholder={ph} readOnly={k === "email"} style={{ ...sIn, fontSize: 12.5, fontWeight: 600, opacity: k === "email" ? 0.7 : 1 }} />
              </div>
            ))}
            <div style={sh}>SKILLS</div>
            <div style={{ display: "flex", flexWrap: "wrap", gap: 6, marginBottom: 8 }}>
              {d.desiredRoles.map((r, i) => { const ok = isVal("competencia", r); return <span key={i} title={ok ? "Validado por O-Lab" : ""} style={{ fontSize: 11, fontWeight: 700, color: "#fff", background: ok ? "rgba(198,242,78,0.16)" : "rgba(255,255,255,0.1)", border: ok ? "1px solid rgba(198,242,78,0.5)" : "1px solid rgba(255,255,255,0.18)", borderRadius: 999, padding: "4px 9px", display: "inline-flex", alignItems: "center", gap: 5 }}>{ok && <span style={{ color: C.lime }}>✓</span>}{r}<span onClick={() => upd({ desiredRoles: d.desiredRoles.filter((_, j) => j !== i) })} style={{ cursor: "pointer", color: "rgba(255,255,255,0.6)" }}>✕</span></span>; })}
            </div>
            <input value={newSkill} onChange={(e) => setNewSkill(e.target.value)} onKeyDown={(e) => { if (e.key === "Enter" && newSkill.trim()) { upd({ desiredRoles: d.desiredRoles.concat(newSkill.trim()) }); setNewSkill(""); } }} placeholder="Escribe y Enter para agregar…" style={{ ...sIn, fontSize: 12 }} />
            <div style={sh}>IDIOMAS</div>
            {d.languages.map((l, i) => (
              <div key={i} style={{ display: "flex", gap: 6, alignItems: "center", marginBottom: 6 }}>
                <input value={l.lang} onChange={setArr("languages", i, "lang")} placeholder="Idioma" style={{ ...sIn, flex: 1 }} />
                <span style={{ color: "rgba(255,255,255,0.4)" }}>—</span>
                <input value={l.level} onChange={setArr("languages", i, "level")} placeholder="Nivel" style={{ ...sIn, flex: 1 }} />
                <span onClick={() => upd({ languages: d.languages.filter((_, j) => j !== i) })} style={{ cursor: "pointer", color: "rgba(255,255,255,0.5)", fontSize: 12 }}>✕</span>
              </div>
            ))}
            <button onClick={() => upd({ languages: d.languages.concat({ lang: "", level: "" }) })} style={{ border: "1px dashed rgba(255,255,255,0.25)", background: "transparent", color: "rgba(255,255,255,0.7)", fontFamily: FONT, fontSize: 11.5, fontWeight: 800, padding: "6px 10px", borderRadius: 9, cursor: "pointer", marginTop: 4 }}>+ Idioma</button>
          </div>
          {/* MAIN blanco */}
          <div style={{ flex: "2 1 320px", minWidth: 0, padding: "24px 22px" }}>
            <div style={{ display: "flex", alignItems: "center", justifyContent: "space-between", gap: 8 }}>
              <div style={h2}>Perfil</div>
              {isAdmin && <button onClick={() => improveAI("bio", d.bio, (t) => upd({ bio: t }))} disabled={aiBusy === "bio"} style={{ ...aiBtn, opacity: aiBusy === "bio" ? 0.6 : 1 }}>✨ {aiBusy === "bio" ? "Mejorando…" : "Mejorar con IA"}</button>}
            </div>
            <textarea value={d.bio} onChange={(e) => upd({ bio: e.target.value })} rows={3} placeholder="Escribe un resumen profesional de 2-3 frases: quién eres, tu experiencia y el valor que aportas." style={{ ...mIn, borderBottom: 0, resize: "vertical", fontSize: 13.5, lineHeight: 1.6, color: "#3D3548" }} />
            <div style={h2}>Experiencia</div>
            {d.experience.map((e, i) => (
              <div key={i} style={{ marginBottom: 14, paddingBottom: 12, borderBottom: `1px solid ${C.line}`, position: "relative" }}>
                <button onClick={() => upd({ experience: d.experience.filter((_, j) => j !== i) })} style={{ ...delX, position: "absolute", right: 0, top: 0 }}>✕</button>
                <input value={e.cargo} onChange={setArr("experience", i, "cargo")} placeholder="Cargo más reciente" style={{ ...mIn, fontSize: 15, fontWeight: 800, borderBottom: 0 }} />
                <div style={{ display: "flex", gap: 8, flexWrap: "wrap" }}>
                  <input value={e.empresa} onChange={setArr("experience", i, "empresa")} placeholder="Empresa" style={{ ...mIn, flex: 1, minWidth: 90, color: C.orange, fontWeight: 700, fontSize: 12.5 }} />
                  <input value={e.periodo} onChange={setArr("experience", i, "periodo")} placeholder="2022 — Presente" style={{ ...mIn, flex: 1, minWidth: 90, color: C.orange, fontWeight: 700, fontSize: 12.5 }} />
                  <input value={e.ciudad} onChange={setArr("experience", i, "ciudad")} placeholder="Ciudad" style={{ ...mIn, flex: 1, minWidth: 70, color: C.orange, fontWeight: 700, fontSize: 12.5 }} />
                </div>
                <textarea value={e.bullets} onChange={setArr("experience", i, "bullets")} rows={2} placeholder="Un logro por línea (ej: aumenté las ventas 20%)" style={{ ...mIn, borderBottom: 0, resize: "vertical", fontSize: 12.5, lineHeight: 1.5, color: "#3D3548", marginTop: 6 }} />
                {isAdmin && <button onClick={() => improveAI("exp" + i, e.bullets, (t) => upd({ experience: d.experience.map((x, j) => j === i ? { ...x, bullets: t } : x) }))} disabled={aiBusy === "exp" + i} style={{ ...aiBtn, marginTop: 4, opacity: aiBusy === "exp" + i ? 0.6 : 1 }}>✨ {aiBusy === "exp" + i ? "Mejorando…" : "Mejorar logros con IA"}</button>}
              </div>
            ))}
            <button onClick={() => upd({ experience: d.experience.concat({ cargo: "", empresa: "", periodo: "", ciudad: "", bullets: "" }) })} style={addBtn}>+ Experiencia</button>
            <div style={h2}>Educación</div>
            {d.education.map((e, i) => (
              <div key={i} style={{ marginBottom: 12, position: "relative" }}>
                <button onClick={() => upd({ education: d.education.filter((_, j) => j !== i) })} style={{ ...delX, position: "absolute", right: 0, top: 0 }}>✕</button>
                <input value={e.titulo} onChange={setArr("education", i, "titulo")} placeholder="Título / Programa de estudios" style={{ ...mIn, fontSize: 14, fontWeight: 800, borderBottom: 0 }} />
                <div style={{ display: "flex", gap: 8 }}>
                  <input value={e.institucion} onChange={setArr("education", i, "institucion")} placeholder="Institución educativa" style={{ ...mIn, flex: 2, color: C.mut2, fontWeight: 600, fontSize: 12.5 }} />
                  <input value={e.periodo} onChange={setArr("education", i, "periodo")} placeholder="2017 — 2019" style={{ ...mIn, flex: 1, color: C.mut2, fontWeight: 600, fontSize: 12.5 }} />
                </div>
              </div>
            ))}
            <button onClick={() => upd({ education: d.education.concat({ titulo: "", institucion: "", periodo: "" }) })} style={addBtn}>+ Educación</button>

            {isAdmin && (() => {
              const VT = { competencia: ["Competencia", "🎯", C.violet], lab: ["Lab", "🧪", C.orange], test: ["Test", "📝", C.cyanDk], evento: ["Evento", "🎟️", C.green] };
              const labNames = Object.values(me.myLabs || {}).map((l) => (l && (l.title || l.name)) || "").filter(Boolean);
              const testNames = Object.values(me.testResults || {}).map((t) => (t && (t.title || t.name)) || "").filter(Boolean);
              const sugg = [].concat(
                (d.desiredRoles || []).map((n) => ["competencia", n]),
                labNames.map((n) => ["lab", n]),
                testNames.map((n) => ["test", n]),
              ).filter(([t, n]) => n && !isVal(t, n));
              return (
                <div style={{ marginTop: 26, borderTop: `2px solid ${C.line}`, paddingTop: 18 }}>
                  <div style={{ display: "flex", alignItems: "center", gap: 8 }}>
                    <span style={{ fontSize: 18 }}>🏅</span>
                    <div>
                      <div style={{ fontSize: 13, fontWeight: 900, color: C.ink }}>Validación O-Lab</div>
                      <div style={{ fontSize: 11, fontWeight: 600, color: C.mut2 }}>Como validadores, certifica los labs, tests, eventos y competencias que esta persona sí completó. Cada sello es oficial de O-Lab.</div>
                    </div>
                  </div>

                  {vals.length > 0 && (
                    <div style={{ display: "flex", flexWrap: "wrap", gap: 8, marginTop: 12 }}>
                      {vals.map((v, i) => { const m = VT[v.type] || ["Ítem", "✓", C.mut3]; return (
                        <span key={i} style={{ display: "inline-flex", alignItems: "center", gap: 6, fontSize: 12, fontWeight: 800, color: C.ink, background: "linear-gradient(135deg,#FFF6E9,#F3FBE3)", border: `1.5px solid ${C.lime}`, borderRadius: 999, padding: "5px 10px 5px 8px" }}>
                          <span style={{ fontSize: 13 }}>{m[1]}</span>{v.name}
                          <span style={{ fontSize: 9, fontWeight: 900, color: "#0B7A3B", background: "rgba(198,242,78,0.55)", borderRadius: 999, padding: "1px 6px", letterSpacing: "0.03em" }}>✓ o-lab.ai</span>
                          <span onClick={() => removeVal(i)} title="Quitar sello" style={{ cursor: "pointer", color: C.mut3, fontWeight: 900 }}>✕</span>
                        </span>
                      ); })}
                    </div>
                  )}

                  {sugg.length > 0 && (
                    <div style={{ marginTop: 12 }}>
                      <div style={{ fontSize: 10, fontWeight: 800, letterSpacing: "0.06em", color: C.mut4, marginBottom: 6 }}>TOCA PARA VALIDAR (de su actividad)</div>
                      <div style={{ display: "flex", flexWrap: "wrap", gap: 6 }}>
                        {sugg.map(([t, n], i) => { const m = VT[t]; return (
                          <button key={t + n + i} onClick={() => addVal(t, n)} style={{ display: "inline-flex", alignItems: "center", gap: 5, border: `1px dashed ${m[2]}`, background: "#fff", color: C.ink, fontFamily: FONT, fontSize: 11.5, fontWeight: 700, padding: "5px 10px", borderRadius: 999, cursor: "pointer" }}>{m[1]} {n} <span style={{ color: m[2], fontWeight: 900 }}>+ Validar</span></button>
                        ); })}
                      </div>
                    </div>
                  )}

                  <div style={{ marginTop: 12 }}>
                    <div style={{ fontSize: 10, fontWeight: 800, letterSpacing: "0.06em", color: C.mut4, marginBottom: 6 }}>VALIDAR MANUALMENTE</div>
                    <div style={{ display: "flex", gap: 6, flexWrap: "wrap", alignItems: "center" }}>
                      <select value={valType} onChange={(e) => setValType(e.target.value)} style={{ fontFamily: FONT, fontSize: 12.5, fontWeight: 700, color: C.ink, background: C.sand, border: `1px solid ${C.line2}`, borderRadius: 10, padding: "8px 10px" }}>
                        {Object.keys(VT).map((k) => <option key={k} value={k}>{VT[k][1]} {VT[k][0]}</option>)}
                      </select>
                      <input value={valName} onChange={(e) => setValName(e.target.value)} onKeyDown={(e) => { if (e.key === "Enter") { addVal(valType, valName); setValName(""); } }} placeholder="Nombre (ej: Lab de Excel, Liderazgo…)" style={{ flex: 1, minWidth: 140, boxSizing: "border-box", fontFamily: FONT, fontSize: 12.5, fontWeight: 600, color: C.ink, background: C.sand, border: `1px solid ${C.line2}`, borderRadius: 10, padding: "8px 11px" }} />
                      <button onClick={() => { addVal(valType, valName); setValName(""); }} style={{ border: 0, background: "linear-gradient(135deg,#FF6600,#FF3D7F)", color: "#fff", fontFamily: FONT, fontSize: 12.5, fontWeight: 800, padding: "8px 14px", borderRadius: 10, cursor: "pointer" }}>Validar 🏅</button>
                    </div>
                  </div>
                </div>
              );
            })()}
          </div>
        </div>
      </Card>
    );
  }

  /* ----------------------------- PERFIL ----------------------------- */
  function Perfil({ me, update, openRedirect, goVacantes, isAdmin }) {
    const [uploading, setUploading] = useState(false);
    const fileRef = useRef(null);
    const roleOpts = SEED().rolesBuscados;
    const desired = me.desiredRoles || [];
    const photo = me.photoUrl || "";
    const comp = profileCompleteness(me);
    const xpPct = Math.max(4, Math.min(100, Math.round(((Number(me.xp) || 0) / (Number(me.xpMax) || 1000)) * 100)));
    const toggleRole = (r) => update({ desiredRoles: desired.includes(r) ? desired.filter((x) => x !== r) : desired.concat(r) });
    async function onPhoto(e) {
      const f = e.target.files && e.target.files[0]; e.target.value = "";
      if (!f) return; setUploading(true);
      try { const url = await B().uploadPhoto(f); update({ photoUrl: url }); } catch (err) { console.warn(err); }
      finally { setUploading(false); }
    }
    const myLabs = Object.values(me.myLabs || {});
    const testsList = Object.values(me.testResults || {});
    const applied = Object.values(me.appliedJobs || {}).sort((a, b) => (b.at || 0) - (a.at || 0));
    return (
      <div style={{ display: "flex", flexDirection: "column", gap: 16 }}>
        <div style={{ borderRadius: 24, padding: "20px 18px", background: C.heroGrad, display: "flex", gap: 14, alignItems: "center" }}>
          <div style={{ position: "relative", flexShrink: 0 }}>
            <div style={{ width: 112, height: 112, borderRadius: 999, padding: 4, boxSizing: "border-box", background: `conic-gradient(#C6F24E 0turn ${xpPct / 100}turn, rgba(255,255,255,0.16) ${xpPct / 100}turn 1turn)`, display: "flex", alignItems: "center", justifyContent: "center", boxShadow: "0 0 26px rgba(198,242,78,0.35)" }}>
              <div style={{ width: "100%", height: "100%", borderRadius: 999, padding: 3, boxSizing: "border-box", background: C.heroGrad }}>
                {photo
                  ? <img src={photo} alt="foto" style={{ width: "100%", height: "100%", borderRadius: 999, objectFit: "cover" }} />
                  : <Avatar name={me.name} bg="linear-gradient(135deg,#FF6600,#FF3D7F)" size={92} style={{ width: "100%", height: "100%" }} />}
              </div>
            </div>
            <button onClick={() => fileRef.current && fileRef.current.click()} title="Subir foto" style={{ position: "absolute", right: 0, bottom: 2, width: 34, height: 34, borderRadius: 999, background: C.lime, border: "3px solid #17131F", display: "flex", alignItems: "center", justifyContent: "center", cursor: "pointer", color: C.ink }}>{uploading ? <span style={{ fontSize: 13, fontWeight: 900 }}>…</span> : <Icon name="plus" size={17} color={C.ink} />}</button>
            <input ref={fileRef} type="file" accept="image/*" onChange={onPhoto} style={{ display: "none" }} />
          </div>
          <div style={{ flex: 1, minWidth: 0 }}>
            <div style={{ display: "flex", alignItems: "center", gap: 8, flexWrap: "wrap" }}>
              <div style={{ fontSize: 20, fontWeight: 900, color: "#fff", letterSpacing: "-0.01em" }}>{me.name || "Tu nombre"}</div>
              <span style={{ display: "flex", alignItems: "center", gap: 4, fontSize: 11, fontWeight: 900, color: C.ink, background: C.lime, padding: "3px 9px", borderRadius: 999, boxShadow: "0 0 14px rgba(198,242,78,0.5)" }}><Icon name="star" size={12} color={C.ink} />NV. {me.level}</span>
            </div>
            <div style={{ fontSize: 12, fontWeight: 600, color: "rgba(255,255,255,0.62)", marginTop: 3 }}>{me.role || "Agrega tu rol"}{me.city ? " · " + me.city : ""}</div>
            <div style={{ marginTop: 10 }}>
              <div style={{ position: "relative", height: 10, borderRadius: 999, background: "rgba(255,255,255,0.14)", overflow: "hidden" }}>
                <div style={{ width: xpPct + "%", height: "100%", borderRadius: 999, background: "linear-gradient(90deg,#FF6600,#FFB347,#C6F24E)", backgroundSize: "200% 100%", animation: "olabShine 3s linear infinite" }} />
              </div>
              <div style={{ display: "flex", justifyContent: "space-between", marginTop: 5 }}>
                <span style={{ fontSize: 10, fontWeight: 800, color: "rgba(255,255,255,0.75)" }}>{me.xp}/{me.xpMax} XP</span>
                <span style={{ display: "flex", alignItems: "center", gap: 8 }}>
                  <span style={{ fontSize: 10, fontWeight: 800, color: C.orange }}>🔥 {me.streak}</span>
                  <span style={{ display: "flex", alignItems: "center", gap: 3, fontSize: 10, fontWeight: 800, color: C.lime }}><Icon name="coin" size={11} color={C.lime} />{(me.coins || 0).toLocaleString("es-CO")}</span>
                </span>
              </div>
            </div>
          </div>
        </div>

        <div style={{ display: "grid", gridTemplateColumns: "repeat(4,1fr)", gap: 8 }}>
          {[["LABS", me.labs, C.orange], ["TESTS", me.tests, C.violet], ["INSIGNIAS", me.badges, C.green], ["RANKING", "#" + me.rank, C.cyan]].map(([lab, val, col]) => (
            <Card key={lab} style={{ padding: "11px 8px", textAlign: "center", borderRadius: 16 }}>
              <div style={{ fontSize: 17, fontWeight: 800, color: col }}>{val}</div>
              <div style={{ fontSize: 9, fontWeight: 800, color: C.mut, letterSpacing: "0.04em", marginTop: 2 }}>{lab}</div>
            </Card>
          ))}
        </div>

        {/* Completitud del perfil + descarga de HV (gate grande y llamativo) */}
        <Card style={{ padding: 18, border: comp.complete ? `2px solid ${C.greenBd}` : `2px solid ${C.orangeSoftBd}`, background: comp.complete ? "linear-gradient(160deg,#EFFCF5,#FFFFFF)" : "linear-gradient(160deg,#FFF3E7,#FFFFFF)" }}>
          <div style={{ display: "flex", alignItems: "center", gap: 14 }}>
            <div style={{ width: 62, height: 62, borderRadius: 999, flexShrink: 0, background: `conic-gradient(${comp.complete ? C.green : C.orange} ${comp.pct}%, #EDE6DD 0)`, display: "flex", alignItems: "center", justifyContent: "center" }}>
              <div style={{ width: 47, height: 47, borderRadius: 999, background: "#fff", display: "flex", alignItems: "center", justifyContent: "center", fontSize: 15, fontWeight: 900, color: comp.complete ? C.green : C.orangeDk }}>{comp.pct}%</div>
            </div>
            <div style={{ flex: 1, minWidth: 0 }}>
              <div style={{ fontSize: 17, fontWeight: 900, color: C.ink, letterSpacing: "-0.01em" }}>{comp.complete ? "Tu hoja de vida está lista 🎉" : "Completa tu perfil para generar tu CV"}</div>
              <div style={{ fontSize: 12.5, fontWeight: 600, color: C.mut2, marginTop: 3, lineHeight: 1.45 }}>{comp.complete ? "Descárgala cuando quieras. Mantén tu perfil actualizado y tu CV se genera con la info nueva." : "Con tu perfil O-Lab generas una hoja de vida descargable. Llena y actualiza toda tu info para desbloquearla."}</div>
            </div>
          </div>
          <div style={{ marginTop: 13 }}><Bar pct={comp.pct + "%"} color={comp.complete ? C.green : C.orange} track={C.sand2} h={9} shine={!comp.complete} /></div>
          {!comp.complete && (
            <div style={{ display: "flex", flexWrap: "wrap", gap: 6, marginTop: 11 }}>
              {comp.missing.map((m) => <span key={m} style={{ fontSize: 11, fontWeight: 800, color: C.orangeDk, background: C.orangeSoft, border: `1px solid ${C.orangeSoftBd}`, padding: "5px 10px", borderRadius: 999 }}>Falta: {m}</span>)}
            </div>
          )}
          <button onClick={() => comp.complete && downloadCV(me)} disabled={!comp.complete}
            style={comp.complete ? {
              width: "100%", marginTop: 16, border: 0, background: "linear-gradient(135deg,#FF6600,#FF3D7F)", color: "#fff", fontFamily: FONT, fontSize: 17, fontWeight: 900, padding: "17px 0", borderRadius: 16, cursor: "pointer", letterSpacing: "0.01em", display: "flex", alignItems: "center", justifyContent: "center", gap: 10, boxShadow: "0 12px 26px rgba(255,102,0,0.34)", animation: "olabPulse 2.4s ease-in-out infinite" }
            : {
              width: "100%", marginTop: 16, border: `2px dashed ${C.orangeSoftBd}`, background: "#fff", color: C.orangeDk, fontFamily: FONT, fontSize: 15, fontWeight: 900, padding: "16px 0", borderRadius: 16, cursor: "not-allowed", display: "flex", alignItems: "center", justifyContent: "center", gap: 9 }}>
            {comp.complete
              ? <React.Fragment><Icon name="external" size={20} color="#fff" />Descargar mi hoja de vida (PDF)</React.Fragment>
              : <React.Fragment><span style={{ fontSize: 17 }}>🔒</span>Completa el {comp.pct}% restante para descargar</React.Fragment>}
          </button>
        </Card>

        <CvEditor me={me} update={update} isAdmin={isAdmin} />

        {(() => {
          const vs = Array.isArray(me.validations) ? me.validations : [];
          if (!vs.length) return null;
          const VT = { competencia: ["Competencia", "🎯"], lab: ["Lab", "🧪"], test: ["Test", "📝"], evento: ["Evento", "🎟️"] };
          return (
            <Card style={{ padding: 16, border: `2px solid ${C.lime}`, background: "linear-gradient(160deg,#F6FBEA,#FFFFFF)" }}>
              <div style={{ display: "flex", alignItems: "center", gap: 8 }}>
                <span style={{ fontSize: 20 }}>🏅</span>
                <div>
                  <div style={{ fontSize: 15, fontWeight: 900, color: C.ink }}>Validado por O-Lab</div>
                  <div style={{ fontSize: 11.5, fontWeight: 600, color: C.mut2 }}>Logros certificados oficialmente por el equipo O-Lab.</div>
                </div>
              </div>
              <div style={{ display: "flex", flexWrap: "wrap", gap: 8, marginTop: 13 }}>
                {vs.map((v, i) => { const m = VT[v.type] || ["Ítem", "✓"]; return (
                  <span key={i} title={"Validado por O-Lab" + (v.at ? " · " + new Date(v.at).toLocaleDateString("es-CO") : "")} style={{ display: "inline-flex", alignItems: "center", gap: 6, fontSize: 12.5, fontWeight: 800, color: C.ink, background: "#fff", border: `1.5px solid ${C.lime}`, borderRadius: 999, padding: "6px 11px 6px 9px", boxShadow: "0 3px 10px -6px rgba(198,242,78,0.9)" }}>
                    <span style={{ fontSize: 14 }}>{m[1]}</span>{v.name}
                    <span style={{ fontSize: 8.5, fontWeight: 900, color: "#0B7A3B", background: "rgba(198,242,78,0.5)", borderRadius: 999, padding: "2px 6px", letterSpacing: "0.03em" }}>✓ o-lab.ai</span>
                  </span>
                ); })}
              </div>
            </Card>
          );
        })()}

        <Card style={{ padding: 15 }}>
          <div style={{ display: "flex", alignItems: "baseline", justifyContent: "space-between" }}>
            <span style={{ fontSize: 14, fontWeight: 800, color: C.ink }}>Vacantes aplicadas</span>
            <span style={{ fontSize: 11, fontWeight: 700, color: C.mut }}>{applied.length}</span>
          </div>
          {applied.length === 0 ? (
            <div style={{ display: "flex", alignItems: "center", justifyContent: "space-between", gap: 10, marginTop: 12, background: C.sand, borderRadius: 12, padding: "12px 14px" }}>
              <span style={{ fontSize: 12, fontWeight: 600, color: C.mut2 }}>Aún no te has postulado a ninguna vacante.</span>
              <Btn onClick={goVacantes} style={{ padding: "8px 12px", fontSize: 12 }}>Ver vacantes</Btn>
            </div>
          ) : (
            <div style={{ display: "flex", flexDirection: "column", gap: 8, marginTop: 12 }}>
              {applied.map((a, i) => (
                <div key={i} style={{ display: "flex", alignItems: "center", gap: 10, background: C.sand, borderRadius: 12, padding: "10px 12px" }}>
                  <Icon name="briefcase" color={C.mut3} size={16} />
                  <div style={{ flex: 1, minWidth: 0 }}>
                    <div style={{ fontSize: 12, fontWeight: 800, color: C.ink }}>{a.title}</div>
                    <div style={{ fontSize: 10, fontWeight: 700, color: C.mut }}>{a.company}{a.match ? " · match " + a.match : ""}</div>
                  </div>
                  <Pill color={C.violetDk} bg={C.violetSoft} style={{ fontSize: 10 }}>{a.status || "Postulada"}</Pill>
                </div>
              ))}
            </div>
          )}
        </Card>

        <Card style={{ padding: 15 }}>
          <div style={{ fontSize: 14, fontWeight: 800, color: C.ink }}>Competencias validadas</div>
          <div style={{ display: "flex", flexDirection: "column", gap: 12, marginTop: 13 }}>
            {me.skills.map((s) => (
              <div key={s.name}>
                <div style={{ display: "flex", justifyContent: "space-between", alignItems: "baseline" }}>
                  <span style={{ fontSize: 12, fontWeight: 700, color: "#3D3548" }}>{s.name}</span>
                  <span style={{ fontSize: 11, fontWeight: 800, color: s.color }}>{s.pct}</span>
                </div>
                <div style={{ marginTop: 6 }}><Bar pct={s.pct} color={s.color} h={8} /></div>
              </div>
            ))}
          </div>
        </Card>

        {myLabs.length > 0 && (
          <Card style={{ padding: 15 }}>
            <div style={{ display: "flex", alignItems: "baseline", justifyContent: "space-between" }}>
              <span style={{ fontSize: 14, fontWeight: 800, color: C.ink }}>Mis Labs</span>
              <span style={{ fontSize: 11, fontWeight: 700, color: C.mut }}>{myLabs.filter((l) => l.status === "completado").length} completados</span>
            </div>
            <div style={{ display: "flex", flexDirection: "column", gap: 8, marginTop: 12 }}>
              {myLabs.map((l, i) => (
                <div key={i} style={{ display: "flex", alignItems: "center", gap: 10, background: C.sand, borderRadius: 12, padding: "9px 12px" }}>
                  <Icon name="flask" color={C.violet} size={16} />
                  <div style={{ flex: 1, minWidth: 0 }}>
                    <div style={{ fontSize: 12, fontWeight: 800, color: C.ink }}>{l.title}</div>
                    <div style={{ fontSize: 10, fontWeight: 700, color: C.mut }}>{l.area}</div>
                  </div>
                  <Pill color={l.status === "completado" ? C.green : C.orangeDk} bg={l.status === "completado" ? C.greenBg : C.orangeSoft} style={{ fontSize: 10 }}>{l.status === "completado" ? "✓ Completado" : "En progreso"}</Pill>
                </div>
              ))}
            </div>
          </Card>
        )}

        {testsList.length > 0 && (
          <Card style={{ padding: 15 }}>
            <div style={{ display: "flex", alignItems: "baseline", justifyContent: "space-between" }}>
              <span style={{ fontSize: 14, fontWeight: 800, color: C.ink }}>Tests realizados</span>
              <span style={{ fontSize: 11, fontWeight: 700, color: C.mut }}>{testsList.length}</span>
            </div>
            <div style={{ display: "flex", flexDirection: "column", gap: 8, marginTop: 12 }}>
              {testsList.map((t, i) => (
                <div key={i} style={{ display: "flex", alignItems: "center", gap: 10, background: C.sand, borderRadius: 12, padding: "9px 12px" }}>
                  <Icon name="test" color={C.violet} size={16} />
                  <div style={{ flex: 1, minWidth: 0 }}>
                    <div style={{ fontSize: 12, fontWeight: 800, color: C.ink }}>{t.name || t.skill || "Test"}</div>
                    {t.skill && t.name && <div style={{ fontSize: 10, fontWeight: 700, color: C.mut }}>{t.skill}</div>}
                  </div>
                  {t.pct != null && <Pill color={C.violetDk} bg={C.violetSoft} style={{ fontSize: 10 }}>{t.pct}%</Pill>}
                </div>
              ))}
            </div>
          </Card>
        )}

        <EvidenceCard me={me} update={update} />

        <Card style={{ padding: 15 }}>
          <div style={{ display: "flex", alignItems: "baseline", justifyContent: "space-between" }}>
            <span style={{ fontSize: 14, fontWeight: 800, color: C.ink }}>Insignias</span>
            <span style={{ fontSize: 11, fontWeight: 700, color: C.mut }}>{me.badges} de 24</span>
          </div>
          <div style={{ display: "grid", gridTemplateColumns: "repeat(6,1fr)", gap: 10, marginTop: 13 }}>
            {[["trophy", "linear-gradient(140deg,#FF6600,#FFB347)"], ["star", "linear-gradient(140deg,#6C4CF1,#12C2E9)"], ["check", "linear-gradient(140deg,#C6F24E,#0E8F5B)"]].map((b, i) => (
              <div key={i} style={{ aspectRatio: "1", borderRadius: 16, background: b[1], display: "flex", alignItems: "center", justifyContent: "center" }}><Icon name={b[0]} color="#fff" size={22} /></div>
            ))}
            <div style={{ aspectRatio: "1", borderRadius: 16, background: C.sand, border: "1px dashed #D6CEC4", display: "flex", alignItems: "center", justifyContent: "center", fontSize: 11, fontWeight: 800, color: C.mut4 }}>+6</div>
          </div>
        </Card>

        <div style={{ borderRadius: 22, padding: 15, background: C.ink, display: "flex", alignItems: "center", gap: 10 }}>
          <Icon name="coin" color={C.violet} size={22} />
          <div style={{ flex: 1 }}>
            <div style={{ fontSize: 13, fontWeight: 800, color: "#fff" }}>{me.coins.toLocaleString("es-CO")} O-Coins</div>
            <div style={{ fontSize: 11, fontWeight: 600, color: "rgba(255,255,255,0.55)", marginTop: 2 }}>Canjea por datos, cursos o mentorías</div>
          </div>
          <Btn onClick={openRedirect} style={{ padding: "8px 12px", fontSize: 12, borderRadius: 999 }}>Canjear</Btn>
        </div>
      </div>
    );
  }

  const SITUACION_OPTS = ["Estudiando", "Recién graduado", "Graduado hace más de 5 años", "Trabajando actualmente y buscando nuevas oportunidades"];
  function EditProfileModal({ me, update, onClose }) {
    const [v, setV] = useState({
      name: me.name || "", role: me.role || "", city: me.city || "", phone: me.phone || "", bio: me.bio || "",
      edad: me.edad || "", situacion: me.situacion || "", institucion: me.institucion || "", comoNosEscucho: me.comoNosEscucho || "",
    });
    const set = (k) => (e) => setV({ ...v, [k]: e.target.value });
    const inp = { fontFamily: FONT, fontSize: 14, fontWeight: 600, color: C.ink, background: C.sand, border: `1px solid ${C.line2}`, borderRadius: 13, padding: "12px 13px", width: "100%", boxSizing: "border-box" };
    const cap = { fontSize: 10, fontWeight: 800, letterSpacing: "0.08em", color: C.mut4, marginBottom: 6, display: "block" };
    function save() { update({ name: v.name, role: v.role, city: v.city, phone: v.phone, bio: v.bio, edad: v.edad, situacion: v.situacion, institucion: v.institucion, comoNosEscucho: v.comoNosEscucho }); onClose(); }
    return (
      <div onClick={onClose} style={{ position: "fixed", inset: 0, zIndex: 65, background: "rgba(23,19,31,0.62)", display: "flex", alignItems: "center", justifyContent: "center", padding: 20 }}>
        <div onClick={(e) => e.stopPropagation()} style={{ width: "100%", maxWidth: 460, background: "#fff", borderRadius: 24, padding: 24, animation: "olabRise .25s ease both", maxHeight: "90vh", overflowY: "auto" }}>
          <div style={{ display: "flex", justifyContent: "space-between", alignItems: "center" }}>
            <div style={{ fontSize: 20, fontWeight: 800, color: C.ink }}>Editar perfil</div>
            <button onClick={onClose} style={{ border: `1px solid ${C.line2}`, background: "#fff", borderRadius: 999, width: 32, height: 32, cursor: "pointer", color: C.mut3 }}><Icon name="close" size={16} /></button>
          </div>
          <div style={{ display: "flex", flexDirection: "column", gap: 12, marginTop: 18 }}>
            <div><label style={cap}>NOMBRE COMPLETO</label><input value={v.name} onChange={set("name")} style={inp} /></div>
            <div><label style={cap}>TÍTULO / ROL ACTUAL</label><input value={v.role} onChange={set("role")} placeholder="Auxiliar de bodega" style={inp} /></div>
            <div style={{ display: "grid", gridTemplateColumns: "1fr 1fr", gap: 12 }}>
              <div><label style={cap}>CIUDAD</label><input value={v.city} onChange={set("city")} placeholder="Medellín" style={inp} /></div>
              <div><label style={cap}>EDAD</label><input value={v.edad} onChange={set("edad")} type="number" placeholder="24" style={inp} /></div>
            </div>
            <div><label style={cap}>WHATSAPP</label><input value={v.phone} onChange={set("phone")} placeholder="+57 300 000 0000" style={inp} /></div>
            <div style={{ fontSize: 11, fontWeight: 800, color: C.mut3, letterSpacing: "0.04em", marginTop: 4 }}>DATOS DE TU SOLICITUD (mejóralos para que te descubran)</div>
            <div><label style={cap}>¿CUÁL ES TU SITUACIÓN?</label>
              <select value={v.situacion} onChange={set("situacion")} style={inp}>
                <option value="">Selecciona…</option>
                {SITUACION_OPTS.map((o) => <option key={o} value={o}>{o}</option>)}
              </select>
            </div>
            <div><label style={cap}>INSTITUCIÓN A LA QUE PERTENECES / PERTENECISTE</label><input value={v.institucion} onChange={set("institucion")} placeholder="Universidad / SENA / colegio…" style={inp} /></div>
            <div><label style={cap}>¿DE DÓNDE NOS ESCUCHASTE?</label><input value={v.comoNosEscucho} onChange={set("comoNosEscucho")} placeholder="LinkedIn, un amigo, redes…" style={inp} /></div>
            <div><label style={cap}>SOBRE MÍ</label><textarea value={v.bio} onChange={set("bio")} rows={3} placeholder="Cuéntale a las empresas quién eres y qué buscas…" style={{ ...inp, resize: "vertical" }} /></div>
          </div>
          <div style={{ display: "flex", gap: 8, marginTop: 20 }}>
            <Btn kind="ghost" onClick={onClose} style={{ flex: 1, padding: 13 }}>Cancelar</Btn>
            <Btn onClick={save} style={{ flex: 1.4, padding: 13 }}>Guardar cambios</Btn>
          </div>
        </div>
      </div>
    );
  }

  /* --------------------- BANNERS / PUBLICIDAD (feed) --------------------- */
  function PromoCard({ item, isAdmin }) {
    const isAd = item.kind === "ADs";
    const accent = item.color || (isAd ? C.pink : C.violet);
    function go() { if (item.link) { try { window.open(item.link, "_blank", "noopener"); } catch (e) {} } else if (item.onClick) item.onClick(); }
    // ADs = poster vertical grande y llamativo (imagen a pantalla completa + texto encima).
    if (isAd) {
      return (
        <div onClick={go} style={{ position: "relative", cursor: "pointer", borderRadius: 18, overflow: "hidden", aspectRatio: "2 / 3.15", minHeight: 400, display: "flex", flexDirection: "column", justifyContent: "flex-end", border: "1px solid rgba(23,19,31,0.6)", boxShadow: "0 22px 44px rgba(23,19,31,0.34)", background: item.image ? `#0B0910 url(${item.image}) center/cover no-repeat` : `linear-gradient(160deg,${accent},${C.orange})`, transition: "transform .2s, box-shadow .2s" }}
          onMouseEnter={(e) => { e.currentTarget.style.transform = "translateY(-4px) scale(1.015)"; e.currentTarget.style.boxShadow = "0 28px 54px rgba(23,19,31,0.45)"; }}
          onMouseLeave={(e) => { e.currentTarget.style.transform = "none"; e.currentTarget.style.boxShadow = "0 22px 44px rgba(23,19,31,0.34)"; }}>
          {/* viñeta cinematográfica: oscurece arriba y abajo como poster de cine */}
          <div style={{ position: "absolute", inset: 0, background: "linear-gradient(to bottom, rgba(11,9,16,0.55) 0%, rgba(11,9,16,0) 24%, rgba(11,9,16,0) 46%, rgba(11,9,16,0.86) 82%, rgba(11,9,16,0.97) 100%)" }} />
          <div style={{ position: "absolute", top: 11, left: 11, display: "flex", alignItems: "center", gap: 6, fontSize: 9.5, fontWeight: 900, letterSpacing: "0.16em", color: C.ink, background: C.lime, padding: "5px 10px", borderRadius: 6, boxShadow: "0 4px 14px rgba(198,242,78,0.55)" }}><Icon name="star" size={11} color={C.ink} />ADs</div>
          <div style={{ position: "absolute", top: 13, right: 12, fontSize: 8.5, fontWeight: 900, letterSpacing: "0.22em", color: "rgba(255,255,255,0.82)", textShadow: "0 1px 6px rgba(0,0,0,0.7)" }}>PRÓXIMAMENTE</div>
          {!item.image && <div style={{ position: "absolute", inset: 0, display: "grid", placeItems: "center", opacity: 0.9 }}><Icon name={item.icon || "star"} color="#fff" size={72} /></div>}
          {isAdmin && item.id && <ThumbButton id={item.id} current={item.image} />}
          <div style={{ position: "relative", padding: "20px 15px 17px" }}>
            <div style={{ width: 34, height: 3, borderRadius: 999, background: C.lime, marginBottom: 10, boxShadow: "0 0 10px rgba(198,242,78,0.7)" }} />
            <div style={{ fontSize: 20, fontWeight: 900, color: "#fff", lineHeight: 1.12, letterSpacing: "-0.02em", textShadow: "0 2px 14px rgba(0,0,0,0.6)" }}>{item.title}</div>
            {item.desc && <div style={{ fontSize: 12, fontWeight: 600, color: "rgba(255,255,255,0.82)", marginTop: 7, lineHeight: 1.4, display: "-webkit-box", WebkitLineClamp: 3, WebkitBoxOrient: "vertical", overflow: "hidden", textShadow: "0 1px 8px rgba(0,0,0,0.6)" }}>{item.desc}</div>}
            <div style={{ marginTop: 14, display: "flex", alignItems: "center", justifyContent: "center", gap: 7, fontSize: 13, fontWeight: 900, color: C.ink, background: C.lime, padding: "11px 14px", borderRadius: 12, boxShadow: "0 6px 18px rgba(198,242,78,0.45)", letterSpacing: "0.01em" }}>{item.cta || (item.link ? "Ver más" : "Ver ahora")} <span style={{ fontSize: 15 }}>→</span></div>
          </div>
        </div>
      );
    }
    return (
      <div onClick={go} style={{ cursor: "pointer", background: "#fff", border: `1px solid ${C.line2}`, borderRadius: 18, overflow: "hidden", boxShadow: "0 10px 26px rgba(23,19,31,0.06)", transition: "transform .15s" }}
        onMouseEnter={(e) => { e.currentTarget.style.transform = "translateY(-2px)"; }} onMouseLeave={(e) => { e.currentTarget.style.transform = "none"; }}>
        <div style={{ height: 96, background: item.image ? `#EEE url(${item.image}) center/cover` : `linear-gradient(135deg,${accent},${C.cyan})`, position: "relative", display: "flex", alignItems: "flex-start", padding: 9 }}>
          <span style={{ fontSize: 9.5, fontWeight: 900, letterSpacing: "0.08em", color: accent, background: "#fff", padding: "4px 8px", borderRadius: 999 }}>{item.tag || "NOVEDAD"}</span>
          {!item.image && <div style={{ position: "absolute", inset: 0, display: "grid", placeItems: "center", opacity: 0.9 }}><Icon name={item.icon || "star"} color="#fff" size={30} /></div>}
        </div>
        <div style={{ padding: "11px 12px 13px" }}>
          <div style={{ fontSize: 13.5, fontWeight: 800, color: C.ink, lineHeight: 1.25 }}>{item.title}</div>
          {item.desc && <div style={{ fontSize: 11.5, fontWeight: 600, color: C.mut, marginTop: 4, lineHeight: 1.4, display: "-webkit-box", WebkitLineClamp: 2, WebkitBoxOrient: "vertical", overflow: "hidden" }}>{item.desc}</div>}
          <div style={{ marginTop: 9, fontSize: 11.5, fontWeight: 800, color: accent }}>{item.cta || (item.link ? "Ver más" : "Explorar")} →</div>
        </div>
      </div>
    );
  }
  function PromoRail({ items, isAdmin }) {
    if (!items || !items.length) return null;
    return (
      <aside style={{ width: 234, flexShrink: 0, position: "sticky", top: 18, alignSelf: "flex-start", display: "flex", flexDirection: "column", gap: 14 }}>
        <div style={{ fontSize: 10, fontWeight: 900, letterSpacing: "0.1em", color: C.mut4 }}>DESTACADOS</div>
        {items.map((it, i) => <PromoCard key={i} item={it} isAdmin={isAdmin} />)}
      </aside>
    );
  }

  /* ----------------------------- APP ----------------------------- */
  function AplicanteApp({ profile, onLogout, onSwitchRole, isAdmin }) {
    const mob = useIsMobile();
    const [tab, setTab] = useState("feed");
    const [redirect, setRedirect] = useState(false);
    const [adminOpen, setAdminOpen] = useState(false);
    const [content, setContent] = useState(null);
    useEffect(() => B().subscribeContent((l) => setContent(l)), []);
    const [thumbs, setThumbs] = useState({});
    useEffect(() => B().subscribeThumbnails(setThumbs), []);
    const [hiddenMap, setHiddenMap] = useState({});
    useEffect(() => B().subscribeHidden(setHiddenMap), []);
    const contentOf = (t) => (Array.isArray(content) ? content.filter((c) => c.type === t && !hiddenMap[c.id]) : []);
    // Labs publicados desde el registro Labs de Project OS ("Sí, publicar en portal").
    const [projLabs, setProjLabs] = useState([]);
    useEffect(() => { B().publicLabs().then((l) => setProjLabs(Array.isArray(l) ? l : [])); }, []);
    const labsPublished = [
      ...projLabs.map((l) => ({ id: "proj-" + (l.id || l.title), title: l.title, area: l.tipo || l.area || "", tag: l.estado || "LAB", link: l.link || "", xp: "", image: "" })),
      ...contentOf("lab"),
    ].filter((l) => !hiddenMap[l.id]);
    // Borra un lab: si lo publicó el portal lo elimina de verdad; si viene de Projects lo oculta.
    function deleteLab(l) {
      if (!window.confirm('¿Quitar el lab "' + (l.title || "") + '" del portal?')) return;
      if (String(l.id).indexOf("proj-") === 0) B().setHidden(l.id, true);
      else B().deleteContent(l.id);
    }
    const [prof, setProf] = useState(() => profile || {});
    // Estado central del perfil: SEED (base) + perfil real + ediciones locales.
    const me = Object.assign({}, SEED().me, profile || {}, prof);
    const update = (patch) => { setProf((p) => ({ ...p, ...patch })); B().saveProfile(patch); };
    const openRedirect = () => setRedirect(true);
    function applyToJob(job) {
      const applied = Object.assign({}, me.appliedJobs, { [job.id]: { title: job.title, company: job.company, match: job.match, codigo: job.codigo, link: job.link, at: Date.now(), status: "Postulada" } });
      update({ appliedJobs: applied });
      B().apply({ vacId: job.id, codigo: job.codigo, empresa: job.company, cargo: job.title, match: job.match, link: job.link });
      // Abre el link real de la vacante (columna LINK VACANTE de Comunidad Talentos).
      if (job.link) { try { window.open(job.link, "_blank", "noopener"); } catch (e) {} }
      else setRedirect(true);
    }
    // Progresión: 1 Lab completado → desbloquea Tests; 1 Test hecho → desbloquea Vacantes.
    const labsCompleted = Object.values(me.myLabs || {}).filter((l) => l.status === "completado").length;
    const testsCompleted = Object.keys(me.testResults || {}).length;
    const locked = { tests: labsCompleted < 1, vacantes: testsCompleted < 1 };
    const lockReason = { tests: "Completa al menos 1 Lab para desbloquear los Tests 🔬", vacantes: "Completa al menos 1 Test para desbloquear las Vacantes 🧪" };
    const [lockMsg, setLockMsg] = useState("");
    const tabs = [["feed", "Feed", "home"], ["labs", "Labs", "flask"], ["tests", "Tests", "test"], ["vacantes", "Vacantes", "briefcase"], ["eventos", "Eventos", "calendar"], ["perfil", "Perfil", "user"]];
    function tryTab(k) { if (locked[k]) { setLockMsg(lockReason[k]); setTimeout(() => setLockMsg(""), 3200); } else { setLockMsg(""); setTab(k); } }
    const curTab = locked[tab] ? "feed" : tab; // por si el activo quedó bloqueado

    // Publicidad para los rieles del Feed: ADs publicadas por el admin + novedades
    // automáticas (últimos labs/tests/eventos + un banner fijo de vacantes).
    const ads = contentOf("ad").map((a) => ({ kind: "ADs", id: a.id, title: a.title, desc: a.description, image: thumbs[a.id] || a.image, link: a.link, color: C.pink, icon: "star" }));
    const nov = [];
    const lastLab = labsPublished[0]; if (lastLab) nov.push({ kind: "NOVEDAD", tag: "NUEVO LAB", title: lastLab.title, desc: lastLab.area || "Nuevo Lab disponible", image: lastLab.image, color: C.violet, icon: "flask", cta: "Ir a Labs", onClick: () => tryTab("labs") });
    const lastTest = contentOf("test")[0]; if (lastTest) nov.push({ kind: "NOVEDAD", tag: "NUEVO TEST", title: lastTest.title, desc: lastTest.description || "Suma competencias", image: lastTest.image, color: C.orange, icon: "test", cta: "Ir a Tests", onClick: () => tryTab("tests") });
    const lastEv = contentOf("evento")[0]; if (lastEv) nov.push({ kind: "NOVEDAD", tag: "EVENTO", title: lastEv.title, desc: lastEv.area || lastEv.description, image: lastEv.image, color: C.green, icon: "calendar", cta: "Ver eventos", onClick: () => tryTab("eventos") });
    nov.push({ kind: "NOVEDAD", tag: "VACANTES", title: "Vacantes con match real", desc: "Explora ofertas hechas para tu perfil", color: C.cyan, icon: "briefcase", cta: "Ver vacantes", onClick: () => tryTab("vacantes") });
    const railAll = [...ads, ...nov];
    const leftRail = railAll.filter((_, i) => i % 2 === 0);
    const rightRail = railAll.filter((_, i) => i % 2 === 1);

    let body = null;
    if (curTab === "feed") body = <Feed me={me} openRedirect={openRedirect} isAdmin={isAdmin} update={update} goTab={tryTab} />;
    else if (curTab === "vacantes") body = <Vacantes openRedirect={openRedirect} goLabs={() => setTab("labs")} me={me} update={update} onApply={applyToJob} />;
    else if (curTab === "labs") body = <Labs openRedirect={openRedirect} me={me} update={update} published={labsPublished} isAdmin={isAdmin} thumbs={thumbs} hiddenMap={hiddenMap} />;
    else if (curTab === "tests") body = <Tests me={me} update={update} published={contentOf("test")} isAdmin={isAdmin} thumbs={thumbs} />;
    else if (curTab === "eventos") body = <Eventos openRedirect={openRedirect} me={me} update={update} published={contentOf("evento")} isAdmin={isAdmin} />;
    else if (curTab === "perfil") body = <Perfil me={me} update={update} openRedirect={openRedirect} goVacantes={() => tryTab("vacantes")} isAdmin={isAdmin} />;

    return (
      <div style={{ minHeight: "100vh", background: C.cream, fontFamily: FONT }}>
        <Header me={me} onLogout={onLogout} onSwitchRole={onSwitchRole} isAdmin={isAdmin} onAdmin={() => setAdminOpen(true)} mob={mob} />
        <div style={{ maxWidth: curTab === "feed" && !mob ? 1400 : 1080, margin: "0 auto", padding: mob ? "14px 12px 36px" : "20px 20px 40px" }}>
          <TabBar tabs={tabs} tab={curTab} setTab={tryTab} locked={locked} me={me} />
          {lockMsg && <div style={{ marginTop: 12, background: C.orangeSoft, border: `1px solid ${C.orangeSoftBd}`, borderRadius: 12, padding: "11px 14px", fontSize: 13, fontWeight: 700, color: C.orangeDk, display: "flex", alignItems: "center", gap: 8 }}>🔒 {lockMsg}</div>}
          {curTab === "feed" && !mob ? (
            <div style={{ display: "flex", gap: 20, marginTop: 16, alignItems: "flex-start" }}>
              <PromoRail items={leftRail} isAdmin={isAdmin} />
              <div style={{ flex: 1, minWidth: 0 }}>
                <RetosSemana me={me} isAdmin={isAdmin} update={update} goTab={tryTab} />
                <div style={{ marginTop: 16 }}>{body}</div>
              </div>
              <PromoRail items={rightRail} isAdmin={isAdmin} />
            </div>
          ) : (
            <>
              <div style={{ marginTop: 16 }}><RetosSemana me={me} isAdmin={isAdmin} update={update} goTab={tryTab} /></div>
              <div style={{ marginTop: 16 }}>{body}</div>
            </>
          )}
        </div>
        {redirect && <RedirectSheet onClose={() => setRedirect(false)} />}
        {adminOpen && <AdminModal onClose={() => setAdminOpen(false)} />}
      </div>
    );
  }

  function Header({ me, onLogout, onSwitchRole, isAdmin, onAdmin, mob }) {
    return (
      <div style={{ background: "#fff", borderBottom: `1px solid ${C.line}`, padding: mob ? "10px 12px" : "12px 20px" }}>
        <div style={{ maxWidth: 1080, margin: "0 auto", display: "flex", alignItems: "center", justifyContent: "space-between", gap: 8, flexWrap: "wrap" }}>
          <img src="assets/logo-olab.png" alt="o-lab" style={{ height: 24 }} />
          <div style={{ display: "flex", alignItems: "center", gap: mob ? 6 : 8, flexWrap: "wrap", justifyContent: "flex-end" }}>
            {isAdmin && onAdmin && <button onClick={onAdmin} title="Panel de superadmin" style={{ border: `1px solid ${C.line2}`, background: C.ink, color: "#fff", fontFamily: FONT, fontSize: 12, fontWeight: 800, padding: "7px 11px", borderRadius: 999, cursor: "pointer", display: "flex", alignItems: "center", gap: 6 }}><Icon name="star" size={14} color={C.lime} />{!mob && "Superadmin"}</button>}
            {onSwitchRole && <button onClick={onSwitchRole} title="Cambiar a vista de empresa" style={{ border: `1px solid ${C.violetSoftBd}`, background: C.violetSoft, color: C.violetDk, fontFamily: FONT, fontSize: 12, fontWeight: 800, padding: "7px 11px", borderRadius: 999, cursor: "pointer", display: "flex", alignItems: "center", gap: 6 }}><Icon name="briefcase" size={14} color={C.violetDk} />{!mob && "Empresa"}</button>}
            <div style={{ display: "flex", alignItems: "center", gap: 5, background: "linear-gradient(135deg,#FFF1E4,#FFE2CB)", border: "1px solid #FFC79A", padding: "5px 9px", borderRadius: 999 }}>
              <Icon name="fire" color={C.orange} size={13} /><span style={{ fontSize: 12, fontWeight: 800, color: C.orangeDk }}>{me.streak}</span>
            </div>
            <div style={{ display: "flex", alignItems: "center", gap: 5, background: C.violetSoft, border: "1px solid #D9CDFF", padding: "5px 9px", borderRadius: 999 }}>
              <Icon name="coin" color={C.violet} size={13} /><span style={{ fontSize: 12, fontWeight: 800, color: C.violetDk }}>{me.coins.toLocaleString("es-CO")}</span>
            </div>
            <div style={{ position: "relative", flexShrink: 0 }}>
              {me.photoUrl
                ? <img src={me.photoUrl} alt="tú" style={{ width: 36, height: 36, borderRadius: 999, objectFit: "cover", border: "2px solid #fff", boxShadow: `0 0 0 2px ${C.lime}` }} />
                : <Avatar name={me.name} bg="linear-gradient(135deg,#FF6600,#FF3D7F)" size={36} style={{ border: "2px solid #fff", boxShadow: `0 0 0 2px ${C.lime}` }} />}
              <span style={{ position: "absolute", bottom: -4, left: "50%", transform: "translateX(-50%)", fontSize: 8.5, fontWeight: 900, color: C.ink, background: C.lime, padding: "1px 5px", borderRadius: 999, border: "1.5px solid #fff", whiteSpace: "nowrap" }}>NV{me.level}</span>
            </div>
            <button onClick={onLogout} title="Salir" style={{ border: `1px solid ${C.line2}`, background: "#fff", borderRadius: 999, width: 32, height: 32, display: "flex", alignItems: "center", justifyContent: "center", cursor: "pointer", color: C.mut3 }}><Icon name="logout" size={16} /></button>
          </div>
        </div>
        <div style={{ maxWidth: 1080, margin: "10px auto 0", display: "flex", alignItems: "center", gap: 8 }}>
          <Pill color="#fff" bg={C.ink} style={{ fontSize: 10, padding: "3px 7px", borderRadius: 6 }}>NV. {me.level}</Pill>
          <div style={{ flex: 1 }}><Bar pct={Math.round((me.xp / me.xpMax) * 100) + "%"} shine h={7} /></div>
          <span style={{ fontSize: 10, fontWeight: 700, color: C.mut }}>{me.xp}/{me.xpMax} XP</span>
        </div>
      </div>
    );
  }

  function TabBar({ tabs, tab, setTab, locked, me }) {
    locked = locked || {};
    return (
      <div style={{ display: "flex", gap: 6, overflowX: "auto", paddingBottom: 4 }}>
        {tabs.map(([k, label, icon]) => {
          const on = tab === k;
          const lk = !!locked[k];
          const showPhoto = k === "perfil" && me && me.photoUrl && !lk;
          return (
            <button key={k} onClick={() => setTab(k)} title={lk ? "Bloqueado" : label} style={{ border: `1px solid ${on ? C.ink : C.line2}`, background: on ? (k === "perfil" ? "linear-gradient(135deg,#FF6600,#FF3D7F)" : C.ink) : "#fff", borderColor: on && k === "perfil" ? "transparent" : (on ? C.ink : C.line2), color: on ? "#fff" : lk ? C.mut4 : C.mut3, fontFamily: FONT, fontSize: 13, fontWeight: 800, padding: showPhoto ? "6px 15px 6px 6px" : "9px 15px", borderRadius: 999, cursor: "pointer", display: "flex", alignItems: "center", gap: 7, whiteSpace: "nowrap", opacity: lk ? 0.75 : 1 }}>
              {showPhoto
                ? <img src={me.photoUrl} alt="" style={{ width: 24, height: 24, borderRadius: 999, objectFit: "cover", border: `1.5px solid ${on ? "#fff" : C.lime}` }} />
                : <Icon name={lk ? "clock" : icon} size={16} color={on ? "#fff" : lk ? C.mut4 : C.mut3} />}
              {label}{lk && <span style={{ fontSize: 12 }}>🔒</span>}
            </button>
          );
        })}
      </div>
    );
  }

  function RedirectSheet({ onClose }) {
    return (
      <div onClick={onClose} style={{ position: "fixed", inset: 0, zIndex: 60, background: "rgba(23,19,31,0.6)", display: "flex", alignItems: "flex-end", justifyContent: "center", padding: 16 }}>
        <div onClick={(e) => e.stopPropagation()} style={{ width: "100%", maxWidth: 420, background: "#fff", borderRadius: 26, padding: 20, animation: "olabRise .25s ease both" }}>
          <div style={{ width: 44, height: 5, borderRadius: 999, background: C.line2, margin: "0 auto 16px" }} />
          <div style={{ display: "flex", alignItems: "center", gap: 11 }}>
            <div style={{ width: 44, height: 44, borderRadius: 14, background: C.orangeSoft, display: "flex", alignItems: "center", justifyContent: "center" }}><Icon name="external" color={C.orange} size={20} /></div>
            <div style={{ flex: 1 }}>
              <div style={{ fontSize: 16, fontWeight: 800, color: C.ink }}>Continuar en O-Lab</div>
              <div style={{ fontSize: 12, fontWeight: 600, color: C.mut, marginTop: 2 }}>Tu progreso y XP vuelven aquí solos</div>
            </div>
          </div>
          <div style={{ display: "flex", gap: 8, marginTop: 18 }}>
            <Btn kind="ghost" onClick={onClose} style={{ flex: 1, padding: 13, borderRadius: 15 }}>Cancelar</Btn>
            <Btn onClick={onClose} style={{ flex: 1.4, padding: 13, borderRadius: 15 }}>Ir a O-Lab</Btn>
          </div>
        </div>
      </div>
    );
  }

  window.AplicanteApp = AplicanteApp;
  window.PORTAL_AdminModal = AdminModal;
})();
