New features· 14 items

Version 1.19.0

The release notes are written in Portuguese, the language DeskcommCRM is built in. Your browser can translate them, or you can open this page already translated.

Open in Google Translate

Added4

Chamada de voz pelo WhatsApp — desligada por padrão, e com botão de desligar de verdade

O sistema passa a poder fazer e receber chamadas de voz pelo WhatsApp, e ela chega desligada. Atualizar não liga nada: nenhum número seu é conectado a nada, nenhum serviço novo sobe na sua VPS, e nada muda na sua tela até você decidir.

A razão de tanto cuidado está escrita na própria tela, antes do botão: para fazer chamadas, o sistema precisa conectar um segundo aparelho ao mesmo número de WhatsApp que você já usa para atender — e essa conexão não é feita pelo caminho oficial do WhatsApp. Se ele entender isso como uso indevido, quem é bloqueada é a conta, não só a chamada: você perde também as mensagens desse número. Por isso ligar é decisão de quem administra a empresa, exige marcar que leu o aviso, e fica registrado quem aceitou e quando.

E desligar desliga mesmo. Antes, o único botão que existia era o de conectar — não havia caminho de volta: apagar a configuração escondia a tela e deixava o aparelho vinculado ao seu número para sempre, do lado do WhatsApp. Agora, ao desligar, o sistema desconecta o aparelho de verdade e só então marca como desligado; se a desconexão falhar, ele avisa e mantém tudo como estava, em vez de dizer que acabou com o aparelho ainda lá.

No servidor, o serviço de chamada de voz também nasce desligado: ele só é criado quando quem administra a instalação o liga no arquivo de configuração. Quem não usar a chamada de voz não paga por ela — nem em memória da VPS, nem em superfície exposta. O serviço usado é o oficial do projeto WaCalls, fixado por versão exata e com login obrigatório; ele não é acessível pela internet, apenas pelo próprio sistema.

Trabalho original de @eudanielhenrique.

Os e-mails de acesso passam a funcionar (e a ter marca) num Supabase próprio

Quem roda Supabase self-hosted ganha o que só existia na nuvem: e-mail de confirmação de conta e de redefinição de senha com a marca da instalação, e — o que importa mais — com o link que fecha a sessão.

O app passa a servir os dois moldes em /email-templates/confirmation e /email-templates/recovery. Aponte o GoTrue para eles:

GOTRUE_MAILER_TEMPLATES_CONFIRMATION=https://SEU_DOMINIO/email-templates/confirmation
GOTRUE_MAILER_TEMPLATES_RECOVERY=https://SEU_DOMINIO/email-templates/recovery
GOTRUE_MAILER_SUBJECTS_CONFIRMATION="Confirme seu e-mail · SUA MARCA"
GOTRUE_MAILER_SUBJECTS_RECOVERY="Redefinir sua senha · SUA MARCA"

Nada muda para quem não apontar, e nada muda na nuvem do Supabase — lá o caminho continua sendo o marca-emails.sh pela Management API.

O kit ensina e confere, mas não escreve — e o motivo é honesto. O GoTrue não é serviço deste compose: o kit sobe app, worker, scheduler, waha, redis, srh e caddy, e o Supabase próprio é outra stack, que pode nem estar na mesma máquina. Escrever nela seria o instalador editar instalação de terceiro. Então o install.sh passa a imprimir as quatro linhas exatas quando a topologia é própria (antes ele mandava o self-hoster para supabase.com/dashboard, que ele não tem), e bash hostgator-setup-kit/healthcheck.sh ganhou uma seção que mede o estado: se o app serve o molde, se algum GoTrue desta máquina aponta para ele, e se o valor configurado é URL — acusando em vermelho o caminho de arquivo que falha calado.

Por que isso conserta e não só embeleza. O modelo padrão do GoTrue linka para /auth/v1/verify, que devolve um code PKCE. O verificador desse code vive num cookie SameSite=Strict, e clique vindo de webmail é navegação cross-site: o cookie não viaja e a sessão nunca fecha. A conta é confirmada, a pessoa entra pela senha, e fica sem organização e sem menu. Os moldes do app linkam com token_hash, que não depende de cookie nenhum.

A marca passa a seguir o banco. O marca-emails.sh lê o .env, então trocar nome ou cor em Configurações › Marca não reescrevia os e-mails de acesso. Servindo pelo app, a marca é resolvida a cada busca e o GoTrue re-busca sozinho a cada 10 minutos (GOTRUE_MAILER_TEMPLATE_MAX_AGE) — sem reiniciar nada e sem rodar script.

Se você seguiu a receita antiga, troque as variáveis. Até esta versão, docs/deploy-selfhost/README.md e o marca-emails.sh mandavam apontar GOTRUE_MAILER_TEMPLATES_* para um caminho de arquivo. Isso não funciona e falha calado: o GoTrue cola o que não começa com http no fim do SITE_URL e faz um GET, então ele busca https://SEU_DOMINIO/opt/.../confirmation.html, recebe o HTML da tela de login e manda isso para a caixa de entrada do cliente. Medido em 2026-09-09; o Gmail marcou como phishing.

Achado instalando numa VPS com Supabase próprio, seguindo a documentação do produto do começo ao fim.

Guias do assistente para quem instala, opera e contribui com um CLI de IA

Com o repositório aberto no Claude Code, Codex, Cursor, OpenCode ou Antigravity, cinco guias carregam sozinhos na hora certa: instalar e consertar a instalação, montar um cliente por nicho (agentes, roteadores, follow-ups, base de conhecimento), analisar as métricas sem expor dado pessoal, afinar o prompt de um agente com dados, e contribuir com um PR que passa na triagem de primeira. O roteiro do kit de instalação foi corrigido (a verificação em duas etapas é opcional; três provedores de IA; token do Supabase) e o banner final do instalador passa a refletir a escolha de telemetria.

Chamada de voz pelo WhatsApp — ligar e atender de dentro do CRM

O CRM passa a fazer e receber ligações de voz pelo WhatsApp. Quem administra pareia um segundo aparelho no mesmo número, em Configurações › Conexões, e o botão Chamar aparece na ficha de todo contato com telefone. Chamada recebida toca para o time inteiro, como um telefone de escritório; o painel da ligação em andamento, com mudo e desligar, é só de quem está na linha.

O que o sistema faz por conta própria enquanto isso acontece:

  • O assistente se cala durante a ligação naquela conversa e volta a falar quando você desliga. Ele não responde por cima de alguém que está ao telefone com o cliente.

  • Ligação atendida conta como contato feito

    O negócio deixa de aparecer como parado no Radar de Risco, e o assistente para de propor "retomar contato" com quem você acabou de atender.

  • Ligação atendida conta como trabalho seu no relatório de atendentes.

  • Chamada perdida vira aviso na Central, com o número de quem ligou, o motivo em português e um botão para ligar de volta.

  • A linha do tempo do negócio diz quem atendeu, não "Sistema".

Três recusas deliberadas, porque o certo é não fazer:

  • Contato que pediu para não ser incomodado não recebe ligação

    Quem mandou "PARAR" já não recebia mensagem; agora também não recebe telefonema.

  • Só quem está na linha desliga

    Ninguém derruba a ligação de um colega.

  • Apagar o canal não apaga o histórico de ligações

    — a exclusão vira arquivamento, e o diálogo diz quantas chamadas estão penduradas antes de você confirmar.

Quem exercer o direito de ser esquecido tem o telefone das chamadas apagado junto com o resto; quem pedir seus dados recebe o registro das ligações no relatório.

Nada muda para quem não parear o recurso: ele é opcional e nasce desligado.

Trabalho original de @eudanielhenrique.

Changed2

A marca do produto ganha símbolo e logotipo

Instalação que não configurou marca própria passa a mostrar o logotipo do Deskcomm no menu lateral, na tela de entrada e no ícone da aba do navegador — no lugar do nome em texto e da letra "D" sobre a cor de destaque. Quem já definiu nome ou logo próprio em Marca não vê nenhuma diferença: a marca configurada continua valendo em todos esses lugares.

No editor de follow-up, dá para organizar o fluxo e excluir um nó ou uma aresta

Montar um follow-up no canvas exigia arrastar cada bloco à mão, e o único botão de apagar era o do fluxo inteiro. Quem errava uma ligação tinha que desfazer o rascunho ou começar de novo.

Agora, no editor, Organizar empilha o fluxo conectado de cima para baixo; Excluir nó e Excluir aresta saem no painel do item selecionado e na barra de cima — sem apagar o fluxo. As ligações passam a ser em degrau (não diagonais por cima dos blocos), e os botões de zoom do canvas seguem o tema escuro em vez de sumirem no fundo branco da biblioteca.

Fixed8

Webhook de captação agora reconhece o formato de lead do RD Station

Ao apontar um webhook do RD Station para uma fonte de captação de leads, os envios reais não viravam lead: o RD Station empacota os dados dentro de uma lista (leads: [...]), e o leitor de campos do webhook só olhava o nível de cima, então nome, e-mail e telefone chegavam "em branco" e a captação era recusada. O botão interno "Enviar lead de teste" funcionava porque manda os campos soltos — o que escondia o problema.

Agora o webhook reconhece esse formato: extrai o nome, o e-mail e o telefone (inclusive quando o telefone vem no campo de celular do RD, e não no campo de telefone comercial, que costuma vir vazio) e cria o lead na fonte/funil/etapa configurados. Reenvio do mesmo evento pelo RD Station não gera lead duplicado.

Os formatos que já funcionavam (campos soltos, Respondi) continuam iguais. Você não precisa fazer nada para adotar — a partir desta versão os leads do RD Station passam a entrar sozinhos.

Preferências de aviso param de divergir entre o servidor e o navegador

A tela de configurações de notificação abria com o navegador discordando do HTML que o servidor tinha mandado. Quem havia desligado o push de mensagem via, por um instante, o interruptor ligado — e o React reagia a essa discordância descartando e refazendo a árvore da tela no cliente.

O valor era lido dentro do inicializador de useState, que roda de novo na hidratação. Sem window, essa leitura devolve o padrão (tudo ligado); com window, devolve o que está no localStorage. Os dois lados não tinham como concordar para quem tivesse mudado qualquer preferência — em dez interruptores e no identificador que a tela de alertas procura.

A tela passou a ler as preferências por useSyncExternalStore, o mesmo mecanismo que o seletor de tema já usa desde o #666: existe um valor determinístico para a comparação de hidratação, e só depois do commit o React troca para o valor real. O interruptor continua respondendo na hora, sem recarregar a página.

Sem mudança de configuração: nada a editar no .env e nenhum passo a mais na atualização.

Achado a partir do relato de que a divergência reaparecia a cada conserto — a leitura do navegador voltava para dentro de um inicializador novo. Junto vem a guarda que reprova esse padrão, para que a terceira instância não nasça igual.

Quem é convidado entra na empresa ao confirmar o e-mail, sem mais um clique

Confirmar o e-mail vindo de um convite passa a criar o vínculo e abrir o CRM já dentro da empresa. Antes, a confirmação levava a uma tela com um botão "Aceitar convite" — e quem não o apertava terminava autenticado, sem organização e sem menu, num CRM vazio.

Três consertos, todos no ciclo de vida do vínculo:

  • O convite é aceito na própria confirmação

    A rota já sabia tudo o que o botão exigia, e com garantia mais forte: o e-mail do convite é comparado com o que o provedor de autenticação acabou de confirmar. Se o vínculo falhar (convite revogado, banco fora), a tela de aceite continua existindo e recebe a pessoa — nada fica sem saída.

  • Clicar duas vezes no link do e-mail não desloga mais ninguém

    O token é de uso único: o segundo clique falhava e mandava para a tela de login quem já estava logado pelo primeiro, com o cookie de sessão intacto. A pessoa reentrava pela senha e perdia o fio do convite. Agora a rota reconhece a sessão que já existe e segue.

  • Acesso revogado deixa de virar convite para abrir empresa

    Quem tinha o vínculo retirado caía numa tela vazia oferecendo "Configure sua organização" — uma revogação virando criação de tenant. Agora vê uma tela que nomeia o que aconteceu, e a ação de recuperação recusa com o motivo certo, em vez da mensagem sobre convite pendente que aparecia por acaso.

Nada muda na configuração: não há variável nova, passo de atualização nem mudança de schema.

Achado instalando numa VPS com Supabase self-hosted, com dois convidados reais que não conseguiram entrar.

Conectar um número de WhatsApp voltou a funcionar

Conectar um número de WhatsApp novo — no onboarding ou pela Central de Conexões — e reconectar um número que caiu falhavam com "Falha na comunicação com o WhatsApp (WAHA)" (waha_create_400), e o canal ficava preso em "Parado" pedindo reparo.

A causa: o identificador interno que o sistema gera para a sessão no WAHA tinha 69 caracteres, e a versão do WAHA que o kit usa recusa identificadores com mais de 54 — então nenhuma sessão nova chegava a ser criada do outro lado. O identificador passou a ter 45 caracteres.

Canais que já ficaram presos por causa disso são consertados na atualização (o identificador é regravado no formato novo); nenhum número já pareado é tocado. Depois de atualizar, quem estava travado é só clicar em Conectar/Reconectar de novo.

Revogar um membro deixa de ser uma porta que só abre por fora

Revogar sumia com a pessoa. Ela desaparecia da lista de Equipe, e a única forma de devolver o acesso era emitir um convite novo — um caminho longo, com três becos, todos medidos numa instalação real com alguém de verdade preso neles.

O que muda:

  • O membro revogado continua na lista, com o estado Revogado, e quem administra devolve o acesso pelo menu da própria linha. Antes ele simplesmente sumia.

  • Quem já tem conta e clica num convite deixa de receber "Não foi possível criar a conta. Tente novamente." — instrução impossível, porque tentar de novo nunca funciona. Passa a ler que já tem conta, com um botão que entra e cai direto no aceite.

  • A tela de acesso revogado deixa de ser beco

    ela diz que, se chegou convite novo, o link do e-mail funciona mesmo dali.

Nada disso mudou o banco. O comando que aceita convite já sabia reativar quem foi revogado, desde que o convite seja posterior à revogação — e foi exatamente isso que a prova em tela confirmou. O que faltava era caminho até ele.

Reativar não promove. Ela devolve o papel que a pessoa tinha; trocar papel continua sendo outra ação, com outra rota. Juntar as duas faria uma reativação distraída virar promoção silenciosa.

Quem devolveu o acesso fica registrado (member.reactivated). A coluna que guarda a revogação volta a ficar vazia e não conta história nenhuma — a trilha é a única resposta para "quem readmitiu esta pessoa, e quando?".

Uma requisição que demora demais não vira mais um erro genérico na tela

Quando uma chamada à API não respondia a tempo, o navegador mostrava um erro genérico ("signal is aborted without reason") em vez de dizer que foi um tempo esgotado. Agora o motivo do cancelamento vem explícito, com o mesmo nome que o resto do produto já usa para timeout — quem lida com o erro consegue reconhecê-lo, e quem só vê a tela entende o que aconteceu.

A chamada de voz avisa quando não há áudio, em vez de contar o tempo em silêncio

O painel da ligação em andamento mostrava o cronômetro correndo assim que o WhatsApp atendia — e o cronômetro continuava correndo mesmo quando o som não chegava ao navegador. Uma ligação muda tinha exatamente a mesma aparência de uma ligação perfeita: nenhum aviso, nenhum sinal, só o relógio. Quem instalou numa VPS ficava sem saber se o problema era o microfone, a rede do escritório ou o produto.

Agora o painel escuta a conexão de áudio de verdade. Enquanto ela está abrindo, ele diz "Abrindo o áudio…". Se ela não abrir, ele diz "Sem áudio: o canal de voz não abriu" — e o cronômetro continua, porque a ligação existe mesmo e o outro lado está esperando. O silêncio deixa de se disfarçar de normalidade.

Se o servidor de voz demorar demais para responder, o aviso aparece em até 12 segundos, em vez de "Abrindo o áudio…" para sempre. E se a conexão se restabelecer depois de um soluço de rede, o aviso some sozinho.

Nada muda para quem não usa chamada de voz.

Trabalho original da chamada de voz de @eudanielhenrique.

O laço rápido do worker volta a montar o admin client

@react-pdf/hyphenate é ESM puro e não expunha a condição require no seu exports. Como o worker roda via tsx (CommonJS), qualquer import de @react-pdf/renderer (usado pela exportação de dados LGPD) derrubava carregarDeps() do drain loop com ERR_PACKAGE_PATH_NOT_EXPORTED — e como register-handlers.ts registra os 12 handlers do event_log num só import chain, isso tirava o laço rápido de TODOS eles, não só do LGPD, caindo pro cron de 1×/min como única rede de segurança.

Patch (patches/@react-pdf__hyphenate.patch) acrescenta a condição require ao exports map — Node 22.12+/24 já sabe carregar ESM via require() quando o mapa permite. Provado no worker real: o warning "event-log drain OFF" some do log de boot.

Back to all releasesView on GitHub