mirror of
https://github.com/melgarafael/DeskcommCRM.git
synced 2026-10-02 01:28:34 +08:00
Merge remote-tracking branch 'origin/main' into tri-2008
This commit is contained in:
@@ -1,13 +0,0 @@
|
||||
---
|
||||
impacto: capacidade_nova
|
||||
secao: adicionado
|
||||
titulo: O tempo que o agente espera antes de responder passa a ser ajustável por número
|
||||
---
|
||||
|
||||
A proteção de envio de cada número (em Conexões › Proteção de envio) ganhou quatro campos para o "tempo de pensar" do agente antes da primeira mensagem da resposta: o tempo para ver a notificação, o tempo por caractere digitado, o mínimo e o máximo. Campo vazio mantém exatamente o ritmo de antes (0,9 s + 22 ms por caractere, entre 1,2 s e 7,5 s). Um mínimo acima do máximo é recusado na hora, com a explicação na tela.
|
||||
|
||||
Quando a resposta sai em várias mensagens, o intervalo entre elas passa a seguir o "intervalo entre mensagens" configurado para o número, em vez de um valor fixo de 1,2 s a 2 s. Quem nunca mudou esse intervalo não nota diferença; quem mudou vê as mensagens da resposta seguindo o valor escolhido.
|
||||
|
||||
Não é preciso fazer nada na instalação: a atualização acrescenta as colunas sozinha.
|
||||
|
||||
Contribuição de @webtecnica (#1996), fechando a #653.
|
||||
@@ -0,0 +1,7 @@
|
||||
---
|
||||
impacto: nada_mudou
|
||||
secao: corrigido
|
||||
titulo: O agente respeita os funis autorizados ao atualizar a etapa de um negócio
|
||||
---
|
||||
|
||||
Ao confirmar uma etapa durante a conversa, o agente agora passa ao espelho do CRM os funis autorizados em sua versão publicada. Um agente sem acesso ao funil não move mais o cartão, inclusive quando retoma um caso interno; agentes com o funil autorizado continuam podendo atualizar a etapa. O estado da conversa e a resposta ao cliente seguem seu fluxo normal.
|
||||
@@ -1,8 +0,0 @@
|
||||
---
|
||||
impacto: capacidade_nova
|
||||
secao: adicionado
|
||||
titulo: Espera antes de responder (janela de rajada) configurável por agente
|
||||
---
|
||||
No cadastro do agente, em "Freios de segurança", agora dá para ajustar quanto tempo o agente espera antes de responder mensagens do mesmo contato que chegam em partes (ex.: "Obrigada" e, 14 s depois, "Tem piscina?"). Deixar o campo vazio usa a janela padrão da instalação (8 s), e o valor máximo é 60 segundos — nada de travar o atendimento sem querer. Não muda nada para quem não mexer: o campo é opcional e o comportamento de antes (janela padrão da instalação) segue valendo quando ele está vazio.
|
||||
|
||||
Contribuição de @webtecnica (#1997).
|
||||
+18
-1
@@ -8,6 +8,22 @@ Se você roda o DeskcommCRM numa VPS, **leia a seção da versão para a qual es
|
||||
|
||||
## [Não lançado]
|
||||
|
||||
## [1.67.0] — 2026-09-30
|
||||
|
||||
### Adicionado
|
||||
|
||||
- **O tempo que o agente espera antes de responder passa a ser ajustável por número** A proteção de envio de cada número (em Conexões › Proteção de envio) ganhou quatro campos para o "tempo de pensar" do agente antes da primeira mensagem da resposta: o tempo para ver a notificação, o tempo por caractere digitado, o mínimo e o máximo. Campo vazio mantém exatamente o ritmo de antes (0,9 s + 22 ms por caractere, entre 1,2 s e 7,5 s). Um mínimo acima do máximo é recusado na hora, com a explicação na tela.
|
||||
|
||||
Quando a resposta sai em várias mensagens, o intervalo entre elas passa a seguir o "intervalo entre mensagens" configurado para o número, em vez de um valor fixo de 1,2 s a 2 s. Quem nunca mudou esse intervalo não nota diferença; quem mudou vê as mensagens da resposta seguindo o valor escolhido.
|
||||
|
||||
Não é preciso fazer nada na instalação: a atualização acrescenta as colunas sozinha.
|
||||
|
||||
Contribuição de @webtecnica (#1996), fechando a #653.
|
||||
|
||||
- **Espera antes de responder (janela de rajada) configurável por agente** No cadastro do agente, em "Freios de segurança", agora dá para ajustar quanto tempo o agente espera antes de responder mensagens do mesmo contato que chegam em partes (ex.: "Obrigada" e, 14 s depois, "Tem piscina?"). Deixar o campo vazio usa a janela padrão da instalação (8 s), e o valor máximo é 60 segundos — nada de travar o atendimento sem querer. Não muda nada para quem não mexer: o campo é opcional e o comportamento de antes (janela padrão da instalação) segue valendo quando ele está vazio.
|
||||
|
||||
Contribuição de @webtecnica (#1997).
|
||||
|
||||
## [1.66.1] — 2026-09-30
|
||||
|
||||
### Corrigido
|
||||
@@ -9645,7 +9661,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.66.1...HEAD
|
||||
[Não lançado]: https://github.com/melgarafael/DeskcommCRM/compare/v1.67.0...HEAD
|
||||
[1.67.0]: https://github.com/melgarafael/DeskcommCRM/compare/v1.66.1...v1.67.0
|
||||
[1.66.1]: https://github.com/melgarafael/DeskcommCRM/compare/v1.66.0...v1.66.1
|
||||
[1.66.0]: https://github.com/melgarafael/DeskcommCRM/compare/v1.65.0...v1.66.0
|
||||
[1.65.0]: https://github.com/melgarafael/DeskcommCRM/compare/v1.64.1...v1.65.0
|
||||
|
||||
@@ -3463,6 +3463,7 @@ async function executarTurnoDoAgente(
|
||||
tenantId,
|
||||
leadId,
|
||||
toStage: update.transition.to,
|
||||
...(agentConfig !== null ? { pipelineIds: agentConfig.pipelineIds } : {}),
|
||||
...(update.transition.reason !== undefined
|
||||
? { reason: update.transition.reason }
|
||||
: {}),
|
||||
|
||||
@@ -24,6 +24,39 @@ describe('mirrorLeadStageToCrm', () => {
|
||||
});
|
||||
});
|
||||
|
||||
it('respeita os funis da versão publicada — sem funil autorizado o card não se move', async () => {
|
||||
const sync = vi.fn().mockResolvedValue({
|
||||
moveu: false, motivo: 'fora_do_escopo', detalhe: 'nenhum funil liberado para este assistente',
|
||||
});
|
||||
|
||||
const r = await mirrorLeadStageToCrm(
|
||||
db, cfg as never,
|
||||
{ tenantId: 'org-1', leadId: 'contato-1', toStage: 'qualified', pipelineIds: [] },
|
||||
{ sync },
|
||||
);
|
||||
|
||||
expect(sync).toHaveBeenCalledWith(cfg.supabase, {
|
||||
organizationId: 'org-1', contactId: 'contato-1', passo: 'qualified', escopoDeFunis: [],
|
||||
});
|
||||
expect(r).toMatchObject({ ok: false, reason: 'fora_do_escopo' });
|
||||
});
|
||||
|
||||
it('leva ao sincronizador somente os funis que o agente publicado pode alterar', async () => {
|
||||
const sync = vi.fn().mockResolvedValue({ moveu: true, motivo: 'movido' });
|
||||
|
||||
const r = await mirrorLeadStageToCrm(
|
||||
db, cfg as never,
|
||||
{ tenantId: 'org-1', leadId: 'contato-1', toStage: 'qualified', pipelineIds: ['funil-comercial'] },
|
||||
{ sync },
|
||||
);
|
||||
|
||||
expect(r).toEqual({ ok: true });
|
||||
expect(sync).toHaveBeenCalledWith(cfg.supabase, {
|
||||
organizationId: 'org-1', contactId: 'contato-1', passo: 'qualified',
|
||||
escopoDeFunis: ['funil-comercial'],
|
||||
});
|
||||
});
|
||||
|
||||
it('estágio já ocupado é sucesso, não falha', async () => {
|
||||
const sync = vi.fn().mockResolvedValue({ moveu: false, motivo: 'ja_esta_la' });
|
||||
const r = await mirrorLeadStageToCrm(
|
||||
|
||||
@@ -237,7 +237,14 @@ interface Deps {
|
||||
export async function mirrorLeadStageToCrm(
|
||||
_db: Queryable,
|
||||
cfg: CrmEdgeConfig,
|
||||
input: { tenantId: string; leadId: string; toStage: LeadStage; reason?: string },
|
||||
input: {
|
||||
tenantId: string;
|
||||
leadId: string;
|
||||
toStage: LeadStage;
|
||||
reason?: string;
|
||||
/** Funis autorizados na versão publicada; ausente só no caminho legado. */
|
||||
pipelineIds?: readonly string[];
|
||||
},
|
||||
deps: Deps = {},
|
||||
): Promise<MirrorResult> {
|
||||
const sync = deps.sync ?? sincronizaEstagioDoAgente;
|
||||
@@ -249,6 +256,7 @@ export async function mirrorLeadStageToCrm(
|
||||
organizationId: input.tenantId,
|
||||
contactId: input.leadId,
|
||||
passo: input.toStage,
|
||||
...(input.pipelineIds !== undefined ? { escopoDeFunis: input.pipelineIds } : {}),
|
||||
});
|
||||
|
||||
if (r.moveu || r.motivo === 'ja_esta_la') return { ok: true };
|
||||
|
||||
@@ -88,3 +88,14 @@ describe("todo montador de ferramentas do turno passa o escopo", () => {
|
||||
).toEqual([]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("o espelho de etapa recebe o escopo do agente publicado", () => {
|
||||
it("o turno não omite os funis ao chamar mirrorLeadStageToCrm", () => {
|
||||
const fonte = readFileSync(resolve(RAIZ, "lib/agent-engine/agent/inbound-turn.ts"), "utf8");
|
||||
const chamada = fonte.match(/const mirror = await mirrorLeadStageToCrm\([\s\S]*?\n\s*\}\);/);
|
||||
expect(chamada, "o turno não chama o espelho do CRM").not.toBeNull();
|
||||
expect(chamada?.[0], "o espelho ignoraria os funis autorizados na versão publicada").toMatch(
|
||||
/pipelineIds: agentConfig\.pipelineIds/,
|
||||
);
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user