Skip to content

PRD: Página de Monitoramento Unificada + Captura de Foto de Perfil via Pastorini #245

Description

@joel299

Problem Statement

Operadores e administradores do ISing precisam acessar três áreas de configuração distintas — Buffer Humanizado, Personas & IA e Disparo em Massa — em páginas separadas, o que fragmenta o fluxo de trabalho e dificulta a visão geral do sistema.

Além disso, os cards de contato e conversas não exibem a foto de perfil do WhatsApp do cliente, tornando a identificação visual lenta e obrigando o operador a memorizar nomes ou números. Quando chega uma mensagem de um novo contato, o sistema não busca automaticamente a foto de perfil na Pastorini API, e não existe nenhum mecanismo de sincronização em lote para contatos existentes.

Por fim, o sistema não filtra adequadamente JIDs de broadcast (@lid) e grupos (@g.us) na camada de negócio, o que pode causar tentativas de busca de foto para JIDs inválidos e poluição do banco de dados.


Solution

1. Página de Monitoramento Unificada: Consolidar as páginas de Buffer, Personas e Disparo em uma única página com quatro abas — adicionando uma aba nova de Leads & Fotos. A navegação entre abas persiste o estado localmente para não perder configurações ao trocar de aba.

2. Captura Automática de Foto de Perfil: Ao receber webhook de mensagem de novo contato com JID válido (@s.whatsapp.net), o backend dispara em background uma requisição à Pastorini API para buscar a foto de perfil e armazena a URL no banco. O frontend recebe a atualização via WebSocket e exibe o avatar em tempo real.

3. Sincronização em Lote: A aba Leads & Fotos expõe um componente de sincronização que busca todos os contatos com status pending e processa em batches de 10, com barra de progresso e estatísticas em tempo real.

4. Validação de JID: Utilitários centralizados garantem que apenas JIDs de clientes diretos (@s.whatsapp.net) sejam processados, rejeitando broadcasts (@lid) e grupos (@g.us) em todas as camadas.


User Stories

Monitoramento Unificado

  1. Como operador, quero acessar o Buffer Humanizado, Personas & IA, Disparo em Massa e Leads & Fotos em um único lugar, para não precisar navegar entre múltiplas páginas para configurar o sistema.
  2. Como operador, quero que a aba ativa seja lembrada entre sessões, para retomar o trabalho exatamente onde parei sem renavegar.
  3. Como operador, quero visualizar um indicador visual claro de qual aba está ativa, para saber em que contexto de configuração estou trabalhando.
  4. Como operador, quero que as abas carreguem de forma independente, para que uma aba lenta não bloqueie o acesso às outras.
  5. Como administrador, quero poder acessar a página de Monitoramento diretamente pelo menu lateral, para não precisar lembrar URLs específicas.

Foto de Perfil — Captura Automática

  1. Como operador, quero que ao receber uma mensagem de um novo contato a foto de perfil do WhatsApp seja capturada automaticamente, para que o avatar apareça no card de conversa sem intervenção manual.
  2. Como operador, quero que a foto seja exibida em tempo real após a captura (sem recarregar a página), para que a tela de atendimento se atualize fluidamente.
  3. Como operador, quero ver um avatar com as iniciais do nome como fallback quando o contato não possui foto de perfil, para que o card nunca fique com uma imagem quebrada ou em branco.
  4. Como operador, quero que uma foto que falhou de ser buscada seja marcada como "erro" e não tente novamente indefinidamente, para que a fila de sincronização não fique bloqueada.
  5. Como administrador, quero que contatos de grupos e listas de broadcast sejam automaticamente ignorados no processo de captura de foto, para não gerar requisições desnecessárias à Pastorini API.

Foto de Perfil — Sincronização em Lote

  1. Como administrador, quero iniciar uma sincronização de fotos para todos os contatos existentes com status pendente, para atualizar a base histórica de uma só vez.
  2. Como administrador, quero acompanhar o progresso da sincronização em tempo real (barra de progresso, contador de processados/atualizados/falhas), para saber quanto tempo ainda levará e se há erros.
  3. Como administrador, quero que a sincronização processe os contatos em batches de tamanho configurável, para não sobrecarregar a Pastorini API com requisições simultâneas.
  4. Como administrador, quero poder sincronizar individualmente a foto de um contato específico clicando em um botão no card, para corrigir pontualmente registros desatualizados.
  5. Como administrador, quero filtrar os leads por status de foto (todos, pendentes, com foto, sem foto, erro), para priorizar ações de sincronização.

Aba Leads & Fotos

  1. Como administrador, quero visualizar todos os contatos diretos (não grupos) em um grid de cards com foto de perfil, nome e número formatado, para ter uma visão de identidade dos clientes.
  2. Como administrador, quero ver estatísticas no rodapé da aba (total, com foto, sem foto, erros), para entender rapidamente a cobertura de fotos da base.
  3. Como administrador, quero que contatos com JID @lid (broadcast) e @g.us (grupo) sejam filtrados e não apareçam na listagem, para manter o foco em clientes individuais.

Validação de JID

  1. Como desenvolvedor, quero usar uma função centralizada isValidClientJid para validar JIDs em frontend e backend, para garantir consistência de filtragem em todas as camadas.
  2. Como desenvolvedor, quero que o JID 5511999999999@s.whatsapp.net seja formatado como +55 11 99999-9999 nos displays do frontend, para melhorar a legibilidade.
  3. Como desenvolvedor, quero poder normalizar um número de telefone em qualquer formato para o JID padrão do WhatsApp, para aceitar uploads de listas de leads em formatos variados.

Implementation Decisions

Frontend — Módulos a Criar ou Modificar

  • MonitoringPage (container): Gerencia estado da aba ativa, persiste no localStorage, renderiza TabNavigation e o painel da aba selecionada. Mantém estética LiquidGlass existente no projeto.
  • TabNavigation: Componente de navegação entre as quatro abas. Recebe mapa de abas com label e ícone, emite evento de mudança. Sem lógica de negócio — apenas apresentação.
  • BufferTab, PersonasTab, DispatchTab: Conteúdo migrado das páginas atuais (BufferPage, PersonasPage, DisparadorPage) sem alteração de funcionalidade. As páginas originais são removidas após migração validada.
  • LeadsTab: Aba nova. Busca contatos via API filtrados por instância e status de avatar. Filtra JIDs inválidos no frontend. Renderiza grid de LeadCard e o componente ProfilePictureSync.
  • LeadCard: Card de lead com avatar (imagem ou fallback de iniciais), nome (pushName), número formatado, badge de status e botão de sincronização individual. Trata erros de carregamento de imagem com fallback automático.
  • ProfilePictureSync: Busca contatos pendentes, processa em batches de 10 com intervalo entre batches, atualiza barra de progresso e estatísticas em tempo real.
  • Utilitário JID (jid.ts): Funções puras: isValidClientJid, extractPhoneFromJid, formatJid, normalizeJid, validateJidBatch, normalizeJidBatch. Sem dependências externas — 100% testável em isolamento.
  • Tipos TypeScript: Novos tipos LeadWithAvatar, AvatarStatus, SyncProfilePicturesRequest, SyncProfilePicturesResponse, ProfilePictureResponse e eventos WebSocket associados.
  • Sidebar: Adicionar entrada "Monitoramento" apontando para /monitoring. Remover entradas separadas das páginas unificadas.
  • Roteamento: Adicionar rota /monitoringMonitoringPage. Remover rotas das páginas que foram unificadas.

Backend — Módulos a Criar ou Modificar

  • PastoriniClient (extensão): Adicionar método get_profile_picture(instance_id, jid) que faz GET /api/instances/{instance_id}/profile-picture/{jid} e retorna a URL ou None. Adicionar método sync_profile_pictures_batch(instance_id, jids, concurrency=5) que usa asyncio.Semaphore para paralelismo limitado.
  • Endpoint GET /api/v1/profile-pictures/{jid}: Busca em cache no banco primeiro. Se avatar_url está preenchida retorna de cache. Caso contrário, chama Pastorini, persiste e retorna. Parâmetros de query: instance_id (obrigatório).
  • Endpoint POST /api/v1/profile-pictures/sync: Recebe lista de JIDs no body. Valida cada JID (rejeita @lid e @g.us). Chama sync_profile_pictures_batch. Retorna { updated, failed, failures }.
  • Webhook (atualização): No handler de mensagens inbound, após validar o JID (@s.whatsapp.net, não @lid), agendar fetch_and_store_profile_picture como BackgroundTask. Armazenar pushName quando disponível no payload.
  • Modelo Contact (migração): Adicionar colunas: avatar_status ENUM('pending','fetched','not_found','error') DEFAULT 'pending', avatar_fetched_at TIMESTAMP NULL, avatar_retry_count INT DEFAULT 0, push_name VARCHAR(255) NULL. Criar índice em (avatar_status, avatar_fetched_at) para queries de sincronização.
  • Evento WebSocket profile_picture_updated: Após persistir foto de perfil com sucesso, emitir evento à instância com { jid, avatar_url, push_name } para atualização em tempo real no frontend.

Decisões Arquiteturais

  • Cache-first para foto de perfil: O endpoint de foto consulta o banco antes de chamar a Pastorini API, reduzindo chamadas externas para contatos já processados.
  • Background task no webhook: A busca de foto não bloqueia a resposta do webhook. O Pastorini recebe 200 OK imediatamente; a foto chega ao frontend via WebSocket quando pronta.
  • Semaphore para batch: Limitar concorrência a 5 requisições simultâneas na Pastorini protege contra rate-limiting (429).
  • Persist avatar_status: O campo de status evita retentativas infinitas e permite filtragem eficiente na aba Leads.
  • Filtragem de JID em todas as camadas: Frontend filtra antes de exibir, backend valida antes de processar, webhook valida antes de agendar task.

Contratos de API

Método Endpoint Descrição
GET /api/v1/profile-pictures/{jid}?instance_id={id} Obter foto de um contato (cache-first)
POST /api/v1/profile-pictures/sync Sincronizar fotos em lote (body: {jids:[...]})
GET /api/v1/contacts?instance_id={id}&avatar_status={status} Listar contatos com filtro de status de avatar

Testing Decisions

Princípio: Testes validam comportamento externo observável — o que o usuário ou sistema vê — não detalhes de implementação interna.

Utilitários de JID (alta prioridade — lógica de negócio crítica)

  • isValidClientJid deve retornar true para @s.whatsapp.net, false para @lid, false para @g.us, false para string vazia ou null
  • normalizeJid deve converter +55 (11) 99999-99995511999999999@s.whatsapp.net
  • normalizeJid deve lançar erro para números com menos de 10 dígitos
  • formatJid deve retornar +55 11 99999-9999 para JID brasileiro de 13 dígitos

Endpoint de Foto de Perfil (integração)

  • GET /profile-pictures/{jid} com contato que já tem avatar_url no banco deve retornar sem chamar Pastorini
  • GET /profile-pictures/{jid} com contato novo deve chamar Pastorini e persistir resultado
  • POST /profile-pictures/sync deve rejeitar JIDs @lid e @g.us antes de processar
  • POST /profile-pictures/sync deve retornar { updated, failed } mesmo se alguns JIDs falharem

LeadCard (componente)

  • Deve renderizar a imagem quando avatar_url é válida
  • Deve renderizar fallback de iniciais quando avatar_url é null ou imagem falha ao carregar
  • Botão de sincronização deve ficar desabilitado durante operação em andamento

ProfilePictureSync (componente)

  • Deve exibir barra de progresso com percentual correto durante sincronização
  • Deve chamar onSyncEnd quando o processamento de todos os batches completar
  • Deve continuar processando batches subsequentes mesmo se um batch falhar

Out of Scope

  • Download e armazenamento local das imagens de perfil (URLs externas da Pastorini são usadas diretamente)
  • Atualização automática periódica de fotos de perfil (as fotos do WhatsApp expiram — esse mecanismo de refresh não está no escopo)
  • Exibição de foto de perfil na janela de chat durante o atendimento (escopo deste PRD é apenas a aba Leads & Fotos e o card de conversa na lista)
  • Suporte a grupos e listas de broadcast no gerenciamento de fotos
  • Upload manual de foto de perfil pelo operador
  • Configuração de Typebot, Chatwoot ou AI Agent dentro da página de Monitoramento
  • Implementação de BufferTab, PersonasTab e DispatchTab — apenas a migração das páginas existentes para o novo container

Further Notes

  • O campo pushName (nome do contato no WhatsApp) deve ser capturado no webhook quando disponível e armazenado em contacts.push_name. O frontend deve priorizar pushName sobre name para exibição.
  • A Pastorini API retorna { "url": null } (não 404) quando o contato não tem foto. O backend deve tratar ambos os casos como avatar_status = 'not_found'.
  • URLs de foto de perfil do WhatsApp têm prazo de expiração (expires_at). Não é necessário implementar refresh automático neste PRD, mas o campo de data de busca (avatar_fetched_at) deve ser mantido para facilitar a implementação futura.
  • O estilo LiquidGlass já existente no projeto deve ser mantido em todos os novos componentes — usar as variáveis CSS --glass-*, --border-subtle, --color-brand, etc. já definidas em globals.css e liquidglass.css.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions