{"id":659,"date":"2026-09-11T12:25:03","date_gmt":"2026-09-11T15:25:03","guid":{"rendered":"https:\/\/carloslopes.programandosolucoes.com.br\/index.php\/portfolio-softphone-zendesk\/"},"modified":"2026-09-11T12:43:24","modified_gmt":"2026-09-11T15:43:24","slug":"portfolio-softphone-zendesk","status":"publish","type":"page","link":"https:\/\/carloslopes.programandosolucoes.com.br\/it\/index.php\/portfolio-softphone-zendesk\/","title":{"rendered":"Softphone WebRTC Integrado ao Helpdesk (Zendesk)"},"content":{"rendered":"<style>\n.pf-wrap{max-width:1100px;margin:0 auto;font-size:16px;line-height:1.65}\n.pf-chamada{font-size:1.15rem;opacity:.85;margin:0 0 2rem}\n.pf-grade{display:grid;grid-template-columns:repeat(auto-fill,minmax(300px,1fr));gap:1.5rem}\n.pf-card{border:1px solid rgba(128,128,128,.3);border-radius:10px;overflow:hidden;display:flex;flex-direction:column;transition:transform .15s ease,box-shadow .15s ease}\n.pf-card:hover{transform:translateY(-3px);box-shadow:0 8px 24px rgba(0,0,0,.12)}\n.pf-card img{width:100%;height:180px;object-fit:cover;display:block;background:rgba(128,128,128,.12)}\n.pf-card-corpo{padding:1rem 1.1rem 1.2rem;display:flex;flex-direction:column;gap:.6rem;flex:1}\n.pf-card h3{margin:0;font-size:1.05rem;line-height:1.35}\n.pf-card p{margin:0;font-size:.92rem;opacity:.85;flex:1}\n.pf-chips{display:flex;flex-wrap:wrap;gap:.35rem}\n.pf-chip{font-size:.75rem;padding:.18rem .55rem;border-radius:999px;border:1px solid rgba(128,128,128,.35);opacity:.9}\n.pf-meta{font-size:.8rem;opacity:.7}\n.pf-cadeado{font-size:.8rem;opacity:.75}\n.pf-hero{margin:0 0 1.5rem}\n.pf-hero h1{margin:0 0 .4rem;line-height:1.2}\n.pf-secao{margin:2rem 0}\n.pf-secao h2{font-size:1.15rem;margin:0 0 .6rem;text-transform:uppercase;letter-spacing:.06em;opacity:.75}\n.pf-galeria{display:grid;gap:1.2rem;margin:2rem 0}\n.pf-galeria figure{margin:0}\n.pf-galeria img{width:100%;border:1px solid rgba(128,128,128,.3);border-radius:8px;display:block}\n.pf-galeria figcaption{font-size:.85rem;opacity:.75;margin-top:.4rem}\n.pf-telas{display:grid;grid-template-columns:repeat(auto-fill,minmax(200px,1fr));gap:1rem;margin:1rem 0 2rem}\n.pf-telas figure{margin:0}\n.pf-telas img{width:100%;border:1px solid rgba(128,128,128,.3);border-radius:8px;display:block;background:#fff}\n.pf-telas figcaption{font-size:.8rem;opacity:.75;margin-top:.35rem;text-align:center}\n.pf-repo{border:1px solid rgba(128,128,128,.35);border-left-width:4px;border-radius:8px;padding:1rem 1.2rem;margin:2rem 0}\n.pf-repo h2{margin:0 0 .5rem;font-size:1rem;text-transform:uppercase;letter-spacing:.06em}\n.pf-repo pre{background:rgba(128,128,128,.12);padding:.7rem .9rem;border-radius:6px;overflow-x:auto;margin:.6rem 0;font-size:.85rem}\n.pf-contato{margin:2rem 0;padding-top:1.2rem;border-top:1px solid rgba(128,128,128,.3);font-size:.95rem}\n.pf-voltar{display:inline-block;margin-bottom:1rem;font-size:.9rem}\n@media (max-width:600px){.pf-card img{height:150px}}\n<\/style>\n<div class=\"pf-wrap\">\n  <a class=\"pf-voltar\" href=\"\/index.php\/portfolio-fullstack\/\">&larr; voltar ao portf\u00f3lio<\/a><\/p>\n<header class=\"pf-hero\">\n<p class=\"pf-meta\">08\/2026 &#8211; atual \u00b7 em andamento<\/p>\n<p>Softphone WebRTC que vive dentro do ticket do helpdesk. O atendente disca, atende, transfere e grava sem sair da tela em que j\u00e1 est\u00e1 \u2014 e a pr\u00f3pria chamada produz o registro: cliente identificado, ticket vinculado, grava\u00e7\u00e3o anexada, transcri\u00e7\u00e3o e resumo por IA.<\/p>\n<div class=\"pf-chips\"><span class=\"pf-chip\">JavaScript<\/span><span class=\"pf-chip\">Zendesk Apps Framework 2.0<\/span><span class=\"pf-chip\">WebRTC<\/span><span class=\"pf-chip\">Node.js<\/span><span class=\"pf-chip\">WebSocket<\/span><span class=\"pf-chip\">Electron<\/span><span class=\"pf-chip\">Docker<\/span><\/div>\n<\/header>\n<section class=\"pf-secao\">\n<h2>O problema<\/h2>\n<p>Num atendimento por telefone o operador trabalha em duas telas: o discador de um lado, o helpdesk do outro. Ele atende, ouve o nome do cliente, procura o cadastro \u00e0 m\u00e3o, abre o ticket, e no fim digita de mem\u00f3ria o que aconteceu. O registro acaba sendo a vers\u00e3o que o operador lembrou \u2014 n\u00e3o a que aconteceu.<\/p>\n<\/section>\n<section class=\"pf-secao\">\n<h2>O que o software faz<\/h2>\n<p><strong>Telefonia completa no navegador.<\/strong> Discar com teclado e hist\u00f3rico clic\u00e1vel, atender, recusar, mudo, espera, teclado num\u00e9rico durante a chamada, transfer\u00eancia cega e assistida, e grava\u00e7\u00e3o sob demanda \u2014 condicionada \u00e0 permiss\u00e3o do ramal na central.<\/p>\n<p><strong>Qualidade medida, n\u00e3o sentida.<\/strong> Perda de pacotes e jitter em quatro barras durante a conversa, registradas junto da chamada. Quando o cliente diz que &#8220;ficou ruim&#8221;, existe n\u00famero.<\/p>\n<p><strong>O ticket se escreve sozinho.<\/strong> Identifica\u00e7\u00e3o do cliente 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.<\/p>\n<p><strong>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.<\/p>\n<p><strong>Erros em portugu\u00eas de gente.<\/strong> Vinte c\u00f3digos de erro do SDK de voz traduzidos para causa e a\u00e7\u00e3o, no lugar do c\u00f3digo num\u00e9rico.<\/p>\n<\/section>\n<section class=\"pf-secao\">\n<h2>Tr\u00eas abordagens para o mesmo problema<\/h2>\n<p>Antes da primeira linha, inventariei tr\u00eas implementa\u00e7\u00f5es de telefonia dentro de helpdesk, capacidade por capacidade \u2014 duas anteriores e a que constru\u00ed.<\/p>\n<table>\n<thead>\n<tr>\n<th>Capacidade<\/th>\n<th>Painel que observa<\/th>\n<th>Softphone s\u00f3 no ticket<\/th>\n<th>Esta abordagem<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td>\u00c1udio no pr\u00f3prio navegador<\/td>\n<td>\u274c discador externo<\/td>\n<td>\u2705<\/td>\n<td>\u2705<\/td>\n<\/tr>\n<tr>\n<td>Funciona sem ticket aberto<\/td>\n<td>\u2705<\/td>\n<td>\u274c<\/td>\n<td>\u2705<\/td>\n<\/tr>\n<tr>\n<td>Eventos da central em tempo real<\/td>\n<td>\u2705<\/td>\n<td>\u274c<\/td>\n<td>\u2705<\/td>\n<\/tr>\n<tr>\n<td>Cliente na tela antes do \u201cal\u00f4\u201d<\/td>\n<td>\u2705<\/td>\n<td>\ud83d\udfe1<\/td>\n<td>\u2705<\/td>\n<\/tr>\n<tr>\n<td>Teclado num\u00e9rico na chamada<\/td>\n<td>\u274c<\/td>\n<td>\u274c<\/td>\n<td>\u2705<\/td>\n<\/tr>\n<tr>\n<td>Transfer\u00eancia assistida<\/td>\n<td>\u274c<\/td>\n<td>\u274c<\/td>\n<td>\u2705<\/td>\n<\/tr>\n<tr>\n<td>Grava\u00e7\u00e3o por permiss\u00e3o do ramal<\/td>\n<td>\u274c<\/td>\n<td>\u274c<\/td>\n<td>\u2705<\/td>\n<\/tr>\n<tr>\n<td>Qualidade medida na tela<\/td>\n<td>\u274c<\/td>\n<td>\u274c<\/td>\n<td>\u2705<\/td>\n<\/tr>\n<tr>\n<td>Registro antes do primeiro clique<\/td>\n<td>\u274c<\/td>\n<td>\u274c<\/td>\n<td>\u2705<\/td>\n<\/tr>\n<tr>\n<td>Transcri\u00e7\u00e3o e resumo por IA<\/td>\n<td>\u2705<\/td>\n<td>\u274c<\/td>\n<td>\u2705<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<p>A tabela decidiu a arquitetura: <strong>n\u00e3o trocar uma metade pela outra<\/strong>. O painel que observa j\u00e1 fazia bem a parte de registro; faltava voz. A escolha foi costurar a voz dentro dele \u2014 e a lista de capacidades perdidas no caminho ficou vazia.<\/p>\n<\/section>\n<section class=\"pf-secao\">\n<h2>Como foi constru\u00eddo<\/h2>\n<p><strong>Um c\u00f3digo, tr\u00eas casas.<\/strong> O mesmo frontend roda embutido no helpdesk, direto no navegador e como aplicativo de mesa. Ele detecta o ambiente sozinho e esconde os controles que n\u00e3o fazem sentido em cada um.<\/p>\n<p><strong>Casca fina.<\/strong> O aplicativo instalado tem 44 KB e n\u00e3o cont\u00e9m a l\u00f3gica: carrega a aplica\u00e7\u00e3o do backend em tempo de execu\u00e7\u00e3o. Melhoria nova chega ao atendente com um F5 \u2014 sem reinstalar e sem passar pelo painel de administra\u00e7\u00e3o.<\/p>\n<p><strong>Duas fontes de verdade, com fronteira escrita.<\/strong> O SDK de voz manda enquanto a chamada vive; a API da central manda depois que ela termina. Uma camada de correla\u00e7\u00e3o costura as duas por n\u00famero e janela de tempo, com rede de seguran\u00e7a: sem o registro oficial em 45 segundos, a nota sai com os n\u00fameros do navegador \u2014 declarando a origem.<\/p>\n<\/section>\n<div class=\"pf-galeria\">\n<figure>\n      <img decoding=\"async\" src=\"https:\/\/carloslopes.programandosolucoes.com.br\/wp-content\/uploads\/portfolio\/softphone-zendesk-ticket.png\" alt=\"O softphone embutido no painel lateral do ticket, dentro do helpdesk\" loading=\"lazy\"><figcaption>O softphone embutido no painel lateral do ticket: o atendente n\u00e3o troca de janela para falar com o cliente<\/figcaption><\/figure>\n<figure>\n      <img decoding=\"async\" src=\"https:\/\/carloslopes.programandosolucoes.com.br\/wp-content\/uploads\/portfolio\/softphone-zendesk-painel.png\" alt=\"Primeiro acesso: o softphone pede o ramal do atendente\" loading=\"lazy\"><figcaption>Primeiro acesso: o atendente informa o ramal uma \u00fanica vez e o backend valida a permiss\u00e3o dele na central<\/figcaption><\/figure>\n<\/p><\/div>\n<section class=\"pf-secao\">\n<h2>As telas do discador<\/h2>\n<\/section>\n<div class=\"pf-telas\">\n<figure><img decoding=\"async\" src=\"https:\/\/carloslopes.programandosolucoes.com.br\/wp-content\/uploads\/portfolio\/softphone-discador.png\" alt=\"Discador com teclado, hist\u00f3rico e ramal registrado\" loading=\"lazy\"><figcaption>Discador: teclado, hist\u00f3rico e ramal registrado<\/figcaption><\/figure>\n<figure><img decoding=\"async\" src=\"https:\/\/carloslopes.programandosolucoes.com.br\/wp-content\/uploads\/portfolio\/softphone-entrante.png\" alt=\"Chamada entrante com atender e recusar\" loading=\"lazy\"><figcaption>Chamada entrante: atender ou recusar<\/figcaption><\/figure>\n<figure><img decoding=\"async\" src=\"https:\/\/carloslopes.programandosolucoes.com.br\/wp-content\/uploads\/portfolio\/softphone-conversa.png\" alt=\"Em conversa, com cron\u00f4metro, grava\u00e7\u00e3o e qualidade\" loading=\"lazy\"><figcaption>Em conversa: cron\u00f4metro, grava\u00e7\u00e3o e qualidade medida<\/figcaption><\/figure>\n<figure><img decoding=\"async\" src=\"https:\/\/carloslopes.programandosolucoes.com.br\/wp-content\/uploads\/portfolio\/softphone-espera.png\" alt=\"Chamada em espera\" loading=\"lazy\"><figcaption>Em espera<\/figcaption><\/figure>\n<figure><img decoding=\"async\" src=\"https:\/\/carloslopes.programandosolucoes.com.br\/wp-content\/uploads\/portfolio\/softphone-dtmf.png\" alt=\"Teclado num\u00e9rico durante a chamada\" loading=\"lazy\"><figcaption>Teclado num\u00e9rico durante a chamada<\/figcaption><\/figure>\n<figure><img decoding=\"async\" src=\"https:\/\/carloslopes.programandosolucoes.com.br\/wp-content\/uploads\/portfolio\/softphone-transferir.png\" alt=\"Transfer\u00eancia cega e assistida\" loading=\"lazy\"><figcaption>Transfer\u00eancia cega e assistida<\/figcaption><\/figure>\n<figure><img decoding=\"async\" src=\"https:\/\/carloslopes.programandosolucoes.com.br\/wp-content\/uploads\/portfolio\/softphone-erro.png\" alt=\"Erro traduzido para linguagem do operador\" loading=\"lazy\"><figcaption>Erro traduzido: o operador l\u00ea a causa, n\u00e3o o c\u00f3digo<\/figcaption><\/figure>\n<\/p><\/div>\n<div class=\"pf-repo\">\n<h2>\ud83d\udd12 C\u00f3digo-fonte<\/h2>\n<p>Projeto sob acordo de confidencialidade com o cliente. As telas desta p\u00e1gina est\u00e3o descaracterizadas: marca, cores e dados de exemplo foram substitu\u00eddos. Converso sobre a arquitetura e as decis\u00f5es em detalhe a convite.<\/p>\n<\/p><\/div>\n<div class=\"pf-contato\">\n    <strong>Carlos Alberto<\/strong> \u2014 carlosalberto4ti@gmail.com \u00b7 +55 11 2615-2880\n  <\/div>\n<\/div>\n","protected":false},"excerpt":{"rendered":"<p>&larr; voltar ao portf\u00f3lio 08\/2026 &#8211; atual \u00b7 em andamento Softphone WebRTC que vive dentro do ticket do helpdesk. O atendente disca, atende, transfere e grava sem sair da tela em que j\u00e1 est\u00e1 \u2014 e a pr\u00f3pria chamada produz o registro: cliente identificado, ticket vinculado, grava\u00e7\u00e3o anexada, transcri\u00e7\u00e3o e resumo por IA. JavaScriptZendesk Apps [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":0,"parent":0,"menu_order":0,"comment_status":"closed","ping_status":"closed","template":"","meta":{"footnotes":""},"class_list":["post-659","page","type-page","status-publish","hentry"],"translation":{"provider":"WPGlobus","version":"3.0.2","language":"it","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\/it\/index.php\/wp-json\/wp\/v2\/pages\/659","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/carloslopes.programandosolucoes.com.br\/it\/index.php\/wp-json\/wp\/v2\/pages"}],"about":[{"href":"https:\/\/carloslopes.programandosolucoes.com.br\/it\/index.php\/wp-json\/wp\/v2\/types\/page"}],"author":[{"embeddable":true,"href":"https:\/\/carloslopes.programandosolucoes.com.br\/it\/index.php\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/carloslopes.programandosolucoes.com.br\/it\/index.php\/wp-json\/wp\/v2\/comments?post=659"}],"version-history":[{"count":1,"href":"https:\/\/carloslopes.programandosolucoes.com.br\/it\/index.php\/wp-json\/wp\/v2\/pages\/659\/revisions"}],"predecessor-version":[{"id":668,"href":"https:\/\/carloslopes.programandosolucoes.com.br\/it\/index.php\/wp-json\/wp\/v2\/pages\/659\/revisions\/668"}],"wp:attachment":[{"href":"https:\/\/carloslopes.programandosolucoes.com.br\/it\/index.php\/wp-json\/wp\/v2\/media?parent=659"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}