Sorteios parecem a coisa mais simples do mundo: você tem uma lista de nomes e escolhe um ao acaso. Mas, quando o sorteio acontece ao vivo, na frente de uma plateia, com os participantes vindo de um formulário de presença e várias palestras ao longo do dia, os detalhes começam a importar. Neste artigo, conto como construí o Sorteador, uma aplicação web feita só com HTML, CSS e JavaScript puro, que importa os participantes direto do Google Sheets e sorteia sem repetir ninguém.
O código é aberto e está no meu GitHub.
O problema
A necessidade era sortear participantes de palestras. Cada palestra tinha o seu próprio formulário do Google Forms, usado para registrar a presença, e as respostas caíam em uma planilha do Google Sheets. Na hora do sorteio, alguém precisaria abrir a planilha certa, copiar os nomes, colar em algum site de sorteio genérico e torcer para não ter nomes duplicados ou alguém sorteado duas vezes.
Eu queria uma ferramenta que resolvesse isso em poucos cliques: escolher a palestra, carregar os nomes e sortear, com uma apresentação bonita o suficiente para ser projetada em um telão.
Por que JavaScript puro, sem framework
Escolhi não usar React, Vue ou qualquer framework. O projeto inteiro são quatro arquivos: index.html, styles.css, script.js e um pequeno palestras.js de configuração. Os motivos:
- Não precisa de servidor: tudo roda no navegador. A hospedagem é estática, na Vercel, sem custo.
- Carrega instantaneamente: não há bundle de framework para baixar, o que importa quando a internet do evento é instável.
- Manutenção simples: qualquer pessoa com conhecimento básico de web consegue entender e alterar o código.
Como funciona o sorteio
O coração da aplicação é a função de sorteio. Ela segue três regras: cada nome tem a mesma chance, ninguém é sorteado duas vezes no mesmo sorteio e é possível sortear vários nomes de uma vez.
Para garantir que ninguém repita, os nomes já sorteados ficam guardados em um Set. A cada rodada, a lista de disponíveis é filtrada, e os vencedores são retirados de uma cópia dessa lista com splice, o que impede que o mesmo nome saia duas vezes na mesma rodada:
const pool = [...availableNames];
for (let index = 0; index < quantity; index += 1) {
const selectedIndex = Math.floor(Math.random() * pool.length);
const [selectedName] = pool.splice(selectedIndex, 1);
selectedNames.push(selectedName);
drawnNames.add(normalizeName(selectedName));
}
Antes do resultado aparecer, há uma contagem regressiva de cinco segundos no próprio botão ("Sorteando em 5s"). Tecnicamente, ela não é necessária, mas faz toda a diferença na experiência ao vivo: cria expectativa na plateia e deixa claro para todos que o sorteio está acontecendo naquele momento.
Quando a quantidade é maior que um, os vencedores aparecem um por vez, com setas para navegar entre eles. Assim, cada nome ganha o seu momento na tela, em vez de uma lista despejada de uma só vez. E o botão "Reiniciar sorteio" libera todos os nomes novamente, se for preciso começar do zero.
Evitando nomes duplicados
Em formulários de presença, é comum a mesma pessoa responder duas vezes, ou digitar o nome com letras maiúsculas e minúsculas diferentes. Se nada fosse feito, essa pessoa teria o dobro de chances. A solução foi normalizar os nomes antes de comparar e usar um Map para manter só uma ocorrência de cada:
function normalizeName(name) {
return name.trim().toLocaleLowerCase('pt-BR');
}
function getNames() {
const uniqueNames = new Map();
namesInput.value
.split(/\r?\n/)
.map((name) => name.trim())
.filter(Boolean)
.forEach((name) => uniqueNames.set(normalizeName(name), name));
return [...uniqueNames.values()];
}
O uso de toLocaleLowerCase('pt-BR') em vez do toLowerCase() comum garante o tratamento correto de letras acentuadas em português.
Lendo o Google Sheets sem API e sem servidor
Essa foi a parte mais interessante do projeto. Eu não queria criar um backend nem configurar credenciais da API do Google só para ler uma planilha. A solução foi aproveitar um recurso pouco conhecido: uma planilha compartilhada como "qualquer pessoa com o link" pode ser baixada em formato CSV por um endereço específico.
A função buildCsvUrl recebe o link normal da planilha, aquele que você copia do navegador, e o transforma no endereço de exportação. Ela trata dois formatos:
- Planilha compartilhada por link (
/spreadsheets/d/ID/edit): é convertida para o endpoint/gviz/tq?tqx=out:csv, respeitando a aba (gid) do link. - Planilha publicada na web (
/spreadsheets/d/e/ID/pubhtml): é convertida para/pub?output=csv.
Com o CSV em mãos, o próximo desafio foi interpretá-lo. Separar por vírgula parece fácil, até aparecer um nome como "Silva, João" entre aspas, ou um campo com aspas dentro. Por isso, escrevi um pequeno parser que percorre o texto caractere por caractere e controla se está dentro ou fora de aspas, tratando também aspas duplicadas e quebras de linha do Windows e do Linux.
Outro detalhe: o Google Forms sempre coloca o "Carimbo de data/hora" na primeira coluna. O código procura automaticamente uma coluna cujo título contenha "nome" ou "name" e, se não encontrar, usa a segunda coluna. Assim, funciona com a maioria dos formulários sem nenhuma configuração.
Quando a planilha não está compartilhada corretamente, o Google devolve uma página HTML de login em vez do CSV. O código detecta isso verificando se a resposta começa com < e mostra uma mensagem clara pedindo para compartilhar a planilha como "qualquer pessoa com o link", em vez de um erro genérico.
Uma planilha para organizar todas as palestras
A primeira versão da seleção de palestras tinha um formulário dentro do próprio sorteador para cadastrar dia, horário e link de cada palestra, salvando tudo no localStorage do navegador. Funcionou, mas logo apareceu o problema: os dados ficavam presos naquele computador. Se o sorteio fosse feito em outra máquina, era preciso cadastrar tudo de novo.
Ainda no mesmo dia, troquei essa abordagem por algo mais simples para quem organiza: uma planilha central do Google Sheets com as colunas Dia, Hora, Título e Link. O endereço dela fica no arquivo palestras.js, e o sorteador lê essa planilha ao abrir. Qualquer pessoa da organização pode atualizar a programação pela própria planilha, sem tocar no código.
Para facilitar ainda mais, ao abrir a página o sistema já seleciona o dia de hoje e a palestra que está acontecendo naquele horário. Na maioria das vezes, basta clicar em "Carregar" e depois em "Sortear". As datas e horários são normalizados, então tanto "02/10" quanto "2026-10-02", ou "8h" e "08:00", são aceitos.
Também deixei uma opção "Usar outro link", para importar qualquer planilha de respostas manualmente, e um botão "Atualizar", que busca só os nomes novos de uma palestra já carregada. Isso é útil quando pessoas ainda estão respondendo o formulário minutos antes do sorteio.
Sorteio de números
A última funcionalidade adicionada foi o sorteio de números: você informa um intervalo, como de 1 a 100, e a lista é preenchida automaticamente. Serve para rifas, números de inscrição ou qualquer situação em que os participantes são identificados por número. Para evitar travar o navegador, se o intervalo passar de 100 mil números, a aplicação pede confirmação antes de gerar a lista.
Detalhes de interface que fazem diferença
- Contador de nomes sempre visível, atualizado conforme você digita ou importa.
- Mensagens de status com
aria-live, para que leitores de tela anunciem o resultado do sorteio. - Validações amigáveis: se você pedir para sortear mais nomes do que há disponíveis, a mensagem diz exatamente quantos restam.
- Visual pensado para projeção: o nome sorteado aparece grande, em um painel escuro com alto contraste, legível do fundo da sala.
O que aprendi
- Nem todo projeto precisa de framework ou backend. Para uma ferramenta focada, JavaScript puro e hospedagem estática deixam tudo mais rápido e mais barato.
- Os dados devem ficar onde as pessoas já trabalham. Trocar o cadastro no
localStoragepela planilha foi a mudança que tornou a ferramenta realmente prática para a organização. - Os casos extremos aparecem no uso real: nomes duplicados, vírgulas dentro de nomes, planilhas sem permissão. Tratar cada um com uma mensagem clara evita constrangimento na frente da plateia.
- Experiência também é funcionalidade. A contagem regressiva não muda o resultado, mas muda completamente a sensação do sorteio.
O que ainda pode melhorar
Algumas ideias para as próximas versões: usar crypto.getRandomValues no lugar do Math.random, que é uma fonte de aleatoriedade mais robusta; manter um histórico dos sorteios realizados, com data e hora, para transparência; e um modo de tela cheia dedicado à projeção.
Experimente
O Sorteador está publicado e pode ser usado gratuitamente em sorteador-swart.vercel.app. Basta colar os nomes, um por linha, e clicar em "Sortear nome". Se quiser adaptar para o seu evento, o código completo está no repositório do GitHub.