// ====================================================================
// SECTIONS — las 6 vistas del war room
// ====================================================================

// ── Traducción automática (Google Translate API, sin clave, con caché) ──
// Caché global en memoria: { 'lang_texto_es': 'traducción' }
const _TL_CACHE = {};
window._TL_CACHE = _TL_CACHE;

// Cache global en memoria para traducción de Deep Research a inglés
if (!window._DR_EN_CACHE) {
  window._DR_EN_CACHE = { content: {}, analysis: {} };
}

// Helper para traducción rápida de textos inline (ES, ZH, EN)
const tS = (lang, es, zh, en) => {
  if (lang === 'zh') return zh;
  if (lang === 'en') return en || es;
  return es;
};

// Hook to translate a Chinese search query to Spanish asynchronously
function useTranslateQuery(q) {
  const [translated, setTranslated] = React.useState("");

  React.useEffect(() => {
    if (!q) {
      setTranslated("");
      return;
    }

    const hasChinese = /[\u4e00-\u9fa5]/.test(q);
    if (!hasChinese) {
      setTranslated("");
      return;
    }

    if (!window._ZH_TO_ES_CACHE) {
      window._ZH_TO_ES_CACHE = {};
    }

    if (window._ZH_TO_ES_CACHE[q]) {
      setTranslated(window._ZH_TO_ES_CACHE[q]);
      return;
    }

    let active = true;
    const url = `https://translate.googleapis.com/translate_a/single?client=gtx&sl=zh-CN&tl=es&dt=t&q=${encodeURIComponent(q)}`;
    fetch(url)
      .then(res => res.json())
      .then(data => {
        if (!active) return;
        const result = (Array.isArray(data[0]) ? data[0] : [])
          .map(x => (Array.isArray(x) && x[0]) ? x[0] : '')
          .join('').trim().toLowerCase();
        if (result) {
          window._ZH_TO_ES_CACHE[q] = result;
          setTranslated(result);
        }
      })
      .catch(() => {});

    return () => { active = false; };
  }, [q]);

  return translated;
}

// Cola de peticiones pendientes para no saturar la API
let _TL_QUEUE = [];
let _TL_TIMER = null;

function _scheduleTranslate(text, targetLang, callback) {
  const cacheKey = targetLang + "_" + text;
  if (!text || _TL_CACHE[cacheKey]) {
    if (_TL_CACHE[cacheKey]) callback(_TL_CACHE[cacheKey]);
    return;
  }
  _TL_QUEUE.push({ text, targetLang, callback });
  if (_TL_TIMER) clearTimeout(_TL_TIMER);
  _TL_TIMER = setTimeout(async () => {
    const batch = [..._TL_QUEUE];
    _TL_QUEUE = [];
    _TL_TIMER = null;
    for (const item of batch) {
      const key = item.targetLang + "_" + item.text;
      if (_TL_CACHE[key]) {
        item.callback(_TL_CACHE[key]);
        continue;
      }
      try {
        const tlCode = item.targetLang === 'zh' ? 'zh-CN' : 'en';
        const url = `https://translate.googleapis.com/translate_a/single?client=gtx&sl=es&tl=${tlCode}&dt=t&q=${encodeURIComponent(item.text)}`;
        const r = await fetch(url);
        if (!r.ok) continue;
        const data = await r.json();
        const translated = (Array.isArray(data[0]) ? data[0] : [])
          .map(x => (Array.isArray(x) && x[0]) ? x[0] : '')
          .join('');
        if (translated) {
          _TL_CACHE[key] = translated;
          item.callback(translated);
        }
      } catch (_) { /* silencioso */ }
    }
  }, 80); // batch con 80ms de debounce
}

// Hook: devuelve el texto traducido al idioma destino (o el original si lang === 'es')
function useAutoTranslate(text, lang) {
  const needsTranslation = lang === 'zh' || lang === 'en';
  const cacheKey = needsTranslation ? (lang + "_" + text) : null;
  const [translated, setTranslated] = React.useState(() => (needsTranslation && _TL_CACHE[cacheKey]) ? _TL_CACHE[cacheKey] : null);

  React.useEffect(() => {
    if (!needsTranslation || !text) { setTranslated(null); return; }
    if (_TL_CACHE[cacheKey]) { setTranslated(_TL_CACHE[cacheKey]); return; }
    setTranslated(null);
    _scheduleTranslate(text, lang, (res) => {
      setTranslated(res);
    });
  }, [text, lang, cacheKey]);

  if (!needsTranslation) return text;
  return translated || text; // muestra texto original mientras carga
}

// Componente de traducción automática con indicador sutil
function AutoTranslate({ text, lang, style, className }) {
  const translated = useAutoTranslate(text, lang);
  const isLoading = (lang === 'zh' || lang === 'en') && translated === text && !_TL_CACHE[lang + "_" + text];
  return (
    <span className={className} style={{ ...style, opacity: isLoading ? 0.7 : 1, transition: 'opacity .3s' }}>
      {translated}
    </span>
  );
}

// Función auxiliar para traducir markdown completo a inglés por fragmentos
async function translateMarkdownToEn(mdText) {
  if (!mdText) return '';
  const lines = mdText.split('\n');
  const translatedLines = [];
  let currentBatch = [];
  let currentLen = 0;

  const translateChunk = async (chunkLines) => {
    if (chunkLines.length === 0) return [];
    const textToTranslate = chunkLines.join('\n');
    const url = `https://translate.googleapis.com/translate_a/single?client=gtx&sl=es&tl=en&dt=t&q=${encodeURIComponent(textToTranslate)}`;
    try {
      const res = await fetch(url);
      if (!res.ok) return chunkLines;
      const data = await res.json();
      const translated = (Array.isArray(data[0]) ? data[0] : [])
        .map(x => (Array.isArray(x) && x[0]) ? x[0] : '')
        .join('');
      return translated ? translated.split('\n') : chunkLines;
    } catch (e) {
      console.warn("Failed translating chunk:", e);
      return chunkLines;
    }
  };

  for (const line of lines) {
    if (line.trim().length === 0) {
      if (currentBatch.length > 0) {
        const tls = await translateChunk(currentBatch);
        translatedLines.push(...tls);
        currentBatch = [];
        currentLen = 0;
      }
      translatedLines.push(line);
      continue;
    }
    currentBatch.push(line);
    currentLen += line.length + 1;
    if (currentLen > 800) {
      const tls = await translateChunk(currentBatch);
      translatedLines.push(...tls);
      currentBatch = [];
      currentLen = 0;
    }
  }

  if (currentBatch.length > 0) {
    const tls = await translateChunk(currentBatch);
    translatedLines.push(...tls);
  }

  return translatedLines.join('\n');
}

// render excerpt con <hl>resaltado</hl>
function Excerpt({ text }) {
  const parts = String(text).split(/(<hl>.*?<\/hl>)/g);
  return <>{parts.map((p, i) => {
    const m = p.match(/^<hl>(.*?)<\/hl>$/);
    return m ? <span key={i} className="hl">{m[1]}</span> : <span key={i}>{p}</span>;
  })}</>;
}

function topicLabel(t, topic) { return t(TOPIC_KEYS[topic] || topic); }

// Periodo del snapshot (mes que muestra el dashboard) — lo calcula api.jsx
// desde los datos reales; aquí solo se lee, con fallback seguro.
function getPeriod() {
  return (window.MOCK && window.MOCK.PERIOD) || { month: '2026-05', labelEs: 'Mayo 2026', labelZh: '2026年5月', days: 31 };
}

// ---------- helpers de mención ----------
function MentionMeta({ m }) {
  const { t, lang } = useLang();
  return (
    <div className="meta">
      <SourceTag platform={m.platform} name={m.author} />
      <span style={{ color: "var(--ice-faint)", fontSize: 11.5 }}>{m.handle}</span>
      <span className="badge neu" style={{ background: "transparent", color: "var(--ice-dim)", padding: 0 }}>· {topicLabel(t, m.topic)}</span>
      <SentBadge s={m.sentiment} t={t} severity={m.severity} />
    </div>
  );
}

// ====================================================================
// 1 · RESUMEN
// ====================================================================
// ---------- helper: Qcrece Daily Trend Chart ----------
function QcreceDailyChart({ data }) {
  const W = 500, H = 160;
  const padL = 38, padR = 20, padT = 15, padB = 25;
  const innerW = W - padL - padR, innerH = H - padT - padB;
  
  const maxCount = Math.max(1, ...data.map(d => d.count));
  const getX = (day) => padL + ((day - 1) / 30) * innerW;
  const getYCount = (count) => padT + innerH - (count / maxCount) * innerH;
  const getYRating = (rating) => padT + innerH - (rating / 5) * innerH;
  
  const ratingPoints = data.filter(d => d.avg > 0);
  const ratingPath = ratingPoints.map((d, idx) => {
    return `${idx === 0 ? 'M' : 'L'} ${getX(d.day).toFixed(1)} ${getYRating(d.avg).toFixed(1)}`;
  }).join(' ');

  return (
    <svg viewBox={`0 0 ${W} ${H}`} width="100%" height={H}>
      {/* Grid Lines + Y Axis Labels */}
      {[0, 0.25, 0.5, 0.75, 1].map((g, idx) => {
        const yVal = Math.round(maxCount * (1 - g));
        return (
          <g key={idx}>
            <line
              x1={padL}
              x2={W - padR}
              y1={padT + innerH * g}
              y2={padT + innerH * g}
              stroke="rgba(255,255,255,0.06)"
              strokeWidth="1"
            />
            <text
              x={padL - 5}
              y={padT + innerH * g + 3}
              fill="rgba(180,200,240,0.45)"
              fontSize="8"
              fontFamily="var(--font-mono)"
              textAnchor="end"
            >
              {yVal > 0 ? yVal : ''}
            </text>
          </g>
        );
      })}
      
      {/* Volume Bars */}
      {data.map((d) => {
        const barW = Math.max(2, innerW / 31 - 2);
        const barH = (d.count / maxCount) * innerH;
        const x = getX(d.day) - barW / 2;
        const y = padT + innerH - barH;
        return (
          <rect 
            key={d.day} 
            x={x} 
            y={y} 
            width={barW} 
            height={barH} 
            fill="rgba(47, 111, 224, 0.3)" 
            rx="1" 
          />
        );
      })}

      {/* Average Rating Line */}
      {ratingPath && (
        <>
          <path 
            d={ratingPath} 
            fill="none" 
            stroke="var(--brand)" 
            strokeWidth="2.5" 
            strokeLinecap="round" 
            strokeLinejoin="round" 
          />
          {ratingPoints.map(d => (
            <circle 
              key={d.day} 
              cx={getX(d.day)} 
              cy={getYRating(d.avg)} 
              r="3" 
              fill="var(--brand)" 
              stroke="var(--navy-bg)" 
              strokeWidth="1.5"
            />
          ))}
        </>
      )}

      {/* X Axis Labels */}
      {[1, 5, 10, 15, 20, 25, 30].map(day => (
        <text 
          key={day} 
          x={getX(day)} 
          y={H - 6} 
          fill="var(--ice-faint)" 
          fontSize="9" 
          fontFamily="var(--font-mono)"
          textAnchor="middle"
        >
          {day}
        </text>
      ))}
    </svg>
  );
}

// ====================================================================
// 1 · RESUMEN
// ====================================================================
function Summary({ onOpen }) {
  const { t, lang } = useLang();
  
  // Periodo del snapshot (mes ingerido por la última corrida)
  const P = getPeriod();

  // Filtrar reseñas de crece de playstore del mes del snapshot
  const qcreceReviews = React.useMemo(() => {
    return MOCK.MENTIONS.filter(
      (m) => m.entity === "crece" && m.platform === "playstore" && m.publishedAt && m.publishedAt.startsWith(P.month)
    );
  }, [MOCK.MENTIONS, P.month]);

  const [sentimentFilter, setSentimentFilter] = React.useState("all");
  const [searchQuery, setSearchQuery] = React.useState("");
  const translatedQ = useTranslateQuery(searchQuery);
  const [dayFrom, setDayFrom] = React.useState(1);
  const [dayTo, setDayTo] = React.useState(P.days);
  const [reviewPage, setReviewPage] = React.useState(0);
  const REVIEWS_PER_PAGE = 25;
  React.useEffect(() => setReviewPage(0), [sentimentFilter, searchQuery, dayFrom, dayTo]);

  // Calcular métricas
  const totalReviews = qcreceReviews.length;
  
  const ratings = React.useMemo(() => {
    return qcreceReviews.map(r => {
      const match = (r.handle || '').match(/★\s*([0-9.]+)/);
      return match ? parseFloat(match[1]) : null;
    }).filter(v => v !== null);
  }, [qcreceReviews]);

  const avgRating = React.useMemo(() => {
    return ratings.length ? (ratings.reduce((a, b) => a + b, 0) / ratings.length).toFixed(1) : "0.0";
  }, [ratings]);

  const positivePct = React.useMemo(() => {
    const positiveCount = qcreceReviews.filter(r => r.sentiment === "pos" || r.sentiment === "positive").length;
    return totalReviews ? Math.round((positiveCount / totalReviews) * 100) : 0;
  }, [qcreceReviews, totalReviews]);

  const negativePct = React.useMemo(() => {
    const negativeCount = qcreceReviews.filter(r => r.sentiment === "neg" || r.sentiment === "negative").length;
    return totalReviews ? Math.round((negativeCount / totalReviews) * 100) : 0;
  }, [qcreceReviews, totalReviews]);



  // Distribución de calificaciones (5 estrellas a 1 estrella)
  const starDistribution = React.useMemo(() => {
    const counts = [0, 0, 0, 0, 0];
    ratings.forEach(val => {
      const idx = Math.min(5, Math.max(1, Math.round(val))) - 1;
      counts[idx]++;
    });
    return counts.reverse().map((count, idx) => {
      const stars = 5 - idx;
      const pct = totalReviews ? Math.round((count / totalReviews) * 100) : 0;
      return { stars, count, pct };
    });
  }, [ratings, totalReviews]);

  // Tendencia diaria (días del mes del snapshot)
  const dailyData = React.useMemo(() => {
    return Array.from({ length: P.days }, (_, i) => {
      const day = i + 1;
      const dayStr = String(day).padStart(2, '0');
      const dayReviews = qcreceReviews.filter(r => r.publishedAt && r.publishedAt.startsWith(`${P.month}-${dayStr}`));
      const dayRatings = dayReviews.map(r => {
        const match = (r.handle || '').match(/★\s*([0-9.]+)/);
        return match ? parseFloat(match[1]) : null;
      }).filter(v => v !== null);
      const avg = dayRatings.length ? (dayRatings.reduce((a, b) => a + b, 0) / dayRatings.length) : 0;
      return { day, count: dayReviews.length, avg };
    });
  }, [qcreceReviews]);

  // Filtros estructurales para la nube de palabras (sin incluir búsqueda)
  const baseFilteredReviews = React.useMemo(() => {
    const dFrom = Math.max(1, Math.min(P.days, Number(dayFrom) || 1));
    const dTo   = Math.max(1, Math.min(P.days, Number(dayTo)   || P.days));
    return qcreceReviews.filter(r => {
      if (sentimentFilter !== "all" && r.sentiment !== sentimentFilter) return false;
      // Filtro de día — día UTC del ISO, igual que el badge del mes
      if (r.publishedAt) {
        const day = parseInt(r.publishedAt.slice(8, 10), 10);
        if (day < dFrom || day > dTo) return false;
      }
      return true;
    });
  }, [qcreceReviews, sentimentFilter, dayFrom, dayTo]);

  // Filtrado final incluyendo búsqueda
  const filteredReviews = React.useMemo(() => {
    const q = searchQuery.toLowerCase().trim();
    return baseFilteredReviews.filter(r => {
      if (q) {
        const authorMatch = (r.author || "").toLowerCase().includes(q);
        const esText = (L(r.excerpt, 'es') || "").toLowerCase();
        const originalText = L(r.excerpt, 'zh');
        const translatedText = window._TL_CACHE?.[originalText] || "";
        const qEs = window._WC_TRANSLATIONS?.zhToEs?.[q] || translatedQ || q;
        const textMatch = esText.includes(q) || 
                          translatedText.toLowerCase().includes(q) || 
                          esText.includes(qEs);
        if (!authorMatch && !textMatch) return false;
      }
      return true;
    });
  }, [baseFilteredReviews, searchQuery, translatedQ, lang]);

  return (
    <div className="fade-in">
      {/* ── Banner de Cabecera ─────────────────────────────── */}
      <div className="scan-banner" style={{ background: 'linear-gradient(135deg, rgba(255,255,255,0.06), rgba(255,255,255,0.02))', marginBottom: 18 }}>
        <div style={{ display: 'flex', alignItems: 'center', gap: 12 }}>
          <div className="logo" style={{ background: '#fff', padding: 5, width: 34, height: 34, borderRadius: 9, display: 'flex', alignItems: 'center', justifyContent: 'center' }}>
            <img src="/logos/qcrece.png" style={{ width: '100%', height: '100%', objectFit: 'contain' }} />
          </div>
          <div>
            <div style={{ fontSize: 14, fontWeight: 700, color: 'var(--white)' }}>
              {tS(lang, `Análisis de Reseñas Qcrece · Play Store México · ${P.labelEs}`, `Qcrece · 墨西哥用户评价深度分析 · ${P.labelZh}`, `Qcrece Review Analysis · Play Store Mexico · ${P.labelEn || P.labelEs}`)}
            </div>
            <div style={{ fontSize: 11, color: 'var(--ice-faint)', marginTop: 3, display: 'flex', alignItems: 'center', gap: 10 }}>
              <span style={{ display: 'inline-flex', alignItems: 'center', gap: 4 }}>
                <img src="https://upload.wikimedia.org/wikipedia/commons/7/78/Google_Play_Store_badge_EN.svg" style={{ height: 14 }} alt="Play Store" />
                {tS(lang, `Google Play Store MX — reseñas verificadas de ${P.labelEs.toLowerCase()}`, '仅 Google Play Store MX 渠道', `Google Play Store MX — verified reviews from ${(P.labelEn || P.labelEs).toLowerCase()}`)}
              </span>
              <span style={{ color: 'var(--border-strong)' }}>|</span>
              <span>México</span>
            </div>
          </div>
        </div>
      </div>

      {/* ── KPI Grid ──────────────────────────────────────────────────── */}
      {(() => {
        const Tilt3D = window.Tilt3D || (({ children, ...p }) => <div {...p}>{children}</div>);
        return (
          <div className="grid kpi-row" style={{ marginBottom: 18 }}>
            <Tilt3D className="card kpi" max={8}>
              <span className="accent-bar" style={{ background: "var(--blue)" }}></span>
              <div className="kpi-top">
                <span className="kpi-ic" style={{ background: "var(--blue-bg)", color: "var(--blue)" }}><Ic name="mentions" /></span>
                <div className="label">{tS(lang, 'Total Reseñas', '总评价量', 'Total Reviews')}</div>
              </div>
              <div className="value">{totalReviews}</div>
              <div style={{ fontSize: 10, color: 'var(--ice-faint)', marginTop: 4 }}>{tS(lang, 'Play Store MX', '仅Play Store渠道', 'Play Store MX')}</div>
            </Tilt3D>

            <Tilt3D className="card kpi" max={8}>
              <span className="accent-bar" style={{ background: "var(--brand)" }}></span>
              <div className="kpi-top">
                <span className="kpi-ic" style={{ background: "var(--pos-bg)", color: "var(--brand)" }}><Ic name="spark" /></span>
                <div className="label">{tS(lang, 'Calificación Promedio', '平均评分', 'Average Rating')}</div>
              </div>
              <div className="value" style={{ color: 'var(--white)' }}>{avgRating} <span style={{ fontSize: 20, color: 'var(--ice-dim)' }}>★</span></div>
            </Tilt3D>

            <Tilt3D className="card kpi" max={8}>
              <span className="accent-bar" style={{ background: "var(--brand)" }}></span>
              <div className="kpi-top">
                <span className="kpi-ic" style={{ background: "var(--pos-bg)", color: "var(--brand)" }}><Ic name="check" /></span>
                <div className="label">{tS(lang, 'Reseñas Positivas', '好评率 (4-5★)', 'Positive Reviews (4-5★)')}</div>
              </div>
              <div className="value" style={{ color: 'var(--brand)' }}>{positivePct}<span style={{ fontSize: 18, color: 'var(--ice-dim)' }}>%</span></div>
              <div style={{ fontSize: 10, color: 'var(--ice-faint)', marginTop: 4 }}>{negativePct}% {tS(lang, 'reseñas críticas (1-2★)', '差评率', 'critical reviews (1-2★)')}</div>
            </Tilt3D>
          </div>
        );
      })()}

      {/* ── Gráficos ──────────────────────────────────────────────────── */}
      <div className="comp-cols" style={{ marginBottom: 18 }}>
        {/* Distribución de estrellas */}
        <div className="card card-pad">
          <div className="card-head">
            <h3>{tS(lang, 'Distribución de Calificaciones', '评分分布比例', 'Rating Distribution')}</h3>
          </div>
          <div style={{ display: 'flex', flexDirection: 'column', gap: 11, marginTop: 14 }}>
            {starDistribution.map((row) => (
              <div key={row.stars} style={{ display: 'flex', alignItems: 'center', gap: 12 }}>
                <div style={{ width: 32, fontSize: 12, fontWeight: 700, color: 'var(--ice-dim)', textAlign: 'right' }}>
                  {row.stars} ★
                </div>
                <div style={{ flex: 1, height: 12, background: 'rgba(160,180,220,.08)', borderRadius: 3, overflow: 'hidden' }}>
                  <div style={{ 
                    width: `${row.pct}%`, 
                    height: '100%', 
                    background: row.stars >= 4 ? 'var(--brand)' : row.stars === 3 ? 'var(--gold)' : 'var(--neg)', 
                    borderRadius: 3 
                  }}></div>
                </div>
                <div style={{ width: 70, fontFamily: 'var(--font-mono)', fontSize: 12, color: 'var(--ice)', textAlign: 'right' }}>
                  {row.pct}% <span style={{ fontSize: 10, color: 'var(--ice-faint)' }}>({row.count})</span>
                </div>
              </div>
            ))}
          </div>
        </div>

        {/* Tendencia mensual */}
        <div className="card card-pad">
          <div className="card-head">
            <h3>{tS(lang, 'Volumen Diario y Calificación Promedio', '每日评价量与平均评分走势', 'Daily Volume and Average Rating Trend')}</h3>
          </div>
          <div style={{ marginTop: 8 }}>
            <QcreceDailyChart data={dailyData} />
            <div style={{ display: 'flex', gap: 16, justifyContent: 'center', marginTop: 8, fontSize: 10, color: 'var(--ice-faint)' }}>
              <span style={{ display: 'inline-flex', alignItems: 'center', gap: 5 }}>
                <span style={{ width: 8, height: 8, background: 'rgba(47, 111, 224, 0.35)', borderRadius: 1 }}></span>
                {tS(lang, 'Volumen de Reseñas (Barras)', '评价数量 (Barra)', 'Review Volume (Bars)')}
              </span>
              <span style={{ display: 'inline-flex', alignItems: 'center', gap: 5 }}>
                <span style={{ width: 14, height: 2, background: 'var(--brand)' }}></span>
                {tS(lang, 'Calificación Promedio (Línea)', '平均评分 (Línea)', 'Average Rating (Line)')}
              </span>
            </div>
          </div>
        </div>
      </div>

      {/* Mapa de palabras — frecuencia en las reseñas filtradas; clic = filtrar */}
      <WordCloud
        reviews={baseFilteredReviews}
        lang={lang}
        active={searchQuery}
        onWord={(word) => {
          setSearchQuery(searchQuery === word ? "" : word);
        }}
      />

      {/* ── Tabla de Reseñas Detalladas ─────────────────────────────── */}
      <div className="card card-pad">
        <div className="card-head" style={{ marginBottom: 14, flexWrap: 'wrap', gap: 12 }}>
          <div>
            <div style={{ display: 'flex', alignItems: 'center', gap: 10 }}>
              <h3>{tS(lang, 'Registro de Evaluaciones', '用户评价明细记录', 'Reviews Log')}</h3>
              {/* Badge del período del snapshot */}
              <span style={{ fontSize: 10, fontWeight: 700, padding: '3px 9px', borderRadius: 20, background: 'rgba(255,255,255,0.07)', color: 'var(--brand)', border: '1px solid rgba(255,255,255,0.18)', letterSpacing: '.05em', textTransform: 'uppercase' }}>
                {tS(lang, P.labelEs, P.labelZh, P.labelEn || P.labelEs)}
              </span>
            </div>
            <div style={{ fontSize: 11, color: 'var(--ice-faint)', marginTop: 2 }}>
              {filteredReviews.length} {tS(lang, 'reseñas encontradas', '条符合条件的评价', 'reviews found')}
              {(dayFrom > 1 || dayTo < P.days) && (
                <span style={{ marginLeft: 8, color: 'var(--brand)', fontFamily: 'var(--font-mono)' }}>
                  · días {dayFrom}–{dayTo}
                </span>
              )}
            </div>
          </div>
          
          <div className="spacer"></div>
          
          {/* Caja de Búsqueda */}
          <div className="searchbox" style={{ width: 220, padding: '6px 10px' }}>
            <Ic name="search" style={{ width: 14, height: 14 }} />
            <input 
              placeholder={tS(lang, 'Buscar en las reseñas...', '搜索评价内容...', 'Search reviews...')} 
              value={searchQuery}
              onChange={e => setSearchQuery(e.target.value)}
              style={{ fontSize: 12 }}
            />
          </div>
        </div>

        {/* Filtros de Tabla */}
        <div className="filters" style={{ marginBottom: 14, display: 'flex', flexWrap: 'wrap', gap: 12, alignItems: 'center', fontSize: 12 }}>
          <div>
            <span className="filter-lab">{tS(lang, 'Sentimiento', '情感筛选', 'Sentiment')}</span>
            <div className="filter-group" style={{ padding: 2 }}>
              {[
                ["all", t("all_f")],
                ["pos", t("positive_f")],
                ["neu", t("neutral_f")],
                ["neg", t("negative_f")]
              ].map(([v, lbl]) => (
                <button 
                  key={v} 
                  className={sentimentFilter === v ? "active" : ""} 
                  onClick={() => setSentimentFilter(v)}
                  style={{ padding: '4px 10px', fontSize: 11.5 }}
                >
                  {lbl}
                </button>
              ))}
            </div>
          </div>



          {/* Filtro por días del mes del snapshot */}
          <div style={{ display: 'flex', alignItems: 'center', gap: 8, marginLeft: 4 }}>
            <span className="filter-lab" style={{ margin: 0, fontSize: 11 }}>{tS(lang, `Días de ${P.labelEs.split(' ')[0].toLowerCase()}`, `${P.labelZh}天数`, `Days of ${(P.labelEn || P.labelEs).split(' ')[0].toLowerCase()}`)}</span>
            <div style={{ display: 'flex', alignItems: 'center', gap: 5, background: 'rgba(255,255,255,0.04)', border: '1px solid var(--border)', borderRadius: 8, padding: '4px 10px' }}>
              <span style={{ fontSize: 10, color: 'var(--ice-faint)', fontFamily: 'var(--font-mono)' }}>01</span>
              <input
                type="range" min={1} max={P.days} value={dayFrom}
                onChange={e => setDayFrom(Math.min(Number(e.target.value), dayTo))}
                style={{ width: 70, accentColor: 'var(--brand)', cursor: 'pointer' }}
              />
              <span style={{ fontSize: 11, fontFamily: 'var(--font-mono)', color: 'var(--brand)', fontWeight: 700, minWidth: 18, textAlign: 'center' }}>{String(dayFrom).padStart(2,'0')}</span>
              <span style={{ color: 'var(--ice-faint)', fontSize: 10 }}>→</span>
              <span style={{ fontSize: 11, fontFamily: 'var(--font-mono)', color: 'var(--brand)', fontWeight: 700, minWidth: 18, textAlign: 'center' }}>{String(dayTo).padStart(2,'0')}</span>
              <input
                type="range" min={1} max={P.days} value={dayTo}
                onChange={e => setDayTo(Math.max(Number(e.target.value), dayFrom))}
                style={{ width: 70, accentColor: 'var(--brand)', cursor: 'pointer' }}
              />
              <span style={{ fontSize: 10, color: 'var(--ice-faint)', fontFamily: 'var(--font-mono)' }}>{P.days}</span>
              {(dayFrom > 1 || dayTo < P.days) && (
                <button
                  onClick={() => { setDayFrom(1); setDayTo(P.days); }}
                  style={{ marginLeft: 4, background: 'rgba(255,255,255,0.06)', border: 'none', borderRadius: 5, color: 'var(--ice-faint)', fontSize: 10, padding: '2px 6px', cursor: 'pointer' }}
                  title="Restablecer rango de días"
                >✕</button>
              )}
            </div>
          </div>
        </div>

        {/* Listado de Comentarios */}
        <div style={{ display: 'flex', flexDirection: 'column', gap: 10 }}>
          {filteredReviews.length === 0 ? (
            <div style={{ textAlign: 'center', padding: '40px 0', color: 'var(--ice-faint)' }}>
              {tS(lang, 'No se encontraron reseñas con los filtros aplicados.', '未找到符合筛选条件的评价', 'No reviews found with the applied filters.')}
            </div>
          ) : (
            filteredReviews.slice(reviewPage * REVIEWS_PER_PAGE, (reviewPage + 1) * REVIEWS_PER_PAGE).map((r) => (
              <div
                key={r.id}
                className="risk-item"
                style={{
                  background: 'rgba(255,255,255,0.01)',
                  border: '1px solid var(--border)',
                  borderRadius: 10,
                  display: 'flex',
                  flexDirection: 'column',
                  gap: 8,
                  padding: 12,
                  cursor: 'default',
                }}
              >
                {/* Meta */}
                <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'center', width: '100%' }}>
                  <div style={{ display: 'flex', alignItems: 'center', gap: 10 }}>
                    <span style={{ fontWeight: 600, color: 'var(--white)', fontSize: 13 }}>{r.author}</span>
                    <span style={{ fontSize: 11, color: getStarsColor(r.handle), fontWeight: 700 }}>{r.handle}</span>
                    <span style={{ fontSize: 11, color: 'var(--ice-faint)' }}>· {fmtDate(r.publishedAt)}</span>
                  </div>
                  <div style={{ display: 'flex', alignItems: 'center', gap: 8 }}>
                    {r.link && r.link.trim() !== "" && r.link !== "null" && r.link !== "undefined" && (
                      <a 
                        href={r.link.startsWith('http') ? r.link : `https://${r.link}`} 
                        target="_blank" 
                        rel="noopener noreferrer" 
                        onClick={(e) => {
                          e.stopPropagation();
                        }} 
                        style={{ 
                          color: 'var(--ice-faint)', 
                          display: 'inline-flex', 
                          alignItems: 'center', 
                          justifyContent: 'center',
                          width: 24, 
                          height: 24, 
                          borderRadius: 6, 
                          background: 'rgba(255,255,255,0.06)',
                          marginRight: 4,
                          cursor: 'pointer',
                          transition: 'all 0.2s ease'
                        }} 
                        title={tS(lang, 'Abrir enlace original', '打开原评价链接', 'Open original link')}
                        onMouseEnter={(e) => { e.currentTarget.style.color = 'var(--brand)'; e.currentTarget.style.background = 'rgba(255,255,255,0.1)'; }}
                        onMouseLeave={(e) => { e.currentTarget.style.color = 'var(--ice-faint)'; e.currentTarget.style.background = 'rgba(255,255,255,0.06)'; }}
                      >
                        <Ic name="external" style={{ width: 13, height: 13, pointerEvents: 'none' }} />
                      </a>
                    )}
                    <SentBadge s={r.sentiment} t={t} severity={r.severity} />
                    <Severity value={r.severity} />
                  </div>
                </div>

                {/* Text */}
                <div style={{ fontSize: 13, color: 'var(--ice)', lineHeight: 1.4 }}>
                  <AutoTranslate text={L(r.excerpt, lang)} lang={lang} />
                </div>

                {/* Reply del desarrollador */}

              </div>
            ))
          )}
        </div>
        {/* Paginación */}
        {filteredReviews.length > REVIEWS_PER_PAGE && (
          <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginTop: 16, paddingTop: 14, borderTop: '1px solid var(--border)' }}>
            <span style={{ fontSize: 11, color: 'var(--ice-faint)', fontFamily: 'var(--font-mono)' }}>
              {filteredReviews.length} {tS(lang, 'reseñas ·', '条 ·', 'reviews ·')} {tS(lang, 'página', '第', 'page')} {reviewPage + 1}/{Math.ceil(filteredReviews.length / REVIEWS_PER_PAGE)}
            </span>
            <div style={{ display: 'flex', gap: 6 }}>
              <button
                className="btn btn-ghost"
                onClick={() => setReviewPage(p => Math.max(0, p - 1))}
                disabled={reviewPage === 0}
                style={{ fontSize: 12, padding: '5px 14px', opacity: reviewPage === 0 ? 0.35 : 1 }}
              >{tS(lang, '← Anterior', '← 上一页', '← Previous')}</button>
              <button
                className="btn btn-ghost"
                onClick={() => setReviewPage(p => Math.min(Math.ceil(filteredReviews.length / REVIEWS_PER_PAGE) - 1, p + 1))}
                disabled={reviewPage >= Math.ceil(filteredReviews.length / REVIEWS_PER_PAGE) - 1}
                style={{ fontSize: 12, padding: '5px 14px', opacity: reviewPage >= Math.ceil(filteredReviews.length / REVIEWS_PER_PAGE) - 1 ? 0.35 : 1 }}
              >{tS(lang, 'Siguiente →', '下一页 →', 'Next →')}</button>
            </div>
          </div>
        )}
      </div>
      {/* ── Deep Research ── */}
      <DeepResearchPanel t={t} lang={lang} />
    </div>
  );
}

// ---------- Deep Research Panel ----------
function DeepResearchPanel({ t, lang }) {
  const [status, setStatus] = React.useState('idle');
  const [phase, setPhase] = React.useState(0);
  const [result, setResult] = React.useState(null);
  const [errorMsg, setErrorMsg] = React.useState('');

  const PHASES = React.useMemo(() => [
    t('deep_research_phase1'),
    t('deep_research_phase2'),
    t('deep_research_phase3'),
    t('deep_research_phase4'),
  ], [lang]);

  const runResearch = React.useCallback(async () => {
    setStatus('loading');
    setPhase(0);
    setResult(null);
    setErrorMsg('');
    let phaseIdx = 0;
    const phaseTimer = setInterval(() => {
      phaseIdx = Math.min(phaseIdx + 1, 3);
      setPhase(phaseIdx);
    }, 25000);
    try {
      const res = await fetch('/api/deep-research', { method: 'POST', headers: { 'content-type': 'application/json' } });
      clearInterval(phaseTimer);
      if (!res.ok) {
        const err = await res.json().catch(() => ({ error: res.statusText }));
        setErrorMsg(err.error || `Error ${res.status}`);
        setStatus('error');
        return;
      }
      const data = await res.json();
      setResult(data);
      setStatus('done');
    } catch (e) {
      clearInterval(phaseTimer);
      setErrorMsg(e.message || 'Error de red');
      setStatus('error');
    }
  }, []);

  const reset = () => { setStatus('idle'); setResult(null); setErrorMsg(''); };

  return (
    <div style={{ marginTop: 24, display: 'none' }}>
      {status === 'idle' && (
        <div style={{ border: '1px solid var(--border-strong)', borderRadius: 'var(--radius)', padding: '22px 24px', display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 16, background: 'rgba(255,255,255,0.02)', flexWrap: 'wrap' }}>
          <div>
            <div style={{ fontSize: 14, fontWeight: 700, color: 'var(--white)', marginBottom: 4 }}>{t('deep_research_title')}</div>
            <div style={{ fontSize: 12, color: 'var(--ice-faint)', maxWidth: 480 }}>{t('deep_research_sub')}</div>
          </div>
          <button
            id="btn-deep-research"
            onClick={runResearch}
            style={{ display: 'inline-flex', alignItems: 'center', gap: 8, padding: '11px 22px', borderRadius: 11, background: 'var(--white)', color: 'var(--navy-bg)', fontWeight: 700, fontSize: 13.5, border: 'none', cursor: 'pointer', whiteSpace: 'nowrap', boxShadow: '0 4px 18px rgba(255,255,255,0.15)', transition: 'all .15s', flexShrink: 0 }}
            onMouseEnter={e => e.currentTarget.style.boxShadow = '0 6px 24px rgba(255,255,255,0.25)'}
            onMouseLeave={e => e.currentTarget.style.boxShadow = '0 4px 18px rgba(255,255,255,0.15)'}
          >
            <Ic name="search" style={{ width: 15, height: 15 }} /> {t('deep_research_btn')}
          </button>
        </div>
      )}

      {status === 'loading' && (
        <div className="card card-pad" style={{ background: 'rgba(255,255,255,0.03)', border: '1px solid rgba(255,255,255,0.12)' }}>
          <div style={{ display: 'flex', alignItems: 'center', gap: 14, marginBottom: 20 }}>
            <div style={{ width: 36, height: 36, borderRadius: '50%', border: '3px solid rgba(255,255,255,0.12)', borderTopColor: 'var(--white)', animation: 'spin 0.85s linear infinite', flexShrink: 0 }} />
            <div>
              <div style={{ fontSize: 14, fontWeight: 700, color: 'var(--white)', marginBottom: 3 }}>{t('deep_research_title')}</div>
              <div style={{ fontSize: 12, color: 'var(--blue-soft)' }}>{PHASES[phase]}</div>
            </div>
          </div>
          <div style={{ height: 6, borderRadius: 4, background: 'rgba(255,255,255,0.08)', overflow: 'hidden', marginBottom: 14 }}>
            <div style={{ height: '100%', width: `${25 + phase * 25}%`, borderRadius: 4, background: 'linear-gradient(90deg, var(--brand), var(--white), var(--brand))', backgroundSize: '200% 100%', animation: 'drShimmer 1.8s linear infinite', transition: 'width 1.5s ease' }} />
          </div>
          <div style={{ display: 'flex', gap: 6, flexWrap: 'wrap' }}>
            {PHASES.map((p, i) => (
              <div key={i} style={{ display: 'flex', alignItems: 'center', gap: 6, fontSize: 11, color: i <= phase ? 'var(--brand)' : 'var(--ice-faint)', background: i <= phase ? 'rgba(255,255,255,0.07)' : 'transparent', padding: '3px 8px', borderRadius: 20, transition: 'all .4s' }}>
                <span style={{ width: 6, height: 6, borderRadius: '50%', background: i < phase ? 'var(--brand)' : i === phase ? 'var(--white)' : 'var(--border-strong)', display: 'inline-block', animation: i === phase ? 'drPulse 1.2s ease infinite' : 'none' }} />
                {p}
              </div>
            ))}
          </div>
          <div style={{ fontSize: 11, color: 'var(--ice-faint)', marginTop: 14, fontFamily: 'var(--font-mono)' }}>{t('deep_research_loading')}</div>
        </div>
      )}

      {status === 'error' && (
        <div className="card card-pad" style={{ border: '1px solid rgba(224,83,61,0.25)', background: 'rgba(224,83,61,0.05)' }}>
          <div style={{ display: 'flex', alignItems: 'flex-start', gap: 12, marginBottom: 14 }}>
            <span style={{ width: 32, height: 32, borderRadius: 8, background: 'rgba(224,83,61,0.15)', display: 'flex', alignItems: 'center', justifyContent: 'center', flexShrink: 0 }}><Ic name="alert" style={{ width: 16, height: 16, color: 'var(--neg)' }} /></span>
            <div>
              <div style={{ fontSize: 14, fontWeight: 700, color: 'var(--neg)', marginBottom: 4 }}>{t('deep_research_err')}</div>
              <div style={{ fontSize: 12, color: 'var(--ice-dim)', fontFamily: 'var(--font-mono)' }}>{errorMsg || t('deep_research_no_tavily')}</div>
            </div>
          </div>
          <button className="btn btn-ghost" onClick={reset} style={{ fontSize: 12 }}>{t('deep_research_retry')}</button>
        </div>
      )}

      {status === 'done' && result && (
        <div className="card card-pad" style={{ animation: 'fadeIn .4s ease' }}>
          <div className="card-head" style={{ marginBottom: 18 }}>
            <div>
              <h3 style={{ fontSize: 15 }}>{t('deep_research_title')}</h3>
              <div style={{ fontSize: 11, color: 'var(--ice-faint)', marginTop: 3, fontFamily: 'var(--font-mono)' }}>
                {t('deep_research_period')}: <span style={{ color: 'var(--blue-soft)' }}>{result.period}</span>
              </div>
            </div>
            <button className="btn btn-ghost" onClick={reset} style={{ fontSize: 11, padding: '5px 12px', marginLeft: 'auto' }}>{t('deep_research_new')}</button>
          </div>

          {result.answer && (
            <div style={{ background: 'linear-gradient(135deg,rgba(255,255,255,.05),rgba(255,255,255,.02))', border: '1px solid rgba(255,255,255,.15)', borderRadius: 12, padding: 18, marginBottom: 20 }}>
              <div style={{ display: 'flex', alignItems: 'center', gap: 8, marginBottom: 10, fontSize: 12, fontWeight: 700, color: 'var(--blue-soft)' }}><Ic name="spark" style={{ width: 14, height: 14 }} /> {t('deep_research_summary')}</div>
              <div style={{ fontSize: 13.5, color: 'var(--ice)', lineHeight: 1.65 }}>{result.answer}</div>
            </div>
          )}

          <div style={{ background: 'rgba(255,255,255,.02)', border: '1px solid var(--border)', borderRadius: 9, padding: '10px 14px', marginBottom: 18, fontSize: 11, color: 'var(--ice-faint)', fontFamily: 'var(--font-mono)' }}>
            <span style={{ color: 'var(--ice-dim)', fontWeight: 600 }}>{t('deep_research_query')}: </span>{result.query}
          </div>

          <div style={{ fontSize: 12, fontWeight: 700, color: 'var(--ice-dim)', marginBottom: 12, letterSpacing: '.05em', textTransform: 'uppercase' }}>
            {t('deep_research_sources')} ({result.results?.length ?? 0})
          </div>
          <div style={{ display: 'flex', flexDirection: 'column', gap: 10 }}>
            {(result.results || []).map((r, i) => {
              let domain = '';
              try { domain = new URL(r.url).hostname.replace('www.', ''); } catch {}
              const scoreColor = r.score > 0.7 ? 'var(--brand)' : r.score > 0.4 ? 'var(--gold)' : 'var(--ice-faint)';
              return (
                <div key={i}
                  style={{ background: 'rgba(255,255,255,.02)', border: '1px solid var(--border)', borderRadius: 10, padding: '12px 14px', transition: 'border-color .15s,background .15s' }}
                  onMouseEnter={e => { e.currentTarget.style.borderColor = 'var(--border-strong)'; e.currentTarget.style.background = 'rgba(255,255,255,.04)'; }}
                  onMouseLeave={e => { e.currentTarget.style.borderColor = 'var(--border)'; e.currentTarget.style.background = 'rgba(255,255,255,.02)'; }}
                >
                  <div style={{ display: 'flex', alignItems: 'flex-start', justifyContent: 'space-between', gap: 10, marginBottom: 6 }}>
                    <div style={{ flex: 1, minWidth: 0 }}>
                      <a href={r.url} target="_blank" rel="noopener noreferrer" style={{ fontSize: 13, fontWeight: 600, color: 'var(--blue-soft)', textDecoration: 'none', display: 'block', overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>
                        {r.title || r.url}
                      </a>
                      <div style={{ fontSize: 10.5, color: 'var(--ice-faint)', marginTop: 2, display: 'flex', gap: 10 }}>
                        <span>{domain}</span>
                        {r.published_date && <span>{r.published_date?.slice(0, 10)}</span>}
                      </div>
                    </div>
                    <div style={{ fontSize: 10, fontFamily: 'var(--font-mono)', fontWeight: 700, color: scoreColor, background: 'rgba(255,255,255,.04)', padding: '3px 8px', borderRadius: 20, flexShrink: 0 }}>
                      {(r.score * 100).toFixed(0)}%
                    </div>
                  </div>
                  {r.content && <div style={{ fontSize: 12, color: 'var(--ice-dim)', lineHeight: 1.55 }}>{r.content}</div>}
                </div>
              );
            })}
          </div>
        </div>
      )}

      <style>{`
        @keyframes drShimmer { 0%{background-position:-200% 0} 100%{background-position:200% 0} }
        @keyframes drPulse { 0%,100%{opacity:1;transform:scale(1)} 50%{opacity:.5;transform:scale(.75)} }
      `}</style>
    </div>
  );
}

// ====================================================================
// 2 · MENCIONES
// ====================================================================
// Paleta de colores por entity key (debe coincidir con api.jsx COLORS)
const ENTITY_COLORS = {
  crece: '#14C46A', kueski: '#7B2FF7', klar: '#2A6FDB', stori: '#E0533D',
  tala: '#1F9D77', kubo: '#F2A30F', moneyman: '#2BB0A6', creditea: '#3FB6C9', avafin: '#5EABD6',
  ipeso: '#C8412F', didi: '#E85D00', maxcredito: '#A8331F',
  mexacash: '#D45A2A', fintopia: '#8A2417', mexdin: '#B84A2A',
};
const ENTITY_NAMES = {
  crece: 'Qcrece', kueski: 'Kueski', klar: 'Klar', stori: 'Stori',
  tala: 'Tala', kubo: 'Kubo Financiero', moneyman: 'MoneyMan', creditea: 'Creditea', avafin: 'AvaFin',
  ipeso: 'iPeso', didi: 'DiDi Finanzas', maxcredito: 'MaxCrédito',
  mexacash: 'MexaCash/XFectivo', fintopia: 'Fintopia', mexdin: 'MexDin',
};
function AppBadge({ entityKey }) {
  if (!entityKey) return <span style={{ color: "var(--ice-dim)", fontSize: 12 }}>—</span>;
  const name = ENTITY_NAMES[entityKey] || entityKey;
  const color = ENTITY_COLORS[entityKey] || '#888';
  return (
    <div style={{ display: "flex", alignItems: "center", gap: 8 }}>
      <window.CompLogo name={name} color={color} />
      <span style={{ color: "var(--ice)", fontSize: 12.5, fontWeight: 500 }}>{name}</span>
    </div>
  );
}

// Formatea ISO date a DD/MM/AA usando la fecha UTC del string
// (evita que reseñas del 1 de mayo UTC se muestren como 30/04 en hora local)
function fmtDate(iso) {
  if (!iso) return '—';
  try {
    const s = String(iso);
    if (s.length >= 10 && s[4] === '-' && s[7] === '-') {
      return `${s.slice(8, 10)}/${s.slice(5, 7)}/${s.slice(2, 4)}`;
    }
    const d = new Date(s);
    const dd = String(d.getUTCDate()).padStart(2, '0');
    const mm = String(d.getUTCMonth() + 1).padStart(2, '0');
    const yy = String(d.getUTCFullYear()).slice(2);
    return `${dd}/${mm}/${yy}`;
  } catch { return '—'; }
}

const getStarsColor = (handle) => {
  if (handle && handle.includes('★')) {
    const val = parseFloat(handle.replace('★', '').trim());
    if (!isNaN(val)) {
      if (val <= 2) return 'var(--neg)';
      if (val >= 4) return 'var(--brand)';
      return 'var(--gold)';
    }
  }
  return 'var(--ice-faint)';
};

function Mentions({ onOpen, entityFilter, searchQuery }) {
  const { t, lang } = useLang();
  const translatedQ = useTranslateQuery(searchQuery);
  const [fl, setFl] = React.useState("all");
  const [fs, setFs] = React.useState("all");
  const [ft, setFt] = React.useState("all");
  const [fromDate, setFromDate] = React.useState("");
  const [toDate, setToDate] = React.useState("");

  // Si hay entityFilter, filtrar solo esa entidad
  const baseMentions = entityFilter
    ? MOCK.MENTIONS.filter(m => m.entity === entityFilter)
    : MOCK.MENTIONS;

  const q = (searchQuery || "").trim().toLowerCase();
  const rows = baseMentions.filter(m => {
    if (fl !== "all" && SOURCE_LAYERS[m.platform] !== fl) return false;
    if (fs !== "all" && m.sentiment !== fs) return false;
    if (ft !== "all" && m.topic !== ft) return false;
    if (fromDate && m.publishedAt && m.publishedAt.slice(0, 10) < fromDate) return false;
    if (toDate && m.publishedAt && m.publishedAt.slice(0, 10) > toDate) return false;
    if (q) {
      const authorMatch = (m.author || "").toLowerCase().includes(q);
      const esText = (L(m.excerpt, 'es') || "").toLowerCase();
      const originalText = L(m.excerpt, 'zh');
      const translatedText = window._TL_CACHE?.[originalText] || "";
      const qEs = window._WC_TRANSLATIONS?.zhToEs?.[q] || translatedQ || q;
      const textMatch = esText.includes(q) || 
                        translatedText.toLowerCase().includes(q) || 
                        esText.includes(qEs);
      const topicMatch = (topicLabel(t, m.topic) || "").toLowerCase().includes(q);
      const platformMatch = (m.platform || "").toLowerCase().includes(q);
      if (!authorMatch && !textMatch && !topicMatch && !platformMatch) return false;
    }
    return true;
  });
  const FG = ({ value, set, opts, render }) => (
    <div className="filter-group">
      <button className={value === "all" ? "active" : ""} onClick={() => set("all")}>{t("all")}</button>
      {opts.map(o => <button key={o} className={value === o ? "active" : ""} onClick={() => set(o)}>{render(o)}</button>)}
    </div>
  );
  return (
    <div className="fade-in">
      {entityFilter && (
        <div className="scan-banner" style={{ marginBottom: 14, background: 'linear-gradient(135deg,rgba(255,255,255,0.06),rgba(255,255,255,0.02))' }}>
          <div style={{ display: 'flex', alignItems: 'center', gap: 12 }}>
            <AppBadge entityKey={entityFilter} />
            <div>
              <div style={{ fontSize: 11, color: 'var(--brand)', fontWeight: 700, textTransform: 'uppercase', letterSpacing: '.08em' }}>{tS(lang, 'Tu marca · War Room', '您的品牌 · 战时室', 'Your Brand · War Room')}</div>
              <div style={{ fontSize: 12, color: 'var(--ice-faint)', marginTop: 2 }}>{tS(lang, 'Menciones de Crece/QFIN en todas las fuentes monitoreadas', '在所有监测渠道中提及 Crece/QFIN 的记录', 'Mentions of Crece/QFIN across all monitored channels')}</div>
            </div>
          </div>
          <div style={{ textAlign: 'right' }}>
            <div style={{ fontFamily: 'var(--font-mono)', fontSize: 22, fontWeight: 700, color: 'var(--brand)' }}>{baseMentions.length}</div>
            <div style={{ fontSize: 10, color: 'var(--ice-faint)' }}>{tS(lang, 'menciones totales', '全网提及总量', 'total mentions')}</div>
          </div>
        </div>
      )}
      <div className="filters" style={{ display: "flex", flexWrap: "wrap", gap: "16px", alignItems: "center" }}>
        <div>
          <span className="filter-lab">{t("f_layer")}</span>
          <FG value={fl} set={setFl}
            opts={["comunidades", "resenas", "prensa"]}
            render={o => t("layer_" + o)} />
        </div>
        <div>
          <span className="filter-lab">{t("f_sentiment")}</span>
          <FG value={fs} set={setFs} opts={["pos", "neu", "neg"]} render={o => t({ pos: "positive", neu: "neutral", neg: "negative" }[o])} />
        </div>
        <div>
          <span className="filter-lab">{t("f_topic")}</span>
          <FG value={ft} set={setFt} opts={["cobranza", "china", "tasas", "ia", "fraude"]} render={o => topicLabel(t, o)} />
        </div>
        <div style={{ display: "inline-flex", alignItems: "center", gap: "8px" }}>
          <span className="filter-lab" style={{ margin: 0 }}>Desde</span>
          <input 
            type="date" 
            value={fromDate} 
            onChange={e => setFromDate(e.target.value)} 
            style={{ background: "rgba(255,255,255,0.05)", border: "1px solid rgba(255,255,255,0.1)", color: "var(--ice)", padding: "5px 8px", borderRadius: "8px", fontSize: "12px", outline: "none" }} 
          />
          <span className="filter-lab" style={{ margin: 0 }}>hasta</span>
          <input 
            type="date" 
            value={toDate} 
            onChange={e => setToDate(e.target.value)} 
            style={{ background: "rgba(255,255,255,0.05)", border: "1px solid rgba(255,255,255,0.1)", color: "var(--ice)", padding: "5px 8px", borderRadius: "8px", fontSize: "12px", outline: "none" }} 
          />
        </div>
      </div>
      <div className="card card-pad" style={{ paddingTop: 14 }}>
        <div style={{ fontSize: 12, color: "var(--ice-faint)", marginBottom: 8, fontFamily: "var(--font-mono)" }}>{rows.length} {t("results")}</div>
        <div style={{ overflowX: "auto" }}>
          <table className="tbl">
            <thead><tr>
              <th>{t("th_source")}</th>
              {!entityFilter && <th>{tS(lang, 'App', '应用', 'App')}</th>}
              <th>{t("th_author")}</th><th>{t("th_reach")}</th>
              <th>{t("th_excerpt")}</th><th>{t("th_topic")}</th><th>{t("th_sentiment")}</th>
              <th>{t("th_severity")}</th><th>{t("date_label")}</th><th>{t("th_action")}</th>
            </tr></thead>
            <tbody>
              {rows.map(m => (
                <tr key={m.id} style={{ cursor: 'default' }}>
                  <td>
                    <div style={{ display: "flex", alignItems: "center", gap: 8 }}>
                      <SourceTag platform={m.platform} />
                      {m.link && (
                        <a href={m.link.startsWith('http') ? m.link : `https://${m.link}`} target="_blank" rel="noopener noreferrer" onClick={(e) => e.stopPropagation()} style={{ color: "var(--ice-faint)", display: "inline-flex", alignItems: "center", textDecoration: "none" }} title="Abrir enlace original">
                          <Ic name="external" style={{ width: 13, height: 13 }} />
                        </a>
                      )}
                    </div>
                  </td>
                  {!entityFilter && <td><AppBadge entityKey={m.entity} /></td>}
                  <td style={{ color: "var(--ice)", fontWeight: 500 }}>{m.author}<div style={{ fontSize: 11, color: getStarsColor(m.handle), fontWeight: 500 }}>{m.handle}</div></td>
                  <td className="reach">{m.reach >= 1000 ? (m.reach / 1000).toFixed(0) + "k" : m.reach}</td>
                  <td className="excerpt-cell"><AutoTranslate text={L(m.excerpt, lang)} lang={lang} /></td>
                  <td>{topicLabel(t, m.topic)}</td>
                  <td><SentBadge s={m.sentiment} t={t} severity={m.severity} /></td>
                  <td><Severity value={m.severity} /></td>
                  <td style={{ fontSize: 11, color: "var(--ice-faint)", whiteSpace: "nowrap", fontFamily: "var(--font-mono)" }}>{fmtDate(m.publishedAt)}</td>
                  <td>{m.actionable ? <span className="badge pos"><span className="bdot"></span>{t("actionable")}</span> : <span className="badge neu"><span className="bdot"></span>{t("not_actionable")}</span>}</td>
                </tr>
              ))}
            </tbody>
          </table>
        </div>
      </div>
    </div>
  );
}

// ====================================================================
// 3 · COMPETIDORES
// ====================================================================
function Competitors({ onSelectComp }) {
  const { t, lang } = useLang();
  const P = getPeriod();
  // Incluir Qcrece (tier=self) junto a los competidores para comparación
  const kosher = (MOCK.COMP_KOSHER || []).filter(c => c.mentions > 0 || c.sov > 0);
  const aztlan = (MOCK.COMP_AZTLAN || []).filter(c => c.mentions > 0 || c.sov > 0);
  const allSov = [...kosher.map(c => ({ ...c })), ...aztlan.map(c => ({ ...c, note: '' }))].sort((a, b) => b.sov - a.sov);

  // Calcular porcentaje de reseñas positivas para cada competidor y ordenar
  const allComps = [...kosher, ...aztlan].map(c => {
    const entityKey = c.key;
    
    // Si vienen del backend en vivo, usamos los valores precalculados directamente!
    let positivePercent = c.playstorePositivePercentPeriod;
    let mentionsCount = c.playstoreCountPeriod;

    if (positivePercent === undefined || positivePercent === null) {
      // Solo reseñas de Google Play Store del mes del snapshot (para comparar con Qcrece)
      const compReviews = (MOCK.MENTIONS || []).filter(m => m.entity === entityKey && m.platform === 'playstore' && (m.publishedAt || '').startsWith(P.month));
      const positiveReviews = compReviews.filter(m => m.sentiment === 'pos').length;
      
      positivePercent = compReviews.length ? Math.round((positiveReviews / compReviews.length) * 100) : 0;
      mentionsCount = compReviews.length > 0 ? compReviews.length : (c.mentions ?? 0);

      if (compReviews.length === 0) {
        // Si es mock fallback (sin reseñas en BD para esta entidad), simular según sentiment y SOV
        if (c.sentiment === 'pos') {
          positivePercent = 75;
        } else if (c.sentiment === 'neg') {
          positivePercent = 15;
        } else {
          positivePercent = 45;
        }

        if (c.sov > 0 && !c.mentions) {
          mentionsCount = Math.round(c.sov * 35);
        }
      }
    }

    return {
      ...c,
      entityKey,
      mentions: mentionsCount,
      positivePercent
    };
  }).sort((a, b) => b.positivePercent - a.positivePercent);

  const CompRow = ({ c }) => {
    const pctColor = c.positivePercent > 80 ? "var(--pos)" : c.positivePercent >= 50 ? "var(--gold)" : "var(--neg)";
    const sentLabel = c.positivePercent > 80 ? tS(lang, 'Positivo favorable', '正面/好评', 'Positive favorable') : c.positivePercent >= 50 ? tS(lang, 'Reseñas mixtas', '中性评价', 'Mixed reviews') : tS(lang, 'Negativo', '负面/差评', 'Negative');
    const isYou = c.note === "you";
    const Tilt3D = window.Tilt3D || (({ children, ...p }) => <div {...p}>{children}</div>);
    return (
      <Tilt3D
        key={c.name}
        className={`comp-row ${isYou ? "you-highlight" : ""}`}
        onClick={() => onSelectComp && onSelectComp({ key: c.entityKey, name: c.name, color: c.color, mentions: c.mentions, sov: c.sov })}
        style={{
          cursor: 'pointer',
          padding: '12px',
          borderRadius: '10px',
          border: isYou ? '1px solid rgba(255,255,255,0.3)' : '1px solid rgba(255,255,255,0.05)',
          background: isYou ? 'rgba(255,255,255,0.06)' : 'rgba(255,255,255,0.02)'
        }}
        onMouseEnter={e => {
          e.currentTarget.style.background = isYou ? 'rgba(255,255,255,0.1)' : 'rgba(255,255,255,0.06)';
          e.currentTarget.style.borderColor = isYou ? 'rgba(255,255,255,0.45)' : 'rgba(255,255,255,0.1)';
        }}
        onMouseLeave={e => {
          e.currentTarget.style.background = isYou ? 'rgba(255,255,255,0.06)' : 'rgba(255,255,255,0.02)';
          e.currentTarget.style.borderColor = isYou ? 'rgba(255,255,255,0.3)' : 'rgba(255,255,255,0.05)';
        }}
        title={tS(lang, `Ver ${(c.mentions || 0).toLocaleString()} reseñas de ${c.name}`, `查看 ${c.name} 的 ${(c.mentions || 0).toLocaleString()} 条评价`, `View ${(c.mentions || 0).toLocaleString()} reviews of ${c.name}`)}
      >
        <div style={{ display: 'flex', alignItems: 'center', gap: 12 }}>
          <CompLogo name={c.name} color={c.color} />
          <div style={{ flex: 1, minWidth: 0 }}>
            <div className={`nm ${isYou ? "you" : c.note === "benchmark" ? "bench" : ""}`} style={{ fontSize: '13.5px', fontWeight: '600', color: isYou ? 'var(--brand)' : 'var(--white)' }}>
              {c.name}
              {c.note === "benchmark" ? ` · ${t("benchmark")}` : ""}
            </div>
            <div style={{ display: 'flex', alignItems: 'center', gap: 8, marginTop: 4 }}>
              <span style={{ fontSize: 10, color: 'var(--ice-faint)', fontFamily: 'var(--font-mono)' }}>
                {(c.mentions || 0).toLocaleString()} {tS(lang, 'reseñas', '条评价', 'reviews')}
              </span>
            </div>
          </div>
          <div style={{ textAlign: 'right', flexShrink: 0, minWidth: 110 }}>
            <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'flex-end', gap: 6 }}>
              <span style={{ fontSize: 10, color: pctColor, fontWeight: 600 }}>{sentLabel}</span>
              <span style={{ fontSize: 16, fontWeight: 700, color: pctColor }}>{c.positivePercent}%</span>
            </div>
            <div style={{ marginTop: 5, height: 6, background: 'rgba(160,180,220,.08)', borderRadius: 3, overflow: 'hidden' }}>
              <div style={{ width: `${c.positivePercent}%`, height: '100%', background: pctColor, borderRadius: 3, transition: 'width .5s ease' }} />
            </div>
          </div>
        </div>
      </Tilt3D>
    );
  };

  return (
    <div className="fade-in">
      <div style={{ marginBottom: 10, fontSize: 12, color: 'var(--ice-faint)' }}>
        {tS(lang, 'Haz clic en un competidor para ver sus reseñas completas', '点击竞争对手以查看其完整评价', 'Click on a competitor to view their full reviews')}
      </div>
      
      <div className="card card-pad" style={{ marginBottom: 18 }}>
        <div className="card-head" style={{ marginBottom: 16 }}>
          <h3>{tS(lang, 'Competidores', '竞争对手', 'Competitors')} <span style={{ fontSize: 12, color: 'var(--ice-faint)', fontWeight: 400 }}>— {tS(lang, `Reseñas ${P.labelEs} · México`, `墨西哥评价 · ${P.labelZh}`, `Reviews ${P.labelEn || P.labelEs} · Mexico`)}</span></h3>
          <span className="badge info" style={{ marginLeft: 8 }}>{allComps.length}</span>
        </div>
        <div style={{ display: "grid", gridTemplateColumns: "repeat(auto-fill, minmax(300px, 1fr))", gap: "12px" }}>
          {allComps.map(c => <CompRow key={c.name} c={c} />)}
        </div>
      </div>

      <div className="card card-pad">
        <div className="card-head"><h3>{t("sov_chart")}</h3></div>
        <p style={{ fontSize: 11, color: 'var(--ice-faint)', margin: '0 0 16px', lineHeight: 1.5 }}>
          {tS(lang,
            'La presente gráfica muestra la distribución de todas las reseñas de Google Play Store en México sobre los jugadores más importantes de la industria. Nos ayuda a tener un entendimiento aproximado del share of voice.',
            '本图表展示了墨西哥 Google Play 商店中关于各主要金融科技竞品的用户评价分布情况，有助于了解市场声量份额 (Share of Voice)。',
            'This chart displays the distribution of all Google Play Store reviews in Mexico regarding the major fintech competitors, helping to understand their approximate share of voice.'
          )}
        </p>
        <SoVBars rows={allSov} t={t} />
      </div>
    </div>
  );
}

// ====================================================================
// 3B · RESEÑAS DE COMPETIDOR (vista al hacer click en un competidor)
// ====================================================================
function CompReviews({ onOpen, comp, onBack, searchQuery, setSearchQuery }) {
  const { t, lang } = useLang();
  const translatedQ = useTranslateQuery(searchQuery);
  const [fs, setFs] = React.useState("all");
  const [storeFilter, setStoreFilter] = React.useState("all");
  const [fromDate, setFromDate] = React.useState("");
  const [toDate, setToDate] = React.useState("");
  const [page, setPage] = React.useState(0);
  const PAGE_SIZE = 50;
  const [summaryData, setSummaryData] = React.useState(null);

  React.useEffect(() => {
    if (!comp || !comp.key) {
      setSummaryData(null);
      return;
    }
    fetch('/api/competitor_summaries.json')
      .then(res => res.json())
      .then(data => {
        if (data && data[comp.key]) {
          setSummaryData(data[comp.key]);
        } else {
          setSummaryData(null);
        }
      })
      .catch(err => {
        console.error("Error loading competitor summaries:", err);
        setSummaryData(null);
      });
  }, [comp?.key]);

  const [reviews, setReviews] = React.useState([]);
  const [loading, setLoading] = React.useState(true);

  React.useEffect(() => {
    if (!comp || !comp.key) {
      setReviews([]);
      setLoading(false);
      return;
    }
    
    if (window.QO_USE_LIVE === false) {
      const filteredMocks = (MOCK.MENTIONS || []).filter(m => m.entity === comp.key);
      setReviews(filteredMocks);
      setLoading(false);
      return;
    }

    setLoading(true);
    fetch(`/api/mentions/entity/${comp.key}`)
      .then(res => {
        if (!res.ok) throw new Error("Fetch failed");
        return res.json();
      })
      .then(data => {
        const mapped = data.map(m => typeof window.QO_MAP_MENTION === 'function' ? window.QO_MAP_MENTION(m) : m);
        setReviews(mapped);
        setLoading(false);
      })
      .catch(err => {
        console.error("Error loading entity reviews, falling back to mock:", err);
        const filteredMocks = (MOCK.MENTIONS || []).filter(m => m.entity === comp.key);
        setReviews(filteredMocks);
        setLoading(false);
      });
  }, [comp?.key]);

  if (!comp) return (
    <div className="fade-in" style={{ padding: 40, textAlign: 'center', color: 'var(--ice-faint)' }}>
      <div style={{ fontSize: 14, marginBottom: 12 }}>{tS(lang, 'Selecciona un competidor para ver sus reseñas.', '请选择一个竞争对手以查看其评价。', 'Select a competitor to view their reviews.')}</div>
      <button className="btn btn-ghost" onClick={onBack}>{tS(lang, '← Volver a Competidores', '← 返回竞争对手列表', '← Back to Competitors')}</button>
    </div>
  );

  if (loading) return (
    <div className="fade-in" style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', padding: 80, gap: 16 }}>
      <div className="loading-spinner"></div>
      <div style={{ color: 'var(--ice-faint)', fontSize: 13 }}>
        {tS(lang, 'Cargando reseñas de la base de datos...', '正在加载评价数据...', 'Loading reviews from the database...')}
      </div>
    </div>
  );

  // Solo reseñas de Google Play Store — sin prensa, Twitter, foros ni App Store (para comparar de forma justa con Qcrece)
  const allReviews = reviews.filter(m => m.platform === 'playstore');
  const q = (searchQuery || "").trim().toLowerCase();
  
  // Filtros estructurales (sentimiento, tienda, fechas) — base para la nube de palabras
  const baseFiltered = allReviews.filter(m => {
    if (fs !== "all" && m.sentiment !== fs) return false;
    if (storeFilter !== "all" && m.platform !== storeFilter) return false;
    if (fromDate && m.publishedAt && m.publishedAt.slice(0, 10) < fromDate) return false;
    if (toDate && m.publishedAt && m.publishedAt.slice(0, 10) > toDate) return false;
    return true;
  });

  const filtered = baseFiltered.filter(m => {
    if (q) {
      const authorMatch = (m.author || "").toLowerCase().includes(q);
      const esText = (L(m.excerpt, 'es') || "").toLowerCase();
      const originalText = L(m.excerpt, 'zh');
      const translatedText = window._TL_CACHE?.[originalText] || "";
      const qEs = window._WC_TRANSLATIONS?.zhToEs?.[q] || translatedQ || q;
      const textMatch = esText.includes(q) || 
                        translatedText.toLowerCase().includes(q) || 
                        esText.includes(qEs);
      const platformMatch = (m.platform || "").toLowerCase().includes(q);
      if (!authorMatch && !textMatch && !platformMatch) return false;
    }
    return true;
  });

  const paged = filtered.slice(page * PAGE_SIZE, (page + 1) * PAGE_SIZE);
  const totalPages = Math.ceil(filtered.length / PAGE_SIZE) || 1;

  // Estadísticas basadas en las reseñas realmente descargadas
  const realTotal = allReviews.length || 1;
  const sampleTotal = allReviews.length || 1;
  const neg = allReviews.filter(m => m.sentiment === 'neg').length;
  const pos = allReviews.filter(m => m.sentiment === 'pos').length;
  const negScaled = Math.round(realTotal * (neg / sampleTotal));
  const posScaled = Math.round(realTotal * (pos / sampleTotal));

  const avgScore = (() => {
    const stars = allReviews.map(m => {
      const match = (m.handle || '').match(/★\s*([0-9.]+)/);
      return match ? parseFloat(match[1]) : null;
    }).filter(v => v !== null);
    return stars.length ? (stars.reduce((a, b) => a + b, 0) / stars.length).toFixed(1) : '—';
  })();

  return (
    <div className="fade-in">
      {/* Header con info del competidor */}
      <div className="scan-banner" style={{ marginBottom: 18 }}>
        <div style={{ display: 'flex', alignItems: 'center', gap: 14 }}>
          <button className="btn btn-ghost" onClick={onBack} style={{ padding: '6px 12px', fontSize: 12 }}>{t("back_to_comps")}</button>
          <AppBadge entityKey={comp.key} />
          <div>
            <div style={{ fontSize: 11, color: 'var(--ice-faint)', textTransform: 'uppercase', letterSpacing: '.08em', fontWeight: 600 }}>{t("title_reviews")}</div>
            <div style={{ fontSize: 12, color: 'var(--ice-dim)', marginTop: 2 }}>{allReviews.length.toLocaleString()} {tS(lang, 'reseñas', '条评价', 'reviews')} · {tS(lang, `${getPeriod().labelEs} · México`, getPeriod().labelZh, `${getPeriod().labelEn || getPeriod().labelEs} · Mexico`)}</div>
          </div>
        </div>
        <div style={{ display: 'flex', gap: 20, alignItems: 'center' }}>
          <div style={{ textAlign: 'center' }}>
            <div style={{ fontFamily: 'var(--font-mono)', fontSize: 20, fontWeight: 700, color: 'var(--neg)' }}>{negScaled}</div>
            <div style={{ fontSize: 10, color: 'var(--ice-faint)' }}>{t("negative_f")}</div>
          </div>
          <div style={{ textAlign: 'center' }}>
            <div style={{ fontFamily: 'var(--font-mono)', fontSize: 20, fontWeight: 700, color: 'var(--brand)' }}>{posScaled}</div>
            <div style={{ fontSize: 10, color: 'var(--ice-faint)' }}>{t("positive_f")}</div>
          </div>
          <div style={{ textAlign: 'center' }}>
            <div style={{ fontFamily: 'var(--font-mono)', fontSize: 20, fontWeight: 700, color: comp.color || '#888' }}>{avgScore} ★</div>
            <div style={{ fontSize: 10, color: 'var(--ice-faint)' }}>{tS(lang, 'Promedio', '平均得分', 'Average')}</div>
          </div>
        </div>
      </div>

      {/* Resumen de Reseñas en Tarjetas */}
      {summaryData && (
        <div style={{
          display: "grid",
          gridTemplateColumns: "repeat(auto-fit, minmax(280px, 1fr))",
          gap: "16px",
          marginBottom: "20px"
        }}>
          {/* Tarjeta 1: Cosas Buenas */}
          <Tilt3D className="card card-pad" style={{
            borderLeft: "4px solid var(--brand)",
            display: "flex",
            flexDirection: "column",
            gap: "12px",
            background: "rgba(255,255,255,0.02)"
          }}>
            <div style={{ display: "flex", alignItems: "center", gap: "8px" }}>
              <span style={{
                width: "24px",
                height: "24px",
                borderRadius: "50%",
                background: "rgba(255,255,255,0.08)",
                display: "inline-flex",
                alignItems: "center",
                justifyContent: "center",
                color: "var(--brand)"
              }}>
                <Ic name="check" style={{ width: 14, height: 14 }} />
              </span>
              <h4 style={{ margin: 0, color: "var(--white)", fontSize: "14px", fontWeight: "600" }}>
                {tS(lang, 'Cosas Buenas', '优点 / 亮点', 'Pros / Highlights')}
              </h4>
            </div>
            <ul style={{ margin: 0, paddingLeft: "16px", listStyleType: "none", display: "flex", flexDirection: "column", gap: "8px" }}>
              {(summaryData.positives[lang] || summaryData.positives['es'] || []).map((pt, idx) => (
                <li key={idx} style={{
                  fontSize: "12.5px",
                  lineHeight: "1.5",
                  color: "var(--ice)",
                  position: "relative"
                }}>
                  <span style={{ position: "absolute", left: "-16px", color: "var(--brand)" }}>•</span>
                  {pt}
                </li>
              ))}
            </ul>
          </Tilt3D>

          {/* Tarjeta 2: Cosas Malas */}
          <Tilt3D className="card card-pad" style={{
            borderLeft: "4px solid var(--neg)",
            display: "flex",
            flexDirection: "column",
            gap: "12px",
            background: "rgba(224,83,61,0.03)"
          }}>
            <div style={{ display: "flex", alignItems: "center", gap: "8px" }}>
              <span style={{
                width: "24px",
                height: "24px",
                borderRadius: "50%",
                background: "rgba(224,83,61,0.1)",
                display: "inline-flex",
                alignItems: "center",
                justifyContent: "center",
                color: "var(--neg)"
              }}>
                <Ic name="x" style={{ width: 12, height: 12 }} />
              </span>
              <h4 style={{ margin: 0, color: "var(--white)", fontSize: "14px", fontWeight: "600" }}>
                {tS(lang, 'Cosas Malas', '缺点 / 痛点', 'Cons / Pain Points')}
              </h4>
            </div>
            <ul style={{ margin: 0, paddingLeft: "16px", listStyleType: "none", display: "flex", flexDirection: "column", gap: "8px" }}>
              {(summaryData.negatives[lang] || summaryData.negatives['es'] || []).map((pt, idx) => (
                <li key={idx} style={{
                  fontSize: "12.5px",
                  lineHeight: "1.5",
                  color: "var(--ice)",
                  position: "relative"
                }}>
                  <span style={{ position: "absolute", left: "-16px", color: "var(--neg)" }}>•</span>
                  {pt}
                </li>
              ))}
            </ul>
          </Tilt3D>

          {/* Tarjeta 3: Análisis General */}
          <Tilt3D className="card card-pad" style={{
            borderLeft: "4px solid var(--blue-soft)",
            display: "flex",
            flexDirection: "column",
            gap: "12px",
            background: "rgba(42,111,219,0.03)"
          }}>
            <div style={{ display: "flex", alignItems: "center", gap: "8px" }}>
              <span style={{
                width: "24px",
                height: "24px",
                borderRadius: "50%",
                background: "rgba(42,111,219,0.1)",
                display: "inline-flex",
                alignItems: "center",
                justifyContent: "center",
                color: "var(--blue-soft)"
              }}>
                <Ic name="shield" style={{ width: 13, height: 13 }} />
              </span>
              <h4 style={{ margin: 0, color: "var(--white)", fontSize: "14px", fontWeight: "600" }}>
                {tS(lang, 'Análisis General', '综合分析', 'General Analysis')}
              </h4>
            </div>
            <ul style={{ margin: 0, paddingLeft: "16px", listStyleType: "none", display: "flex", flexDirection: "column", gap: "8px" }}>
              {(summaryData.analysis[lang] || summaryData.analysis['es'] || []).map((pt, idx) => (
                <li key={idx} style={{
                  fontSize: "12.5px",
                  lineHeight: "1.5",
                  color: "var(--ice)",
                  position: "relative"
                }}>
                  <span style={{ position: "absolute", left: "-16px", color: "var(--blue-soft)" }}>•</span>
                  {pt}
                </li>
              ))}
            </ul>
          </Tilt3D>
        </div>
      )}

      {/* Filtros */}
      <div className="filters" style={{ marginBottom: 14, display: "flex", flexWrap: "wrap", gap: "16px", alignItems: "center" }}>
        <div>
          <span className="filter-lab">{t("sentiment_label")}</span>
          <div className="filter-group">
            {[['all', t("all_f")], ['neg', t("negative_f")], ['pos', t("positive_f")], ['neu', t("neutral_f")]].map(([v,label]) => (
              <button key={v} className={fs === v ? 'active' : ''} onClick={() => { setFs(v); setPage(0); }}>{label}</button>
            ))}
          </div>
        </div>
        <div>
          <span className="filter-lab">{t("store_label")}</span>
          <div className="filter-group">
            {[['all', t("all_f")], ['appstore', t("app_store_f")], ['playstore', t("play_store_f")]].map(([v,label]) => (
              <button key={v} className={storeFilter === v ? 'active' : ''} onClick={() => { setStoreFilter(v); setPage(0); }}>{label}</button>
            ))}
          </div>
        </div>
        <div style={{ display: "inline-flex", alignItems: "center", gap: "8px" }}>
          <span className="filter-lab" style={{ margin: 0 }}>{t("date_from_f")}</span>
          <input 
            type="date" 
            value={fromDate} 
            onChange={e => { setFromDate(e.target.value); setPage(0); }} 
            style={{ background: "rgba(255,255,255,0.05)", border: "1px solid rgba(255,255,255,0.1)", color: "var(--ice)", padding: "5px 8px", borderRadius: "8px", fontSize: "12px", outline: "none" }} 
          />
          <span className="filter-lab" style={{ margin: 0 }}>{t("date_to_f")}</span>
          <input 
            type="date" 
            value={toDate} 
            onChange={e => { setToDate(e.target.value); setPage(0); }} 
            style={{ background: "rgba(255,255,255,0.05)", border: "1px solid rgba(255,255,255,0.1)", color: "var(--ice)", padding: "5px 8px", borderRadius: "8px", fontSize: "12px", outline: "none" }} 
          />
        </div>
      </div>

      {/* Mapa de palabras — frecuencia en las reseñas filtradas; clic = filtrar */}
      <WordCloud
        reviews={baseFiltered}
        lang={lang}
        active={q}
        onWord={(word) => {
          if (!setSearchQuery) return;
          setSearchQuery(q === word ? "" : word);
          setPage(0);
        }}
      />

      <div className="card card-pad" style={{ paddingTop: 14 }}>
        <div style={{ display: 'flex', justifyContent: 'space-between', marginBottom: 10, alignItems: 'center' }}>
          <div style={{ fontSize: 12, color: 'var(--ice-faint)', fontFamily: 'var(--font-mono)' }}>{filtered.length.toLocaleString()} {tS(lang, 'reseñas', '条评价', 'reviews')} · {t("page_f")} {page + 1}/{totalPages}</div>
          <div style={{ display: 'flex', gap: 8 }}>
            <button className="btn btn-ghost" style={{ padding: '4px 10px', fontSize: 11 }} disabled={page === 0} onClick={() => setPage(p => p - 1)}>{t("prev_f")}</button>
            <button className="btn btn-ghost" style={{ padding: '4px 10px', fontSize: 11 }} disabled={page >= totalPages - 1} onClick={() => setPage(p => p + 1)}>{t("next_f")}</button>
          </div>
        </div>
        <div style={{ overflowX: 'auto' }}>
          <table className="tbl">
            <thead><tr>
              <th>{t("store_label")}</th>
              <th>{t("author_label")}</th>
              <th>{t("score_label")}</th>
              <th style={{ minWidth: 300 }}>{t("review_label")}</th>
              <th>{t("sentiment_label")}</th>
              <th>{t("severity_label")}</th>
              <th>{t("date_label")}</th>
              <th>{t("link_label")}</th>
            </tr></thead>
            <tbody>
              {paged.map(m => (
                <tr key={m.id} style={{ cursor: 'default' }}>
                  <td>
                    <SourceTag platform={m.platform} />
                  </td>
                  <td style={{ color: 'var(--ice)', fontWeight: 500, fontSize: 12 }}>{m.author}</td>
                  <td>
                    <span style={{ fontSize: 13, fontWeight: 700, color: getStarsColor(m.handle) }}>{m.handle}</span>
                  </td>
                  <td className="excerpt-cell" style={{ maxWidth: 360 }}>
                    <AutoTranslate text={L(m.excerpt, lang)} lang={lang} />
                  </td>
                  <td><SentBadge s={m.sentiment} t={t} severity={m.severity} /></td>
                  <td><Severity value={m.severity} /></td>
                  <td style={{ fontSize: 11, color: 'var(--ice-faint)', whiteSpace: 'nowrap', fontFamily: 'var(--font-mono)' }}>{fmtDate(m.publishedAt)}</td>
                  <td>
                    {m.link && (
                      <a
                        href={m.link.startsWith('http') ? m.link : `https://${m.link}`}
                        target="_blank"
                        rel="noopener noreferrer"
                        onClick={e => e.stopPropagation()}
                        className="btn btn-ghost"
                        style={{ padding: '3px 8px', fontSize: 11, display: 'inline-flex', alignItems: 'center', gap: 4 }}
                        title="Ver reseña original"
                      >
                        <Ic name="external" style={{ width: 11, height: 11 }} /> {t("view_original_f")}
                      </a>
                    )}
                  </td>
                </tr>
              ))}
            </tbody>
          </table>
        </div>
        {totalPages > 1 && (
          <div style={{ display: 'flex', justifyContent: 'center', gap: 8, marginTop: 16 }}>
            <button className="btn btn-ghost" style={{ padding: '4px 10px', fontSize: 11 }} disabled={page === 0} onClick={() => setPage(p => p - 1)}>{t("prev_f")}</button>
            <span style={{ fontSize: 11, color: 'var(--ice-faint)', alignSelf: 'center' }}>{tS(lang, 'Pág', '第', 'Page')} {page + 1} / {totalPages}{tS(lang, '', '页', '')}</span>
            <button className="btn btn-ghost" style={{ padding: '4px 10px', fontSize: 11 }} disabled={page >= totalPages - 1} onClick={() => setPage(p => p + 1)}>{t("next_f")}</button>
          </div>
        )}
      </div>
    </div>
  );
}

// ====================================================================
// 4 · COBRANZA
// ====================================================================
function Collection({ onOpen, searchQuery }) {
  const { t, lang } = useLang();
  const translatedQ = useTranslateQuery(searchQuery);
  const q = (searchQuery || "").trim().toLowerCase();
  const complaints = MOCK.RAW_COMPLAINTS.filter(c => {
    if (!q) return true;
    const esText = (L(c.text, 'es') || "").toLowerCase();
    const originalText = L(c.text, 'zh');
    const translatedText = window._TL_CACHE?.[originalText] || "";
    const qEs = window._WC_TRANSLATIONS?.zhToEs?.[q] || translatedQ || q;
    const textMatch = esText.includes(q) || 
                      translatedText.toLowerCase().includes(q) || 
                      esText.includes(qEs);
    const platformMatch = (c.platform || "").toLowerCase().includes(q);
    const aiClassMatch = (L(c.aiClass, lang) || "").toLowerCase().includes(q);
    return textMatch || platformMatch || aiClassMatch;
  });
  return (
    <div className="fade-in">
      <div className="card card-pad" style={{ marginBottom: 18 }}>
        <div className="card-head"><h3>{t("heat_title")}</h3></div>
        <div style={{ overflowX: "auto", WebkitOverflowScrolling: "touch" }}>
        <table className="heat" style={{ minWidth: 380 }}>
          <thead><tr>
            <th className="rowh"></th>
            {MOCK.HEAT_PLATFORMS.map(p => {
              const col = HEAT_COLS[p] || { label: PLATFORMS[p]?.label || p, glyph: null };
              const iconColor = PLATFORMS[p]?.color;
              return (
                <th key={p}>
                  {col.glyph
                    ? <span style={{display:"inline-flex",alignItems:"center",gap:6,justifyContent:"center"}}>
                        <span style={{
                          display:"inline-flex", alignItems:"center", justifyContent:"center",
                          width:20, height:20, borderRadius:5,
                          background: iconColor || "#555", flexShrink:0,
                        }}>
                          <StoreGlyph glyph={col.glyph} size="md"/>
                        </span>
                        {col.label}
                      </span>
                    : col.label}
                </th>
              );
            })}
          </tr></thead>
          <tbody>
            {MOCK.HEAT_ROWS.map(r => (
              <tr key={r.name}>
                <th className="rowh" style={{ color: r.name === "Crece" ? "var(--brand)" : "var(--ice)" }}>{r.name === "Crece" ? "Crece ●" : r.name}</th>
                {r.v.map((v, i) => (
                  v < 0
                    ? <td key={i} style={{ background: "rgba(255,255,255,0.04)", color: "var(--ice-faint)", fontSize: 11 }}>—</td>
                    : <td key={i} style={{ background: heatColor(v) }}>{v}</td>
                ))}
              </tr>
            ))}
          </tbody>
        </table>
        </div>{/* /overflow-x wrapper */}
        <div className="heat-legend">
          <span>{t("legend_low")}</span>
          <span className="scale" style={{ background: "linear-gradient(90deg,#14C46A,#E0A106,#C8412F)" }}></span>
          <span>{t("legend_high")}</span>
        </div>
      </div>

      <div className="card card-pad">
        <div className="card-head"><h3>{t("raw_complaints")}</h3></div>
        <div>
          {complaints.map(c => (
            <div key={c.id} className="risk-item" style={{ cursor: 'default' }}>
              <div className="body">
                <div className="meta">
                  <SourceTag platform={c.platform} />
                  <span className="badge info"><span className="bdot"></span>{t("ai_class")}: {L(c.aiClass, lang)}</span>
                </div>
                <div className="excerpt" style={{ fontStyle: "italic", color: "var(--ice-dim)" }}>{L(c.text, lang)}</div>
              </div>
              <div className="right"><Severity value={c.severity} /></div>
            </div>
          ))}
        </div>
      </div>
    </div>
  );
}

// ====================================================================
// 5 · NARRATIVAS
// ====================================================================
function Narratives() {
  const { t } = useLang();
  return (
    <div className="fade-in">
      <div className="card card-pad">
        <div className="card-head">
          <h3>{t("narr_title")}</h3>
          <div className="spacer"></div>
          <div style={{ display: "flex", gap: 14, fontSize: 11 }}>
            {MOCK.NARR_MONTHS.map((m, i) => <span key={i} style={{ color: "var(--ice-faint)", fontFamily: "var(--font-mono)", width: 56, textAlign: "center" }}>{i % 2 === 0 || i === MOCK.NARR_MONTHS.length - 1 ? m : ""}</span>)}
          </div>
        </div>
        {MOCK.NARRATIVES.map(n => {
          const last = n.vol[n.vol.length - 1];
          return (
            <div key={n.topic} className="narr-row">
              <div className="name">
                {topicLabel(t, n.topic)}
                <div className="sub"><SentBadge s={n.tone} t={t} /></div>
              </div>
              <div style={{ display: "flex", alignItems: "center", gap: 16 }}>
                <div style={{ flex: 1 }} className="narr-track"><NarrTrack vol={n.vol} tone={n.tone} /></div>
                <div style={{ width: 70, textAlign: "right" }}>
                  <div style={{ fontFamily: "var(--font-mono)", fontSize: 16, fontWeight: 600, color: "var(--white)" }}>{last}</div>
                  <div style={{ fontSize: 10, color: "var(--ice-faint)" }}>{t("vol")}</div>
                </div>
              </div>
            </div>
          );
        })}
      </div>
    </div>
  );
}

// ====================================================================
// 6 · ALERTAS
// ====================================================================
function Alerts({ onOpen, searchQuery }) {
  const { t, lang } = useLang();
  const translatedQ = useTranslateQuery(searchQuery);
  const q = (searchQuery || "").trim().toLowerCase();
  const alerts = MOCK.ALERTS.filter(a => {
    if (!q) return true;
    const esTitle = (L(a.title, 'es') || "").toLowerCase();
    const zhTitle = (L(a.title, 'zh') || "").toLowerCase();
    const esDesc = (L(a.desc, 'es') || "").toLowerCase();
    const zhDesc = (L(a.desc, 'zh') || "").toLowerCase();
    const qEs = window._WC_TRANSLATIONS?.zhToEs?.[q] || translatedQ || q;
    
    const titleMatch = esTitle.includes(q) || zhTitle.includes(q) || esTitle.includes(qEs);
    const descMatch = esDesc.includes(q) || zhDesc.includes(q) || esDesc.includes(qEs);
    const topicMatch = (a.topic || "").toLowerCase().includes(q);
    return titleMatch || descMatch || topicMatch;
  });
  return (
    <div className="fade-in" style={{ display: "flex", flexDirection: "column", gap: 14 }}>
      {alerts.map(a => {
        const color = a.level === "crisis" ? "var(--neg)" : a.level === "warn" ? "var(--blue-soft)" : "var(--neu)";
        return (
          <div key={a.id} className={`card alert-card lvl-${a.level === "crisis" ? "crisis" : a.level}`}>
            <div className="ico"><Ic name={a.level === "crisis" ? "flame" : "alerts"} style={{ width: 19, height: 19 }} /></div>
            <div className="body">
              <h4>{L(a.title, lang)}</h4>
              <div className="desc">{L(a.desc, lang)}</div>
              <div className="foot">
                <span className="badge warn" style={{ background: "transparent", color: color, padding: 0 }}><Ic name="alerts" style={{ width: 12, height: 12 }} />{t("threshold")}: {L(a.threshold, lang)}</span>
                <span style={{ display: "flex", alignItems: "center", gap: 5 }}><Ic name="clock" style={{ width: 12, height: 12 }} />{t("since")} {a.since}</span>
                <span style={{ marginLeft: "auto", fontSize: 11, color: "var(--ice-faint)" }}>{topicLabel(t, a.topic)}</span>
              </div>
            </div>
            <div className="viral">
              <div style={{ textAlign: "right" }}>
                <div className="lab">{t("velocity")}</div>
                <div style={{ fontFamily: "var(--font-mono)", fontWeight: 600, fontSize: 18, color }}>{a.velocity}<span style={{ fontSize: 11, color: "var(--ice-faint)" }}>/100</span></div>
              </div>
              <Sparkline data={a.spark} color={color} />
            </div>
          </div>
        );
      })}
    </div>
  );
}

// Parser inline: links [texto](url), URLs sueltas, **negrita**, *cursiva*, `código`.
// Los links se procesan primero para que las negritas dentro del texto del link funcionen.
function MdLink({ url, children }) {
  return (
    <a
      href={url.startsWith('http') ? url : `https://${url}`}
      target="_blank"
      rel="noopener noreferrer"
      style={{ color: 'var(--blue-soft)', textDecoration: 'underline', fontWeight: 500, wordBreak: 'break-word' }}
    >
      {children}
    </a>
  );
}

function parseEmphasis(text, keyBase) {
  // **negrita**, *cursiva* y `código` dentro de un fragmento de texto plano
  const parts = String(text).split(/(\*\*[^*]+\*\*|\*[^*\n]+\*|`[^`]+`)/g);
  return parts.map((part, i) => {
    const key = `${keyBase}-${i}`;
    if (part.startsWith('**') && part.endsWith('**') && part.length > 4) {
      return <strong key={key} style={{ color: 'var(--white)', fontWeight: 'bold' }}>{part.slice(2, -2)}</strong>;
    }
    if (part.startsWith('*') && part.endsWith('*') && part.length > 2) {
      return <em key={key}>{part.slice(1, -1)}</em>;
    }
    if (part.startsWith('`') && part.endsWith('`') && part.length > 2) {
      return <code key={key} style={{ background: 'rgba(255,255,255,.06)', borderRadius: 4, padding: '1px 5px', fontFamily: 'var(--font-mono)', fontSize: '0.9em' }}>{part.slice(1, -1)}</code>;
    }
    return part;
  });
}

function parseInlineMarkdown(text) {
  if (!text) return '';
  // 1º links markdown y URLs sueltas; 2º énfasis dentro de cada fragmento
  const parts = String(text).split(/(\[[^\]]+\]\([^)\s]+\)|https?:\/\/[^\s)\]]+)/g);
  return parts.map((part, i) => {
    const linkMatch = part.match(/^\[([^\]]+)\]\(([^)\s]+)\)$/);
    if (linkMatch) {
      return <MdLink key={i} url={linkMatch[2]}>{parseEmphasis(linkMatch[1], `l${i}`)}</MdLink>;
    }
    if (/^https?:\/\//.test(part)) {
      // URL suelta → link clicable (recorta puntuación final común)
      const clean = part.replace(/[.,;:]+$/, '');
      const trail = part.slice(clean.length);
      return (
        <React.Fragment key={i}>
          <MdLink url={clean}>{clean.replace(/^https?:\/\/(www\.)?/, '').slice(0, 60)}</MdLink>{trail}
        </React.Fragment>
      );
    }
    return parseEmphasis(part, i);
  });
}

function parseOpusToBullets(text, lang = 'es') {
  if (!text) return null;
  const lines = text.split('\n');
  const positives = [];
  const negatives = [];
  const general = [];

  let currentHeader = '';

  for (let line of lines) {
    const trimmed = line.trim();
    if (!trimmed) continue;

    // Detectar headers
    if (trimmed.startsWith('#')) {
      currentHeader = trimmed.toLowerCase();
      continue;
    }

    // Si es un item de lista
    if (trimmed.startsWith('-') || trimmed.startsWith('*') || trimmed.match(/^\d+\./)) {
      const clean = trimmed.replace(/^[-*\d+.]\s*/, '').replace(/\*\*+/g, ''); // Limpiar marcadores y negritas
      if (!clean) continue;

      if (currentHeader.includes('diagnóstico') || currentHeader.includes('diagnostico') || currentHeader.includes('诊断') || currentHeader.includes('diagnosis') || currentHeader.includes('action')) {
        general.push(clean);
      } else if (
        currentHeader.includes('riesgo') ||
        currentHeader.includes('oportunidad') ||
        currentHeader.includes('风险') ||
        currentHeader.includes('机遇') ||
        currentHeader.includes('机会') ||
        currentHeader.includes('risk') ||
        currentHeader.includes('opportunit')
      ) {
        // Si la línea misma o la sección habla de oportunidad o positivo
        const lower = clean.toLowerCase();
        if (
          lower.includes('oportunidad') ||
          lower.includes('fuerte') ||
          lower.includes('ventaja') ||
          lower.includes('机会') ||
          lower.includes('机遇') ||
          lower.includes('opportunit') ||
          lower.includes('strength') ||
          lower.includes('advantage') ||
          currentHeader.includes('oportunidad') ||
          currentHeader.includes('机遇') ||
          currentHeader.includes('机会') ||
          currentHeader.includes('opportunit')
        ) {
          positives.push(clean);
        } else {
          negatives.push(clean);
        }
      } else if (
        currentHeader.includes('implicación') ||
        currentHeader.includes('implicacion') ||
        currentHeader.includes('aprender') ||
        currentHeader.includes('hacer') ||
        currentHeader.includes('启示') ||
        currentHeader.includes('影响') ||
        currentHeader.includes('行动') ||
        currentHeader.includes('方案') ||
        currentHeader.includes('implication') ||
        currentHeader.includes('what to do')
      ) {
        general.push(clean);
      }
    } else {
      // Si es texto normal en Diagnóstico o Implicaciones, lo tomamos como bullet general
      if (
        currentHeader.includes('diagnóstico') ||
        currentHeader.includes('diagnostico') ||
        currentHeader.includes('诊断') ||
        currentHeader.includes('implicación') ||
        currentHeader.includes('implicacion') ||
        currentHeader.includes('启示') ||
        currentHeader.includes('影响') ||
        currentHeader.includes('diagnosis') ||
        currentHeader.includes('implication') ||
        currentHeader.includes('action')
      ) {
        if (!trimmed.startsWith('<') && trimmed.length > 10) {
          general.push(trimmed.replace(/\*\*+/g, ''));
        }
      }
    }
  }

  // Rellenar con fallbacks inteligentes si no se encontró nada estructurado
  return {
    positives: positives.length ? positives.slice(0, 3) : [tS(lang, "Oportunidades identificadas en el reporte completo.", "完整报告中确定的机会。", "Opportunities identified in the complete report.")],
    negatives: negatives.length ? negatives.slice(0, 3) : [tS(lang, "Riesgos potenciales detallados en el análisis completo.", "完整分析中详细说明的潜在风险。", "Potential risks detailed in the complete analysis.")],
    analysis: general.length ? general.slice(0, 3) : [tS(lang, "Diagnóstico estratégico disponible en la sección inferior.", "底部区域提供战略诊断。", "Strategic diagnosis available in the bottom section.")]
  };
}

function parseMarkdown(md) {
  if (!md) return null;
  const lines = String(md).replace(/\r\n/g, '\n').split('\n');
  const blocks = [];
  let i = 0;

  const cellStyle = { padding: '6px 10px', border: '1px solid var(--border)', fontSize: 12.5, color: 'var(--ice)', textAlign: 'left' };

  while (i < lines.length) {
    const line = lines[i];

    // Encabezados
    const h = line.match(/^(#{1,4})\s+(.*)$/);
    if (h) {
      const lvl = h[1].length;
      const content = parseInlineMarkdown(h[2]);
      const styles = [
        { marginTop: '16px', marginBottom: '8px', color: 'var(--white)', fontSize: '1.5em' },
        { marginTop: '14px', marginBottom: '8px', color: 'var(--white)', fontSize: '1.25em', borderBottom: '1px solid var(--border)', paddingBottom: '4px' },
        { marginTop: '12px', marginBottom: '6px', color: 'var(--white)', fontSize: '1.1em' },
        { marginTop: '10px', marginBottom: '6px', color: 'var(--white)', fontSize: '1em' },
      ][lvl - 1];
      const Tag = `h${lvl}`;
      blocks.push(<Tag key={i} style={styles}>{content}</Tag>);
      i++; continue;
    }

    // Separador horizontal
    if (/^\s*(-{3,}|\*{3,}|_{3,})\s*$/.test(line)) {
      blocks.push(<hr key={i} style={{ border: 'none', borderTop: '1px solid var(--border)', margin: '14px 0' }} />);
      i++; continue;
    }

    // Tablas: agrupar líneas consecutivas que empiezan con |
    if (line.trim().startsWith('|')) {
      const tableLines = [];
      while (i < lines.length && lines[i].trim().startsWith('|')) { tableLines.push(lines[i].trim()); i++; }
      const rows = tableLines
        .filter(l => !/^\|[\s\-:|]+\|$/.test(l)) // saltar fila separadora |---|---|
        .map(l => l.replace(/^\||\|$/g, '').split('|').map(c => c.trim()));
      if (rows.length) {
        blocks.push(
          <div key={`t${i}`} style={{ overflowX: 'auto', margin: '10px 0' }}>
            <table style={{ borderCollapse: 'collapse', width: '100%' }}>
              <thead>
                <tr>{rows[0].map((c, ci) => <th key={ci} style={{ ...cellStyle, color: 'var(--white)', fontWeight: 700, background: 'rgba(255,255,255,.03)' }}>{parseInlineMarkdown(c)}</th>)}</tr>
              </thead>
              <tbody>
                {rows.slice(1).map((r, ri) => <tr key={ri}>{r.map((c, ci) => <td key={ci} style={cellStyle}>{parseInlineMarkdown(c)}</td>)}</tr>)}
              </tbody>
            </table>
          </div>
        );
      }
      continue;
    }

    // Listas con viñetas (agrupar en <ul>)
    if (/^\s*[-*]\s+/.test(line)) {
      const items = [];
      while (i < lines.length && /^\s*[-*]\s+/.test(lines[i])) {
        items.push(lines[i].replace(/^\s*[-*]\s+/, ''));
        i++;
      }
      blocks.push(
        <ul key={`ul${i}`} style={{ margin: '6px 0 10px', paddingLeft: 24 }}>
          {items.map((it, k) => <li key={k} style={{ marginBottom: '4px', listStyleType: 'disc', color: 'var(--ice)', lineHeight: 1.55 }}>{parseInlineMarkdown(it)}</li>)}
        </ul>
      );
      continue;
    }

    // Listas numeradas (agrupar en <ol>)
    if (/^\s*\d+\.\s+/.test(line)) {
      const items = [];
      while (i < lines.length && /^\s*\d+\.\s+/.test(lines[i])) {
        items.push(lines[i].replace(/^\s*\d+\.\s+/, ''));
        i++;
      }
      blocks.push(
        <ol key={`ol${i}`} style={{ margin: '6px 0 10px', paddingLeft: 24 }}>
          {items.map((it, k) => <li key={k} style={{ marginBottom: '4px', listStyleType: 'decimal', color: 'var(--ice)', lineHeight: 1.55 }}>{parseInlineMarkdown(it)}</li>)}
        </ol>
      );
      continue;
    }

    // Cita
    if (line.startsWith('> ')) {
      blocks.push(
        <blockquote key={i} style={{ borderLeft: '3px solid var(--blue)', margin: '8px 0', padding: '4px 12px', color: 'var(--ice-dim)', fontStyle: 'italic' }}>
          {parseInlineMarkdown(line.slice(2))}
        </blockquote>
      );
      i++; continue;
    }

    // Línea vacía
    if (line.trim() === '') {
      blocks.push(<div key={i} style={{ height: '8px' }} />);
      i++; continue;
    }

    // Párrafo normal
    blocks.push(<p key={i} style={{ marginBottom: '10px', lineHeight: '1.6', color: 'var(--ice)' }}>{parseInlineMarkdown(line)}</p>);
    i++;
  }
  return blocks;
}

function DeepResearchView() {
  const { t, lang } = useLang();
  const [view, setView] = React.useState('grid'); // 'grid' | 'details'
  const [selectedComp, setSelectedComp] = React.useState(null);
  const [status, setStatus] = React.useState('idle'); // 'idle' | 'loading' | 'done' | 'empty' | 'error'
  const [result, setResult] = React.useState(null);
  const [errorMsg, setErrorMsg] = React.useState('');

  React.useEffect(() => {
    if (!selectedComp) return;
    setStatus('loading');
    setResult(null);
    setErrorMsg('');

    fetch(`/api/deep-research/entity/${selectedComp.key}`)
      .then(res => {
        if (!res.ok) throw new Error("Failed to load history");
        return res.json();
      })
      .then(async (data) => {
        if (data && data.length > 0) {
          const item = data[0]; // el más reciente

          let enContent = window._DR_EN_CACHE.content[item.id] || '';
          let enAnalysis = window._DR_EN_CACHE.analysis[item.id] || '';

          if (lang === 'en' && !enContent) {
            enContent = await translateMarkdownToEn(item.content);
            window._DR_EN_CACHE.content[item.id] = enContent;
          }
          if (lang === 'en' && item.analysis && !enAnalysis) {
            enAnalysis = await translateMarkdownToEn(item.analysis);
            window._DR_EN_CACHE.analysis[item.id] = enAnalysis;
          }

          setResult({
            id: item.id,
            content: { es: item.content, zh: item.content_zh || item.content, en: enContent || item.content },
            sources: item.sources,
            analysis: item.analysis ? { es: item.analysis, zh: item.analysis_zh || item.analysis, en: enAnalysis || item.analysis } : null,
            period: getPeriod().labelEs,
            query: item.prompt
          });
          setStatus('done');
        } else {
          setStatus('empty');
        }
      })
      .catch(err => {
        console.error('Error loading deep research history:', err);
        setErrorMsg(tS(lang, 'Error al cargar el reporte de investigación.', '加载调研报告失败。', 'Failed to load research report.'));
        setStatus('error');
      });
  }, [selectedComp, lang]);

  const handleSelect = (c) => {
    setSelectedComp(c);
    setView('details');
  };

  const handleBack = () => {
    setView('grid');
    setSelectedComp(null);
    setStatus('idle');
    setResult(null);
  };

  const kosherList = (window.MOCK.COMP_KOSHER || []).map(c => {
    const key = c.key || c.name.toLowerCase().replace(' ', '');
    return { ...c, key };
  });
  const controversialList = (window.MOCK.COMP_AZTLAN || []).map(c => {
    const key = c.key || c.name.toLowerCase().replace(' ', '');
    return { ...c, key };
  });

  if (view === 'grid') {
    const allCompsList = [
      ...kosherList,
      ...controversialList
    ].sort((a, b) => b.sov - a.sov); // sort by SOV

    return (
      <div className="fade-in">
        <div className="section-head" style={{ marginBottom: 20 }}>
          <div>
            <h2>{t('deep_research_title')}</h2>
            <div className="desc">{t('deep_research_sub')}</div>
          </div>
        </div>

        <div className="card card-pad">
          <div className="card-head" style={{ marginBottom: 16 }}>
            <h3>{tS(lang, 'Competidores', '分析实体 (Competidores)', 'Competitors')}</h3>
          </div>
          <div style={{ display: 'grid', gridTemplateColumns: 'repeat(auto-fill, minmax(280px, 1fr))', gap: '12px' }}>
            {allCompsList.map(c => {
              const Tilt3D = window.Tilt3D || (({ children, ...p }) => <div {...p}>{children}</div>);
              return (
                <Tilt3D key={c.name} max={6} className="comp-row dr-card" style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', padding: '14px', background: 'rgba(255,255,255,0.02)', border: '1px solid var(--border)', borderRadius: '12px', cursor: 'pointer' }} onClick={() => handleSelect(c)}>
                  <div style={{ display: 'flex', alignItems: 'center', gap: 12 }}>
                    <window.CompLogo name={c.name} color={c.color} />
                    <div>
                      <div style={{ fontWeight: 600, color: 'var(--white)' }}>{c.name}</div>
                      <div style={{ fontSize: 10.5, color: 'var(--ice-faint)', fontFamily: 'var(--font-mono)' }}>{c.sov}% Share of Voice</div>
                    </div>
                  </div>
                  <button className="btn btn-ghost" onClick={(e) => { e.stopPropagation(); handleSelect(c); }} style={{ padding: '6px 13px', fontSize: 12, background: c.tier === 'controversial' ? 'rgba(200,80,60,0.1)' : 'rgba(255,255,255,0.07)', borderColor: c.tier === 'controversial' ? 'rgba(200,80,60,0.2)' : 'rgba(255,255,255,0.15)', color: c.tier === 'controversial' ? 'var(--neg)' : 'var(--brand)' }}>
                    {tS(lang, 'Analizar', '分析', 'Analyze')} <window.Ic name="arrowRight" style={{ width: 12, height: 12 }} />
                  </button>
                </Tilt3D>
              );
            })}
          </div>
        </div>
      </div>
    );
  }

  return (
    <div className="fade-in">
      <div className="scan-banner" style={{ marginBottom: 18 }}>
        <div style={{ display: 'flex', alignItems: 'center', gap: 14 }}>
          <button className="btn btn-ghost" onClick={handleBack} style={{ padding: '6px 12px', fontSize: 12 }}>
            {t('back_to_comps')}
          </button>
          <window.CompLogo name={selectedComp.name} color={selectedComp.color} />
          <div>
            <div style={{ fontSize: 11, color: 'var(--ice-faint)', textTransform: 'uppercase', letterSpacing: '.08em', fontWeight: 600 }}>
              {t('deep_research_title')}
            </div>
            <h2 style={{ fontSize: 18, color: 'var(--white)', margin: 0 }}>{selectedComp.name}</h2>
          </div>
        </div>
      </div>

      {status === 'loading' && (
        <div className="fade-in" style={{ display: 'flex', flexDirection: 'column', alignItems: 'center', justifyContent: 'center', padding: 80, gap: 16 }}>
          <div className="loading-spinner"></div>
          <div style={{ color: 'var(--ice-faint)', fontSize: 13 }}>
            {tS(lang, 'Cargando reporte de investigación...', '正在加载调研报告...', 'Loading research report...')}
          </div>
        </div>
      )}

      {status === 'empty' && (
        <div className="card card-pad" style={{ textAlign: 'center', padding: 40 }}>
          <div style={{ fontSize: 14, color: 'var(--ice-dim)', marginBottom: 8 }}>
            {tS(lang, 'No se encontró un reporte de investigación para este competidor.', '未找到该实体的调研报告。', 'No research report found for this competitor.')}
          </div>
        </div>
      )}

      {status === 'error' && (
        <div className="card card-pad" style={{ border: '1px solid rgba(224,83,61,0.25)', background: 'rgba(224,83,61,0.05)' }}>
          <div style={{ display: 'flex', alignItems: 'flex-start', gap: 12, marginBottom: 14 }}>
            <span style={{ width: 32, height: 32, borderRadius: 8, background: 'rgba(224,83,61,0.15)', display: 'flex', alignItems: 'center', justifyContent: 'center', flexShrink: 0 }}><Ic name="alert" style={{ width: 16, height: 16, color: 'var(--neg)' }} /></span>
            <div>
              <div style={{ fontSize: 14, fontWeight: 700, color: 'var(--neg)', marginBottom: 4 }}>
                {t('deep_research_err')}
              </div>
              <div style={{ fontSize: 12, color: 'var(--ice-dim)', fontFamily: 'var(--font-mono)' }}>
                {errorMsg}
              </div>
            </div>
          </div>
          <button className="btn btn-ghost" onClick={() => handleSelect(selectedComp)} style={{ fontSize: 12 }}>
            {t('deep_research_retry')}
          </button>
        </div>
      )}

      {status === 'done' && result && (
        <div className="card card-pad" style={{ animation: 'fadeIn .4s ease' }}>
          <div className="card-head" style={{ marginBottom: 18 }}>
            <div>
              <h3 style={{ fontSize: 15 }}>{tS(lang, 'Reporte de Investigación', '调研报告', 'Research Report')}</h3>
              <div style={{ fontSize: 11, color: 'var(--ice-faint)', marginTop: 3, fontFamily: 'var(--font-mono)' }}>
                {tS(lang, 'Reporte de investigación profunda en tiempo real', '实时深度研究报告', 'Real-time deep research report')}
              </div>
            </div>
          </div>

          {/* Tarjetas de Análisis (Bullets) en Deep Research */}
          {result.analysis && (() => {
            // Resumen ejecutivo desde el parser estructurado; fallback al parser legado
            const clean = (s) => String(s || '').replace(/\*\*/g, '').replace(/\*/g, '').trim();
            const parsed = typeof window.parseCommAnalysis === 'function' ? window.parseCommAnalysis(L(result.analysis, lang)) : null;
            let bullets;
            if (parsed) {
              const lead = (b) => clean(b.title || b.text);
              const altas = (parsed.actions || []).filter(a => /alta|高/i.test(a.priority)).slice(0, 3).map(a => clean(a.action));
              bullets = {
                positives: parsed.opps.slice(0, 3).map(lead),
                negatives: parsed.risks.slice(0, 3).map(lead),
                analysis: altas.length ? altas : parsed.diagnosis.slice(0, 2).map(clean),
              };
              if (!bullets.positives.length) bullets.positives = [lang === 'zh' ? '详见下方完整分析。' : 'Ver análisis completo abajo.'];
              if (!bullets.negatives.length) bullets.negatives = [lang === 'zh' ? '详见下方完整分析。' : 'Ver análisis completo abajo.'];
            } else {
              bullets = parseOpusToBullets(L(result.analysis, lang), lang);
            }
            if (!bullets) return null;
            return (
              <div style={{
                display: "grid",
                gridTemplateColumns: "repeat(auto-fit, minmax(280px, 1fr))",
                gap: "16px",
                marginBottom: "20px"
              }}>
                {/* Tarjeta 1: Puntos Fuertes / Oportunidades (Verde) */}
                <Tilt3D className="card card-pad" style={{
                  borderLeft: "4px solid var(--brand)",
                  display: "flex",
                  flexDirection: "column",
                  gap: "12px",
                  background: "rgba(255,255,255,0.02)"
                }}>
                  <div style={{ display: "flex", alignItems: "center", gap: "8px" }}>
                    <span style={{
                      width: "24px",
                      height: "24px",
                      borderRadius: "50%",
                      background: "rgba(255,255,255,0.08)",
                      display: "inline-flex",
                      alignItems: "center",
                      justifyContent: "center",
                      color: "var(--brand)"
                    }}>
                      <window.Ic name="check" style={{ width: 14, height: 14 }} />
                    </span>
                    <h4 style={{ margin: 0, color: "var(--white)", fontSize: "14px", fontWeight: "600" }}>
                      {tS(lang, 'Cosas Buenas / Oportunidades', '优点 / 机会', 'Pros / Opportunities')}
                    </h4>
                  </div>
                  <ul style={{ margin: 0, paddingLeft: "16px", listStyleType: "none", display: "flex", flexDirection: "column", gap: "8px" }}>
                    {bullets.positives.map((pt, idx) => (
                      <li key={idx} style={{ fontSize: "12.5px", lineHeight: "1.5", color: "var(--ice)", position: "relative" }}>
                        <span style={{ position: "absolute", left: "-16px", color: "var(--brand)" }}>•</span>
                        {pt}
                      </li>
                    ))}
                  </ul>
                </Tilt3D>

                {/* Tarjeta 2: Puntos Débiles / Riesgos (Rojo) */}
                <Tilt3D className="card card-pad" style={{
                  borderLeft: "4px solid var(--neg)",
                  display: "flex",
                  flexDirection: "column",
                  gap: "12px",
                  background: "rgba(224,83,61,0.03)"
                }}>
                  <div style={{ display: "flex", alignItems: "center", gap: "8px" }}>
                    <span style={{
                      width: "24px",
                      height: "24px",
                      borderRadius: "50%",
                      background: "rgba(224,83,61,0.1)",
                      display: "inline-flex",
                      alignItems: "center",
                      justifyContent: "center",
                      color: "var(--neg)"
                    }}>
                      <window.Ic name="x" style={{ width: 12, height: 12 }} />
                    </span>
                    <h4 style={{ margin: 0, color: "var(--white)", fontSize: "14px", fontWeight: "600" }}>
                      {tS(lang, 'Cosas Malas / Riesgos', '缺点 / 风险', 'Cons / Risks')}
                    </h4>
                  </div>
                  <ul style={{ margin: 0, paddingLeft: "16px", listStyleType: "none", display: "flex", flexDirection: "column", gap: "8px" }}>
                    {bullets.negatives.map((pt, idx) => (
                      <li key={idx} style={{ fontSize: "12.5px", lineHeight: "1.5", color: "var(--ice)", position: "relative" }}>
                        <span style={{ position: "absolute", left: "-16px", color: "var(--neg)" }}>•</span>
                        {pt}
                      </li>
                    ))}
                  </ul>
                </Tilt3D>

                {/* Tarjeta 3: Diagnóstico / Qué Hacer (Azul) */}
                <Tilt3D className="card card-pad" style={{
                  borderLeft: "4px solid var(--blue-soft)",
                  display: "flex",
                  flexDirection: "column",
                  gap: "12px",
                  background: "rgba(42,111,219,0.03)"
                }}>
                  <div style={{ display: "flex", alignItems: "center", gap: "8px" }}>
                    <span style={{
                      width: "24px",
                      height: "24px",
                      borderRadius: "50%",
                      background: "rgba(42,111,219,0.1)",
                      display: "inline-flex",
                      alignItems: "center",
                      justifyContent: "center",
                      color: "var(--blue-soft)"
                    }}>
                      <window.Ic name="shield" style={{ width: 13, height: 13 }} />
                    </span>
                    <h4 style={{ margin: 0, color: "var(--white)", fontSize: "14px", fontWeight: "600" }}>
                      {tS(lang, 'Diagnóstico / Qué Hacer', '诊断 / 行动建议', 'Diagnosis / Actions')}
                    </h4>
                  </div>
                  <ul style={{ margin: 0, paddingLeft: "16px", listStyleType: "none", display: "flex", flexDirection: "column", gap: "8px" }}>
                    {bullets.analysis.map((pt, idx) => (
                      <li key={idx} style={{ fontSize: "12.5px", lineHeight: "1.5", color: "var(--ice)", position: "relative" }}>
                        <span style={{ position: "absolute", left: "-16px", color: "var(--blue-soft)" }}>•</span>
                        {pt}
                      </li>
                    ))}
                  </ul>
                </Tilt3D>
              </div>
            );
          })()}

          <div style={{ background: 'rgba(255,255,255,0.015)', border: '1px solid var(--border)', borderRadius: 12, padding: 20, marginBottom: 20 }}>
            {parseMarkdown(L(result.content, lang))}
          </div>

          <div style={{ fontSize: 12, fontWeight: 700, color: 'var(--ice-dim)', marginBottom: 12, letterSpacing: '.05em', textTransform: 'uppercase' }}>
            {t('deep_research_sources')} ({result.sources?.length ?? 0})
          </div>
          <div style={{ display: 'flex', flexDirection: 'column', gap: 10 }}>
            {(result.sources || []).map((src, i) => {
              let domain = '';
              try { domain = new URL(src.url).hostname.replace('www.', ''); } catch {}
              return (
                <div key={i}
                  style={{ background: 'rgba(255,255,255,.015)', border: '1px solid var(--border)', borderRadius: 10, padding: '12px 14px', transition: 'border-color .15s,background .15s' }}
                  onMouseEnter={e => { e.currentTarget.style.borderColor = 'var(--border-strong)'; e.currentTarget.style.background = 'rgba(255,255,255,.03)'; }}
                  onMouseLeave={e => { e.currentTarget.style.borderColor = 'var(--border)'; e.currentTarget.style.background = 'rgba(255,255,255,.015)'; }}
                >
                  <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 10 }}>
                    <div style={{ minWidth: 0 }}>
                      <a href={src.url} target="_blank" rel="noopener noreferrer" style={{ fontSize: 13, fontWeight: 600, color: 'var(--blue-soft)', textDecoration: 'none', display: 'flex', alignItems: 'center', gap: 8 }}>
                        {src.favicon && <img src={src.favicon} alt="" style={{ width: 16, height: 16, borderRadius: 3, objectFit: 'contain' }} onError={(e) => e.target.style.display = 'none'} />}
                        <span style={{ overflow: 'hidden', textOverflow: 'ellipsis', whiteSpace: 'nowrap' }}>{src.title || src.url}</span>
                      </a>
                      <div style={{ fontSize: 10.5, color: 'var(--ice-faint)', marginTop: 4 }}>
                        {domain}
                      </div>
                    </div>
                    <a href={src.url} target="_blank" rel="noopener noreferrer" className="btn btn-ghost" style={{ padding: '4px 10px', fontSize: 11, marginLeft: 'auto' }}>
                      <window.Ic name="external" style={{ width: 12, height: 12 }} />
                    </a>
                  </div>
                </div>
              );
            })}
          </div>

          {/* ── Capa extra: Análisis de Comunicación Corporativa (parseado e interactivo) ── */}
          {result.analysis && (
            <div style={{ marginTop: 28 }}>
              <CommAnalysis md={L(result.analysis, lang)} lang={lang} />
            </div>
          )}
        </div>
      )}

      <style>{`
        @keyframes drShimmer { 0%{background-position:-200% 0} 100%{background-position:200% 0} }
        @keyframes drPulse { 0%,100%{opacity:1;transform:scale(1)} 50%{opacity:.5;transform:scale(.75)} }
      `}</style>
    </div>
  );
}

Object.assign(window, { Summary, Mentions, Competitors, CompReviews, Collection, Narratives, Alerts, Excerpt, topicLabel, DeepResearchView, parseMarkdown, parseInlineMarkdown });
