Iniciativa Cidadã Independente · Transparência Parlamentar
TSX386 linhas16.131 bytes

apps/web/src/app/premiacao/page.tsx

SHA-256

835f63bbacc878efd5abd41a29630d1acc25db175466835f2710277def7eab84

Somente leiturafonte-e58fe007b6ee
'use client'

import { useEffect, useState } from 'react'
import Link from 'next/link'
import { getPremiacao } from '@/lib/api'
import type { CategoriaPremio, PremiacaoResponse } from '@/lib/types'
import CategoriaCard from '@/components/premiacao/CategoriaCard'
import ShareButtons from '@/components/ShareButtons'

/** ISO → 20/09/2026. */
function dataLonga(iso: string): string {
  const d = new Date(iso)
  if (Number.isNaN(d.getTime())) return iso
  return d.toLocaleDateString('pt-BR', {
    day: '2-digit',
    month: '2-digit',
    year: 'numeric',
  })
}

function Etapa({
  valor,
  rotulo,
  nota,
}: {
  valor: number
  rotulo: string
  nota: string
}) {
  return (
    <div className="bg-surface px-4 py-4">
      <div className="font-mono text-2xl font-bold text-ink">{valor}</div>
      <div className="mt-0.5 text-xs font-medium text-ink">{rotulo}</div>
      <div className="mt-1 text-[11px] leading-snug text-muted">{nota}</div>
    </div>
  )
}

function Secao({
  eyebrow,
  titulo,
  children,
  nota,
}: {
  eyebrow: string
  titulo: string
  nota?: React.ReactNode
  children: React.ReactNode
}) {
  return (
    <section className="space-y-5">
      <header>
        <p className="eyebrow">{eyebrow}</p>
        <h2 className="mt-1 font-serif text-2xl font-semibold text-ink">{titulo}</h2>
        {nota && <p className="mt-2 max-w-3xl text-sm leading-relaxed text-muted">{nota}</p>}
      </header>
      {children}
    </section>
  )
}

export default function PremiacaoPage() {
  const [dados, setDados] = useState<PremiacaoResponse | null>(null)
  const [erro, setErro] = useState<string | null>(null)
  const [carregando, setCarregando] = useState(true)

  useEffect(() => {
    getPremiacao()
      .then(setDados)
      .catch((e) => setErro(String(e)))
      .finally(() => setCarregando(false))
  }, [])

  const por = (eixo: CategoriaPremio['eixo']) =>
    dados?.categorias.filter((c) => c.eixo === eixo) ?? []

  const grandePremio = por('grande_premio')[0]
  const regionais = por('regional')
  const tematicas = por('tematico')

  // Acúmulo e distribuição partidária são fatos do resultado: exibidos, não escondidos.
  const vitoriasPorSenador = new Map<string, { nome: string; titulos: string[] }>()
  for (const c of dados?.categorias ?? []) {
    const v = c.vencedor
    const atual = vitoriasPorSenador.get(v.senadorCod) ?? { nome: v.nome, titulos: [] }
    atual.titulos.push(c.titulo)
    vitoriasPorSenador.set(v.senadorCod, atual)
  }
  const multiplos = [...vitoriasPorSenador.values()]
    .filter((x) => x.titulos.length > 1)
    .sort((a, b) => b.titulos.length - a.titulos.length)

  const partidos = new Map<string, number>()
  for (const c of dados?.categorias ?? []) {
    partidos.set(c.vencedor.partido, (partidos.get(c.vencedor.partido) ?? 0) + 1)
  }
  const partidosOrdenados = [...partidos.entries()].sort(
    (a, b) => b[1] - a[1] || a[0].localeCompare(b[0], 'pt-BR'),
  )

  return (
    <div className="space-y-14 fade-in">
      {/* ============= ABERTURA ============= */}
      <section className="border-b border-border pb-10">
        <p className="eyebrow">Prêmio da 57ª Legislatura · 2023 — 2027</p>
        <h1 className="mt-3 font-serif text-4xl sm:text-5xl font-semibold leading-tight tracking-tight text-ink">
          Prêmio Observatório do Senado
        </h1>
        <p className="mt-5 max-w-3xl text-base sm:text-lg leading-relaxed text-muted">
          Doze reconhecimentos atribuídos <strong className="text-ink">por cálculo,
          não por escolha</strong>. Não há júri, indicação, voto popular nem
          curadoria editorial: cada categoria premia quem lidera um critério
          numérico definido antes da apuração, calculado sobre as bases públicas
          oficiais do Senado Federal.
        </p>
        <p className="mt-4 max-w-3xl text-sm leading-relaxed text-muted">
          Todo resultado desta página pode ser refeito de forma independente. O
          código que apura está publicado em{' '}
          <Link href="/codigo" className="text-primary underline-offset-2 hover:underline">
            código auditável
          </Link>
          , a fórmula está na{' '}
          <Link href="/metodologia" className="text-primary underline-offset-2 hover:underline">
            metodologia
          </Link>{' '}
          e os dados de origem vêm dos portais de dados abertos do Senado.
        </p>
      </section>

      {carregando && (
        <p className="py-16 text-center text-sm text-muted">Carregando apuração…</p>
      )}

      {erro && !carregando && (
        <section className="rounded-sm border-l-4 border-danger bg-panel px-5 py-4">
          <p className="eyebrow text-danger">Apuração indisponível</p>
          <p className="mt-2 text-sm leading-relaxed text-muted">
            Não foi possível exibir o resultado agora — ou a fonte de dados está
            fora do ar, ou a apuração recusou publicar sobre dados que não
            passaram nas verificações de integridade. A segunda hipótese é
            proposital: a página prefere não exibir resultado a exibir um
            resultado calculado sobre uma coorte incompleta. O detalhe técnico
            abaixo distingue os dois casos.
          </p>
          <p className="mt-2 font-mono text-xs text-subtle">{erro}</p>
        </section>
      )}

      {dados && (
        <>
          {/* ============= PROCEDÊNCIA ============= */}
          <Secao
            eyebrow="Procedência"
            titulo="De onde vem este resultado"
            nota={
              <>
                A apuração roda sobre um retrato datado do ranking, e não sobre
                dados que mudam a cada visita. Assim o resultado publicado é
                sempre o mesmo para todos os leitores e pode ser reconferido
                depois.
              </>
            }
          >
            <div className="grid grid-cols-2 gap-px overflow-hidden rounded-sm border border-border bg-border sm:grid-cols-4">
              <Etapa
                valor={dados.coorte.avaliados}
                rotulo="Avaliados"
                nota="Exerceram mandato na 57ª Legislatura"
              />
              <Etapa
                valor={dados.coorte.elegiveis}
                rotulo="Pontuados"
                nota={`≥ ${dados.coorte.mesesMinimos} meses de exercício — recebem nota IDS`}
              />
              <Etapa
                valor={dados.coorte.concorrentes}
                rotulo="Concorrem"
                nota={`≥ ${dados.coorte.mesesMinimosVitoria} meses — metade da legislatura`}
              />
              <Etapa
                valor={dados.categorias.length}
                rotulo="Categorias"
                nota="1 geral, 5 regionais e 6 temáticas"
              />
            </div>

            <div className="rounded-sm border border-border bg-panel px-5 py-4 text-sm leading-relaxed text-muted">
              <p>
                <strong className="text-ink">Dados apurados em {dataLonga(dados.snapshot)}.</strong>{' '}
                {dados.congelado ? (
                  <>
                    O resultado está <strong className="text-ink">fixado</strong> nesse
                    retrato: os recálculos semanais seguintes não alteram os
                    vencedores publicados.
                  </>
                ) : (
                  <>
                    O resultado ainda <strong className="text-ink">não está fixado</strong> e
                    acompanha o recálculo semanal — pode mudar até ser congelado.
                  </>
                )}
              </p>
              <p className="mt-3">
                <strong className="text-ink">Duas réguas de tempo, propositalmente distintas.</strong>{' '}
                Recebe nota quem exerceu ao menos {dados.coorte.mesesMinimos} meses;
                concorre aos prêmios quem exerceu ao menos{' '}
                {dados.coorte.mesesMinimosVitoria} — metade dos 48 meses da
                legislatura. Quem fica entre as duas segue com nota publicada no
                ranking, apenas fora da disputa. Quem encerrou o mandato antes do
                fim da legislatura continua concorrendo: o trabalho feito não é
                apagado pela saída.
              </p>
            </div>
          </Secao>

          {/* ============= GRANDE PRÊMIO ============= */}
          {grandePremio && (
            <Secao
              eyebrow="Eixo geral"
              titulo="Grande Prêmio"
              nota="Maior Índice de Desempenho Senatorial entre todos os concorrentes, somadas as seis dimensões com seus pesos."
            >
              <div className="max-w-2xl">
                <CategoriaCard categoria={grandePremio} />
              </div>
            </Secao>
          )}

          {/* ============= REGIONAIS ============= */}
          <Secao
            eyebrow="Eixo regional"
            titulo="Destaques por região"
            nota={
              <>
                Um reconhecimento por região do IBGE. As notas{' '}
                <strong className="text-ink">não são renormalizadas dentro da região</strong>:
                todos são medidos na mesma régua nacional, então as diferenças de
                nota entre regiões aparecem como são, sem nivelamento. As regiões
                também têm números diferentes de concorrentes — o Nordeste tem
                quase o triplo do Sul —, o que é informado em cada card.
              </>
            }
          >
            <div className="grid gap-5 md:grid-cols-2">
              {regionais.map((c) => (
                <CategoriaCard key={c.id} categoria={c} />
              ))}
            </div>
          </Secao>

          {/* ============= TEMÁTICAS ============= */}
          <Secao
            eyebrow="Eixo temático"
            titulo="Categorias por dimensão"
            nota={
              <>
                Uma categoria para cada dimensão que compõe o IDS, com o peso que
                ela tem no índice indicado em cada card. Os pesos são os mesmos
                publicados na metodologia e não foram ajustados para esta
                premiação.
              </>
            }
          >
            <div className="grid gap-5 md:grid-cols-2">
              {tematicas.map((c) => (
                <CategoriaCard key={c.id} categoria={c} />
              ))}
            </div>
          </Secao>

          {/* ============= LEITURA DO CONJUNTO ============= */}
          <Secao
            eyebrow="Leitura do conjunto"
            titulo="O que o resultado mostra"
            nota="Fatos da apuração que o formato de cards não deixa ver de imediato."
          >
            <div className="grid gap-5 md:grid-cols-2">
              <div className="card">
                <h3 className="font-serif text-base font-semibold text-ink">
                  Acúmulo de prêmios
                </h3>
                <p className="mt-2 text-sm leading-relaxed text-muted">
                  Um senador pode vencer em mais de um eixo. Nenhuma categoria
                  remove alguém da disputa nas outras — suprimir isso distorceria
                  os dados para distribuir prêmios.
                </p>
                {multiplos.length > 0 ? (
                  <ul className="mt-3 space-y-2">
                    {multiplos.map((m) => (
                      <li key={m.nome} className="text-sm">
                        <span className="font-medium text-ink">{m.nome}</span>
                        <span className="text-muted"> — {m.titulos.length} categorias: </span>
                        <span className="text-muted">{m.titulos.join(', ')}</span>
                      </li>
                    ))}
                  </ul>
                ) : (
                  <p className="mt-3 text-sm text-muted">
                    Nesta apuração, nenhum senador venceu mais de uma categoria.
                  </p>
                )}
              </div>

              <div className="card">
                <h3 className="font-serif text-base font-semibold text-ink">
                  Partidos dos premiados
                </h3>
                <p className="mt-2 text-sm leading-relaxed text-muted">
                  O partido não entra em nenhum critério de cálculo. A
                  distribuição abaixo é resultado da apuração, publicada para que
                  qualquer viés eventual fique visível em vez de oculto.
                </p>
                <ul className="mt-3 space-y-1.5">
                  {partidosOrdenados.map(([sigla, n]) => (
                    <li key={sigla} className="flex items-center gap-3 text-sm">
                      <span className="w-28 shrink-0 font-medium text-ink">{sigla}</span>
                      <span
                        className="h-2 rounded-sm bg-primary/70"
                        style={{ width: `${(n / dados.categorias.length) * 100}%` }}
                      />
                      <span className="font-mono text-xs text-muted">{n}</span>
                    </li>
                  ))}
                </ul>
              </div>
            </div>
          </Secao>

          {/* ============= LIMITES ============= */}
          <section className="rounded-sm border-l-4 border-accent bg-panel px-5 py-5">
            <p className="eyebrow text-accent">O que este prêmio não mede</p>
            <div className="mt-3 space-y-3 text-sm leading-relaxed text-muted">
              <p>
                O IDS é um indicador{' '}
                <strong className="text-ink">quantitativo de atividade parlamentar</strong>.
                Ele conta atos registrados nas bases oficiais — proposições,
                votações, relatorias, discursos, despesas de cota. Não avalia o{' '}
                <strong className="text-ink">mérito, o conteúdo ou as consequências</strong>{' '}
                do que foi proposto ou votado, nem posições ideológicas.
              </p>
              <p>
                Um número alto significa atividade intensa e regular, não
                necessariamente boa representação; um número baixo indica pouca
                atividade registrada, não necessariamente mau desempenho. Trabalho
                que não deixa registro formal nas bases do Senado — articulação,
                atuação em base eleitoral, negociação — não é capturado por
                nenhuma das dimensões.
              </p>
              <p>
                Os critérios e pesos foram definidos antes da apuração e valem
                igualmente para todos. Divergências sobre a escolha dos critérios
                são legítimas e devem ser dirigidas à{' '}
                <Link href="/metodologia" className="text-primary underline-offset-2 hover:underline">
                  metodologia
                </Link>
                ; erro de dado ou de cálculo pode ser verificado no{' '}
                <Link href="/codigo" className="text-primary underline-offset-2 hover:underline">
                  código auditável
                </Link>{' '}
                e reportado pelo{' '}
                <Link href="/contato" className="text-primary underline-offset-2 hover:underline">
                  contato
                </Link>
                .
              </p>
            </div>
          </section>

          <section className="border-t border-border pt-8">
            <ShareButtons
              variante="card"
              titulo="Divulgue a apuração"
              url="https://observasenado.org/premiacao"
              texto={
                `Prêmio Observatório do Senado — 57ª Legislatura: ${dados.categorias.length} ` +
                `categorias apuradas por cálculo sobre os dados abertos do Senado, ` +
                `entre ${dados.coorte.concorrentes} senadores concorrentes.`
              }
            />
          </section>
        </>
      )}
    </div>
  )
}