Files
convite-rael/instrucoes.md

36 KiB
Raw Blame History

Convites Digitais — Manual de Instruções

Este documento reúne tudo o que é preciso para instalar, publicar e manter os Convites Digitais no servidor. É para o administrador do sistema.

  • Parte 1 — Deploy: instalação, configuração, operação, backup e solução de problemas.
  • Parte 2 — Histórico de alterações: o que mudou em cada versão e como atualizar o servidor.

Para usar o sistema (criar eventos e convites, enviar, acompanhar respostas, PDFs), veja o MANUAL-USUARIO.md. Ele é escrito para o organizador da festa, sem termos técnicos, e pode ser entregue a quem vai gerenciar os eventos.

Para desenvolver ou alterar o código (arquitetura, banco de dados, desenvolvimento local, serviço Python), veja o README.md.


Sumário

Parte 1 — Deploy

  1. Visão geral da arquitetura
  2. Pré-requisitos
  3. Copiar o projeto para o servidor
  4. Configurar as variáveis de ambiente
  5. Subir a aplicação
  6. Verificar se está tudo funcionando
  7. Publicar via Cloudflare
  8. Atualizar para uma nova versão
  9. Backup e restauração
  10. Operação do dia a dia
  11. Solução de problemas

Parte 2 — Histórico de alterações

  1. Versão de 05/10/2026
  2. Versão de 06/10/2026 — PDFs dos convites
  3. Versão de 07/10/2026 — Mensagem do WhatsApp personalizada

Parte 1 — Deploy

1. Visão geral da arquitetura

A aplicação roda em três containers gerenciados pelo Docker Compose:

Serviço Imagem Função
app construída pelo Dockerfile do projeto Servidor Next.js (páginas, área do organizador, RSVP) na porta 3000
db postgres:17-alpine Banco de dados. Os dados ficam no volume pgdata
pdf-service construída por pdf-service/Dockerfile (Python) Gera os PDFs personalizados dos convites. Só o app acessa
Convidado / Organizador
        │
        ▼
 Cloudflare (HTTPS)  ──►  Servidor :3000  ──►  container app  ──►  container db (Postgres)
                                                    │                └─ volume pgdata
                                                    │ HTTP interno (http://pdf-service:8000)
                                                    ▼
                                              container pdf-service
                                                    │
                          volume app-storage ◄──────┴── (app e pdf-service: PDFs modelo e gerados)

Pontos importantes:

  • O banco e o pdf-service não ficam expostos para fora do servidor; só o container app os acessa.
  • Os PDFs (modelos e gerados) ficam no volume Docker app-storage, fora do banco. O banco guarda só as informações sobre eles.
  • As migrations (criação/atualização das tabelas) são aplicadas automaticamente toda vez que o container app sobe.
  • Os dados ficam no volume Docker pgdata e sobrevivem a reinícios e rebuilds dos containers.

2. Pré-requisitos

No servidor (ex.: 192.168.18.97):

  • Docker com o plugin Compose. Para conferir:

    docker --version
    docker compose version
    
  • Acesso à internet durante o build — o build baixa as dependências npm e as fontes do Google.

  • Porta livre para a aplicação (padrão 3000; pode ser alterada em APP_PORT).


3. Copiar o projeto para o servidor

Copie a pasta do projeto para o servidor sem as pastas node_modules, .next e dist (elas são recriadas no build).

Exemplo com rsync, a partir da máquina onde está o código:

rsync -av --exclude node_modules --exclude .next --exclude dist ./ usuario@192.168.18.97:/opt/convite/

Troque usuario e /opt/convite/ pelo usuário e pela pasta usados nas outras aplicações do servidor.

Arquivos essenciais que precisam estar no servidor:

Dockerfile
compose.yml
.dockerignore
.env.example
package.json
package-lock.json
next.config.ts
tsconfig.json
postcss.config.mjs
drizzle.config.ts
drizzle/        (migrations SQL)
scripts/        (script de migration)
pdf-service/    (serviço Python de PDFs)
src/
public/

4. Configurar as variáveis de ambiente

No servidor, dentro da pasta do projeto:

cd /opt/convite
cp .env.example .env
nano .env

Variáveis

Variável Obrigatória Exemplo Descrição
POSTGRES_DB não convite Nome do banco
POSTGRES_USER não convite Usuário do banco
POSTGRES_PASSWORD sim Xk29fLq81 Senha do banco. Só letras e números
DATABASE_URL não* — Usada apenas fora do Docker. No compose é montada automaticamente
APP_PORT não 3000 Porta publicada no servidor
APP_URL recomendada https://convite.seudominio.com Endereço público do app, sem barra no final. Usado no card de pré-visualização do WhatsApp
ORGANIZER_PASSWORD sim — Senha de acesso à área do organizador
SESSION_SECRET sim — Chave secreta de 32+ caracteres usada na verificação da senha
COOKIE_SECURE sim false false para acesso por http://IP; true atrás do Cloudflare (HTTPS)
RUN_MIGRATIONS não true false desliga a migration automática na subida
INVITE_LINK_BASE_URL não https://convite.n1b3.com.br Endereço usado no link gravado em cada PDF: <valor>/r/<token>, sem barra no final. Em branco, usa o APP_URL. Só defina se o link do PDF tiver de ser diferente do endereço do app
INVITE_LINK_PATTERN não com.br/r/ Trecho que identifica, no PDF modelo, o link a ser trocado pelo link de cada convite
PDF_SERVICE_URL não* http://pdf-service:8000 Endereço do serviço de PDFs. No compose é definido automaticamente
STORAGE_DIR não* /storage Pasta dos PDFs. No compose é definida automaticamente (volume app-storage)

* Não precisa alterar para o deploy com Docker. Essas variáveis só importam ao rodar fora do Docker (ver README.md).

Gerar o SESSION_SECRET

Em qualquer máquina com Node.js:

node -e "console.log(require('crypto').randomBytes(32).toString('base64url'))"

Ou no próprio servidor, sem Node:

openssl rand -base64 32 | tr -d '/+=' 

Cole o resultado em SESSION_SECRET=.

Exemplo de .env preenchido

POSTGRES_DB=convite
POSTGRES_USER=convite
POSTGRES_PASSWORD=Xk29fLq81mPz

APP_PORT=3000
APP_URL=http://192.168.18.97:3000

ORGANIZER_PASSWORD=uma-senha-forte-para-o-admin
SESSION_SECRET=3qD0x9bN1pY7sKcVb2T8wM4rH6jLz5aEoUfGiQ
COOKIE_SECURE=false

INVITE_LINK_PATTERN=com.br/r/

Os PDFs usam o APP_URL como endereço do link. Para um endereço diferente, acrescente INVITE_LINK_BASE_URL=https://....

🔒 O .env contém senhas. Não envie esse arquivo por e-mail/chat e restrinja o acesso: chmod 600 .env.


5. Subir a aplicação

docker compose up -d --build

O que acontece, em ordem:

  1. As imagens do app e do pdf-service são construídas (pode levar alguns minutos na primeira vez).
  2. O Postgres e o pdf-service sobem; o app aguarda o banco ficar saudável.
  3. O app aplica as migrations pendentes.
  4. O servidor Next.js inicia na porta APP_PORT.

6. Verificar se está tudo funcionando

Status dos containers — os três devem estar running (e db e pdf-service como healthy):

docker compose ps

Logs do app:

docker compose logs -f app

Saída esperada (resumida):

Migrations aplicadas.
▲ Next.js 16.x
- Local: http://0.0.0.0:3000
✓ Ready

No navegador:

Endereço Deve mostrar
http://192.168.18.97:3000/ Página inicial "Convites Digitais de Aniversário"
http://192.168.18.97:3000/admin Tela de login do organizador

Entre com a ORGANIZER_PASSWORD. Se cair na lista de eventos (vazia), o deploy está concluído.


7. Publicar via Cloudflare

Quando o Cloudflare for apontar para a aplicação (mesmo padrão das outras aplicações do servidor):

  1. Configure o domínio/túnel no Cloudflare apontando para http://<servidor>:<APP_PORT>.

  2. No .env, altere:

    COOKIE_SECURE=true
    APP_URL=https://<seu-domínio>
    
  3. Recrie o container do app:

    docker compose up -d app
    
  4. Acesse https://<seu-domínio>/admin e faça login para confirmar.

⚠️ Depois de COOKIE_SECURE=true, o login pelo IP em HTTP deixa de funcionar — isso é esperado. Use sempre o domínio HTTPS.

Os links de convite são gerados a partir do endereço em que o organizador está navegando. Por isso, depois da publicação, gere/copie os links acessando o /admin pelo domínio, e não pelo IP.


8. Atualizar para uma nova versão

  1. Copie o código novo para o servidor (mesmo comando rsync do passo 3). O .env do servidor não é sobrescrito, pois não está na pasta de origem — confira antes.

  2. Faça um backup do banco (ver seção 9) — recomendado sempre que houver mudança no banco.

  3. Reconstrua e suba o app:

    docker compose up -d --build app
    
  4. Verifique os logs (docker compose logs -f app). Novas migrations são aplicadas automaticamente.

  5. Consulte o Histórico de alterações para saber se a versão pede algum passo extra (ex.: variável nova no .env).

Os dados não são perdidos em atualizações.


9. Backup e restauração

Backup

docker compose exec -T db sh -c 'pg_dump -U "$POSTGRES_USER" "$POSTGRES_DB"' > backup-$(date +%F).sql

Gera um arquivo como backup-2026-11-10.sql na pasta atual. Guarde uma cópia fora do servidor.

Backup automático diário (opcional) — adicione ao crontab -e do servidor:

0 3 * * * cd /opt/convite && docker compose exec -T db sh -c 'pg_dump -U "$POSTGRES_USER" "$POSTGRES_DB"' > /opt/convite/backups/backup-$(date +\%F).sql

(crie a pasta antes: mkdir -p /opt/convite/backups)

Backup dos PDFs

Os PDFs modelo e gerados ficam no volume app-storage, não entram no pg_dump. Para copiá-los:

docker run --rm -v convite_app-storage:/storage -v "$PWD":/backup alpine tar czf /backup/pdfs-$(date +%F).tar.gz -C /storage .

Para restaurar (com os containers parados):

docker run --rm -v convite_app-storage:/storage -v "$PWD":/backup alpine sh -c 'tar xzf /backup/pdfs-AAAA-MM-DD.tar.gz -C /storage && chown -R 1001:1001 /storage'

Os PDFs gerados podem ser refeitos a qualquer momento (botão Gerar PDFs de todos os convites). O que vale guardar é o PDF modelo de cada evento.

Restauração

A restauração deve ser feita em um banco vazio. Para recriar o banco do zero:

docker compose down
docker volume rm convite_pgdata        # ⚠️ APAGA todos os dados atuais
docker compose up -d db
docker compose exec -T db sh -c 'psql -U "$POSTGRES_USER" "$POSTGRES_DB"' < backup-AAAA-MM-DD.sql
docker compose up -d app

⚠️ Nunca use docker compose down -v sem querer apagar o banco — o -v remove os volumes pgdata e app-storage (PDFs).


10. Operação do dia a dia

Tarefa Comando
Ver status docker compose ps
Ver logs do app docker compose logs -f app
Ver logs do banco docker compose logs -f db
Ver logs do serviço de PDFs docker compose logs -f pdf-service
Reiniciar o serviço de PDFs docker compose restart pdf-service
Reiniciar o app docker compose restart app
Parar tudo (mantém dados) docker compose down
Subir novamente docker compose up -d
Console SQL docker compose exec db sh -c 'psql -U "$POSTGRES_USER" "$POSTGRES_DB"'
Trocar a senha do organizador Edite ORGANIZER_PASSWORD no .env e rode docker compose up -d app
Derrubar todas as sessões abertas No /admin, clique em Sair de todos (no topo da página)

Os containers estão com restart: unless-stopped: voltam sozinhos se o servidor reiniciar.


11. Solução de problemas

Sintoma Causa provável Solução
defina POSTGRES_PASSWORD no .env ao subir Variável ausente Preencha POSTGRES_PASSWORD no .env
App reiniciando em loop; log com Falha ao aplicar migrations Banco inacessível ou senha errada Confira docker compose logs db e as variáveis POSTGRES_*
Erro de senha no Postgres após trocar POSTGRES_PASSWORD A senha só é definida na primeira criação do volume Volte a senha antiga no .env ou altere no banco: ALTER USER convite PASSWORD 'nova';
Login do /admin sempre volta para a tela de login COOKIE_SECURE=true com acesso via HTTP Use false no acesso por IP, ou acesse pelo domínio HTTPS
Erro SESSION_SECRET não definida no log Variável vazia ou curta Gere um valor de 32+ caracteres (seção 4)
"Senha incorreta." no login ORGANIZER_PASSWORD diferente da digitada Confira o .env e recrie o app após alterar
"Muitas tentativas incorretas" no login 5 senhas erradas em 15 minutos no mesmo IP Aguarde o tempo indicado, ou reinicie o app (docker compose restart app) para zerar o bloqueio
Link colado no WhatsApp aparece sem imagem APP_URL ausente ou errada, ou o WhatsApp guardou uma versão antiga Ajuste APP_URL no .env, rode docker compose up -d app e teste com um convite que ainda não foi compartilhado
Build falha baixando fontes/pacotes Servidor sem internet no build Libere o acesso à internet durante o docker compose build
Porta 3000 em uso Outra aplicação na mesma porta Altere APP_PORT no .env
Link do convite abre com o IP em vez do domínio Link copiado acessando o admin pelo IP Acesse o /admin pelo domínio e copie de novo
"Endereço do link não configurado" ao gerar PDF Nem APP_URL nem INVITE_LINK_BASE_URL no .env Defina APP_URL (seção 4) e rode docker compose up -d pdf-service
"Serviço de PDF indisponível" ao enviar modelo ou gerar PDF Container pdf-service parado ou com erro docker compose ps e docker compose logs pdf-service; suba com docker compose up -d pdf-service
"Nenhum link de convite (…/r/…) foi encontrado neste PDF" O modelo não tem um link clicável com INVITE_LINK_PATTERN Confira o link no PDF (precisa ser um link clicável, não só texto) ou ajuste INVITE_LINK_PATTERN
Convite com "Erro ao gerar" Falha ao gerar aquele PDF A mensagem aparece no card. Veja docker compose logs app pdf-service e clique em Gerar novamente
"Geração interrompida pela reinicialização do servidor" O app reiniciou durante a geração Clique em Gerar só pendentes e com erro
Erro de permissão (Permission denied) em /storage nos logs Arquivos do volume com outro dono docker compose run --rm --user root --entrypoint chown pdf-service -R 1001:1001 /storage
PDF baixado com link errado APP_URL (ou INVITE_LINK_BASE_URL, se definida) errada Corrija no .env, rode docker compose up -d pdf-service e gere os PDFs de novo

11.1 Diagnóstico dos PDFs

Onde olhar Comando
Saúde do serviço de PDFs docker compose ps pdf-service (deve estar healthy)
Logs da geração (lado Python) docker compose logs -f pdf-service — linhas PDF gerado convite=... ou o erro completo
Logs da geração (lado app) docker compose logs -f app — linhas Falha ao gerar o PDF do convite ...
Arquivos no volume docker compose exec app ls -R /storage/events
Lotes recentes docker compose exec db sh -c 'psql -U "$POSTGRES_USER" "$POSTGRES_DB" -c "select status, processed, failed, total, created_at from pdf_jobs order by created_at desc limit 5"'
Testar o serviço por dentro docker compose exec app wget -qO- http://pdf-service:8000/health

Para erros no código do serviço Python (traceback, links de um PDF, nível de log), veja "Manutenção do pdf-service" no README.md.


Parte 2 — Histórico de alterações

12. Versão de 05/10/2026

12.1 Resumo

# Alteração Para quem muda algo Exige ação no deploy?
1 Visual neutro (Slate) na área do organizador e na página inicial Organizador Não
2 Limite de tentativas de login Organizador Não
3 Sessões guardadas no banco + botão Sair de todos Organizador Sim: migration automática; todos precisam entrar de novo
4 Nova variável APP_URL Quem faz o deploy Sim: adicionar ao .env
5 Card de pré-visualização do convite no WhatsApp Convidado Depende do item 4
6 Exportação da lista de convidados em CSV Organizador Não
7 Projeto renomeado para Convites Digitais e documentação atualizada Todos Não

Os passos para atualizar o servidor estão na seção 12.9.


12.2 Visual neutro na área do organizador

Antes: login, painel e página inicial usavam as cores e o 🦁 do tema Safári, mesmo em eventos com outro tema.

Agora:

  • Paleta Slate / Grafite: fundo cinza-claro liso, cards brancos, textos em cinza-azulado e botões principais em índigo.
  • O 🦁 foi trocado por um ícone de calendário no login, no cabeçalho do painel e na página inicial.
  • A página inicial agora diz "Convites Digitais" em vez de "Convites Safari".
  • Contorno de foco, seleção de texto e checkboxes da área do organizador também usam índigo.
  • A página "Convite não encontrado" ficou neutra (ícone de lupa, fundo cinza-claro).
  • A cor da barra do navegador no celular agora é cinza-claro no app e a cor do tema na página de cada convite.

O que continua igual, de propósito:

  • As cores de status: verde = presente, vermelho = ausente, âmbar = pendente.
  • A prévia do tema no formulário de evento (mostra as cores do tema escolhido).
  • O botão WhatsApp (verde).
  • As páginas dos convites, que continuam com o tema escolhido para cada evento.

Arquivos: src/app/admin/**, src/components/admin/**, src/app/page.tsx, src/app/r/[token]/not-found.tsx, src/app/globals.css (classe .admin-ui) e src/components/ui/index.ts. Button e Input ganharam a opção tone="neutral"; o padrão continua sendo as cores do tema, usadas no convite.


12.3 Limite de tentativas de login

Antes: a senha do /admin podia ser testada sem limite.

Agora:

  • Depois de 5 senhas erradas em 15 minutos, o login fica bloqueado por 15 minutos para aquele IP.
  • Durante o bloqueio a senha nem é conferida, e a tela mostra quantos minutos faltam.
  • Um login certo zera o contador.

Detalhes técnicos:

  • Os contadores ficam na memória do app. Reiniciar o container (docker compose restart app) zera todos os bloqueios.
  • O IP vem do cabeçalho cf-connecting-ip, enviado pelo Cloudflare. Sem ele, o app usa x-forwarded-for ou x-real-ip.
  • ⚠️ Quem acessa o servidor direto pelo IP, sem passar pelo Cloudflare, consegue forjar esses cabeçalhos e escapar do limite. A proteção completa é o servidor aceitar só tráfego vindo do Cloudflare (Cloudflare Tunnel ou regra de firewall).

Arquivos: src/lib/login-rate-limit.ts (novo) e src/app/admin/auth-actions.ts.


12.4 Sessões no banco e "Sair de todos"

Antes: o cookie de sessão se validava sozinho, por uma assinatura. Sair só apagava o cookie do navegador: uma cópia dele continuava valendo por até 7 dias. Para derrubar todas as sessões era preciso trocar o SESSION_SECRET e recriar o app.

Agora:

  • Cada login cria uma linha na tabela nova organizer_sessions. O cookie guarda um token aleatório, e o banco guarda só o hash dele.
  • Toda página e ação do organizador confere se a sessão ainda existe e não venceu (continua durando 7 dias).
  • Sair apaga a sessão no banco, então uma cópia do cookie para de funcionar.
  • Sair de todos, um botão novo no topo do painel com confirmação, apaga as sessões de todos os dispositivos.
  • Sessões vencidas são apagadas a cada novo login.
  • O SESSION_SECRET continua obrigatório: agora ele é usado na verificação da senha.

Impacto no deploy:

  • Migration nova drizzle/0004_organizer_sessions.sql, aplicada automaticamente quando o container sobe.
  • Os cookies antigos deixam de valer: depois da atualização, todos precisam entrar de novo.

Arquivos: src/db/schema.ts, src/lib/auth.ts, src/app/admin/auth-actions.ts, src/app/admin/(painel)/layout.tsx e drizzle/0004_organizer_sessions.sql.


12.5 Variável APP_URL

Antes: o endereço base usado nos links das pré-visualizações estava fixo no código como http://localhost:3000.

Agora: esse endereço vem da variável APP_URL do .env.

Situação Valor de APP_URL
Acesso pelo IP (antes do Cloudflare) http://192.168.18.97:3000
Depois da publicação no Cloudflare https://<seu-domínio>
  • Escreva sem barra no final.
  • O valor é lido com o app rodando: depois de alterar, basta docker compose up -d app (sem rebuild).
  • Se a variável faltar ou for inválida, o app usa http://localhost:3000 e registra um aviso no log. Nesse caso o card do WhatsApp (12.6) fica sem imagem.

Arquivos: src/lib/app-url.ts (novo), src/app/layout.tsx e .env.example.


12.6 Card de pré-visualização no WhatsApp

Antes: o link do convite colado no WhatsApp aparecia sem imagem e com um título genérico.

Agora: aparece um card com:

  • imagem (1200×630) nas cores do tema do evento, com o selo e o ícone do tema, "Família, você está convidado para", o nome da festa, data · horário · local e "Toque para confirmar sua presença";
  • título "Família, você está convidado!";
  • descrição com o nome da festa, a data, o horário e o local.

Detalhes:

  • A imagem mostra só o que a página do convite já mostra para quem tem o link. A lista de convidados não aparece.
  • A imagem é gerada na hora, então edições no evento aparecem nos próximos compartilhamentos.
  • O WhatsApp guarda o card de cada link por um tempo: links compartilhados antes da atualização podem continuar sem imagem.
  • Os emojis da imagem são baixados da internet ao gerar o card: o servidor precisa de acesso à internet.
  • A mensagem pronta do botão WhatsApp agora usa o ícone do tema do evento (antes era sempre 🦁).

Arquivos: src/app/r/[token]/opengraph-image.tsx (novo), src/app/r/[token]/page.tsx e src/components/admin/ShareInvitation.tsx.


12.7 Exportar a lista de convidados (CSV)

Agora: na página do evento, abaixo do Resumo de presença, em Exportar lista (CSV), há os botões:

Botão Conteúdo do arquivo
Todos Todos os convidados do evento
Presentes Só quem confirmou
Ausentes Só quem recusou (e, depois do prazo, quem não respondeu)
Pendentes Só quem ainda não respondeu
  • Colunas: Convite, Convidado, Tipo (Adulto/Adolescente/Criança), Status e Respondido em (horário de Brasília).
  • Abre direto no Excel em português e no Google Planilhas, com colunas e acentos corretos (separador ;, UTF-8).
  • Nome do arquivo: parte do nome da festa e do filtro, ex.: 1-aniversario-do-arthur-presentes.csv.
  • Só funciona com o organizador logado. Sem login, o endereço redireciona para a tela de login.
  • Nomes que começam com =, +, - ou @ recebem um apóstrofo na frente, para o Excel não tratá-los como fórmula.

Arquivos: src/app/admin/(painel)/eventos/[id]/exportar/route.ts (novo) e src/app/admin/(painel)/eventos/[id]/page.tsx.


12.8 Projeto renomeado e documentação

  • Novo nome: "Convite Safari" passou a se chamar Convites Digitais no README.md, no instrucoes.md e na página inicial.
  • Documentação atualizada: o README e o instrucoes.md, que na época também trazia o manual de uso (hoje no MANUAL-USUARIO.md, ver 14.2), passaram a descrever o que já existia e não estava documentado (temas, prazo de confirmação, convidados adolescentes, textos de selo e rodapé) e as novidades desta versão.
  • Mantido: o tema padrão no banco continua safari, só para eventos antigos criados antes da escolha de tema. O tema Safári continua disponível normalmente.

12.9 Roteiro de atualização desta versão

  1. Backup do banco (esta versão cria uma tabela nova):

    docker compose exec -T db sh -c 'pg_dump -U "$POSTGRES_USER" "$POSTGRES_DB"' > backup-$(date +%F).sql
    
  2. Copie o código novo para o servidor (mesmo rsync da seção 3).

  3. Adicione APP_URL ao .env do servidor (ver 12.5):

    APP_URL=http://192.168.18.97:3000
    
  4. Reconstrua e suba o app:

    docker compose up -d --build app
    
  5. Confira os logs (docker compose logs -f app): deve aparecer Migrations aplicadas. e nenhum aviso de APP_URL inválida.

  6. Valide:

    • abra /admin: a tela de login deve estar no visual neutro; entre com a senha (as sessões antigas foram encerradas);
    • abra um evento e baixe um CSV em Exportar lista (CSV);
    • cole o link de um convite ainda não compartilhado no WhatsApp e confira o card com imagem.
  7. Quando publicar no Cloudflare, além de COOKIE_SECURE=true, troque APP_URL para https://<seu-domínio> e rode docker compose up -d app.


13. Versão de 06/10/2026 — PDFs dos convites

13.1 Resumo

# Alteração Para quem muda algo Exige ação no deploy?
1 PDF modelo por evento (upload, substituição, download) Organizador Não
2 Geração do PDF personalizado por convite e em lote, com progresso Organizador Não
3 Novo container pdf-service (Python) e volume app-storage Quem faz o deploy Sim: build do novo serviço
4 Novas variáveis opcionais INVITE_LINK_BASE_URL (padrão: APP_URL) e INVITE_LINK_PATTERN Quem faz o deploy Só conferir se APP_URL está correto
5 Migration 0005_pdf_generation (colunas de PDF e tabela pdf_jobs) — Automática na subida

Os passos para atualizar o servidor estão na seção 13.3.


13.2 O que mudou

  • Para o organizador:

    • seção PDF do convite na página do evento;
    • status e botões de PDF em cada convite;
    • geração em lote com progresso.

    Uso detalhado na seção 2.11 do MANUAL-USUARIO.md.

  • No servidor:

    • container novo pdf-service (Python), sem porta publicada;
    • volume novo app-storage com os PDFs, montado em /storage no app e no pdf-service;
    • migration 0005_pdf_generation, aplicada na subida.
  • Variáveis: INVITE_LINK_BASE_URL (opcional; em branco usa o APP_URL) e INVITE_LINK_PATTERN (padrão com.br/r/). Ver seção 4.

  • Segurança:

    • downloads e ações exigem o organizador logado;
    • o convite precisa pertencer ao evento da URL;
    • o navegador nunca envia caminhos de arquivo;
    • o upload aceita só PDF de até 10 MB.

Arquitetura, banco, endpoints e manutenção do código estão no README.md.

Arquivos:

  • Novos:
    • pdf-service/ (Dockerfile, requirements.txt, app/main.py);
    • src/lib/pdf/ (storage.ts, service.ts, jobs.ts, state.ts, types.ts);
    • src/app/admin/pdf-actions.ts;
    • src/components/admin/EventPdf.tsx;
    • src/instrumentation.ts;
    • src/lib/slugify.ts;
    • rotas em src/app/admin/(painel)/eventos/[id]/pdf/ e .../convites/[conviteId]/pdf/;
    • drizzle/0005_pdf_generation.sql.
  • Alterados:
    • src/db/schema.ts;
    • src/app/admin/actions.ts (apaga os PDFs ao excluir evento ou convite);
    • página do evento;
    • next.config.ts (limite de upload de 11 MB nas Server Actions);
    • Dockerfile, compose.yml, .env.example, .dockerignore e .gitignore.

13.3 Roteiro de atualização desta versão

  1. Backup do banco (esta versão altera tabelas):

    docker compose exec -T db sh -c 'pg_dump -U "$POSTGRES_USER" "$POSTGRES_DB"' > backup-$(date +%F).sql
    
  2. Copie o código novo para o servidor (mesmo rsync da seção 3). Confira que a pasta pdf-service/ foi copiada.

  3. Confira o .env do servidor: o APP_URL deve ser o endereço público (https://convite.n1b3.com.br), porque os PDFs usam esse endereço no link. As variáveis novas são opcionais:

    # INVITE_LINK_BASE_URL=https://convite.n1b3.com.br   # só se o link do PDF tiver de ser diferente do APP_URL
    INVITE_LINK_PATTERN=com.br/r/                        # padrão; pode omitir
    
  4. Construa e suba tudo (o pdf-service é novo):

    docker compose up -d --build
    
  5. Confira: docker compose ps deve mostrar o pdf-service como healthy, e os logs do app devem mostrar Migrations aplicadas..

  6. Valide:

    • abra um evento e envie o PDF modelo em PDF do convite;
    • gere o PDF de um convite e baixe o arquivo;
    • abra o PDF e clique no link: deve abrir <APP_URL>/r/<token do convite>;
    • clique em Gerar PDFs de todos os convites e acompanhe o progresso;
    • rode docker compose restart e confira que os PDFs continuam disponíveis para download.

14. Versão de 07/10/2026 — Mensagem do WhatsApp personalizada

14.1 Resumo

# Alteração Para quem muda algo Exige ação no deploy?
1 Campo Mensagem do WhatsApp no evento, com variáveis e formatação do WhatsApp Organizador Não
2 Pré-visualização da mensagem no formulário do evento Organizador Não
3 Migration 0006_event_whatsapp_message (coluna whatsapp_message em events) — Automática na subida
4 Manual de uso separado em MANUAL-USUARIO.md Organizador e administrador Não

Eventos já existentes ficam com o campo vazio e passam a usar a nova mensagem padrão (ver 14.2) a partir dos próximos cliques no botão. Mensagens já enviadas não mudam.


14.2 O que mudou

Antes: a mensagem do botão WhatsApp era fixa no código: "Olá, Família! Vocês estão convidados para o festa ícone", seguida do link.

Agora:

  • O formulário do evento tem o campo Mensagem do WhatsApp, com:

    • botões para inserir as variáveis {nome}, {evento}, {data}, {hora}, {local}, {endereco}, {icone} e {link};
    • uma linha de ajuda com a formatação do WhatsApp;
    • três contadores: Modelo, Texto final (exemplo) e Link do WhatsApp (exemplo);
    • uma pré-visualização no estilo do WhatsApp, que aplica negrito, itálico, riscado, monoespaçado e código. Em navegadores sem suporte (Safari < 16.4), a prévia aparece sem formatação, sem quebrar o formulário.
  • O botão WhatsApp de cada convite monta o texto a partir do modelo do evento.

  • Botão novo Copiar mensagem em cada convite: copia o mesmo texto do botão WhatsApp, para enviar por outro aplicativo.

  • Nova mensagem padrão, usada com o campo vazio:

    Olá, {nome}! Chegou o seu convite para: *{evento}* {icone}
    Confirme a presença: {link}
    

    A anterior ("…convidados para o festa") só combinava com nomes de festa masculinos no singular e com convites de família: "para o Festa da Maria" e "Vocês" num convite de uma pessoa só. A nova não depende do nome da festa nem de quantas pessoas há no convite.

  • Outros textos com a mesma correção:

    Onde Antes Agora
    Título do card do link no WhatsApp "Família, você está convidado!" "Família, chegou o seu convite!"
    Imagem do card "Família, você está convidado para" "Família, chegou o seu convite para"
    Topo da página do convite (e prévia do tema no formulário do evento) "esperamos vocês para celebrar!" "estamos esperando por você(s)!"

    Links já compartilhados podem continuar mostrando o card antigo por um tempo, porque o WhatsApp guarda o card (ver 12.6).

  • Sem {link} na mensagem, o link é adicionado no final.

  • Limite de 950 caracteres, com margem abaixo de 1000 para o link do wa.me. Ele vale para o modelo e também para o texto final, já com as variáveis trocadas.

    • O texto final é conferido no servidor ao criar ou editar o evento, com os nomes de todos os convites, e ao criar ou editar um convite.
    • Nessa conferência, o link é estimado com o APP_URL e um código de 8 caracteres.
    • A mensagem padrão (campo vazio) não é limitada.
  • Limite de 2300 caracteres no link codificado (https://wa.me/?text=...), conferido nos mesmos momentos. Ele existe porque, no link, acentos ocupam 6 caracteres e emojis até 12. O WhatsApp não publica um limite oficial; os dois valores são margens de segurança.

  • Mensagem por evento: não há mensagem diferente por convite. Para personalizar o texto de uma família, o organizador edita a mensagem no próprio WhatsApp antes de enviar.

Uso detalhado na seção 2.5.1 do MANUAL-USUARIO.md.

Arquivos:

  • Novos:
    • src/lib/whatsapp-message.ts (mensagem padrão, variáveis, limite e montagem do texto);
    • src/components/admin/WhatsAppMessageField.tsx (campo, botões de variáveis e pré-visualização);
    • drizzle/0006_event_whatsapp_message.sql.
  • Alterados:
    • src/db/schema.ts (coluna whatsappMessage);
    • src/app/admin/actions.ts (leitura e validação do campo; conferência do texto final ao salvar evento e convite);
    • src/components/admin/EventForm.tsx (campo novo; data, horário, local e endereço alimentam a pré-visualização);
    • src/components/admin/ShareInvitation.tsx (usa o modelo do evento; botão Copiar mensagem);
    • src/app/r/[token]/page.tsx, src/app/r/[token]/opengraph-image.tsx e src/components/invitation/InvitationHero.tsx (frases neutras);
    • página do evento e página de edição do evento.

Documentação:

  • O manual de uso, que era a antiga Parte 2 deste arquivo, foi movido para o MANUAL-USUARIO.md. Ele foi reescrito sem termos técnicos e ganhou a seção "Quando falar com o administrador".
  • Este arquivo ficou só com o deploy (Parte 1) e o histórico, que passou a ser a Parte 2. As seções do histórico foram renumeradas de 16–18 para 12–14.

14.3 Roteiro de atualização desta versão

  1. Backup do banco (esta versão adiciona uma coluna):

    docker compose exec -T db sh -c 'pg_dump -U "$POSTGRES_USER" "$POSTGRES_DB"' > backup-$(date +%F).sql
    
  2. Copie o código novo para o servidor (mesmo rsync da seção 3).

  3. Reconstrua e suba o app:

    docker compose up -d --build app
    
  4. Confira os logs (docker compose logs -f app): deve aparecer Migrations aplicadas..

  5. Valide:

    • em um evento existente com o campo vazio, clique em WhatsApp num convite: deve aparecer a nova mensagem padrão ("Chegou o seu convite para: …");
    • em Editar evento, escreva uma mensagem com *negrito* e {nome} e confira a pré-visualização;
    • salve, clique em WhatsApp e confira no WhatsApp o nome da família, a formatação e o link.