<?xml version="1.0" encoding="UTF-8"?><rss xmlns:dc="http://purl.org/dc/elements/1.1/" xmlns:content="http://purl.org/rss/1.0/modules/content/" xmlns:atom="http://www.w3.org/2005/Atom" version="2.0"><channel><title><![CDATA[Documentação Técnica - Parte 2]]></title><description><![CDATA[<h2><img src="http://insoft-docker1:4567/assets/plugins/nodebb-plugin-emoji/emoji/android/1f539.png?v=8k80dgruung" class="not-responsive emoji emoji-android emoji--small_blue_diamond" title=":small_blue_diamond:" alt="🔹" /> Fluxo 3: Atualização de Configurações</h2>
<p dir="auto"><strong>Explicação simplificada (Suporte):</strong></p>
<blockquote>
<p dir="auto">O usuário altera as configurações (ex: intervalo de leitura para 500ms, prefixo "ID:"). Ao salvar, as mudanças são aplicadas imediatamente sem reiniciar o sistema.</p>
</blockquote>
<p dir="auto"><strong>Explicação técnica (Dev):</strong></p>
<pre><code>[Frontend] SettingsForm.onSubmit(data)
  │
  ├─► PUT /settings/reader (axios)
  │     │
  │     CardService.updateReaderSettings(payload)
  │       ├─ ReaderSettingsStore.update(payload) → normaliza → grava JSON em disco
  │       └─ DeviceManager.applyReaderSettings(updated)
  │             └─ DigiconDG710Reader.applySettings(settings)
  │                   ├─ pollingMs = settings.intervalMs
  │                   ├─ continuousRead = settings.continuousRead
  │                   └─ reinicia loop de polling (clearTimeout + scheduleNextPoll)
  │
  └─► window.readerApi.syncRuntimeSettings(updated)  [IPC]
        └─ ipcMain: "reader-runtime-settings:update-cache"
              └─ runtimeSettingsCache = { ...cache, ...payload }
                    (usado pelo auto-paste: appendNewLine)
</code></pre>
<hr />
<h2><img src="http://insoft-docker1:4567/assets/plugins/nodebb-plugin-emoji/emoji/android/1f539.png?v=8k80dgruung" class="not-responsive emoji emoji-android emoji--small_blue_diamond" title=":small_blue_diamond:" alt="🔹" /> Fluxo 4: Reconexão de Leitores</h2>
<p dir="auto"><strong>Explicação simplificada (Suporte):</strong></p>
<blockquote>
<p dir="auto">Se o leitor parar de responder, o usuário pode clicar em "Reconectar leitores" no menu da bandeja. O sistema faz disconnect e connect novamente automaticamente.</p>
</blockquote>
<p dir="auto"><strong>Explicação técnica (Dev):</strong></p>
<pre><code>[Tray menu: "Reconectar leitores"]
  │  ou [Frontend: botão Reconectar]
  ▼
performReconnectReaders()
  │
  ├─► GET /readers → lista todos os leitores
  │
  └─► Para cada reader (em paralelo):
        POST /readers/disconnect { key }
        POST /readers/connect    { key }
        POST /readers/start      { key }
</code></pre>
<hr />
<h2><img src="http://insoft-docker1:4567/assets/plugins/nodebb-plugin-emoji/emoji/android/1f539.png?v=8k80dgruung" class="not-responsive emoji emoji-android emoji--small_blue_diamond" title=":small_blue_diamond:" alt="🔹" /> Fluxo 5: Manutenção Automática de Leitores</h2>
<p dir="auto"><strong>Explicação simplificada (Suporte):</strong></p>
<blockquote>
<p dir="auto">A cada 1,5 segundos, o sistema verifica se o leitor está conectado e escutando. Se estiver desconectado, tenta reconectar sozinho.</p>
</blockquote>
<p dir="auto"><strong>Explicação técnica (Dev):</strong></p>
<pre><code>setInterval(service.maintainReaders, 1500ms)
  │
  └─► CardService.maintainReaders()
        Para cada readerKey:
          reader.getStatus()
          ├─ !connected → reader.connect()
          └─ connected + !listening → reader.startListening()
        Erros → logger.debug (não interrompem)
</code></pre>
<hr />
<h1><img src="http://insoft-docker1:4567/assets/plugins/nodebb-plugin-emoji/emoji/android/2699.png?v=8k80dgruung" class="not-responsive emoji emoji-android emoji--gear" title=":gear:" alt="⚙" />️ 5. ROTINAS E MÉTODOS IMPORTANTES</h1>
<h2><img src="http://insoft-docker1:4567/assets/plugins/nodebb-plugin-emoji/emoji/android/1f538.png?v=8k80dgruung" class="not-responsive emoji emoji-android emoji--small_orange_diamond" title=":small_orange_diamond:" alt="🔸" /> <code>bootstrap()</code> — <code>server.ts</code></h2>
<p dir="auto"><strong>Descrição simplificada:</strong></p>
<blockquote>
<p dir="auto">Inicializa toda a API: verifica instância única, cria componentes, monta rotas e inicia o servidor.</p>
</blockquote>
<p dir="auto"><strong>Descrição técnica:</strong></p>
<ul>
<li>Localização: <code>insoft-readers-api/src/server.ts</code></li>
<li>Fluxo: <code>ensureSingleApiInstance()</code> → instância de todos os serviços → monta Express → escuta na porta</li>
<li>Parâmetros: nenhum (usa variáveis de ambiente)</li>
<li>Retorno: <code>Promise&lt;void&gt;</code></li>
<li>Possíveis saídas de emergência: <code>process.exit(0)</code> (duplicata), <code>process.exit(1)</code> (falha crítica)</li>
</ul>
<hr />
<h2><img src="http://insoft-docker1:4567/assets/plugins/nodebb-plugin-emoji/emoji/android/1f538.png?v=8k80dgruung" class="not-responsive emoji emoji-android emoji--small_orange_diamond" title=":small_orange_diamond:" alt="🔸" /> <code>runPollingTick()</code> — <code>DigiconDG710Reader.ts</code></h2>
<p dir="auto"><strong>Descrição simplificada:</strong></p>
<blockquote>
<p dir="auto">O loop que "pergunta" ao hardware a cada N milissegundos se há um cartão presente.</p>
</blockquote>
<p dir="auto"><strong>Descrição técnica:</strong></p>
<ul>
<li>Localização: <code>DigiconDG710Reader</code> (método privado)</li>
<li>Usa <code>setTimeout</code> recursivo (não <code>setInterval</code>), garantindo que o próximo tick só ocorre após o atual terminar</li>
<li>Gerencia estado <code>wasCardPresent</code> para evitar eventos duplicados no modo não-contínuo</li>
<li>Em caso de erro: registra em <code>lastError</code> e continua o polling no próximo tick</li>
<li>Parada: <code>stopListening()</code> define <code>pollingActive = false</code>; o <code>finally</code> do tick detecta isso e não agenda o próximo</li>
</ul>
<hr />
<h2><img src="http://insoft-docker1:4567/assets/plugins/nodebb-plugin-emoji/emoji/android/1f538.png?v=8k80dgruung" class="not-responsive emoji emoji-android emoji--small_orange_diamond" title=":small_orange_diamond:" alt="🔸" /> <code>readMifareCard()</code> — <code>DigiconFacade.ts</code></h2>
<p dir="auto"><strong>Descrição simplificada:</strong></p>
<blockquote>
<p dir="auto">Lê os bytes do cartão físico e os converte para um número decimal (o "código do cartão").</p>
</blockquote>
<p dir="auto"><strong>Descrição técnica:</strong></p>
<ul>
<li>Localização: <code>DigiconFacade</code></li>
<li>Parâmetros: <code>handle: number</code> → identificador do canal conectado</li>
<li>Retorno: <code>{ cardId: string; rawHex: string } | null</code></li>
<li>Algoritmo: buffer de 4 bytes → leitura em ordem decrescente de índice (big-endian) → hex string → <code>BigInt(0x${rawHex}).toString(10)</code></li>
<li>Exemplo: bytes <code>[0x4E, 0x61, 0xBC, 0x00]</code> → rawHex <code>"00BC614E"</code> → cardId <code>"12345678"</code></li>
<li>Em mock mode: retorna número aleatório de 8 dígitos</li>
</ul>
<hr />
<h2><img src="http://insoft-docker1:4567/assets/plugins/nodebb-plugin-emoji/emoji/android/1f538.png?v=8k80dgruung" class="not-responsive emoji emoji-android emoji--small_orange_diamond" title=":small_orange_diamond:" alt="🔸" /> <code>applyRuntimeSettingsToEvent()</code> — <code>CardService.ts</code></h2>
<p dir="auto"><strong>Descrição simplificada:</strong></p>
<blockquote>
<p dir="auto">Adiciona o prefixo e o sufixo configurados ao código do cartão antes de distribuí-lo.</p>
</blockquote>
<p dir="auto"><strong>Descrição técnica:</strong></p>
<ul>
<li>Localização: <code>CardService</code> (método privado)</li>
<li>Parâmetros: <code>event: CardReadEvent</code></li>
<li>Retorno: novo <code>CardReadEvent</code> com <code>cardId = prefix + originalCardId + suffix</code></li>
<li>Não modifica o evento original (imutabilidade via spread <code>{...event}</code>)</li>
<li>Exemplo: prefixo <code>"ID:"</code>, sufixo <code>""</code>, cardId <code>"12345678"</code> → <code>"ID:12345678"</code></li>
</ul>
<hr />
<h2><img src="http://insoft-docker1:4567/assets/plugins/nodebb-plugin-emoji/emoji/android/1f538.png?v=8k80dgruung" class="not-responsive emoji emoji-android emoji--small_orange_diamond" title=":small_orange_diamond:" alt="🔸" /> <code>ensureApiRunning()</code> — <code>ApiProcessManager.ts</code></h2>
<p dir="auto"><strong>Descrição simplificada:</strong></p>
<blockquote>
<p dir="auto">Garante que a API está disponível antes de abrir a janela. Tenta todas as formas de iniciá-la.</p>
</blockquote>
<p dir="auto"><strong>Descrição técnica:</strong></p>
<ul>
<li>Localização: <code>insoft-readers-app/electron/main/services/ApiProcessManager.ts</code></li>
<li>Retorno: <code>Promise&lt;void&gt;</code></li>
<li>Lança <code>Error</code> apenas se todas as tentativas falharem</li>
<li>Em dev: spawna <code>npm run dev:once</code>; em prod: executa o <code>.exe</code> da pasta resources</li>
</ul>
<hr />
<h2><img src="http://insoft-docker1:4567/assets/plugins/nodebb-plugin-emoji/emoji/android/1f538.png?v=8k80dgruung" class="not-responsive emoji emoji-android emoji--small_orange_diamond" title=":small_orange_diamond:" alt="🔸" /> <code>handleCardRead()</code> — <code>main.ts</code></h2>
<p dir="auto"><strong>Descrição simplificada:</strong></p>
<blockquote>
<p dir="auto">Decide o que fazer quando um cartão é lido: mostrar na UI ou colar no aplicativo ativo.</p>
</blockquote>
<p dir="auto"><strong>Descrição técnica:</strong></p>
<ul>
<li>Localização: <code>insoft-readers-app/electron/main/main.ts</code></li>
<li>Se janela em foco: <code>mainWindow.webContents.send("card:read", event)</code></li>
<li>Se janela em background: <code>pasteToExternalFocusedApp(cardId, appendNewLine)</code></li>
<li>Também envia <code>app:notification</code> quando em background (aparece como toast)</li>
</ul>
<hr />
<h2><img src="http://insoft-docker1:4567/assets/plugins/nodebb-plugin-emoji/emoji/android/1f538.png?v=8k80dgruung" class="not-responsive emoji emoji-android emoji--small_orange_diamond" title=":small_orange_diamond:" alt="🔸" /> <code>pasteToExternalFocusedApp()</code> — <code>main.ts</code></h2>
<p dir="auto"><strong>Descrição simplificada:</strong></p>
<blockquote>
<p dir="auto">Cola o código do cartão no campo de texto ativo usando a área de transferência do Windows.</p>
</blockquote>
<p dir="auto"><strong>Descrição técnica:</strong></p>
<ul>
<li>Localização: <code>insoft-readers-app/electron/main/main.ts</code></li>
<li>Usa <code>clipboard.writeText(cardId)</code> do Electron</li>
<li>Executa VBScript pré-criado em <code>%TEMP%</code> (criado na inicialização para evitar latência)
<ul>
<li><code>insoft_paste.vbs</code>: <code>SendKeys "^v"</code> (Ctrl+V)</li>
<li><code>insoft_paste_nl.vbs</code>: <code>SendKeys "^v{ENTER}"</code> (Ctrl+V + Enter)</li>
</ul>
</li>
<li><code>wscript.exe //B</code> suprime diálogos; latência ~50ms vs ~1.500ms do PowerShell</li>
<li>Apenas funciona em <code>win32</code></li>
</ul>
<hr />
<h1><img src="http://insoft-docker1:4567/assets/plugins/nodebb-plugin-emoji/emoji/android/1f517.png?v=8k80dgruung" class="not-responsive emoji emoji-android emoji--link" title=":link:" alt="🔗" /> 6. INTEGRAÇÕES EXTERNAS</h1>
<h2>DLL Nativa — <code>DG710Facade.dll</code></h2>
<table class="table table-bordered table-striped">
<thead>
<tr>
<th>Item</th>
<th>Detalhe</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>Tipo</strong></td>
<td>DLL C/C++ do fabricante Digicon</td>
</tr>
<tr>
<td><strong>Arquitetura</strong></td>
<td>x64 (requerida; x86 causa erro detectável no log)</td>
</tr>
<tr>
<td><strong>Localização</strong></td>
<td>Configurável via <code>DIGICON_DLL_PATH</code>; buscada em ~9 caminhos candidatos</td>
</tr>
<tr>
<td><strong>Integração</strong></td>
<td><code>koffi</code> (FFI puro TypeScript, sem recompilar addons Node)</td>
</tr>
<tr>
<td><strong>Funções usadas</strong></td>
<td><code>registryStart/Stop/IsStarted/GetChannelList</code>, <code>channelConnect/Disconnect/IsConnected/GetSerialNumber</code>, <code>mifareIsCardPresent</code>, <code>mifareReadCardSerialNumber</code></td>
</tr>
<tr>
<td><strong>Fallback</strong></td>
<td>Mock mode automático se a DLL não for encontrada</td>
</tr>
</tbody>
</table>
<p dir="auto"><strong>Pontos de atenção:</strong></p>
<ul>
<li>A DLL deve estar acessível no mesmo diretório do executável ou em caminho absoluto</li>
<li>O processo deve ser x64 para carregar a DLL x64</li>
<li>A DLL pode requerer drivers do fabricante instalados no Windows</li>
</ul>
<hr />
<h2>NSSM — Non-Sucking Service Manager</h2>
<table class="table table-bordered table-striped">
<thead>
<tr>
<th>Item</th>
<th>Detalhe</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>Tipo</strong></td>
<td>Ferramenta externa de linha de comando</td>
</tr>
<tr>
<td><strong>Uso</strong></td>
<td>Gerenciar a API como Serviço Windows</td>
</tr>
<tr>
<td><strong>Comandos</strong></td>
<td><code>sc query &lt;name&gt;</code> (verificar), <code>nssm start &lt;name&gt;</code> (iniciar)</td>
</tr>
<tr>
<td><strong>Requisito</strong></td>
<td><code>nssm.exe</code> deve estar no PATH do sistema</td>
</tr>
</tbody>
</table>
<hr />
<h2>VBScript / wscript.exe</h2>
<table class="table table-bordered table-striped">
<thead>
<tr>
<th>Item</th>
<th>Detalhe</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>Tipo</strong></td>
<td>Runtime nativo do Windows</td>
</tr>
<tr>
<td><strong>Uso</strong></td>
<td>Simular Ctrl+V no aplicativo ativo (auto-paste)</td>
</tr>
<tr>
<td><strong>Mecanismo</strong></td>
<td><code>SendKeys "^v"</code> / <code>SendKeys "^v{ENTER}"</code></td>
</tr>
<tr>
<td><strong>Limitação</strong></td>
<td>Funciona apenas em Windows; requer que o aplicativo de destino suporte Ctrl+V</td>
</tr>
</tbody>
</table>
<hr />
<h1><img src="http://insoft-docker1:4567/assets/plugins/nodebb-plugin-emoji/emoji/android/1f6a8.png?v=8k80dgruung" class="not-responsive emoji emoji-android emoji--rotating_light" title=":rotating_light:" alt="🚨" /> 7. TRATAMENTO DE ERROS E EXCEÇÕES</h1>
<h2>Estratégia Geral</h2>
<table class="table table-bordered table-striped">
<thead>
<tr>
<th>Camada</th>
<th>Estratégia</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>Backend API</strong></td>
<td>Middleware global de erros; <code>ZodError</code> → 400; erros Digicon conhecidos → 503; outros → 500</td>
</tr>
<tr>
<td><strong>DeviceManager</strong></td>
<td>Erros de inicialização/discover logados, não propagados (sistema continua)</td>
</tr>
<tr>
<td><strong>CardService.maintainReaders</strong></td>
<td>Erros de auto-reconexão logados como <code>debug</code>, sem interrupção</td>
</tr>
<tr>
<td><strong>DigiconFacade</strong></td>
<td>Mock mode ativado silenciosamente se DLL não carregar</td>
</tr>
<tr>
<td><strong>Electron main</strong></td>
<td>Erros de startup da API encerram o app via <code>app.quit()</code></td>
</tr>
<tr>
<td><strong>ApiProcessManager</strong></td>
<td>Lança <code>Error</code> se health check final falhar; Electron encerra</td>
</tr>
<tr>
<td><strong>Frontend</strong></td>
<td><code>useReaders</code> captura erros axios e os expõe via estado <code>error</code></td>
</tr>
<tr>
<td><strong>SSE (Electron)</strong></td>
<td><code>scheduleCardEventsReconnect(1500ms)</code> em caso de falha ou disconnect</td>
</tr>
</tbody>
</table>
<h2>Logs Importantes</h2>
<p dir="auto">O backend usa <strong>pino</strong> com logs estruturados JSON. Nível padrão: <code>info</code>.</p>
<table class="table table-bordered table-striped">
<thead>
<tr>
<th>Mensagem de Log</th>
<th>Significado</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>"Reader backend started"</code></td>
<td>API iniciada com sucesso na porta</td>
</tr>
<tr>
<td><code>"API já está em execução. Encerrando instância duplicada."</code></td>
<td>Segunda instância detectada, saiu normalmente</td>
</tr>
<tr>
<td><code>"Porta em uso por outro processo."</code></td>
<td>Porta 3791 ocupada por processo externo</td>
</tr>
<tr>
<td><code>"Reader initialization failed; continuing backend startup"</code></td>
<td>Leitor não inicializou (hardware ausente?)</td>
</tr>
<tr>
<td><code>"Digicon native DLL not loaded. Running in mock mode."</code></td>
<td>DLL não encontrada; simulando hardware</td>
</tr>
<tr>
<td><code>"Failed to load Digicon native DLL"</code></td>
<td>Detalhe do erro ao carregar DLL (inclui arquitetura)</td>
</tr>
<tr>
<td><code>"Reader discovery failed"</code></td>
<td>Leitor não detectado no discover</td>
</tr>
<tr>
<td><code>"Failed to connect Digicon reader"</code></td>
<td>Falha ao conectar; <code>lastError</code> contém detalhes</td>
</tr>
<tr>
<td><code>"Error while reading Digicon card"</code></td>
<td>Erro durante polling de leitura</td>
</tr>
<tr>
<td><code>"Card read"</code></td>
<td>Cartão lido com sucesso (inclui o evento completo)</td>
</tr>
<tr>
<td><code>"Shutting down backend"</code></td>
<td>Shutdown iniciado (SIGTERM/SIGINT)</td>
</tr>
</tbody>
</table>
<h2>Variáveis de Ambiente para Diagnóstico</h2>
<table class="table table-bordered table-striped">
<thead>
<tr>
<th>Variável</th>
<th>Padrão</th>
<th>Uso</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>LOG_LEVEL</code></td>
<td><code>info</code></td>
<td>Nível de log (<code>debug</code>, <code>info</code>, <code>warn</code>, <code>error</code>)</td>
</tr>
<tr>
<td><code>BACKEND_PORT</code></td>
<td><code>3791</code></td>
<td>Porta do servidor HTTP da API</td>
</tr>
<tr>
<td><code>BACKEND_BASE_URL</code></td>
<td><code>http://127.0.0.1:3791</code></td>
<td>URL base da API</td>
</tr>
<tr>
<td><code>APP_CONTROL_URL</code></td>
<td><code>http://127.0.0.1:3792</code></td>
<td>URL do AppControlServer</td>
</tr>
<tr>
<td><code>DIGICON_DLL_PATH</code></td>
<td><code>DG710Facade.dll</code></td>
<td>Caminho explícito da DLL nativa</td>
</tr>
<tr>
<td><code>READER_SETTINGS_FILE</code></td>
<td><code>reader-runtime-settings.json</code> (cwd)</td>
<td>Arquivo de settings</td>
</tr>
<tr>
<td><code>API_SERVICE_NAME</code></td>
<td><code>InsoftReadersApiService</code></td>
<td>Nome do serviço Windows</td>
</tr>
<tr>
<td><code>API_EXE_NAME</code></td>
<td><code>insoft-reader-api.exe</code></td>
<td>Nome do executável da API</td>
</tr>
<tr>
<td><code>API_EXE_PATH</code></td>
<td>—</td>
<td>Caminho absoluto do executável (override)</td>
</tr>
<tr>
<td><code>APP_EXECUTABLE_PATH</code></td>
<td>—</td>
<td>Caminho do <code>.exe</code> da interface (override)</td>
</tr>
</tbody>
</table>
<hr />
<h1>🧪 8. CENÁRIOS COMUNS DE SUPORTE</h1>
<h2><img src="http://insoft-docker1:4567/assets/plugins/nodebb-plugin-emoji/emoji/android/1f6d1.png?v=8k80dgruung" class="not-responsive emoji emoji-android emoji--octagonal_sign" title=":octagonal_sign:" alt="🛑" /> Cenário 1: O cartão não está sendo lido</h2>
<table class="table table-bordered table-striped">
<thead>
<tr>
<th></th>
<th></th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>Sintoma</strong></td>
<td>Aproxima o cartão e nada acontece</td>
</tr>
<tr>
<td><strong>Possíveis causas</strong></td>
<td>(a) Leitor desconectado; (b) API não está rodando; (c) Leitor em estado "stopped"; (d) DLL não encontrada (modo mock)</td>
</tr>
<tr>
<td><strong>Como investigar</strong></td>
<td>1. Abrir a interface → Dashboard → verificar se "Active Readers" &gt; 0; 2. Verificar se status é "Connected" e "Listening" na página Readers; 3. Verificar logs do backend por <code>"mock mode"</code> ou <code>"Reader not found"</code></td>
</tr>
<tr>
<td><strong>Como resolver</strong></td>
<td>1. Página Readers → selecionar leitor → clicar "Conectar" e depois "Iniciar leitura"; 2. Menu da bandeja → "Reconectar leitores"; 3. Se DLL: verificar se <code>DG710Facade.dll</code> está no diretório da API</td>
</tr>
</tbody>
</table>
<hr />
<h2><img src="http://insoft-docker1:4567/assets/plugins/nodebb-plugin-emoji/emoji/android/1f6d1.png?v=8k80dgruung" class="not-responsive emoji emoji-android emoji--octagonal_sign" title=":octagonal_sign:" alt="🛑" /> Cenário 2: O código do cartão está errado (com caracteres extras)</h2>
<table class="table table-bordered table-striped">
<thead>
<tr>
<th></th>
<th></th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>Sintoma</strong></td>
<td>O cartão é lido mas o código aparece com prefixo/sufixo estranho (ex: <code>"AAA12345678BBBB"</code>)</td>
</tr>
<tr>
<td><strong>Possíveis causas</strong></td>
<td>Configurações de prefixo/sufixo estão definidas</td>
</tr>
<tr>
<td><strong>Como investigar</strong></td>
<td>Abrir interface → Settings → verificar campos "Prefix" e "Suffix"</td>
</tr>
<tr>
<td><strong>Como resolver</strong></td>
<td>Settings → limpar campos Prefix e Suffix → salvar</td>
</tr>
</tbody>
</table>
<hr />
<h2><img src="http://insoft-docker1:4567/assets/plugins/nodebb-plugin-emoji/emoji/android/1f6d1.png?v=8k80dgruung" class="not-responsive emoji emoji-android emoji--octagonal_sign" title=":octagonal_sign:" alt="🛑" /> Cenário 3: O cartão está sendo lido mas não é colado no aplicativo</h2>
<table class="table table-bordered table-striped">
<thead>
<tr>
<th></th>
<th></th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>Sintoma</strong></td>
<td>O toast de notificação aparece mas o texto não é inserido no campo</td>
</tr>
<tr>
<td><strong>Possíveis causas</strong></td>
<td>(a) Janela do Insoft Readers está em foco (comportamento esperado); (b) Aplicativo de destino não suporta Ctrl+V</td>
</tr>
<tr>
<td><strong>Como investigar</strong></td>
<td>1. Verificar se a janela do Insoft Readers está na frente — se sim, o modo é exibição na UI, não auto-paste; 2. Testar colar manualmente no campo do aplicativo (Ctrl+V)</td>
</tr>
<tr>
<td><strong>Como resolver</strong></td>
<td>Minimizar ou mover a janela do Insoft Readers para segundo plano antes de aproximar o cartão</td>
</tr>
</tbody>
</table>
<hr />
<h2><img src="http://insoft-docker1:4567/assets/plugins/nodebb-plugin-emoji/emoji/android/1f6d1.png?v=8k80dgruung" class="not-responsive emoji emoji-android emoji--octagonal_sign" title=":octagonal_sign:" alt="🛑" /> Cenário 4: A interface abre mas mostra erro de conexão</h2>
<table class="table table-bordered table-striped">
<thead>
<tr>
<th></th>
<th></th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>Sintoma</strong></td>
<td>Frontend exibe mensagem de erro ao carregar leitores</td>
</tr>
<tr>
<td><strong>Possíveis causas</strong></td>
<td>API não está rodando na porta 3791</td>
</tr>
<tr>
<td><strong>Como investigar</strong></td>
<td>Abrir navegador → acessar <code>http://127.0.0.1:3791/health</code> → se retornar <code>{"status":"ok"}</code>, API está OK</td>
</tr>
<tr>
<td><strong>Como resolver</strong></td>
<td>1. Aguardar alguns segundos (API pode estar iniciando); 2. Menu da bandeja → Reconectar leitores; 3. Fechar e reabrir o aplicativo</td>
</tr>
</tbody>
</table>
<hr />
<h2><img src="http://insoft-docker1:4567/assets/plugins/nodebb-plugin-emoji/emoji/android/1f6d1.png?v=8k80dgruung" class="not-responsive emoji emoji-android emoji--octagonal_sign" title=":octagonal_sign:" alt="🛑" /> Cenário 5: Erro "Arquitetura incompatível" no log</h2>
<table class="table table-bordered table-striped">
<thead>
<tr>
<th></th>
<th></th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>Sintoma</strong></td>
<td>Log contém <code>"Arquitetura incompatível: a DLL é x86 (32-bit)"</code></td>
</tr>
<tr>
<td><strong>Possíveis causas</strong></td>
<td><code>DG710Facade.dll</code> é versão 32-bit mas o processo é 64-bit</td>
</tr>
<tr>
<td><strong>Como investigar</strong></td>
<td>Verificar qual versão da DLL está instalada</td>
</tr>
<tr>
<td><strong>Como resolver</strong></td>
<td>Substituir a DLL pela versão x64 fornecida pelo fabricante Digicon</td>
</tr>
</tbody>
</table>
<hr />
<h2><img src="http://insoft-docker1:4567/assets/plugins/nodebb-plugin-emoji/emoji/android/1f6d1.png?v=8k80dgruung" class="not-responsive emoji emoji-android emoji--octagonal_sign" title=":octagonal_sign:" alt="🛑" /> Cenário 6: Porta 3791 já está em uso</h2>
<table class="table table-bordered table-striped">
<thead>
<tr>
<th></th>
<th></th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>Sintoma</strong></td>
<td>Log contém <code>"Porta em uso por outro processo. Abortando inicialização da API."</code> e a API não inicia</td>
</tr>
<tr>
<td><strong>Possíveis causas</strong></td>
<td>Outro processo ocupou a porta 3791</td>
</tr>
<tr>
<td><strong>Como investigar</strong></td>
<td>Executar no terminal: <code>netstat -ano | findstr 3791</code> para ver qual processo usa a porta</td>
</tr>
<tr>
<td><strong>Como resolver</strong></td>
<td>Encerrar o processo conflitante ou configurar <code>BACKEND_PORT</code> para outra porta</td>
</tr>
</tbody>
</table>
<hr />
<h2><img src="http://insoft-docker1:4567/assets/plugins/nodebb-plugin-emoji/emoji/android/1f6d1.png?v=8k80dgruung" class="not-responsive emoji emoji-android emoji--octagonal_sign" title=":octagonal_sign:" alt="🛑" /> Cenário 7: Leitor conecta mas para de responder após um tempo</h2>
<table class="table table-bordered table-striped">
<thead>
<tr>
<th></th>
<th></th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>Sintoma</strong></td>
<td>Leituras param sem erro aparente; leitor aparece como conectado mas não lê</td>
</tr>
<tr>
<td><strong>Possíveis causas</strong></td>
<td>(a) Leitor fisicamente desconectado (USB); (b) Timeout de hardware</td>
</tr>
<tr>
<td><strong>Como investigar</strong></td>
<td>Dashboard → verificar se "Listening" mudou para "Stopped" ou se há <code>lastError</code> na página Readers</td>
</tr>
<tr>
<td><strong>Como resolver</strong></td>
<td>O timer de manutenção (1,5s) tenta reconexão automática. Se persistir: desconectar fisicamente e reconectar o USB; usar "Reconectar leitores"</td>
</tr>
</tbody>
</table>
<hr />
<h1><img src="http://insoft-docker1:4567/assets/plugins/nodebb-plugin-emoji/emoji/android/1f4dd.png?v=8k80dgruung" class="not-responsive emoji emoji-android emoji--memo" title=":memo:" alt="📝" /> 9. BOAS PRÁTICAS E OBSERVAÇÕES</h1>
<h2>Adicionando Suporte a Novo Modelo de Leitor</h2>
<p dir="auto">Para adicionar um novo leitor (ex: fabricante X, modelo Y):</p>
<ol>
<li>Criar pasta: <code>insoft-readers-api/src/devices/fabricante-x/</code></li>
<li>Implementar a interface <code>ICardReader</code> em uma nova classe</li>
<li>Registrar em <code>server.ts</code>:<pre><code class="language-typescript">registry.register("fabricante-x-modelY", () =&gt; new FabricanteXReaderY());
</code></pre>
</li>
<li>Se usar DLL nativa: criar uma Facade similar ao <code>DigiconFacade.ts</code></li>
</ol>
<h2>Pontos Sensíveis ao Modificar</h2>
<table class="table table-bordered table-striped">
<thead>
<tr>
<th>Área</th>
<th>Cuidado</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong><code>normalizeSettings()</code></strong></td>
<td>Limites de <code>intervalMs</code> (50ms–10s) estão validados aqui e também no Zod do settings.routes. Alterar um sem o outro cria inconsistência</td>
</tr>
<tr>
<td><strong><code>readMifareCard()</code></strong></td>
<td>A ordem de bytes (little-endian → big-endian) é específica do hardware Digicon. Alterar sem hardware para testar pode causar IDs incorretos</td>
</tr>
<tr>
<td><strong><code>pasteToExternalFocusedApp()</code></strong></td>
<td>Os arquivos <code>.vbs</code> são criados uma vez na inicialização. Se <code>tmpdir()</code> não for gravável, o auto-paste falha silenciosamente</td>
</tr>
<tr>
<td><strong><code>ensureSingleApiInstance()</code></strong></td>
<td>Depende do endpoint <code>/health</code> retornar exatamente <code>{ "status": "ok" }</code>. Mudanças no formato quebram a detecção de instância duplicada</td>
</tr>
<tr>
<td><strong>Porta 3792 (AppControlServer)</strong></td>
<td>É usada bidirecionalmente: Electron escuta notificações vindas da API. Conflitos de porta impedem notificações no tray</td>
</tr>
<tr>
<td><strong><code>recentReads.splice(50)</code></strong></td>
<td>O histórico é armazenado apenas em memória. Reiniciar a API limpa o histórico</td>
</tr>
</tbody>
</table>
<h2>Configuração de Runtime em Produção</h2>
<p dir="auto">O arquivo <code>reader-runtime-settings.json</code> é persistido no diretório de trabalho (<code>cwd</code>) da API. Em produção, isso é o diretório do executável. Ao atualizar a API, preserve esse arquivo para não perder configurações.</p>
<h2>Modo de Desenvolvimento</h2>
<p dir="auto">Para rodar o projeto em modo desenvolvimento:</p>
<pre><code class="language-bash"># Na raiz do monorepo — inicia todos os serviços simultaneamente:
npm run dev

# Separado:
npm run dev:frontend   # Vite na porta 5173
npm run dev:electron   # Electron + TypeScript watch
# A API é iniciada automaticamente pelo Electron via ApiProcessManager
</code></pre>
<h2>Compilação para Produção</h2>
<pre><code class="language-bash"># Gera instalador NSIS para Windows x64:
npm run dist

# Resultado em:
# insoft-readers-app/electron/release/
#   Insoft-Readers-1.0.0-Setup-x64.exe
</code></pre>
<p dir="auto">O instalador empacota:</p>
<ul>
<li><code>resources/api/insoft-reader-api.exe</code> — API compilada com <code>pkg</code></li>
<li><code>resources/frontend/</code> — Build estático do React</li>
<li><code>resources/</code> — Preload, assets</li>
</ul>
<hr />
<h2>Diagrama de Dependências dos Pacotes</h2>
<pre><code>@insoft/backend (API)
  ├── express          ← servidor HTTP
  ├── koffi            ← FFI para DLL nativa
  ├── pino             ← logging
  ├── zod              ← validação
  └── dotenv           ← variáveis de ambiente

@insoft/electron
  └── electron         ← framework desktop

@insoft/frontend
  ├── react            ← UI
  ├── zustand          ← estado global
  ├── axios            ← cliente HTTP
  ├── react-hook-form  ← formulários
  ├── zod              ← validação de forms
  ├── flowbite-react   ← componentes UI
  └── tailwindcss      ← estilos
</code></pre>
<hr />
<p dir="auto"><em>Documentação gerada a partir do código-fonte do projeto. Para atualizações, reanalise os arquivos correspondentes após mudanças significativas na arquitetura e atualize essa documentação.</em></p>
]]></description><link>http://insoft-docker1:4567/topic/506/documentação-técnica-parte-2</link><generator>RSS for Node</generator><lastBuildDate>Sat, 06 Jun 2026 00:49:01 GMT</lastBuildDate><atom:link href="http://insoft-docker1:4567/topic/506.rss" rel="self" type="application/rss+xml"/><pubDate>Fri, 22 May 2026 17:03:28 GMT</pubDate><ttl>60</ttl></channel></rss>