// app.jsx — Mi VIRAL Activación PWA
// Real PWA wrapper: splash → login → tracker stack
// Provides window.__viralNav for screen-level navigation

// Inject shake keyframe (usado por LoginScreen en error de envío)
if (typeof document !== 'undefined' && !document.getElementById('viral-shake-style')) {
  const s = document.createElement('style');
  s.id = 'viral-shake-style';
  s.textContent = '@keyframes viral-shake { 0%,100% { transform: translateX(0); } 25% { transform: translateX(-8px); } 75% { transform: translateX(8px); } }';
  document.head.appendChild(s);
}

// ─────────────────────────────────────────────────────────────
// SplashScreen — boot animation with VIRAL branding
// ─────────────────────────────────────────────────────────────
function SplashScreen({ onDone }) {
  React.useEffect(() => {
    const t = setTimeout(onDone, 1600);
    return () => clearTimeout(t);
  }, [onDone]);

  return (
    <div className="viral-app viral-screen-bg" style={{
      width: '100%', height: '100%',
      display: 'flex', flexDirection: 'column',
      alignItems: 'center', justifyContent: 'center',
      gap: 24,
      animation: 'viral-fade-in-up 0.4s ease',
    }}>
      <img
        src="logo_viral.png"
        alt="VIRAL"
        style={{
          height: 140, width: 'auto',
          userSelect: 'none', pointerEvents: 'none',
          animation: 'viral-splash-breathe 2.4s ease-in-out infinite',
        }}
      />
    </div>
  );
}

// ─────────────────────────────────────────────────────────────
// LoginScreen — identificador (teléfono o correo) + contraseña.
// La contraseña se valida contra Odoo (ViralApi.login). El servidor emite un
// token; el navegador ya no compara ninguna contraseña.
// ─────────────────────────────────────────────────────────────
// Host de Odoo desde la config global (index.html → window.VIRAL_CONFIG).
const ODOO_BASE = (window.VIRAL_CONFIG && window.VIRAL_CONFIG.ODOO_URL)
  || 'https://www.viralcel.com';
const ODOO_RESET_URL = ODOO_BASE + '/web/reset_password';

// Abre Odoo con SSO (sesión web iniciada por ticket de un solo uso), para que
// el cliente compre sin volver a loguearse. `opts` puede ser:
//   - un string: ruta local de Odoo (p.ej. '/my/reemplazos') → solo redirige.
//   - un objeto { productTmplId, line }: agrega el plan (PREPAGO) al carrito y
//     manda directo a pagar.
// Patrón popup-safe: abre la pestaña YA (dentro del gesto de clic) y le pone la
// URL cuando llega el ticket, para que el bloqueador de pop-ups no la mate.
function openViaSSO(opts) {
  const w = window.open('', '_blank');
  window.ViralApi.shopSSO(opts).then((url) => {
    if (!url) { if (w) w.close(); return; }
    if (w) w.location = url; else window.open(url, '_blank', 'noopener');
  }).catch(() => { if (w) w.close(); });
}

// Saca el id del template Odoo del slug del shop ('bonus-6231?…' → 6231).
function tmplIdFromUrl(url) {
  const m = String(url || '').match(/-(\d+)(?:\?|$)/);
  return m ? parseInt(m[1], 10) : null;
}

function LoginScreen({ onLogin, notice }) {
  const [identifier, setIdentifier] = React.useState('');
  const [password, setPassword] = React.useState('');
  const [showPwd, setShowPwd] = React.useState(false);
  const [error, setError] = React.useState('');
  const [loading, setLoading] = React.useState(false);
  const [shake, setShake] = React.useState(false);

  const idValid = (() => {
    const v = identifier.trim();
    if (v.length === 0) return false;
    const digits = v.replace(/\D/g, '');
    if (digits.length === 10) return true; // looks like phone
    if (v.includes('@') && v.includes('.')) return true; // looks like email
    return false;
  })();
  const canSubmit = idValid && password.length > 0 && !loading;

  const handleSubmit = async (e) => {
    e?.preventDefault();
    if (!canSubmit) return;
    setLoading(true);
    setError('');
    try {
      const session = await window.ViralApi.login(identifier.trim(), password);
      onLogin(session);
      // No tocar setLoading: el componente se desmonta al pasar a 'tracker'.
    } catch (err) {
      const msg = err.status === 401 ? 'Usuario o contraseña incorrectos'
                : err.status === 429 ? 'Demasiados intentos. Espera unos minutos.'
                : err.status === 409 ? (err.message || 'Inicia sesión con tu correo.')
                : err.status        ? (err.message || 'No pudimos iniciar sesión.')
                :                     'Sin conexión. Revisa tu internet e intenta de nuevo.';
      setError(msg);
      setShake(true); setTimeout(() => setShake(false), 400);
      setLoading(false);
    }
  };
  return (
    <form onSubmit={handleSubmit} className="viral-app viral-screen-bg" style={{
      width: '100%', height: '100%',
      display: 'flex', flexDirection: 'column',
      padding: '0 28px',
      paddingTop: 'max(28px, env(safe-area-inset-top, 28px))',
      paddingBottom: 'max(28px, env(safe-area-inset-bottom, 28px))',
      animation: shake ? 'viral-shake 0.4s' : 'none',
    }}>
      {/* Brand */}
      <img
        src="logo_viral.png" alt="VIRAL"
        style={{ height: 44, width: 'auto', alignSelf: 'flex-start', userSelect: 'none', pointerEvents: 'none' }}
      />

      <div style={{ marginTop: 28, animation: 'viral-fade-in-up 0.4s ease' }}>
        <div style={{
          fontSize: 11.5, fontWeight: 700, letterSpacing: 1.4,
          textTransform: 'uppercase', color: '#FC5A00', marginBottom: 8,
        }}>VIRAL</div>
        <h1 style={{
          margin: 0, fontSize: 28, fontWeight: 700,
          letterSpacing: '-0.025em', lineHeight: 1.15, color: '#fff',
        }}>Inicia sesión</h1>
        <p style={{
          margin: '10px 0 0', fontSize: 14, lineHeight: 1.5,
          color: 'rgba(255,255,255,0.6)',
        }}>Accede a tu plan, recargas, activación y soporte.</p>
      </div>

      {notice && (
        <div style={{
          marginTop: 20, padding: 12, borderRadius: 12,
          background: 'rgba(252,90,0,0.10)',
          boxShadow: 'inset 0 0 0 1px rgba(252,90,0,0.30)',
          fontSize: 13, color: '#FDBA74', lineHeight: 1.4,
        }}>{notice}</div>
      )}

      <div style={{ marginTop: 28 }}>
        <label style={{
          display: 'block', fontSize: 12, fontWeight: 600,
          color: 'rgba(255,255,255,0.5)', marginBottom: 8,
          letterSpacing: 0.4, textTransform: 'uppercase',
        }}>Número celular o correo</label>
        <div style={{
          padding: '14px 16px',
          background: 'rgba(255,255,255,0.04)',
          borderRadius: 14,
          boxShadow: `inset 0 0 0 1px ${error ? '#DC2626' : 'rgba(255,255,255,0.08)'}`,
          transition: 'box-shadow 0.15s',
        }}>
          <input
            type="text" inputMode="email" autoComplete="username"
            value={identifier}
            onChange={(e) => { setIdentifier(e.target.value); setError(''); }}
            placeholder="55 1234 5678 o nombre@correo.com"
            style={{
              width: '100%', background: 'transparent', border: 'none', outline: 'none',
              color: '#fff', fontSize: 15, fontWeight: 500,
              fontFamily: 'inherit', letterSpacing: 0.3,
            }}
            autoFocus
          />
        </div>
      </div>

      <div style={{ marginTop: 14 }}>
        <label style={{
          display: 'block', fontSize: 12, fontWeight: 600,
          color: 'rgba(255,255,255,0.5)', marginBottom: 8,
          letterSpacing: 0.4, textTransform: 'uppercase',
        }}>Contraseña</label>
        <div style={{
          display: 'flex', alignItems: 'center', gap: 8,
          padding: '14px 16px',
          background: 'rgba(255,255,255,0.04)',
          borderRadius: 14,
          boxShadow: `inset 0 0 0 1px ${error ? '#DC2626' : 'rgba(255,255,255,0.08)'}`,
          transition: 'box-shadow 0.15s',
        }}>
          <input
            type={showPwd ? 'text' : 'password'}
            autoComplete="current-password"
            value={password}
            onChange={(e) => { setPassword(e.target.value); setError(''); }}
            placeholder="••••••••"
            style={{
              flex: 1, background: 'transparent', border: 'none', outline: 'none',
              color: '#fff', fontSize: 15, fontWeight: 500,
              fontFamily: 'inherit', letterSpacing: 0.3,
            }}
          />
          <button
            type="button" onClick={() => setShowPwd(s => !s)}
            style={{
              appearance: 'none', background: 'transparent', border: 'none',
              color: 'rgba(255,255,255,0.5)', cursor: 'pointer',
              fontSize: 12, fontWeight: 600, fontFamily: 'inherit',
            }}>{showPwd ? 'Ocultar' : 'Mostrar'}</button>
        </div>
      </div>

      {error && (
        <div style={{
          marginTop: 12, fontSize: 13, color: '#F87171', textAlign: 'center',
        }}>{error}</div>
      )}

      <div style={{ marginTop: 14, textAlign: 'center' }}>
        <a
          href={ODOO_RESET_URL} target="_blank" rel="noopener noreferrer"
          style={{ fontSize: 13, color: 'rgba(255,255,255,0.55)', textDecoration: 'none', fontWeight: 500 }}
        >¿Olvidaste tu contraseña?</a>
      </div>

      <div style={{ flex: 1 }}/>

      <button
        type="submit"
        disabled={!canSubmit}
        style={{
          width: '100%', height: 54,
          background: canSubmit ? '#FC5A00' : 'rgba(255,255,255,0.08)',
          color: canSubmit ? '#fff' : 'rgba(255,255,255,0.35)',
          border: 'none', borderRadius: 14,
          fontSize: 15.5, fontWeight: 600, letterSpacing: '-0.01em',
          fontFamily: 'inherit', cursor: canSubmit ? 'pointer' : 'not-allowed',
          transition: 'background 0.15s',
          display: 'flex', alignItems: 'center', justifyContent: 'center', gap: 10,
        }}
      >
        {loading
          ? <span style={{
              width: 18, height: 18, borderRadius: '50%',
              border: '2px solid rgba(255,255,255,0.3)', borderTopColor: '#fff',
              animation: 'viral-spin 0.8s linear infinite',
            }}/>
          : <>Iniciar sesión <Icon name="arrow-right" size={18} strokeWidth={2.2}/></>
        }
      </button>
    </form>
  );
}

// ─────────────────────────────────────────────────────────────
// MiLineaScreen — Estado de línea, créditos, plan, uso
// ─────────────────────────────────────────────────────────────
// Mapea la respuesta del webhook ALTAN al shape que renderea MiLineaScreen.
function mapAltanToLine(api) {
  const d = (api && api.detalle) || {};
  const usedGB  = (b) => b ? +((b.totalGB || 0) - (b.disponibleGB || 0)).toFixed(2) : 0;
  const totalGB = (b) => b ? (b.totalGB || 0) : 0;
  const usedN   = (b) => b ? ((b.total || 0) - (b.disponible || 0)) : 0;
  const totalN  = (b) => b ? (b.total || 0) : 0;
  const hasGB = (b) => b && totalGB(b) > 0;
  const hasN  = (b) => b && totalN(b)  > 0;

  // ── Filas México (GB > GB bono > Min locales > Min LDI > Redes sociales > SMS) ──
  const mx = [
    { label: 'GB', icon: 'sim', used: usedGB(d.datos_nacionales), total: totalGB(d.datos_nacionales), unit: 'GB' },
  ];
  if (hasGB(d.datos_promo)) {
    mx.push({ label: 'GB bono', icon: 'spark', used: usedGB(d.datos_promo), total: totalGB(d.datos_promo), unit: 'GB' });
  }
  mx.push({ label: 'Minutos', icon: 'phone', used: usedN(d.minutos_locales), total: totalN(d.minutos_locales), unit: '' });
  if (hasN(d.minutos_ldi)) {
    mx.push({ label: 'Min. internacional', icon: 'phone', used: usedN(d.minutos_ldi), total: totalN(d.minutos_ldi), unit: '' });
  }
  if (hasGB(d.redes_sociales)) {
    mx.push({ label: 'Redes sociales', icon: 'signal', used: usedGB(d.redes_sociales), total: totalGB(d.redes_sociales), unit: 'GB' });
  }
  mx.push({ label: 'SMS', icon: 'message', used: usedN(d.sms_nacionales), total: totalN(d.sms_nacionales), unit: '' });

  // ── Filas USA/CA ──
  const intl = [
    { label: 'GB', icon: 'sim', used: usedGB(d.datos_roaming), total: totalGB(d.datos_roaming), unit: 'GB' },
    { label: 'Minutos', icon: 'phone', used: usedN(d.minutos_roaming), total: totalN(d.minutos_roaming), unit: '' },
  ];
  if (hasGB(d.redes_sociales_roaming)) {
    intl.push({ label: 'Redes sociales', icon: 'signal', used: usedGB(d.redes_sociales_roaming), total: totalGB(d.redes_sociales_roaming), unit: 'GB' });
  }
  intl.push({ label: 'SMS', icon: 'message', used: usedN(d.sms_roaming), total: totalN(d.sms_roaming), unit: '' });

  return {
    number: window.ViralApi.formatPhone(api.numero_viral),
    plan: api.plan_detectado || '—',
    tipoPlan: api.tipo_plan || null,
    expires: window.ViralApi.formatDateISOToMX(api.fecha_expiracion),
    diasRestantes: api.dias_restantes,
    lineaActiva: !!api.linea_activa,
    estadoLinea: api.estado_linea,
    tieneSaldo: api.tiene_saldo !== false,
    necesitaRecarga: !!api.necesita_recarga,
    hayProblemas: !!api.hay_problemas,
    status: api.linea_activa ? 'vinculado' : 'inactivo',
    mx, intl,
  };
}

// Construye una línea mínima cuando ALTAN no respondió pero Odoo sí tiene info
function buildFallbackLine(numero, info) {
  // info viene de acct.lineas_info[]: { numero, plan, ultima_orden }
  // Saca "BONUS" / "PRO" del nombre del producto Odoo (ej: "MASTER (PREPAGO)" → "MASTER")
  let planClean = null;
  let tipoPlan = null;
  if (info?.plan) {
    const m = /^([A-Z]+)\s*\(([^)]+)\)/i.exec(info.plan);
    if (m) { planClean = m[1].toUpperCase(); tipoPlan = m[2].toUpperCase(); }
    else { planClean = info.plan; }
  }
  return {
    number: window.ViralApi.formatPhone(numero),
    plan: planClean || '—',
    tipoPlan: tipoPlan,
    expires: '—',
    diasRestantes: null,
    lineaActiva: null,         // desconocido (sin ALTAN)
    estadoLinea: null,
    tieneSaldo: true,
    necesitaRecarga: false,
    hayProblemas: false,
    status: 'unknown',
    sinDatos: true,             // ← flag para UI: dim + leyenda
    // Skeleton: mantiene la estructura visual con "—" cuando no hay datos
    mx: [
      { label: 'GB',      icon: 'sim',     used: 0, total: 0, unit: 'GB' },
      { label: 'Minutos', icon: 'phone',   used: 0, total: 0, unit: '' },
      { label: 'SMS',     icon: 'message', used: 0, total: 0, unit: '' },
    ],
    intl: [
      { label: 'GB',      icon: 'sim',     used: 0, total: 0, unit: 'GB' },
      { label: 'Minutos', icon: 'phone',   used: 0, total: 0, unit: '' },
      { label: 'SMS',     icon: 'message', used: 0, total: 0, unit: '' },
    ],
  };
}

function MiLineaScreen() {
  const [apiLines, setApiLines] = React.useState([]);
  const [loading, setLoading]   = React.useState(true);
  const [error, setError]       = React.useState(null);
  const [lineIdx, setLineIdx]   = React.useState(0);
  const [pickerOpen, setPickerOpen] = React.useState(false);

  React.useEffect(() => {
    let cancelled = false;
    setLoading(true); setError(null);

    (async () => {
      try {
        const acct = await window.ViralApi.getAccount();
        if (cancelled) return;
        const lineas = (acct && acct.ok && Array.isArray(acct.lineas)) ? acct.lineas : [];
        const lineasInfo = (acct && Array.isArray(acct.lineas_info)) ? acct.lineas_info : [];
        if (!lineas.length) {
          setError('No tienes líneas vinculadas a tu cuenta.');
          return;
        }

        // Mapa de info Odoo por número (para fallback cuando ALTAN no responda)
        const odooInfoByNum = {};
        lineasInfo.forEach(info => { if (info.numero) odooInfoByNum[info.numero] = info; });

        const results = await Promise.all(
          lineas.map(num =>
            window.ViralApi.getAltanStatus(num)
              .then(r => (r && r.ok) ? mapAltanToLine(r) : null)
              .catch(() => null)
              .then(altanLine => altanLine || buildFallbackLine(num, odooInfoByNum[num]))
          )
        );
        if (cancelled) return;
        // Ordenar: activas con datos primero, después las que solo tienen Odoo
        results.sort((a, b) => {
          if (a.sinDatos && !b.sinDatos) return 1;
          if (!a.sinDatos && b.sinDatos) return -1;
          return (b.lineaActiva ? 1 : 0) - (a.lineaActiva ? 1 : 0);
        });
        setApiLines(results);
        setLineIdx(0);
      } catch (err) {
        if (!cancelled) setError(err.message || 'Error de red');
      } finally {
        if (!cancelled) setLoading(false);
      }
    })();

    return () => { cancelled = true; };
  }, []);

  // SIN mock — si no hay líneas, line es null y el render se ajusta
  const lines = apiLines;
  const line  = lines[lineIdx] || lines[0] || null;

  // Totales = MX + USA/CA por label (algunos buckets no tienen contraparte intl)
  // Suma TODAS las filas que comparten unidad/categoría (incluye "GB bono" en el total de GB)
  const sumByLabel = (label, unit) => {
    if (!line) return null;
    let used = 0, total = 0, icon = null;
    const matches = (r) =>
      r.label === label ||
      (label === 'GB' && r.label === 'GB bono');  // GB bono cuenta en total GB
    (line.mx || []).forEach(r => {
      if (matches(r)) { used += r.used || 0; total += r.total || 0; if (!icon) icon = r.icon; }
    });
    (line.intl || []).forEach(r => {
      if (matches(r)) { used += r.used || 0; total += r.total || 0; if (!icon) icon = r.icon; }
    });
    if (!total && !used) return null;
    return {
      label, unit, icon: icon || 'sim',
      used:  +used.toFixed(2),
      total: +total.toFixed(2),
    };
  };
  const total = line ? [
    sumByLabel('GB', 'GB'),
    sumByLabel('Minutos', ''),
    sumByLabel('SMS', ''),
  ].filter(Boolean) : [];

  const UsageRow = ({ row }) => {
    const isEmpty = !row.total;
    const available = !isEmpty ? +(row.total - row.used).toFixed(2) : 0;
    const pct = !isEmpty ? Math.min(100, (row.used / row.total) * 100) : 0;
    const unitSuffix = row.unit ? ' ' + row.unit : '';
    return (
      <div style={{ padding: '16px 0', borderBottom: '1px solid rgba(255,255,255,0.06)' }}>
        <div style={{ display: 'flex', alignItems: 'center', gap: 12 }}>
          <IconCircle name={row.icon} tone="navy" size={32}/>
          <div style={{ flex: 1 }}>
            <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'baseline', fontSize: 13.5, fontWeight: 600, color: '#fff', marginBottom: 8 }}>
              <span>{row.label}</span>
              <span style={{ fontVariantNumeric: 'tabular-nums', color: isEmpty ? 'rgba(255,255,255,0.35)' : '#fff' }}>
                {isEmpty
                  ? '—'
                  : <>
                      <strong style={{ fontWeight: 700 }}>{available}{unitSuffix}</strong>
                      <span style={{ color: 'rgba(255,255,255,0.4)', fontWeight: 500, fontSize: 11.5, marginLeft: 5 }}>
                        de {row.total}{unitSuffix}
                      </span>
                    </>
                }
              </span>
            </div>
            <div style={{
              height: 12,
              background: 'rgba(255,255,255,0.08)',
              borderRadius: 999,
              overflow: 'hidden',
              boxShadow: 'inset 0 1px 2px rgba(0,0,0,0.22)',
            }}>
              <div style={{
                width: `${pct}%`, height: '100%',
                background: 'linear-gradient(90deg, #FC6A0F 0%, #FF8A3D 100%)',
                borderRadius: 999,
                boxShadow: pct > 2 ? '0 0 10px rgba(252,90,0,0.50), inset 0 1px 0 rgba(255,255,255,0.22)' : 'none',
                transition: 'width 0.7s cubic-bezier(0.22, 1, 0.36, 1)',
              }}/>
            </div>
          </div>
        </div>
      </div>
    );
  };

  return (
    <div className="viral-app viral-screen-bg" style={{
      width: '100%', height: '100%',
      display: 'flex', flexDirection: 'column',
      paddingTop: 'calc(env(safe-area-inset-top, 0px) + 16px)',
      paddingBottom: 'max(90px, calc(env(safe-area-inset-bottom, 0px) + 90px))',
      overflowY: 'auto',
    }}>
      <ScreenHeader eyebrow="Mi línea" title="Tu plan VIRAL"/>

      {/* Loading → skeleton premium replicando estructura. Error → ErrorState con retry. */}
      {loading && !error && <MiLineaSkeleton />}
      {error && (
        <ErrorState
          title="No pudimos cargar tu línea"
          hint={error}
          onRetry={() => window.location.reload()}
        />
      )}

      {/* Banner condicional — solo si la línea tiene problemas o no tiene saldo */}
      {apiLines.length > 0 && (line.hayProblemas || line.necesitaRecarga || !line.tieneSaldo) && (
        <div style={{ padding: '20px 20px 0' }}>
          <div style={{
            background: line.hayProblemas ? 'rgba(220,38,38,0.12)' : 'rgba(252,90,0,0.14)',
            border: '1px solid ' + (line.hayProblemas ? 'rgba(220,38,38,0.35)' : 'rgba(252,90,0,0.35)'),
            borderRadius: 14, padding: '14px 16px',
            display: 'flex', alignItems: 'flex-start', gap: 12,
          }}>
            <Icon name="alert" size={20} color={line.hayProblemas ? '#F87171' : '#FC5A00'}/>
            <div style={{ flex: 1, minWidth: 0 }}>
              <div style={{ fontSize: 13.5, fontWeight: 700, color: line.hayProblemas ? '#FCA5A5' : '#FFB37A', marginBottom: 4 }}>
                {line.hayProblemas ? 'Hay un problema con tu línea' : 'Tu línea no tiene saldo'}
              </div>
              <div style={{ fontSize: 12, color: 'rgba(255,255,255,0.7)', lineHeight: 1.45 }}>
                {line.hayProblemas
                  ? `Estado: ${line.estadoLinea || 'desconocido'}. Contacta a un asesor para revisar tu caso.`
                  : 'Recarga tu plan para seguir usando datos, minutos y SMS.'}
              </div>
            </div>
          </div>
        </div>
      )}

      {/* Selector de línea + estado — solo renderiza si line existe.
          z-index dinámico: cuando el dropdown está abierto, toda la sección sube
          por encima de las cards de abajo para que el dropdown no quede cortado. */}
      {line && (
      <div style={{ padding: '24px 20px 0', position: 'relative', zIndex: pickerOpen ? 50 : 1 }}>
        <div style={{
          background: 'linear-gradient(135deg, #FC5A00 0%, #ff7a30 100%)',
          borderRadius: 22, padding: 20, color: '#fff', position: 'relative',
        }}>
          <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: 14 }}>
            <span style={{ fontSize: 11.5, fontWeight: 700, letterSpacing: 1.4, textTransform: 'uppercase', opacity: 0.85 }}>
              {lines.length > 1 ? `Línea ${lineIdx + 1} de ${lines.length}` : 'Línea'}
            </span>
            <StatusBadge
              tone={line.sinDatos ? 'neutral' : (line.lineaActiva ? 'success' : 'error')}
              size="sm" dot pulse={false}
            >
              {line.sinDatos
                ? 'Sin datos en vivo'
                : (line.lineaActiva ? 'Activa' : (line.estadoLinea || 'Inactiva'))}
            </StatusBadge>
          </div>
          {/* Wrapper relativo SOLO al campo del número → el dropdown se abre justo
              debajo del campo, no debajo de toda la card naranja. */}
          <div style={{ position: 'relative' }}>
            <div
              onClick={() => lines.length > 1 && setPickerOpen(o => !o)}
              style={{
                background: 'rgba(255,255,255,0.18)', borderRadius: 12,
                padding: '12px 16px', display: 'flex', alignItems: 'center', justifyContent: 'space-between',
                cursor: lines.length > 1 ? 'pointer' : 'default',
              }}
            >
              <span style={{ fontSize: 18, fontWeight: 700, fontVariantNumeric: 'tabular-nums', letterSpacing: 0.5 }}>{line.number}</span>
              {lines.length > 1 && (
                <span style={{ transform: pickerOpen ? 'rotate(180deg)' : 'none', transition: 'transform 0.2s' }}>
                  <Icon name="chevron-d" size={18} strokeWidth={2.4}/>
                </span>
              )}
            </div>
            {/* Dropdown justo debajo del campo del número (no de la card). */}
            {pickerOpen && lines.length > 1 && (
              <div style={{
                position: 'absolute', left: 0, right: 0, top: 'calc(100% + 6px)',
                background: 'linear-gradient(180deg, #14184E 0%, #0E1140 100%)',
                borderRadius: 14, padding: 6, zIndex: 100,
                boxShadow: '0 20px 48px rgba(0,0,0,0.55), 0 4px 12px rgba(0,0,0,0.30)',
                border: '1px solid rgba(255,255,255,0.10)',
                backdropFilter: 'blur(12px)',
              }}>
                {lines.map((l, i) => (
                  <button
                    key={i}
                    onClick={() => { setLineIdx(i); setPickerOpen(false); }}
                    style={{
                      width: '100%', display: 'flex', alignItems: 'center', justifyContent: 'space-between',
                      background: i === lineIdx ? 'rgba(252,90,0,0.18)' : 'transparent',
                      border: 'none', color: '#fff', padding: '11px 14px', borderRadius: 11,
                      cursor: 'pointer', fontSize: 14, textAlign: 'left',
                      boxShadow: i === lineIdx ? 'inset 0 0 0 1px rgba(252,90,0,0.30)' : 'none',
                    }}
                  >
                    <span style={{ fontWeight: 600, fontVariantNumeric: 'tabular-nums', letterSpacing: 0.3 }}>{l.number}</span>
                    <span style={{ fontSize: 11, opacity: 0.55, fontWeight: 500, letterSpacing: 0.4, textTransform: 'uppercase' }}>{l.plan}</span>
                  </button>
                ))}
              </div>
            )}
          </div>
          <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'flex-end', marginTop: 14, fontSize: 13, gap: 12 }}>
            <div style={{ display: 'flex', alignItems: 'center', gap: 8, flexWrap: 'wrap' }}>
              <span><span style={{ opacity: 0.85 }}>Plan </span><strong style={{ fontWeight: 700 }}>{line.plan}</strong></span>
              {line.tipoPlan && (
                <span style={{
                  fontSize: 9.5, fontWeight: 800, letterSpacing: 0.9, textTransform: 'uppercase',
                  padding: '2.5px 7px', background: 'rgba(255,255,255,0.22)', borderRadius: 999, lineHeight: 1.1,
                }}>{line.tipoPlan}</span>
              )}
            </div>
            {!line.sinDatos && (
              <div style={{ textAlign: 'right', lineHeight: 1.2 }}>
                {line.diasRestantes != null && (
                  <div style={{ fontSize: 13 }}>
                    <span style={{ opacity: 0.85 }}>Vence en </span>
                    <strong style={{ fontWeight: 700, fontVariantNumeric: 'tabular-nums' }}>
                      {line.diasRestantes} {line.diasRestantes === 1 ? 'día' : 'días'}
                    </strong>
                  </div>
                )}
                <div style={{ fontSize: 11, opacity: 0.7, fontVariantNumeric: 'tabular-nums', marginTop: line.diasRestantes != null ? 2 : 0 }}>
                  {line.expires}
                </div>
              </div>
            )}
          </div>
        </div>
      </div>
      )}

      {/* Aviso cuando ALTAN no devolvió datos */}
      {line && line.sinDatos && (
        <div style={{ padding: '16px 20px 0' }}>
          <div style={{
            background: 'rgba(255,255,255,0.04)',
            border: '1px solid rgba(255,255,255,0.08)',
            borderRadius: 12, padding: '14px 16px',
            fontSize: 12.5, color: 'rgba(255,255,255,0.7)', lineHeight: 1.5,
          }}>
            <strong style={{ color: '#fff', display: 'block', marginBottom: 4 }}>
              Datos de consumo no disponibles
            </strong>
            La información en tiempo real de tu línea (GB, minutos, fecha de vencimiento) no se pudo obtener en este momento. Reintentaremos en unos minutos.
          </div>
        </div>
      )}

      {line && (
        <div style={{ opacity: line.sinDatos ? 0.55 : 1, transition: 'opacity 0.3s' }}>
          {/* Total = México + USA/CA */}
          <div style={{ padding: '20px 20px 0' }}>
            <div style={{ fontSize: 11.5, fontWeight: 700, letterSpacing: 1.2, textTransform: 'uppercase', color: 'rgba(255,255,255,0.4)', marginBottom: 10 }}>Total disponible</div>
            <div className="viral-card" style={{ padding: '8px 22px' }}>
              {total.map((row, i) => <UsageRow key={i} row={row}/>)}
            </div>
          </div>

          {/* Disponible en México */}
          <div style={{ padding: '16px 20px 0' }}>
            <div style={{ fontSize: 11.5, fontWeight: 700, letterSpacing: 1.2, textTransform: 'uppercase', color: 'rgba(255,255,255,0.4)', marginBottom: 10 }}>Disponible en México</div>
            <div className="viral-card" style={{ padding: '8px 22px' }}>
              {line.mx.map((row, i) => <UsageRow key={i} row={row}/>)}
            </div>
          </div>

          {/* Disponible en EUA + Canadá */}
          <div style={{ padding: '16px 20px 0' }}>
            <div style={{ fontSize: 11.5, fontWeight: 700, letterSpacing: 1.2, textTransform: 'uppercase', color: 'rgba(255,255,255,0.4)', marginBottom: 10 }}>Disponible en EUA + Canadá</div>
            <div className="viral-card" style={{ padding: '8px 22px' }}>
              {line.intl.map((row, i) => <UsageRow key={i} row={row}/>)}
            </div>
          </div>
        </div>
      )}
    </div>
  );
}

// ─────────────────────────────────────────────────────────────
// MiCuentaScreen — Portabilidad, referidos, órdenes, facturas
// ─────────────────────────────────────────────────────────────
function MiCuentaScreen() {
  const [account, setAccount] = React.useState(null);
  const [loading, setLoading] = React.useState(true);
  const [error, setError] = React.useState(null);
  const [orders, setOrders] = React.useState(null);
  const [ordersOpen, setOrdersOpen] = React.useState(false);
  const [invoices, setInvoices] = React.useState(null);
  const [invoicesOpen, setInvoicesOpen] = React.useState(false);

  React.useEffect(() => {
    let cancelled = false;
    setLoading(true); setError(null);
    window.ViralApi.getAccount()
      .then(res => {
        if (cancelled) return;
        if (res && res.ok) setAccount(res);
        else setError(res?.mensaje || 'Sin datos de la cuenta');
      })
      .catch(err => { if (!cancelled) setError(err.message || 'Error de red'); })
      .finally(() => { if (!cancelled) setLoading(false); });
    // Cargar órdenes + facturas en paralelo (no bloquea la UI principal)
    window.ViralApi.getOrders()
      .then(res => { if (!cancelled && res && res.ok) setOrders(res); })
      .catch(() => {});
    window.ViralApi.getInvoices()
      .then(res => { if (!cancelled && res && res.ok) setInvoices(res); })
      .catch(() => {});
    return () => { cancelled = true; };
  }, []);

  // SIN mock — vacío durante loading/error
  const persona = account?.persona ? {
    nombre: account.persona.nombre || '—',
    email: account.persona.email || '—',
    direccion: account.persona.direccion || '—',
    rfc: (account.persona.rfc && account.persona.rfc !== '-') ? account.persona.rfc : '—',
    razon_social: (account.persona.razon_social && account.persona.razon_social !== '-')
      ? account.persona.razon_social
      : '— (persona física)',
  } : null;

  // Vinculación: prioridad al backend; fallback al ?vincular=1 del URL
  const vinculacionPendiente = account?.vinculacion
    ? account.vinculacion.es_vinculado === false
    : new URLSearchParams(window.location.search).get('vincular') === '1';
  const urlVincular = account?.vinculacion?.url_vinc_linea || null;

  // Nombre corto para el header ("Juan Pérez López..." → "Juan P.")
  const nombreHeader = (() => {
    const n = (persona?.nombre || '').trim();
    if (!n) return 'Mi cuenta';
    const parts = n.split(/\s+/);
    if (parts.length === 1) return parts[0];
    return parts[0] + ' ' + parts[1][0] + '.';
  })();

  return (
    <div className="viral-app viral-screen-bg" style={{
      width: '100%', height: '100%',
      display: 'flex', flexDirection: 'column',
      paddingTop: 'calc(env(safe-area-inset-top, 0px) + 16px)',
      paddingBottom: 'max(90px, calc(env(safe-area-inset-bottom, 0px) + 90px))',
      overflowY: 'auto',
    }}>
      <ScreenHeader eyebrow="Mi cuenta" title={nombreHeader}/>

      {/* Loading → skeleton de la estructura completa. Error → ErrorState con retry. */}
      {loading && !error && <MiCuentaSkeleton />}
      {error && (
        <ErrorState
          title="No se pudo cargar tu cuenta"
          onRetry={() => window.location.reload()}
        />
      )}

      {/* Vincular línea — banner arriba SOLO si pendiente Y ya cargaron datos */}
      {!loading && !error && vinculacionPendiente && (
        <div style={{ padding: '24px 20px 0' }}>
          <div style={{
            background: 'linear-gradient(135deg, #FC5A00 0%, #ff7a30 100%)',
            borderRadius: 18, padding: 18, color: '#fff',
          }}>
            <div style={{ display: 'flex', alignItems: 'center', gap: 10, marginBottom: 8 }}>
              <span style={{ fontSize: 18 }}>⚠️</span>
              <span style={{ fontSize: 15, fontWeight: 700 }}>Vincular tu línea</span>
            </div>
            <p style={{ margin: '0 0 12px', fontSize: 13, opacity: 0.92, lineHeight: 1.45 }}>
              Tu línea aún no está vinculada. Es un trámite rápido (selfie + INE, 5 min) que pide la IFT a todas las compañías.
            </p>
            <Button
              variant="cream"
              fullWidth
              icon="arrow-right"
              onClick={() => { if (urlVincular) window.open(urlVincular, '_blank'); }}
            >Vincular ahora</Button>
          </div>
        </div>
      )}

      {/* ════ RESUMEN DE CUENTA (cliente_desde + count_orders + total_gastado) ════ */}
      {account?.resumen && (
        <div style={{ padding: '24px 20px 0' }}>
          <div style={{
            background: 'linear-gradient(135deg, rgba(252,90,0,0.08) 0%, rgba(216,255,75,0.05) 100%)',
            border: '1px solid rgba(255,255,255,0.08)',
            borderRadius: 16, padding: 14,
            display: 'grid', gridTemplateColumns: '1fr 1fr 1fr', gap: 8, textAlign: 'center',
          }}>
            <div>
              <div style={{ fontSize: 10.5, color: 'rgba(255,255,255,0.5)', letterSpacing: 0.4, textTransform: 'uppercase', marginBottom: 4 }}>Cliente desde</div>
              <div style={{ fontSize: 13.5, fontWeight: 700, color: '#fff' }}>
                {(() => {
                  const cd = account.persona?.cliente_desde;
                  if (!cd) return '—';
                  const meses = ['Ene','Feb','Mar','Abr','May','Jun','Jul','Ago','Sep','Oct','Nov','Dic'];
                  const d = new Date(cd.replace(' ', 'T') + 'Z');
                  return meses[d.getMonth()] + ' ' + d.getFullYear();
                })()}
              </div>
            </div>
            <div style={{ borderLeft: '1px solid rgba(255,255,255,0.06)', borderRight: '1px solid rgba(255,255,255,0.06)' }}>
              <div style={{ fontSize: 10.5, color: 'rgba(255,255,255,0.5)', letterSpacing: 0.4, textTransform: 'uppercase', marginBottom: 4 }}>Órdenes</div>
              <div style={{ fontSize: 16, fontWeight: 700, color: '#D8FF4B', fontVariantNumeric: 'tabular-nums' }}>{account.resumen.count_orders}</div>
            </div>
            <div>
              <div style={{ fontSize: 10.5, color: 'rgba(255,255,255,0.5)', letterSpacing: 0.4, textTransform: 'uppercase', marginBottom: 4 }}>Total gastado</div>
              <div style={{ fontSize: 13.5, fontWeight: 700, color: '#FC5A00', fontVariantNumeric: 'tabular-nums' }}>
                ${Number(account.resumen.total_gastado || 0).toLocaleString('es-MX', { minimumFractionDigits: 0, maximumFractionDigits: 0 })}
              </div>
            </div>
          </div>
        </div>
      )}

      {/* ════ 1. DATOS PERSONALES (solo si ya cargó) ════ */}
      {persona && (
        <div style={{ padding: '24px 20px 0' }}>
          <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: 10 }}>
            <div style={{ fontSize: 11.5, fontWeight: 700, letterSpacing: 1.2, textTransform: 'uppercase', color: 'rgba(255,255,255,0.4)' }}>Datos personales</div>
            <button
              onClick={() => window.__viralNav?.navigate('editar-perfil')}
              style={{
                background: 'transparent', border: 'none', color: '#FC5A00', fontSize: 12.5, fontWeight: 600, cursor: 'pointer',
                display: 'flex', alignItems: 'center', gap: 4, fontFamily: 'inherit',
              }}
            >Editar <Icon name="chevron-r" size={12} strokeWidth={2.4}/></button>
          </div>
          <div className="viral-card" style={{ padding: 16 }}>
            <DataRow label="Nombre completo" value={persona.nombre}/>
            <DataRow label="Correo" value={persona.email}/>
            <DataRow label="Dirección" value={persona.direccion}/>
            <DataRow label="RFC" value={persona.rfc}/>
            <DataRow label="Razón social (facturación)" value={persona.razon_social} last/>
          </div>
        </div>
      )}

      {/* ════ CRÉDITOS VIRAL (3 valores: recibidos, usados, disponible — match Odoo /mi-linea) ════ */}
      {account?.referidos && (
        <div style={{ padding: '24px 20px 0' }}>
          <div style={{ fontSize: 11.5, fontWeight: 700, letterSpacing: 1.2, textTransform: 'uppercase', color: 'rgba(255,255,255,0.4)', marginBottom: 10 }}>Créditos VIRAL</div>
          <div className="viral-card" style={{
            padding: 16,
            background: 'linear-gradient(135deg, rgba(216,255,75,0.10) 0%, rgba(252,90,0,0.05) 100%)',
          }}>
            {(() => {
              const fmt = (n) => '$' + Number(n || 0).toLocaleString('es-MX', { minimumFractionDigits: 2, maximumFractionDigits: 2 });
              const itemStyle = {
                display: 'flex', alignItems: 'center', justifyContent: 'space-between',
                padding: '10px 0',
              };
              const labelStyle = { fontSize: 12.5, color: 'rgba(255,255,255,0.7)' };
              const valStyle = (color) => ({
                fontSize: 16, fontWeight: 800, color, fontVariantNumeric: 'tabular-nums',
                padding: '4px 14px', borderRadius: 999,
                background: 'rgba(255,255,255,0.06)',
                minWidth: 96, textAlign: 'center',
              });
              return (
                <React.Fragment>
                  <div style={itemStyle}>
                    <span style={labelStyle}>Tus referidos te han dado</span>
                    <span style={valStyle('#D8FF4B')}>{fmt(account.referidos.creditos_recibidos)}</span>
                  </div>
                  <div style={{ ...itemStyle, borderTop: '1px solid rgba(255,255,255,0.06)' }}>
                    <span style={labelStyle}>Has utilizado</span>
                    <span style={valStyle('rgba(255,255,255,0.85)')}>{fmt(account.referidos.creditos_usados)}</span>
                  </div>
                  <div style={{ ...itemStyle, borderTop: '1px solid rgba(255,255,255,0.06)' }}>
                    <span style={{ ...labelStyle, fontWeight: 700, color: '#fff' }}>Crédito VIRAL disponible</span>
                    <span style={{ ...valStyle('#FC5A00'), fontSize: 18 }}>{fmt(account.referidos.creditos)}</span>
                  </div>
                  <p style={{ margin: '10px 0 0', fontSize: 11.5, color: 'rgba(255,255,255,0.5)', textAlign: 'center', lineHeight: 1.45 }}>
                    Obtén crédito compartiendo tu número con amigos.
                  </p>
                </React.Fragment>
              );
            })()}
          </div>
        </div>
      )}

      {/* ════ REFERIDOS (mi código + quien me invitó) ════ */}
      {account?.referidos && (
        <div style={{ padding: '20px 20px 0' }}>
          <div style={{ fontSize: 11.5, fontWeight: 700, letterSpacing: 1.2, textTransform: 'uppercase', color: 'rgba(255,255,255,0.4)', marginBottom: 10 }}>Te invito un amigo</div>
          <div className="viral-card" style={{ padding: 16 }}>
            <p style={{ margin: '0 0 12px', fontSize: 13, color: 'rgba(255,255,255,0.7)', lineHeight: 1.45 }}>
              Comparte tu número VIRAL — cuando alguien lo registre al activar su línea, ambos reciben créditos.
            </p>

            {/* Mi código */}
            {account.referidos.mi_codigo && (
              <div style={{
                padding: 12, borderRadius: 10,
                background: 'rgba(255,255,255,0.04)',
                boxShadow: 'inset 0 0 0 1px rgba(255,255,255,0.06)',
                display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 10,
                marginBottom: 8,
              }}>
                <div>
                  <div style={{ fontSize: 10.5, color: 'rgba(255,255,255,0.5)', letterSpacing: 0.5, textTransform: 'uppercase' }}>Mi código</div>
                  <div style={{ fontSize: 16, fontWeight: 700, color: '#fff', fontVariantNumeric: 'tabular-nums', letterSpacing: 1, marginTop: 2 }}>
                    {window.ViralApi.formatPhone(account.referidos.mi_codigo)}
                  </div>
                </div>
                <button
                  onClick={() => {
                    const text = `Te invito a VIRAL Cel — usa mi código ${account.referidos.mi_codigo} al activar tu línea para que ambos recibamos crédito.`;
                    if (navigator.share) {
                      navigator.share({ title: 'Mi código VIRAL', text }).catch(()=>{});
                    } else {
                      navigator.clipboard?.writeText(text).then(() => alert('Mensaje copiado al portapapeles'));
                    }
                  }}
                  style={{
                    background: '#FC5A00', color: '#fff', border: 'none',
                    padding: '8px 14px', borderRadius: 999,
                    fontSize: 12.5, fontWeight: 700, cursor: 'pointer', fontFamily: 'inherit',
                  }}
                >Compartir</button>
              </div>
            )}

            {/* Quien me invitó / input para aplicar código */}
            {account.referidos.referido_por ? (
              <div style={{
                padding: 12, borderRadius: 10,
                background: 'rgba(216,255,75,0.06)',
                boxShadow: 'inset 0 0 0 1px rgba(216,255,75,0.18)',
                fontSize: 12.5, color: 'rgba(255,255,255,0.75)',
              }}>
                Te invitó <strong style={{ color: '#D8FF4B', fontVariantNumeric: 'tabular-nums' }}>{window.ViralApi.formatPhone(account.referidos.referido_por)}</strong>
              </div>
            ) : (
              <AplicarReferralInput/>
            )}
          </div>
        </div>
      )}

      {/* ════ GESTIÓN (al final) ════ */}
      <div style={{ padding: '20px 20px 0' }}>
        <div style={{ fontSize: 11.5, fontWeight: 700, letterSpacing: 1.2, textTransform: 'uppercase', color: 'rgba(255,255,255,0.4)', marginBottom: 10 }}>Gestión</div>
        <div style={{ display: 'flex', flexDirection: 'column', gap: 10 }}>
          {/* Sus órdenes — expansible con datos reales */}
          <div className="viral-card" style={{ overflow: 'hidden' }}>
            <button
              onClick={() => setOrdersOpen(o => !o)}
              style={{
                width: '100%', display: 'flex', alignItems: 'center', gap: 12,
                background: 'transparent', border: 'none', color: '#fff',
                padding: 14, cursor: 'pointer', fontFamily: 'inherit', textAlign: 'left',
              }}
            >
              <IconCircle name="copy" tone="orange" size={36}/>
              <div style={{ flex: 1, minWidth: 0 }}>
                <div style={{ fontSize: 14.5, fontWeight: 600 }}>Sus órdenes</div>
                <div style={{ fontSize: 12, color: 'rgba(255,255,255,0.55)', marginTop: 2 }}>
                  {orders
                    ? `${orders.total_ordenes} órdenes · última ${orders.ordenes?.[0]?.fecha || '—'}`
                    : 'Cargando…'}
                </div>
              </div>
              <span style={{ transform: ordersOpen ? 'rotate(180deg)' : 'none', transition: 'transform 0.2s', opacity: 0.6 }}>
                <Icon name="chevron-d" size={18} strokeWidth={2.4}/>
              </span>
            </button>
            {ordersOpen && orders && (orders.ordenes || []).length === 0 && (
              <div style={{ borderTop: '1px solid rgba(255,255,255,0.06)' }}>
                <EmptyState
                  icon="cart"
                  title="Aún no tienes órdenes"
                  hint="Tu primera recarga o compra de SIM aparecerá aquí."
                />
              </div>
            )}
            {ordersOpen && orders && (orders.ordenes || []).length > 0 && (
              <div style={{ padding: '0 14px 14px', borderTop: '1px solid rgba(255,255,255,0.06)' }}>
                {(orders.ordenes || []).slice(0, 8).map((o, i) => (
                  <div key={i} style={{
                    padding: '12px 0',
                    borderBottom: i < Math.min(orders.ordenes.length, 8) - 1 ? '1px solid rgba(255,255,255,0.04)' : 'none',
                    display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 12,
                  }}>
                    <div style={{ flex: 1, minWidth: 0 }}>
                      <div style={{ fontSize: 13, fontWeight: 600, color: '#fff' }}>{o.numero}</div>
                      <div style={{ fontSize: 11.5, color: 'rgba(255,255,255,0.5)', marginTop: 2 }}>
                        {o.fecha} · {o.estado_label}{o.lineas?.[0]?.product ? ' · ' + o.lineas[0].product : ''}
                      </div>
                    </div>
                    <div style={{ fontSize: 13.5, fontWeight: 700, color: '#FC5A00', fontVariantNumeric: 'tabular-nums' }}>
                      ${o.total.toFixed(2)}
                    </div>
                  </div>
                ))}
                {orders.ordenes && orders.ordenes.length > 8 && (
                  <div style={{ fontSize: 11.5, color: 'rgba(255,255,255,0.4)', textAlign: 'center', paddingTop: 10 }}>
                    + {orders.ordenes.length - 8} más
                  </div>
                )}
              </div>
            )}
          </div>

          {/* Sus facturas — expansible con datos reales */}
          <div className="viral-card" style={{ overflow: 'hidden' }}>
            <button
              onClick={() => setInvoicesOpen(o => !o)}
              style={{
                width: '100%', display: 'flex', alignItems: 'center', gap: 12,
                background: 'transparent', border: 'none', color: '#fff',
                padding: 14, cursor: 'pointer', fontFamily: 'inherit', textAlign: 'left',
              }}
            >
              <IconCircle name="mail" tone="lime" size={36}/>
              <div style={{ flex: 1, minWidth: 0 }}>
                <div style={{ fontSize: 14.5, fontWeight: 600 }}>Sus facturas</div>
                <div style={{ fontSize: 12, color: 'rgba(255,255,255,0.55)', marginTop: 2 }}>
                  {invoices
                    ? (invoices.total_facturas > 0
                        ? `${invoices.total_facturas} facturas${invoices.saldo_pendiente > 0 ? ` · saldo $${invoices.saldo_pendiente.toFixed(2)}` : ' · al corriente'}`
                        : 'Sin facturas emitidas')
                    : 'Cargando…'}
                </div>
              </div>
              <span style={{ transform: invoicesOpen ? 'rotate(180deg)' : 'none', transition: 'transform 0.2s', opacity: 0.6 }}>
                <Icon name="chevron-d" size={18} strokeWidth={2.4}/>
              </span>
            </button>
            {invoicesOpen && invoices && !(invoices.facturas?.length > 0) && (
              <div style={{ borderTop: '1px solid rgba(255,255,255,0.06)' }}>
                <EmptyState
                  icon="mail"
                  title="Sin facturas emitidas"
                  hint="Cuando se emita una factura asociada a tu cuenta, aparecerá aquí."
                />
              </div>
            )}
            {invoicesOpen && invoices && (invoices.facturas?.length > 0) && (
              <div style={{ padding: '0 14px 14px', borderTop: '1px solid rgba(255,255,255,0.06)' }}>
                {(invoices.facturas || []).slice(0, 8).map((f, i) => (
                  <div key={i} style={{
                    padding: '12px 0',
                    borderBottom: i < Math.min(invoices.facturas.length, 8) - 1 ? '1px solid rgba(255,255,255,0.04)' : 'none',
                    display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 12,
                  }}>
                    <div style={{ flex: 1, minWidth: 0 }}>
                      <div style={{ fontSize: 13, fontWeight: 600, color: '#fff' }}>{f.numero}</div>
                      <div style={{ fontSize: 11.5, color: 'rgba(255,255,255,0.5)', marginTop: 2 }}>
                        {f.fecha || '—'} · <span style={{ color: f.pagada ? '#D8FF4B' : '#FCA5A5' }}>{f.pago_label}</span>
                      </div>
                    </div>
                    <div style={{ display: 'flex', flexDirection: 'column', alignItems: 'flex-end', gap: 4 }}>
                      <div style={{ fontSize: 13.5, fontWeight: 700, color: '#FC5A00', fontVariantNumeric: 'tabular-nums' }}>
                        ${f.total.toFixed(2)}
                      </div>
                      {f.url_pdf && (
                        <a
                          href={f.url_pdf} target="_blank" rel="noopener noreferrer"
                          style={{ fontSize: 10.5, color: '#FC5A00', textDecoration: 'none', fontWeight: 600 }}
                        >Descargar PDF →</a>
                      )}
                    </div>
                  </div>
                ))}
                {invoices.facturas && invoices.facturas.length > 8 && (
                  <div style={{ fontSize: 11.5, color: 'rgba(255,255,255,0.4)', textAlign: 'center', paddingTop: 10 }}>
                    + {invoices.facturas.length - 8} más
                  </div>
                )}
              </div>
            )}
          </div>
          <ActionCard icon="card"  iconTone="navy"   title="Métodos de pago"      subtitle="Tarjetas y domiciliación"/>
          <ActionCard icon="info"  iconTone="navy"   title="Conexión y seguridad" subtitle="APN, contraseña y dispositivos"/>
        </div>
      </div>
    </div>
  );
}

// ─────────────────────────────────────────────────────────────
// EditarPerfilScreen — formulario para actualizar res.partner
// ─────────────────────────────────────────────────────────────
function EditarPerfilScreen() {
  const [loading, setLoading] = React.useState(true);
  const [saving, setSaving] = React.useState(false);
  const [error, setError] = React.useState(null);
  const [saved, setSaved] = React.useState(false);

  // Campos editables
  const [name, setName] = React.useState('');
  const [email, setEmail] = React.useState('');
  const [phone, setPhone] = React.useState('');
  const [street, setStreet] = React.useState('');
  const [street2, setStreet2] = React.useState('');
  const [city, setCity] = React.useState('');
  const [zip, setZip] = React.useState('');
  const [vat, setVat] = React.useState('');
  const [curp, setCurp] = React.useState('');

  // Carga datos actuales
  React.useEffect(() => {
    let cancelled = false;
    window.ViralApi.getAccount(undefined, { force: true })
      .then(acct => {
        if (cancelled) return;
        if (!acct?.ok) { setError(acct?.mensaje || 'No se pudo cargar tu cuenta'); return; }
        const p = acct.persona || {};
        setName(p.nombre || '');
        setEmail(p.email || '');
        setPhone(p.telefono || '');
        setCurp((p.curp || '').toUpperCase());
        setVat(p.rfc || '');
        setStreet(p.street || '');
        setStreet2(p.street2 || '');
        setCity(p.city || '');
        setZip(p.zip || '');
      })
      .catch(err => { if (!cancelled) setError(err.message || 'Error de red'); })
      .finally(() => { if (!cancelled) setLoading(false); });
    return () => { cancelled = true; };
  }, []);

  const handleSave = async () => {
    if (saving) return;
    setSaving(true); setError(null); setSaved(false);
    try {
      // SIEMPRE enviar los 9 campos (aunque estén vacíos) para no sobrescribir
      // con vacío los no enviados. Los inputs vienen pre-llenados con los valores
      // actuales, así que los no modificados se mandan tal cual.
      const fields = {
        name:             name.trim(),
        email:            email.trim(),
        phone:            phone.trim(),
        street:           street.trim(),
        street2:          street2.trim(),
        city:             city.trim(),
        zip:              zip.trim(),
        vat:              vat.trim().toUpperCase(),
        l10n_mx_edi_curp: curp.trim().toUpperCase(),
      };
      const res = await window.ViralApi.updateAccount(fields);
      if (!res?.ok) {
        setError(res?.error || 'No se pudo guardar');
      } else {
        setSaved(true);
        setTimeout(() => setSaved(false), 3000);
      }
    } catch (err) {
      setError(err.message || 'Error de red');
    } finally {
      setSaving(false);
    }
  };

  const inputStyle = {
    width: '100%', padding: '12px 14px', marginTop: 6,
    background: 'rgba(255,255,255,0.04)', color: '#fff',
    border: 'none', outline: 'none', borderRadius: 12,
    boxShadow: 'inset 0 0 0 1px rgba(255,255,255,0.08)',
    fontSize: 15, fontWeight: 500, fontFamily: 'inherit',
  };
  const labelStyle = {
    display: 'block', fontSize: 11.5, fontWeight: 600,
    color: 'rgba(255,255,255,0.5)', letterSpacing: 0.4, textTransform: 'uppercase',
    marginTop: 14,
  };

  return (
    <div className="viral-app viral-screen-bg" style={{
      width: '100%', height: '100%',
      display: 'flex', flexDirection: 'column',
      paddingTop: 'calc(env(safe-area-inset-top, 0px) + 16px)',
      paddingBottom: 'max(90px, calc(env(safe-area-inset-bottom, 0px) + 90px))',
      overflowY: 'auto',
    }}>
      <ScreenHeader eyebrow="Mi cuenta" title="Editar perfil"/>

      {loading && <EditarPerfilSkeleton />}

      <div style={{ padding: '8px 20px 0' }}>
        {!loading && (
          <React.Fragment>
            <label style={{ ...labelStyle, marginTop: 0 }}>Nombre completo</label>
            <input type="text" value={name} onChange={e => setName(e.target.value)} style={inputStyle} />

            <label style={labelStyle}>Correo electrónico</label>
            <input type="email" value={email} onChange={e => setEmail(e.target.value)} style={inputStyle} />

            <label style={labelStyle}>Teléfono</label>
            <input
              type="tel" value={phone}
              onChange={e => setPhone(e.target.value.replace(/[^\d+\s\-()]/g, ''))}
              style={inputStyle}
            />

            <label style={labelStyle}>CURP</label>
            <input
              type="text" value={curp}
              onChange={e => setCurp(e.target.value.toUpperCase().replace(/[^A-Z0-9]/g, '').slice(0, 18))}
              placeholder="18 caracteres"
              style={{ ...inputStyle, letterSpacing: 1, fontVariantNumeric: 'tabular-nums' }}
            />

            <label style={labelStyle}>RFC</label>
            <input
              type="text" value={vat}
              onChange={e => setVat(e.target.value.toUpperCase().replace(/[^A-Z0-9]/g, '').slice(0, 13))}
              placeholder="13 caracteres"
              style={{ ...inputStyle, letterSpacing: 1 }}
            />

            <div style={{
              marginTop: 24, paddingTop: 16,
              borderTop: '1px solid rgba(255,255,255,0.08)',
              fontSize: 11.5, fontWeight: 700, color: 'rgba(255,255,255,0.4)',
              letterSpacing: 1.2, textTransform: 'uppercase',
            }}>Dirección de envío</div>

            <label style={labelStyle}>Calle y número</label>
            <input type="text" value={street} onChange={e => setStreet(e.target.value)} placeholder="Ej: Av. Reforma 123" style={inputStyle} />

            <label style={labelStyle}>Colonia / referencias</label>
            <input type="text" value={street2} onChange={e => setStreet2(e.target.value)} placeholder="Opcional" style={inputStyle} />

            <label style={labelStyle}>Ciudad</label>
            <input type="text" value={city} onChange={e => setCity(e.target.value)} style={inputStyle} />

            <label style={labelStyle}>Código postal</label>
            <input
              type="tel" value={zip}
              onChange={e => setZip(e.target.value.replace(/\D/g, '').slice(0, 5))}
              style={{ ...inputStyle, letterSpacing: 1 }}
            />

            {/* Feedback de guardado */}
            {error && (
              <div style={{
                marginTop: 20, padding: 12, borderRadius: 12,
                background: 'rgba(220,38,38,0.10)',
                boxShadow: 'inset 0 0 0 1px rgba(220,38,38,0.35)',
                fontSize: 13, color: '#FCA5A5',
              }}>{error}</div>
            )}
            {saved && (
              <div style={{
                marginTop: 20, padding: 12, borderRadius: 12,
                background: 'rgba(216,255,75,0.10)',
                boxShadow: 'inset 0 0 0 1px rgba(216,255,75,0.30)',
                fontSize: 13, color: '#D8FF4B', fontWeight: 600,
              }}>✓ Cambios guardados</div>
            )}

            <div style={{ marginTop: 24, display: 'flex', flexDirection: 'column', gap: 10 }}>
              <Button
                variant="primary" fullWidth icon={saving ? null : "arrow-right"}
                onClick={handleSave}
              >
                {saving ? 'Guardando…' : 'Guardar cambios'}
              </Button>
              <Button
                variant="secondary" fullWidth
                onClick={() => window.__viralNav?.navigate('mi-cuenta')}
              >Cancelar</Button>
            </div>

            <p style={{ margin: '14px 0 0', fontSize: 11.5, color: 'rgba(255,255,255,0.45)', lineHeight: 1.5 }}>
              Los cambios se reflejan en Odoo de inmediato. Si actualizas tu CURP, también se usará al iniciar una portabilidad.
            </p>
          </React.Fragment>
        )}
      </div>
    </div>
  );
}

// Input para aplicar código de referido (msisdn del referente).
// Se renderiza dentro de la tarjeta de Referidos en MiCuenta cuando referido_por está vacío.
function AplicarReferralInput() {
  const [codigo, setCodigo] = React.useState('');
  const [loading, setLoading] = React.useState(false);
  const [error, setError] = React.useState('');
  const [success, setSuccess] = React.useState(false);

  const handleApply = async () => {
    const clean = codigo.replace(/\D/g, '').slice(-10);
    if (clean.length !== 10) { setError('Ingresa los 10 dígitos del número que te invitó'); return; }
    setLoading(true); setError('');
    try {
      const r = await window.ViralApi.applyReferral(clean);
      if (r?.ok) {
        setSuccess(true);
        setTimeout(() => window.location.reload(), 1200);  // refresh para que se vea el cambio
      } else {
        setError(r?.error || 'No se pudo aplicar el código');
      }
    } catch (err) { setError(err.message); }
    finally { setLoading(false); }
  };

  return (
    <div style={{
      padding: 12, borderRadius: 10,
      background: 'rgba(255,255,255,0.03)',
      boxShadow: 'inset 0 0 0 1px dashed rgba(255,255,255,0.12)',
    }}>
      <div style={{ fontSize: 12.5, color: 'rgba(255,255,255,0.7)', marginBottom: 8 }}>
        ¿Te invitó alguien? Aplica su código (10 dígitos)
      </div>
      <div style={{ display: 'flex', gap: 8 }}>
        <input
          type="tel" inputMode="numeric" value={codigo}
          onChange={(e) => { setCodigo(e.target.value.replace(/\D/g, '').slice(0, 10)); setError(''); }}
          placeholder="5512345678"
          disabled={loading || success}
          style={{
            flex: 1, padding: '10px 12px',
            background: 'rgba(255,255,255,0.05)', color: '#fff',
            border: 'none', outline: 'none', borderRadius: 10,
            boxShadow: 'inset 0 0 0 1px rgba(255,255,255,0.08)',
            fontSize: 14, fontWeight: 600, fontFamily: 'inherit',
            fontVariantNumeric: 'tabular-nums', letterSpacing: 0.5,
          }}
        />
        <button
          onClick={handleApply} disabled={loading || success}
          style={{
            background: success ? '#D8FF4B' : '#FC5A00',
            color: success ? '#0A0C28' : '#fff',
            border: 'none', padding: '0 16px', borderRadius: 10,
            fontSize: 12.5, fontWeight: 700, cursor: 'pointer', fontFamily: 'inherit',
            minWidth: 88,
          }}
        >
          {success ? '✓' : loading ? '...' : 'Aplicar'}
        </button>
      </div>
      {error && (
        <div style={{ marginTop: 8, fontSize: 11.5, color: '#FCA5A5' }}>{error}</div>
      )}
    </div>
  );
}

// Helper para filas de datos personales
function DataRow({ label, value, last }) {
  return (
    <div style={{
      padding: '10px 0',
      borderBottom: last ? 'none' : '1px solid rgba(255,255,255,0.06)',
      display: 'flex', flexDirection: 'column', gap: 3,
    }}>
      <div style={{ fontSize: 11, color: 'rgba(255,255,255,0.45)', fontWeight: 600, letterSpacing: 0.3, textTransform: 'uppercase' }}>{label}</div>
      <div style={{ fontSize: 13.5, color: '#fff', fontWeight: 500, lineHeight: 1.4 }}>{value}</div>
    </div>
  );
}

// ─────────────────────────────────────────────────────────────
// RecargasScreen — Comprar paquetes
// ─────────────────────────────────────────────────────────────
// Host del shop Odoo.
const SHOP_HOST = ODOO_BASE;   // mismo host de Odoo (config global)

// Paquetes hardcoded — URLs del shop. Si cambian precios o paquetes, editar acá.
const PAQUETES_RECARGA = [
  { name: 'BONUS',  price: 149, gb: 4,  intl: 0.8, mins: 750,  popular: false, url: SHOP_HOST + '/shop/bonus-6231?category=7' },
  { name: 'PLUS',   price: 199, gb: 12, intl: 2.4, mins: 750,  popular: false, url: SHOP_HOST + '/shop/plus-6236?category=7' },
  { name: 'PRO',    price: 249, gb: 24, intl: 4.8, mins: 750,  popular: true,  url: SHOP_HOST + '/shop/pro-40?category=7' },
  { name: 'MASTER', price: 299, gb: 35, intl: 7,   mins: 750,  popular: false, url: SHOP_HOST + '/shop/master-6233?category=7' },
  { name: 'EPIC',   price: 499, gb: 50, intl: 10,  mins: 1500, popular: false, url: SHOP_HOST + '/shop/epic-6737?category=7' },
];

function RecargasScreen() {
  const [lineIdx, setLineIdx] = React.useState(0);
  const [pickerOpen, setPickerOpen] = React.useState(false);
  const [lines, setLines] = React.useState([]);

  React.useEffect(() => {
    let cancelled = false;
    window.ViralApi.getAccount()
      .then(acct => {
        if (cancelled) return;
        if (acct?.ok && Array.isArray(acct.lineas_info)) {
          setLines(acct.lineas_info.map(li => ({
            number: window.ViralApi.formatPhone(li.numero),
            plan: (li.plan || '').replace(/\s*\(.*\)$/, '') || '—',
          })));
        }
      })
      .catch(() => {});
    return () => { cancelled = true; };
  }, []);

  const selectedLine = lines[lineIdx];

  const PaqueteCard = ({ p }) => {
    const isPopular = p.popular;
    const btnBg = isPopular ? '#F5F5F0' : '#FC5A00';
    const btnFg = isPopular ? '#12152E' : '#fff';
    // Compra directa: el SSO agrega el plan (PREPAGO) al carrito y va a pagar.
    const tmplId = tmplIdFromUrl(p.url);
    let lineDigits = null;
    if (selectedLine) {
      const digits = String(selectedLine.number || '').replace(/\D/g, '');
      if (digits.length === 10) lineDigits = digits;
    }
    return (
      <div style={{
        position: 'relative',
        background: isPopular ? 'linear-gradient(135deg, #FC5A00 0%, #ff7a30 100%)' : '#1A1D52',
        borderRadius: 20, padding: 16,
        boxShadow: isPopular ? 'none' : 'inset 0 0 0 1px rgba(255,255,255,0.06)',
        color: '#fff',
      }}>
        {isPopular && (
          <div style={{
            position: 'absolute', top: -10, right: 14,
            background: '#D8FF4B', color: '#0A0C28',
            padding: '4px 10px', borderRadius: 999,
            fontSize: 10.5, fontWeight: 800, letterSpacing: 0.8, textTransform: 'uppercase',
          }}>Más popular</div>
        )}
        <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'baseline', marginBottom: 10 }}>
          <span style={{ fontSize: 18, fontWeight: 800, letterSpacing: '-0.02em' }}>{p.name}</span>
          <span style={{ fontSize: 22, fontWeight: 700, fontVariantNumeric: 'tabular-nums' }}>${p.price}</span>
        </div>
        <div style={{ display: 'flex', flexWrap: 'wrap', gap: '6px 14px', fontSize: 12.5, opacity: 0.9, marginBottom: 14 }}>
          <span>📶 {p.gb} GB MX</span>
          <span>🌎 {p.intl} GB EUA/CA</span>
          <span>📞 {p.mins} min EUA/CA</span>
          <span>💬 SMS ilim.</span>
        </div>
        <button
          onClick={() => openViaSSO({ productTmplId: tmplId, line: lineDigits })}
          style={{
            display: 'flex', alignItems: 'center', justifyContent: 'center', gap: 8,
            height: 38, padding: '0 16px', border: 'none', cursor: 'pointer',
            background: btnBg, color: btnFg,
            borderRadius: 12, fontFamily: 'inherit', fontSize: 14, fontWeight: 600,
            width: '100%', boxSizing: 'border-box',
          }}
        >
          {selectedLine ? 'Recargar ' + selectedLine.number : 'Comprar'}
          <Icon name="arrow-right" size={18} strokeWidth={2}/>
        </button>
      </div>
    );
  };

  return (
    <div className="viral-app viral-screen-bg" style={{
      width: '100%', height: '100%',
      display: 'flex', flexDirection: 'column',
      paddingTop: 'calc(env(safe-area-inset-top, 0px) + 16px)',
      paddingBottom: 'max(90px, calc(env(safe-area-inset-bottom, 0px) + 90px))',
      overflowY: 'auto',
    }}>
      <ScreenHeader eyebrow="Recargas" title="Fácil, rápido, donde sea"/>

      {/* Selector de línea a recargar — z-index alto cuando el dropdown está abierto
          para que las cards de paquetes de abajo no lo corten. */}
      {selectedLine && (
        <div style={{ padding: '20px 20px 0', position: 'relative', zIndex: pickerOpen ? 50 : 1 }}>
          <div style={{ fontSize: 11.5, fontWeight: 700, letterSpacing: 1.2, textTransform: 'uppercase', color: 'rgba(255,255,255,0.4)', marginBottom: 10 }}>Línea a recargar</div>
          <div style={{ position: 'relative' }}>
            <div
              onClick={() => lines.length > 1 && setPickerOpen(o => !o)}
              style={{
                padding: '14px 16px',
                background: '#1A1D52',
                borderRadius: 14,
                boxShadow: 'inset 0 0 0 1px rgba(255,255,255,0.08)',
                display: 'flex', alignItems: 'center', justifyContent: 'space-between',
                cursor: lines.length > 1 ? 'pointer' : 'default',
              }}
            >
              <div style={{ display: 'flex', alignItems: 'center', gap: 12 }}>
                <IconCircle name="sim" tone="orange" size={32}/>
                <div style={{ display: 'flex', flexDirection: 'column' }}>
                  <span style={{ fontSize: 11, color: 'rgba(255,255,255,0.5)', fontWeight: 600, letterSpacing: 0.3, textTransform: 'uppercase' }}>{selectedLine.plan}</span>
                  <span style={{ fontSize: 16, fontWeight: 700, color: '#fff', fontVariantNumeric: 'tabular-nums' }}>{selectedLine.number}</span>
                </div>
              </div>
              {lines.length > 1 && (
                <span style={{ transform: pickerOpen ? 'rotate(180deg)' : 'none', transition: 'transform 0.2s', color: 'rgba(255,255,255,0.5)' }}>
                  <Icon name="chevron-d" size={18} strokeWidth={2.4}/>
                </span>
              )}
            </div>
            {pickerOpen && lines.length > 1 && (
              <div style={{
                position: 'absolute', left: 0, right: 0, top: 'calc(100% + 8px)',
                background: 'linear-gradient(180deg, #14184E 0%, #0E1140 100%)',
                borderRadius: 16, padding: 6, zIndex: 100,
                boxShadow: '0 20px 48px rgba(0,0,0,0.55), 0 4px 12px rgba(0,0,0,0.30)',
                border: '1px solid rgba(255,255,255,0.10)',
                backdropFilter: 'blur(12px)',
              }}>
                {lines.map((l, i) => (
                  <button
                    key={i}
                    onClick={() => { setLineIdx(i); setPickerOpen(false); }}
                    style={{
                      width: '100%', display: 'flex', alignItems: 'center', justifyContent: 'space-between',
                      background: i === lineIdx ? 'rgba(252,90,0,0.18)' : 'transparent',
                      border: 'none', color: '#fff', padding: '11px 14px', borderRadius: 11,
                      cursor: 'pointer', fontSize: 14, textAlign: 'left',
                      boxShadow: i === lineIdx ? 'inset 0 0 0 1px rgba(252,90,0,0.30)' : 'none',
                    }}
                  >
                    <span style={{ fontWeight: 600, fontVariantNumeric: 'tabular-nums', letterSpacing: 0.3 }}>{l.number}</span>
                    <span style={{ fontSize: 11, opacity: 0.55, fontWeight: 500, letterSpacing: 0.4, textTransform: 'uppercase' }}>{l.plan}</span>
                  </button>
                ))}
              </div>
            )}
          </div>
        </div>
      )}

      {/* Paquetes (hardcoded — URLs reales viralcel.com) */}
      <div style={{ padding: '20px 20px 0', display: 'flex', flexDirection: 'column', gap: 14 }}>
        {PAQUETES_RECARGA.map((p, i) => <PaqueteCard key={i} p={p}/>)}
      </div>

      <div style={{ padding: '20px 20px 0', textAlign: 'center', fontSize: 11.5, color: 'rgba(255,255,255,0.4)', lineHeight: 1.5 }}>
        Folio IFT: 1837076 / 1837086 — válido en redes Altán + Telcel + AT&T. <br/>
        Al recargar, el pago se procesa en viralcel.com.
      </div>
    </div>
  );
}

// ─────────────────────────────────────────────────────────────
// SectionPlaceholder — para Soporte y futuras pantallas
// ─────────────────────────────────────────────────────────────
function SectionPlaceholder({ kind, label, hint }) {
  return (
    <ScreenPlaceholder kind={kind} label={label} hint={hint}/>
  );
}

// ─────────────────────────────────────────────────────────────
// TiendaScreen — comprar chip nuevo (planes nacionales, category=6)
// ─────────────────────────────────────────────────────────────
const PLANES_TIENDA = [
  { name: 'BONUS',  price: 149, gb: 4,  intl: 0.8, mins: 750,  popular: false, url: SHOP_HOST + '/shop/bonus-6232?category=6' },
  { name: 'PLUS',   price: 199, gb: 12, intl: 2.4, mins: 750,  popular: false, url: SHOP_HOST + '/shop/plus-6235?category=6' },
  { name: 'PRO',    price: 249, gb: 24, intl: 4.8, mins: 750,  popular: true,  url: SHOP_HOST + '/shop/pro-4?category=6' },
  { name: 'MASTER', price: 299, gb: 35, intl: 7,   mins: 750,  popular: false, url: SHOP_HOST + '/shop/master-6234?category=6' },
  { name: 'EPIC',   price: 499, gb: 50, intl: 10,  mins: 1500, popular: false, url: SHOP_HOST + '/shop/epic-6735?category=6' },
];

function TiendaScreen() {
  const ChipCard = ({ p }) => {
    const isPopular = p.popular;
    const btnBg = isPopular ? '#F5F5F0' : '#FC5A00';
    const btnFg = isPopular ? '#12152E' : '#fff';
    return (
      <div style={{
        position: 'relative',
        background: isPopular ? 'linear-gradient(135deg, #FC5A00 0%, #ff7a30 100%)' : '#1A1D52',
        borderRadius: 20, padding: 16,
        boxShadow: isPopular ? 'none' : 'inset 0 0 0 1px rgba(255,255,255,0.06)',
        color: '#fff',
      }}>
        {isPopular && (
          <div style={{
            position: 'absolute', top: -10, right: 14,
            background: '#D8FF4B', color: '#0A0C28',
            padding: '4px 10px', borderRadius: 999,
            fontSize: 10.5, fontWeight: 800, letterSpacing: 0.8, textTransform: 'uppercase',
          }}>Más popular</div>
        )}
        <div style={{ display: 'flex', justifyContent: 'space-between', alignItems: 'baseline', marginBottom: 10 }}>
          <span style={{ fontSize: 18, fontWeight: 800, letterSpacing: '-0.02em' }}>{p.name}</span>
          <span style={{ fontSize: 22, fontWeight: 700, fontVariantNumeric: 'tabular-nums' }}>${p.price}</span>
        </div>
        <div style={{ display: 'flex', flexWrap: 'wrap', gap: '6px 14px', fontSize: 12.5, opacity: 0.9, marginBottom: 14 }}>
          <span>📶 {p.gb} GB MX</span>
          <span>🌎 {p.intl} GB EUA/CA</span>
          <span>📞 {p.mins} min EUA/CA</span>
          <span>💬 SMS ilim.</span>
        </div>
        <button
          onClick={() => openViaSSO(p.url.replace(SHOP_HOST, ''))}
          style={{
            display: 'flex', alignItems: 'center', justifyContent: 'center', gap: 8,
            height: 38, padding: '0 16px', border: 'none', cursor: 'pointer',
            background: btnBg, color: btnFg,
            borderRadius: 12, fontFamily: 'inherit', fontSize: 14, fontWeight: 600,
            width: '100%', boxSizing: 'border-box',
          }}
        >
          Pedir SIM
          <Icon name="arrow-right" size={18} strokeWidth={2}/>
        </button>
      </div>
    );
  };

  return (
    <div className="viral-app viral-screen-bg" style={{
      width: '100%', height: '100%',
      display: 'flex', flexDirection: 'column',
      paddingTop: 'calc(env(safe-area-inset-top, 0px) + 16px)',
      paddingBottom: 'max(90px, calc(env(safe-area-inset-bottom, 0px) + 90px))',
      overflowY: 'auto',
    }}>
      <ScreenHeader eyebrow="Tienda" title="Pide tu SIM VIRAL"/>

      <div style={{ padding: '4px 20px 0', fontSize: 13, color: 'rgba(255,255,255,0.65)', lineHeight: 1.45 }}>
        Elige tu plan, paga en viralcel.com y recibe tu SIM física o eSIM lista para activar.
      </div>

      <div style={{ padding: '20px 20px 0', display: 'flex', flexDirection: 'column', gap: 14 }}>
        {PLANES_TIENDA.map((p, i) => <ChipCard key={i} p={p}/>)}
      </div>

      <div style={{ padding: '20px 20px 0', textAlign: 'center', fontSize: 11.5, color: 'rgba(255,255,255,0.4)', lineHeight: 1.5 }}>
        Pago seguro en viralcel.com. Envío gratis o eSIM al instante.
      </div>
    </div>
  );
}

// ─────────────────────────────────────────────────────────────
// PortabilidadScreen — flujo CASCADA (todos los pasos visibles)
//   1. Número a portar
//   2. NIP
//   3. Selecciona la línea VIRAL o compra una
//   4. Vinculación (auto-check Odoo — si está, pasa; si no, vincular inline)
//   5. Activar línea (eSIM / SIM)
//   6. Solicitud status (Pendiente → Programada/Cancelada — viene de Odoo)
//   7. Fecha portabilidad + Realizada
// ─────────────────────────────────────────────────────────────
const PORTABILIDAD_STORAGE_KEY = 'miviral_portabilidad_v1';

function PortabilidadScreen() {
  // Carga inicial desde localStorage (si hay)
  const initial = (() => {
    try {
      const raw = localStorage.getItem(PORTABILIDAD_STORAGE_KEY);
      return raw ? JSON.parse(raw) : null;
    } catch (_) { return null; }
  })();

  const [numero, setNumero] = React.useState(initial?.numero || '');
  const [nip, setNip] = React.useState(initial?.nip || '');
  const [lineaIdx, setLineaIdx] = React.useState(initial?.lineaIdx ?? -1);
  const [metodoActivacion, setMetodoActivacion] = React.useState(initial?.metodoActivacion || null);
  // Datos personales para pre-llenar el form de Odoo (CURP y nombres)
  const [curp, setCurp] = React.useState(initial?.curp || '');
  const [nombres, setNombres] = React.useState(initial?.nombres || '');
  const [apellidoPaterno, setApellidoPaterno] = React.useState(initial?.apellidoPaterno || '');
  const [apellidoMaterno, setApellidoMaterno] = React.useState(initial?.apellidoMaterno || '');

  // Persiste cualquier cambio
  React.useEffect(() => {
    try {
      localStorage.setItem(PORTABILIDAD_STORAGE_KEY, JSON.stringify({
        numero, nip, lineaIdx, metodoActivacion,
        curp, nombres, apellidoPaterno, apellidoMaterno,
        savedAt: new Date().toISOString(),
      }));
    } catch (_) {}
  }, [numero, nip, lineaIdx, metodoActivacion, curp, nombres, apellidoPaterno, apellidoMaterno]);

  // Permite borrar el progreso (botón "reiniciar")
  const reset = () => {
    setNumero(''); setNip(''); setLineaIdx(-1); setMetodoActivacion(null);
    setCurp(''); setNombres(''); setApellidoPaterno(''); setApellidoMaterno('');
    try { localStorage.removeItem(PORTABILIDAD_STORAGE_KEY); } catch (_) {}
  };

  // Líneas VIRAL reales del cliente (desde Odoo) — keepPlain msisdn para Odoo redirect
  const [lineasViral, setLineasViral] = React.useState([]);
  React.useEffect(() => {
    let cancelled = false;
    window.ViralApi.getAccount()
      .then(acct => {
        if (cancelled || !acct?.ok || !Array.isArray(acct.lineas_info)) return;
        // Asume todas vinculadas si la cuenta lo está; status real requiere Odoo
        const vincLinea = acct.vinculacion?.es_vinculado === true;
        setLineasViral(acct.lineas_info.map(li => ({
          numero_raw: li.numero,  // 10 dígitos para mandar a Odoo
          number: window.ViralApi.formatPhone(li.numero),
          plan: (li.plan || '').replace(/\s*\(.*\)$/, '') || '—',
          adquirida: li.ultima_orden ? 'última orden ' + li.ultima_orden.split(' ')[0] : '—',
          vinculada: vincLinea,
        })));
        // Auto-prefill datos personales SOLO si los inputs están vacíos.
        // Respeta lo que el usuario ya haya escrito (localStorage).
        const persona = acct.persona || {};
        if (!curp && persona.curp) setCurp(String(persona.curp).toUpperCase());
        if (!nombres && persona.nombres) setNombres(persona.nombres);
        if (!apellidoPaterno && persona.apellido_paterno) setApellidoPaterno(persona.apellido_paterno);
        if (!apellidoMaterno && persona.apellido_materno) setApellidoMaterno(persona.apellido_materno);
      }).catch(() => {});
    return () => { cancelled = true; };
  }, []);
  // Línea elegida (vacío si nada)
  const lineaElegida = lineaIdx >= 0 ? lineasViral[lineaIdx] : null;

  // Validación async de la línea VIRAL elegida → /msisdn_port_validation
  // null = no consultado, true/false = resultado real
  const [lineaValid, setLineaValid] = React.useState(null);
  const [lineaValidMsg, setLineaValidMsg] = React.useState('');
  React.useEffect(() => {
    if (!lineaElegida) { setLineaValid(null); setLineaValidMsg(''); return; }
    let cancelled = false;
    setLineaValid(null);  // loading
    window.ViralApi.validatePort(lineaElegida.numero_raw)
      .then(r => {
        if (cancelled) return;
        setLineaValid(r?.valid === true);
        setLineaValidMsg(r?.msg || (r?.valid ? '' : 'Línea no elegible'));
      })
      .catch(() => { if (!cancelled) { setLineaValid(false); setLineaValidMsg('Error al validar'); } });
    return () => { cancelled = true; };
  }, [lineaElegida?.numero_raw]);

  // ¿Qué pasos están completos? — controla el "color verde / candado abierto"
  const okNumero  = numero.length === 10;
  const okNip     = nip.length >= 4;
  const okLinea   = lineaElegida !== null && lineaValid === true;   // ahora exige validación real
  const okVincul  = okLinea && lineaElegida.vinculada;
  const okActivar = metodoActivacion !== null;
  const okDatos   = curp.length === 18 && nombres.trim().length > 0
                    && apellidoPaterno.trim().length > 0 && apellidoMaterno.trim().length > 0;
  const okEnviado = okNumero && okNip && okLinea && okVincul && okActivar && okDatos;

  // Tras el submit, el usuario ve el folio en la pestaña del portal y recibe un
  // SMS de confirmación.

  // Redirect al portal Odoo con auto-inicio del flujo + prefill completo
  const continuarEnPortal = () => {
    if (!okEnviado || !lineaElegida) return;
    const url = window.ViralApi.buildPortabilityUrl({
      line: lineaElegida.numero_raw,
      numeroPortar: numero,
      nip: nip,
      curp, nombres, apellidoPaterno, apellidoMaterno,
    });
    window.open(url, '_blank', 'noopener');
  };

  return (
    <div className="viral-app viral-screen-bg" style={{
      width: '100%', height: '100%',
      display: 'flex', flexDirection: 'column',
      paddingTop: 'calc(env(safe-area-inset-top, 0px) + 16px)',
      paddingBottom: 'max(90px, calc(env(safe-area-inset-bottom, 0px) + 90px))',
      overflowY: 'auto',
    }}>
      <ScreenHeader eyebrow="Portabilidad" title="Trae tu número a VIRAL"/>

      {/* Indicador de progreso global */}
      <div style={{ padding: '0 20px 12px' }}>
        <div style={{ display: 'flex', alignItems: 'center', justifyContent: 'space-between', marginBottom: 6, gap: 12 }}>
          <span style={{ fontSize: 11.5, fontWeight: 700, color: 'rgba(255,255,255,0.5)', letterSpacing: 1 }}>
            COMPLETA LOS 6 PASOS
          </span>
          <div style={{ display: 'flex', alignItems: 'center', gap: 10 }}>
            <span style={{ fontSize: 11.5, color: 'rgba(255,255,255,0.4)' }}>
              {[okNumero, okNip, okLinea, okVincul, okActivar, okDatos].filter(Boolean).length} / 6
            </span>
            {(numero || nip || lineaIdx >= 0 || metodoActivacion) && (
              <button
                onClick={() => { if (confirm('¿Borrar el progreso guardado?')) reset(); }}
                style={{
                  background: 'transparent', border: '1px solid rgba(255,255,255,0.12)',
                  color: 'rgba(255,255,255,0.55)', cursor: 'pointer', fontFamily: 'inherit',
                  fontSize: 10.5, fontWeight: 600, padding: '4px 10px', borderRadius: 999,
                }}
              >Reiniciar</button>
            )}
          </div>
        </div>
      </div>

      {/* Paso 1 — Número (siempre visible) */}
      <CascadeStep n={1} title="¿Qué número quieres portar?" done={okNumero}>
        <p style={{ margin: '0 0 12px', fontSize: 13, color: 'rgba(255,255,255,0.65)', lineHeight: 1.45 }}>
          Tu número actual de otra compañía. Lo conservas — solo cambia la red.
        </p>
        <input
          type="tel" inputMode="numeric" value={numero}
          onChange={(e) => setNumero(e.target.value.replace(/\D/g, '').slice(0, 10))}
          placeholder="10 dígitos, sin +52"
          style={{
            width: '100%', padding: '12px 14px',
            background: 'rgba(255,255,255,0.04)', color: '#fff',
            border: 'none', outline: 'none', borderRadius: 12,
            boxShadow: 'inset 0 0 0 1px rgba(255,255,255,0.08)',
            fontSize: 16, fontWeight: 600, fontFamily: 'inherit',
            fontVariantNumeric: 'tabular-nums', letterSpacing: 0.5,
          }}
        />
      </CascadeStep>

      {/* Paso 2 — NIP (solo si okNumero) */}
      {okNumero && (
      <CascadeStep n={2} title="Escribe tu NIP" done={okNip}>
        <p style={{ margin: '0 0 10px', fontSize: 13, color: 'rgba(255,255,255,0.65)', lineHeight: 1.45 }}>
          Solicítalo enviando un SMS con la palabra <strong style={{ color: '#fff' }}>NIP</strong> al <strong style={{ color: '#fff' }}>051</strong> desde el número a portar. Es válido 24 horas.
        </p>
        <input
          type="tel" inputMode="numeric" value={nip}
          onChange={(e) => setNip(e.target.value.replace(/\D/g, '').slice(0, 8))}
          placeholder="NIP de portabilidad"
          style={{
            width: '100%', padding: '12px 14px',
            background: 'rgba(255,255,255,0.04)', color: '#fff',
            border: 'none', outline: 'none', borderRadius: 12,
            boxShadow: 'inset 0 0 0 1px rgba(255,255,255,0.08)',
            fontSize: 16, fontWeight: 600, fontFamily: 'inherit',
            fontVariantNumeric: 'tabular-nums', letterSpacing: 2,
          }}
        />
      </CascadeStep>
      )}

      {/* Paso 3 — Línea VIRAL (solo si okNip) */}
      {okNip && (
      <CascadeStep n={3} title="Selecciona la línea VIRAL" done={okLinea}>
        <p style={{ margin: '0 0 12px', fontSize: 13, color: 'rgba(255,255,255,0.65)', lineHeight: 1.45 }}>
          Solo aparecen líneas adquiridas hace menos de 30 días.
        </p>
        <div style={{ display: 'flex', flexDirection: 'column', gap: 8 }}>
          {lineasViral.map((l, i) => (
            <button
              key={i}
              onClick={() => setLineaIdx(i)}
              style={{
                textAlign: 'left', cursor: 'pointer',
                background: i === lineaIdx ? 'rgba(252,90,0,0.15)' : 'rgba(255,255,255,0.03)',
                border: 'none',
                boxShadow: i === lineaIdx ? 'inset 0 0 0 2px #FC5A00' : 'inset 0 0 0 1px rgba(255,255,255,0.08)',
                borderRadius: 12, padding: 12,
                display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 10,
              }}
            >
              <div style={{ display: 'flex', alignItems: 'center', gap: 10 }}>
                <IconCircle name="sim" tone="orange" size={28}/>
                <div style={{ display: 'flex', flexDirection: 'column' }}>
                  <span style={{ fontSize: 14.5, fontWeight: 700, color: '#fff', fontVariantNumeric: 'tabular-nums' }}>{l.number}</span>
                  <span style={{ fontSize: 11, color: 'rgba(255,255,255,0.5)' }}>{l.plan} · {l.adquirida}</span>
                </div>
              </div>
              {i === lineaIdx && <span style={{ color: '#FC5A00', fontSize: 16, fontWeight: 700 }}>✓</span>}
            </button>
          ))}
          <button
            onClick={() => window.__viralNav?.navigate('tienda')}
            style={{
              cursor: 'pointer', background: 'rgba(255,255,255,0.03)',
              border: 'none', boxShadow: 'inset 0 0 0 1px dashed rgba(255,255,255,0.18)',
              borderRadius: 12, padding: 12, color: 'rgba(255,255,255,0.7)', fontSize: 13, fontWeight: 500,
            }}
          >
            + Comprar una línea nueva (Tienda)
          </button>
        </div>

        {/* Estado de validación de la línea elegida */}
        {lineaElegida && lineaValid === null && (
          <div style={{ marginTop: 10, fontSize: 12, color: 'rgba(255,255,255,0.55)' }}>
            Validando con Odoo…
          </div>
        )}
        {lineaElegida && lineaValid === false && (
          <div style={{
            marginTop: 10, padding: 10, borderRadius: 10,
            background: 'rgba(252,90,0,0.10)',
            boxShadow: 'inset 0 0 0 1px rgba(252,90,0,0.30)',
            fontSize: 12.5, color: '#FC5A00', fontWeight: 600,
          }}>
            Esta línea no es elegible{lineaValidMsg ? ' — ' + lineaValidMsg : ''}. Elige otra.
          </div>
        )}
        {lineaElegida && lineaValid === true && (
          <div style={{ marginTop: 10, fontSize: 12, color: 'rgba(216,255,75,0.85)', fontWeight: 600 }}>
            ✓ Línea elegible para portabilidad
          </div>
        )}
      </CascadeStep>
      )}

      {/* Paso 4 — Vinculación (solo si okLinea) */}
      {okLinea && (
      <CascadeStep n={4} title="Vinculación" done={okVincul}>
        {okLinea && lineaElegida.vinculada ? (
          <div style={{
            padding: 12, background: 'rgba(216, 255, 75, 0.08)',
            border: '1px solid rgba(216, 255, 75, 0.25)', borderRadius: 12,
            display: 'flex', alignItems: 'center', gap: 12,
          }}>
            <div style={{
              width: 32, height: 32, borderRadius: '50%', background: '#D8FF4B',
              display: 'flex', alignItems: 'center', justifyContent: 'center', color: '#0A0C28', fontSize: 18, fontWeight: 800,
            }}>✓</div>
            <div>
              <div style={{ fontSize: 13.5, fontWeight: 700, color: '#fff' }}>Línea ya vinculada</div>
              <div style={{ fontSize: 12, color: 'rgba(255,255,255,0.6)' }}>Verificada con tu INE</div>
            </div>
          </div>
        ) : (
          <div>
            <p style={{ margin: '0 0 10px', fontSize: 13, color: 'rgba(255,255,255,0.65)' }}>
              La línea seleccionada aún no está vinculada. Necesitas selfie + INE (5 min).
            </p>
            <Button variant="primary" fullWidth icon="arrow-right">Vincular ahora</Button>
          </div>
        )}
      </CascadeStep>
      )}

      {/* Paso 5 — Activar (solo si okVincul) */}
      {okVincul && (
      <CascadeStep n={5} title="Activar tu línea" done={okActivar}>
        <p style={{ margin: '0 0 10px', fontSize: 13, color: 'rgba(255,255,255,0.65)', lineHeight: 1.45 }}>
          ¿Cómo quieres activar tu línea VIRAL antes de portar?
        </p>
        <div style={{ display: 'flex', flexDirection: 'column', gap: 8 }}>
          {[
            { id: 'esim', icon: '📲', title: 'eSIM', sub: 'Escanea el código QR del correo de bienvenida' },
            { id: 'sim',  icon: '🧩', title: 'Chip físico', sub: 'Inserta tu chip VIRAL y reinicia el equipo' },
          ].map(o => (
            <button
              key={o.id}
              onClick={() => setMetodoActivacion(o.id)}
              style={{
                textAlign: 'left', cursor: 'pointer',
                background: metodoActivacion === o.id ? 'rgba(252,90,0,0.15)' : 'rgba(255,255,255,0.03)',
                border: 'none',
                boxShadow: metodoActivacion === o.id ? 'inset 0 0 0 2px #FC5A00' : 'inset 0 0 0 1px rgba(255,255,255,0.08)',
                borderRadius: 12, padding: 12,
                display: 'flex', alignItems: 'center', gap: 12,
              }}
            >
              <span style={{ fontSize: 24 }}>{o.icon}</span>
              <div style={{ display: 'flex', flexDirection: 'column' }}>
                <span style={{ fontSize: 14.5, fontWeight: 700, color: '#fff' }}>{o.title}</span>
                <span style={{ fontSize: 12, color: 'rgba(255,255,255,0.55)' }}>{o.sub}</span>
              </div>
            </button>
          ))}
        </div>
      </CascadeStep>
      )}

      {/* Paso 6 — Datos personales (CURP + nombres) */}
      {okActivar && (
      <CascadeStep n={6} title="Datos personales" done={okDatos}>
        <p style={{ margin: '0 0 10px', fontSize: 13, color: 'rgba(255,255,255,0.65)', lineHeight: 1.45 }}>
          Lo que IFT requiere. Si ya los tenemos registrados, vienen pre-llenados — solo confirma.
        </p>
        {(() => {
          const inputStyle = {
            width: '100%', padding: '12px 14px', marginTop: 8,
            background: 'rgba(255,255,255,0.04)', color: '#fff',
            border: 'none', outline: 'none', borderRadius: 12,
            boxShadow: 'inset 0 0 0 1px rgba(255,255,255,0.08)',
            fontSize: 15, fontWeight: 500, fontFamily: 'inherit',
          };
          return (
            <React.Fragment>
              <input
                type="text" value={curp}
                onChange={(e) => setCurp(e.target.value.toUpperCase().replace(/[^A-Z0-9]/g, '').slice(0, 18))}
                placeholder="CURP (18 caracteres)"
                style={{ ...inputStyle, letterSpacing: 1, fontVariantNumeric: 'tabular-nums' }}
              />
              <input
                type="text" value={nombres}
                onChange={(e) => setNombres(e.target.value.replace(/[^A-Za-zÁÉÍÓÚÑáéíóúñ\s]/g, ''))}
                placeholder="Nombres"
                style={inputStyle}
              />
              <input
                type="text" value={apellidoPaterno}
                onChange={(e) => setApellidoPaterno(e.target.value.replace(/[^A-Za-zÁÉÍÓÚÑáéíóúñ\s]/g, ''))}
                placeholder="Apellido paterno"
                style={inputStyle}
              />
              <input
                type="text" value={apellidoMaterno}
                onChange={(e) => setApellidoMaterno(e.target.value.replace(/[^A-Za-zÁÉÍÓÚÑáéíóúñ\s]/g, ''))}
                placeholder="Apellido materno"
                style={inputStyle}
              />
              {curp.length > 0 && curp.length < 18 && (
                <div style={{ marginTop: 8, fontSize: 11.5, color: 'rgba(252,90,0,0.85)' }}>
                  CURP debe tener 18 caracteres ({curp.length}/18)
                </div>
              )}
            </React.Fragment>
          );
        })()}
      </CascadeStep>
      )}

      {/* Paso 7 — Enviar la solicitud */}
      {okDatos && okActivar && (
      <CascadeStep n={7} title="Enviar solicitud" done={false} last>
        <p style={{ margin: '0 0 12px', fontSize: 13, color: 'rgba(255,255,255,0.65)', lineHeight: 1.45 }}>
          Vamos al portal seguro de Odoo para confirmar y procesar. Todos los datos van pre-llenados — solo le das "Continuar" allá.
        </p>
        <Button
          variant="primary" fullWidth icon="arrow-right"
          onClick={continuarEnPortal}
        >
          Continuar en portal seguro
        </Button>
        <p style={{ margin: '10px 0 0', fontSize: 11.5, color: 'rgba(255,255,255,0.45)', lineHeight: 1.4 }}>
          Se abre en pestaña nueva.
        </p>
      </CascadeStep>
      )}
    </div>
  );
}

// ─────────────────────────────────────────────────────────────
// ReemplazoSimScreen — flujo simple: comprar SIM $50 → portar tu número
// ─────────────────────────────────────────────────────────────
// Card colapsable que verifica si un IMEI es compatible con la red VIRAL.
// Útil antes de comprar SIM/eSIM. Endpoint /get_imei_status público.
function ImeiCheckerCard() {
  const [open, setOpen] = React.useState(false);
  const [imei, setImei] = React.useState('');
  const [result, setResult] = React.useState(null);
  const [loading, setLoading] = React.useState(false);
  const [error, setError] = React.useState('');

  const check = async () => {
    setLoading(true); setError(''); setResult(null);
    try {
      const r = await window.ViralApi.checkImei(imei);
      if (r?.ok || r?.compatible !== undefined) setResult(r);
      else setError(r?.error || 'Error al verificar');
    } catch (err) { setError(err.message); }
    finally { setLoading(false); }
  };

  return (
    <div style={{ padding: '0 20px 14px' }}>
      <div className="viral-card" style={{ overflow: 'hidden' }}>
        <button onClick={() => setOpen(o => !o)} style={{
          width: '100%', display: 'flex', alignItems: 'center', gap: 12,
          background: 'transparent', border: 'none', color: '#fff',
          padding: 14, cursor: 'pointer', fontFamily: 'inherit', textAlign: 'left',
        }}>
          <IconCircle name="info" tone="navy" size={36}/>
          <div style={{ flex: 1, minWidth: 0 }}>
            <div style={{ fontSize: 14.5, fontWeight: 600 }}>¿Tu equipo es compatible?</div>
            <div style={{ fontSize: 12, color: 'rgba(255,255,255,0.55)', marginTop: 2 }}>
              Verifica IMEI antes de comprar
            </div>
          </div>
          <span style={{ transform: open ? 'rotate(180deg)' : 'none', transition: 'transform 0.2s', opacity: 0.6 }}>
            <Icon name="chevron-d" size={18} strokeWidth={2.4}/>
          </span>
        </button>
        {open && (
          <div style={{ padding: '0 14px 14px', borderTop: '1px solid rgba(255,255,255,0.06)' }}>
            <p style={{ margin: '12px 0 10px', fontSize: 12, color: 'rgba(255,255,255,0.55)', lineHeight: 1.4 }}>
              Marca <strong style={{ color: '#fff' }}>*#06#</strong> en tu teléfono para ver tu IMEI (15 dígitos).
            </p>
            <div style={{ display: 'flex', gap: 8 }}>
              <input
                type="tel" inputMode="numeric" value={imei}
                onChange={(e) => { setImei(e.target.value.replace(/\D/g, '').slice(0, 15)); setError(''); setResult(null); }}
                placeholder="15 dígitos"
                style={{
                  flex: 1, padding: '10px 12px',
                  background: 'rgba(255,255,255,0.04)', color: '#fff',
                  border: 'none', outline: 'none', borderRadius: 10,
                  boxShadow: 'inset 0 0 0 1px rgba(255,255,255,0.08)',
                  fontSize: 14, fontWeight: 600, fontFamily: 'inherit',
                  fontVariantNumeric: 'tabular-nums', letterSpacing: 0.5,
                }}
              />
              <button
                onClick={check} disabled={loading || imei.length !== 15}
                style={{
                  background: '#FC5A00', color: '#fff', border: 'none',
                  padding: '0 16px', borderRadius: 10,
                  fontSize: 12.5, fontWeight: 700, cursor: 'pointer', fontFamily: 'inherit',
                  opacity: (loading || imei.length !== 15) ? 0.5 : 1,
                  minWidth: 90,
                }}
              >{loading ? '...' : 'Verificar'}</button>
            </div>
            {error && (
              <div style={{ marginTop: 10, fontSize: 12, color: '#FCA5A5' }}>{error}</div>
            )}
            {result && (
              <div style={{
                marginTop: 12, padding: 12, borderRadius: 10,
                background: result.compatible
                  ? 'rgba(216,255,75,0.08)'
                  : 'rgba(220,38,38,0.10)',
                boxShadow: 'inset 0 0 0 1px ' + (result.compatible ? 'rgba(216,255,75,0.25)' : 'rgba(220,38,38,0.30)'),
              }}>
                <div style={{ fontSize: 14, fontWeight: 700, color: result.compatible ? '#D8FF4B' : '#FCA5A5', marginBottom: 4 }}>
                  {result.compatible ? '✓ Compatible' : '✗ No compatible'}
                </div>
                <div style={{ fontSize: 12.5, color: 'rgba(255,255,255,0.75)', lineHeight: 1.4 }}>
                  {result.mensaje}
                </div>
                {result.compatible && (result.brand || result.model) && (
                  <div style={{ marginTop: 8, paddingTop: 8, borderTop: '1px solid rgba(255,255,255,0.06)',
                    fontSize: 11.5, color: 'rgba(255,255,255,0.55)' }}>
                    {result.brand} · {result.model}
                    {result.volte && <span> · VoLTE</span>}
                    {result.band28 && <span> · Banda 28</span>}
                  </div>
                )}
              </div>
            )}
          </div>
        )}
      </div>
    </div>
  );
}

// El reemplazo se completa en el flujo real de Odoo (/my/reemplazos, del módulo
// mzs_viral_custom): ahí el cliente elige tipo (CHIP/eSIM) y línea a conservar,
// se agrega la variante correcta al carrito y se etiqueta la orden. La PWA solo
// hace el salto con SSO. El precio es solo para mostrar.
const SIM_REEMPLAZO_PRICE = 149;

function ReemplazoSimScreen() {
  const [lineas, setLineas] = React.useState([]);
  const [lineaIdx, setLineaIdx] = React.useState(-1);

  React.useEffect(() => {
    let cancelled = false;
    window.ViralApi.getAccount()
      .then(acct => {
        if (cancelled || !acct?.ok || !Array.isArray(acct.lineas_info)) return;
        setLineas(acct.lineas_info);
      }).catch(() => {});
    return () => { cancelled = true; };
  }, []);

  const lineaElegida = lineaIdx >= 0 ? lineas[lineaIdx] : null;

  const agregarAlCarrito = () => {
    // Salto con SSO al flujo real de reemplazo en Odoo. La línea elegida se pasa
    // como preselección; el cliente confirma tipo (CHIP/eSIM) y línea ahí.
    let next = '/my/reemplazos';
    if (lineaElegida && lineaElegida.numero) {
      const digits = String(lineaElegida.numero).replace(/\D/g, '').slice(-10);
      if (digits.length === 10) next += '?line=' + digits;
    }
    openViaSSO(next);
  };

  return (
    <div className="viral-app viral-screen-bg" style={{
      width: '100%', height: '100%',
      display: 'flex', flexDirection: 'column',
      paddingTop: 'calc(env(safe-area-inset-top, 0px) + 16px)',
      paddingBottom: 'max(90px, calc(env(safe-area-inset-bottom, 0px) + 90px))',
      overflowY: 'auto',
    }}>
      <ScreenHeader eyebrow="Reemplazo eSIM / SIM" title="¿Perdiste o dañaste tu SIM?"/>

      <div style={{ padding: '8px 20px 0' }}>
        <p style={{ margin: '0 0 18px', fontSize: 13.5, color: 'rgba(255,255,255,0.7)', lineHeight: 1.5 }}>
          Pide un chip de reemplazo desde aquí. Lo agregamos al carrito por <strong style={{ color: '#fff' }}>${SIM_REEMPLAZO_PRICE} MXN</strong> y un agente te contacta para coordinar el envío y la migración de tu línea. Conservas tu mismo número.
        </p>
      </div>

      {/* Verificador de IMEI — útil antes de comprar (eSIM vs chip físico) */}
      <ImeiCheckerCard/>

      {/* Paso 1 — Elegir línea a reemplazar (opcional pero recomendado) */}
      <CascadeStep n={1} title="¿Qué línea necesita reemplazo?" done={lineaIdx >= 0}>
        <p style={{ margin: '0 0 12px', fontSize: 13, color: 'rgba(255,255,255,0.65)', lineHeight: 1.45 }}>
          Opcional, pero ayuda al agente a tener todo listo más rápido.
        </p>
        {lineas.length === 0 && (
          <div style={{ fontSize: 12.5, color: 'rgba(255,255,255,0.5)', padding: '8px 0' }}>
            Cargando tus líneas…
          </div>
        )}
        <div style={{ display: 'flex', flexDirection: 'column', gap: 8 }}>
          {lineas.map((l, i) => (
            <button
              key={i}
              onClick={() => setLineaIdx(i)}
              style={{
                textAlign: 'left', cursor: 'pointer',
                background: i === lineaIdx ? 'rgba(252,90,0,0.15)' : 'rgba(255,255,255,0.03)',
                border: 'none',
                boxShadow: i === lineaIdx ? 'inset 0 0 0 2px #FC5A00' : 'inset 0 0 0 1px rgba(255,255,255,0.08)',
                borderRadius: 12, padding: 12,
                display: 'flex', alignItems: 'center', justifyContent: 'space-between', gap: 10,
              }}
            >
              <div style={{ display: 'flex', alignItems: 'center', gap: 10 }}>
                <IconCircle name="sim" tone="orange" size={28}/>
                <div style={{ display: 'flex', flexDirection: 'column' }}>
                  <span style={{ fontSize: 14.5, fontWeight: 700, color: '#fff', fontVariantNumeric: 'tabular-nums' }}>{window.ViralApi.formatPhone(l.numero)}</span>
                  <span style={{ fontSize: 11, color: 'rgba(255,255,255,0.5)' }}>{(l.plan || '').replace(/\s*\(.*\)$/, '') || '—'}</span>
                </div>
              </div>
              {i === lineaIdx && <span style={{ color: '#FC5A00', fontSize: 16, fontWeight: 700 }}>✓</span>}
            </button>
          ))}
        </div>
      </CascadeStep>

      {/* Paso 2 — Agregar al carrito */}
      <CascadeStep n={2} title="Solicita tu chip de reemplazo" done={false} last>
        <div style={{
          padding: 12, borderRadius: 10, marginBottom: 12,
          background: 'rgba(255,255,255,0.04)',
          boxShadow: 'inset 0 0 0 1px rgba(255,255,255,0.06)',
          display: 'flex', alignItems: 'center', justifyContent: 'space-between',
        }}>
          <div>
            <div style={{ fontSize: 13.5, fontWeight: 700, color: '#fff' }}>Chip de reemplazo</div>
            <div style={{ fontSize: 12, color: 'rgba(255,255,255,0.55)', marginTop: 2 }}>SIM física o eSIM (a coordinar)</div>
          </div>
          <div style={{ fontSize: 18, fontWeight: 800, color: '#FC5A00', fontVariantNumeric: 'tabular-nums' }}>${SIM_REEMPLAZO_PRICE}</div>
        </div>
        <Button variant="primary" fullWidth icon="arrow-right" onClick={agregarAlCarrito}>
          Solicitar reemplazo
        </Button>
        <p style={{ margin: '12px 0 0', fontSize: 11.5, color: 'rgba(255,255,255,0.45)', lineHeight: 1.45 }}>
          Se abre el flujo de reemplazo en Odoo (ya con tu sesión iniciada): eliges chip físico o eSIM, confirmas y pagas. Un agente te contacta para el envío.
        </p>
      </CascadeStep>

      <div style={{ padding: '0 20px', fontSize: 12, color: 'rgba(255,255,255,0.45)', lineHeight: 1.5, textAlign: 'center' }}>
        ¿Dudas? Escríbenos por WhatsApp y un agente te ayuda con el reemplazo.
      </div>
    </div>
  );
}

// Helper: tarjeta de paso (cascada) — solo se renderiza si el paso anterior está done
function CascadeStep({ n, title, done, last, children }) {
  const bg = done ? '#D8FF4B' : '#FC5A00';
  const fg = done ? '#0A0C28' : '#fff';
  return (
    <div
      style={{
        padding: `0 20px ${last ? 24 : 14}px`,
        animation: 'viral-fade-in-up 0.35s ease-out both',
      }}
    >
      <div className="viral-card" style={{ padding: 16 }}>
        <div style={{ display: 'flex', alignItems: 'center', gap: 10, marginBottom: 10 }}>
          <div style={{
            width: 26, height: 26, borderRadius: '50%', background: bg, color: fg,
            display: 'flex', alignItems: 'center', justifyContent: 'center',
            fontSize: 13, fontWeight: 800, flexShrink: 0,
          }}>{done ? '✓' : n}</div>
          <span style={{ fontSize: 14.5, fontWeight: 700, color: '#fff' }}>{title}</span>
        </div>
        {children}
      </div>
    </div>
  );
}

// ─────────────────────────────────────────────────────────────
// App — top-level state machine
// ─────────────────────────────────────────────────────────────
function App() {
  // app state: 'splash' → 'login' → 'tracker'
  const initialUser = (() => {
    const s = window.ViralApi.getSession();
    // Sesiones viejas (sin token) o vencidas → fuera. Limpia también la PII de
    // portabilidad, que pertenecía a quien haya usado antes el dispositivo.
    if (!s || !s.token || !s.expires_at || Date.parse(s.expires_at) <= Date.now()) {
      if (s) { try { window.ViralApi.clearCache(); } catch (_) {} window.ViralApi.logout(); }
      return null;
    }
    return s;
  })();
  const initialPhase = initialUser ? 'tracker' : 'splash';
  const [phase, setPhase] = React.useState(initialPhase);
  const [user, setUser] = React.useState(initialUser);
  const [notice, setNotice] = React.useState('');

  // tracker nav state
  // ── URL params for sharing specific states (and showing demo dock) ──
  const params = React.useMemo(() => {
    try { return new URLSearchParams(window.location.search); }
    catch (_) { return new URLSearchParams(); }
  }, []);
  const initialVariant = (() => {
    const v = params.get('state') || params.get('variant');
    if (v === 'delayed' || v === 'success') return v;
    return 'in-progress';
  })();
  const initialRoute = (() => {
    // Default SIEMPRE a 'mi-linea' al abrir la app (la pantalla principal)
    const r = params.get('screen') || params.get('route');
    if (['mi-linea', 'mi-cuenta', 'editar-perfil', 'recargas', 'tienda', 'portability', 'reemplazo', 'soporte'].includes(r)) return r;
    return 'mi-linea';
  })();
  // Dock visible by default for demo navigation; disable with ?demo=0
  const demoMode = params.get('demo') !== '0';

  const [route, setRoute] = React.useState(initialRoute);
  const [variant, setVariant] = React.useState(initialVariant);
  const [drawerOpen, setDrawerOpen] = React.useState(false);
  const [chatOpen, setChatOpen] = React.useState(false);

  const handleLogin = (u) => {
    // ViralApi.login ya persistió la sesión; aquí solo movemos la UI.
    setUser(u);
    setNotice('');
    setRoute('mi-linea');
    setPhase('tracker');
  };
  const handleLogout = () => {
    window.ViralApi.logout();   // revoca token + borra sesión + portabilidad + cache
    setUser(null);
    setPhase('login');
    setRoute('mi-linea'); setVariant('in-progress'); setDrawerOpen(false);
  };

  // Expose nav globally so screens can call back/navigate.
  // Sin deps: se re-asigna cada render para no capturar closures viejos.
  React.useEffect(() => {
    window.__viralNav = {
      navigate: (r) => setRoute(r),
      back: () => setRoute('mi-linea'),
      setVariant,
      logout: handleLogout,
    };
  });

  // 401/403 desde cualquier llamada → cerrar sesión. Escuchamos un evento del
  // window (no __viralNav) y solo tocamos setters, cuyas identidades son
  // estables, así que deps [] es seguro. ViralApi ya limpió el storage.
  React.useEffect(() => {
    const onUnauth = () => {
      setUser(null);
      setNotice('Tu sesión expiró. Inicia sesión de nuevo.');
      setPhase('login');
      setRoute('mi-linea'); setDrawerOpen(false);
    };
    window.addEventListener('viral:unauthorized', onUnauth);
    return () => window.removeEventListener('viral:unauthorized', onUnauth);
  }, []);

  // Close drawer with Escape
  React.useEffect(() => {
    const onKey = (e) => { if (e.key === 'Escape') setDrawerOpen(false); };
    window.addEventListener('keydown', onKey);
    return () => window.removeEventListener('keydown', onKey);
  }, []);

  // Render current screen
  const renderTrackerScreen = () => {
    if (route === 'mi-linea')   return <MiLineaScreen/>;
    if (route === 'recargas')   return <RecargasScreen/>;
    if (route === 'mi-cuenta')  return <MiCuentaScreen/>;
    if (route === 'editar-perfil') return <EditarPerfilScreen/>;
    if (route === 'portability') return <PortabilidadScreen/>;
    if (route === 'reemplazo')  return <ReemplazoSimScreen/>;
    if (route === 'tienda')     return <TiendaScreen/>;
    // Ruta por defecto.
    return <MiLineaScreen/>;
  };

  if (phase === 'splash') {
    return <SplashScreen onDone={() => setPhase('login')}/>;
  }
  if (phase === 'login') {
    return <LoginScreen onLogin={handleLogin} notice={notice}/>;
  }

  // Phase: tracker (the actual app)
  return (
    <React.Fragment>
      <div className="screen-host" key={`${route}-${variant}`}>
        {renderTrackerScreen()}
      </div>

      {/* Chat FAB — abre el sheet del asistente VIRAL embebido. */}
      <button
        className="viral-chat-fab"
        onClick={() => setChatOpen(true)}
        aria-label="Abrir chat con asistente VIRAL"
        type="button"
      >
        <svg width="24" height="24" viewBox="0 0 24 24" fill="none" stroke="#fff" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
          <path d="M21 12a8 8 0 01-12 7l-5 1 1-5a8 8 0 1116-3z"/>
        </svg>
      </button>

      {/* Sheet del chat — controlado por chatOpen */}
      <ChatPanel open={chatOpen} onClose={() => setChatOpen(false)}/>

      {/* FAB hamburguesa */}
      <button className="nav-fab" onClick={() => setDrawerOpen(true)} aria-label="Abrir menú de navegación">
        <svg width="22" height="22" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
          <line x1="4" y1="7"  x2="20" y2="7"/>
          <line x1="4" y1="12" x2="20" y2="12"/>
          <line x1="4" y1="17" x2="20" y2="17"/>
        </svg>
      </button>

      {/* Backdrop */}
      <div
        className={`nav-backdrop ${drawerOpen ? 'open' : ''}`}
        onClick={() => setDrawerOpen(false)}
      />

      {/* Drawer */}
      <aside className={`nav-drawer ${drawerOpen ? 'open' : ''}`} aria-hidden={!drawerOpen}>
        <div className="nd-head">
          <span className="title">VIRAL</span>
          <button className="nd-close" onClick={() => setDrawerOpen(false)} aria-label="Cerrar menú">
            <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
              <line x1="6" y1="6" x2="18" y2="18"/>
              <line x1="6" y1="18" x2="18" y2="6"/>
            </svg>
          </button>
        </div>

        {(() => {
          const NavItem = ({ id, label, iconPath, isActive, onClick, tag }) => (
            <button className={`nd-item ${isActive ? 'active' : ''}`} onClick={() => { onClick(); setDrawerOpen(false); }}>
              <span className="icon">
                <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round">
                  {iconPath}
                </svg>
              </span>
              <span className="item-label">{label}</span>
              {tag && <span className="item-tag">{tag}</span>}
            </button>
          );

          return (
            <React.Fragment>
              <div className="nd-section">
                <NavItem
                  label="Mi línea"
                  isActive={route === 'mi-linea'}
                  onClick={() => setRoute('mi-linea')}
                  iconPath={<><path d="M9 3h7l4 4v13a1.5 1.5 0 01-1.5 1.5h-13A1.5 1.5 0 014 20V8l5-5z"/><rect x="8" y="11" width="8" height="7" rx="1.5"/></>}
                />
                <NavItem
                  label="Recargas"
                  isActive={route === 'recargas'}
                  onClick={() => setRoute('recargas')}
                  iconPath={<><rect x="3" y="6" width="18" height="13" rx="2"/><path d="M3 10h18"/></>}
                />
                <NavItem
                  label="Mi cuenta"
                  isActive={route === 'mi-cuenta'}
                  onClick={() => setRoute('mi-cuenta')}
                  iconPath={<><circle cx="12" cy="8" r="4"/><path d="M4 21v-2a4 4 0 014-4h8a4 4 0 014 4v2"/></>}
                />
                <NavItem
                  label="Portabilidad"
                  isActive={route === 'portability'}
                  onClick={() => setRoute('portability')}
                  iconPath={<><path d="M5 12h14M13 6l6 6-6 6"/></>}
                />
                <NavItem
                  label="Reemplazo eSIM"
                  isActive={route === 'reemplazo'}
                  onClick={() => setRoute('reemplazo')}
                  iconPath={<><rect x="6" y="4" width="12" height="16" rx="2"/><path d="M9 8h6M9 12h4M12 17h.01"/></>}
                />
                <NavItem
                  label="Tienda"
                  isActive={route === 'tienda'}
                  onClick={() => setRoute('tienda')}
                  iconPath={<><path d="M4 7h16l-1.5 11a2 2 0 01-2 1.7H7.5a2 2 0 01-2-1.7L4 7z"/><path d="M9 7V5a3 3 0 016 0v2"/></>}
                />
              </div>

              <div className="nd-section">
                <div className="nd-section-title">Otros</div>
                <NavItem
                  label="Soporte"
                  isActive={false}
                  onClick={() => {
                    // Abre WhatsApp con mensaje pre-cargado para agente especializado
                    window.open(
                      'https://wa.me/528145808015?text=' + encodeURIComponent('Quiero hablar con un agente especializado'),
                      '_blank', 'noopener,noreferrer'
                    );
                    setDrawerOpen(false);
                  }}
                  iconPath={<><circle cx="12" cy="12" r="9"/><path d="M9 9.5a3 3 0 015.7 1.3c0 1.5-2.2 2-2.2 3.7M12 17.5h.01"/></>}
                />
              </div>

              <div className="nd-foot">
                <button className="logout" onClick={handleLogout}>Cerrar sesión</button>
                <div className="ver">VIRAL Cel · v1.0</div>
              </div>
            </React.Fragment>
          );
        })()}
      </aside>
    </React.Fragment>
  );
}

// ─────────────────────────────────────────────────────────────
// Mount — into the existing .app-viewport (preserves desktop frame)
// ─────────────────────────────────────────────────────────────
(function mount() {
  const root = document.getElementById('root');
  // Find or create the .app-viewport (desktop frame already includes it)
  let viewport = document.querySelector('.app-viewport');
  if (!viewport) {
    root.innerHTML = '';
    const stage = document.createElement('div');
    stage.className = 'app-stage';
    viewport = document.createElement('div');
    viewport.className = 'app-viewport';
    stage.appendChild(viewport);
    root.appendChild(stage);
  } else {
    viewport.innerHTML = '';
  }
  ReactDOM.createRoot(viewport).render(<App/>);
})();
