New features· 24 items

Version 1.72.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

Added5

Quem escreve fora do horário de atendimento pode receber um aviso configurável — uma vez por contato por período fechado

O turno de quem chega fora da janela do agente era adiado para a abertura sem nenhuma resposta: no sábado à noite, silêncio até segunda de manhã, e a pessoa sem saber se a mensagem chegou. Agora a tela de gatilho do agente (em "Só atender em horário de funcionamento") aceita um texto de aviso, e esse texto é enviado quando a mensagem chega fora da janela, registrado na conversa como Automação. Vale só para quem preencher o texto: o padrão é não enviar nada, e quem não mexer na tela não vê mudança nenhuma.

O aviso só sai DENTRO da janela de envio do número (por padrão 7h–22h, ajustável por canal). Quem escreve entre o fechamento dessa janela e a abertura seguinte (por padrão, das 22h às 7h) não recebe o aviso na hora: o turno fica em silêncio até a janela de envio abrir, e só então o aviso sai, se o horário de atendimento ainda estiver fechado.

Vale no máximo um aviso por contato por período fechado: o período começa quando a janela fecha e só volta a valer quando ela abre e fecha de novo: um segundo contato na mesma noite não recebe de novo, e o contato bloqueado (opt-out/is_blocked), o titular anonimizado e o número interno da própria organização não recebem nunca. O aviso é RESPOSTA a quem escreveu primeiro, não disparo ativo: ele respeita a janela de resposta do número, o warm-up, o teto diário e o espaçamento, e entra na contagem de envios como qualquer outro. Quando o horário de atendimento abre, a IA responde normalmente sobre a conversa que já tem o aviso.

Contribuição de @webtecnica (#2284, refs #1926).

Em coexistência, o que a equipe cadastra no endereço do app WhatsApp Business vira cadastro no CRM

Num número em coexistência (o mesmo número no app WhatsApp Business e na Cloud API), a empresa também cadastra e renomeia contato no endereço do app — o da agenda do celular. A Meta entrega isso no webhook smb_app_state_sync, que o canal oficial descartava: o CRM ficava sem o nome que a equipe usa no celular, e o contato só nascia na primeira mensagem, muitas vezes sem nome nenhum.

Agora esse webhook entra: o contato passa a existir no CRM com o nome do app (o mesmo que a Meta manda para o display_name, pela mesma resolução de número da mensagem recebida e do eco). A aba "API Oficial (Meta)" passa a listar smb_app_state_sync entre os campos a assinar no dashboard da Meta — sem coexistência a Meta não envia esse evento, então assinar é inofensivo para quem não usa.

O que este passo não faz, de propósito: não cria conversa nem mensagem (nada mudou na caixa de entrada, em last_inbound_at, no contador de não lidas nem no agente — ninguém trocou mensagem), não sobrescreve um nome que já existe no CRM (o coalesce da fn_upsert_wa_contact preenche quando vazio e nunca sobrescreve), e não apaga cadastro quando o app remove o contato do endereço (a Meta manda remove sem nome, e apagar histórico do CRM por causa da agenda do celular não está provado em lugar nenhum).

A importação do histórico de conversas (history, até 180 dias) e o botão de Embedded Signup com coexistência continuam fora deste passo — ver a #1632.

Contribuição de @webtecnica (#2283, refs #1632).

Relatórios ganham a aba Por etiqueta

Em Relatórios (Atividades) há agora a aba Por etiqueta: para cada etiqueta em uso, quantas conversas ela teve no período (7, 30 ou 90 dias), quantas estão abertas e quantas foram resolvidas, a espera média e a fatia do total. Espera que não foi medida aparece como "—", nunca como zero, e período sem conversa etiquetada diz o motivo em vez de mostrar uma tabela de zeros. A aba abre direto por /app/activities?aba=etiquetas.

Cada etiqueta leva à caixa de entrada na aba Todas, já filtrada por ela. O número da linha e a lista podem não bater: a lista também traz as conversas em que a etiqueta está no contato, e não se limita ao período.

Nada a fazer na VPS.

Contribuição de @webtecnica (#2285, refs #1891).

Empresas ganham consulta de CNPJ antes de criar, edição e exclusão na tela, e o CNPJ mantém a máscara quando o enriquecimento falha

No cadastro de uma empresa nova, o botão "Consultar CNPJ" busca os dados públicos na BrasilAPI e preenche o formulário para revisão antes de criar. Nada é gravado na consulta, e a tela avisa quando o CNPJ já existe na organização. Quando a BrasilAPI recusa a consulta (403) ou está fora do ar, a tela diz que é indisponibilidade e sugere tentar de novo, em vez de dizer que o CNPJ não existe. A consulta exige o papel de gestor, o mesmo da criação.

A tela de detalhe da empresa passa a permitir editar o cadastro e excluir a empresa. Excluir exige o papel de gestor e fica registrado na auditoria. Se houver pessoas vinculadas à empresa, a exclusão é recusada e a tela mostra quantas são.

O CNPJ deixa de perder a máscara (aparecia 33547054000120 no lugar de 33.547.054/0001-20) quando o enriquecimento automático falha.

Nada é preciso fazer na instalação.

Contribuição de @webtecnica (#2273), a partir da issue #1937 de @Fabricio-Point-Machine.

A ficha do negócio lê as pessoas relacionadas ao negócio

A ficha do negócio passa a listar, abaixo do contato principal, as outras pessoas ligadas àquele negócio, com o papel de cada uma e a marca de anonimizado quando for o caso. Nesta versão nenhuma tela grava esse vínculo ainda (é a próxima etapa da #1506), então quem opera não vê diferença. Nenhuma ação do operador.

Contribuição de @webtecnica (#2272, refs #1506, proposta de @Alencaf).

Fixed19

O cron do scheduler que falha deixa de falhar em silêncio — o log do scheduler diz qual rota errou e o que conferir

Quando uma rodada de cron do scheduler falha (o caso clássico é o sync-model-catalog tomando 401 porque o segredo que o agendador manda não é o que o app enxerga, o defeito da #1109), a linha do crontab descartava a saída E o erro do curl (>/dev/null 2>&1), e o crond ignora o status de saída. O resultado era um cron quebrado sem sintoma nenhum em lugar nenhum: o catálogo ficava vazio e ninguém via um log.

Agora o >/dev/null fica só no corpo da resposta. O status continua no STDERR do curl -fsS (é o -S que o imprime) e, quando o comando falha, a linha imprime no STDERR qual rota falhou e o que conferir — os dois chegam ao docker logs do scheduler. Em rodada saudável nada muda: o || só dispara em falha.

Nada a fazer na instalação.

Contribuição de @webtecnica (#2279, refs #1109).

Com o Jev medindo o clima, relatar o problema deixa de passar a conversa para uma pessoa, e a concordância do Jev usa o limiar de cada agente

Quando o Jev decide o clima da conversa (Jev decidindo, ou sem IA de linguagem configurada), a escala dele passa a separar o relato do problema, que fica no neutro e não aciona a passagem, da insatisfação com o atendimento e da hostilidade aberta, que acionam. É a mesma separação que a IA de linguagem ganhou na 1.71.0. Vale para toda organização com o Jev decidindo, sem ajuste nenhum. Quem atende e-commerce deve ver menos passagens para uma pessoa por decepção com produto; para passar mais, suba o limiar do agente.

O cartão do Jev passa a medir a concordância e os clientes irritados pelo limiar de cada agente, gravado em cada mensagem. Mensagens anteriores seguem medidas pelo padrão 0,3 até saírem da janela. Nenhuma ação do operador.

Contribuição de @webtecnica (#2248, refs #2219, #2209).

As ações do assistente numa conversa passam a valer só para o cliente dessa conversa

Durante uma conversa, toda ação do assistente que aponta um registro pelo identificador (contato, conversa, compromisso, retorno, chamado, marcador) só é aceita quando o registro é do cliente com quem a conversa está. Fora disso, a ação é recusada com uma explicação para o assistente seguir a conversa, e nada é alterado. Integrações e pessoas usando o sistema continuam com o mesmo alcance de antes.

Nada a fazer na instalação.

Com as propostas desligadas, Configurações › Propostas esconde o link de modelos, e a tela de modelos explica como ligar em vez de travar em Carregando

Com as propostas comerciais desligadas na organização, a tela Configurações › Propostas deixa de mostrar o link Modelos de proposta, que levava a uma tela que travava em Carregando…. O link segue o valor salvo, então virar a chave sem salvar não o mostra. Quem chega à tela de modelos pela URL vê uma explicação e o atalho para Configurações › Propostas, em vez do aviso cru Not found e de Carregando para sempre. Nenhuma ação do operador.

Contribuição de @webtecnica (#2270, refs #1889).

A atualização não devolve mais INSERT/UPDATE/DELETE em orçamentos de IA nem TRUNCATE do log de auditoria às chaves anon e authenticated

O baseline.sql é reaplicado inteiro em toda atualização. Duas concessões do snapshot ao papel anon eram revogadas só no fim do arquivo: INSERT/UPDATE/DELETE em ai_budgets e TRUNCATE em api_audit_log (o privilégio que a RLS não alcança). Entre as duas pontas, e até a próxima atualização completa se a passada morresse no meio, a chave anônima carregava o privilégio.

Agora as revogações acompanham as concessões, e o estado final não muda: o anon segue sem o que era revogado e com o resto — a leitura escopada por política em ai_budgets e SELECT/INSERT no log de auditoria. Nada é preciso fazer na instalação.

Contribuição de @Tong-bit-art (#2257).

A atualização para de devolver o TRUNCATE do log de auditoria ao papel de serviço, e anon/authenticated perdem o TRUNCATE em orçamentos de IA

O baseline.sql é reaplicado inteiro em toda atualização. Três pontas da mesma família ficaram de fora do #2257: o service_role recuperava TRUNCATE no log de auditoria entre a concessão do snapshot e o bloco da 0258 (mais de 20 mil linhas de janela, de pé até a próxima atualização completa se a passada morresse no meio); e anon/authenticated mantinham TRUNCATE em ai_budgets no estado final, porque a 0160 revogou só INSERT/UPDATE/DELETE.

Agora a revogação acompanha as concessões nos três casos, e o TRUNCATE de ai_budgets sai também do estado final para as duas chaves — ele não passa pela RLS e nenhum consumidor o usa (toda escrita da tabela é service role). Leitura e escrita legítimas seguem como estavam. Nada é preciso fazer na instalação.

Contribuição de @Tong-bit-art (#2259).

A atualização interrompida não deixa mais de pé a versão antiga do portão de anonimização

O baseline.sql reaplica a história da função de anonimizar contato em duas definições. A intermediária, anterior à correção do #2196, ainda usava a checagem de plataforma que ignora o scope do JWT: se uma atualização morresse entre as duas, era essa versão que ficava no banco até a próxima passada completa, e um platform admin support_readonly fora de sessão de suporte voltava a poder anonimizar contato.

A definição intermediária passou a usar a guarda completa (fn_is_platform_admin_full, que já existe no snapshot), então o furo não fica de pé em nenhuma passada. O estado final não muda e nada é preciso fazer na instalação.

Contribuição de @Tong-bit-art (#2256).

A atualização não devolve mais o privilégio de TRUNCATE ao papel anônimo em idempotency_keys

O baseline.sql é reaplicado inteiro em toda atualização. A concessão de ALL ao anon em idempotency_keys vinha do snapshot e a revogação de TRUNCATE — um privilégio que a RLS não alcança — ficava no apêndice: entre as duas pontas, e até a próxima atualização completa se a passada morresse no meio, o papel anônimo carregava o privilégio.

Agora a revogação acompanha a concessão, e o estado final não muda: o anon continua sem TRUNCATE e com os demais privilégios da concessão. Nada é preciso fazer na instalação.

Contribuição de @Tong-bit-art (#2253).

A atualização da VPS deixa de reabrir o gatilho velho da demanda e a permissão anônima das tabelas de conhecimento

O baseline.sql é reaplicado inteiro em toda atualização. Dois trechos eram reconstruídos no começo do arquivo e desfeitos no fim, e cada passada reabria a janela: o gatilho trg_demanda_fecha_com_conversa (substituído pelo modelo de atendimento na migration 0222) voltava a valer até ser derrubado, e quatro tabelas de conhecimento (fontes, versões, chunks e FAQ da IA) recebiam ALL para o papel anon — a chave pública do navegador — até serem revogadas adiante. Se a atualização morresse no meio, o gatilho antigo e a permissão anônima ficavam de pé até a próxima tentativa.

Agora as duas recriações saíram do texto: o gatilho não é mais reinstalado, e as quatro tabelas não são mais concedidas ao anon de passagem. O estado final é o mesmo de antes — nenhuma tela, nenhuma regra e nenhum dado mudam, e nada é preciso fazer na instalação.

Contribuição de @Tong-bit-art (#2250).

A Central para de abrir em dobro os avisos de orçamento de IA (limite atingido e aviso de gasto)

Os dois avisos de orçamento — "o orçamento de IA foi atingido" e "o gasto passou do aviso" — deduplicam por organização e tipo, mas a pergunta "já existe um aberto?" e a escrita não eram atômicas, e dois turnos podem avaliar o gasto no mesmo instante (o worker roda tarefas em paralelo, e o worker de respostas é outro processo). Dois avisos idênticos apareciam para o mesmo problema.

Agora o banco é quem segura: um índice único parcial em agent_inbox_items (organização e tipo, só enquanto o aviso está aberto) recusa a segunda linha. A decisão do orçamento continua chegando inteira — os dois caminhos do motor tratam a recusa como "já havia aviso", sem trocar o bloqueio por um erro. E a escrita do aviso não depende de o índice já existir: para quem atualiza, tanto faz se o código novo sobe antes ou depois do banco — o portão de orçamento segue fechando, e a deduplicação vale a partir do momento em que o índice chega. Os dois tipos convivem abertos, como sempre: um relata que a IA parou, o outro que o gasto passou do aviso e ela segue.

Nada é preciso fazer na instalação.

Contribuição de @Tong-bit-art (#2265, com o refinamento do #2266).

A Central para de abrir em dobro os avisos de IA por título (Jev parado, saldo, endereço sem chave)

Quatro avisos da Central deduplicam pelo TÍTULO — o "O Jev parou de funcionar", o de IA sem saldo, a recusa por endereço sem chave da empresa e o aviso de laço do processamento de eventos. A pergunta "já existe um aberto?" e a escrita não eram atômicas, e os escritores rodam concorrentes: o worker roda tarefas em paralelo e o aviso do Jev chega por dois drenos. Dois avisos idênticos apareciam para o mesmo problema.

Agora o banco é quem segura: um índice único parcial em agent_inbox_items (organização, kind e título, só enquanto o aviso está aberto) recusa a segunda linha, e os quatro escritores tratam a recusa como "já havia aviso" — nenhum deles para de avisar. Avisos que abrem UM POR NEGÓCIO (o espelho de estágio e o de etapa) continuam abrindo um por negócio, mesmo com o título repetido.

Nada é preciso fazer na instalação.

Contribuição de @Tong-bit-art (#2263).

A Central para de abrir em dobro o aviso de resposta a caso obsoleto quando a rota e o worker escrevem no mesmo instante

A resposta de um humano a um caso que já mudou de atendimento abre um aviso na Central ("Resposta registrada; atendimento mudou"), deduplicado por conversa. Dois escritores podiam chegar juntos — a rota que registra a resposta e o worker que percebe a fronteira de serviço velha — e os dois inseriam, porque a pergunta "já existe um aviso aberto?" e a escrita não eram atômicas.

Agora o banco é quem segura: um índice único parcial em agent_inbox_items (organização, kind e conversa, só enquanto o aviso está aberto) recusa a segunda linha. A resposta do humano continua sendo registrada mesmo quando o aviso já existia — o caminho transacional isola o aviso num savepoint, para que a recusa do banco não derrube a resolução do caso. Avisos job_dead de tarefa e de cron seguem como sempre foram, um por ocorrência.

Nada é preciso fazer na instalação.

Contribuição de @Tong-bit-art (#2260).

Numa conversa de atendimento, preencher resposta pronta e abrir conversa ficam no contato da conversa

Numa conversa de atendimento, crm_render_message_template passa a preencher a resposta pronta apenas com os dados do contato daquela conversa: um contact_id ou um lead_id de outro contato é recusado, e sem nenhum dos dois o contato da conversa é usado. Em crm_get_conversation, toda conversa que não é do contato da conversa recebe a mesma recusa. Fora de uma conversa de atendimento (integração, MCP externo, rota HTTP), nada muda.

Numa conversa de atendimento, as consultas do agente a negócios, agenda, retornos e casos ficam no contato da conversa

Numa conversa de atendimento, as consultas do agente a negócios, compromissos, retornos, radar de risco e casos humanos passam a considerar apenas o contato daquela conversa. Um pedido sobre outro cliente recebe a mesma recusa que um identificador inexistente. A busca de contatos encontra o contato da conversa mesmo quando outros nomes parecidos vêm antes na lista.

Fora de uma conversa de atendimento (integração, MCP externo, rota HTTP), nada muda.

A mensagem de um fluxo de follow-up apagado não sai mais depois da exclusão

Apagar um fluxo de follow-up pela tela encerra a inscrição, mas o turno que já estava na fila (ou adiado para a janela de envio, via cron) continuava valendo: o worker enviava a mensagem e só depois conferia se a inscrição ainda existia — o cliente recebia um passo de um fluxo que não existe mais, e o item terminava como concluído, sem aviso.

Agora o turno confere a inscrição antes de qualquer efeito: se ela foi apagada, cancelada, pausada ou já saiu do nó, o envio é descartado em silêncio — a mesma regra que o caminho sem worker já aplicava. Nada muda para quem tem a inscrição viva no nó certo, e nada é preciso fazer na instalação.

Contribuição de @Tong-bit-art (#2261).

Os pedidos de privacidade, os honorários e a lista de conversas de uma conversa de atendimento também ficam do lado do contato do turno

O #2182 escopou ao contato da conversa as quatro leituras do Atender, mas deixou três ferramentas e um conserto de página de fora. Um pedido de EXCLUSÃO de outro cliente (crm_list_privacy_requests) continuava chegando ao modelo como instrução de "não insista com esta pessoa", e o contrato e as parcelas de honorários de um caso que não é desta conversa (crm_get_honorarios_contrato, crm_list_honorarios_parcelas) continuavam trazendo valor, percentual e repasse para o lado de cá. As três agora leem ctx.contatoDoTurno: a lista de privacidade filtra a própria consulta pelo contato do turno, e o contrato e as parcelas são recusados — antes da consulta — quando o caso não é deste cliente, com a mesma resposta para caso que não existe, caso de outra organização e caso de outro cliente.

crm_list_conversations também passa a filtrar o contato NA CONSULTA, e não mais na página que o handler já tinha truncado: com a conversa mais antiga do mesmo cliente fora da primeira página, o agente ouvia que não havia mais nada. No predicado, cursor e has_more voltam a valer, porque a página já é do contato. O 404 de um uuid inexistente em crm_get_conversation_history passa a ser a MESMA recusa de uma conversa de outro cliente, em vez de dois estados distintos para o mesmo "não é seu para ler".

Fora de uma conversa de atendimento — rota HTTP, MCP externo, agente sem conversa —, os pedidos de privacidade, os honorários e o histórico seguem como antes. A lista de conversas muda num ponto: GET /api/v1/conversations passa a aceitar ?contact_id= (antes o parâmetro era ignorado e a resposta era a lista da organização inteira), e crm_list_conversations com contact_id explícito passa a filtrar no banco, então cursor e has_more descrevem as conversas daquele contato. O filtro só estreita a lista, nunca a amplia.

Contribuição de @webtecnica (#2271, refs #2184).

A reunião remarcada para mais longe volta a receber o lembrete da data nova

Quando um lembrete já tinha saído e a reunião era remarcada para uma data mais distante, a data nova ficava sem esse lembrete, porque o sistema guardava apenas que ele já tinha sido enviado, sem dizer para qual data. Agora o lembrete volta a valer para a data nova e sai uma única vez, na hora certa.

Para não mandar duas mensagens em sequência, o lembrete só volta quando a hora dele na data nova fica a pelo menos metade do seu intervalo depois do último envio. Para o lembrete da véspera, isso quer dizer 12 horas: a reunião empurrada para o mesmo horário do dia seguinte recebe a véspera nova, e a reunião empurrada em 30 minutos logo depois de a véspera sair não recebe uma segunda. Remarcar para mais perto não reenvia nada, e o lembrete cuja hora já tinha passado no momento da remarcação continua não saindo. Reuniões remarcadas antes da atualização para a 1.71.0, quando o sistema ainda não registrava a hora da remarcação, seguem como antes.

Contribuição de @webtecnica (#2249, refs #2243).

O Testar do agente mostra a resposta mesmo fora do horário de envio, com um aviso

Fora do horário de envio configurado, o botão Testar do agente escondia a resposta: a regra de ritmo de envio (horário, aquecimento do número e limite diário) barrava o candidato também no teste, onde nada é enviado. Agora o Testar mostra a resposta com o aviso de que, em produção, ela não sairia naquele momento, e o registro da verificação diz que essa regra não foi aplicada ao teste, em vez de dizer que passou. O envio real não muda: fora do horário ele continua barrado. Só essa regra é afrouxada, e só no Testar; a janela de 24 horas do WhatsApp, o descadastro e as regras de conteúdo seguem na mesma cadeia de antes, e o rascunho do modo assistido continua barrado fora do horário.

Na aba Execuções, as rodadas do Testar passam a aparecer como teste. Os rascunhos do modo assistido, que atendem conversas reais, continuam aparecendo como produção. E um teste que não produziu resposta deixa de aparecer como aprovado.

Contribuição de @leadframeassessoria-lab (#2254, substitui #2252).

O follow-up pausado volta a andar quando o atendimento humano termina

Um follow-up que estava no passo de enviar mensagem e ficava pausado porque uma pessoa assumiu o atendimento perdia o envio em silêncio: o turno rodava durante a pausa, era descartado, e nada ficava registrado. Quando o atendimento era retomado, o motor continuava achando que aquele turno estava em voo — só conferia de novo, nunca mandava outro — e o fluxo ficava parado no mesmo passo até o sistema marcá-lo como morto por tempo esgotado, com um motivo que não era o verdadeiro.

Agora o descarte durante a pausa deixa o mesmo rastro que a suspensão da conta já deixava: na retomada, o passo é reenfileirado e a mensagem sai normalmente. Follow-ups encerrados, passos que já andaram e fluxos apagados continuam descartados em silêncio, como antes — e nada precisa ser feito na instalação.

Contribuição de @webtecnica (#2277, refs #2262).

← Back to all releasesView on GitHub