/* ============================================================
   FLOWSHIELD — Red Team (dynamic scan forms + live API runner)
   ============================================================ */

/* Wordlist picker: choose a built-in/uploaded dictionary for this category,
   or paste a custom one. Resolves the chosen list's content into the field
   value so the existing payload builder ships it as plain text. */
function WordlistField({ f, value, onChange, disabled }) {
  const [lists, setLists] = useStateL([]);
  const [src, setSrc] = useStateL('');      // '' engine default · <id> chosen list · '__custom'
  const [loading, setLoading] = useStateL(false);

  useEffectL(() => {
    let alive = true;
    window.fsApi('/api/wordlists?category=' + encodeURIComponent(f.category), { method: 'GET' })
      .then(d => { if (alive) setLists((d && d.wordlists) || []); })
      .catch(() => {});
    return () => { alive = false; };
  }, [f.category]);

  const pick = async (val) => {
    setSrc(val);
    if (val === '' || val === '__custom') { onChange(''); return; }
    setLoading(true);
    try {
      const text = await window.fsApi('/api/wordlists/' + encodeURIComponent(val) + '/content', { method: 'GET' });
      onChange(typeof text === 'string' ? text : '');
    } catch (e) { onChange(''); }
    setLoading(false);
  };

  // Optional: load a file from the local disk into the textarea (opt-in via f.upload).
  const onFile = (e) => {
    const file = e.target.files && e.target.files[0];
    if (!file) { return; }
    const reader = new FileReader();
    reader.onload = () => { setSrc('__custom'); onChange(((value ? value + '\n' : '') + String(reader.result || '')).trim()); };
    reader.readAsText(file);
    e.target.value = '';
  };

  const chosen = lists.find(l => l.id === src);
  const count = value ? value.split('\n').filter(s => s.trim() && !s.trim().startsWith('#')).length : 0;
  const label = <label className="mono" style={{ fontSize:11, color:'var(--txt-dim)', display:'block', marginBottom:7 }}>{f.label}</label>;

  return (
    <div style={{ marginBottom:16 }}>
      {label}
      <select className="rt-sel" value={src} onChange={e => pick(e.target.value)} disabled={disabled} style={{ marginBottom:9 }}>
        <option value="">Lista incorporada del motor (por defecto)</option>
        {lists.length > 0 && <optgroup label="Diccionarios disponibles">
          {lists.map(l => <option key={l.id} value={l.id}>{l.name} · {l.entries} entradas{l.builtin ? '' : ' · subida'}</option>)}
        </optgroup>}
        <option value="__custom">Personalizada — pegar abajo…</option>
      </select>
      {f.upload && (
        <label className="btn btn-ghost btn-sm" style={{ cursor: disabled?'default':'pointer', marginBottom:9, display:'inline-flex' }}>
          <Icon name="folder" size={13} /> Subir fichero
          <input type="file" accept=".txt,.csv,text/plain" style={{ display:'none' }} disabled={disabled} onChange={onFile} />
        </label>
      )}
      {src === '' ? (
        <div className="mono" style={{ fontSize:10.5, color:'var(--txt-mute)' }}>
          Se usará el diccionario incorporado del motor. Sube los tuyos en Ajustes › Wordlists.
        </div>
      ) : (
        <div>
          {loading && <div className="mono" style={{ fontSize:10.5, color:'var(--acc)', marginBottom:5 }}>Cargando diccionario…</div>}
          <textarea className="input" rows={f.rows || 5}
            style={{ resize:'vertical', fontFamily:'var(--mono, monospace)', lineHeight:1.5 }}
            value={value ?? ''} placeholder={f.placeholder || ''} disabled={disabled || loading}
            onChange={e => { setSrc('__custom'); onChange(e.target.value); }} />
          <div className="mono" style={{ fontSize:10.5, color:'var(--txt-mute)', marginTop:5 }}>
            {count} entradas{chosen ? ' · ' + chosen.name + ' (editable)' : ' · personalizada'}
          </div>
        </div>
      )}
    </div>
  );
}

/* Renders a single config field from a tool's engine schema. */
function FieldInput({ f, value, onChange, disabled }) {
  if (f.type === 'toggle') {
    return <Toggle label={f.label} on={!!value} onClick={() => onChange(!value)} disabled={disabled} />;
  }
  if (f.type === 'wordlist') {
    return <WordlistField f={f} value={value} onChange={onChange} disabled={disabled} />;
  }
  const label = (
    <label className="mono" style={{ fontSize:11, color:'var(--txt-dim)', display:'block', marginBottom:7 }}>{f.label}</label>
  );
  if (f.type === 'select') {
    return (
      <div style={{ marginBottom:16 }}>
        {label}
        <select className="rt-sel" value={value ?? ''} onChange={e => onChange(e.target.value)} disabled={disabled}>
          {(f.options || []).map(o => {
            const v = o.value ?? o; const l = o.label ?? o;
            return <option key={v} value={v}>{l}</option>;
          })}
        </select>
      </div>
    );
  }
  if (f.type === 'filetext') {
    const count = value ? value.split('\n').filter(s => s.trim() && !s.trim().startsWith('#')).length : 0;
    const onFile = (e) => {
      const file = e.target.files && e.target.files[0];
      if (!file) return;
      const reader = new FileReader();
      reader.onload = () => { onChange(((value ? value + '\n' : '') + String(reader.result || '')).trim()); };
      reader.readAsText(file);
      e.target.value = '';
    };
    return (
      <div style={{ marginBottom:16 }}>
        {label}
        <textarea className="input" rows={f.rows || 6}
          style={{ resize:'vertical', fontFamily:'var(--mono, monospace)', lineHeight:1.5 }}
          value={value ?? ''} placeholder={f.placeholder || ''} disabled={disabled}
          onChange={e => onChange(e.target.value)} />
        <div style={{ display:'flex', alignItems:'center', gap:10, marginTop:6 }}>
          <label className="btn btn-ghost btn-sm" style={{ cursor: disabled?'default':'pointer' }}>
            <Icon name="folder" size={13} /> Subir fichero
            <input type="file" accept=".txt,.csv,text/plain" style={{ display:'none' }} disabled={disabled} onChange={onFile} />
          </label>
          <span className="mono" style={{ fontSize:10.5, color:'var(--txt-mute)' }}>{count} credencial(es)</span>
        </div>
      </div>
    );
  }
  if (f.type === 'textarea') {
    return (
      <div style={{ marginBottom:16 }}>
        {label}
        <textarea className="input" rows={f.rows || 4}
          style={{ resize:'vertical', fontFamily:'var(--mono, monospace)', lineHeight:1.5 }}
          value={value ?? ''} placeholder={f.placeholder || ''} disabled={disabled}
          onChange={e => onChange(e.target.value)} />
      </div>
    );
  }
  const type = f.type === 'password' ? 'password' : f.type === 'number' ? 'number' : 'text';
  return (
    <div style={{ marginBottom:16 }}>
      {label}
      <div className="field" style={{ width: f.width || 'auto' }}>
        {f.icon && <Icon name={f.icon} size={16} style={{ position:'absolute', left:13, top:13, color:'var(--acc)' }} />}
        <input className={'input' + (f.icon ? ' input-prefix' : '')} type={type}
          value={value ?? ''} placeholder={f.placeholder || ''} disabled={disabled}
          onChange={e => onChange(e.target.value)} />
      </div>
    </div>
  );
}

/* Collapsible group of advanced fields (keeps big forms tidy; no aesthetic change). */
function SectionGroup({ title, children }) {
  const [open, setOpen] = useStateL(false);
  return (
    <div style={{ marginBottom:16, borderTop:'1px solid color-mix(in srgb,var(--txt-mute) 22%,transparent)' }}>
      <button type="button" onClick={() => setOpen(o => !o)} className="mono"
        style={{ width:'100%', display:'flex', alignItems:'center', justifyContent:'space-between',
          background:'transparent', border:0, cursor:'pointer', padding:'11px 2px 9px',
          color:'var(--txt-dim)', fontSize:11, letterSpacing:'.05em', textTransform:'uppercase' }}>
        <span>{title}</span>
        <span style={{ fontSize:12, color:'var(--txt-mute)' }}>{open ? '▾' : '▸'}</span>
      </button>
      {open && <div style={{ paddingTop:2 }}>{children}</div>}
    </div>
  );
}

/* Renders the per-field inputs for a scan engine's field list.
   Extracted from ScanRunner so it can be reused (e.g. scan robots).
   AuthGate and run/cancel buttons are NOT part of this component —
   they live in the parent (ScanRunner / robot step). */
function ScanFields({ fields, form, setField, disabled }) {
  const fld = (f) => (
    <FieldInput key={f.name} f={f} value={form[f.name]}
      disabled={disabled} onChange={v => setField(f.name, v)} />
  );
  const flat = fields.filter(f => !f.section);
  const groups = [];
  const idx = {};
  fields.forEach(f => {
    if (!f.section) return;
    if (idx[f.section] === undefined) { idx[f.section] = groups.length; groups.push({ title: f.section, items: [] }); }
    groups[idx[f.section]].items.push(f);
  });
  return (
    <>
      {flat.map(fld)}
      {groups.map(g => (
        <SectionGroup key={g.title} title={g.title}>{g.items.map(fld)}</SectionGroup>
      ))}
    </>
  );
}
window.ScanFields = ScanFields;

/* Mandatory authorization acknowledgement for intrusive scans. */
function AuthGate({ on, onClick, disabled }) {
  return (
    <button onClick={onClick} disabled={disabled} style={{ display:'flex', alignItems:'flex-start', gap:10, background:'var(--acc-soft)', border:'1px solid '+(on?'var(--acc)':'color-mix(in srgb,var(--acc) 30%,transparent)'), borderRadius:8, padding:'11px 13px', cursor: disabled?'default':'pointer', width:'100%', textAlign:'left', marginBottom:16 }}>
      <span style={{ width:17, height:17, borderRadius:5, flexShrink:0, marginTop:1, border:'1.5px solid '+(on?'var(--acc)':'var(--line-bright)'), background: on?'var(--acc)':'var(--bg-void)', display:'grid', placeItems:'center', boxShadow: on?'0 0 8px var(--acc-glow)':'none' }}>
        {on && <Icon name="check" size={11} style={{ color:'#04110D' }} />}
      </span>
      <span style={{ fontSize:12, color:'var(--txt)', lineHeight:1.45 }}>Confirmo que soy propietario del objetivo o tengo <strong style={{ color:'var(--txt-hi)' }}>autorización explícita</strong> para escanearlo.</span>
    </button>
  );
}

/* CVSS base-score → UI sev token, mirroring the backend bands. */
function cvssTone(score) {
  const s = parseFloat(score);
  if (isNaN(s)) return 'info';
  if (s >= 9) return 'crit';
  if (s >= 7) return 'high';
  if (s >= 4) return 'med';
  if (s > 0) return 'low';
  return 'info';
}

/* A single finding rendered CVE-style: severity, CVSS, CVE/CWE chips,
   remediation and on-demand live enrichment against NVD/CIRCL. */
function FindingCard({ f }) {
  const sev = window.normSev(f.severity);
  const cves = f.cve || [];
  const cwes = f.cwe || [];
  const refs = f.references || [];
  const links = f.cve_links || cves.map(id => ({ id, nvd: 'https://nvd.nist.gov/vuln/detail/' + id }));
  const [enrich, setEnrich] = useStateL(null); // null | 'loading' | {map}
  const [open, setOpen] = useStateL(false);

  const compare = async () => {
    if (!cves.length) return;
    setOpen(true);
    if (enrich) return;
    setEnrich('loading');
    try {
      const data = await window.fsApi('/api/redteam/cve', { body: { cve_ids: cves } });
      const map = {};
      (data.results || []).forEach(r => { map[r.cve_id] = r; });
      setEnrich(map);
    } catch (e) {
      setEnrich({ __error: e.message || 'No se pudo consultar el CVE.' });
    }
  };

  return (
    <div style={{ padding:'11px 13px', background:'var(--bg-void)', border:'1px solid var(--line)', borderRadius:8, borderLeft:`3px solid var(--sev-${sev})` }}>
      <div style={{ display:'flex', alignItems:'flex-start', gap:12 }}>
        <Sev level={sev} pulse={sev === 'crit'} />
        <div style={{ flex:1, minWidth:0 }}>
          <div style={{ fontSize:13, color:'var(--txt-hi)' }}>{f.title}</div>
          {f.detail && <div className="mono" style={{ fontSize:11, color:'var(--txt-dim)', marginTop:3, wordBreak:'break-word' }}>{f.detail}</div>}

          {/* CVE-style meta chips */}
          {(f.cvss_score != null || cves.length || cwes.length || f.owasp) ? (
            <div style={{ display:'flex', flexWrap:'wrap', gap:6, marginTop:8, alignItems:'center' }}>
              {f.owasp && (
                <span className="mono" style={{ fontSize:10.5, color:'var(--amber)', border:'1px solid color-mix(in srgb,var(--amber) 38%,transparent)', borderRadius:5, padding:'2px 7px', background:'color-mix(in srgb,var(--amber) 12%,transparent)' }} title="OWASP Top 10">{f.owasp}</span>
              )}
              {f.cvss_score != null && (
                <span className={'badge ' + window.sevClass(cvssTone(f.cvss_score))} style={{ padding:'2px 8px' }} title={f.cvss_vector || ''}>CVSS {f.cvss_score}</span>
              )}
              {cves.map((id, i) => (
                <a key={id} href={(links[i] && links[i].nvd) || ('https://nvd.nist.gov/vuln/detail/' + id)} target="_blank" rel="noopener noreferrer"
                   className="mono" style={{ fontSize:10.5, color:'var(--acc)', textDecoration:'none', border:'1px solid color-mix(in srgb,var(--acc) 35%,transparent)', borderRadius:5, padding:'2px 7px', background:'var(--acc-soft)' }}>{id} ↗</a>
              ))}
              {cwes.map(id => (
                <span key={id} className="mono" style={{ fontSize:10.5, color:'var(--txt-dim)', border:'1px solid var(--line-bright)', borderRadius:5, padding:'2px 7px' }}>{id}</span>
              ))}
            </div>
          ) : null}

          {/* Remediation */}
          {f.remediation && (
            <div style={{ display:'flex', gap:8, marginTop:9, padding:'8px 10px', background:'var(--ok-soft, color-mix(in srgb, var(--ok) 8%, transparent))', border:'1px solid color-mix(in srgb,var(--ok) 28%,transparent)', borderRadius:7 }}>
              <Icon name="shield" size={13} style={{ color:'var(--ok)', flexShrink:0, marginTop:1 }} />
              <span style={{ fontSize:11.5, color:'var(--txt)', lineHeight:1.5 }}><strong style={{ color:'var(--txt-hi)' }}>Remediación:</strong> {f.remediation}</span>
            </div>
          )}

          {/* References + compare action */}
          {(refs.length || cves.length) ? (
            <div style={{ display:'flex', flexWrap:'wrap', gap:12, marginTop:9, alignItems:'center' }}>
              {refs.slice(0, 3).map((u, i) => (
                <a key={i} href={u} target="_blank" rel="noopener noreferrer" className="mono" style={{ fontSize:10.5, color:'var(--txt-mute)', textDecoration:'none', display:'inline-flex', alignItems:'center', gap:4 }}>
                  <Icon name="globe" size={11} /> ref{refs.length > 1 ? ' ' + (i + 1) : ''}
                </a>
              ))}
              {cves.length ? (
                <button onClick={compare} className="mono" style={{ background:'none', border:'none', color:'var(--acc)', cursor:'pointer', fontSize:10.5, display:'inline-flex', alignItems:'center', gap:5, padding:0 }}>
                  <Icon name="search" size={11} /> {open ? 'ocultar comparación' : 'comparar en CVE (NVD)'}
                </button>
              ) : null}
            </div>
          ) : null}

          {/* Live enrichment panel */}
          {open && (
            <div style={{ marginTop:10, padding:'10px 12px', background:'var(--bg-panel-2)', border:'1px solid var(--line)', borderRadius:7 }}>
              {enrich === 'loading' && <div className="mono" style={{ fontSize:11, color:'var(--txt-mute)' }}>Consultando bases de datos CVE…<span className="cursor-blink" /></div>}
              {enrich && enrich.__error && <div className="mono" style={{ fontSize:11, color:'var(--red)' }}>{enrich.__error}</div>}
              {enrich && typeof enrich === 'object' && !enrich.__error && cves.map(id => {
                const r = enrich[id];
                if (!r) return null;
                if (!r.found) return <div key={id} className="mono" style={{ fontSize:11, color:'var(--txt-mute)', marginBottom:8 }}>{id}: sin datos oficiales. <a href={r.links && r.links.nvd} target="_blank" rel="noopener noreferrer" style={{ color:'var(--acc)' }}>abrir NVD ↗</a></div>;
                return (
                  <div key={id} style={{ marginBottom:10, paddingBottom:10, borderBottom:'1px solid var(--line)' }}>
                    <div style={{ display:'flex', alignItems:'center', gap:8, flexWrap:'wrap', marginBottom:5 }}>
                      <span className="mono" style={{ fontSize:11.5, color:'var(--txt-hi)', fontWeight:600 }}>{id}</span>
                      {r.cvss_score != null && <span className={'badge ' + window.sevClass(cvssTone(r.cvss_score))} style={{ padding:'1px 7px' }}>CVSS {r.cvss_score}</span>}
                      <span className="mono" style={{ fontSize:10, color:'var(--txt-mute)' }}>fuente: {r.source}</span>
                    </div>
                    {r.description && <div style={{ fontSize:11.5, color:'var(--txt)', lineHeight:1.55, marginBottom:5 }}>{r.description}</div>}
                    {r.cvss_vector && <div className="mono" style={{ fontSize:10, color:'var(--txt-dim)', marginBottom:5, wordBreak:'break-all' }}>{r.cvss_vector}</div>}
                    <div style={{ display:'flex', gap:12, flexWrap:'wrap' }}>
                      {r.links && r.links.nvd && <a href={r.links.nvd} target="_blank" rel="noopener noreferrer" className="mono" style={{ fontSize:10.5, color:'var(--acc)', textDecoration:'none' }}>NVD ↗</a>}
                      {r.links && r.links.exploitdb && <a href={r.links.exploitdb} target="_blank" rel="noopener noreferrer" className="mono" style={{ fontSize:10.5, color:'var(--acc)', textDecoration:'none' }}>Exploit-DB ↗</a>}
                      {r.links && r.links.cve_org && <a href={r.links.cve_org} target="_blank" rel="noopener noreferrer" className="mono" style={{ fontSize:10.5, color:'var(--acc)', textDecoration:'none' }}>CVE.org ↗</a>}
                    </div>
                  </div>
                );
              })}
            </div>
          )}
        </div>
      </div>
    </div>
  );
}

function ScanRunner({ tool, onBack }) {
  window.usePlan && window.usePlan();   // re-render si cambia el plan
  const eng = window.redteamEngine(tool.id);
  const planned = eng.status === 'planned';
  const tierLocked = !planned && window.fsToolAllowed && !window.fsToolAllowed(tool.id);
  const blocked = planned || tierLocked;
  const fields = eng.fields || [];

  const initForm = () => {
    const o = {};
    const pre = (tool && tool.prefill) || {};
    fields.forEach(f => {
      const hasPre = Object.prototype.hasOwnProperty.call(pre, f.name);
      o[f.name] = hasPre ? pre[f.name]
        : (f.default !== undefined ? f.default : (f.type === 'toggle' ? false : ''));
    });
    return o;
  };

  const [phase, setPhase] = useStateL('config'); // config | running | done | error
  const [form, setForm] = useStateL(initForm);
  const [authorized, setAuthorized] = useStateL(false);
  const [lines, setLines] = useStateL([]);
  const [prog, setProg] = useStateL(0);
  const [result, setResult] = useStateL(null);
  const [error, setError] = useStateL('');
  const [showRaw, setShowRaw] = useStateL(false);
  const [elapsed, setElapsed] = useStateL(0);
  const termRef = useRefL(null);
  const timers = useRefL([]);
  const shimmerRef = useRefL(null);
  const tickRef = useRefL(null);
  const abortRef = useRefL(null);
  const jobRef = useRefL(null);

  const setField = (name, v) => setForm(o => ({ ...o, [name]: v }));
  const push = (t, c = '') => setLines(l => [...l, { t, c }]);

  useEffectL(() => { if (termRef.current) termRef.current.scrollTop = termRef.current.scrollHeight; }, [lines]);
  useEffectL(() => () => { timers.current.forEach(clearTimeout); if (shimmerRef.current) clearInterval(shimmerRef.current); if (tickRef.current) clearInterval(tickRef.current); }, []);

  const fmtElapsed = (s) => Math.floor(s / 60) + ':' + String(s % 60).padStart(2, '0');
  const cancelScan = async () => {
    const jid = jobRef.current;
    push('[!] cancelando escaneo…', 'warn');
    if (jid) { try { await window.fsApi('/api/redteam/job/' + jid + '/cancel', { method: 'POST' }); } catch (e) { /* noop */ } }
    if (abortRef.current) abortRef.current.abort();
  };

  const missingRequired = fields.filter(f => f.required && !String(form[f.name] ?? '').trim()).map(f => f.label);

  const buildPayload = () => {
    const p = {};
    fields.forEach(f => {
      let v = form[f.name];
      if (f.type === 'number') v = (v === '' || v == null) ? undefined : Number(v);
      else if (f.type === 'toggle') v = !!v;
      else if (typeof v === 'string') { v = v.trim(); if (!v) v = undefined; }
      if (v !== undefined) p[f.name] = v;
    });
    // Exploitation engines require an explicit authorization flag server-side.
    if (eng.exploit) p.authorized = !!authorized;
    return p;
  };

  const run = async () => {
    if (blocked) return;
    if (missingRequired.length) { setError('Completa: ' + missingRequired.join(', ')); return; }
    if (eng.intrusive && !authorized) { setError('Debes confirmar la autorización para escanear este objetivo.'); return; }
    setError(''); setResult(null); setShowRaw(false); setPhase('running'); setLines([]); setElapsed(0);

    const payload = buildPayload();
    const primary = fields[0] ? form[fields[0].name] : '';
    const t = timers.current; t.forEach(clearTimeout); t.length = 0;
    t.push(setTimeout(() => push('> flowshield-engine · ' + tool.name, 'acc'), 60));
    t.push(setTimeout(() => push('> objetivo: ' + primary, 'dim'), 260));
    t.push(setTimeout(() => push('[*] enviando solicitud al motor…', ''), 520));

    // Honest elapsed-time clock (no fake percentage), real cancellation.
    const jobId = (window.crypto && crypto.randomUUID) ? crypto.randomUUID()
      : 'job-' + Date.now() + '-' + Math.random().toString(16).slice(2);
    jobRef.current = jobId;
    const ac = new AbortController(); abortRef.current = ac;
    const t0 = Date.now();
    if (tickRef.current) clearInterval(tickRef.current);
    tickRef.current = setInterval(() => setElapsed(Math.floor((Date.now() - t0) / 1000)), 250);

    try {
      const data = await window.fsApi(eng.endpoint, { body: payload, headers: { 'X-Scan-Job': jobId }, signal: ac.signal });
      clearInterval(tickRef.current);
      const fnd = data.findings || [];
      Object.entries(data.summary || {}).forEach(([k, v]) => push(`> ${k}: ${v}`, 'dim'));
      push(`[+] ${fnd.length} hallazgo(s)`, fnd.length ? 'warn' : 'ok');
      push('> escaneo completado ✓', 'ok');
      setResult(data);
      t.push(setTimeout(() => setPhase('done'), 500));
    } catch (e) {
      clearInterval(tickRef.current);
      const aborted = e.name === 'AbortError' || (ac.signal && ac.signal.aborted);
      if (aborted) {
        push('[!] escaneo cancelado ✓', 'warn');
        setPhase('config');
      } else {
        push('[x] ' + (e.message || 'fallo del escaneo'), 'err');
        setError(e.message || 'Fallo del escaneo');
        setPhase('error');
      }
    } finally {
      jobRef.current = null; abortRef.current = null;
    }
  };

  const findings = (result && result.findings) || [];
  const counts = findings.reduce((a, f) => { const s = window.normSev(f.severity); a[s] = (a[s] || 0) + 1; return a; }, {});

  const [reporting, setReporting] = useStateL(false);
  const downloadReport = async () => {
    if (!result || reporting) return;
    setReporting(true);
    try {
      await window.fsDownloadPdf('/api/report/redteam', result);
    } catch (e) {
      push('[x] no se pudo generar el informe: ' + (e.message || e), 'err');
    } finally {
      setReporting(false);
    }
  };

  return (
    <div className={'enter theme-' + (tool.color || 'red')}>
      <button onClick={onBack} style={{ display:'inline-flex', alignItems:'center', gap:7, background:'none', border:'none', color:'var(--txt-dim)', cursor:'pointer', fontSize:12.5, marginBottom:18, fontFamily:'var(--font-mono)' }}>
        ← volver al catálogo
      </button>

      <div style={{ display:'flex', gap:16, alignItems:'center', marginBottom:24 }}>
        <div className="tool-ico" style={{ width:58, height:58 }}><Icon name={tool.icon} size={30} /></div>
        <div>
          <div className="mono upper" style={{ fontSize:10.5, color:'var(--acc)', letterSpacing:'.16em', marginBottom:4 }}>RED TEAM · {eng.exploit ? 'EXPLOTACIÓN' : 'ESCANEO'}</div>
          <h1 className="disp" style={{ fontSize:26, fontWeight:700, color:'var(--txt-hi)' }}>{tool.name}</h1>
        </div>
      </div>

      <div style={{ display:'grid', gridTemplateColumns:'1fr 1.15fr', gap:18 }}>
        {/* Config / target */}
        <div className="panel" style={{ padding:22, alignSelf:'start' }}>
          <div className="eyebrow" style={{ marginBottom:16 }}>Configuración del objetivo</div>

          {tierLocked ? (
            <div style={{ display:'flex', flexDirection:'column', gap:12, alignItems:'flex-start' }}>
              <span className="badge" style={{ color:'var(--acc)', borderColor:'color-mix(in srgb,var(--acc) 40%,transparent)', background:'var(--acc-soft)' }}><Icon name="lock" size={12} /> No incluido en tu plan</span>
              <div style={{ fontSize:13, color:'var(--txt-dim)', lineHeight:1.6 }}>Esta herramienta requiere el plan <strong style={{ color:'var(--acc)' }}>{window.fsPlanForTier ? window.fsPlanForTier(window.fsToolTier(tool.id)) : 'superior'}</strong> o superior. Tu plan actual es <strong style={{ color:'var(--txt-hi)' }}>{window.fsPlan ? window.fsPlan.name() : '—'}</strong>.</div>
              <div className="mono" style={{ fontSize:11, color:'var(--txt-mute)' }}>Cámbialo en Ajustes · Licencia.</div>
            </div>
          ) : planned ? (
            <div style={{ display:'flex', flexDirection:'column', gap:12, alignItems:'flex-start' }}>
              <span className="badge sev-info">Motor en preparación</span>
              <div style={{ fontSize:13, color:'var(--txt-dim)', lineHeight:1.6 }}>{eng.note || 'Esta herramienta aún no tiene un motor conectado.'}</div>
              <div className="mono" style={{ fontSize:11, color:'var(--txt-mute)' }}>El backend de FlowShield expondrá este escaneo en una próxima fase.</div>
            </div>
          ) : (
            <>
              <ScanFields fields={fields} form={form} setField={setField} disabled={phase === 'running'} />

              {eng.intrusive && <AuthGate on={authorized} disabled={phase === 'running'} onClick={() => setAuthorized(a => !a)} />}

              {error && (
                <div style={{ display:'flex', alignItems:'flex-start', gap:9, padding:'10px 12px', marginBottom:14, background:'var(--red-soft)', border:'1px solid color-mix(in srgb,var(--red) 32%,transparent)', borderRadius:8 }}>
                  <Icon name="alert" size={15} style={{ color:'var(--red)', flexShrink:0, marginTop:1 }} />
                  <span style={{ fontSize:12.5, color:'var(--txt)' }}>{error}</span>
                </div>
              )}

              {(phase === 'config' || phase === 'error') && <Btn variant="solid" size="block" icon="bolt" onClick={run} glitch>LANZAR ESCANEO</Btn>}
              {phase === 'running' && (
                <div>
                  <div style={{ display:'flex', justifyContent:'space-between', marginBottom:8 }}>
                    <span className="mono" style={{ fontSize:11, color:'var(--acc)' }}>ESCANEANDO…</span>
                    <span className="mono" style={{ fontSize:11, color:'var(--txt-hi)' }}>⏱ {fmtElapsed(elapsed)}</span>
                  </div>
                  <div className="bar fs-indet"><span /></div>
                  <Btn variant="ghost" size="block" icon="x" onClick={cancelScan} style={{ marginTop:12 }}>Cancelar escaneo</Btn>
                </div>
              )}
              {phase === 'done' && (
                <div style={{ display:'flex', gap:10 }}>
                  <Btn variant="ghost" icon="play" onClick={run} style={{ flex:1 }}>Re-escanear</Btn>
                  <Btn variant="solid" icon="report" onClick={downloadReport} style={{ flex:1 }}>{reporting ? 'Generando…' : 'Informe PDF'}</Btn>
                </div>
              )}
            </>
          )}
        </div>

        {/* Terminal + results */}
        <div style={{ display:'flex', flexDirection:'column', gap:18 }}>
          <div className="panel" style={{ padding:0, overflow:'hidden' }}>
            <div style={{ display:'flex', alignItems:'center', gap:8, padding:'10px 14px', borderBottom:'1px solid var(--line)', background:'var(--bg-panel-2)' }}>
              <span className="dot" style={{ background:'#FF5F57', width:10, height:10 }} /><span className="dot" style={{ background:'#FEBC2E', width:10, height:10 }} /><span className="dot" style={{ background:'#28C840', width:10, height:10 }} />
              <span className="mono" style={{ fontSize:11, color:'var(--txt-mute)', marginLeft:8 }}>flowshield@redteam: ~/{tool.id}</span>
            </div>
            <div ref={termRef} className="terminal" style={{ height:248, borderRadius:0, border:'none' }}>
              {(phase === 'config' || phase === 'error') && !lines.length && <div className="t-dim">// configura el objetivo y pulsa LANZAR ESCANEO…<span className="cursor-blink" /></div>}
              {lines.map((l, i) => l && <div key={i} className={'t-' + (l.c || '')} style={{ animation:'enter .2s forwards' }}>{l.t}</div>)}
              {phase === 'running' && <div className="cursor-blink" />}
            </div>
          </div>

          {phase === 'done' && result && (
            <div className="panel enter" style={{ padding:18 }}>
              <div style={{ display:'flex', justifyContent:'space-between', alignItems:'center', marginBottom:14 }}>
                <div className="eyebrow" style={{ margin:0 }}>Hallazgos · {findings.length}</div>
                <div style={{ display:'flex', gap:5 }}>
                  {['crit','high','med','low','info'].map(k => counts[k] ? <span key={k} className={'badge ' + window.sevClass(k)} style={{ padding:'2px 8px' }}>{window.sevLabel(k)} {counts[k]}</span> : null)}
                </div>
              </div>
              {findings.length ? (
                <div style={{ display:'grid', gap:8 }}>
                  {findings.map((r, i) => <FindingCard key={i} f={r} />)}
                </div>
              ) : (
                <div style={{ display:'flex', alignItems:'center', gap:10, padding:'14px', background:'var(--bg-void)', border:'1px solid var(--line)', borderRadius:8 }}>
                  <Icon name="check" size={16} style={{ color:'var(--ok)' }} />
                  <span style={{ fontSize:13, color:'var(--txt)' }}>Sin hallazgos para este objetivo.</span>
                </div>
              )}

              <button onClick={() => setShowRaw(s => !s)} className="mono" style={{ marginTop:14, background:'none', border:'none', color:'var(--txt-mute)', cursor:'pointer', fontSize:11, display:'inline-flex', alignItems:'center', gap:6 }}>
                <Icon name="chevron" size={12} style={{ transform: showRaw ? 'rotate(90deg)' : 'none', transition:'transform .18s' }} /> {showRaw ? 'ocultar' : 'ver'} respuesta cruda (JSON)
              </button>
              {showRaw && (
                <pre className="terminal" style={{ marginTop:10, fontSize:11, maxHeight:260, overflow:'auto', whiteSpace:'pre-wrap', wordBreak:'break-word' }}>{JSON.stringify(result.raw ?? result, null, 2)}</pre>
              )}
            </div>
          )}
        </div>
      </div>

      <style>{`
        .seg{flex:1;font-family:var(--font-display);font-weight:600;font-size:12px;padding:9px;border:1px solid;border-radius:7px;cursor:pointer;transition:all .18s}.seg:disabled{opacity:.5;cursor:default}
        .rt-sel{width:100%;font-family:var(--font-mono);font-size:12.5px;color:var(--txt-hi);background:var(--bg-void);border:1px solid var(--line-bright);border-radius:6px;padding:11px 13px;outline:none;cursor:pointer;transition:border-color .2s}
        .rt-sel:focus,.rt-sel:hover{border-color:var(--acc)}
        .bar.fs-indet{position:relative;overflow:hidden}
        .bar.fs-indet>span{position:absolute;top:0;bottom:0;width:38%;left:-38%;border-radius:inherit;animation:fsIndet 1.15s cubic-bezier(.65,.05,.36,1) infinite}
        @keyframes fsIndet{0%{left:-38%}100%{left:100%}}
      `}</style>
    </div>
  );
}

/* Desplegable personalizado con la estética de la web (sustituye el <select>
   nativo, que en Windows/Chrome abre una lista blanca fuera del tema). */
function FsSelect({ value, options, onChange, disabled, placeholder, style }) {
  const [open, setOpen] = useStateL(false);
  const ref = useRefL(null);
  useEffectL(() => {
    if (!open) return;
    const onDoc = (e) => { if (ref.current && !ref.current.contains(e.target)) setOpen(false); };
    const onKey = (e) => { if (e.key === 'Escape') setOpen(false); };
    document.addEventListener('mousedown', onDoc);
    document.addEventListener('keydown', onKey);
    return () => { document.removeEventListener('mousedown', onDoc); document.removeEventListener('keydown', onKey); };
  }, [open]);
  const opts = options || [];
  const sel = opts.find(o => String(o.value) === String(value));
  const label = sel ? sel.label : (placeholder || '—');
  return (
    <div ref={ref} style={Object.assign({ position:'relative' }, style || {})}>
      <button type="button" className="fssel-btn" disabled={disabled}
        onClick={() => { if (!disabled) setOpen(o => !o); }}>
        <span style={{ overflow:'hidden', textOverflow:'ellipsis', whiteSpace:'nowrap' }}>{label}</span>
        <Icon name="chevrond" size={14} style={{ flexShrink:0, color:'var(--txt-mute)', transform: open ? 'rotate(180deg)' : 'none', transition:'transform .15s' }} />
      </button>
      {open && (
        <div className="fssel-list hidden-scroll">
          {opts.map(o => (
            <button type="button" key={o.value} className={'fssel-opt' + (String(o.value) === String(value) ? ' on' : '')}
              onClick={() => { onChange(o.value); setOpen(false); }}>{o.label}</button>
          ))}
        </div>
      )}
      <style>{`
        .fssel-btn{width:100%;display:flex;align-items:center;justify-content:space-between;gap:8px;font-family:var(--font-mono);font-size:12.5px;color:var(--txt-hi);background:var(--bg-void);border:1px solid var(--line-bright);border-radius:6px;padding:11px 13px;cursor:pointer;transition:border-color .2s;text-align:left}
        .fssel-btn:hover:not(:disabled),.fssel-btn:focus{border-color:var(--acc);outline:none}
        .fssel-btn:disabled{opacity:.5;cursor:default}
        .fssel-list{position:absolute;top:calc(100% + 4px);left:0;right:0;z-index:80;max-height:240px;overflow-y:auto;background:var(--bg-panel);border:1px solid var(--line-bright);border-radius:8px;padding:5px;box-shadow:0 18px 50px -16px rgba(0,0,0,.75)}
        .fssel-opt{width:100%;text-align:left;font-family:var(--font-mono);font-size:12.5px;color:var(--txt-dim);background:none;border:none;border-radius:6px;padding:9px 11px;cursor:pointer;transition:background .14s,color .14s;display:block}
        .fssel-opt:hover{background:var(--bg-panel-2);color:var(--txt-hi)}
        .fssel-opt.on{color:var(--acc);background:color-mix(in srgb,var(--acc) 13%,transparent)}
      `}</style>
    </div>
  );
}
window.FsSelect = FsSelect;

function Toggle({ label, on, onClick, disabled }) {
  return (
    <button onClick={onClick} disabled={disabled} style={{ display:'flex', alignItems:'center', justifyContent:'space-between', background:'none', border:'none', cursor: disabled?'default':'pointer', padding:0, opacity: disabled?0.5:1, width:'100%', marginBottom:10 }}>
      <span style={{ fontSize:12.5, color:'var(--txt)' }}>{label}</span>
      <span style={{ width:38, height:21, borderRadius:99, background: on?'var(--acc)':'var(--bg-elevated)', border:'1px solid '+(on?'var(--acc)':'var(--line-bright)'), position:'relative', transition:'all .2s', boxShadow: on?'0 0 10px var(--acc-glow)':'none', flexShrink:0 }}>
        <span style={{ position:'absolute', top:2, left: on?19:2, width:15, height:15, borderRadius:99, background: on?'#04110D':'var(--txt-dim)', transition:'all .2s' }} />
      </span>
    </button>
  );
}

function RobotsPanel() {
  const [robots, setRobots] = useStateL(null);
  const [cat, setCat] = useStateL([]);
  const [editing, setEditing] = useStateL(null);
  const [run, setRun] = useStateL(null);
  const [askTarget, setAskTarget] = useStateL(null);   // robot pendiente de objetivo (modal)
  const [targetInput, setTargetInput] = useStateL('');
  const pollRef = useRefL(null);
  const launchingRef = useRefL(false);

  // Nombre legible de una tool: vive en las tarjetas de REDTEAM_TABS, no en
  // REDTEAM_ENGINES. Fallback al id si no se encuentra.
  const toolName = (id) => {
    let nm = null;
    (window.REDTEAM_TABS || []).forEach(tab => (tab.sections || []).forEach(sec =>
      (sec.tools || []).forEach(t => { if (t.id === id && t.name) nm = t.name; })));
    if (nm) return nm;
    // Fallback: prettify the id (fingerprint -> Fingerprint, url-fuzzer -> Url Fuzzer).
    return String(id || '').split('-').map(w => w ? w[0].toUpperCase() + w.slice(1) : w).join(' ');
  };

  // Icono de una tool (de su tarjeta en REDTEAM_TABS); null si no tiene tarjeta.
  const toolIcon = (id) => {
    let ic = null;
    (window.REDTEAM_TABS || []).forEach(tab => (tab.sections || []).forEach(sec =>
      (sec.tools || []).forEach(t => { if (t.id === id && t.icon) ic = t.icon; })));
    return ic;
  };
  // Icono representativo del robot: el de su herramienta más específica (orden de
  // prioridad: vulns/web > recon > utilidades). Distinto por tipo de robot.
  const _ROBOT_ICON_PRIORITY = ['wordpress-scanner', 'api-scanner', 'website-scanner',
    'url-fuzzer', 'vhost-finder', 'waf-detector', 'ssl-scanner', 'website-recon',
    'fingerprint', 'port-scanner', 'network-scanner', 'subdomain-finder',
    'domain-finder', 'whois', 'icmp-ping'];
  // Herramienta "principal" del robot (la más específica por prioridad, o la del 1er paso).
  const primaryTool = (r) => {
    const tools = (r.steps || []).map(s => s.tool);
    for (const id of _ROBOT_ICON_PRIORITY) {
      if (tools.includes(id)) return id;
    }
    return tools[0] || null;
  };
  const robotIcon = (r) => {
    const id = primaryTool(r);
    return id ? (toolIcon(id) || 'scan') : 'robot';
  };
  // Color de la tarjeta según el tipo de robot: web/vulns rojo, API teal,
  // SSL/WAF ámbar, recon azul (por defecto). Distinto y con significado.
  const _THEME_BY_TOOL = {
    'wordpress-scanner': 'red', 'website-scanner': 'red', 'url-fuzzer': 'red', 'vhost-finder': 'red',
    'api-scanner': 'fs',
    'ssl-scanner': 'amber', 'waf-detector': 'amber',
  };
  const robotTheme = (r) => 'theme-' + (_THEME_BY_TOOL[primaryTool(r)] || 'blue');

  const load = () => window.fsApi('/api/robots', { method:'GET' })
    .then(d => setRobots((d&&d.robots)||[])).catch(()=>setRobots([]));

  useEffectL(() => {
    load();
    window.fsApi('/api/robots/catalog',{method:'GET'}).then(d=>setCat((d&&d.tools)||[])).catch(()=>setCat([]));
  }, []);
  useEffectL(() => () => { if (pollRef.current) clearInterval(pollRef.current); }, []);

  const blank = () => ({ name:'', description:'', steps:[], default_target:'', schedule:{}, enabled:true });

  const save = async (robot) => {
    const method = robot.id ? 'PUT' : 'POST';
    const path = robot.id ? '/api/robots/'+robot.id : '/api/robots';
    let saved;
    try {
      saved = await window.fsApi(path, { method, body: robot });
    } catch (e) { window.alert('No se pudo guardar: ' + (e.message||e)); return; }
    setEditing(null); load();
    const sid = (saved && saved.id) || robot.id;
    if (sid && robot.schedule && robot.schedule.kind && robot.schedule.kind !== 'none') {
      try {
        await window.fsApi('/api/robots/'+sid+'/schedule', { method:'PUT', body:{ schedule: robot.schedule } });
        load();
      } catch (e) { window.alert('Robot guardado, pero el agendado no se pudo aplicar: ' + (e.message||e)); }
    }
  };

  const del = async (r) => {
    if (!window.confirm('¿Borrar el robot «'+r.name+'»?')) return;
    try { await window.fsApi('/api/robots/'+r.id,{method:'DELETE'}); load(); } catch(e){ window.alert(e.message||'Error'); }
  };

  const toggle = async (r) => {
    try { await window.fsApi('/api/robots/'+r.id, { method:'PUT', body:{ enabled:!r.enabled } }); load(); }
    catch(e){ load(); }
  };

  // Abre el modal de objetivo (en vez del prompt nativo del navegador).
  const launch = (r) => { setTargetInput(r.default_target || ''); setAskTarget(r); };

  const doLaunch = async () => {
    if (launchingRef.current) return;
    const r = askTarget;
    const target = (targetInput || '').trim();
    if (!r || !target) return;
    launchingRef.current = true;
    setAskTarget(null);
    try {
      const d = await window.fsApi('/api/robots/'+r.id+'/run', { body:{ target } });
      setRun({ robotId:r.id, runId:d.run_id, state:null });
      if (pollRef.current) clearInterval(pollRef.current);
      pollRef.current = setInterval(async () => {
        try {
          const st = await window.fsApi('/api/robots/runs/'+d.run_id, { method:'GET' });
          setRun(cur => cur && { ...cur, state: st });
          if (st.status !== 'running') { clearInterval(pollRef.current); load(); }
        } catch (e) { clearInterval(pollRef.current); }
      }, 1500);
    } catch (e) { window.alert('No se pudo lanzar: ' + (e.message||e)); }
    finally { launchingRef.current = false; }
  };

  const cancelRun = async () => {
    if (!run) return;
    try { await window.fsApi('/api/robots/runs/'+run.runId+'/cancel',{method:'POST'}); } catch(e){}
  };

  const seed = async () => {
    try { await window.fsApi('/api/robots/seed', { method:'POST' }); load(); }
    catch(e){ window.alert(e.message||'Error'); }
  };

  /* ── step helpers ── */
  const addStep = () => {
    if (!cat.length) return;
    const toolId = cat[0].id;
    setEditing(e => ({ ...e, steps: [...e.steps, { tool: toolId, params:{}, condition:{type:'always'} }] }));
  };

  const removeStep = (idx) => {
    setEditing(e => ({ ...e, steps: e.steps.filter((_,i)=>i!==idx) }));
  };

  const moveStep = (idx, dir) => {
    setEditing(e => {
      const steps = [...e.steps];
      const swap = idx + dir;
      if (swap < 0 || swap >= steps.length) return e;
      [steps[idx], steps[swap]] = [steps[swap], steps[idx]];
      return { ...e, steps };
    });
  };

  const setStepTool = (idx, toolId) => {
    setEditing(e => {
      const steps = e.steps.map((s,i) => i===idx ? { tool:toolId, params:{}, condition:{type:'always'} } : s);
      return { ...e, steps };
    });
  };

  const updateStepParam = (idx, name, val) => {
    setEditing(e => {
      const steps = e.steps.map((s,i) => i===idx ? { ...s, params:{ ...s.params, [name]:val } } : s);
      return { ...e, steps };
    });
  };

  const updateStepCondition = (idx, condition) => {
    setEditing(e => {
      const steps = e.steps.map((s,i) => i===idx ? { ...s, condition } : s);
      return { ...e, steps };
    });
  };

  /* ── step status rendering ── */
  const stepStatusBadge = (s) => {
    if (!s) return null;
    if (s.status === 'done') return <span className="badge sev-ok" style={{ marginLeft:8 }}>✓ done</span>;
    if (s.status === 'error') return <span className="badge sev-high" style={{ marginLeft:8 }}>✗ error</span>;
    if (s.status === 'skipped') return <span className="badge sev-info" style={{ marginLeft:8 }}>— skip</span>;
    if (s.status === 'running') return <span className="badge sev-med" style={{ marginLeft:8 }}>… running</span>;
    return <span className="badge" style={{ marginLeft:8 }}>{s.status}</span>;
  };

  /* ── builder panel ── */
  if (editing) {
    const canSave = editing.name.trim() && editing.steps.length > 0 &&
      editing.steps.every(s => {
        const c = s.condition || {};
        if (!c.type || c.type === 'always' || c.type === 'sev') return true;
        return !!(c.value && String(c.value).trim());
      });
    return (
      <div className="enter">
        <PageHeader title={editing.id ? 'Editar robot' : 'Crear robot'} crumb="RED TEAM / ROBOTS / EDITOR" />
        <div className="panel" style={{ padding:24, marginBottom:20 }}>
          <div className="eyebrow theme-fs" style={{ marginBottom:16 }}>{editing.id ? 'Editar robot' : 'Nuevo robot'}</div>

          {/* name */}
          <div style={{ marginBottom:16 }}>
            <label className="mono" style={{ fontSize:11, color:'var(--txt-dim)', display:'block', marginBottom:7 }}>Nombre *</label>
            <div className="field">
              <input className="input" type="text" value={editing.name} placeholder="Mi robot de recon…"
                onChange={e => setEditing(ed => ({ ...ed, name: e.target.value }))} />
            </div>
          </div>

          {/* description */}
          <div style={{ marginBottom:16 }}>
            <label className="mono" style={{ fontSize:11, color:'var(--txt-dim)', display:'block', marginBottom:7 }}>Descripción</label>
            <div className="field">
              <textarea className="input" rows={2} value={editing.description} placeholder="¿Qué hace este robot?"
                style={{ resize:'vertical' }}
                onChange={e => setEditing(ed => ({ ...ed, description: e.target.value }))} />
            </div>
          </div>

          {/* default target */}
          <div style={{ marginBottom:24 }}>
            <label className="mono" style={{ fontSize:11, color:'var(--txt-dim)', display:'block', marginBottom:7 }}>Objetivo por defecto (opcional)</label>
            <div className="field">
              <input className="input" type="text" value={editing.default_target} placeholder="ejemplo.com"
                onChange={e => setEditing(ed => ({ ...ed, default_target: e.target.value }))} />
            </div>
          </div>

          {/* steps */}
          <div className="eyebrow" style={{ marginBottom:12 }}>Pasos ({editing.steps.length})</div>
          {editing.steps.length === 0 && (
            <div className="mono" style={{ fontSize:12, color:'var(--txt-mute)', marginBottom:12, padding:'10px 14px', background:'var(--bg-void)', borderRadius:8, border:'1px dashed var(--line)' }}>
              Sin pasos — añade al menos uno.
            </div>
          )}
          {editing.steps.map((step, idx) => {
            const eng = window.REDTEAM_ENGINES && window.REDTEAM_ENGINES[step.tool];
            const extraFields = eng ? (eng.fields||[]).slice(1) : [];
            return (
              <div key={idx} className="panel" style={{ padding:16, marginBottom:12, background:'var(--bg-void)', border:'1px solid var(--line)' }}>
                <div style={{ display:'flex', alignItems:'center', gap:10, marginBottom: extraFields.length ? 12 : 0 }}>
                  <span className="mono" style={{ fontSize:11, color:'var(--txt-mute)', minWidth:22 }}>#{idx+1}</span>
                  <FsSelect style={{ flex:1 }} value={step.tool}
                    onChange={v => setStepTool(idx, v)}
                    options={cat.map(c => ({ value:c.id, label: toolName(c.id) + (c.tier > 1 ? ' · Purple+' : '') }))} />
                  <Btn size="sm" ghost onClick={()=>moveStep(idx,-1)} disabled={idx===0} title="Subir">
                    <Icon name="arrow-up" size={13} />
                  </Btn>
                  <Btn size="sm" ghost onClick={()=>moveStep(idx,1)} disabled={idx===editing.steps.length-1} title="Bajar">
                    <Icon name="arrow-down" size={13} />
                  </Btn>
                  <Btn size="sm" danger onClick={()=>removeStep(idx)} title="Eliminar paso">
                    <Icon name="x" size={13} />
                  </Btn>
                </div>
                {/* condition editor */}
                {idx === 0 ? (
                  <div style={{ paddingLeft:32, marginTop:8 }}>
                    <span className="mono" style={{ fontSize:11, color:'var(--txt-mute)' }}>Siempre se ejecuta (primer paso)</span>
                  </div>
                ) : (
                  <div style={{ paddingLeft:32, marginTop:8 }}>
                    <div style={{ display:'flex', alignItems:'center', gap:8, flexWrap:'wrap' }}>
                      <span className="mono" style={{ fontSize:11, color:'var(--txt-dim)', minWidth:64 }}>Condición</span>
                      <FsSelect style={{ minWidth:210 }} value={step.condition ? step.condition.type : 'always'}
                        onChange={t => updateStepCondition(idx, { type: t, value: t === 'sev' ? 'high' : '' })}
                        options={[
                          { value:'always', label:'Siempre' },
                          { value:'tech', label:'Si se detectó tecnología…' },
                          { value:'port', label:'Si hay puerto/servicio abierto…' },
                          { value:'sev', label:'Si severidad acumulada ≥…' },
                        ]} />
                      {(step.condition && (step.condition.type === 'tech' || step.condition.type === 'port')) && (
                        <div className="field" style={{ flex:1, minWidth:120 }}>
                          <input className="input" type="text"
                            placeholder={step.condition.type === 'tech' ? 'wordpress' : '443'}
                            value={step.condition.value || ''}
                            onChange={ev => updateStepCondition(idx, { ...step.condition, value: ev.target.value })} />
                        </div>
                      )}
                      {(step.condition && step.condition.type === 'sev') && (
                        <FsSelect style={{ minWidth:130 }} value={step.condition.value || 'high'}
                          onChange={v => updateStepCondition(idx, { ...step.condition, value: v })}
                          options={[
                            { value:'crit', label:'Crítica' },
                            { value:'high', label:'Alta' },
                            { value:'med', label:'Media' },
                            { value:'low', label:'Baja' },
                          ]} />
                      )}
                    </div>
                  </div>
                )}
                {extraFields.length > 0 && (
                  <div style={{ paddingLeft:32, marginTop:8 }}>
                    <ScanFields fields={extraFields} form={step.params}
                      setField={(n,v)=>updateStepParam(idx,n,v)} disabled={false} />
                  </div>
                )}
              </div>
            );
          })}

          <Btn ghost onClick={addStep} disabled={cat.length===0} style={{ marginBottom:24 }}>
            <Icon name="plus" size={14} /> Añadir paso
          </Btn>

          {/* schedule section */}
          {(() => {
            const sched = editing.schedule || {};
            const setSched = (patch) => setEditing(e => ({ ...e, schedule: { ...(e.schedule||{}), ...patch } }));
            return (
              <div style={{ marginBottom:24, paddingTop:16, borderTop:'1px solid var(--line)' }}>
                <div className="eyebrow" style={{ marginBottom:12 }}>Agendado</div>
                <div style={{ marginBottom:12 }}>
                  <label className="mono" style={{ fontSize:11, color:'var(--txt-dim)', display:'block', marginBottom:7 }}>Frecuencia</label>
                  <FsSelect value={sched.kind || 'none'} onChange={v => setSched({ kind: v })}
                    options={[
                      { value:'none', label:'Sin agendar' },
                      { value:'daily', label:'Diario' },
                      { value:'weekly', label:'Semanal' },
                    ]} />
                </div>
                {(sched.kind === 'daily' || sched.kind === 'weekly') && (
                  <div style={{ marginBottom:12 }}>
                    <label className="mono" style={{ fontSize:11, color:'var(--txt-dim)', display:'block', marginBottom:7 }}>Hora</label>
                    <div className="field">
                      <input className="input" type="time" value={sched.time || '03:00'}
                        onChange={e => setSched({ time: e.target.value })} />
                    </div>
                  </div>
                )}
                {sched.kind === 'weekly' && (
                  <div style={{ marginBottom:12 }}>
                    <label className="mono" style={{ fontSize:11, color:'var(--txt-dim)', display:'block', marginBottom:7 }}>Día de la semana</label>
                    <FsSelect value={String(sched.weekday ?? 0)} onChange={v => setSched({ weekday: Number(v) })}
                      options={['Lunes','Martes','Miércoles','Jueves','Viernes','Sábado','Domingo'].map((d,i) => ({ value:String(i), label:d }))} />
                  </div>
                )}
                {sched.kind && sched.kind !== 'none' && (
                  <>
                    <Toggle label="Activar agendado" on={!!sched.enabled} onClick={() => setSched({ enabled: !sched.enabled })} />
                    {!editing.default_target && (
                      <div className="mono" style={{ fontSize:11, color:'var(--txt-dim)', marginTop:6 }}>
                        Define un «Objetivo por defecto» para los escaneos agendados.
                      </div>
                    )}
                  </>
                )}
              </div>
            );
          })()}

          <div style={{ display:'flex', gap:10, justifyContent:'flex-end', borderTop:'1px solid var(--line)', paddingTop:16 }}>
            <Btn ghost onClick={()=>setEditing(null)}>Cancelar</Btn>
            <Btn primary onClick={()=>save(editing)} disabled={!canSave}>
              <Icon name="save" size={14} /> Guardar robot
            </Btn>
          </div>
        </div>
      </div>
    );
  }

  /* ── run monitor panel ── */
  const runPanel = run && (
    <div className="panel" style={{ padding:20, marginBottom:20, background:'var(--bg-void)', border:'1px solid var(--line)' }}>
      <div style={{ display:'flex', alignItems:'center', justifyContent:'space-between', marginBottom:14 }}>
        <div className="mono" style={{ fontSize:12, color:'var(--acc)' }}>
          <Icon name="cpu" size={13} style={{ marginRight:6 }} />
          Ejecución: <span style={{ color:'var(--txt-hi)' }}>{run.runId}</span>
          {run.state && <span className={'badge ' + (run.state.status==='done'?'sev-ok':run.state.status==='error'?'sev-high':'sev-med')} style={{ marginLeft:10 }}>{run.state.status}</span>}
        </div>
        <div style={{ display:'flex', gap:8 }}>
          {run.state && run.state.status === 'running' && (
            <Btn danger size="sm" onClick={cancelRun}><Icon name="x" size={13} /> Cancelar</Btn>
          )}
          <Btn ghost size="sm" onClick={()=>setRun(null)}><Icon name="x" size={13} /></Btn>
        </div>
      </div>
      {!run.state && (
        <div className="mono" style={{ fontSize:11, color:'var(--txt-mute)' }}>Iniciando…</div>
      )}
      {run.state && (run.state.steps_result||[]).map((sr, i) => (
        <div key={i} style={{ display:'flex', alignItems:'flex-start', gap:10, padding:'6px 0', borderBottom:'1px solid var(--line)' }}>
          <span className="mono" style={{ fontSize:10.5, color:'var(--txt-mute)', minWidth:22 }}>#{i+1}</span>
          <span className="mono" style={{ fontSize:11, color:'var(--txt-hi)', flex:1 }}>{toolName(sr.tool)}</span>
          {stepStatusBadge(sr)}
          {sr.reason && <span className="mono" style={{ fontSize:10.5, color:'var(--txt-dim)', marginLeft:8 }}>{sr.reason}</span>}
          {sr.counts && <span className="mono" style={{ fontSize:10.5, color:'var(--txt-mute)', marginLeft:8 }}>
            {Object.entries(sr.counts).map(([k,v])=>k+':'+v).join(' ')}
          </span>}
        </div>
      ))}
      {run.state && run.state.summary && (
        <div style={{ marginTop:12, padding:'10px 12px', background:'var(--bg-panel)', borderRadius:7, border:'1px solid var(--line-bright)' }}>
          <div className="mono" style={{ fontSize:10.5, color:'var(--txt-dim)', marginBottom:4 }}>RESUMEN</div>
          <div className="mono" style={{ fontSize:12, color:'var(--txt)' }}>
            {[
              run.state.summary.steps_run != null && `pasos ejecutados: ${run.state.summary.steps_run}`,
              run.state.summary.steps_total != null && `total: ${run.state.summary.steps_total}`,
              run.state.summary.steps_skipped != null && `omitidos: ${run.state.summary.steps_skipped}`,
              run.state.summary.findings_total != null && `hallazgos: ${run.state.summary.findings_total}`,
            ].filter(Boolean).join(' · ')}
          </div>
        </div>
      )}
    </div>
  );

  /* ── target modal (sustituye al window.prompt nativo) ── */
  const targetModal = askTarget && (
    <div onClick={()=>setAskTarget(null)}
      style={{ position:'fixed', inset:0, zIndex:9000, background:'rgba(3,5,10,.7)', backdropFilter:'blur(4px)', display:'grid', placeItems:'center', padding:20 }}>
      <div className="enter theme-fs" onClick={e=>e.stopPropagation()}
        style={{ width:'min(460px,94vw)', background:'var(--bg-panel)', border:'1px solid var(--line-bright)', borderRadius:14, overflow:'hidden', boxShadow:'0 30px 80px -20px rgba(0,0,0,.7)' }}>
        <div style={{ display:'flex', alignItems:'center', gap:10, padding:'15px 18px', borderBottom:'1px solid var(--line)' }}>
          <div className="tool-ico"><Icon name="robot" size={18} /></div>
          <div>
            <div className="disp" style={{ fontSize:15, fontWeight:700, color:'var(--txt-hi)' }}>Ejecutar «{askTarget.name}»</div>
            <div className="mono" style={{ fontSize:10.5, color:'var(--txt-mute)', marginTop:2 }}>{(askTarget.steps||[]).length} paso(s) · recon + vulns</div>
          </div>
        </div>
        <div style={{ padding:'18px' }}>
          <label className="mono" style={{ fontSize:11, color:'var(--txt-dim)', display:'block', marginBottom:7 }}>OBJETIVO (dominio / host / URL)</label>
          <div className="field">
            <Icon name="crosshair" size={15} style={{ position:'absolute', left:13, top:13, color:'var(--fs)' }} />
            <input className="input input-prefix" autoFocus value={targetInput} placeholder="ejemplo.com"
              onChange={e=>setTargetInput(e.target.value)}
              onKeyDown={e=>{ if(e.key==='Enter') doLaunch(); if(e.key==='Escape') setAskTarget(null); }} />
          </div>
          <div className="mono" style={{ fontSize:10, color:'var(--txt-mute)', marginTop:8 }}>El objetivo se aplica a todos los pasos del robot.</div>
          <div style={{ display:'flex', gap:10, justifyContent:'flex-end', marginTop:18 }}>
            <Btn ghost onClick={()=>setAskTarget(null)}>Cancelar</Btn>
            <Btn primary onClick={doLaunch} disabled={!targetInput.trim()}><Icon name="bolt" size={14} /> Lanzar</Btn>
          </div>
        </div>
      </div>
    </div>
  );

  /* ── main list view ── */
  return (
    <div className="enter">
      {targetModal}
      <div style={{ display:'flex', alignItems:'center', justifyContent:'space-between', marginBottom:20 }}>
        <PageHeader title="Robots de escaneo" crumb="RED TEAM / ROBOTS"
          sub="Flujos automatizados que encadenan varias herramientas con lógica condicional." />
        <Btn primary onClick={()=>setEditing(blank())} style={{ flexShrink:0 }}>
          <Icon name="plus" size={14} /> Crear robot
        </Btn>
      </div>

      {runPanel}

      {robots === null && (
        <div className="mono" style={{ fontSize:12, color:'var(--txt-mute)', padding:'24px 0', textAlign:'center' }}>
          <Icon name="loader" size={14} style={{ marginRight:6 }} />Cargando robots…
        </div>
      )}

      {robots && robots.length === 0 && (
        <div style={{ textAlign:'center', padding:'48px 0' }}>
          <div className="tool-ico theme-fs" style={{ width:56, height:56, margin:'0 auto 16px' }}>
            <Icon name="robot" size={28} />
          </div>
          <div style={{ fontSize:14, color:'var(--txt-hi)', marginBottom:6 }}>Sin robots configurados</div>
          <div style={{ fontSize:12, color:'var(--txt-dim)', marginBottom:20 }}>Crea tu primer robot para automatizar flujos de escaneo.</div>
          <div style={{ display:'inline-flex', gap:10 }}>
            <Btn primary onClick={()=>setEditing(blank())}><Icon name="plus" size={14} /> Crear robot</Btn>
            <Btn ghost onClick={seed}><Icon name="layers" size={14} /> Plantillas de ejemplo</Btn>
          </div>
        </div>
      )}

      {robots && robots.length > 0 && (
        <div style={{ display:'grid', gridTemplateColumns:'repeat(auto-fill, minmax(290px, 1fr))', gap:14 }}>
          {robots.map(r => {
            const stepNames = (r.steps||[]).map(s => toolName(s.tool));
            return (
              <div key={r.id} className={'tool-card ' + robotTheme(r)} style={{ flexDirection:'column' }}>
                <span className="bracket tl" /><span className="bracket tr" /><span className="bracket bl" /><span className="bracket br" />
                <div style={{ display:'flex', justifyContent:'space-between', width:'100%', alignItems:'center' }}>
                  <div className="tool-ico"><Icon name={robotIcon(r)} size={20} /></div>
                  <div style={{ display:'flex', alignItems:'center', gap:8 }}>
                    <span className="badge sev-info">{(r.steps||[]).length} paso{(r.steps||[]).length!==1?'s':''}</span>
                    {r.schedule && r.schedule.enabled && (r.schedule.kind === 'daily' || r.schedule.kind === 'weekly') && (
                      <span className="badge sev-info" title="Agendado">
                        <Icon name="clock" size={11} style={{ marginRight:3 }} />{r.schedule.kind === 'daily' ? 'diario' : 'semanal'}
                      </span>
                    )}
                    <Toggle on={r.enabled} onClick={()=>toggle(r)} />
                  </div>
                </div>
                <div className="tool-title" style={{ marginTop:12 }}>{r.name}</div>
                {r.description && <div className="tool-desc">{r.description}</div>}
                {stepNames.length > 0 && (
                  <div className="mono" style={{ fontSize:10.5, color:'var(--txt-mute)', marginTop:8, lineHeight:1.5 }}>
                    {stepNames.join(' → ')}
                  </div>
                )}
                {r.last_run && (
                  <div className="mono" style={{ fontSize:10, color:'var(--txt-mute)', marginTop:6 }}>
                    Último: {new Date(r.last_run).toLocaleString()}
                  </div>
                )}
                <div style={{ display:'flex', gap:6, marginTop:14, width:'100%' }}>
                  <Btn primary size="sm" onClick={()=>launch(r)} style={{ flex:1 }}>
                    <Icon name="play" size={13} /> Ejecutar
                  </Btn>
                  <Btn ghost size="sm" onClick={()=>setEditing({...r})} title="Editar">
                    <Icon name="edit" size={13} />
                  </Btn>
                  <Btn danger size="sm" onClick={()=>del(r)} title="Borrar">
                    <Icon name="trash" size={13} />
                  </Btn>
                </div>
              </div>
            );
          })}
        </div>
      )}
    </div>
  );
}

function RedTeam({ initialTool }) {
  const [tab, setTab] = useStateL('quick');
  const [q, setQ] = useStateL('');
  const [active, setActive] = useStateL(null);

  useEffectL(() => { if (initialTool) setActive(initialTool); }, [initialTool]);

  if (active) return (
    <div>
      <PageHeader title="Nuevo escaneo" crumb="RED TEAM / ESCANEOS / NUEVO" />
      <ScanRunner tool={active} onBack={()=>setActive(null)} />
    </div>
  );

  const current = window.REDTEAM_TABS.find(t => t.id === tab);
  const filterTools = (tools) => q ? tools.filter(t => (t.name+t.desc).toLowerCase().includes(q.toLowerCase())) : tools;

  return (
    <div className="enter theme-red">
      <PageHeader title="Nuevo escaneo" crumb="RED TEAM / ESCANEOS / NUEVO"
        sub="Arsenal ofensivo: reconocimiento, escaneo de vulnerabilidades y explotación controlada." />

      <div className="tabs" style={{ marginBottom:24 }}>
        {window.REDTEAM_TABS.map(t => (
          <button key={t.id} className={'tab' + (tab===t.id?' active':'')} onClick={()=>setTab(t.id)}>{t.label}</button>
        ))}
      </div>

      {tab !== 'robots' && tab !== 'advisor' && (
        <div className="field" style={{ marginBottom:26, maxWidth:'100%' }}>
          <Icon name="search" size={16} style={{ position:'absolute', left:14, top:14, color:'var(--txt-mute)' }} />
          <input className="input input-prefix" placeholder="Búsqueda rápida de herramientas…" value={q} onChange={e=>setQ(e.target.value)} style={{ padding:'13px 14px 13px 40px', background:'var(--bg-panel)' }} />
        </div>
      )}

      {tab === 'advisor' ? (
        <AiAdvisor faction="redteam" onOpenModule={(id) => {
          let tool = null;
          (window.REDTEAM_TABS || []).forEach(t => (t.sections || []).forEach(s => (s.tools || []).forEach(x => { if (x.id === id) tool = x; })));
          if (!tool) tool = { id, name: id, icon: 'scan', color: 'red' };
          setActive(tool);
        }} />
      ) : tab === 'robots' ? <RobotsPanel /> : (
        <div className="stagger">
          {current.sections.map((sec, si) => {
            const tools = filterTools(sec.tools);
            if (!tools.length) return null;
            return (
              <div key={si} style={{ marginBottom:30, animationDelay:(si*0.05)+'s' }}>
                <div className="eyebrow">{sec.title}</div>
                <div style={{ display:'grid', gridTemplateColumns:'repeat(auto-fill, minmax(290px, 1fr))', gap:14 }}>
                  {tools.map(t => <ToolCard key={t.id} tool={t} onClick={()=>setActive(t)} />)}
                </div>
              </div>
            );
          })}
        </div>
      )}
    </div>
  );
}

Object.assign(window, { RedTeam, ScanRunner });
