{"id":666,"date":"2026-09-11T12:29:15","date_gmt":"2026-09-11T15:29:15","guid":{"rendered":"https:\/\/carloslopes.programandosolucoes.com.br\/index.php\/2026\/09\/11\/softphone-webrtc-no-helpdesk\/"},"modified":"2026-09-11T12:54:59","modified_gmt":"2026-09-11T15:54:59","slug":"softphone-webrtc-no-helpdesk","status":"publish","type":"post","link":"https:\/\/carloslopes.programandosolucoes.com.br\/index.php\/2026\/09\/11\/softphone-webrtc-no-helpdesk\/","title":{"rendered":"Softphone WebRTC dentro do helpdesk: um c\u00f3digo, tr\u00eas casas"},"content":{"rendered":"\n<p><strong>Softphone WebRTC dentro do Zendesk: como transformei o ticket na pr\u00f3pria esta\u00e7\u00e3o de trabalho do atendente.<\/strong><\/p>\n\n\n\n<p>Antes de escrever a primeira linha, fiz um invent\u00e1rio de tr\u00eas implementa\u00e7\u00f5es de telefonia dentro de helpdesk \u2014 duas j\u00e1 existiam \u2014 e comparei <em>capacidade por capacidade<\/em>, n\u00e3o por volume de c\u00f3digo.<\/p>\n\n\n\n<p>Minha primeira hip\u00f3tese era que eu teria de escolher uma delas e reescrever o resto. Estava errado. A tabela mostrou duas coisas:<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n  <li>A implementa\u00e7\u00e3o que <em>s\u00f3 observava<\/em> a central j\u00e1 fazia o registro muito bem \u2014 identifica\u00e7\u00e3o do cliente, ticket, notas, grava\u00e7\u00e3o, transcri\u00e7\u00e3o. O que faltava nela era <strong>voz<\/strong>.<\/li>\n  <li>A implementa\u00e7\u00e3o que <em>falava<\/em> s\u00f3 existia enquanto houvesse um ticket aberto na aba. Boa para ligar, fr\u00e1gil para receber.<\/li>\n<\/ol>\n\n\n\n<p>A decis\u00e3o de arquitetura saiu da tabela, n\u00e3o da prefer\u00eancia: <strong>n\u00e3o trocar uma metade pela outra \u2014 costurar a voz dentro do painel que j\u00e1 observava.<\/strong> A lista de capacidades perdidas no caminho ficou vazia.<\/p>\n\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">\ud83d\udcde Uma liga\u00e7\u00e3o recebida, antes e depois<\/h2>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter size-large\"><img decoding=\"async\" src=\"\/wp-content\/uploads\/portfolio\/softphone-zendesk-ticket.png\" alt=\"Softphone embutido no painel lateral do ticket, dentro do helpdesk\"\/><figcaption class=\"wp-element-caption\">O softphone embutido no painel lateral do ticket: o atendente n\u00e3o troca de janela para falar com o cliente<\/figcaption><\/figure><\/div>\n\n\n<figure class=\"wp-block-table\"><table>\n<thead><tr><th>Momento<\/th><th>Sem o softphone<\/th><th>Com o softphone<\/th><\/tr><\/thead>\n<tbody>\n<tr><td><strong>O telefone toca<\/strong><\/td><td>Aviso num aplicativo separado, fora do helpdesk.<\/td><td>Aviso dentro do helpdesk, com o nome de quem liga.<\/td><\/tr>\n<tr><td><strong>Quem \u00e9 o cliente?<\/strong><\/td><td>Ouve o nome e procura o cadastro \u00e0 m\u00e3o.<\/td><td>O cadastro j\u00e1 est\u00e1 na tela \u2014 n\u00famero resolvido em tr\u00eas formatos.<\/td><\/tr>\n<tr><td><strong>N\u00e3o tem cadastro<\/strong><\/td><td>Abrir outra aba, criar o contato, voltar.<\/td><td>Contato e organiza\u00e7\u00e3o criados dali mesmo.<\/td><\/tr>\n<tr><td><strong>Atender<\/strong><\/td><td>Discador \u00e0 parte: segunda janela, segundo foco.<\/td><td>Um bot\u00e3o ao lado do ticket. \u00c1udio no pr\u00f3prio navegador.<\/td><\/tr>\n<tr><td><strong>Durante a conversa<\/strong><\/td><td>Mudo, espera e teclado no outro aplicativo.<\/td><td>Tudo ao lado do ticket.<\/td><\/tr>\n<tr><td><strong>Transferir<\/strong><\/td><td>Cega, sem falar antes com quem recebe.<\/td><td>Cega ou assistida.<\/td><\/tr>\n<tr><td><strong>Caiu ruim<\/strong><\/td><td>Fica a impress\u00e3o: \u201cestava ruim hoje\u201d.<\/td><td>Perda e jitter medidos, em quatro barras, durante a chamada.<\/td><\/tr>\n<tr><td><strong>Desligou<\/strong><\/td><td>O atendente digita de mem\u00f3ria.<\/td><td>Nota autom\u00e1tica: status, dura\u00e7\u00f5es e grava\u00e7\u00e3o anexada.<\/td><\/tr>\n<tr><td><strong>O que foi dito<\/strong><\/td><td>S\u00f3 ouvindo a grava\u00e7\u00e3o inteira de novo.<\/td><td>Transcri\u00e7\u00e3o e resumo por IA no pr\u00f3prio ticket.<\/td><\/tr>\n<tr><td><strong>Perdeu a chamada<\/strong><\/td><td>Descobre no relat\u00f3rio, no dia seguinte.<\/td><td>Aviso persistente, sem duplicar se a mesma liga\u00e7\u00e3o insistir.<\/td><\/tr>\n<\/tbody>\n<\/table><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\">\ud83e\udde9 O que o software faz, em 6 blocos<\/h2>\n\n\n\n<ul class=\"wp-block-list\">\n  <li><strong>1. Telefonia completa no navegador:<\/strong> discar com teclado e hist\u00f3rico clic\u00e1vel, atender, recusar, mudo, espera, teclado num\u00e9rico durante a chamada (para navegar em uras de terceiros), transfer\u00eancia cega e assistida. Uma segunda chamada que chegue durante a primeira \u00e9 avisada <em>sem roubar a tela<\/em>.<\/li>\n  <li><strong>2. Grava\u00e7\u00e3o comandada na central:<\/strong> a grava\u00e7\u00e3o acontece onde ela \u00e9 confi\u00e1vel \u2014 no PABX, n\u00e3o no navegador. O bot\u00e3o s\u00f3 fica dispon\u00edvel se o ramal daquele atendente tiver permiss\u00e3o, e o estado vem do evento da pr\u00f3pria central.<\/li>\n  <li><strong>3. Qualidade medida, n\u00e3o sentida:<\/strong> perda de pacotes e jitter em quatro barras, atualizadas durante a conversa e registradas junto da chamada. Quando o cliente reclama que \u201cficou ruim\u201d, existe n\u00famero.<\/li>\n  <li><strong>4. O ticket se escreve sozinho:<\/strong> identifica\u00e7\u00e3o pelo telefone em tr\u00eas formatos, cria\u00e7\u00e3o de contato e organiza\u00e7\u00e3o, v\u00ednculo com ticket existente ou cria\u00e7\u00e3o de um novo, nota de in\u00edcio e de encerramento com status e dura\u00e7\u00f5es, grava\u00e7\u00e3o anexada com player nativo, transcri\u00e7\u00e3o e resumo por IA.<\/li>\n  <li><strong>5. Dispon\u00edvel antes do primeiro clique:<\/strong> o registro de telefonia sobe num processo em segundo plano assim que o helpdesk abre. H\u00e1 elei\u00e7\u00e3o de dono entre as abas \u2014 o mesmo ramal nunca registra duas vezes \u2014 e uma trava que impede o registro quando o microfone est\u00e1 bloqueado, com escape manual.<\/li>\n  <li><strong>6. Erros em portugu\u00eas de gente:<\/strong> 20 c\u00f3digos de erro do SDK de voz traduzidos para causa e a\u00e7\u00e3o. O operador l\u00ea <em>\u201co plano do SDK est\u00e1 desabilitado na central\u201d<\/em>, n\u00e3o um n\u00famero.<\/li>\n<\/ul>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter size-large\"><img decoding=\"async\" src=\"\/wp-content\/uploads\/portfolio\/softphone-conversa.png\" alt=\"Chamada em andamento com cron\u00f4metro, grava\u00e7\u00e3o e qualidade medida\"\/><figcaption class=\"wp-element-caption\">Em conversa: cron\u00f4metro, grava\u00e7\u00e3o comandada na central e qualidade da liga\u00e7\u00e3o em quatro barras<\/figcaption><\/figure><\/div>\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">\u2696\ufe0f Tr\u00eas abordagens para o mesmo problema<\/h2>\n\n\n\n<p>A tabela que decidiu a arquitetura, resumida. As tr\u00eas resolvem o mesmo problema por caminhos diferentes:<\/p>\n\n\n\n<figure class=\"wp-block-table\"><table>\n<thead><tr><th>Capacidade<\/th><th>Painel que observa<\/th><th>Softphone s\u00f3 no ticket<\/th><th>Esta abordagem<\/th><\/tr><\/thead>\n<tbody>\n<tr><td>\u00c1udio no pr\u00f3prio navegador<\/td><td>\u274c discador externo<\/td><td>\u2705<\/td><td>\u2705<\/td><\/tr>\n<tr><td>Funciona sem ticket aberto<\/td><td>\u2705<\/td><td>\u274c<\/td><td>\u2705<\/td><\/tr>\n<tr><td>Eventos da central em tempo real<\/td><td>\u2705<\/td><td>\u274c<\/td><td>\u2705<\/td><\/tr>\n<tr><td>Cliente na tela antes do \u201cal\u00f4\u201d<\/td><td>\u2705<\/td><td>\ud83d\udfe1 depende de onde o agente olha<\/td><td>\u2705<\/td><\/tr>\n<tr><td>Teclado num\u00e9rico na chamada<\/td><td>\u274c<\/td><td>\u274c<\/td><td>\u2705<\/td><\/tr>\n<tr><td>Transfer\u00eancia assistida<\/td><td>\u274c<\/td><td>\u274c<\/td><td>\u2705<\/td><\/tr>\n<tr><td>Grava\u00e7\u00e3o por permiss\u00e3o do ramal<\/td><td>\u274c<\/td><td>\u274c<\/td><td>\u2705<\/td><\/tr>\n<tr><td>Qualidade medida na tela<\/td><td>\u274c<\/td><td>\u274c<\/td><td>\u2705<\/td><\/tr>\n<tr><td>Registro ativo antes do 1\u00ba clique<\/td><td>\u274c<\/td><td>\u274c<\/td><td>\u2705<\/td><\/tr>\n<tr><td>Transcri\u00e7\u00e3o e resumo por IA<\/td><td>\u2705<\/td><td>\u274c<\/td><td>\u2705<\/td><\/tr>\n<\/tbody>\n<\/table><\/figure>\n\n\n\n<p><strong>A li\u00e7\u00e3o que ficou:<\/strong> comparar por volume de c\u00f3digo teria dado o veredito oposto. Comparar por capacidade mostrou que a base certa era a que parecia menor.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">\ud83c\udfd7\ufe0f Um c\u00f3digo, tr\u00eas casas<\/h2>\n\n\n\n<ul class=\"wp-block-list\">\n  <li><strong>1. Dentro do helpdesk:<\/strong> o aplicativo instalado tem <strong>44 KB<\/strong> e n\u00e3o cont\u00e9m a l\u00f3gica do softphone \u2014 ele carrega a aplica\u00e7\u00e3o do backend em tempo de execu\u00e7\u00e3o. Uma melhoria chega ao atendente com um F5, sem reinstalar e sem janela de manuten\u00e7\u00e3o.<\/li>\n  <li><strong>2. No navegador:<\/strong> a mesma aplica\u00e7\u00e3o aberta direto, para opera\u00e7\u00e3o avulsa.<\/li>\n  <li><strong>3. Como aplicativo de mesa:<\/strong> janela nativa, bandeja do sistema e notifica\u00e7\u00e3o do sistema operacional. O frontend detecta o ambiente sozinho e esconde os controles que n\u00e3o fazem sentido nele.<\/li>\n<\/ul>\n\n\n\n<p><strong>Duas fontes de verdade, com fronteira escrita.<\/strong> O SDK de voz manda enquanto a chamada est\u00e1 viva \u2014 quem toca, quem atende, quanto tempo faz. A API da central manda depois que ela termina \u2014 status oficial, dura\u00e7\u00e3o fatur\u00e1vel, grava\u00e7\u00e3o em arquivo, transcri\u00e7\u00e3o.<\/p>\n\n\n\n<p>Cada uma chama a mesma liga\u00e7\u00e3o por um identificador diferente, ent\u00e3o existe uma camada de correla\u00e7\u00e3o por n\u00famero e janela de tempo \u2014 com uma rede de seguran\u00e7a: se o registro oficial n\u00e3o chegar em 45 segundos, a nota sai com os n\u00fameros do navegador <em>e declara a origem<\/em>. N\u00famero sem proced\u00eancia declarada \u00e9 n\u00famero que algu\u00e9m vai usar errado depois.<\/p>\n\n\n<div class=\"wp-block-image\">\n<figure class=\"aligncenter size-large\"><img decoding=\"async\" src=\"\/wp-content\/uploads\/portfolio\/softphone-discador.png\" alt=\"Discador com teclado, hist\u00f3rico e ramal registrado\"\/><figcaption class=\"wp-element-caption\">O mesmo discador roda embutido no helpdesk, no navegador e como aplicativo de mesa<\/figcaption><\/figure><\/div>\n\n\n<hr class=\"wp-block-separator has-alpha-channel-opacity\"\/>\n\n\n\n<h2 class=\"wp-block-heading\">\ud83d\uddbc\ufe0f As telas<\/h2>\n\n\n\n<figure class=\"wp-block-gallery has-nested-images columns-3 wp-block-gallery-1 is-layout-flex wp-block-gallery-is-layout-flex\">\n  <figure class=\"wp-block-image size-large\"><img decoding=\"async\" src=\"\/wp-content\/uploads\/portfolio\/softphone-entrante.png\" alt=\"Chamada entrante: atender ou recusar\"\/><figcaption class=\"wp-element-caption\">Chamada entrante<\/figcaption><\/figure>\n  <figure class=\"wp-block-image size-large\"><img decoding=\"async\" src=\"\/wp-content\/uploads\/portfolio\/softphone-espera.png\" alt=\"Chamada em espera\"\/><figcaption class=\"wp-element-caption\">Em espera<\/figcaption><\/figure>\n  <figure class=\"wp-block-image size-large\"><img decoding=\"async\" src=\"\/wp-content\/uploads\/portfolio\/softphone-dtmf.png\" alt=\"Teclado num\u00e9rico durante a chamada\"\/><figcaption class=\"wp-element-caption\">Teclado num\u00e9rico na chamada<\/figcaption><\/figure>\n  <figure class=\"wp-block-image size-large\"><img decoding=\"async\" src=\"\/wp-content\/uploads\/portfolio\/softphone-transferir.png\" alt=\"Transfer\u00eancia cega e assistida\"\/><figcaption class=\"wp-element-caption\">Transfer\u00eancia assistida<\/figcaption><\/figure>\n  <figure class=\"wp-block-image size-large\"><img decoding=\"async\" src=\"\/wp-content\/uploads\/portfolio\/softphone-erro.png\" alt=\"Erro traduzido para linguagem do operador\"\/><figcaption class=\"wp-element-caption\">Erro traduzido: causa e a\u00e7\u00e3o<\/figcaption><\/figure>\n  <figure class=\"wp-block-image size-large\"><img decoding=\"async\" src=\"\/wp-content\/uploads\/portfolio\/softphone-zendesk-painel.png\" alt=\"Primeiro acesso: o softphone pede o ramal do atendente\"\/><figcaption class=\"wp-element-caption\">Primeiro acesso: o ramal, uma vez s\u00f3<\/figcaption><\/figure>\n<\/figure>\n\n\n\n<h2 class=\"wp-block-heading\">\ud83d\udd12 C\u00f3digo-fonte e Painel Git pr\u00f3prio<\/h2>\n\n\n\n<p>O c\u00f3digo-fonte integral e o hist\u00f3rico de commits est\u00e3o versionados em <strong>infraestrutura pr\u00f3pria<\/strong> \u2014 uma plataforma de hospedagem Git que eu mesmo constru\u00ed e que tamb\u00e9m est\u00e1 no portf\u00f3lio:<\/p>\n\n\n\n<pre class=\"wp-block-preformatted\"><code>https:\/\/git.programandosolucoes.com.br\/git\/<\/code><\/pre>\n\n\n\n<p>Nada de GitHub, GitLab ou Bitbucket no caminho: reposit\u00f3rios, chaves SSH, permiss\u00f5es por usu\u00e1rio, pull requests e pol\u00edtica de branch rodam em servidor pr\u00f3prio. <a href=\"\/index.php\/portfolio-painel-git\/\">Ver o projeto do Painel Git no portf\u00f3lio \u2192<\/a><\/p>\n\n\n\n<p><em>Nota: este reposit\u00f3rio \u00e9 <strong>privado e sob acordo de confidencialidade<\/strong> com o cliente \u2014 por isso o endere\u00e7o do reposit\u00f3rio em si n\u00e3o \u00e9 publicado. Caso deseje acesso para avalia\u00e7\u00e3o t\u00e9cnica ou parceria, entre em contato diretamente.<\/em><\/p>\n\n\n\n<h2 class=\"wp-block-heading\">\ud83d\uddbc\ufe0f Sobre as telas deste artigo<\/h2>\n\n\n\n<p>Projeto executado sob acordo de confidencialidade. As telas deste artigo est\u00e3o <strong>descaracterizadas<\/strong>: marca, cores e dados de exemplo foram substitu\u00eddos \u2014 a troca \u00e9 feita por inje\u00e7\u00e3o no instante da captura, n\u00e3o em editor de imagem, para que a apar\u00eancia do produto real nunca vaze por descuido de recorte.<\/p>\n\n\n\n<p><em>Nota: c\u00f3digo privado. Converso sobre a arquitetura e as decis\u00f5es em detalhe \u2014 para avalia\u00e7\u00e3o t\u00e9cnica ou parceria, entre em contato diretamente.<\/em><\/p>\n\n\n\n<p><strong>Carlos Alberto<\/strong> \u2014 carlosalberto4ti@gmail.com \u00b7 +55 11 2615-2880<\/p>\n\n","protected":false},"excerpt":{"rendered":"<p>Softphone WebRTC dentro do Zendesk: como transformei o ticket na pr\u00f3pria esta\u00e7\u00e3o de trabalho do atendente. Antes de escrever a primeira linha, fiz um invent\u00e1rio de tr\u00eas implementa\u00e7\u00f5es de telefonia dentro de helpdesk \u2014 duas j\u00e1 existiam \u2014 e comparei capacidade por capacidade, n\u00e3o por volume de c\u00f3digo. Minha primeira hip\u00f3tese era que eu teria [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"footnotes":""},"categories":[1],"tags":[125,126,124,121,122,123,119,118,120],"class_list":["post-666","post","type-post","status-publish","format-standard","hentry","category-sem-categoria","tag-arquitetura","tag-cti","tag-electron","tag-javascript","tag-nodejs","tag-telefonia","tag-voip","tag-webrtc","tag-zendesk"],"translation":{"provider":"WPGlobus","version":"3.0.2","language":"en","enabled_languages":["en","br","it"],"languages":{"en":{"title":true,"content":true,"excerpt":false},"br":{"title":false,"content":false,"excerpt":false},"it":{"title":false,"content":false,"excerpt":false}}},"_links":{"self":[{"href":"https:\/\/carloslopes.programandosolucoes.com.br\/index.php\/wp-json\/wp\/v2\/posts\/666","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/carloslopes.programandosolucoes.com.br\/index.php\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/carloslopes.programandosolucoes.com.br\/index.php\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/carloslopes.programandosolucoes.com.br\/index.php\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/carloslopes.programandosolucoes.com.br\/index.php\/wp-json\/wp\/v2\/comments?post=666"}],"version-history":[{"count":3,"href":"https:\/\/carloslopes.programandosolucoes.com.br\/index.php\/wp-json\/wp\/v2\/posts\/666\/revisions"}],"predecessor-version":[{"id":670,"href":"https:\/\/carloslopes.programandosolucoes.com.br\/index.php\/wp-json\/wp\/v2\/posts\/666\/revisions\/670"}],"wp:attachment":[{"href":"https:\/\/carloslopes.programandosolucoes.com.br\/index.php\/wp-json\/wp\/v2\/media?parent=666"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/carloslopes.programandosolucoes.com.br\/index.php\/wp-json\/wp\/v2\/categories?post=666"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/carloslopes.programandosolucoes.com.br\/index.php\/wp-json\/wp\/v2\/tags?post=666"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}