/* sections3.jsx — secciones Fase 3: Cola/Estado, Consultar RNC, Clasificador, Certificación */
const { useState, useEffect, useCallback, useRef } = React;

/* Carga Stripe.js (v3) una sola vez. */
function loadStripeJs() {
  return new Promise(function(resolve, reject) {
    if (window.Stripe) return resolve(window.Stripe)
    var ex = document.getElementById('stripe-js')
    if (ex) { ex.addEventListener('load', function() { resolve(window.Stripe) }); ex.addEventListener('error', function() { reject(new Error('No se pudo cargar Stripe.js')) }); return }
    var s = document.createElement('script')
    s.id = 'stripe-js'; s.src = 'https://js.stripe.com/v3/'
    s.onload = function() { resolve(window.Stripe) }
    s.onerror = function() { reject(new Error('No se pudo cargar Stripe.js')) }
    document.head.appendChild(s)
  })
}

/* Modal de pago embebido (Stripe Elements / PaymentElement). */
function StripePaymentModal({ publishableKey, clientSecret, planName, onClose, onSuccess }) {
  const elRef = useRef(null)
  const stripeRef = useRef(null)
  const elementsRef = useRef(null)
  const [ready, setReady] = useState(false)
  const [error, setError] = useState(null)
  const [paying, setPaying] = useState(false)

  useEffect(function() {
    var mounted = true
    loadStripeJs().then(function(Stripe) {
      if (!mounted) return
      var stripe = Stripe(publishableKey)
      var elements = stripe.elements({ clientSecret: clientSecret, appearance: { theme: 'stripe' } })
      var pe = elements.create('payment')
      pe.mount(elRef.current)
      pe.on('ready', function() { if (mounted) setReady(true) })
      stripeRef.current = stripe
      elementsRef.current = elements
    }).catch(function(e) { if (mounted) setError(e.message) })
    return function() { mounted = false }
  }, [])

  function pay() {
    if (!stripeRef.current || !elementsRef.current) return
    setPaying(true); setError(null)
    stripeRef.current.confirmPayment({
      elements: elementsRef.current,
      // Solo redirige si la tarjeta exige 3DS; al volver, la app detecta redirect_status
      confirmParams: { return_url: window.location.origin + '/?billing=success' },
      redirect: 'if_required',
    })
      .then(function(res) {
        if (res.error) { setError(res.error.message || 'No se pudo procesar el pago'); setPaying(false); return }
        var pi = res.paymentIntent
        if (pi && (pi.status === 'succeeded' || pi.status === 'processing')) { onSuccess() }
        else { setError('El pago no se completó'); setPaying(false) }
      })
      .catch(function(e) { setError(e.message); setPaying(false) })
  }

  return ReactDOM.createPortal(
    <div className="fixed inset-0 z-50 grid place-items-center bg-black/40 backdrop-blur-sm p-4" onClick={onClose}>
      <Card className="p-6 max-w-md w-full anim-in" onClick={function(e) { e.stopPropagation() }}>
        <div className="flex items-center justify-between mb-4">
          <h3 className="text-[18px] font-extrabold">Suscribirte a {planName}</h3>
          <button onClick={onClose} className="grid place-items-center h-8 w-8 rounded-md hover:bg-accent"><Icon name="x" size={18} /></button>
        </div>
        <div ref={elRef} className="min-h-[100px]" />
        {!ready && !error && <div className="text-[13px] text-muted-foreground mt-2 flex items-center gap-2"><Icon name="refresh" size={14} className="spin" />Cargando formulario de pago…</div>}
        {error && <div className="text-[12.5px] text-danger mt-3">{error}</div>}
        <Button className="w-full mt-4" loading={paying} disabled={!ready || paying} onClick={pay}>Pagar y activar</Button>
        <div className="text-[11px] text-muted-foreground mt-3 text-center">Pago seguro procesado por Stripe · tu tarjeta no toca nuestros servidores.</div>
      </Card>
    </div>,
    document.body
  )
}

/* ============ COLA / ESTADO (circuit breaker) ============ */
function ColaEstado() {
  const { data, loading, reload } = useApi(function() { return API.queue() }, [])
  var q = data && data.queue ? data.queue : {}
  var cb = data && data.circuitBreaker ? data.circuitBreaker : {}

  var cbMap = {
    CLOSED:    { tone: 'success', label: 'Operativo',    desc: 'Los envíos a DGII funcionan normalmente' },
    OPEN:      { tone: 'danger',  label: 'Pausado',      desc: 'DGII no responde — los envíos están en cola y se reintentan automáticamente' },
    HALF_OPEN: { tone: 'warning', label: 'Reintentando', desc: 'Probando si DGII se recuperó' },
  }
  var cbInfo = cbMap[cb.state] || cbMap.CLOSED

  var cards = [
    { label: 'Pendientes', value: q.pending,   tone: 'warning', icon: 'clock' },
    { label: 'Con error',  value: q.error,     tone: 'danger',  icon: 'alert' },
    { label: 'Enviadas',   value: q.submitted, tone: 'primary', icon: 'arrowRight' },
    { label: 'Aceptadas',  value: q.accepted,  tone: 'success', icon: 'checkCircle' },
    { label: 'Rechazadas', value: q.rejected,  tone: 'danger',  icon: 'xCircle' },
  ]

  return (
    <div className="anim-in">
      <PageHeader title="Cola y estado de DGII" desc="Estado de los envíos y conexión con la DGII (últimos 7 días)">
        <Button variant="secondary" icon="refresh" onClick={reload}>Actualizar</Button>
      </PageHeader>

      <Card className={cn('p-5 mb-5 border-2', cbInfo.tone === 'success' ? 'border-success/30' : cbInfo.tone === 'danger' ? 'border-danger/30' : 'border-warning/30')}>
        <div className="flex items-center gap-4">
          <div className={cn('grid place-items-center h-12 w-12 rounded-full',
            cbInfo.tone === 'success' ? 'bg-success-bg text-success' : cbInfo.tone === 'danger' ? 'bg-danger-bg text-danger' : 'bg-warning-bg text-warning')}>
            <span className="relative flex h-3 w-3">
              <span className={cn('absolute inline-flex h-full w-full rounded-full opacity-60', cbInfo.tone === 'success' ? 'bg-success pulse-dot' : cbInfo.tone === 'danger' ? 'bg-danger' : 'bg-warning pulse-dot')} />
              <span className={cn('relative inline-flex h-3 w-3 rounded-full', cbInfo.tone === 'success' ? 'bg-success' : cbInfo.tone === 'danger' ? 'bg-danger' : 'bg-warning')} />
            </span>
          </div>
          <div className="flex-1">
            <div className="flex items-center gap-2">
              <span className="text-[16px] font-bold">Conexión DGII: {cbInfo.label}</span>
              <Badge tone={cbInfo.tone}>{cb.state || 'CLOSED'}</Badge>
            </div>
            <div className="text-[13px] text-muted-foreground mt-0.5">{cbInfo.desc}</div>
            {cb.failures > 0 && <div className="text-[12px] text-muted-foreground mt-1">Fallos consecutivos: {cb.failures}{cb.lastFailure ? ' · ' + cb.lastFailure : ''}</div>}
          </div>
        </div>
      </Card>

      <div className="grid grid-cols-2 lg:grid-cols-5 gap-4">
        {cards.map(function(c) {
          var tb = { primary:'bg-primary-soft text-primary', success:'bg-success-bg text-success', warning:'bg-warning-bg text-warning', danger:'bg-danger-bg text-danger' }[c.tone]
          return (
            <Card key={c.label} className="p-5" hover>
              <div className={cn('grid place-items-center h-9 w-9 rounded-[var(--radius)] mb-3', tb)}><Icon name={c.icon} size={17} /></div>
              {loading ? <div className="h-7 w-12 bg-muted animate-pulse rounded" /> : <div className="text-[26px] font-extrabold tabnum">{c.value != null ? c.value : 0}</div>}
              <div className="text-[12.5px] text-muted-foreground mt-1">{c.label}</div>
            </Card>
          )
        })}
      </div>

      <Card className="p-5 mt-5">
        <div className="flex items-start gap-3">
          <Icon name="zap" size={18} className="text-primary shrink-0 mt-0.5" />
          <div className="text-[13px] text-muted-foreground">
            El <strong className="text-foreground">circuit breaker</strong> protege tus facturas: si la DGII deja de responder, los envíos se pausan y quedan en cola
            (sin perder el NCF). El reintento automático corre cada 5 minutos y reanuda en cuanto la DGII se recupera.
          </div>
        </div>
      </Card>
    </div>
  )
}

/* ============ CONSULTAR RNC ============ */
function ConsultarRNC() {
  const [query, setQuery] = useState('')
  const [suggestions, setSuggestions] = useState([])
  const [result, setResult] = useState(null)
  const [loading, setLoading] = useState(false)
  const { data: statsData } = useApi(function() { return API.rncStats() }, [])
  const toast = useToast()

  // Autocomplete on type (debounced via simple guard)
  useEffect(function() {
    if (query.trim().length < 3) { setSuggestions([]); return }
    var active = true
    var t = setTimeout(function() {
      API.rncAutocomplete(query).then(function(d) { if (active) setSuggestions(d.results || []) }).catch(function(){})
    }, 250)
    return function() { active = false; clearTimeout(t) }
  }, [query])

  function lookup(rnc) {
    setLoading(true); setResult(null); setSuggestions([])
    API.rncLookup(rnc).then(function(d) { setResult(d) })
      .catch(function(e) { toast({ title: 'RNC no encontrado', desc: e.message, tone: 'danger' }); setResult(null) })
      .finally(function() { setLoading(false) })
  }

  return (
    <div className="anim-in">
      <PageHeader title="Consultar RNC" desc={'Validación contra el registro local' + (statsData ? ' (' + statsData.total.toLocaleString('es-DO') + ' contribuyentes)' : '') + ' y DGII en vivo'} />
      <div className="max-w-2xl">
        <Card className="p-5">
          <div className="relative">
            <Input icon="search" placeholder="Escribe un RNC o nombre de empresa…" value={query}
              onChange={function(e) { setQuery(e.target.value) }}
              onKeyDown={function(e) { if (e.key === 'Enter') lookup(query.replace(/\D/g,'')) }} />
            {suggestions.length > 0 && (
              <div className="absolute z-10 left-0 right-0 mt-1 bg-card border border-border rounded-[var(--radius)] shadow-lg overflow-hidden">
                {suggestions.map(function(s) {
                  return (
                    <button key={s.rnc} onClick={function() { setQuery(s.rnc); lookup(s.rnc) }}
                      className="w-full text-left px-4 py-2.5 hover:bg-accent transition-colors border-b border-border/50 last:border-0">
                      <div className="text-[13px] font-semibold">{s.razonSocial}</div>
                      <div className="text-[11px] text-muted-foreground font-mono">{s.rnc}</div>
                    </button>
                  )
                })}
              </div>
            )}
          </div>
          <Button className="mt-3" icon="search" loading={loading} onClick={function() { lookup(query.replace(/\D/g,'')) }}>Consultar</Button>
        </Card>

        {result && result.success && (
          <Card className="p-5 mt-4 anim-in">
            <div className="flex items-center justify-between mb-3">
              <Badge tone={result.estado === 'ACTIVO' ? 'success' : 'warning'} icon="check">{result.estado || 'ACTIVO'}</Badge>
              <Badge tone="muted">{result.source === 'local' ? 'Registro local' : 'DGII en vivo'}</Badge>
            </div>
            <div className="grid sm:grid-cols-2 gap-x-6 gap-y-3 text-[13.5px]">
              <div><span className="text-muted-foreground">RNC</span><div className="font-mono font-semibold">{result.rnc}</div></div>
              <div><span className="text-muted-foreground">Razón social</span><div className="font-semibold">{result.razonSocial}</div></div>
              {result.nombreComercial && <div><span className="text-muted-foreground">Nombre comercial</span><div className="font-semibold">{result.nombreComercial}</div></div>}
              {result.actividad && <div><span className="text-muted-foreground">Actividad</span><div className="font-semibold">{result.actividad}</div></div>}
            </div>
          </Card>
        )}
      </div>
    </div>
  )
}

/* ============ CLASIFICADOR FISCAL ============ */
function Clasificador() {
  const [text, setText] = useState('Arroz Premium 5 lbs\nServicio de consultoría legal\nLaptop Dell Inspiron\nConsulta médica general\nMatrícula escolar')
  const [results, setResults] = useState(null)
  const [loading, setLoading] = useState(false)
  const toast = useToast()

  function classify() {
    var lines = text.split('\n').map(function(l) { return l.trim() }).filter(Boolean)
    if (lines.length === 0) return
    setLoading(true)
    API.classify(lines).then(function(d) { setResults(d.results.map(function(r, i) { return Object.assign({}, r, { descripcion: lines[i] }) })) })
      .catch(function(e) { toast({ title: 'Error', desc: e.message, tone: 'danger' }) })
      .finally(function() { setLoading(false) })
  }

  return (
    <div className="anim-in">
      <PageHeader title="Clasificador fiscal" desc="Sugiere el tratamiento de ITBIS y tipo (bien/servicio) para cada item" />
      <div className="grid lg:grid-cols-2 gap-5">
        <Card className="p-5">
          <SectionTitle>Descripciones (una por línea)</SectionTitle>
          <textarea value={text} onChange={function(e) { setText(e.target.value) }} rows={10}
            className="w-full rounded-[var(--radius)] bg-card border border-input p-3 text-[13px] font-mono focus:outline-none focus:ring-2 focus:ring-ring/50 resize-none" />
          <Button className="mt-3 w-full" icon="zap" loading={loading} onClick={classify}>Clasificar</Button>
          <p className="text-[11.5px] text-muted-foreground mt-3 flex items-start gap-1.5"><Icon name="shield" size={13} className="mt-0.5 shrink-0" />Motor de reglas basado en categorías de exención de la DGII. No sustituye el criterio del contador.</p>
        </Card>

        <Card className="overflow-hidden">
          <div className="px-5 py-3.5 border-b border-border text-[13.5px] font-bold">Resultados</div>
          {!results ? (
            <div className="py-16 text-center text-muted-foreground"><Icon name="zap" size={28} className="mx-auto mb-3 opacity-30" /><div className="text-[13px]">Clasifica para ver resultados</div></div>
          ) : (
            <div className="divide-y divide-border">
              {results.map(function(r, i) {
                return (
                  <div key={i} className="px-5 py-3">
                    <div className="flex items-center justify-between gap-3">
                      <div className="font-semibold text-[13px] truncate flex-1">{r.descripcion}</div>
                      {r.exento ? <Badge tone="success">Exento</Badge> : <Badge tone="primary">ITBIS {r.tasaITBIS}%</Badge>}
                    </div>
                    <div className="flex items-center gap-2 mt-1 text-[11.5px] text-muted-foreground">
                      <Badge tone="muted">{r.indicadorBienServicio === '1' ? 'Bien' : 'Servicio'}</Badge>
                      <span>{r.categoria}</span>
                      <span className="ml-auto tabnum">{Math.round(r.confianza * 100)}% conf.</span>
                    </div>
                  </div>
                )
              })}
            </div>
          )}
        </Card>
      </div>
    </div>
  )
}

/* ============ CERTIFICACIÓN DGII ============ */
/* Etiqueta corta por tipo de e-CF */
var ECF_TYPE_LABEL = {
  '31': 'Crédito Fiscal', '32': 'Consumo', '33': 'Nota Débito', '34': 'Nota Crédito',
  '41': 'Compras', '43': 'Gastos Menores', '44': 'Régimen Especial', '45': 'Gubernamental',
  '46': 'Exportación', '47': 'Pagos Exterior',
}
var TESTSET_STATUS = {
  PENDING:  { tone: 'muted',   label: 'Pendiente' },
  SENT:     { tone: 'primary', label: 'Enviado' },
  ACCEPTED: { tone: 'success', label: 'Aceptado' },
  REJECTED: { tone: 'danger',  label: 'Rechazado' },
  ERROR:    { tone: 'danger',  label: 'Error' },
}

/* Subir el Excel del portal DGII y enviar cada comprobante de prueba uno por uno.
   kind='datos' (Paso 2: hojas ECF+RFCE) | kind='acecf' (Paso 3: Aprobaciones Comerciales). */
function CertTestSet(props) {
  var kind = (props && props.kind) || 'datos'
  var isAcecf = kind === 'acecf'
  const toast = useToast()
  const fileRef = useRef(null)
  const [all, setAll] = useState(null)          // null = cargando
  const [uploading, setUploading] = useState(false)
  const [sendingId, setSendingId] = useState(null)

  function belongs(x) { return isAcecf ? x.sheet === 'ACECF' : (x.sheet === 'ECF' || x.sheet === 'RFCE') }
  var items = all === null ? null : all.filter(belongs)

  function load() {
    API.certTestset().then(function(r) { setAll(r.items || []) }).catch(function() { setAll([]) })
  }
  useEffect(function() { load() }, [])

  function onFile(e) {
    var f = e.target.files && e.target.files[0]; if (!f) return
    setUploading(true)
    var up = isAcecf ? API.certAcecfUpload(f) : API.certTestsetUpload(f)
    up.then(function(r) {
      toast({ title: (isAcecf ? 'Set de aprobaciones cargado' : 'Set de datos cargado'), desc: r.count + (isAcecf ? ' aprobaciones listas' : ' comprobantes') + ' para enviar' })
      setAll(r.items || [])
    }).catch(function(err) {
      toast({ title: 'No se pudo cargar el Excel', desc: err.message, tone: 'danger' })
    }).finally(function() { setUploading(false); if (fileRef.current) fileRef.current.value = '' })
  }

  function send(it) {
    setSendingId(it.id)
    API.certTestsetSend(it.id).then(function(r) {
      var st = (TESTSET_STATUS[r.item.status] || {}).label || r.item.status
      toast({ title: it.encf + ' enviado', desc: 'DGII: ' + st })
      setAll(function(prev) { return (prev || []).map(function(x) { return x.id === it.id ? r.item : x }) })
    }).catch(function(err) {
      toast({ title: 'Error al enviar ' + it.encf, desc: err.message, tone: 'danger' })
      load()
    }).finally(function() { setSendingId(null) })
  }

  function sendAll() {
    setSendingId('ALL')
    API.certTestsetSendAll(kind).then(function(r) {
      toast({ title: 'Envío masivo completado', desc: r.sent + ' enviados' + (r.failed ? ', ' + r.failed + ' con error' : '') })
      setAll(r.items || [])
    }).catch(function(err) {
      toast({ title: 'Error en envío masivo', desc: err.message, tone: 'danger' })
      load()
    }).finally(function() { setSendingId(null) })
  }

  var sent = (items || []).filter(function(x) { return x.status === 'ACCEPTED' || x.status === 'SENT' }).length

  return (
    <div className="mt-4">
      <div className="flex items-center justify-between gap-3 mb-2">
        <div className="text-[11px] font-bold uppercase tracking-wide text-muted-foreground">{isAcecf ? 'Aprobaciones comerciales — enviar' : 'Set de datos — enviar comprobantes'}</div>
        <div className="flex items-center gap-2">
          {items && items.length > 0 && <span className="text-[11.5px] text-muted-foreground tabnum">{sent}/{items.length} enviados</span>}
          {items && items.some(function(x){ return x.status === 'PENDING' || x.status === 'ERROR' }) && (
            <Button size="sm" variant="primary" icon="arrowRight" loading={sendingId === 'ALL'} disabled={!!sendingId}
              onClick={sendAll}>Enviar todos</Button>
          )}
          <input ref={fileRef} type="file" accept=".xlsx,.xls" className="hidden" onChange={onFile} />
          <Button size="sm" variant="secondary" icon="upload" loading={uploading}
            onClick={function() { if (fileRef.current) fileRef.current.click() }}>
            {items && items.length > 0 ? 'Reemplazar Excel' : 'Subir Excel del set'}
          </Button>
        </div>
      </div>

      {items === null ? (
        <div className="space-y-2">{[0,1,2].map(function(i){return <div key={i} className="h-9 bg-muted animate-pulse rounded" />})}</div>
      ) : items.length === 0 ? (
        <div className="rounded-[var(--radius)] border border-dashed border-border p-4 text-center text-[12.5px] text-muted-foreground">
          {isAcecf
            ? <span>Sube el Excel “Set de Aprobaciones Comerciales” del Portal de Certificación (botón “Descargar aprobaciones comerciales”). Aparecerán aquí con un botón <b>Enviar</b> para cada una.</span>
            : <span>Sube el Excel del set de datos descargado del Portal de Certificación de la DGII (botón “Descargar comprobantes”). Aparecerán aquí los comprobantes con un botón <b>Enviar</b> para cada uno.</span>}
        </div>
      ) : (
        <div className="rounded-[var(--radius)] border border-border overflow-hidden">
          <div className="divide-y divide-border">
            {items.map(function(it) {
              var st = TESTSET_STATUS[it.status] || { tone: 'muted', label: it.status }
              var isRfce = it.sheet === 'RFCE'
              var done = it.status === 'ACCEPTED' || it.status === 'SENT'
              return (
                <div key={it.id} className="flex items-center gap-3 px-3 py-2 hover:bg-muted/30">
                  <code className="font-mono text-[11.5px] w-[150px] shrink-0 truncate">{it.encf}</code>
                  <span className="text-[12px] text-muted-foreground w-[120px] shrink-0 truncate">
                    {it.sheet === 'ACECF' ? (it.estado === '2' ? 'Rechazo' : 'Aprobación')
                      : isRfce ? 'Resumen Consumo' : (ECF_TYPE_LABEL[it.ecfType] || it.ecfType)}
                  </span>
                  <span className="text-[12px] font-mono tabnum text-muted-foreground flex-1 text-right">
                    {it.montoTotal != null ? 'RD$ ' + Number(it.montoTotal).toLocaleString('es-DO', { minimumFractionDigits: 2 }) : ''}
                  </span>
                  <Badge tone={st.tone}>{st.label}</Badge>
                  <div className="w-[96px] shrink-0 flex justify-end">
                    {done ? (
                      <Icon name="check" size={16} className="text-success" />
                    ) : (
                      <Button size="sm" variant="primary" icon="arrowRight"
                        loading={sendingId === it.id} disabled={!!sendingId}
                        onClick={function() { send(it) }}>
                        {it.status === 'ERROR' || it.status === 'REJECTED' ? 'Reintentar' : 'Enviar'}
                      </Button>
                    )}
                  </div>
                </div>
              )
            })}
          </div>
        </div>
      )}
      {items && items.some(function(x){ return x.status === 'REJECTED' || x.status === 'ERROR' }) && (
        <div className="mt-2 space-y-1">
          {items.filter(function(x){ return (x.status === 'REJECTED' || x.status === 'ERROR') && x.dgiiMessage }).map(function(x){
            return <div key={x.id} className="text-[11.5px] text-danger"><b>{x.encf}:</b> {x.dgiiMessage}</div>
          })}
        </div>
      )}
    </div>
  )
}

/* Representación Impresa (Paso 5): genera/descarga el PDF (con QR) de cada comprobante
   emitido, para subirlo al portal DGII. La DGII pide una representación por tipo. */
function CertRepresentations() {
  const toast = useToast()
  const [items, setItems] = useState(null)
  const [busyId, setBusyId] = useState(null)

  useEffect(function() {
    API.certTestset().then(function(r) {
      var emitted = (r.items || []).filter(function(x) {
        return x.sheet === 'ECF' && (x.status === 'SENT' || x.status === 'ACCEPTED')
      })
      setItems(emitted)
    }).catch(function() { setItems([]) })
  }, [])

  function typeLabel(it) {
    if (it.ecfType === '32') return it.sendOrder === 4 ? 'Consumo < 250k' : 'Consumo ≥ 250k'
    return ECF_TYPE_LABEL[it.ecfType] || it.ecfType
  }

  function downloadPdf(it) {
    return API.certInvoicePdf(it.encf).then(function(blob) { downloadBlob(blob, it.encf + '.pdf') })
  }
  function onPdf(it) {
    setBusyId(it.id)
    downloadPdf(it).catch(function(err) { toast({ title: 'No se pudo generar el PDF', desc: err.message, tone: 'danger' }) })
      .finally(function() { setBusyId(null) })
  }
  function onXml(it) {
    setBusyId(it.id + ':xml')
    API.certTestsetXml(it.id).then(function(r) { downloadBlob(r.blob, r.filename) })
      .catch(function(err) { toast({ title: 'No se pudo descargar el XML', desc: err.message, tone: 'danger' }) })
      .finally(function() { setBusyId(null) })
  }
  function downloadAll() {
    setBusyId('ALL')
    var seq = (items || []).reduce(function(pchain, it) {
      return pchain.then(function() { return downloadPdf(it).catch(function(){}) })
    }, Promise.resolve())
    seq.then(function() { toast({ title: 'PDFs descargados', desc: items.length + ' representaciones' }) })
      .finally(function() { setBusyId(null) })
  }
  function toggleUploaded(it) {
    var next = !it.uploadedToPortal
    API.certTestsetUploaded(it.id, next).then(function(r) {
      setItems(function(prev) { return (prev || []).map(function(x) { return x.id === it.id ? r.item : x }) })
    }).catch(function(err) { toast({ title: 'Error', desc: err.message, tone: 'danger' }) })
  }

  var uploaded = (items || []).filter(function(x){ return x.uploadedToPortal }).length

  return (
    <div className="mt-3">
      <div className="flex items-center justify-between gap-3 mb-1.5">
        <div className="text-[11px] font-bold uppercase tracking-wide text-muted-foreground">Representaciones impresas (PDF con QR)</div>
        {items && items.length > 0 && (
          <div className="flex items-center gap-2">
            <span className="text-[11.5px] text-muted-foreground tabnum">{uploaded}/{items.length} subidos</span>
            <Button size="sm" variant="secondary" icon="download" loading={busyId === 'ALL'} disabled={!!busyId} onClick={downloadAll}>Descargar todos</Button>
          </div>
        )}
      </div>
      {items === null ? (
        <div className="space-y-2">{[0,1,2].map(function(i){return <div key={i} className="h-9 bg-muted animate-pulse rounded" />})}</div>
      ) : items.length === 0 ? (
        <div className="rounded-[var(--radius)] border border-dashed border-border p-4 text-center text-[12.5px] text-muted-foreground">
          Primero envía los comprobantes en el paso <b>Pruebas de Datos e-CF</b>. Luego aquí podrás descargar el PDF de cada uno y marcar cuáles subiste al portal (la DGII pide una representación por tipo).
        </div>
      ) : (
        <div className="rounded-[var(--radius)] border border-border overflow-hidden">
          <div className="divide-y divide-border">
            {items.map(function(it) {
              var isConsumoMenor = it.ecfType === '32' && it.sendOrder === 4
              return (
                <div key={it.id} className="flex items-center gap-3 px-3 py-2 hover:bg-muted/30">
                  <label className="flex items-center gap-2 shrink-0 cursor-pointer" title="Marcar como subido al portal DGII">
                    <input type="checkbox" checked={it.uploadedToPortal} onChange={function() { toggleUploaded(it) }} />
                  </label>
                  <code className="font-mono text-[11.5px] w-[140px] shrink-0 truncate">{it.encf}</code>
                  <span className="text-[12px] text-muted-foreground flex-1 truncate">{typeLabel(it)}</span>
                  {isConsumoMenor && (
                    <Button size="sm" variant="ghost" icon="download" loading={busyId === it.id + ':xml'} disabled={!!busyId}
                      onClick={function() { onXml(it) }}>XML</Button>
                  )}
                  <Button size="sm" variant="secondary" icon="file"
                    loading={busyId === it.id} disabled={!!busyId}
                    onClick={function() { onPdf(it) }}>
                    PDF
                  </Button>
                </div>
              )
            })}
          </div>
        </div>
      )}
    </div>
  )
}

/* Descarga un blob como archivo. */
function downloadBlob(blob, filename) {
  var url = URL.createObjectURL(blob)
  var a = document.createElement('a'); a.href = url; a.download = filename
  document.body.appendChild(a); a.click(); a.remove()
  setTimeout(function() { URL.revokeObjectURL(url) }, 2000)
}

/* Aprobación del proveedor (Paso 4): el cliente solicita validación; el operador la
   aprueba/rechaza desde el panel admin. */
function CertProviderApproval(props) {
  const toast = useToast()
  const pa = props.approval || { status: 'NONE' }
  const [busy, setBusy] = useState(false)
  function request() {
    setBusy(true)
    API.certRequestApproval().then(function() {
      toast({ title: 'Solicitud enviada', desc: 'El equipo validará tu postulación.' })
      if (props.onChange) props.onChange()
    }).catch(function(err) { toast({ title: 'Error', desc: err.message, tone: 'danger' }) })
      .finally(function() { setBusy(false) })
  }
  var map = {
    NONE:      { tone: 'muted',   label: 'No solicitada', btn: 'Solicitar aprobación' },
    REQUESTED: { tone: 'warning', label: 'En revisión',    btn: 'Reenviar solicitud' },
    APPROVED:  { tone: 'success', label: 'Aprobada',       btn: null },
    REJECTED:  { tone: 'danger',  label: 'Rechazada',      btn: 'Solicitar de nuevo' },
  }
  var m = map[pa.status] || map.NONE
  return (
    <Card className="p-4 mb-5">
      <div className="flex items-center justify-between gap-3">
        <div className="min-w-0">
          <div className="flex items-center gap-2">
            <span className="text-[13.5px] font-bold">Aprobación del proveedor</span>
            <Badge tone={m.tone}>{m.label}</Badge>
          </div>
          <div className="text-[12px] text-muted-foreground mt-0.5">
            {pa.status === 'APPROVED' ? 'Tu postulación fue validada. Puedes continuar con la certificación.'
              : pa.status === 'REJECTED' ? ('Rechazada' + (pa.notes ? ': ' + pa.notes : '') + '. Corrige y vuelve a solicitar.')
              : pa.status === 'REQUESTED' ? 'Tu solicitud está en revisión por el equipo.'
              : 'Solicita la validación de tu postulación antes de continuar (Paso 4).'}
          </div>
        </div>
        {m.btn && <Button size="sm" variant={pa.status === 'REJECTED' ? 'primary' : 'secondary'} icon="bell" loading={busy} onClick={request}>{m.btn}</Button>}
      </div>
    </Card>
  )
}

/* Datos para registrar la postulación en el portal DGII (Paso 3). */
function CertPostulationData() {
  const toast = useToast()
  const { data } = useApi(function() { return API.certPostulationInfo() }, [])
  if (!data) return null
  function Row(props) {
    return (
      <div className="flex items-center gap-2 mt-1">
        <span className="text-[11.5px] text-muted-foreground w-[150px] shrink-0">{props.label}</span>
        <code className="flex-1 min-w-0 truncate font-mono text-[11.5px] bg-muted rounded px-2.5 py-1.5">{props.value || '—'}</code>
        {props.value && <Button size="sm" variant="secondary" icon="copy" onClick={function() { if (navigator.clipboard) navigator.clipboard.writeText(props.value); toast({ title: 'Copiado' }) }}>Copiar</Button>}
      </div>
    )
  }
  return (
    <div className="mt-3 rounded-[var(--radius)] border border-border p-3.5">
      <div className="text-[12.5px] font-bold mb-1">Datos para tu postulación en el portal DGII</div>
      <div className="text-[11.5px] text-muted-foreground mb-2">Copia estos valores en el formulario “Datos del software a utilizar” del portal de certificación.</div>
      <div className="text-[11px] font-bold uppercase tracking-wide text-muted-foreground mt-2">Software</div>
      <Row label="Tipo de software" value={data.software.tipo} />
      <Row label="Nombre del software" value={data.software.nombre} />
      <Row label="Versión" value={data.software.version} />
      <div className="text-[11px] font-bold uppercase tracking-wide text-muted-foreground mt-3">URLs (con tu RNC)</div>
      <Row label="URL de recepción" value={data.urls.recepcion} />
      <Row label="URL aprobación comercial" value={data.urls.aprobacion} />
      <Row label="URL de autenticación" value={data.urls.autenticacion} />
      {(data.proveedor.rnc || data.proveedor.nombre) && (
        <div>
          <div className="text-[11px] font-bold uppercase tracking-wide text-muted-foreground mt-3">Proveedor</div>
          <Row label="RNC / Cédula" value={data.proveedor.rnc} />
          <Row label="Razón social" value={data.proveedor.nombre} />
          <Row label="Nombre comercial" value={data.proveedor.nombreComercial} />
        </div>
      )}
    </div>
  )
}

/* Herramienta de firma: sube el XML del portal DGII (declaración jurada / postulación),
   lo firma con el certificado del contribuyente y descarga el XML firmado. */
function CertSignTool() {
  const toast = useToast()
  const fileRef = useRef(null)
  const [signing, setSigning] = useState(false)
  const [lastFile, setLastFile] = useState(null)

  function onFile(e) {
    var f = e.target.files && e.target.files[0]; if (!f) return
    setSigning(true)
    API.certSignXml(f).then(function(r) {
      var url = URL.createObjectURL(r.blob)
      var a = document.createElement('a')
      a.href = url; a.download = r.filename
      document.body.appendChild(a); a.click(); a.remove()
      setTimeout(function() { URL.revokeObjectURL(url) }, 2000)
      setLastFile(r.filename)
      toast({ title: 'XML firmado', desc: 'Se descargó ' + r.filename + '. Súbelo a la DGII con “Enviar archivo”.' })
    }).catch(function(err) {
      toast({ title: 'No se pudo firmar', desc: err.message, tone: 'danger' })
    }).finally(function() { setSigning(false); if (fileRef.current) fileRef.current.value = '' })
  }

  return (
    <div className="mt-3 rounded-[var(--radius)] border border-border p-3.5">
      <div className="flex items-center justify-between gap-3">
        <div className="min-w-0">
          <div className="text-[12.5px] font-bold">Firmar archivo XML de la DGII</div>
          <div className="text-[11.5px] text-muted-foreground mt-0.5">
            Sube el XML que generaste en el portal (“Generar archivo”); lo firmamos con tu certificado y descargas el XML firmado para “Enviar archivo”.
          </div>
          {lastFile && <div className="text-[11px] text-success mt-1">✓ Último firmado: {lastFile}</div>}
        </div>
        <input ref={fileRef} type="file" accept=".xml,text/xml,application/xml" className="hidden" onChange={onFile} />
        <Button size="sm" variant="primary" icon="shield" loading={signing}
          onClick={function() { if (fileRef.current) fileRef.current.click() }}>
          Firmar XML
        </Button>
      </div>
    </div>
  )
}

function CertCounter({ label, done, required }) {
  var pct = required ? Math.min(100, Math.round(done / required * 100)) : 0
  var ok = done >= required
  return (
    <div className="flex items-center gap-3 py-1.5">
      <div className="flex-1 min-w-0">
        <div className="flex items-center justify-between text-[12.5px]">
          <span className={cn(ok ? 'text-foreground font-semibold' : 'text-muted-foreground')}>{label}</span>
          <span className={cn('font-mono tabnum', ok ? 'text-success font-bold' : 'text-muted-foreground')}>{done}/{required}</span>
        </div>
        <div className="h-1.5 mt-1 rounded-full bg-muted overflow-hidden"><div className="h-full rounded-full" style={{ width: pct + '%', background: ok ? 'var(--success)' : 'var(--primary)' }} /></div>
      </div>
      {ok && <Icon name="check" size={14} className="text-success shrink-0" />}
    </div>
  )
}

function Certificacion() {
  const { data, loading, reload } = useApi(function() { return API.certification() }, [])
  const toast = useToast()
  const [openStep, setOpenStep] = useState(null)
  var steps = data && data.steps ? data.steps : []
  var progress = data ? data.progress : 0
  var status = data ? data.status : 'IN_PROGRESS'
  var simulation = data && data.simulation ? data.simulation : []
  var dataChk = data && data.data ? data.data : null
  var approvals = data && data.approvals ? data.approvals : null
  var emissionOrder = data && data.emissionOrder ? data.emissionOrder : []

  function toggle(step, e) {
    if (e) e.stopPropagation()
    if (step.auto) return  // los pasos auto-detectados no se marcan manualmente
    API.certStep(step.key, !step.done).then(function() { reload() })
      .catch(function(err) { toast({ title: 'Error', desc: err.message, tone: 'danger' }) })
  }

  function renderChecklist(s) {
    if (s.urls) {
      var origin = window.location.origin
      var rnc = (data && data.rnc) ? data.rnc : ''
      var base = origin + (rnc ? '/' + rnc : '') + '/fe'
      var urls = [
        ['Recepción', base + '/recepcion/api/ecf'],
        ['Aprobación Comercial', base + '/aprobacioncomercial/api/ecf'],
        ['Autenticación · semilla', base + '/autenticacion/api/semilla'],
        ['Autenticación · validación', base + '/autenticacion/api/validacioncertificado'],
      ]
      return (
        <div className="mt-3">
          <div className="text-[11px] font-bold uppercase tracking-wide text-muted-foreground mb-1.5">Tus URLs a registrar en la DGII</div>
          <div className="space-y-2">
            {urls.map(function(u) {
              return (
                <div key={u[0]}>
                  <div className="text-[11.5px] font-semibold text-muted-foreground">{u[0]}</div>
                  <div className="flex items-center gap-2 mt-0.5">
                    <code className="flex-1 min-w-0 truncate font-mono text-[11.5px] bg-muted rounded px-2.5 py-1.5">{u[1]}</code>
                    <Button size="sm" variant="secondary" icon="copy" onClick={function() { if (navigator.clipboard) navigator.clipboard.writeText(u[1]); toast({ title: 'URL copiada' }) }}>Copiar</Button>
                  </div>
                </div>
              )
            })}
          </div>
          <div className="text-[11.5px] text-muted-foreground mt-2">Estas URLs son de la plataforma; el segmento <code className="font-mono">/{rnc || 'TU-RNC'}</code> es tu buzón dedicado (la plataforma enruta por tu RNC). No alojas nada.</div>
        </div>
      )
    }
    if (s.checklist === 'simulacion') {
      return (
        <div className="mt-3">
          <div className="text-[11px] font-bold uppercase tracking-wide text-muted-foreground mb-1.5">Comprobantes requeridos</div>
          {simulation.map(function(x) { return <CertCounter key={x.tipo} label={x.label} done={x.done} required={x.required} /> })}
          {emissionOrder.length > 0 && (
            <div className="mt-3 rounded-[var(--radius)] bg-muted/50 p-3">
              <div className="text-[11px] font-bold uppercase tracking-wide text-muted-foreground mb-1">Orden de emisión</div>
              {emissionOrder.map(function(o, i) { return <div key={i} className="text-[12px] text-muted-foreground">{o}</div> })}
            </div>
          )}
        </div>
      )
    }
    if (s.checklist === 'datos' && dataChk) {
      return (
        <div className="mt-3">
          <div className="text-[11px] font-bold uppercase tracking-wide text-muted-foreground mb-1.5">Set de datos</div>
          <CertCounter label="e-CF aceptados" done={dataChk.ecf.done} required={dataChk.ecf.required} />
          <CertCounter label="Resúmenes de consumo aceptados" done={dataChk.resumenes.done} required={dataChk.resumenes.required} />
          <CertTestSet />
        </div>
      )
    }
    if (s.key === 'pruebas_datos_acecf') {
      return (
        <div className="mt-3">
          <div className="text-[11px] font-bold uppercase tracking-wide text-muted-foreground mb-1.5">Aprobaciones comerciales</div>
          {approvals && <CertCounter label="Aprobaciones aceptadas" done={approvals.done} required={approvals.required} />}
          <CertTestSet kind="acecf" />
        </div>
      )
    }
    if (s.key === 'registrado') {
      return <div><CertPostulationData /><CertSignTool /></div>
    }
    if (s.key === 'declaracion_jurada') {
      return <CertSignTool />
    }
    if (s.key === 'simulacion_representacion') {
      return <CertRepresentations />
    }
    return null
  }

  // agrupar pasos por etapa preservando el orden
  var etapas = []
  steps.forEach(function(s) {
    var g = etapas.filter(function(x) { return x.etapa === s.etapa })[0]
    if (!g) { g = { etapa: s.etapa, items: [] }; etapas.push(g) }
    g.items.push(s)
  })

  return (
    <div className="anim-in">
      <PageHeader title="Certificación DGII" desc="Guía paso a paso para certificarte como Emisor Electrónico">
        {status === 'CERTIFIED' ? <Badge tone="success" icon="checkCircle">Certificado</Badge> : <Badge tone="primary">En proceso</Badge>}
      </PageHeader>

      <Card className="p-5 mb-5">
        <div className="flex items-center justify-between mb-2">
          <span className="text-[14px] font-bold">{data ? data.completed : 0} de {data ? data.total : 13} pasos completados</span>
          <span className="text-[14px] font-extrabold text-primary tabnum">{progress}%</span>
        </div>
        <Progress value={progress} tone={progress === 100 ? 'success' : 'primary'} />
      </Card>

      <CertProviderApproval approval={data ? data.providerApproval : null} onChange={reload} />

      {loading ? <Card className="p-5"><div className="space-y-3">{[0,1,2,3].map(function(i){return <div key={i} className="h-12 bg-muted animate-pulse rounded" />})}</div></Card>
      : etapas.map(function(g, gi) {
          return (
            <div key={g.etapa} className="mb-5">
              <div className="flex items-center gap-2 mb-2 px-1">
                <span className="grid place-items-center h-5 w-5 rounded-full bg-primary text-primary-foreground text-[11px] font-bold">{gi + 1}</span>
                <span className="text-[12.5px] font-extrabold uppercase tracking-wide text-muted-foreground">{g.etapa}</span>
              </div>
              <Card className="overflow-hidden">
                <div className="divide-y divide-border">
                  {g.items.map(function(s) {
                    var open = openStep === s.n
                    return (
                      <div key={s.n}>
                        <div role="button" onClick={function() { setOpenStep(open ? null : s.n) }} className="w-full flex items-center gap-4 px-5 py-3.5 hover:bg-muted/30 transition-colors cursor-pointer">
                          <span onClick={function(e) { toggle(s, e) }}
                            className={cn('grid place-items-center h-8 w-8 rounded-full shrink-0 border-2 transition-all',
                              s.done ? 'bg-success border-success text-white' : 'border-border text-muted-foreground',
                              s.auto ? 'cursor-default' : 'cursor-pointer hover:border-primary')}>
                            {s.done ? <Icon name="check" size={16} stroke={3} /> : <span className="text-[12px] font-bold tabnum">{s.n}</span>}
                          </span>
                          <div className="flex-1 min-w-0">
                            <div className="flex items-center gap-2">
                              <span className={cn('text-[13.5px] font-bold', s.done ? 'text-foreground' : 'text-muted-foreground')}>{s.title}</span>
                              <Badge tone="muted">{s.auto ? 'auto' : 'manual'}</Badge>
                            </div>
                            <div className="text-[12px] text-muted-foreground">{s.desc}</div>
                          </div>
                          <Icon name="chevronDown" size={16} className={cn('text-muted-foreground transition-transform shrink-0', open && 'rotate-180')} />
                        </div>
                        {open && (
                          <div className="px-5 pb-4 anim-in" style={{ paddingLeft: '68px' }}>
                            <ol className="space-y-1.5 text-[12.5px] text-muted-foreground" style={{ listStyle: 'decimal', paddingLeft: '16px' }}>
                              {(s.guide || []).map(function(g2, i) { return <li key={i}>{g2}</li> })}
                            </ol>
                            {renderChecklist(s)}
                            {!s.auto && (
                              <Button size="sm" variant={s.done ? 'secondary' : undefined} className="mt-3" icon={s.done ? 'check' : undefined} onClick={function() { toggle(s) }}>{s.done ? 'Marcado como hecho' : 'Marcar como hecho'}</Button>
                            )}
                          </div>
                        )}
                      </div>
                    )
                  })}
                </div>
              </Card>
            </div>
          )
        })}

      {progress === 100 && status !== 'CERTIFIED' && (
        <Card className="p-5 border-success/30">
          <div className="flex items-center justify-between">
            <div><div className="text-[14px] font-bold">Todos los pasos completados</div><div className="text-[13px] text-muted-foreground">Marca tu empresa como certificada una vez DGII apruebe la postulación.</div></div>
            <Button icon="checkCircle" onClick={function() { API.certCertify().then(function() { toast({ title: '¡Certificado!' }); reload() }) }}>Marcar certificado</Button>
          </div>
        </Card>
      )}
    </div>
  )
}

/* ============ FACTURACIÓN / PLANES (Stripe) ============ */
function Facturacion() {
  const { data, loading, reload } = useApi(function() { return API.billing() }, [])
  const toast = useToast()
  const [busy, setBusy] = useState(null)
  const [pay, setPay] = useState(null)   // { plan, planName, clientSecret }
  var plans   = data && data.plans ? data.plans : []
  var current = data ? data.plan : null
  var status  = data ? data.status : null
  var usage   = data && data.usage ? data.usage : { used: 0, included: null }
  var enabled = data ? data.enabled : false
  var pubKey  = data ? data.publishableKey : null
  var pct = usage.included ? Math.min(100, Math.round(usage.used / usage.included * 100)) : 0

  function subscribe(key) {
    var plan = plans.filter(function(p) { return p.key === key })[0]
    setBusy(key)
    if (pubKey) {
      // Stripe Elements: pago embebido sin salir del panel
      API.billingSubscribe(key).then(function(res) {
        if (res.clientSecret) { setPay({ plan: key, planName: plan ? plan.name : key, clientSecret: res.clientSecret }) }
        else { toast({ title: 'No se pudo iniciar el pago', tone: 'danger' }) }
      }).catch(function(e) { toast({ title: 'Error', desc: e.message, tone: 'danger' }) })
        .finally(function() { setBusy(null) })
    } else {
      // Fallback: Checkout alojado (redirect) si no hay publishable key
      API.billingCheckout(key).then(function(res) {
        if (res.url) { window.location.href = res.url }
        else { toast({ title: 'No se pudo iniciar el checkout', tone: 'danger' }); setBusy(null) }
      }).catch(function(e) { toast({ title: 'Error', desc: e.message, tone: 'danger' }); setBusy(null) })
    }
  }

  function onPaid() {
    setPay(null)
    toast({ title: '¡Suscripción activada!', desc: 'Tu plan se está activando.' })
    setTimeout(reload, 1500)  // dar tiempo al webhook
  }

  var manageable = data ? data.manageable : false
  function openPortal() {
    API.billingPortal().then(function(r) {
      if (r.url) { window.location.href = r.url }
      else { toast({ title: 'No se pudo abrir el portal', tone: 'danger' }) }
    }).catch(function(e) { toast({ title: 'Error', desc: e.message, tone: 'danger' }) })
  }

  // Retorno desde 3DS / portal: limpia la URL y refresca el estado
  useEffect(function() {
    var q = new URLSearchParams(window.location.search)
    var billing = q.get('billing')
    var redirectStatus = q.get('redirect_status')
    if (redirectStatus === 'succeeded' || billing === 'success') {
      toast({ title: '¡Suscripción activada!', desc: 'Tu plan se está activando.' })
      setTimeout(reload, 1500)
    }
    if (billing || redirectStatus) {
      window.history.replaceState({}, '', window.location.pathname)
    }
  }, [])

  return (
    <div className="anim-in">
      <PageHeader title="Facturación" desc="Tu plan y consumo de comprobantes">
        {manageable && <Button size="sm" variant="secondary" icon="external" onClick={openPortal}>Gestionar</Button>}
        {status ? <Badge tone={(status === 'active' || status === 'trialing') ? 'success' : 'danger'}>{status}</Badge> : <Badge tone="muted">sin plan</Badge>}
      </PageHeader>

      {!enabled && (
        <Card className="p-4 mb-5 border-warning/30">
          <div className="flex items-center gap-2 text-[13px] text-muted-foreground"><Icon name="alert" size={16} className="text-warning shrink-0" />La facturación con Stripe aún no está configurada en el servidor. Las claves se agregan en el entorno.</div>
        </Card>
      )}

      {data && data.trial && data.trial.active && (
        <Card className="p-4 mb-5 border-primary/40">
          <div className="flex items-center gap-2.5 text-[13px]">
            <Icon name="zap" size={16} className="text-primary shrink-0" />
            <span><strong>Prueba gratis activa</strong> · te quedan <strong>{data.trial.daysLeft} días</strong>{usage.included != null ? (' y ' + Math.max(0, usage.included - usage.used) + ' docs del plan Inicio') : ''}. Elige un plan para no interrumpir tu emisión al terminar.</span>
          </div>
        </Card>
      )}

      {usage.included != null && (
        <Card className="p-5 mb-5">
          <div className="flex items-center justify-between mb-2 text-[13px]"><span className="font-bold">Consumo del mes</span><span className="tabnum font-mono">{Number(usage.used).toLocaleString('es-DO')} / {Number(usage.included).toLocaleString('es-DO')} docs</span></div>
          <Progress value={pct} tone={pct > 90 ? 'danger' : pct > 75 ? 'warning' : 'primary'} />
        </Card>
      )}

      {loading ? <Card className="p-5"><div className="grid md:grid-cols-3 gap-4">{[0,1,2].map(function(i){return <div key={i} className="h-52 bg-muted animate-pulse rounded" />})}</div></Card>
      : (
        <div className="grid md:grid-cols-3 gap-4">
          {plans.map(function(p) {
            var isCurrent = status === 'active' && current === p.key
            return (
              <Card key={p.key} className={cn('p-5 flex flex-col', p.highlight && 'border-primary')}>
                {p.highlight ? <Badge tone="primary">Popular</Badge> : <span className="h-[22px]" />}
                <div className="text-[16px] font-extrabold mt-1.5">{p.name}</div>
                <div className="text-[28px] font-extrabold mt-1 leading-none">${p.priceUsd}<span className="text-[13px] font-medium text-muted-foreground">/mes</span></div>
                <div className="text-[12.5px] text-muted-foreground mt-2">{Number(p.includedDocs).toLocaleString('es-DO')} docs incluidos</div>
                <div className="text-[12px] text-muted-foreground mt-0.5">${(p.priceUsd / p.includedDocs).toFixed(3)}/doc · adicional ${p.overageUsd}/doc</div>
                <div className="flex-1" />
                <Button className="w-full mt-4" variant={(isCurrent || !p.highlight) ? 'secondary' : undefined}
                  disabled={isCurrent || !p.available || !!busy} loading={busy === p.key}
                  onClick={function() { subscribe(p.key) }}>
                  {isCurrent ? 'Plan actual' : (!p.available ? 'No disponible' : 'Elegir plan')}
                </Button>
              </Card>
            )
          })}
        </div>
      )}

      {pay && (
        <StripePaymentModal
          publishableKey={pubKey}
          clientSecret={pay.clientSecret}
          planName={pay.planName}
          onClose={function() { setPay(null) }}
          onSuccess={onPaid}
        />
      )}
    </div>
  )
}

Object.assign(window, { ColaEstado, ConsultarRNC, Clasificador, Certificacion, Facturacion })
