mirror of
https://github.com/melgarafael/DeskcommCRM.git
synced 2026-10-02 09:34:46 +08:00
release(1.34.0): a versão montada a partir dos fragmentos declarados
This commit is contained in:
@@ -1,9 +0,0 @@
|
||||
---
|
||||
impacto: nada_mudou
|
||||
secao: corrigido
|
||||
titulo: 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]
|
||||
@@ -1,11 +0,0 @@
|
||||
---
|
||||
impacto: nada_mudou
|
||||
secao: corrigido
|
||||
titulo: 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.
|
||||
@@ -1,19 +0,0 @@
|
||||
---
|
||||
impacto: nada_mudou
|
||||
secao: corrigido
|
||||
titulo: 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.
|
||||
@@ -1,22 +0,0 @@
|
||||
---
|
||||
impacto: capacidade_nova
|
||||
secao: corrigido
|
||||
titulo: 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)".
|
||||
@@ -1,26 +0,0 @@
|
||||
---
|
||||
impacto: capacidade_nova
|
||||
secao: adicionado
|
||||
titulo: 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.
|
||||
@@ -1,7 +0,0 @@
|
||||
---
|
||||
impacto: nada_mudou
|
||||
secao: corrigido
|
||||
titulo: 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.
|
||||
@@ -1,6 +0,0 @@
|
||||
---
|
||||
impacto: capacidade_nova
|
||||
secao: adicionado
|
||||
titulo: 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.
|
||||
@@ -1,8 +0,0 @@
|
||||
---
|
||||
impacto: nada_mudou
|
||||
secao: corrigido
|
||||
titulo: 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ó.
|
||||
@@ -1,11 +0,0 @@
|
||||
---
|
||||
impacto: nada_mudou
|
||||
secao: corrigido
|
||||
titulo: 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.
|
||||
@@ -1,22 +0,0 @@
|
||||
---
|
||||
impacto: nada_mudou
|
||||
secao: corrigido
|
||||
titulo: 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.
|
||||
@@ -1,11 +0,0 @@
|
||||
---
|
||||
impacto: nada_mudou
|
||||
secao: alterado
|
||||
titulo: 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.
|
||||
@@ -1,22 +0,0 @@
|
||||
---
|
||||
impacto: nada_mudou
|
||||
secao: corrigido
|
||||
titulo: 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.
|
||||
@@ -1,7 +0,0 @@
|
||||
---
|
||||
impacto: nada_mudou
|
||||
secao: corrigido
|
||||
titulo: 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.
|
||||
@@ -1,15 +0,0 @@
|
||||
---
|
||||
impacto: nada_mudou
|
||||
secao: corrigido
|
||||
titulo: 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.
|
||||
@@ -1,28 +0,0 @@
|
||||
---
|
||||
impacto: capacidade_nova
|
||||
secao: adicionado
|
||||
titulo: 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.
|
||||
@@ -1,9 +0,0 @@
|
||||
---
|
||||
impacto: nada_mudou
|
||||
secao: corrigido
|
||||
titulo: 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.
|
||||
@@ -1,10 +0,0 @@
|
||||
---
|
||||
impacto: nada_mudou
|
||||
secao: corrigido
|
||||
titulo: 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.
|
||||
+171
-1
@@ -8,6 +8,175 @@ Se você roda o DeskcommCRM numa VPS, **leia a seção da versão para a qual es
|
||||
|
||||
## [Não lançado]
|
||||
|
||||
## [1.34.0] — 2026-09-18
|
||||
|
||||
### Adicionado
|
||||
|
||||
- **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.
|
||||
|
||||
### Alterado
|
||||
|
||||
- **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.
|
||||
|
||||
### Corrigido
|
||||
|
||||
- **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.
|
||||
|
||||
## [1.33.0] — 2026-09-17
|
||||
|
||||
### Adicionado
|
||||
@@ -5264,7 +5433,8 @@ Primeira versão marcada do DeskcommCRM. O projeto vinha sendo desenvolvido publ
|
||||
|
||||
- **Node 22 é obrigatório para desenvolvimento.** A suíte de invariantes instancia o cliente do Supabase, que exige o `WebSocket` global — nativo apenas a partir do Node 22. Isso não afeta quem apenas hospeda: a VPS roda a imagem pronta.
|
||||
|
||||
[Não lançado]: https://github.com/melgarafael/DeskcommCRM/compare/v1.33.0...HEAD
|
||||
[Não lançado]: https://github.com/melgarafael/DeskcommCRM/compare/v1.34.0...HEAD
|
||||
[1.34.0]: https://github.com/melgarafael/DeskcommCRM/compare/v1.33.0...v1.34.0
|
||||
[1.33.0]: https://github.com/melgarafael/DeskcommCRM/compare/v1.32.1...v1.33.0
|
||||
[1.32.1]: https://github.com/melgarafael/DeskcommCRM/compare/v1.32.0...v1.32.1
|
||||
[1.32.0]: https://github.com/melgarafael/DeskcommCRM/compare/v1.31.1...v1.32.0
|
||||
|
||||
Reference in New Issue
Block a user