Skip to content

About

Plugin de exemplo para o Novus CRM: lista as vendas do contato no ERP C-Plus 5.

Topics

Resources

Stars

2 stars

Watchers

0 watching

Forks

Repository files navigation

Plugin de exemplo para o Novus CRM

Plugin de exemplo para o Novus CRM

Consulte as vendas do contato no C-Plus 5 dentro do atendimento. Sem build ou dependências no navegador.

Protocolo · Publicação · MIT

O que você vai criar

Este repositório contém um plugin estático. O botão aparece na barra lateral direita do atendimento. Ao selecionar um atendimento, o painel encontra a pessoa no Novus CRM e o cliente no C-Plus 5. Depois mostra as vendas com busca e paginação.

O mesmo código funciona com Servidor próprio ou Hospedado no Novus. A diferença é onde os arquivos ficam e como você publica uma versão.

Arquivo Para que serve
index.html Painel exibido no iframe
plugin.js Protocolo postMessage e lógica de vendas
novus-plugin.json Versão, entrada e permissões do pacote hospedado
README.md Configuração e publicação deste exemplo

Escolha como publicar

Servidor próprio Hospedado no Novus
Cadastro Título e URL da página Título e domínio do plugin
Arquivos No seu servidor, com HTTPS Num pacote .zip enviado ao Novus CRM
Permissões Informadas no cadastro Lidas de novus-plugin.json
Versões Você gerencia no seu servidor Envio, pré-visualização, publicação e reversão na tela Versões

O registro em plugin.js decide onde o botão aparece. O cadastro escolhe a hospedagem, o título e quem pode usar o plugin.

Servidor próprio

Hospede index.html e plugin.js em HTTPS. A página precisa aceitar iframe. Não envie X-Frame-Options ou Content-Security-Policy: frame-ancestors que impeçam a incorporação pelo Novus CRM.

Em Opções > Plugins, escolha Servidor próprio e informe o título e a URL completa de index.html. Declare os dois hosts, as duas variáveis e widgetbar conforme o manifesto abaixo. O arquivo novus-plugin.json continua como referência, mas o Novus CRM não o lê da sua hospedagem.

Hospedado no Novus

  1. Em Opções > Plugins, clique em Incluir, escolha Hospedado no Novus, informe o título e o domínio desejado. Salve. A URL será https://{dominio}.novusplugin.com/.

  2. Confira novus-plugin.json. Antes de cada novo envio, aumente versao. Para este exemplo, o primeiro pacote usa 1.0.0.

  3. Gere o pacote somente com os três arquivos do site:

    zip -j plugin-1.0.0.zip novus-plugin.json index.html plugin.js
    unzip -Z -1 plugin-1.0.0.zip

    A segunda linha deve listar novus-plugin.json, index.html e plugin.js, sem diretório acima deles. Não compacte a raiz do repositório. LICENSE, .git/ e outros arquivos de desenvolvimento não pertencem ao pacote.

  4. Abra Versões no plugin e envie o .zip. O envio cria uma versão, mas não muda a versão ativa. Confira os avisos e use Pré-visualizar para conferir a página no seu navegador. As chamadas ao proxy seguem as permissões da versão ativa.

  5. Clique em Publicar, revise as permissões mostradas na tela e confirme.

  6. Recarregue o atendimento. O plugin publicado aparece no painel. Se precisar voltar, use Voltar para esta versão numa versão anterior.

A publicação chega ao domínio do plugin em até um minuto. A URL ativa verifica atualizações a cada carregamento. Um iframe já aberto não troca de versão sozinho. Cada versão também tem URL imutável: https://{dominio}--v{numero}.novusplugin.com/.

O domínio tem de 3 a 40 caracteres, com letras minúsculas, números e hífens. Começa e termina com letra ou número, não aceita --, é único entre contas e fica fixo depois da primeira publicação. Nomes como www, api, app, admin, docs e suporte são reservados.

A versão do manifesto segue SemVer, como 1.0.0 ou 1.1.0, e não pode ser usada em dois pacotes diferentes do mesmo plugin. Reenviar o mesmo pacote devolve a versão já existente. O Novus CRM guarda as 20 versões mais recentes, a ativa e as instaladas por outras contas.

Manifesto e permissões

O arquivo novus-plugin.json faz parte do pacote. Suas permissões correspondem ao uso real em plugin.js:

{
  "nome": "vendas-cplus5",
  "versao": "1.0.0",
  "entrada": "index.html",
  "spa": false,
  "permissoes": {
    "hosts": ["publica", "cplus5"],
    "dominiosExternos": [],
    "segredos": ["cplus.chaveapi", "cplus.ambiente"],
    "superficies": ["widgetbar"]
  }
}
Campo Regra neste exemplo
nome Identifica o plugin no manifesto
versao SemVer. Aumente antes de enviar conteúdo alterado
entrada Página aberta na raiz. O padrão é index.html
spa false porque o painel não usa rotas do navegador
permissoes.hosts publica consulta a API do Novus CRM. cplus5 consulta o ERP
permissoes.dominiosExternos Vazio porque o código não usa o host externo
permissoes.segredos Nomes citados em secretRefs, sem os valores
permissoes.superficies widgetbar é a única superfície registrada

Os hosts reconhecidos pelo manifesto são publica, cplus, cplus5, vcitech e externo. Para usar externo, declare também os domínios em dominiosExternos. Informe só o domínio, sem protocolo, porta ou caminho. *.exemplo.com cobre subdomínios, mas não exemplo.com.

No plugin hospedado, o proxy usa hosts e segredos da versão ativa. Uma versão apenas enviada não muda as permissões em uso.

Superfícies do plugin

As superfícies definem onde os itens do plugin aparecem no Novus CRM. Registre os itens em initialize e declare cada superfície usada em permissoes.superficies. No servidor próprio, faça essa declaração no cadastro do plugin. No plugin hospedado, um item sem a superfície declarada é ignorado, com aviso no console.

Registro em initialize Valor em superficies Onde aparece e o que faz
widgetbar widgetbar Ícone na barra lateral direita do atendimento. Sem callback, abre o painel do plugin. Com callback, executa a função ao clicar, sem abrir o painel automaticamente.
navbar navbar Item no menu principal, na seção Integrações. Ao clicar, abre a página do plugin no CRM e executa o callback, se houver.
options options Item em Opções, na área de plugins. Ao clicar, abre a página do plugin dentro das opções e executa o callback, se houver.
buttons buttons:{grupo} Ação em um menu ou no cabeçalho de uma tela. O grupo define o local e os dados recebidos pelo callback.

Em navbar e options, use type: "group" para criar um cabeçalho e type: "item" para criar uma opção clicável. item é o padrão. Defina parentId no item para colocá-lo dentro de um grupo com aquele id. Grupos não executam callback.

Dê um id estável a cada item de navbar e options. Assim, o Novus CRM consegue executar o callback no iframe da página, mesmo quando ela termina de carregar após o clique. Sem id, um callback de navbar roda no iframe em segundo plano.

Cada grupo de buttons tem sua própria permissão. Declare o valor exato, como buttons:ticket-menu, para cada grupo que registrar:

Grupo em buttons Onde aparece O que o callback recebe
lista-contatos Menu de ações de cada contato na lista de contatos { id, nome, numero, ehGrupo }
atendimento-chat Cabeçalho do atendimento de chat aberto. Em telas estreitas, o botão pode ir para o menu ⋮ Resumo { id, usuario, contato, canal }
atendimento-chat-menu Menu ⋮ do atendimento de chat O mesmo resumo
atendimento-page Menu ⋮ do atendimento de chat O mesmo resumo
ticket-menu Menu Opções do ticket, em um ticket já salvo O ticket aberto, com campos, vínculos, campos personalizados, referências e interacoes

No botão do Chat, usuario e canal podem ser null ou conter apenas { id }. contato contém { id, numero }. O atendimento chega em id. No evento aoFocarAtendimento, o mesmo identificador chega em atendimentoId, com mais dados do atendimento. Os dois payloads têm formatos diferentes. Os callbacks de widgetbar, navbar e options não recebem payload do clique.

Este exemplo registra somente widgetbar, por isso o manifesto declara apenas widgetbar. Se você adicionar um item em buttons["ticket-menu"], acrescente buttons:ticket-menu às superfícies declaradas. Os eventos registrados em events, como aoAbrirAtendimento, não criam itens na interface e não entram em superficies.

Regras do pacote

O .zip contém os arquivos estáticos prontos. O Novus CRM não instala dependências nem executa build. novus-plugin.json e o arquivo indicado por entrada precisam existir. Uma única pasta de topo com o manifesto dentro também é aceita.

Limite Valor
Tamanho compactado 20 MB
Tamanho descompactado 50 MB
Arquivos 2.000
Profundidade 12 níveis
Compressão por arquivo acima de 1 MB Até 100 vezes

Extensões aceitas para páginas e scripts: html, htm, css, js, mjs, json, map, webmanifest, txt e md. Outros formatos aceitos: svg, png, jpg, jpeg, gif, webp, avif, ico, woff, woff2, ttf, otf e wasm.

O pacote recusa arquivos sem extensão ou de outro tipo. Também recusa links simbólicos, caminho absoluto, .., barra invertida e nomes repetidos que mudam só em maiúsculas e minúsculas. __MACOSX/, .DS_Store e Thumbs.db são ignorados. O Novus CRM lista os problemas do pacote no envio.

Configure as APIs e as variáveis

O host cplus5 aponta para https://api.cplus.com.br. Este exemplo usa GET v1/Clientes e GET v1/Vendas. O host publica chama a API pública do Novus CRM.

Cadastre estas variáveis da conta antes de usar o plugin:

Nome Valor que você cadastra Uso
chaveApiPublica Chave da API pública do Novus CRM O proxy injeta no host publica. Não entra em secretRefs
cplus.chaveapi X-Chave-Api {sua-chave}, incluindo o prefixo Cabeçalho X-Authorization do C-Plus 5
cplus.ambiente Domínio do ambiente no C-Plus 5 Cabeçalho X-Ambiente do C-Plus 5

Gere a chave do Novus CRM em Configurações > Integrações > Chave de API. Peça a chave e o domínio do ERP ao administrador do C-Plus 5. Depois, abra Configurações > Conta > Minha Conta > Variáveis Globais e cadastre cada nome e valor da tabela.

Nunca coloque uma chave no manifesto, no JavaScript ou no .zip. O nome chaveApiPublica é fixo no proxy. Este exemplo escolheu os nomes cplus.chaveapi e cplus.ambiente. Se você os mudar, atualize plugin.js, novus-plugin.json e o cadastro da conta.

O valor de cplus.chaveapi precisa conter X-Chave-Api antes da chave. O proxy insere o valor inteiro no cabeçalho, sem acrescentar esse prefixo. O endereço do ERP vem do registro de hosts do proxy, não de uma variável chamada cplus.api.

Testar com dados

O plugin precisa rodar dentro de um atendimento no Novus CRM. O host publica chama a API pública da conta pelo proxy do Novus CRM. O host cplus5 chama o ERP pelo mesmo proxy. Abrir index.html sozinho ou servir os arquivos em localhost, por si só, não executa essas consultas.

Configure as variáveis acima, cadastre o plugin e publique a versão. Depois, abra um atendimento com contato vinculado a uma pessoa cujo código exista no C-Plus 5. O botão aparece na barra lateral direita. Se ela estiver recolhida ou a tela for estreita, expanda a barra ou use uma tela maior.

Para conferir as vendas, abra a versão publicada dentro do atendimento. A pré-visualização permite conferir a página, mas as chamadas usam as permissões da versão ativa.

Entenda o código

O topo de plugin.js implementa o protocolo direto, sem SDK. initialize usa um MessageChannel persistente para receber eventos. Cada apiRequest usa outro canal para receber uma resposta. O initialize é a primeira mensagem enviada ao host.

O código registra um item em widgetbar e escuta abrir, focar e fechar atendimento. O host reenvia o atendimento em foco quando o iframe termina de carregar. Abrir e focar usam a mesma função. Ao receber o mesmo contato de novo, o plugin mantém a página e a busca atuais.

O Novus CRM carrega um iframe em segundo plano para registrar o botão e outro quando abre o painel. Ambos recebem eventos de atendimento. Cada iframe guarda seu próprio estado, então uma consulta pode ocorrer nos dois. Este exemplo só faz consultas GET. Ao adaptar eventos para alterar dados, mova essa ação para um clique do usuário ou garanta que a operação seja idempotente.

Se você trocar de atendimento ou de página da lista antes de uma consulta terminar, o código descarta a resposta anterior. O teste cobre a troca de atendimento durante a busca da pessoa e das vendas.

Como obter dados do Novus CRM

O enviarComando(command, args) do plugin.js abre um MessageChannel para cada consulta. O host responde com data ou error. A função devolve os dados ou rejeita a Promise. Chame-a depois de initialize:

const usuario = await enviarComando("getInfoUser", null);
// { id, name, email } do usuário logado

const canais = await enviarComando("getInfoChannels", null);
// Lista com canalId, descricao, identificador, type e status

Essas duas consultas são exemplos para adaptar o plugin. O painel de vendas não precisa delas. getInfoChannels também traz number. type é numérico e status vale CONNECTED ou OFFLINE. organizacao e organizacaoId vêm vazios. Para buscar outros dados, use enviarComando("apiRequest", { host: "publica", method: "GET", endpoint: "..." }) com uma rota da API pública. As consultas reais deste plugin estão em plugin.js.

O SDK WlExtension oferece métodos com esses nomes, caso você escolha incluí-lo no seu projeto. WlExtension.identifier traz o id do plugin recebido em ?id=, não o id do usuário. apiRequest() devolve o JSON da API, e openPage() devolve { navigated: boolean }. O guia de comandos mostra todos os métodos.

O atendimento atual chega no callback de aoAbrirAtendimento ou aoFocarAtendimento. O payload inclui atendimentoId, canalId, contato.id, contato.nome e contato.numero. O host reenvia o atendimento em foco após o registro do iframe. Ao receber aoFecharAtendimento, limpe o estado do atendimento. Esse evento não traz payload. Não existe um comando para consultar o atendimento em foco. O usuarioId e o objeto usuario do atendimento indicam o atendente atribuído. Para saber quem está logado, use getInfoUser.

No objeto enviado a initialize, registre os callbacks assim:

events: {
  aoFocarAtendimento: registrarEvento("aoFocarAtendimento", (atendimento) => {
    const idAtendimento = atendimento.atendimentoId;
    const idContato = atendimento.contato.id;
    // Use esses IDs para atualizar o painel ou consultar a API pública.
  }),
  aoFecharAtendimento: registrarEvento("aoFecharAtendimento", () => {
    // Limpe o atendimento guardado em memória.
  }),
}

Um botão recebe no callback o item clicado. Em lista-contatos, recebe o contato. Os grupos atendimento-chat, atendimento-chat-menu e atendimento-page recebem o resumo do atendimento. Em ticket-menu, recebe o ticket, inclusive interacoes. Consulte a tabela de superfícies para escolher onde colocar cada botão.

Use enviarComando para getInfoUser, getInfoChannels, apiRequest, openAlert e openPage. openModal usa um canal próprio para devolver o resultado por callback. closeModal, openWidget e load não devolvem resposta, então não espere por enviarComando nesses casos. O initialize usa o canal persistente dos eventos. Consulte os comandos na documentação para ver os argumentos e o SDK WlExtension.

Ao selecionar um atendimento, o Novus CRM emite aoAbrirAtendimento e aoFocarAtendimento com os mesmos dados. O exemplo evita repetir a consulta para o mesmo contato. Há também eventos para abrir e fechar uma conversa anterior e para entrar no Chat ou na aba Conversas. Os eventos de acesso e fechamento não trazem payload. Veja a lista de eventos.

O contato e o cliente do ERP são cadastros diferentes. O código faz esta sequência:

  1. GET v1/contatodechat/{id} no host publica encontra IdPessoa.
  2. GET v1/pessoas/{IdPessoa} no host publica encontra Codigo.
  3. GET v1/Clientes?Codigo=... no host cplus5 encontra o ID do cliente no ERP.
  4. GET v1/Vendas?IdPessoa=... no host cplus5 traz a página de vendas.

O painel distingue contato sem pessoa, pessoa sem código e cliente ausente no ERP. A busca só com dígitos usa NumeroDaVendaAproximado. Outros termos usam NomeDoProdutoAproximado. A paginação ocorre no servidor do C-Plus 5.

icon_url no registro do widgetbar é um SVG embutido. Se você usar um arquivo próprio, monte a URL absoluta com new URL("icone.svg", location.href).href. O host resolve URLs relativas no domínio do CRM, onde o ícone não existe.

O plugin não recebe o token da sessão nem os valores das variáveis. secretRefs envia o nome da variável e onde ela deve entrar. O servidor resolve o valor e faz a chamada. fetch direto para a API externa pode falhar por CORS e exporia qualquer credencial escrita no navegador.

Erros que o painel explica

explicarErro traduz as respostas do proxy. Erro de configuração não mostra Tentar de novo. Falha passageira mostra. O detalhe técnico continua no console com o prefixo [vendas-cplus5].

O painel mostra Mensagem do proxy O que fazer
Host não disponível Host not allowed Conferir o nome do host usado pelo plugin
Plugin não encontrado Plugin not found Conferir o cadastro e a conta
Host não declarado Host not declared Declarar o host no manifesto ou no cadastro
Domínio não declarado Domain not declared: ... Declarar o domínio se passar a usar externo
Variável não declarada Secret not declared: ... Declarar a variável no manifesto ou no cadastro
Configuração incompleta Secret not found: ... Cadastrar a variável na conta
Chave do Novus CRM ausente Chave da API pública não configurada... Cadastrar chaveApiPublica na conta
Consulta não disponível Endpoint not allowed ou Method not allowed Conferir a rota e o método usados
Endereço da consulta inválido Invalid endpoint Conferir o endpoint no código publicado
Serviço não encontrado Endpoint not resolvable Conferir o domínio do serviço externo
Sessão expirada Unauthorized Recarregar o Novus CRM
Acesso recusado pelo C-Plus 5 proxy 401 ou proxy 403 Conferir chave e ambiente do ERP
C-Plus 5 indisponível Falha de rede, timeout ou outro erro Tentar novamente

O código inclui os erros de externo para servir de base a quem adaptar este exemplo. Este plugin usa apenas publica e cplus5.

Segurança e limites do iframe

Plugins hospedados rodam num iframe com origem isolada. Não use localStorage, sessionStorage, IndexedDB nem document.cookie: o navegador bloqueia essas APIs nesse contexto. Este exemplo guarda estado apenas em memória.

Scripts de módulo e fontes de um servidor próprio precisam de Access-Control-Allow-Origin: *, porque a origem do iframe é opaca. O <script> comum deste projeto não precisa desse cabeçalho. Diálogos nativos (alert, confirm e prompt), downloads iniciados pelo plugin e APIs como câmera e geolocalização também são bloqueados pelo sandbox. Use openAlert para avisos. O host atual responde true a openConfirmDialog sem mostrar diálogo, então faça confirmações na interface do plugin.

Qualquer pessoa com o endereço de uma versão hospedada pode baixar os arquivos dela. A validação avisa sobre padrões comuns de credencial, mas não bloqueia o envio. Revise o pacote antes de publicá-lo.

Verificação

node --check plugin.js confere a sintaxe. Rode os testes com node --test plugin.test.mjs. Eles conferem as permissões, a tradução dos erros e as respostas antigas após trocar de atendimento, sem chamar APIs. Para conferir o pacote, envie o .zip pela tela Versões. O servidor aplica as regras de manifesto e arquivo antes de criar a versão.

Criar outros plugins com IA

A skill criar-plugin-novus orienta um agente de IA a criar ou adaptar um plugin. Ela cobre o protocolo, as superfícies, o manifesto, as chamadas de API pelo proxy e a verificação. Peça, por exemplo: "Crie um plugin que adicione uma ação em ticket-menu e mostre o código do ticket".

Agentes de código

Ao abrir este repositório no Codex ou no Gemini CLI, a skill em .agents/skills já fica disponível. O Claude Code usa o link em .claude/skills. Para usar a skill em outros projetos, copie a pasta conforme a ferramenta:

Ferramenta Pasta para instalar na sua conta
Codex ~/.agents/skills/criar-plugin-novus/
Claude Code ~/.claude/skills/criar-plugin-novus/
Gemini CLI ~/.gemini/skills/criar-plugin-novus/

Copie a pasta inteira, incluindo SKILL.md. Depois, peça para criar um plugin do Novus CRM ou cite a skill pelo nome. No Claude Code, use /criar-plugin-novus. No Codex, use $criar-plugin-novus. No Gemini CLI, confira a instalação com /skills list.

Apps de conversa

Ferramenta Como usar
ChatGPT Abra Plugins > Skills > Create > Upload from your computer e envie o ZIP da skill gerado abaixo.
Claude Gere o arquivo ZIP com o comando abaixo. Em Customize > Skills, escolha Create skill > Upload a skill e envie o ZIP.
Gemini Spark Abra Skills > Upload e selecione SKILL.md. Revise o conteúdo e clique em Create.

Para gerar o ZIP da skill, rode na raiz deste repositório:

(cd .agents/skills && zip -r ../../criar-plugin-novus.zip criar-plugin-novus)

O ZIP contém a pasta criar-plugin-novus/ com SKILL.md dentro dela. Ele serve para instalar a skill no agente de IA. Não envie esse ZIP como pacote do plugin ao Novus CRM.

OpenRouter e outros modelos

O OpenRouter encaminha mensagens para modelos e não instala esta pasta por conta própria. Na aplicação que chama a API, carregue o texto de SKILL.md na mensagem system ou developer e coloque seu pedido na mensagem user. Em qualquer chat sem instalação de skills, anexe SKILL.md ou cole seu conteúdo antes de pedir o plugin. Se o modelo não puder editar arquivos, peça todos os arquivos do plugin e salve-os no projeto.

O guia na documentação do Novus CRM mostra os mesmos passos e links das plataformas.

Consulte a documentação do protocolo. Veja também as páginas de publicação e CLI/API.

Licença

MIT

About

Plugin de exemplo para o Novus CRM: lista as vendas do contato no ERP C-Plus 5.

Topics

Resources

Stars

2 stars

Watchers

0 watching

Forks

Contributors

Languages