/* global React, RadioIcon, RadioBottomNav */
// Yomee — Radiografía dashboard SHELL.
// Header + background + bottom nav. No content cards yet (added in later prompts).
//
// Profile is driven by a single `profile` prop. The 4 profiles share an
// identical header layout but swap their accent color, user pill, profile
// pill, and dummy "first name" / initials.

const { useState: useStateRG } = React;

// ────────────────────────────────────────────────
// Profile palette + dummy header data
// (per spec — overrides whatever lives in RadiografiaData.jsx so the shell
// reads cleanly on its own.)
// ────────────────────────────────────────────────
// Header homologation (May 2026):
// All archetypes now share an IDENTICAL top card. The only thing that
// changes between variants is the archetype pill's background color
// (per-archetype accent token from the Design System). The card
// background itself is always Yomee brand green for every archetype.
//
// `color` here is the archetype-pill color (NOT the card background).
// The legacy per-archetype card-color field has been removed — see
// RADIO_CARD_BG below.
//
// Mapping rationale:
//   • Totalero    → --yo-mint-700  (#37B78B — energetic brand green)
//   • Parcialero  → --yo-orange-soft (warm "in-between" amber)
//   • Mínimero    → --yo-coral     (alert / cost — small payment risk)
//   • Reiniciador → --yo-lavender  (cycle / fresh start)
//
// Note on spec ↔ design system reconciliation: the brief listed
// archetype names "Totalero / Parcialero / Mínimero / Reiniciador" and
// `#2BAE80` for the green. The codebase shipped with a different fourth
// archetype set (`optimizador / escalador`) and the closest design-system
// green is `--yo-mint-700` (#37B78B). I've kept the existing archetype
// keys to avoid breaking every other screen, mapped the new pill colors
// onto them, and used the actual brand token instead of hardcoding
// `#2BAE80` — the brief was explicit about preferring the token.
const RADIO_CARD_BG     = "var(--yo-mint-700)";          // brand green for top card
const RADIO_CARD_RADIUS = "0 0 var(--radius-card) var(--radius-card)";

// Profile-icon background colors — kept in lockstep with PROFILE_CHIP in
// ProfileScreen.jsx so the dashboard's top-card avatar visually matches
// the IdentityCard avatar inside Mi perfil. If either table changes,
// update both.
const PROFILE_CHIP_BG = {
  totalero:    "#1a6b3f",
  optimizador: "#185fa5",
  escalador:   "#7c2d12",
  reiniciador: "#4c1d95",
};

const RADIO_HEADER = {
  totalero:    { color: "var(--yo-mint-700)",     initials: "AR", firstName: "Ana R.",    pillLabel: "Totalero"    },
  optimizador: { color: "var(--yo-orange-soft)",  initials: "LM", firstName: "Luis M.",   pillLabel: "Parcialero"  },
  escalador:   { color: "var(--yo-coral)",        initials: "CV", firstName: "Carlos V.", pillLabel: "Mínimero"    },
  reiniciador: { color: "var(--yo-lavender)",     initials: "SP", firstName: "Sofía P.",  pillLabel: "Reiniciador" },
};

// Soft warm grey body background — pairs with the cream design system but
// reads as "page" rather than "card". Used wherever --color-background-tertiary
// is referenced.
const BODY_BG = "rgb(238, 235, 230)";

// Secondary text color across body cards.
const SECONDARY_FG = "#6b6b6b";

// ────────────────────────────────────────────────
// Hero config per archetype.
//
// Static parts (eyebrow, amount color, stat labels) are fixed per archetype.
// Dynamic parts — amount, subline, scope tag, stat values — are derived
// live from `uploadedBanks` via buildHeroView() below.
//
// `partialSuffix` is the per-archetype tail shown after the bank list when
// uploads are incomplete. On a complete upload the tail is replaced with
// `análisis completo`.
//
// `perBank` holds the contribution each bank makes to the hero amount
// (interest / overpayment / savings, depending on archetype). Summed across
// `uploadedBanks` to produce the live amount.
// ────────────────────────────────────────────────
const RADIO_HERO = {
  totalero: {
    eyebrow: "Puedes ahorrar hasta",
    amountColor: "#1a6b3f",
    // Totalero subline is opportunity-framed; partial state references
    // the limited data set, complete state replaces with `análisis completo`.
    sublinePrefix: "optimizando con los datos de",
    partialSuffix: "el ahorro puede ser mayor",
    perBank: { BBVA: 11400, Nu: 4200, Banorte: 3000 },
    healthByCount: { 1: "82/100", 2: "85/100", 3: "88/100" },
    healthColor: "#1a6b3f",
    stat2Label: "Costo anual de tarjetas",
    stat2Color: "#c97a00",
    perBankStat2: { BBVA: 1200, Nu: 480, Banorte: 360 },
  },
  optimizador: {
    eyebrow: "Pagas de más cada año",
    amountColor: "#c0392b",
    sublinePrefix: "solo con los datos de",
    partialSuffix: "puede ser más",
    perBank: { BBVA: 14400, Nu: 6800, Banorte: 4200 },
    healthByCount: { 1: "58/100", 2: "54/100", 3: "50/100" },
    healthColor: "#c97a00",
    stat2Label: "Intereses al año",
    stat2Color: "#c0392b",
  },
  escalador: {
    eyebrow: "Intereses detectados este año",
    amountColor: "#c0392b",
    sublinePrefix: "con los datos de",
    partialSuffix: "puede ser más alto",
    perBank: { BBVA: 38400, Nu: 14200, Banorte: 9600 },
    healthByCount: { 1: "31/100", 2: "28/100", 3: "24/100" },
    healthColor: "#c0392b",
    stat2Label: "Intereses al año",
    stat2Color: "#c0392b",
  },
  reiniciador: {
    eyebrow: "Intereses al año detectados",
    amountColor: "#c0392b",
    sublinePrefix: "con los datos de",
    partialSuffix: "el ciclo puede ser más amplio",
    perBank: { BBVA: 21600, Nu: 8400, Banorte: 5200 },
    healthByCount: { 1: "44/100", 2: "41/100", 3: "38/100" },
    healthColor: "#c97a00",
    stat2Label: "Intereses al año",
    stat2Color: "#c0392b",
  },
};

// Natural-language es-MX list of bank names: [A] → "A", [A,B] → "A y B",
// [A,B,C] → "A, B y C".
function formatBankList(names) {
  if (!names || names.length === 0) return "";
  if (names.length === 1) return names[0];
  if (names.length === 2) return `${names[0]} y ${names[1]}`;
  const head = names.slice(0, -1).join(", ");
  return `${head} y ${names[names.length - 1]}`;
}

function formatCurrency(n) {
  return `$${Math.round(n).toLocaleString("en-US")}`;
}

// Build the live hero view (amount, subline, tag, stats) from the shared
// upload state. `uploadedBankNames` is the ordered list of display names,
// `totalExpected` is the user's reportedCount.
function buildHeroView(profile, uploadedBankNames, totalExpected) {
  const cfg = RADIO_HERO[profile] || RADIO_HERO.totalero;
  const count = uploadedBankNames.length;

  // ── Empty state ─────────────────────────────────────────────────
  if (count === 0) {
    return {
      eyebrow: cfg.eyebrow,
      amountColor: cfg.amountColor,
      amountText: "—",
      isEmpty: true,
      subline: "Carga tu primer estado de cuenta para ver tu análisis.",
      scopeWarning: null,
      stat1: { label: "Salud financiera", value: "—/100", color: cfg.healthColor },
      stat2: { label: cfg.stat2Label,     value: "$—",    color: cfg.stat2Color },
    };
  }

  const isComplete = count >= totalExpected;
  const bankList = formatBankList(uploadedBankNames);

  // ── Amount: sum per-bank contributions across uploaded banks ────
  const total = uploadedBankNames.reduce(
    (sum, n) => sum + (cfg.perBank[n] || 0),
    0,
  );

  // ── Subline: "<prefix> <bankList> — <suffix>" ───────────────────
  const suffix = isComplete ? "análisis completo" : cfg.partialSuffix;
  const subline = `${cfg.sublinePrefix} ${bankList} — ${suffix}`;

  // ── Scope tag: hidden when complete; otherwise "Basado en …" ────
  let scopeWarning = null;
  if (!isComplete) {
    if (count === 1) scopeWarning = `Basado en ${bankList} únicamente`;
    else             scopeWarning = `Basado en ${bankList}`;
  }

  // ── Stat row ────────────────────────────────────────────────────
  const healthValue = cfg.healthByCount[Math.min(count, 3)] || cfg.healthByCount[1];
  // Totalero stat2 is "Costo anual de tarjetas" — its own per-bank table.
  // For the rest, stat2 mirrors the hero amount (Intereses al año).
  let stat2Value;
  if (cfg.perBankStat2) {
    const s = uploadedBankNames.reduce(
      (sum, n) => sum + (cfg.perBankStat2[n] || 0),
      0,
    );
    stat2Value = formatCurrency(s);
  } else {
    stat2Value = formatCurrency(total);
  }

  return {
    eyebrow: cfg.eyebrow,
    amountColor: cfg.amountColor,
    amountText: formatCurrency(total),
    isEmpty: false,
    subline,
    scopeWarning,
    stat1: { label: "Salud financiera", value: healthValue,  color: cfg.healthColor },
    stat2: { label: cfg.stat2Label,     value: stat2Value,   color: cfg.stat2Color },
  };
}

// ────────────────────────────────────────────────
// Scenarios (BBVA-only, BBVA+Nu, stale-month)
//
// NOTE — the Mayo 2026 statements upload progress card no longer reads
// `monthLabel`, `pct`, `pctLabel`, or `banks` from this table. Those four
// fields are now derived live from the user's actual upload state
// (cardsAnswer + uploadedFiles). The remaining fields below — `cta`
// (legacy Pocopin nudge label) and `scopeWarning` (hero card chip) —
// still drive the rest of the dashboard.
// ────────────────────────────────────────────────
const RADIO_SCENARIOS_LOCAL = {
  A: {
    monthLabel: "Mayo 2026",
    pct: 50,
    pctLabel: "1 de 2 bancos",
    banks: [
      { name: "BBVA", done: true  },
      { name: "Nu",   done: false },
    ],
    cta: "Subir estado de Nu",
    scopeWarning: "Basado en BBVA únicamente",
  },
  B: {
    monthLabel: "Mayo 2026",
    pct: 67,
    pctLabel: "2 de 3 bancos",
    banks: [
      { name: "BBVA",    done: true  },
      { name: "Nu",      done: true  },
      { name: "Banorte", done: false },
    ],
    cta: "Subir estado de Banorte",
    scopeWarning: "Basado en BBVA y Nu",
  },
  C: {
    monthLabel: "Septiembre 2025 (desactualizado)",
    pct: 100,
    pctLabel: "Mes anterior",
    banks: [
      { name: "BBVA", done: true },
      { name: "Nu",   done: true },
    ],
    cta: "Actualizar con octubre 2025",
    scopeWarning: null,
  },
};

// ────────────────────────────────────────────────
// Hero card
//
// Fully data-driven view over `view` (built upstream by buildHeroView).
// Renders:
//  - eyebrow (fixed per archetype)
//  - amount ("—" in empty state)
//  - subline (empty-state copy or "<prefix> <bankList> — <suffix>")
//  - scope warning tag (hidden when empty or complete)
//  - empty-state primary CTA (only when no statements uploaded)
//  - stats row (placeholders in empty state)
// ────────────────────────────────────────────────
function HeroCard({ view, blurred, onGoToUpload }) {
  return (
    <section
      style={{
        background: "#fff",
        borderRadius: 14,
        padding: 16,
        boxShadow: "0 1px 2px rgba(20,32,28,0.04)",
      }}
    >
      {/* Eyebrow (always shown — preserves archetype tone in empty state) */}
      <div
        style={{
          fontFamily: "var(--font-mono)",
          fontSize: 11,
          fontWeight: 700,
          letterSpacing: "0.08em",
          textTransform: "uppercase",
          color: SECONDARY_FG,
        }}
      >
        {view.eyebrow}
      </div>

      {/* Amount row */}
      <div
        style={{
          marginTop: 6,
          display: "flex",
          alignItems: "baseline",
          gap: 6,
        }}
      >
        <span
          style={{
            fontFamily: "var(--font-mono)",
            fontSize: 28,
            fontWeight: 500,
            letterSpacing: "-0.01em",
            color: view.amountColor,
            // Don't blur the empty placeholder — there's nothing to hide.
            filter: blurred && !view.isEmpty ? "blur(7px)" : "none",
            transition: "filter 160ms ease-out",
          }}
        >
          {view.amountText}
        </span>
        <span
          style={{
            fontFamily: "var(--font-body)",
            fontSize: 13,
            fontWeight: 400,
            color: SECONDARY_FG,
          }}
        >
          /año
        </span>
      </div>

      {/* Subline */}
      <div
        style={{
          marginTop: 4,
          fontFamily: "var(--font-body)",
          fontSize: 12,
          fontWeight: 400,
          lineHeight: 1.4,
          color: SECONDARY_FG,
        }}
      >
        {view.subline}
      </div>

      {/* Scope warning chip — hidden when empty or complete */}
      {view.scopeWarning && (
        <div
          style={{
            marginTop: 10,
            display: "inline-flex",
            alignItems: "center",
            gap: 6,
            padding: "4px 8px 4px 7px",
            borderRadius: 6,
            background: "#fef3c7",
            border: "1px solid #f59e0b",
          }}
        >
          <span
            aria-hidden="true"
            style={{ fontSize: 10, lineHeight: 1, color: "#92400e" }}
          >
            ⚠
          </span>
          <span
            style={{
              fontFamily: "var(--font-body)",
              fontSize: 10,
              fontWeight: 600,
              letterSpacing: "0.02em",
              color: "#92400e",
              whiteSpace: "nowrap",
            }}
          >
            {view.scopeWarning}
          </span>
        </div>
      )}

      {/* Empty-state primary CTA — styled to match the legacy
          "Subir estado de Nu" CTA (green #1a6b3f, body 13/600, no caps,
          chevron-right icon). Only rendered when zero statements. */}
      {view.isEmpty && (
        <button
          type="button"
          onClick={onGoToUpload}
          style={{
            marginTop: 14,
            paddingTop: 12,
            width: "100%",
            borderTop: "1px solid rgba(20,32,28,0.08)",
            background: "transparent",
            border: "none",
            borderTop: "1px solid rgba(20,32,28,0.08)",
            display: "flex",
            alignItems: "center",
            justifyContent: "space-between",
            gap: 8,
            cursor: "pointer",
            color: "#1a6b3f",
            fontFamily: "var(--font-body)",
            fontSize: 13,
            fontWeight: 600,
            letterSpacing: "0.005em",
          }}
        >
          <span>Subir mi primer estado de cuenta</span>
          <RadioIcon name="chevron-right" stroke="#1a6b3f" size={18} />
        </button>
      )}

      {/* Divider — hidden in empty state (CTA already separates above) */}
      {!view.isEmpty && (
        <div
          style={{
            height: 1,
            background: "rgba(20,32,28,0.08)",
            margin: "14px 0 12px",
          }}
        />
      )}

      {/* Stats row */}
      <div
        style={{
          marginTop: view.isEmpty ? 14 : 0,
          display: "grid",
          gridTemplateColumns: "1fr 1px 1fr",
          alignItems: "stretch",
          gap: 12,
        }}
      >
        <Stat label={view.stat1.label} value={view.stat1.value} color={view.stat1.color} blurred={blurred} />
        <div style={{ background: "rgba(20,32,28,0.08)" }} />
        <Stat label={view.stat2.label} value={view.stat2.value} color={view.stat2.color} blurred={blurred} />
      </div>
    </section>
  );
}

function Stat({ label, value, color, blurred }) {
  // Don't blur "82/100" style scores — only currency. The empty-state
  // placeholders (`$—`, `—/100`) carry no real number, so don't blur them
  // either; they read as missing data, not hidden data.
  const isCurrency = /^\$/.test(value);
  const hasRealNumber = /\d/.test(value);
  return (
    <div style={{ display: "flex", flexDirection: "column", gap: 4 }}>
      <span
        style={{
          fontFamily: "var(--font-body)",
          fontSize: 11,
          fontWeight: 500,
          letterSpacing: "0.01em",
          color: SECONDARY_FG,
          lineHeight: 1.3,
        }}
      >
        {label}
      </span>
      <span
        style={{
          fontFamily: "var(--font-mono)",
          fontSize: 16,
          fontWeight: 500,
          letterSpacing: "-0.005em",
          color,
          filter: blurred && isCurrency && hasRealNumber ? "blur(6px)" : "none",
          transition: "filter 160ms ease-out",
        }}
      >
        {value}
      </span>
    </div>
  );
}

// ────────────────────────────────────────────────
// Progress block
// ────────────────────────────────────────────────
// Map a parsed bank key (as stored in UploadScreen file rows) to the
// display name shown on the chip.
const BANK_DISPLAY_NAME = {
  bbva:    "BBVA",
  nu:      "Nu",
  banorte: "Banorte",
};

// Mirror of UploadScreen's UPLOAD_SEQUENCE — the simulated next-file
// pattern used everywhere a tap appends to uploadedFiles. Kept as a
// local copy here (not imported) since UploadScreen's constant is
// module-private; the brief says to reuse the existing simulated upload
// pattern rather than introduce a new mechanism.
const RG_UPLOAD_SEQUENCE = [
  { name: "Estado_cta_BBVA_enero.pdf",    bank: "bbva"    },
  { name: "Estado_cta_NU_enero.pdf",      bank: "nu"      },
  { name: "Estado_cta_Banorte_enero.pdf", bank: "banorte" },
];

// Map cardsAnswer (questionnaire response) to the expected statements
// total. "3 o más" defaults to 3 in the prototype, per spec.
function totalExpectedFromCardsAnswer(cardsAnswer) {
  if (cardsAnswer === "2 tarjetas") return 2;
  if (cardsAnswer === "3 o más")    return 3;
  // "1 tarjeta" or null/skipped → 1
  return 1;
}

function ProgressCard({ scenario, cardsAnswer, uploadedFiles, extraDeclaredCount = 0, onGoToUpload, onAgregarTap, onDismissPendingSlot, onPendingChipTap }) {
  const data = RADIO_SCENARIOS_LOCAL[scenario] || RADIO_SCENARIOS_LOCAL.A;

  // Live-derived state. The numerator counts every uploaded statement.
  // The denominator is sourced from the user's cards answer (per spec),
  // minus any pending positions the user has manually dismissed via the
  // × on a grey chip. Floored at 0 (defensive).
  const reportedRaw = totalExpectedFromCardsAnswer(cardsAnswer);
  const reported = Math.max(0, reportedRaw - extraDeclaredCount);
  const uploaded = uploadedFiles || [];
  const uploadedCount = uploaded.length;
  // Total expected — at minimum the (reduced) reported count, but never
  // less than the number actually uploaded (defensive: a user may upload
  // more banks than they originally reported; everything they uploaded
  // must still render).
  const total = Math.max(reported, uploadedCount);
  const isComplete = uploadedCount >= total;
  // Pending = positions in [uploadedCount, total). A × appears on a grey
  // chip iff there is at least one pending position that can be removed
  // without going below uploadedCount — i.e. total > uploadedCount.
  // The last remaining pending position can always be dismissed (it
  // resolves the bar to 100%).
  const canDismissPending = total > uploadedCount && !!onDismissPendingSlot;

  // Chip list: one chip per uploaded statement (in upload order), then
  // `Banco N` placeholders for every remaining position through `total`.
  const chips = [];
  uploaded.forEach((f, i) => {
    chips.push({
      key: `known-${i}`,
      label: BANK_DISPLAY_NAME[f.bank] || f.bank,
      done: true,
    });
  });
  for (let i = uploadedCount; i < total; i++) {
    chips.push({
      key: `placeholder-${i}`,
      label: `Banco ${i + 1}`,
      done: false,
      dismissable: canDismissPending,
    });
  }

  // Progress bar fill — exact percentage so 1/3 reads 33.33% etc.
  const pct = total > 0 ? (uploadedCount / total) * 100 : 0;

  // Counter copy: "X de Y bancos".
  const counterLabel = `${uploadedCount} de ${total} bancos`;

  return (
    <section
      style={{
        background: "#fff",
        borderRadius: 14,
        padding: 12,
        boxShadow: "0 1px 2px rgba(20,32,28,0.04)",
      }}
    >
      {/* Month + pct */}
      <div
        style={{
          display: "flex",
          alignItems: "center",
          justifyContent: "space-between",
          gap: 8,
        }}
      >
        <span
          style={{
            fontFamily: "var(--font-body)",
            fontSize: 12,
            fontWeight: 600,
            color: "#1f2937",
            letterSpacing: "0.005em",
            whiteSpace: "nowrap",
          }}
        >
          {data.monthLabel}
        </span>
        <span
          style={{
            fontFamily: "var(--font-body)",
            fontSize: 12,
            fontWeight: 500,
            color: SECONDARY_FG,
            letterSpacing: "0.005em",
            whiteSpace: "nowrap",
          }}
        >
          {counterLabel}
        </span>
      </div>

      {/* Bar */}
      <div
        style={{
          marginTop: 8,
          height: 5,
          borderRadius: 999,
          background: "rgba(20,32,28,0.08)",
          overflow: "hidden",
        }}
      >
        <div
          style={{
            width: `${pct}%`,
            height: "100%",
            background: "#1a6b3f",
            transition: "width 280ms ease-out",
          }}
        />
      </div>

      {/* Bank chips */}
      <div
        style={{
          marginTop: 10,
          display: "flex",
          flexWrap: "wrap",
          gap: 6,
        }}
      >
        {chips.map((c) => (
          <BankChip
            key={c.key}
            name={c.label}
            done={c.done}
            onTap={!c.done ? onPendingChipTap : undefined}
            onDismiss={c.dismissable ? onDismissPendingSlot : undefined}
          />
        ))}
        {uploadedCount >= reported &&
          uploadedCount >= totalExpectedFromCardsAnswer(cardsAnswer) && (
            <button
              type="button"
              onClick={onAgregarTap || onGoToUpload}
              style={{
                display: "inline-flex",
                alignItems: "center",
                gap: 6,
                padding: "4px 10px",
                borderRadius: 999,
                background: "transparent",
                border: `1px dashed ${SECONDARY_FG}`,
                fontFamily: "var(--font-body)",
                fontSize: 12,
                fontWeight: 600,
                color: SECONDARY_FG,
                letterSpacing: "0.005em",
                cursor: "pointer",
                lineHeight: 1.4,
              }}
            >
              + Agregar
            </button>
          )}
      </div>

      {/* CTA row — hidden when uploads are complete. Same visual style as
          the legacy "Subir estado de Nu" CTA: green #1a6b3f, body font,
          13px / 600, no caps, no trailing arrow, chevron-right icon. */}
      {!isComplete && (
        <button
          type="button"
          onClick={onGoToUpload}
          style={{
            marginTop: 12,
            paddingTop: 10,
            width: "100%",
            borderTop: "1px solid rgba(20,32,28,0.08)",
            background: "transparent",
            border: "none",
            borderTop: "1px solid rgba(20,32,28,0.08)",
            display: "flex",
            alignItems: "center",
            justifyContent: "space-between",
            gap: 8,
            cursor: "pointer",
            color: "#1a6b3f",
            fontFamily: "var(--font-body)",
            fontSize: 13,
            fontWeight: 600,
            letterSpacing: "0.005em",
          }}
        >
          <span>Completar Radiografía</span>
          <RadioIcon name="chevron-right" stroke="#1a6b3f" size={18} />
        </button>
      )}
    </section>
  );
}

function BankChip({ name, done, onTap, onDismiss }) {
  if (done) {
    return (
      <span
        style={{
          display: "inline-flex",
          alignItems: "center",
          gap: 6,
          padding: "4px 10px",
          borderRadius: 999,
          background: "#f0faf4",
          border: "1px solid #b6dfc4",
          fontFamily: "var(--font-body)",
          fontSize: 12,
          fontWeight: 600,
          color: "#146c43",
          letterSpacing: "0.005em",
        }}
      >
        <span
          style={{
            width: 6,
            height: 6,
            borderRadius: "50%",
            background: "#22a06b",
          }}
        />
        {name}
      </span>
    );
  }
  // Placeholder ("Banco N") chip — muted/disabled variant so it reads as
  // a pending position rather than a known-but-not-uploaded bank.
  // Tapping the chip body opens the file picker (onTap). When
  // `onDismiss` is provided, a circular × badge sits at the trailing
  // edge inside the chip boundary so the user can retract this declared
  // position (reduces totalExpected by 1). The × stops propagation so
  // tapping it does NOT also fire onTap / open the picker.
  const interactive = !!onTap;
  return (
    <span
      role={interactive ? "button" : undefined}
      tabIndex={interactive ? 0 : undefined}
      onClick={interactive ? onTap : undefined}
      onKeyDown={interactive ? (e) => {
        if (e.key === "Enter" || e.key === " ") {
          e.preventDefault();
          onTap();
        }
      } : undefined}
      style={{
        display: "inline-flex",
        alignItems: "center",
        gap: 6,
        // Trailing padding bumped to 6px when × is present so the 18px
        // circle has breathing room and never clips the chip boundary.
        padding: onDismiss ? "3px 6px 3px 10px" : "4px 10px",
        borderRadius: 999,
        background: "transparent",
        border: "1px dashed rgba(20,32,28,0.22)",
        fontFamily: "var(--font-body)",
        fontSize: 12,
        fontWeight: 500,
        color: "#9aa0a6",
        letterSpacing: "0.005em",
        cursor: interactive ? "pointer" : "default",
      }}
    >
      <span
        style={{
          width: 6,
          height: 6,
          borderRadius: "50%",
          background: "rgba(20,32,28,0.18)",
        }}
      />
      {name}
      {onDismiss && (
        <button
          type="button"
          onClick={(e) => {
            e.stopPropagation();
            onDismiss();
          }}
          aria-label={`Quitar ${name}`}
          onMouseEnter={(e) => { e.currentTarget.style.background = "rgba(0,0,0,0.22)"; }}
          onMouseLeave={(e) => { e.currentTarget.style.background = "rgba(0,0,0,0.12)"; }}
          onMouseDown={(e) => { e.currentTarget.style.background = "rgba(0,0,0,0.22)"; }}
          onMouseUp={(e) => { e.currentTarget.style.background = "rgba(0,0,0,0.22)"; }}
          style={{
            marginLeft: 2,
            width: 18,
            height: 18,
            padding: 0,
            borderRadius: "50%",
            background: "rgba(0,0,0,0.12)",
            border: "none",
            cursor: "pointer",
            color: "#ffffff",
            fontFamily: "var(--font-body)",
            fontSize: 12,
            lineHeight: 1,
            fontWeight: 600,
            transition: "background 120ms ease-out",
            display: "inline-flex",
            alignItems: "center",
            justifyContent: "center",
            flexShrink: 0,
          }}
        >
          ×
        </button>
      )}
    </span>
  );
}

// ────────────────────────────────────────────────
// Pocopin nudge data per profile × scenario
// ────────────────────────────────────────────────
const POCOPIN_BY_PROFILE = {
  totalero: {
    A: { msg: "Tu análisis de Nu está pendiente. Sin él, tu radiografía solo refleja parte de tu situación real.", bold: "Tu análisis de Nu está pendiente." },
    B: { msg: "Falta Banorte para completar tu panorama. Los insights que ves son con base en BBVA y Nu.", bold: "Falta Banorte para completar tu panorama." },
    C: { msg: "Tu radiografía es del mes pasado. Sube tu estado de octubre para tener el análisis más reciente.", bold: "Tu radiografía es del mes pasado." },
  },
  optimizador: {
    A: { msg: "Sin tu estado de Nu no puedo ver el cuadro completo. Tu costo real de deuda puede ser mayor.", bold: "Sin tu estado de Nu no puedo ver el cuadro completo." },
    B: { msg: "Falta Banorte. Si tienes saldo ahí, el cálculo de intereses está incompleto.", bold: "Falta Banorte." },
    C: { msg: "Estos datos son del mes pasado. Sube octubre para ver si tu situación mejoró.", bold: "Estos datos son del mes pasado." },
  },
  escalador: {
    A: { msg: "Si tienes deuda en Nu también, el número real de intereses es mayor al que ves aquí.", bold: "Si tienes deuda en Nu también, el número real es mayor." },
    B: { msg: "Banorte pendiente. Con 3 tarjetas en deuda el plan de consolidación cambia.", bold: "Banorte pendiente." },
    C: { msg: "El análisis es del mes pasado. Tu deuda puede haber crecido — sube el estado de octubre.", bold: "El análisis es del mes pasado." },
  },
  reiniciador: {
    A: { msg: "Sin Nu no puedo ver el ciclo completo. El patrón real puede ser diferente al que muestro.", bold: "Sin Nu no puedo ver el ciclo completo." },
    B: { msg: "Con Banorte pendiente, el análisis del ciclo está incompleto. El plan de ahorro puede cambiar.", bold: "Con Banorte pendiente, el análisis del ciclo está incompleto." },
    C: { msg: "El análisis es del mes pasado. Sube octubre para ver si el ciclo continúa o se interrumpió.", bold: "El análisis es del mes pasado." },
  },
};

// ────────────────────────────────────────────────
// First step data per profile
// ────────────────────────────────────────────────
const FIRST_STEP_BY_PROFILE = {
  totalero: {
    title: "Reclama la condonación de tu anualidad BBVA",
    subtitle: "8 meses cumpliendo el requisito sin reclamarlo.",
    saving: "Ahorro inmediato: $1,200",
    cta: "Ver cómo solicitarlo",
  },
  optimizador: {
    title: "Cambia a tarjeta con CAT 28%",
    subtitle: "Solo con BBVA detectado. Puede haber más opciones cuando agregues tus otros bancos.",
    saving: "Ahorro estimado: $9,600/año",
    cta: "Ver comparativa",
  },
  escalador: {
    title: "Consolida tu deuda en un crédito al 24%",
    subtitle: "Análisis basado en BBVA únicamente. Al agregar tus otros estados el plan puede mejorar.",
    saving: "Ahorro estimado: $47,000",
    cta: "Ver opciones",
  },
  reiniciador: {
    title: "El patrón que repites — y cómo romperlo",
    subtitle: "Detectado en BBVA. Con todos tus estados el análisis del ciclo será más preciso.",
    saving: "El problema está en el flujo mensual",
    cta: "Ver mi historial",
  },
};

const FIRST_STEP_SCOPE = {
  A: "Estimado con BBVA",
  B: "Estimado con BBVA y Nu",
  C: null,
};

// ────────────────────────────────────────────────
// Pocopin nudge card
// ────────────────────────────────────────────────
function PocopinCard({ profile, scenario, ctaLabel, isComplete, hasPendingPositions, onGoToUpload }) {
  // When the upload is complete, the "missing bank" pocopin tip must hide
  // entirely — there's nothing pending. The C (stale-month) scenario is
  // about outdated data, not missing banks, so it still renders.
  if (isComplete && scenario !== "C") return null;

  // When there are pending positions (Banco N placeholders) and the tip
  // would otherwise reference a specific missing bank, switch to a
  // generic copy that does not name a bank. Applies to scenarios A and B.
  const useGenericCopy = hasPendingPositions && scenario !== "C";

  let title, body, cta, boldPhrase;
  if (useGenericCopy) {
    title = "Sin todos tus estados de cuenta no puedo ver el ciclo completo.";
    body  = "El patrón real puede ser diferente al que muestro.";
    cta   = "Completar mi radiografía";
    boldPhrase = title;
  } else {
    const data = (POCOPIN_BY_PROFILE[profile] || POCOPIN_BY_PROFILE.totalero)[scenario]
              || POCOPIN_BY_PROFILE[profile].A;
    title = null;
    body  = data.msg;
    boldPhrase = data.bold;
    cta = ctaLabel;
  }

  // Render the message with the bold key phrase inlined.
  const fullMsg = title ? `${title} ${body}` : body;
  const idx = fullMsg.indexOf(boldPhrase);
  let msgNode;
  if (idx === -1) {
    msgNode = fullMsg;
  } else {
    msgNode = (
      <>
        {fullMsg.slice(0, idx)}
        <strong style={{ fontWeight: 700, color: "#1f2937" }}>{boldPhrase}</strong>
        {fullMsg.slice(idx + boldPhrase.length)}
      </>
    );
  }

  return (
    <section
      style={{
        background: "#fff",
        borderRadius: 14,
        padding: 12,
        boxShadow: "0 1px 2px rgba(20,32,28,0.04)",
        display: "flex",
        gap: 10,
        alignItems: "flex-start",
      }}
    >
      <img
        src="assets/pocopin-3.png"
        alt=""
        width={32}
        height={32}
        style={{ flexShrink: 0, display: "block" }}
      />
      <div style={{ flex: 1, minWidth: 0 }}>
        <div
          style={{
            fontFamily: "var(--font-mono)",
            fontSize: 10,
            fontWeight: 700,
            letterSpacing: "0.10em",
            textTransform: "uppercase",
            color: "#9aa0a6",
          }}
        >
          POCOPIN
        </div>
        <div
          style={{
            marginTop: 4,
            fontFamily: "var(--font-body)",
            fontSize: 13,
            lineHeight: 1.4,
            color: "#1f2937",
            letterSpacing: "0.005em",
          }}
        >
          {msgNode}
        </div>
        <button
          type="button"
          onClick={useGenericCopy && onGoToUpload ? onGoToUpload : undefined}
          style={{
            marginTop: 8,
            padding: 0,
            background: "transparent",
            border: "none",
            cursor: "pointer",
            display: "inline-flex",
            alignItems: "center",
            gap: 4,
            color: "#1a6b3f",
            fontFamily: "var(--font-body)",
            fontSize: 12,
            fontWeight: 500,
            letterSpacing: "0.005em",
            whiteSpace: "nowrap",
          }}
        >
          {cta}
          <span aria-hidden="true" style={{ fontSize: 12, lineHeight: 1, whiteSpace: "nowrap" }}>→</span>
        </button>
      </div>
    </section>
  );
}

// ────────────────────────────────────────────────
// First step card
// ────────────────────────────────────────────────
// Build the "Estimado con ..." chip text from the actual list of uploaded
// banks. Falls back to the legacy scenario-keyed table when no upload
// state has been threaded through (Tweaks-only navigation, demos).
function buildEstimadoText(uploadedBankNames) {
  if (!uploadedBankNames || uploadedBankNames.length === 0) return null;
  if (uploadedBankNames.length === 1) return `Estimado con ${uploadedBankNames[0]}`;
  if (uploadedBankNames.length === 2) {
    return `Estimado con ${uploadedBankNames[0]} + ${uploadedBankNames[1]}`;
  }
  const head = uploadedBankNames.slice(0, -1).join(", ");
  const last = uploadedBankNames[uploadedBankNames.length - 1];
  return `Estimado con ${head} + ${last}`;
}

function FirstStepCard({ profile, scenario, uploadedBankNames, isComplete }) {
  const data = FIRST_STEP_BY_PROFILE[profile] || FIRST_STEP_BY_PROFILE.totalero;
  let scopeText;
  if (isComplete) {
    scopeText = null;
  } else if (uploadedBankNames && uploadedBankNames.length > 0) {
    scopeText = buildEstimadoText(uploadedBankNames);
  } else {
    scopeText = FIRST_STEP_SCOPE[scenario];
  }

  return (
    <section
      style={{
        background: "#fff",
        borderRadius: 14,
        border: "2px solid #1a6b3f",
        padding: "10px 12px 0",
        boxShadow: "0 1px 2px rgba(20,32,28,0.04)",
      }}
    >
      {/* Header row */}
      <div
        style={{
          display: "flex",
          alignItems: "center",
          gap: 6,
        }}
      >
        <span
          style={{
            width: 8,
            height: 8,
            borderRadius: "50%",
            background: "#1a6b3f",
            flexShrink: 0,
          }}
        />
        <span
          style={{
            fontFamily: "var(--font-mono)",
            fontSize: 10,
            fontWeight: 500,
            letterSpacing: "0.10em",
            textTransform: "uppercase",
            color: "#1a6b3f",
          }}
        >
          TU PRIMER PASO
        </span>
      </div>

      {/* Body */}
      <div style={{ paddingTop: 8, paddingBottom: 12 }}>
        <div
          style={{
            fontFamily: "var(--font-body)",
            fontSize: 14,
            fontWeight: 500,
            lineHeight: 1.3,
            color: "#1f2937",
            letterSpacing: "0.005em",
          }}
        >
          {data.title}
        </div>
        <div
          style={{
            marginTop: 4,
            fontFamily: "var(--font-body)",
            fontSize: 12,
            fontWeight: 400,
            lineHeight: 1.4,
            color: SECONDARY_FG,
            letterSpacing: "0.005em",
          }}
        >
          {data.subtitle}
        </div>
        <div
          style={{
            marginTop: 8,
            fontFamily: "var(--font-body)",
            fontSize: 12,
            fontWeight: 500,
            color: "#1a6b3f",
            letterSpacing: "0.005em",
          }}
        >
          {data.saving}
        </div>

        {scopeText && (
          <div
            style={{
              marginTop: 10,
              display: "inline-flex",
              alignItems: "center",
              gap: 6,
              padding: "4px 8px 4px 7px",
              borderRadius: 6,
              background: "#fef3c7",
              border: "1px solid #f59e0b",
            }}
          >
            <span aria-hidden="true" style={{ fontSize: 10, lineHeight: 1, color: "#92400e" }}>
              ⚠
            </span>
            <span
              style={{
                fontFamily: "var(--font-body)",
                fontSize: 10,
                fontWeight: 600,
                letterSpacing: "0.02em",
                color: "#92400e",
                whiteSpace: "nowrap",
              }}
            >
              {scopeText}
            </span>
          </div>
        )}
      </div>

      {/* CTA row */}
      <button
        type="button"
        style={{
          width: "calc(100% + 24px)",
          marginLeft: -12,
          marginRight: -12,
          padding: "10px 12px",
          background: "transparent",
          border: "none",
          borderTop: "1px solid rgba(20,32,28,0.08)",
          display: "flex",
          alignItems: "center",
          justifyContent: "space-between",
          gap: 8,
          cursor: "pointer",
          color: "#1a6b3f",
          fontFamily: "var(--font-body)",
          fontSize: 13,
          fontWeight: 500,
          letterSpacing: "0.005em",
        }}
      >
        <span>{data.cta}</span>
        <RadioIcon name="chevron-right" stroke="#1a6b3f" size={18} />
      </button>
    </section>
  );
}

// ────────────────────────────────────────────────
// Insights data per profile
// ────────────────────────────────────────────────
const INSIGHTS_BY_PROFILE = {
  totalero: [
    { icon: "plane",  iconBg: "#e6f3ee", iconStroke: "#1a6b3f", title: "Beneficios de viaje sin usar",        meta: "6 vuelos · $4,800/año",         badge: { label: "Más por lo mismo", kind: "save" }, detail: "Tu perfil califica para tarjeta con salas VIP + seguro. $4,800/año en beneficios sin aprovechar.", cta: "Ver opciones",      scoped: true  },
    { icon: "coffee", iconBg: "#fef3e2", iconStroke: "#c97a00", title: "Gastos hormiga: cafeterías",          meta: "$2,340 · últimos 3 meses",      badge: { label: "Oportunidad",      kind: "warn" }, detail: "4–5 visitas/semana sin cashback. ~$700/año recuperables con otra tarjeta.",                       cta: "Ver tarjetas",     scoped: true  },
  ],
  optimizador: [
    { icon: "split",  iconBg: "#fef3e2", iconStroke: "#c97a00", title: "Patrón de pago parcial",              meta: "Últimos 3 meses",               badge: { label: "Costo oculto",     kind: "warn" }, detail: "Pagas 40–70% del total. En 12 meses = 2.3 meses de sueldo perdidos.",                            cta: "Ver plan",         scoped: true  },
    { icon: "tag",    iconBg: "#e6f3ee", iconStroke: "#1a6b3f", title: "MSI disponibles en tus tiendas",       meta: "Compras +$3k detectadas",       badge: { label: "Usar a tu favor",  kind: "save" }, detail: "El 70% de las tiendas donde compras ofrecen MSI que no aprovechas.",                              cta: "Ver tiendas",      scoped: false },
  ],
  escalador: [
    { icon: "alert",  iconBg: "#fde2e2", iconStroke: "#c0392b", title: "Solo el mínimo — tu deuda no baja",   meta: "Detectado en BBVA",             badge: { label: "Urgente",          kind: "risk" }, detail: "Pagando el mínimo tu deuda tarda 9 años en liquidarse.",                                          cta: "Ver simulación",   scoped: true  },
    { icon: "subscriptions", iconBg: "#fef3e2", iconStroke: "#c97a00", title: "Suscripciones activas mientras debes", meta: "11 activas · $2,640/mes", badge: { label: "Liberar flujo",   kind: "warn" }, detail: "Cancelar las que no usas libera $1,100/mes hacia tu deuda.",                                       cta: "Ver suscripciones", scoped: false },
  ],
  reiniciador: [
    { icon: "calendar", iconBg: "#fef3e2", iconStroke: "#c97a00", title: "Optimiza tu fecha de corte",        meta: "Patrón detectado en BBVA",     badge: { label: "Optimizar",        kind: "warn" }, detail: "Mover gastos 6 días después del corte da 28 días extra sin intereses.",                          cta: "Ver cómo",         scoped: true  },
    { icon: "anchor",   iconBg: "#e6f3ee", iconStroke: "#1a6b3f", title: "Ahorro automático como ancla",      meta: "Sin ahorro activo detectado",  badge: { label: "Romper el ciclo",  kind: "save" }, detail: "$500/mes automáticos el día de nómina crean el colchón que rompe el ciclo.",                       cta: "Ver cómo",         scoped: false },
  ],
};

const BADGE_STYLE = {
  save: { bg: "#d1fae5", color: "#065f46" },
  warn: { bg: "#fef3c7", color: "#92400e" },
  risk: { bg: "#fee2e2", color: "#991b1b" },
};

const INSIGHT_SCOPE = {
  A: "Solo BBVA",
  B: "Solo BBVA y Nu",
  C: null,
};

function Badge({ kind, children }) {
  const s = BADGE_STYLE[kind] || BADGE_STYLE.warn;
  return (
    <span
      style={{
        display: "inline-flex",
        alignItems: "center",
        padding: "2px 6px",
        borderRadius: 4,
        background: s.bg,
        color: s.color,
        fontFamily: "var(--font-body)",
        fontSize: 10,
        fontWeight: 600,
        letterSpacing: "0.02em",
        whiteSpace: "nowrap",
      }}
    >
      {children}
    </span>
  );
}

function ScopeChip({ children }) {
  return (
    <span
      style={{
        display: "inline-flex",
        alignItems: "center",
        padding: "2px 6px",
        borderRadius: 4,
        background: "#fef3c7",
        color: "#92400e",
        fontFamily: "var(--font-body)",
        fontSize: 10,
        fontWeight: 600,
        letterSpacing: "0.02em",
        whiteSpace: "nowrap",
      }}
    >
      {children}
    </span>
  );
}

function InsightCard({ insight, scopeText }) {
  const [expanded, setExpanded] = useStateRG(false);
  return (
    <section
      style={{
        background: "#fff",
        borderRadius: 14,
        border: "0.5px solid rgba(20,32,28,0.10)",
        boxShadow: "0 1px 2px rgba(20,32,28,0.04)",
        overflow: "hidden",
      }}
    >
      <button
        type="button"
        onClick={() => setExpanded((v) => !v)}
        style={{
          width: "100%",
          background: "transparent",
          border: "none",
          padding: 12,
          display: "flex",
          alignItems: "center",
          gap: 10,
          cursor: "pointer",
          textAlign: "left",
        }}
        aria-expanded={expanded}
      >
        <span
          style={{
            width: 30,
            height: 30,
            borderRadius: 8,
            background: insight.iconBg,
            display: "inline-flex",
            alignItems: "center",
            justifyContent: "center",
            flexShrink: 0,
          }}
        >
          <RadioIcon name={insight.icon} stroke={insight.iconStroke} size={18} />
        </span>
        <div style={{ flex: 1, minWidth: 0 }}>
          <div
            style={{
              fontFamily: "var(--font-body)",
              fontSize: 13,
              fontWeight: 500,
              color: "#1f2937",
              letterSpacing: "0.005em",
            }}
          >
            {insight.title}
          </div>
          <div
            style={{
              marginTop: 3,
              display: "flex",
              flexWrap: "wrap",
              alignItems: "center",
              gap: 6,
              fontFamily: "var(--font-body)",
              fontSize: 11,
              color: SECONDARY_FG,
              letterSpacing: "0.005em",
            }}
          >
            <span style={{ whiteSpace: "nowrap" }}>{insight.meta}</span>
            <Badge kind={insight.badge.kind}>{insight.badge.label}</Badge>
            {insight.scoped && scopeText && <ScopeChip>{scopeText}</ScopeChip>}
          </div>
        </div>
        <span
          aria-hidden="true"
          style={{
            flexShrink: 0,
            transform: expanded ? "rotate(90deg)" : "rotate(0deg)",
            transition: "transform 200ms ease-out",
            display: "inline-flex",
          }}
        >
          <RadioIcon name="chevron-right" stroke="#9aa0a6" size={18} />
        </span>
      </button>

      {expanded && (
        <div>
          <div
            style={{
              padding: "0 14px 14px",
              fontFamily: "var(--font-body)",
              fontSize: 13,
              lineHeight: 1.5,
              color: SECONDARY_FG,
              letterSpacing: "0.005em",
            }}
          >
            {insight.detail}
          </div>
          <button
            type="button"
            style={{
              width: "100%",
              background: "transparent",
              border: "none",
              borderTop: "1px solid rgba(20,32,28,0.08)",
              padding: "10px 14px",
              display: "flex",
              alignItems: "center",
              justifyContent: "space-between",
              gap: 8,
              cursor: "pointer",
              color: "#1a6b3f",
              fontFamily: "var(--font-body)",
              fontSize: 13,
              fontWeight: 500,
              letterSpacing: "0.005em",
              whiteSpace: "nowrap",
            }}
          >
            <span>{insight.cta}</span>
            <RadioIcon name="chevron-right" stroke="#1a6b3f" size={18} />
          </button>
        </div>
      )}
    </section>
  );
}

function MoreOpportunities({ profile, scenario, uploadedBankNames, isComplete }) {
  const [open, setOpen] = useStateRG(false);
  const list = INSIGHTS_BY_PROFILE[profile] || INSIGHTS_BY_PROFILE.totalero;
  let scopeText;
  if (isComplete) {
    scopeText = null;
  } else if (uploadedBankNames && uploadedBankNames.length > 0) {
    if (uploadedBankNames.length === 1) {
      scopeText = `Solo ${uploadedBankNames[0]}`;
    } else {
      const head = uploadedBankNames.slice(0, -1).join(", ");
      const last = uploadedBankNames[uploadedBankNames.length - 1];
      scopeText = `Solo ${head} y ${last}`;
    }
  } else {
    scopeText = INSIGHT_SCOPE[scenario];
  }
  return (
    <div style={{ display: "flex", flexDirection: "column", gap: 10 }}>
      <button
        type="button"
        onClick={() => setOpen((v) => !v)}
        style={{
          alignSelf: "flex-start",
          background: "transparent",
          border: "none",
          padding: "6px 4px",
          cursor: "pointer",
          display: "inline-flex",
          alignItems: "center",
          gap: 6,
          fontFamily: "var(--font-body)",
          fontSize: 12,
          fontWeight: 500,
          color: SECONDARY_FG,
          letterSpacing: "0.005em",
        }}
        aria-expanded={open}
      >
        <span>{open ? "Ocultar" : `Ver más oportunidades (${list.length})`}</span>
        <span
          aria-hidden="true"
          style={{
            transform: open ? "rotate(180deg)" : "rotate(0deg)",
            transition: "transform 200ms ease-out",
            display: "inline-flex",
          }}
        >
          <RadioIcon name="chevron-down" stroke={SECONDARY_FG} size={16} />
        </span>
      </button>
      {open && list.map((ins, i) => (
        <InsightCard key={i} insight={ins} scopeText={scopeText} />
      ))}
    </div>
  );
}

// ────────────────────────────────────────────────
// Header
// ────────────────────────────────────────────────
function RadioHeader({ profile = "totalero", amountsBlurred, onToggleBlur, onOpenProfile }) {
  const baseData = RADIO_HEADER[profile] || RADIO_HEADER.totalero;
  // Read the active persona's name reactively from the global context.
  // Switching persona via the Demo pill must update the user pill name +
  // avatar initials instantly without remount.
  const yomeeUser = (window.useYomeeUser && window.useYomeeUser()) || {};
  const userName = yomeeUser.name;
  // If a runtime userName is supplied (from the active persona),
  // override the profile's dummy firstName + initials. First name is the
  // first space-separated token; initials are the first two letters of the
  // first name, uppercased.
  const data = React.useMemo(() => {
    const trimmed = (userName || "").trim();
    if (!trimmed) return baseData;
    const first = trimmed.split(" ")[0];
    return {
      ...baseData,
      firstName: first,
      initials: first.slice(0, 2).toUpperCase(),
    };
  }, [baseData, userName]);

  // Per Dashboard Homologation (May 2026):
  // The top card is now visually identical across every archetype. The
  // ONLY thing that changes is the archetype pill's background color
  // (data.color). The card's own background is the brand green.
  //
  // Structure (top → bottom):
  //   Row 1  yomee logo (left)            ·  profile icon (right)
  //          [Gratis upgrade chip is FEATURE-FLAGGED OFF — see below]
  //   Row 2  "Tu radiografía" (headline, Geist Mono — display family
  //          stands in for Poppins from the brief; brief said use the
  //          Design System token, and Geist Mono is the display token)
  //   Row 3  Archetype pill — bg = data.color, text always #FFFFFF
  //
  // Bottom corners use --radius-card (28px). Top corners are flush.
  // Eye toggle (amount blur) was previously in this header; it has been
  // moved into the hero card for the homologated design so the top card
  // stays focused on identity. `amountsBlurred` / `onToggleBlur` props
  // are kept for back-compat but no longer rendered here.
  void amountsBlurred; void onToggleBlur;

  // Feature flag for the upgrade ("Gratis") badge. Hidden in the
  // homologated design but kept in source per the brief — flip to true
  // to re-enable when the upgrade flow ships.
  const SHOW_UPGRADE_BADGE = false;

  return (
    <div
      style={{
        background: RADIO_CARD_BG,
        // Pad-top accommodates the status bar (status bar is ~54px tall in
        // this iOS frame, but the colored header bleeds behind it so the
        // status bar text sits on the colored field). Bottom padding bumped
        // to balance the now-2× yomee wordmark.
        padding: "62px 20px 26px",
        color: "#fff",
        flexShrink: 0,
        borderRadius: RADIO_CARD_RADIUS,
      }}
    >
      {/* ── Row 1: yomee logo (left) · profile icon (right) ───────── */}
      <div
        style={{
          display: "flex",
          alignItems: "center",
          justifyContent: "space-between",
          minHeight: 44,
        }}
      >
        {/* Left: yomee wordmark logo — 200% of previous size (was 22px → now 44px).
            Card vertical padding bumped above so the larger mark doesn't crowd
            the headline block. Position + aspect ratio preserved. */}
        <img
          src="assets/yomee-logo-wordmark.png"
          alt="yomee"
          style={{
            height: 44,
            width: "auto",
            display: "block",
            // Wordmark ships in dark ink — invert for the green field.
            filter: "brightness(0) invert(1)",
          }}
        />

        {/* Right: profile icon — taps open Mi perfil */}
        <div style={{ display: "flex", alignItems: "center", gap: 8 }}>
          {/* Gratis upgrade badge — feature-flagged off for now.
              Kept inline (not commented out) so the flag is the only
              switch needed to bring it back. */}
          {SHOW_UPGRADE_BADGE && (
            <button
              type="button"
              aria-label="Cambiar a plan pagado"
              style={{
                display: "inline-flex",
                alignItems: "center",
                gap: 4,
                height: 32,
                padding: "0 10px 0 8px",
                borderRadius: 16,
                background: "rgba(255,255,255,0.18)",
                border: "1px solid rgba(255,255,255,0.18)",
                color: "#fff",
                cursor: "pointer",
                fontFamily: "var(--font-body)",
                fontSize: 12,
                fontWeight: 600,
                letterSpacing: "0.01em",
              }}
            >
              <RadioIcon name="arrow-up-pill" stroke="#fff" size={14} />
              <span>Gratis</span>
            </button>
          )}

          {/* Profile icon — visually unified with the Perfil module's
              identity-card avatar (PFAvatar). Same silhouette (filled
              circle, no border), same fill (per-archetype PROFILE_CHIP.bg
              from ProfileScreen.jsx), same typography (var(--font-mono),
              weight 700, white initials, 0.02em tracking). Size preserved
              at 36px per spec — only the styling is unified, not the size. */}
          <button
            type="button"
            onClick={onOpenProfile}
            aria-label={`Cuenta de ${data.firstName} — abrir Mi perfil`}
            style={{
              width: 36,
              height: 36,
              borderRadius: "50%",
              background: PROFILE_CHIP_BG[profile] || PROFILE_CHIP_BG.totalero,
              border: "none",
              color: "#fff",
              cursor: "pointer",
              display: "inline-flex",
              alignItems: "center",
              justifyContent: "center",
              padding: 0,
              fontFamily: "var(--font-mono)",
              fontWeight: 700,
              fontSize: 13,
              letterSpacing: "0.02em",
              flexShrink: 0,
            }}
          >
            {data.initials}
          </button>
        </div>
      </div>

      {/* ── Row 2: greeting + headline ──────────────────────────── */}
      <div style={{ marginTop: 22 }}>
        {/* Greeting — Onest Medium, one step below the headline.
            Falls back to "Hola:" when firstName is unavailable. The colon
            is intentional and part of the spec'd copy. */}
        <div
          style={{
            margin: 0,
            marginBottom: 6,
            fontFamily: "'Onest', system-ui, sans-serif",
            fontWeight: 500,
            fontSize: 18,
            lineHeight: 1.2,
            letterSpacing: "0.005em",
            color: "#fff",
          }}
        >
          {data.firstName ? `Hola ${data.firstName}:` : "Hola:"}
        </div>
        <h1
          style={{
            margin: 0,
            // Brief asked for Poppins; Yomee Design System defines
            // --font-display (Geist Mono) for headlines. Using the token.
            fontFamily: "var(--font-display)",
            fontSize: 28,
            fontWeight: 600,
            lineHeight: 1.1,
            letterSpacing: "-0.005em",
            color: "#fff",
          }}
        >
          Tu radiografía
        </h1>
      </div>

      {/* ── Row 3: archetype pill ───────────────────────────────── */}
      <div style={{ marginTop: 14 }}>
        <span
          style={{
            display: "inline-block",
            padding: "6px 14px",
            borderRadius: "var(--radius-pill)",
            // Per spec: pill bg is the only per-archetype variation.
            background: data.color,
            // Subtle inner ring to lift the pill off the green field
            // when bg ≈ card bg (e.g. Totalero).
            boxShadow: "inset 0 0 0 1px rgba(255,255,255,0.28)",
            // Brief asked for Geist Mono; Design System defines
            // --font-body (Onest) for label-like body text. Using token.
            fontFamily: "var(--font-body)",
            fontSize: 13,
            fontWeight: 700,
            letterSpacing: "0.02em",
            color: "#fff",
          }}
        >
          {data.pillLabel}
        </span>
      </div>
    </div>
  );
}

// ────────────────────────────────────────────────
// "+ Agregar" inline upload modal
//
// Centered modal sheet (not full-screen) used when the user taps the
// "+ Agregar" pill on the ProgressCard. Reuses the same visual language
// as UploadScreen — same dashed/solid upload area, same body copy, same
// primary/secondary CTA stack — but lives inside the dashboard rather
// than navigating to a separate screen.
//
// Stages:
//   "upload"       → static upload area + file row (when staged)
//   "procesando"   → spinner + "Procesando archivo…"
//   "eliminando"   → spinner + "Eliminando archivo de forma segura…"
//   "actualizando" → spinner + "Actualizando tu radiografía…"
//
// The primary CTA is disabled until a file has been staged. Tapping the
// secondary CTA dismisses the modal without modifying state.
// ────────────────────────────────────────────────
function AgregarUploadModal({
  visible,
  stage,
  file,
  simLoading,
  onSimulateUpload,
  onClearFile,
  onSkip,
  onBackdrop,
  onConfirm,
}) {
  const isUploadStage = stage === "upload";
  const ctaEnabled = isUploadStage && !!file && !simLoading;

  const stageCopy = {
    procesando:   "Procesando archivo…",
    eliminando:   "Eliminando archivo de forma segura…",
    actualizando: "Actualizando tu radiografía…",
  };

  return (
    <div
      style={{
        position: "absolute",
        inset: 0,
        zIndex: 80,
        pointerEvents: "auto",
      }}
    >
      {/* Backdrop */}
      <div
        onClick={isUploadStage ? onBackdrop : undefined}
        style={{
          position: "absolute",
          inset: 0,
          background: "rgba(0,0,0,0.4)",
          opacity: visible ? 1 : 0,
          transition: "opacity 200ms ease-out",
        }}
      />

      {/* Bottom sheet — same chrome as TotaleroSingleStatementSheet */}
      <div
        role="dialog"
        aria-modal="true"
        style={{
          position: "absolute",
          left: 0,
          right: 0,
          bottom: 0,
          background: "#fff",
          borderTopLeftRadius: 20,
          borderTopRightRadius: 20,
          transform: visible ? "translateY(0)" : "translateY(100%)",
          transition: "transform 200ms ease-out",
          paddingBottom: 24,
        }}
      >
        {/* Handle bar */}
        <div
          style={{
            width: 36,
            height: 4,
            borderRadius: 2,
            background: "#E0E0E0",
            margin: "8px auto 0",
          }}
        />

        <div
          style={{
            padding: "20px 24px 0",
            display: "flex",
            flexDirection: "column",
            alignItems: "center",
            textAlign: "center",
          }}
        >
          {/* Mascot — matches the stopper sheet */}
          <img
            src="assets/pocopin-3.png"
            alt=""
            style={{
              width: 72,
              height: 72,
              objectFit: "contain",
              marginBottom: 12,
            }}
          />

          {/* Title */}
          <div
            style={{
              fontFamily: '"Geist Mono", var(--font-mono)',
              fontSize: 17,
              fontWeight: 900,
              color: "#1a1a1a",
              lineHeight: "24px",
              letterSpacing: "-0.005em",
            }}
          >
            Agregar estado de cuenta
          </div>

          {/* Body */}
          <p
            style={{
              margin: "8px 0 0",
              fontFamily: "var(--font-body)",
              fontSize: 13,
              lineHeight: 1.5,
              color: "#555",
              maxWidth: 320,
            }}
          >
            Sube otro estado de cuenta para enriquecer tu radiografía con más datos.
          </p>
        </div>

        {/* Body content — varies by stage */}
        <div style={{ padding: "16px 24px 0" }}>
          {isUploadStage ? (
            <React.Fragment>
              {/* Simulated upload area — tap appends next file in
                  RG_UPLOAD_SEQUENCE. No file picker. Mirrors the
                  UploadArea component on the upload screen. */}
              <button
                type="button"
                onClick={onSimulateUpload}
                disabled={!!file || simLoading}
                style={{
                  width: "100%",
                  background: "transparent",
                  border: `1.5px solid var(--yo-ink)`,
                  borderRadius: 14,
                  padding: "22px 14px",
                  display: "flex",
                  flexDirection: "column",
                  alignItems: "center",
                  justifyContent: "center",
                  gap: 8,
                  cursor: file || simLoading ? "default" : "pointer",
                  textAlign: "center",
                  opacity: file ? 0.5 : 1,
                  transition: "opacity 200ms ease-out",
                  minHeight: 100,
                }}
              >
                {simLoading ? (
                  <div
                    aria-label="Cargando"
                    style={{
                      width: 26,
                      height: 26,
                      borderRadius: "50%",
                      border: `2.5px solid rgba(20,32,28,0.15)`,
                      borderTopColor: "var(--yo-ink)",
                      animation: "yo-agregar-spin 0.7s linear infinite",
                    }}
                  />
                ) : (
                  <React.Fragment>
                    <svg width="30" height="30" viewBox="0 0 36 36" aria-hidden="true">
                      <path
                        d="M18 23V8 M11 15l7-7 7 7"
                        fill="none"
                        stroke="var(--yo-ink)"
                        strokeWidth="2"
                        strokeLinecap="round"
                        strokeLinejoin="round"
                      />
                      <path
                        d="M7 22v4a2 2 0 0 0 2 2h18a2 2 0 0 0 2-2v-4"
                        fill="none"
                        stroke="var(--yo-ink)"
                        strokeWidth="2"
                        strokeLinecap="round"
                        strokeLinejoin="round"
                      />
                    </svg>
                    <div
                      style={{
                        fontFamily: "var(--font-body)",
                        fontSize: 12,
                        lineHeight: "16px",
                        color: "#888",
                      }}
                    >
                      Sube archivos PDF o imagen. Máx 10 MB.
                    </div>
                  </React.Fragment>
                )}
              </button>

              {/* Staged file row — mirrors UploadScreen FileRow */}
              {file && (
                <div
                  style={{
                    marginTop: 10,
                    display: "flex",
                    alignItems: "center",
                    gap: 10,
                    background: "#F7F6F2",
                    borderRadius: 12,
                    padding: "10px 12px",
                  }}
                >
                  <div
                    style={{
                      width: 20,
                      height: 20,
                      borderRadius: "50%",
                      background: "#2BAE80",
                      flexShrink: 0,
                      display: "flex",
                      alignItems: "center",
                      justifyContent: "center",
                    }}
                  >
                    <svg width="11" height="11" viewBox="0 0 12 12" aria-hidden="true">
                      <path
                        d="M2.5 6.2 L4.9 8.6 L9.5 3.6"
                        fill="none"
                        stroke="#fff"
                        strokeWidth="2"
                        strokeLinecap="round"
                        strokeLinejoin="round"
                      />
                    </svg>
                  </div>
                  <div
                    style={{
                      flex: 1,
                      minWidth: 0,
                      fontFamily: "var(--font-mono)",
                      fontWeight: 500,
                      fontSize: 12,
                      color: "#333",
                      whiteSpace: "nowrap",
                      overflow: "hidden",
                      textOverflow: "ellipsis",
                      textAlign: "left",
                    }}
                  >
                    {file.name}
                  </div>
                  <button
                    type="button"
                    onClick={onClearFile}
                    aria-label={`Eliminar ${file.name}`}
                    style={{
                      width: 22,
                      height: 22,
                      borderRadius: "50%",
                      background: "transparent",
                      border: "none",
                      padding: 0,
                      cursor: "pointer",
                      color: "#9aa0a6",
                      display: "flex",
                      alignItems: "center",
                      justifyContent: "center",
                      flexShrink: 0,
                    }}
                  >
                    <svg width="12" height="12" viewBox="0 0 14 14" aria-hidden="true">
                      <path
                        d="M3 3l8 8M11 3l-8 8"
                        fill="none"
                        stroke="currentColor"
                        strokeWidth="1.8"
                        strokeLinecap="round"
                      />
                    </svg>
                  </button>
                </div>
              )}
            </React.Fragment>
          ) : (
            // Processing — spinner + label + step dots, replaces upload area.
            <div
              style={{
                minHeight: 130,
                borderRadius: 14,
                border: "1.5px solid rgba(20,32,28,0.08)",
                background: "#FAFAF7",
                display: "flex",
                flexDirection: "column",
                alignItems: "center",
                justifyContent: "center",
                gap: 12,
                padding: "20px 16px",
                textAlign: "center",
              }}
            >
              <div
                aria-label="Cargando"
                style={{
                  width: 28,
                  height: 28,
                  borderRadius: "50%",
                  border: `2.5px solid rgba(20,32,28,0.15)`,
                  borderTopColor: "var(--yo-ink)",
                  animation: "yo-agregar-spin 0.7s linear infinite",
                }}
              />
              <div
                style={{
                  fontFamily: "var(--font-body)",
                  fontSize: 13,
                  fontWeight: 600,
                  color: "#1a1a1a",
                  lineHeight: 1.4,
                }}
              >
                {stageCopy[stage] || "Procesando…"}
              </div>
              <div style={{ display: "flex", gap: 6 }}>
                {["procesando", "eliminando", "actualizando"].map((s, i) => {
                  const order = ["procesando", "eliminando", "actualizando"];
                  const idx = order.indexOf(stage);
                  const isDone = i < idx;
                  const isCurrent = i === idx;
                  return (
                    <span
                      key={s}
                      style={{
                        width: isCurrent ? 18 : 6,
                        height: 6,
                        borderRadius: 999,
                        background: isCurrent
                          ? "var(--yo-ink)"
                          : isDone
                            ? "rgba(20,32,28,0.55)"
                            : "rgba(20,32,28,0.18)",
                        transition: "all 200ms ease-out",
                      }}
                    />
                  );
                })}
              </div>
            </div>
          )}
        </div>

        {/* CTAs — same stack pattern as the stopper sheet. Hidden during
            processing so the modal is non-dismissable mid-flow. */}
        {isUploadStage && (
          <div
            style={{
              padding: "20px 16px 0",
              display: "flex",
              flexDirection: "column",
              gap: 10,
            }}
          >
            <button
              type="button"
              onClick={onConfirm}
              disabled={!ctaEnabled}
              style={{
                width: "100%",
                height: 50,
                borderRadius: 14,
                background: ctaEnabled ? "#1a1a1a" : "rgba(20,32,28,0.25)",
                color: "#fff",
                border: "none",
                cursor: ctaEnabled ? "pointer" : "not-allowed",
                fontFamily: '"Geist Mono", var(--font-mono)',
                fontSize: 14,
                fontWeight: 700,
                letterSpacing: "0.04em",
                transition: "background 200ms ease-out",
              }}
            >
              CONTINUAR →
            </button>
            <button
              type="button"
              onClick={onSkip}
              style={{
                width: "100%",
                height: 50,
                borderRadius: 14,
                background: "transparent",
                color: "#1a1a1a",
                border: "1.5px solid #1a1a1a",
                cursor: "pointer",
                fontFamily: '"Geist Mono", var(--font-mono)',
                fontSize: 14,
                fontWeight: 700,
                letterSpacing: "0.04em",
              }}
            >
              OMITIR
            </button>
          </div>
        )}
      </div>

      <style>{`
        @keyframes yo-agregar-spin { to { transform: rotate(360deg); } }
      `}</style>
    </div>
  );
}

// ────────────────────────────────────────────────
// Screen
// ────────────────────────────────────────────────
function RadiografiaScreen({ profile = "totalero", scenario = "A", cardsAnswer = null, uploadedFiles = [], setFiles, onGoToUpload, onOpenProfile }) {
  const [amountsBlurred, setAmountsBlurred] = useStateRG(false);
  // ── "+ Agregar" inline upload modal state ─────────────────────────
  // Tapping "+ Agregar" on the ProgressCard opens an in-place upload
  // modal (mirrors UploadScreen's UploadArea) instead of full-screen
  // navigating away. The modal walks through 4 stages:
  //   "upload"    → user picks a file (primary CTA stays disabled
  //                 until a file is staged; secondary "Omitir" closes)
  //   "procesando" → simulated parse  (~900ms)
  //   "eliminando" → simulated secure delete  (~900ms)
  //   "actualizando" → dashboard re-derive  (~900ms)
  // After "actualizando" finishes, we append the next entry from
  // RG_UPLOAD_SEQUENCE to uploadedFiles via the same `setFiles` setter
  // UploadScreen / pending-chip taps already use, then dismiss the modal.
  const [agregarOpen, setAgregarOpen] = useStateRG(false);
  const [agregarVisible, setAgregarVisible] = useStateRG(false);
  const [agregarStage, setAgregarStage] = useStateRG("upload");
  const [agregarFile, setAgregarFile] = useStateRG(null);
  // Simulated tap-to-upload loading flag — mirrors UploadScreen's
  // UploadArea spinner pattern. Tap → 300ms spinner → file row appears.
  const [agregarSimLoading, setAgregarSimLoading] = useStateRG(false);
  const openAgregar = () => {
    setAgregarStage("upload");
    setAgregarFile(null);
    setAgregarSimLoading(false);
    setAgregarOpen(true);
    setTimeout(() => setAgregarVisible(true), 16);
  };
  const closeAgregar = () => {
    setAgregarVisible(false);
    setTimeout(() => {
      setAgregarOpen(false);
      setAgregarStage("upload");
      setAgregarFile(null);
      setAgregarSimLoading(false);
    }, 220);
  };
  // Simulated upload — appends the next entry from RG_UPLOAD_SEQUENCE
  // as the staged file (mirroring the upload screen, which never opens
  // a real file picker either).
  const handleSimulateAgregarUpload = () => {
    if (agregarFile || agregarSimLoading) return;
    setAgregarSimLoading(true);
    setTimeout(() => {
      const idx = (uploadedFiles || []).length;
      const next =
        RG_UPLOAD_SEQUENCE[idx] || {
          name: `Estado_cta_extra_${idx + 1}.pdf`,
          bank: "bbva",
        };
      setAgregarFile(next);
      setAgregarSimLoading(false);
    }, 300);
  };
  // Number of declared bank positions the user has manually dismissed
  // via the × on a grey/pending chip. Each tap reduces totalExpected by 1
  // (i.e. retracts one declared bank that was never uploaded), which
  // recalculates the progress percentage immediately. Every consumer of
  // totalExpected (ProgressCard, MarketComparisonCard) re-derives off the
  // updated value with no additional sync.
  const [extraDeclaredCount, setExtraDeclaredCount] = useStateRG(0);
  const handleDismissPendingSlot = () => setExtraDeclaredCount((n) => n + 1);

  // Hidden file input — tapping a pending grey chip opens this picker
  // instead of full-screen-navigating to UploadScreen. On change we
  // append the next entry from RG_UPLOAD_SEQUENCE to uploadedFiles via
  // the same `setFiles` setter UploadScreen already uses (passed down
  // from App.jsx). The actual file the user picks isn't parsed in this
  // prototype — we just consume the tap and advance the simulated
  // sequence, matching UploadScreen's behaviour exactly.
  const fileInputRef = React.useRef(null);
  const handlePendingChipTap = () => {
    if (fileInputRef.current) {
      // Reset value so picking the same file twice still fires onChange.
      fileInputRef.current.value = "";
      fileInputRef.current.click();
    }
  };
  const handleFileInputChange = (e) => {
    const picked = e.target.files && e.target.files[0];
    if (!picked) return;
    if (typeof setFiles === "function") {
      setFiles((prev) => {
        const next = RG_UPLOAD_SEQUENCE[prev.length];
        return next ? [...prev, next] : prev;
      });
    }
    // Clear so the next pick re-fires onChange.
    e.target.value = "";
  };
  const accent = (RADIO_HEADER[profile] || RADIO_HEADER.totalero).color;
  const scen = RADIO_SCENARIOS_LOCAL[scenario] || RADIO_SCENARIOS_LOCAL.A;

  // ── Live upload state — single source of truth shared by ProgressCard,
  // PocopinCard, FirstStepCard, and MoreOpportunities. Everything below
  // (counter, chips, progress %, CTA visibility, Pocopin copy, scope tags)
  // is derived from this object. Nothing is hardcoded.
  const totalExpected = Math.max(
    0,
    totalExpectedFromCardsAnswer(cardsAnswer) - extraDeclaredCount,
  );
  const uploadedBankNames = (uploadedFiles || []).map(
    (f) => BANK_DISPLAY_NAME[f.bank] || f.bank,
  );
  const uploadedCount = uploadedBankNames.length;
  const isComplete = uploadedCount >= totalExpected;
  const hasPendingPositions = uploadedCount < totalExpected;

  return (
    <div
      style={{
        position: "absolute",
        inset: 0,
        display: "flex",
        flexDirection: "column",
        background: BODY_BG,
        // Status bar sits above this; we already pad the header for it.
      }}
    >
      <RadioHeader
        profile={profile}
        amountsBlurred={amountsBlurred}
        onToggleBlur={() => setAmountsBlurred((v) => !v)}
        onOpenProfile={onOpenProfile}
      />

      {/* Scrollable content area — empty for now. Cards land here next. */}
      <div
        data-screen-label="radiografia-body"
        style={{
          flex: 1,
          minHeight: 0,
          overflow: "auto",
          padding: "14px 14px 24px",
          display: "flex",
          flexDirection: "column",
          gap: 10,
        }}
      >
        <HeroCard
          view={buildHeroView(profile, uploadedBankNames, totalExpected)}
          blurred={amountsBlurred}
          onGoToUpload={onGoToUpload}
        />
        <ProgressCard
          scenario={scenario}
          cardsAnswer={cardsAnswer}
          uploadedFiles={uploadedFiles}
          extraDeclaredCount={extraDeclaredCount}
          onGoToUpload={onGoToUpload}
          onAgregarTap={openAgregar}
          onDismissPendingSlot={handleDismissPendingSlot}
          onPendingChipTap={handlePendingChipTap}
        />
        {/* Hidden file input — triggered when the user taps a pending
            grey chip. Kept rendered (not portal'd) at the screen root
            so its lifecycle matches the screen's. */}
        <input
          ref={fileInputRef}
          type="file"
          accept=".pdf"
          onChange={handleFileInputChange}
          style={{ display: "none" }}
          aria-hidden="true"
          tabIndex={-1}
        />
        <PocopinCard
          profile={profile}
          scenario={scenario}
          ctaLabel={scen.cta}
          isComplete={isComplete}
          hasPendingPositions={hasPendingPositions}
          onGoToUpload={onGoToUpload}
        />
        <FirstStepCard
          profile={profile}
          scenario={scenario}
          uploadedBankNames={uploadedBankNames}
          isComplete={isComplete}
        />
        {/* Market Comparison — inserted between FirstStep and Progress per
            spec. Derives every row from the SAME uploadedBankNames /
            totalExpected derivation that ProgressCard uses (see lines
            ~1560 above). When uploadedFiles changes, both this card and
            ProgressCard re-render off the same source — no new state. */}
        {window.MarketComparisonCard && (
          <window.MarketComparisonCard
            uploadedBankNames={uploadedBankNames}
            totalExpected={totalExpected}
            profile={profile}
            onGoToUpload={onGoToUpload}
          />
        )}
        <MoreOpportunities
          profile={profile}
          scenario={scenario}
          uploadedBankNames={uploadedBankNames}
          isComplete={isComplete}
        />
      </div>

      {/* Bottom nav hidden on Radiografía dashboard for all 4 profiles per
          design spec — kept mounted (display:none) so it can be re-enabled
          later without rebuilding it. */}
      <div style={{ display: "none" }} aria-hidden="true">
        <RadioBottomNav active="radio" accent={accent} />
      </div>

      {/* "+ Agregar" inline upload modal — appears as a centered card with
          a 40% backdrop. Reuses the upload-area visual language from
          UploadScreen and walks through processing → deletion → dashboard
          update before dismissing back to the dashboard. */}
      {agregarOpen && (
        <AgregarUploadModal
          visible={agregarVisible}
          stage={agregarStage}
          file={agregarFile}
          simLoading={agregarSimLoading}
          onSimulateUpload={handleSimulateAgregarUpload}
          onClearFile={() => setAgregarFile(null)}
          onSkip={closeAgregar}
          onBackdrop={closeAgregar}
          onConfirm={() => {
            // Walk through the 3 simulated stages, then append the staged
            // file and close. Total runtime ≈ 2.7s.
            const staged = agregarFile;
            setAgregarStage("procesando");
            setTimeout(() => setAgregarStage("eliminando"), 900);
            setTimeout(() => setAgregarStage("actualizando"), 1800);
            setTimeout(() => {
              if (typeof setFiles === "function" && staged) {
                setFiles((prev) => [...prev, staged]);
              }
              closeAgregar();
            }, 2700);
          }}
        />
      )}
    </div>
  );
}

window.RadiografiaScreen = RadiografiaScreen;
window.RADIO_HEADER = RADIO_HEADER;
