New features· 17 items

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

Added3

Dá para abrir um dia de atendimento pela tela, e não só fechar

A tela de agenda sabia tirar dias da agenda — feriado, férias, viagem — e não sabia o contrário: acrescentar um dia que a jornada semanal não cobre. O botão só fechava, e sempre o dia inteiro.

Agora o mesmo bloco pergunta o que fazer: fechar o dia, como antes, ou abrir para atendimento num intervalo de horas que você escolhe. A lista abaixo continua mostrando os dois, cada um com o seu rótulo.

Se os dias se repetem, dá para abrir o período inteiro de uma vez: preencha repetir toda semana até e o bloco cria toda semana daquele dia da semana até a data limite, pulando o que já estiver cadastrado em vez de duplicar. O limite é um ano por vez.

Quem mais ganha com isso é quem não atende em jornada fixa. Com a jornada semanal vazia, todo dia nasce fechado e só as datas que você abrir passam a oferecer horário — que é como se monta a agenda de quem atende em dias irregulares, às vezes em lugares diferentes no mesmo dia. Antes isso não tinha como ser feito pela tela.

Nada muda para quem já usava: fechar um dia continua fechando o dia inteiro, e os dias já cadastrados seguem como estão.

Anúncios › Meta mostra o Connect rate de cada campanha

A tabela de campanhas de Anúncios › Meta ganhou a coluna Connect rate (visualizações da página ÷ cliques no link), a mesma conta do Gerenciador de Anúncios da Meta, logo depois do CTR. Campanha que não leva ninguém a uma página — mensagem no WhatsApp, por exemplo — mostra "—", e não 0%, porque ali não houve medição. Não há nada a fazer na instalação: os dois números passam a vir na mesma leitura de insights que a tela já fazia, sem chamada nova e sem gastar cota a mais. E a tabela passa a carregar mesmo quando a métrica nova não vem: se a plataforma recusar um dos dois nomes, a tela repete a leitura sem eles — a coluna fica "—" e todas as outras continuam funcionando, em vez de a tela inteira cair. O "—" não é mudo: no hover ele explica que o número não veio da plataforma (vazio não é zero), e o motivo cru que ela devolveu fica no log do servidor — sem esse rastro, a repetição trocaria um erro visível por um erro invisível. Crédito: @webtecnica.

A presença do atendente passa a existir — e a Equipe mostra quem está aí

O produto tinha o leitor do sinal de presença e nunca teve o emissor: o cron attendant-heartbeat derrubava do plantão quem não emitisse sinal de vida há 15 min, e nenhum arquivo do repositório emitia sinal nenhum. O único escritor de last_heartbeat_at era o clique na chave de plantão — um carimbo de clique se fingindo de batida, e a chave se desligava sozinha ~15 min depois de ligada (medido pelo @paulolimajr77 no PR #720).

Agora cada aba aberta emite uma batida a cada 60 s (POST /api/v1/attendants/presence), e "tem alguém aí?" é respondido na hora da pergunta, a partir do carimbo: sem cron de expiração e sem coluna booleana de presença. Fechar a aba não escreve nada no banco — a pessoa some da lista de presentes dentro do prazo, sozinha. Quem lê: a rota de disponibilidade, a escalação (e a ferramenta MCP que o agente usa), o aviso ao lead e a tela de Equipe, com selo presente/ausente e o carimbo.

A presença não toca na decisão. A separação é de tipo, não de disciplina: o predicado do plantão (estaDePlantao, em lib/routing/eligibility.ts) não recebe presença como entrada. Quem tira alguém do plantão é a pessoa (a chave) ou a jornada publicada — nunca o navegador fechando.

Custo: uma escrita por aba a cada 60 s (480 em um turno de 8 h); com a aba fechada, nenhuma.

Changed1

Arrumação interna de quem valida as chaves de integração

A parte do sistema que confere uma chave de integração (as que começam com dsk_) foi separada em duas: a que decide se a chave vale, e a que traduz a recusa para o formato de quem perguntou. Antes as duas eram a mesma peça, e qualquer outro pedaço do sistema que quisesse conferir uma chave tinha de carregar junto o vocabulário de erro de um protocolo que não era o dele.

Nada muda para quem usa ou opera o sistema: as mesmas chaves continuam valendo, as recusas (chave desconhecida, revogada ou vencida) continuam devolvendo exatamente a mesma resposta, e o registro de último uso da chave continua sendo gravado. Você não precisa fazer nada e nenhuma integração precisa ser refeita.

Crédito: @faxamkt.

Fixed13

A IA voltou a conseguir remarcar um atendimento para logo depois do próprio fim quando há intervalo configurado

Com um intervalo antes do atendimento configurado — o respiro entre uma conversa e a seguinte —, a IA que tentava remarcar um atendimento para o horário logo depois do fim dele mesmo recebia uma recusa de "horário não disponível". A culpa era do próprio atendimento sendo movido: ele entrava na conta como se já estivesse ocupando o horário de destino, e o intervalo só aumentava a área onde essa confusão acontecia. Sem intervalo configurado, o mesmo pedido passava.

Agora o atendimento que está sendo remarcado deixa de contar como ocupação contra si mesmo. Nada muda para os vizinhos: o intervalo continua valendo, e mover um atendimento para cima de outro continua sendo recusado como...[truncated]

A tela de atualização para de anunciar uma versão que o servidor não confirmou

Quando uma atualização terminava bem mas o servidor não voltava a se comunicar com o sistema — serviço parado, tarefa agendada removida, credencial vencida —, a tela de atualização anunciava como instalada a versão que o pedido pedia, e continuava anunciando por tempo indeterminado. Se o aplicativo não tivesse subido na versão nova, quem abrisse a tela lia a versão nova enquanto o que estava de fato em execução era a antiga, e não havia como desconfiar do que a tela dizia.

Agora a tela só afirma a versão que o servidor confirmou por último. Nos minutos seguintes ao fim de uma atualização bem-sucedida, ela diz que o pedido terminou, que a confirmação ainda não chegou e qual é a última versão que o servidor confirmou — e não oferece de novo a atualização que acabou de ser feita. Passado o prazo sem nenhuma confirmação, a versão-alvo volta a aparecer como pedido em aberto, com o botão de atualizar de volta: se o aplicativo realmente não subiu, você consegue tentar outra vez pela própria tela.

Nada para configurar. Quem tem o servidor reportando normalmente não vê diferença nenhuma: a tela segue mostrando a versão em execução e volta sozinha ao estado de sempre assim que a confirmação chega. Crédito: @webtecnica.

O agente de IA responde mais rápido no WhatsApp

O turno do agente pagava tempo que não precisava pagar antes de responder ao cliente: os dois classificadores auxiliares — o que sugere a etapa do funil e o que olha sinal de jailbreak — rodavam um depois do outro, e a pausa que dá ar humano à primeira mensagem era somada por cima do tempo que o turno já tinha gastado pensando.

Agora os dois classificadores rodam ao mesmo tempo (o turno espera só o mais lento) e a pausa humana desconta o que já foi esperado. Quando há fila de mensagens acumulada, o escoamento não dorme entre um lote cheio e o próximo.

Nada muda no ritmo de envio que protege o número do WhatsApp, nem no consumo do banco de quem não tem atendimento nenhum: os dois padrões que mexeriam nisso ficaram de fora, esperando medição.

A origem da página sobrevive quando o contato chega pelo WhatsApp

Quando alguém lia uma campanha no site e tocava no botão que abre o WhatsApp, a conversa entrava no CRM como WhatsApp e a origem da página morria ali: o card nascia sem rótulo nenhum, e o relatório de onde vem o negócio perdia justamente o toque que mais custa — o que veio de anúncio ou de landing page.

Agora o link do botão pode carregar a origem junto (utm_source, utm_medium, utm_campaign, gclid) e ela é estampada no contato ANTES do card nascer, de modo que o card já nasce com o rótulo. Mensagem sem código nenhum segue exatamente como era.

O código vale só na primeira mensagem do contato e nunca sobrescreve uma origem já gravada, inclusive a de anúncio: quem chegou de campanha paga primeiro mantém a campanha paga. Ele leva apenas campos de campanha — nenhum dado pessoal — e tem teto de tamanho. Em Configurações → Conversões há a explicação de como montar o link, com um exemplo gerado na hora, e a lista de contatos ganhou o filtro de origem "Site (landing page)".

Falhas de leitura da Agenda do Google voltam a explicar o motivo

Quando o Google recusa a leitura incremental da agenda, o diagnóstico agora usa o motivo estruturado da resposta em vez de gravar apenas Google HTTP N, sem copiar e-mail ou texto livre devolvido pelo provedor.

Conserto do sistema passa a valer já na primeira atualização, não na seguinte

A atualização carregava as rotinas do instalador antes de trocar para a versão nova, então um conserto que vivesse numa dessas rotinas só entrava em vigor na atualização seguinte. Foi o que aconteceu com o conserto que tira o segredo da linha de tarefas agendadas: quem atualizou continuava com a linha antiga até rodar a atualização outra vez.

Não há nada a fazer: a próxima atualização aplica a correção sozinha — e, desta vez, numa passada só.

Dois stacks locais no mesmo host deixam de semear o banco um do outro

Quem mantém mais de um checkout do CRM rodando ao mesmo tempo na mesma máquina — cada stack com o próprio project_id e a própria faixa de portas — deixa de ver os dados de teste de uma sessão aparecerem no banco da outra. O arquivo .env.e2e, que os scripts de seed usam para abrir conexão direta com o banco, passa a receber a porta do Postgres do stack que está de pé, e não mais um endereço fixo da porta padrão: antes, com dois stacks no ar, a segunda sessão semeava o banco da primeira — conexão válida, schema idêntico, suíte verde e o estrago invisível, que é o que fazia o problema sobreviver sem queixa.

Quando o stack não devolve a URL de conexão, o gerador do .env.e2e agora recusa a gerar o arquivo e diz o que faltou, em vez de gravar a porta padrão em silêncio na esperança de acertar. Os scripts de verificação passam a cobrir isso: um teste de shell sobe um stack falso fora da porta padrão e exige que o arquivo gerado acompanhe a porta daquele stack, para que a volta do endereço fixo reprove em vez de passar despercebida.

Na sua instalação na VPS, nada muda: é o ambiente de desenvolvimento e de testes que fica correto.

A checagem de saúde não diz mais que o banco caiu quando ele está de pé

Quem instalou o CRM num projeto Supabase que já servia outra aplicação podia ver a atualização terminar dizendo que o app não respondeu "ok" — com o CRM atendendo normalmente, o login abrindo e os dados todos no lugar.

A causa era da sonda, não do banco. A checagem de saúde consultava a API do Supabase sem dizer em qual schema procurar, e aí valia o schema padrão do projeto — que é public em projeto novo, mas é o da outra aplicação quando ela chegou primeiro. A sonda procurava a tabela no lugar errado, recebia "não existe" e concluía que o banco estava fora. Agora ela pergunta pelo mesmo schema que o CRM usa de verdade.

Isso importa além do susto: a atualização usa essa resposta para decidir se deu certo, e um "não" falso fazia a versão nova ser revertida sozinha logo depois de instalar. Quem atualiza pela tela podia ver a versão voltar ao que era, sem nenhum erro aparecendo no CRM.

Nada muda para quem instalou num projeto Supabase dedicado ao CRM — nesses, a sonda já acertava o schema por acaso, e continua acertando.

PDF com texto selecionável deixa de ser recusado como "só imagens escaneadas"

Ao anexar um PDF em Ensinar algo novo ao agente, todo arquivo — mesmo um com texto normal, selecionável — era recusado com "não consegui extrair texto deste PDF. Se ele for só imagens escaneadas...". A causa não era o arquivo: o pdfjs-dist, a biblioteca que lê o PDF, saía inteiro do build de produção (next build no modo standalone), porque o rastreador de dependências do Next não segue o import() que essa biblioteca usa para o subcaminho que o projeto carrega. O pacote simplesmente não chegava na imagem Docker, e todo PDF — com ou sem texto — falhava do mesmo jeito.

Agora o build inclui o pacote explicitamente, do mesmo jeito que já era feito para o @napi-rs/canvas e o @swc/helpers — e mais um passo que só apareceu testando o caminho real de produção (o Turbopack bundla o pdfjs-dist num chunk próprio, e esse chunk procura o pdf.worker.mjs do pdf.js como arquivo vizinho dentro de .next/server/chunks/, não em node_modules/). PDFs com texto selecionável voltam a ser lidos; a mensagem de "só imagens escaneadas" volta a aparecer só quando o PDF É, de fato, só imagem. Provado com um PDF real de 170 páginas, extraindo texto de ponta a ponta pelo mecanismo de carregamento de chunk que a produção usa.

Prévia do agente volta a listar tipos de atendimento da Agenda

O botão de testar o agente com contato fictício usa novamente a ferramenta real que lista os tipos de atendimento da Agenda antes de procurar horários disponíveis.

Rodar a suíte de testes numa VPS instalada deixa de acusar um erro que não existe

Quem instala o produto numa VPS copia o .env.hostgator.example para .env — é o caminho normal da instalação. Um dos testes do projeto varre o disco procurando repetições do endereço das imagens Docker, e esse arquivo copiado herda o mesmo endereço que o exemplo já tem permissão de conter. Resultado: a suíte reprovava em toda instalação de verdade, apontando para um arquivo que nem é versionado.

O .env e o .env.local são estado da máquina, não código do projeto; o teste passou a ignorá-los, e continua reprovando qualquer arquivo versionado que repita o endereço.

O CI passa a provar que a imagem publicada extrai texto de PDF

Nada muda na sua VPS: nenhuma variável nova, nenhuma migration, nenhum comando. O que muda é o que o pipeline mede antes de a imagem sair — depois que a imagem do app sobe, o job extrai um PDF de amostra DENTRO dela, pelo mesmo caminho que a rota usa, e reprova se o texto não vier.

Uma imagem publicada podia ler PDF de texto como "sem texto": a extração morre dentro dela antes de o arquivo ser aberto, e nenhum job do pipeline media isso. Os testes de extração rodam pelo repositório, onde a peça que falta na imagem existe; o gate de boot só exige que o app suba. O app sobe — a extração é que não funciona, e isso só aparecia quando alguém mandava um PDF de verdade. Enquanto o empacotamento não levar essa peça para a imagem, este passo fica vermelho de propósito: é ele dizendo no CI, na hora de publicar, o que hoje só aparecia no atendimento. Crédito: @webtecnica.

A Zona de perigo volta a apagar os dados operacionais em quem já enviou resposta revisada

Numa organização com resposta revisada, "apagar dados operacionais" parava na primeira tabela: a chave estrangeira de ai_reply_drafts.message_id apontava para messages sem ação de exclusão, então o banco recusava o delete from messages antes de a exclusão das conversas levar os rascunhos junto. O botão prometia apagar seis tabelas e não apagava nenhuma.

A chave passa a on delete set null, como as outras três que apontam para messages. Na Zona de perigo o rascunho continua indo embora com a conversa, que é o dado operacional da organização; o que a ação muda é o caminho inverso — apagar uma mensagem avulsa deixa de travar (e deixa de arrastar o rascunho). Um invariante de banco novo cobre o caso: organização com resposta revisada apagada por inteiro, e a organização vizinha intacta.

Nada muda para quem opera: a correção é de banco e se aplica sozinha na atualização.

Back to all releasesView on GitHub