Upload de PDFs
Este guia detalha o sistema de upload de arquivos PDF e processamento automático de páginas.
Visão Geral
O ArqSystem possui um sistema robusto de upload de PDFs com:
- Upload direto para Cloudflare R2 (storage em nuvem)
- Processamento assíncrono em background
- Extração automática de páginas
- Conversão para formato WebP otimizado
- Criação automática de documento vinculado
Acesso
Requisito: Apenas usuários autenticados podem fazer upload.
Rota: /upload
Como acessar:
- Faça login no sistema
- Menu lateral: clique em "Upload"
- Ou acesse diretamente
/upload
Tipos de Arquivo
Formato aceito: Apenas arquivos PDF
Restrições:
- ✅ Arquivos
.pdf - ❌ Imagens (jpg, png, etc.)
- ❌ Documentos Word, Excel
- ❌ Outros formatos
Validação:
- Frontend: Atributo
accept="application/pdf"no input - Backend: Verificação de extensão
.pdf
Interface de Upload
Dropzone
Área de arrastar e soltar (drag-and-drop):
┌─────────────────────────────────────────┐
│ 📁 Arraste um arquivo PDF aqui │
│ ou clique para selecionar │
│ │
│ Apenas arquivos PDF são aceitos │
└─────────────────────────────────────────┘Interações:
- Arraste arquivo PDF para a área
- Ou clique para abrir seletor de arquivos
- Apenas 1 arquivo por vez
Preview do Arquivo
Após seleção, exibe card com informações:
┌─ Arquivo Selecionado ──────────────────┐
│ 📄 relatorio-anual-2024.pdf │
│ Tamanho: 2.5 MB │
│ [X] │
└────────────────────────────────────────┘Ações:
- Botão [X]: Remove arquivo selecionado
- Possível selecionar outro arquivo
Botão de Upload
Após seleção do arquivo:
[Enviar PDF] (azul, destacado)Estado desabilitado durante upload:
[Enviando...] (cinza, desabilitado)Processo de Upload
Fluxo Completo
1. Usuário seleciona arquivo PDF
2. Sistema valida formato
3. Clique em "Enviar PDF"
4. Modal de progresso aparece
5. Upload para R2 (0-100%)
6. Confirmação de upload
7. Criação automática de documento (V2)
8. Processamento em background inicia
9. Notificação de conclusãoUpload com Progresso
Modal exibido durante upload:
┌─ Enviando Arquivo ─────────────────────┐
│ │
│ relatorio-anual-2024.pdf │
│ │
│ [████████████░░░░░░] 75% │
│ │
│ Preparando upload... │
│ │
│ [X] │
└─────────────────────────────────────────┘Fases do progresso:
| Progresso | Status | Descrição |
|---|---|---|
| 0-10% | Preparando upload... | Requisitando URL presigned |
| 10-90% | Enviando arquivo... | Upload para R2 |
| 90-100% | Finalizando... | Confirmando upload |
| 100% | Concluído! | Upload finalizado |
Cancelamento:
- Botão [X] no modal
- Cancela upload em progresso
- Arquivo não é salvo
Estados Visuais
Estado Idle (antes de selecionar):
- Borda azul claro (
border-blue-200) - Fundo azul claro (
bg-blue-50)
Estado Uploading:
- Borda azul (
border-blue-300) - Badge "Enviando" com spinner
Estado Success:
- Borda verde (
border-green-500) - Fundo verde claro (
bg-green-50) - Badge "Enviado com sucesso" verde
- Ícone CheckCircle2
Estado Error:
- Borda vermelha (
border-red-500) - Fundo vermelho claro (
bg-red-50) - Badge "Falha no envio" vermelho
- Ícone AlertTriangle
- Botão "Tentar Novamente"
Tratamento de Erros
Erro de Validação:
❌ Apenas arquivos PDF são aceitos.Erro de Upload:
┌─ Erro no Upload ───────────────────────┐
│ │
│ ⚠️ Erro ao enviar PDF │
│ │
│ Falha ao conectar com o servidor. │
│ Verifique sua conexão e tente │
│ novamente. │
│ │
│ [Fechar] [Tentar Novamente] │
└─────────────────────────────────────────┘Ações em erro:
- Fechar: Fecha modal, reseta estado
- Tentar Novamente: Reinicia upload do mesmo arquivo
Armazenamento
Estrutura de Diretórios
Os arquivos são organizados hierarquicamente:
YYYY/MM/DD/file-name/file-name.pdfExemplo prático:
2026/01/14/relatorio-anual/relatorio-anual.pdfComponentes do path:
YYYY: Ano (4 dígitos)MM: Mês (2 dígitos)DD: Dia (2 dígitos)file-name: Nome sanitizado do arquivo (pasta)file-name.pdf: Arquivo PDF original
Sanitização de Nomes
O sistema sanitiza nomes de arquivo usando slugify:
Transformações aplicadas:
- Conversão para lowercase
- Remoção de caracteres especiais
- Espaços substituídos por hífens
- Acentos removidos
- Trim de espaços em branco
Exemplos:
"Portfólio Cerâmica .pdf" → "portfolio-ceramica"
"Relatório Anual 2024.PDF" → "relatorio-anual-2024"
"Documento (FINAL) v2.pdf" → "documento-final-v2"Tratamento de Duplicatas
Quando um arquivo com mesmo nome já existe:
Sistema de sufixo numérico:
primeira tentativa → documento/documento.pdf
segunda tentativa → documento-1/documento-1.pdf
terceira tentativa → documento-2/documento-2.pdfLimite: Máximo 100 duplicatas por dia por nome de arquivo.
Erro após 100 tentativas:
❌ Limite de duplicatas atingido para este arquivo hoje.
Aguarde 24 horas ou renomeie o arquivo.Processamento em Background
Visão Geral
Após upload confirmado (100%), o sistema inicia processamento assíncrono.
Vantagens:
- Usuário pode continuar usando o sistema
- Não bloqueia a interface
- Processamento otimizado em fila
- Retry automático em caso de falha
Pipeline de Processamento
┌─────────────────────────────────────────────┐
│ 1. PENDING │
│ Aguardando processamento │
└─────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────┐
│ 2. GENERATING_IMAGES │
│ Executando pdftoppm │
│ Extraindo páginas como PNG │
└─────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────┐
│ 3. CONVERTING_IMAGES │
│ Convertendo PNG → WebP (Sharp) │
│ Qualidade: 85%, Esforço: 6 │
└─────────────────────────────────────────────┘
↓
┌─────────────────────────────────────────────┐
│ 4. COMPLETED │
│ Todas as páginas processadas │
│ Registros criados no banco │
└─────────────────────────────────────────────┘Fases do Processamento
1. PENDING
Status inicial após confirmação de upload.
Ações:
- Registro criado no banco de dados
- Job adicionado à fila
PROCESS_PDF - Status:
processingStatus = PENDING
2. GENERATING_IMAGES
Extração de páginas do PDF usando pdftoppm.
Comando executado:
pdftoppm -png /tmp/file.pdf /tmp/pageSaída:
/tmp/page-1.png
/tmp/page-2.png
/tmp/page-3.png
...
/tmp/page-N.pngAtualização no banco:
{
processingStatus: "GENERATING_IMAGES",
processingStartedAt: new Date()
}3. CONVERTING_IMAGES
Conversão PNG → WebP usando Sharp.
Configuração:
sharp(pngBuffer)
.webp({
quality: 85, // 0-100
effort: 6 // 0-6 (maior = mais compressão)
})
.toBuffer();Processo:
- Para cada página PNG:
- Carrega PNG em buffer
- Converte para WebP
- Gera nome:
file-name-pg-1.webp - Upload para R2
- Coleta metadados (tamanho, key)
Estrutura de saída:
2026/01/14/relatorio-anual/relatorio-anual-pg-1.webp
2026/01/14/relatorio-anual/relatorio-anual-pg-2.webp
2026/01/14/relatorio-anual/relatorio-anual-pg-3.webpAtualização no banco:
{
processingStatus: "CONVERTING_IMAGES",
totalPages: N
}4. COMPLETED
Finalização do processamento.
Ações:
- Criação de registros
UploadedFilePagepara cada página - Atualização do status final
- Cleanup de arquivos temporários
Banco de dados:
// UploadedFile
{
processingStatus: "COMPLETED",
processingCompletedAt: new Date(),
totalPages: N
}
// UploadedFilePage (para cada página)
{
uploadedFileId: "uuid",
pageNumber: 1,
r2FileKey: "2026/01/14/file/file-pg-1.webp",
fileName: "file-pg-1.webp",
fileSize: 123456,
contentType: "image/webp"
}Status de Erro
FAILED: Processamento falhou.
Causas comuns:
- PDF corrompido
- Falha no pdftoppm
- Erro de conversão Sharp
- Falha no upload para R2
- Timeout de processamento
Atualização no banco:
{
processingStatus: "FAILED",
processingError: "Erro detalhado aqui",
processingCompletedAt: new Date()
}Retry Policy (BullMQ):
- Máximo 3 tentativas
- Backoff exponencial: 5s, 10s, 20s
Monitoramento
Consultar status de processamento:
// API endpoint
GET /api/trpc/upload.getPdfPages?input={"fileId":"uuid"}
// Resposta
{
id: "uuid",
fileName: "relatorio-anual.pdf",
processingStatus: "CONVERTING_IMAGES",
totalPages: 10,
pages: [
{
id: "page-uuid",
pageNumber: 1,
r2FileKey: "2026/01/14/file/file-pg-1.webp",
fileName: "file-pg-1.webp",
fileSize: 123456
},
// ... mais páginas
]
}Criação Automática de Documento (V2)
Visão Geral
Na versão 2, após upload concluído (100%), o sistema cria automaticamente um documento pré-preenchido.
Campos Pré-preenchidos
Metadados extraídos:
- Título: Nome do arquivo (sem extensão)
- Data de Inserção: Data/hora do upload
- Arquivo vinculado: Path do PDF no R2
- Número de páginas: Quantidade de páginas (após processamento)
- Status: "Processando" ou "Concluído"
- Usuário responsável: Usuário que fez upload
Campos a preencher:
- Tipo Documental (obrigatório CONARQ)
- Número do Documento (obrigatório)
- Nível de Acesso (obrigatório para conformidade)
- Palavras-chave
- Observações
- Outros campos dinâmicos
Fluxo Pós-Upload
1. Upload concluído (100%)
2. Sistema cria documento pré-preenchido
3. Status inicial: "Incompleto"
4. Documento aparece na listagem
5. Badge "Requer Atenção" destacado
6. Usuário recebe notificação
7. Acessa /documents/:id/edit
8. Completa campos obrigatórios CONARQ
9. Salva documento
10. Status muda para "Ativo"Identificação de Documentos Incompletos
Na listagem /documents:
Badge visual:
[⚠️ Incompleto]Filtro especial:
[Filtro: Documentos Incompletos]Exibe apenas documentos criados via upload que ainda precisam ser completados.
Instruções e Informações
A página de upload exibe um card informativo "Como funciona":
Instruções Apresentadas
✅ Apenas arquivos PDF são aceitos
📁 O PDF será armazenado em:
dd/mm/yyyy/file-name/file-name.pdf🖼️ As páginas serão convertidas em imagens WebP automaticamente
- Formato otimizado
- Alta qualidade
- Tamanho reduzido
⏳ O processamento ocorre em segundo plano
- Pode levar alguns minutos
- Não bloqueia o uso do sistema
- Notificação quando concluído
Visualização de Arquivos
Árvore de Arquivos
Página /storage exibe todos os arquivos em estrutura hierárquica:
📅 2026
📅 01 (Janeiro)
📅 14
📁 relatorio-anual (3 arquivos)
📄 relatorio-anual.pdf (2.5 MB)
Status: ✅ Processado
Páginas: 10
🖼️ relatorio-anual-pg-1.webp (245 KB)
🖼️ relatorio-anual-pg-2.webp (238 KB)
...Informações exibidas:
- Ícone do tipo (PDF ou imagem)
- Nome do arquivo
- Tamanho do arquivo
- Status de processamento
- Número de páginas (para PDFs)
Ações:
- Clicar para expandir/colapsar
- Botão de download (presigned URL)
- Botão de exclusão (apenas SUPER usuários)
Limites e Restrições
Limites de Upload
- Formato: Apenas PDF
- Quantidade: 1 arquivo por vez
- Tamanho máximo: Configurável (padrão: 50MB)
- Páginas: Sem limite definido
Limites de Armazenamento
- Cloudflare R2: Depende do plano contratado
- Duplicatas: Máximo 100 por dia por nome
- Presigned URLs:
- Upload: 15 minutos de validade
- Download: 1 hora de validade
Timeouts
- Upload: Sem timeout (depende da conexão)
- Processamento: 10 minutos por PDF
- Conversão: 1 minuto por página
Segurança
Upload Direto para R2
Vantagens:
- Backend não manipula arquivo
- Reduz carga no servidor
- Upload mais rápido
- Presigned URL segura (15 min)
Fluxo:
1. Frontend requisita URL presigned
2. Backend gera URL com permissões temporárias
3. Frontend faz PUT direto para R2
4. Backend confirma upload bem-sucedidoProteção de Arquivos
Arquivos não podem ser deletados se:
- Vinculados a documentos ativos
- PDF com páginas vinculadas a documentos
Apenas SUPER usuários podem deletar arquivos:
- Verificação de permissão CASL
- Validação de uso antes de deletar
- Mensagem de erro se arquivo em uso
Próxima Etapa
Continue para Busca Full-Text para aprender sobre o sistema de busca.