Novedades· 17 ítems

Versión 1.34.0

Las notas de versión están escritas en portugués, el idioma en que se desarrolla DeskcommCRM. Tu navegador puede traducirlas, o puedes abrir esta página ya traducida.

Abrir en Google Traductor

Agregado3

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.

Cambiado1

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.

Corregido13

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.

Volver a todas las versionesVer en GitHub