<?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 1]]></title><description><![CDATA[<h1><img src="http://insoft-docker1:4567/assets/plugins/nodebb-plugin-emoji/emoji/android/1f4da.png?v=8k80dgruung" class="not-responsive emoji emoji-android emoji--books" title=":books:" alt="📚" /> Documentação Técnica — Insoft Readers App</h1>
<blockquote>
<p dir="auto"><strong>Versão:</strong> 1.0.0<br />
<strong>Data:</strong> Abril de 2026<br />
<strong>Plataforma:</strong> Windows (x64)<br />
<strong>Público:</strong> Equipe de Suporte Técnico · Desenvolvedores</p>
</blockquote>
<hr />
<h1><img src="http://insoft-docker1:4567/assets/plugins/nodebb-plugin-emoji/emoji/android/1f4cc.png?v=8k80dgruung" class="not-responsive emoji emoji-android emoji--pushpin" title=":pushpin:" alt="📌" /> 1. VISÃO GERAL DO SISTEMA</h1>
<h2>Objetivo do Sistema</h2>
<p dir="auto">O <strong>Insoft Readers App</strong> é uma aplicação desktop para Windows que gerencia leitores de cartões RFID/Mifare (modelo Digicon DG-710) e expõe uma API local para que outros sistemas possam receber e processar os dados de leitura.</p>
<h2>Problema que Resolve</h2>
<p dir="auto">Empresas que utilizam leitores de cartão de proximidade precisam integrar essas leituras a outros softwares (sistemas de ponto, acesso, automação etc.). O Insoft Readers App resolve isso ao:</p>
<ol>
<li>Detectar automaticamente o leitor físico conectado ao computador</li>
<li>Ler os cartões aproximados ao leitor em tempo real</li>
<li>Enviar o código do cartão lido automaticamente para qualquer campo de texto ativo em qualquer aplicativo (como um teclado virtual)</li>
<li>Disponibilizar uma API local REST para que outros sistemas consumam os eventos de leitura</li>
</ol>
<h2>Público-alvo</h2>
<table class="table table-bordered table-striped">
<thead>
<tr>
<th>Perfil</th>
<th>Uso</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>Usuário final</strong></td>
<td>Vê o ícone na bandeja do sistema; o cartão é automaticamente "digitado" na aplicação aberta</td>
</tr>
<tr>
<td><strong>Analista de Suporte</strong></td>
<td>Monitora status dos leitores, diagnostica falhas de conexão e configuração</td>
</tr>
<tr>
<td><strong>Desenvolvedor</strong></td>
<td>Integra o sistema via API HTTP local, configura parâmetros de leitura</td>
</tr>
</tbody>
</table>
<h2>Principais Funcionalidades</h2>
<ul>
<li><img src="http://insoft-docker1:4567/assets/plugins/nodebb-plugin-emoji/emoji/android/2705.png?v=8k80dgruung" class="not-responsive emoji emoji-android emoji--white_check_mark" title=":white_check_mark:" alt="✅" /> Leitura automática de cartões Mifare/RFID via leitor Digicon DG-710</li>
<li><img src="http://insoft-docker1:4567/assets/plugins/nodebb-plugin-emoji/emoji/android/2705.png?v=8k80dgruung" class="not-responsive emoji emoji-android emoji--white_check_mark" title=":white_check_mark:" alt="✅" /> Auto-paste: cola o código do cartão no campo de texto ativo do Windows</li>
<li><img src="http://insoft-docker1:4567/assets/plugins/nodebb-plugin-emoji/emoji/android/2705.png?v=8k80dgruung" class="not-responsive emoji emoji-android emoji--white_check_mark" title=":white_check_mark:" alt="✅" /> API REST local (porta <code>3791</code>) para integração com outros sistemas</li>
<li><img src="http://insoft-docker1:4567/assets/plugins/nodebb-plugin-emoji/emoji/android/2705.png?v=8k80dgruung" class="not-responsive emoji emoji-android emoji--white_check_mark" title=":white_check_mark:" alt="✅" /> Stream de eventos em tempo real (SSE) para consumo live dos cartões lidos</li>
<li><img src="http://insoft-docker1:4567/assets/plugins/nodebb-plugin-emoji/emoji/android/2705.png?v=8k80dgruung" class="not-responsive emoji emoji-android emoji--white_check_mark" title=":white_check_mark:" alt="✅" /> Interface gráfica (dashboard) para monitoramento e configuração</li>
<li><img src="http://insoft-docker1:4567/assets/plugins/nodebb-plugin-emoji/emoji/android/2705.png?v=8k80dgruung" class="not-responsive emoji emoji-android emoji--white_check_mark" title=":white_check_mark:" alt="✅" /> Ícone na bandeja do sistema (system tray) com notificações</li>
<li><img src="http://insoft-docker1:4567/assets/plugins/nodebb-plugin-emoji/emoji/android/2705.png?v=8k80dgruung" class="not-responsive emoji emoji-android emoji--white_check_mark" title=":white_check_mark:" alt="✅" /> Configuração de prefixo/sufixo, intervalo de polling e modo contínuo</li>
<li><img src="http://insoft-docker1:4567/assets/plugins/nodebb-plugin-emoji/emoji/android/2705.png?v=8k80dgruung" class="not-responsive emoji emoji-android emoji--white_check_mark" title=":white_check_mark:" alt="✅" /> Suporte a execução como Serviço do Windows (via NSSM)</li>
<li><img src="http://insoft-docker1:4567/assets/plugins/nodebb-plugin-emoji/emoji/android/2705.png?v=8k80dgruung" class="not-responsive emoji emoji-android emoji--white_check_mark" title=":white_check_mark:" alt="✅" /> Modo mock (sem hardware): simula leituras para desenvolvimento/testes</li>
</ul>
<hr />
<h1><img src="http://insoft-docker1:4567/assets/plugins/nodebb-plugin-emoji/emoji/android/1f3d7.png?v=8k80dgruung" class="not-responsive emoji emoji-android emoji--building_construction" title=":building_construction:" alt="🏗" />️ 2. ARQUITETURA DO SISTEMA</h1>
<h2>Visão Macro</h2>
<p dir="auto">O sistema é composto por <strong>três camadas</strong> independentes que se comunicam via HTTP local:</p>
<pre><code>┌─────────────────────────────────────────────────────────────────┐
│                        HARDWARE                                  │
│              Leitor Digicon DG-710 (USB/Serial)                  │
│              DG710Facade.dll (DLL nativa Windows)                │
└──────────────────────────┬──────────────────────────────────────┘
                           │ FFI (koffi)
┌──────────────────────────▼──────────────────────────────────────┐
│                    BACKEND API (@insoft/backend)                  │
│           Express HTTP · porta 3791 · Node.js / TypeScript        │
│    Gerencia leitores · processa eventos · persiste configurações  │
└────────────┬──────────────────────────────────────┬─────────────┘
             │ REST + SSE (HTTP)                     │ HTTP POST /notify
             │                                       │
┌────────────▼──────────────┐          ┌────────────▼─────────────┐
│   FRONTEND (@insoft/      │          │  ELECTRON (@insoft/       │
│   frontend)               │          │  electron)                │
│   React · Vite · Zustand  │◄─────────│  Processo principal       │
│   Interface de usuário    │   IPC     │  porta control: 3792      │
│   porta dev: 5173         │          │  System Tray · Auto-paste │
└───────────────────────────┘          └──────────────────────────┘
</code></pre>
<h2>Como os Componentes se Comunicam</h2>
<table class="table table-bordered table-striped">
<thead>
<tr>
<th>De</th>
<th>Para</th>
<th>Protocolo</th>
<th>Finalidade</th>
</tr>
</thead>
<tbody>
<tr>
<td>Frontend (React)</td>
<td>Backend API</td>
<td>HTTP REST (axios)</td>
<td>Listar leitores, controlar conexão, buscar configurações</td>
</tr>
<tr>
<td>Frontend (React)</td>
<td>Backend API</td>
<td>SSE (EventSource)</td>
<td>Receber eventos de cartão em tempo real</td>
</tr>
<tr>
<td>Electron (main)</td>
<td>Backend API</td>
<td>HTTP REST (fetch)</td>
<td>Garantir que a API está rodando, reconectar leitores</td>
</tr>
<tr>
<td>Electron (main)</td>
<td>Backend API</td>
<td>SSE (fetch + stream)</td>
<td>Escutar eventos de cartão para auto-paste</td>
</tr>
<tr>
<td>Backend API</td>
<td>Electron (main)</td>
<td>HTTP POST porta 3792</td>
<td>Enviar notificações para a interface</td>
</tr>
<tr>
<td>Frontend</td>
<td>Electron (main)</td>
<td>IPC (contextBridge)</td>
<td>Controle de janela, tema, cache de settings</td>
</tr>
<tr>
<td>Electron (main)</td>
<td>Sistema Windows</td>
<td>VBScript (wscript.exe)</td>
<td>Simular Ctrl+V no aplicativo ativo</td>
</tr>
</tbody>
</table>
<h2>Tecnologias Utilizadas</h2>
<table class="table table-bordered table-striped">
<thead>
<tr>
<th>Tecnologia</th>
<th>Versão</th>
<th>Uso</th>
</tr>
</thead>
<tbody>
<tr>
<td><strong>Node.js</strong></td>
<td>≥ 20</td>
<td>Runtime do Backend e Electron</td>
</tr>
<tr>
<td><strong>TypeScript</strong></td>
<td>5.8</td>
<td>Linguagem principal (todos os módulos)</td>
</tr>
<tr>
<td><strong>Electron</strong></td>
<td>35</td>
<td>Aplicação desktop (janela, tray, IPC)</td>
</tr>
<tr>
<td><strong>Express</strong></td>
<td>4.21</td>
<td>Servidor HTTP da API REST</td>
</tr>
<tr>
<td><strong>React</strong></td>
<td>18.3</td>
<td>Interface gráfica do usuário</td>
</tr>
<tr>
<td><strong>Vite</strong></td>
<td>6.2</td>
<td>Bundler do frontend</td>
</tr>
<tr>
<td><strong>Zustand</strong></td>
<td>5.0</td>
<td>Gerenciamento de estado do frontend</td>
</tr>
<tr>
<td><strong>Zod</strong></td>
<td>3.24</td>
<td>Validação de dados (backend e frontend)</td>
</tr>
<tr>
<td><strong>koffi</strong></td>
<td>2.13</td>
<td>FFI: chama funções da DLL nativa Digicon</td>
</tr>
<tr>
<td><strong>pino</strong></td>
<td>9.6</td>
<td>Logging estruturado no backend</td>
</tr>
<tr>
<td><strong>Tailwind CSS</strong></td>
<td>3.4</td>
<td>Estilos do frontend</td>
</tr>
<tr>
<td><strong>Flowbite React</strong></td>
<td>0.10</td>
<td>Componentes UI</td>
</tr>
<tr>
<td><strong>axios</strong></td>
<td>1.8</td>
<td>Cliente HTTP do frontend</td>
</tr>
<tr>
<td><strong>electron-builder</strong></td>
<td>25</td>
<td>Empacotamento do instalador</td>
</tr>
<tr>
<td><strong>NSSM</strong></td>
<td>—</td>
<td>Gerenciador de Serviço Windows (externo)</td>
</tr>
</tbody>
</table>
<hr />
<h1><img src="http://insoft-docker1:4567/assets/plugins/nodebb-plugin-emoji/emoji/android/1f4c1.png?v=8k80dgruung" class="not-responsive emoji emoji-android emoji--file_folder" title=":file_folder:" alt="📁" /> 3. ESTRUTURA DE PASTAS E COMPONENTES</h1>
<p dir="auto">O projeto é um <strong>monorepo npm workspaces</strong> com três pacotes independentes:</p>
<pre><code>insoft-readers-app/                ← Raiz do monorepo
├── package.json                   ← Workspace root (scripts globais)
├── insoft-readers-api/            ← Pacote: @insoft/backend
└── insoft-readers-app/
    ├── electron/                  ← Pacote: @insoft/electron
    └── frontend/                  ← Pacote: @insoft/frontend
</code></pre>
<hr />
<h2><img src="http://insoft-docker1:4567/assets/plugins/nodebb-plugin-emoji/emoji/android/1f4c2.png?v=8k80dgruung" class="not-responsive emoji emoji-android emoji--open_file_folder" title=":open_file_folder:" alt="📂" /> <code>insoft-readers-api/</code> — Backend API</h2>
<p dir="auto"><strong>Descrição funcional (para suporte):</strong></p>
<blockquote>
<p dir="auto">É o "motor" do sistema. Fica rodando em segundo plano, se comunica com o hardware do leitor e disponibiliza um servidor web local para que a interface e outros sistemas possam receber os dados dos cartões. Roda na porta <strong>3791</strong>.</p>
</blockquote>
<p dir="auto"><strong>Responsabilidades técnicas:</strong></p>
<ul>
<li>Gerenciar o ciclo de vida dos leitores de cartão (inicializar, conectar, desconectar, iniciar/parar polling)</li>
<li>Processar eventos de leitura aplicando formatação (prefixo/sufixo)</li>
<li>Manter histórico dos últimos 50 cartões lidos</li>
<li>Persistir configurações de runtime em arquivo JSON</li>
<li>Emitir eventos SSE para clientes conectados</li>
<li>Garantir que somente uma instância está rodando (single-instance check)</li>
</ul>
<hr />
<h3><img src="http://insoft-docker1:4567/assets/plugins/nodebb-plugin-emoji/emoji/android/1f4c2.png?v=8k80dgruung" class="not-responsive emoji emoji-android emoji--open_file_folder" title=":open_file_folder:" alt="📂" /> <code>src/server.ts</code> — Ponto de entrada do Backend</h3>
<p dir="auto"><strong>Descrição funcional:</strong></p>
<blockquote>
<p dir="auto">Inicializa tudo ao ser executado: verifica se já existe uma API rodando, registra os leitores, monta o servidor HTTP e começa a escutar na porta 3791.</p>
</blockquote>
<p dir="auto"><strong>Descrição técnica:</strong></p>
<ul>
<li>Executa <code>ensureSingleApiInstance()</code>: verifica via <code>/health</code> se já existe instância; se sim, encerra com <code>process.exit(0)</code>; se a porta estiver ocupada por outro processo, encerra com <code>process.exit(1)</code></li>
<li>Instancia <code>ReaderRegistry</code>, <code>DeviceManager</code>, <code>CardService</code>, <code>CardEventBroker</code>, <code>AppProcessManager</code></li>
<li>Monta rotas Express: <code>/health</code>, <code>/readers</code>, <code>/cards</code>, <code>/settings</code></li>
<li>Configura middleware: <code>cors</code>, <code>express.json()</code>, <code>pinoHttp</code> (logging HTTP)</li>
<li>Tratamento global de erros: <code>ZodError</code> → HTTP 400; erros Digicon conhecidos → HTTP 503; outros → HTTP 500</li>
<li>Inicia timer de manutenção (<code>maintainReaders</code>) a cada <strong>1.500ms</strong></li>
<li>Registra handlers de <code>SIGINT</code>/<code>SIGTERM</code> para shutdown gracioso</li>
</ul>
<hr />
<h3><img src="http://insoft-docker1:4567/assets/plugins/nodebb-plugin-emoji/emoji/android/1f4c2.png?v=8k80dgruung" class="not-responsive emoji emoji-android emoji--open_file_folder" title=":open_file_folder:" alt="📂" /> <code>src/core/</code> — Núcleo da Aplicação</h3>
<h4><code>ReaderRegistry.ts</code></h4>
<p dir="auto"><strong>Descrição funcional:</strong></p>
<blockquote>
<p dir="auto">Um catálogo que registra quais tipos de leitores o sistema conhece. Funciona como uma "fábrica" de leitores.</p>
</blockquote>
<p dir="auto"><strong>Descrição técnica:</strong></p>
<ul>
<li>Padrão de projeto: <strong>Registry + Factory</strong></li>
<li><code>register(key, factory)</code>: registra uma função construtora associada a uma chave única (ex: <code>"digicon-dg710"</code>)</li>
<li><code>create(key)</code>: chama a factory correspondente, retornando uma nova instância de <code>ICardReader</code></li>
<li>Lança <code>Error</code> se a chave já existe (prevent duplicates) ou se a chave não foi registrada</li>
</ul>
<hr />
<h4><code>DeviceManager.ts</code></h4>
<p dir="auto"><strong>Descrição funcional:</strong></p>
<blockquote>
<p dir="auto">O gerente dos leitores físicos. Agrupa todos os leitores e permite operações em batch (inicializar todos, descobrir todos, aplicar configurações em todos).</p>
</blockquote>
<p dir="auto"><strong>Descrição técnica:</strong></p>
<ul>
<li>Estende <code>EventEmitter</code> do Node.js</li>
<li>Mantém um <code>Map&lt;string, ICardReader&gt;</code> internamente</li>
<li><code>addReader(reader)</code>: adiciona o leitor ao mapa e registra o callback <code>onCardRead</code> para reemitir o evento <code>"cardRead"</code> no próprio <code>DeviceManager</code></li>
<li><code>initializeAll()</code>: chama <code>reader.initialize()</code> em cada leitor; falhas são logadas mas não interrompem a inicialização dos demais</li>
<li><code>discoverAll()</code>: chama <code>reader.discover()</code> opcionalmente (método opcional na interface)</li>
<li><code>applyReaderSettings(settings)</code>: propaga as configurações de runtime para todos os leitores que implementam <code>applySettings</code></li>
<li><code>onCardRead(listener)</code>: atalho para escutar o evento <code>"cardRead"</code> do EventEmitter</li>
</ul>
<hr />
<h4><code>ReaderSettingsStore.ts</code></h4>
<p dir="auto"><strong>Descrição funcional:</strong></p>
<blockquote>
<p dir="auto">Salva e carrega as configurações de como o leitor deve funcionar (ex: intervalo de leitura, prefixo/sufixo do cartão). As configurações ficam gravadas no arquivo <code>reader-runtime-settings.json</code>.</p>
</blockquote>
<p dir="auto"><strong>Descrição técnica:</strong></p>
<ul>
<li>Lê/escreve o arquivo <code>reader-runtime-settings.json</code> (caminho configurável via <code>READER_SETTINGS_FILE</code>)</li>
<li><code>load()</code>: carrega do disco; se o arquivo não existir ou for inválido, usa <code>defaultReaderRuntimeSettings</code></li>
<li><code>get()</code>: retorna as configurações em memória</li>
<li><code>update(next)</code>: mescla com as configurações atuais, valida/normaliza via <code>normalizeSettings()</code> e persiste em disco</li>
<li><code>normalizeSettings()</code>: garante que <code>intervalMs</code> está entre 50ms e 10.000ms, prefixo/sufixo têm no máximo 32 chars</li>
</ul>
<hr />
<h4><code>CardService.ts</code></h4>
<p dir="auto"><strong>Descrição funcional:</strong></p>
<blockquote>
<p dir="auto">O cérebro do sistema. Orquestra tudo: recebe as leituras do hardware, aplica formatação, guarda o histórico, distribui para os ouvintes e controla individualmente cada leitor.</p>
</blockquote>
<p dir="auto"><strong>Descrição técnica:</strong></p>
<ul>
<li>Injeta <code>DeviceManager</code> (obrigatório) e <code>ReaderSettingsStore</code> (opcional, cria instância padrão)</li>
<li>No construtor: registra listener em <code>deviceManager.onCardRead</code> para:
<ol>
<li>Aplicar prefixo/sufixo ao <code>cardId</code> via <code>applyRuntimeSettingsToEvent()</code></li>
<li>Prepend no array <code>recentReads</code> (máximo 50 itens via <code>splice</code>)</li>
<li>Notificar todos os listeners registrados em <code>this.listeners</code></li>
</ol>
</li>
<li><code>initializeRuntimeSettings()</code>: carrega settings do disco e aplica nos leitores</li>
<li><code>maintainReaders()</code>: para cada leitor, se desconectado tenta <code>connect()</code>; se conectado mas não escutando tenta <code>startListening()</code> — usado pelo timer de manutenção do servidor</li>
<li><code>applyRuntimeSettingsToEvent(event)</code>: retorna novo objeto com <code>cardId = prefix + cardId + suffix</code></li>
</ul>
<hr />
<h4><code>logger.ts</code></h4>
<p dir="auto"><strong>Descrição funcional:</strong></p>
<blockquote>
<p dir="auto">Responsável por gerar os logs do sistema. Em produção, escreve JSON puro; em desenvolvimento, exibe logs coloridos e formatados no terminal.</p>
</blockquote>
<p dir="auto"><strong>Descrição técnica:</strong></p>
<ul>
<li>Usa biblioteca <strong>pino</strong> com nível configurável via variável de ambiente <code>LOG_LEVEL</code> (padrão: <code>"info"</code>)</li>
<li>Em <code>NODE_ENV !== "production"</code>: ativa <code>pino-pretty</code> com cores e timestamp legível</li>
<li>Exporta instância singleton <code>logger</code> usada em todo o backend</li>
</ul>
<hr />
<h4><code>AppProcessManager.ts</code></h4>
<p dir="auto"><strong>Descrição funcional:</strong></p>
<blockquote>
<p dir="auto">Permite que a API avise a interface gráfica quando um cartão é lido. Se a interface não estiver aberta, ela é iniciada automaticamente só para exibir a notificação.</p>
</blockquote>
<p dir="auto"><strong>Descrição técnica:</strong></p>
<ul>
<li><code>notifyApiUsage(payload)</code>: verifica se o AppControlServer (porta 3792) está vivo via <code>/health</code>; se não, executa o <code>.exe</code> da interface com flag <code>--tray</code>; aguarda 1.200ms; tenta novamente; se disponível, faz POST <code>/notify</code></li>
<li><code>launchAppTray()</code>: usa <code>spawn</code> com <code>detached: true</code> para iniciar o processo filho sem bloquear a API</li>
<li><code>getExecutablePath()</code>: resolve o caminho do executável da interface nos cenários dev e produção (via <code>process.pkg</code> para binário compilado com <code>pkg</code>)</li>
</ul>
<hr />
<h3><img src="http://insoft-docker1:4567/assets/plugins/nodebb-plugin-emoji/emoji/android/1f4c2.png?v=8k80dgruung" class="not-responsive emoji emoji-android emoji--open_file_folder" title=":open_file_folder:" alt="📂" /> <code>src/api/routes/</code> — Rotas HTTP</h3>
<h4><code>GET /health</code></h4>
<p dir="auto">Retorna <code>{ "status": "ok" }</code>. Usado por health checks de todas as camadas.</p>
<h4><code>GET /readers</code></h4>
<p dir="auto">Chama <code>service.getReaders()</code> → <code>deviceManager.discoverAll()</code> + retorna lista de <code>ReaderInfo[]</code>.</p>
<h4><code>GET /readers/status</code></h4>
<p dir="auto">Retorna <code>ReaderStatus[]</code> com status atual de conexão/escuta de cada leitor.</p>
<h4><code>POST /readers/connect</code> · <code>POST /readers/disconnect</code></h4>
<p dir="auto">Body: <code>{ "key": "digicon-dg710" }</code> — conecta ou desconecta o leitor identificado pela chave.</p>
<h4><code>POST /readers/start</code> · <code>POST /readers/stop</code></h4>
<p dir="auto">Inicia ou para o polling de leitura no leitor identificado.</p>
<h4><code>GET /cards/recent</code></h4>
<p dir="auto">Retorna os últimos 50 cartões lidos em memória.</p>
<h4><code>GET /cards/events</code></h4>
<p dir="auto">Abre uma conexão <strong>SSE (Server-Sent Events)</strong>. O cliente recebe:</p>
<ul>
<li>Evento <code>connected</code> ao conectar</li>
<li>Evento <code>card-read</code> a cada cartão detectado (payload: <code>CardReadEvent</code>)</li>
</ul>
<h4><code>GET /settings/reader</code></h4>
<p dir="auto">Retorna as configurações de runtime atuais.</p>
<h4><code>PUT /settings/reader</code></h4>
<p dir="auto">Body: <code>ReaderRuntimeSettings</code> — valida via Zod, salva no disco e propaga para os leitores.</p>
<hr />
<h3><img src="http://insoft-docker1:4567/assets/plugins/nodebb-plugin-emoji/emoji/android/1f4c2.png?v=8k80dgruung" class="not-responsive emoji emoji-android emoji--open_file_folder" title=":open_file_folder:" alt="📂" /> <code>src/api/sse/CardEventBroker.ts</code></h3>
<p dir="auto"><strong>Descrição funcional:</strong></p>
<blockquote>
<p dir="auto">Mantém abertas as conexões dos clientes que querem receber cartões em tempo real e distribui cada evento para todos ao mesmo tempo.</p>
</blockquote>
<p dir="auto"><strong>Descrição técnica:</strong></p>
<ul>
<li>Mantém <code>Set&lt;Response&gt;</code> de conexões HTTP ativas</li>
<li><code>attachClient(res)</code>: configura headers SSE, envia evento <code>connected</code>, registra listener <code>res.on("close")</code> para remoção automática</li>
<li><code>broadcast(event)</code>: serializa <code>CardReadEvent</code> como SSE e escreve em todos os clientes conectados</li>
</ul>
<hr />
<h3><img src="http://insoft-docker1:4567/assets/plugins/nodebb-plugin-emoji/emoji/android/1f4c2.png?v=8k80dgruung" class="not-responsive emoji emoji-android emoji--open_file_folder" title=":open_file_folder:" alt="📂" /> <code>src/devices/digicon/</code> — Implementação do Hardware</h3>
<h4><code>DigiconDG710Reader.ts</code></h4>
<p dir="auto"><strong>Descrição funcional:</strong></p>
<blockquote>
<p dir="auto">É o "driver" do leitor Digicon DG-710. Faz a comunicação com o hardware e gera um evento toda vez que um cartão é detectado.</p>
</blockquote>
<p dir="auto"><strong>Descrição técnica:</strong></p>
<ul>
<li>Implementa <code>ICardReader</code> (interface contratual de todos os leitores)</li>
<li>Chave única: <code>"digicon-dg710"</code></li>
<li>Usa <code>DigiconFacade</code> para todas as chamadas à DLL nativa</li>
<li><strong>Polling por timeout</strong>: usa <code>setTimeout</code> recursivo com intervalo configurável (padrão 1.000ms)</li>
<li>Lógica de detecção:
<ul>
<li><code>continuousRead = false</code> (padrão): só dispara evento na <strong>transição</strong> ausente→presente (<code>wasCardPresent</code>)</li>
<li><code>continuousRead = true</code>: dispara em <strong>todo tick</strong> enquanto o cartão estiver presente</li>
</ul>
</li>
<li><code>applySettings(settings)</code>: atualiza <code>pollingMs</code> e <code>continuousRead</code>; reinicia o loop se estiver ativo</li>
<li>Estados internos: <code>connected</code>, <code>listening</code>, <code>handle</code>, <code>serial</code>, <code>lastError</code></li>
<li>Formato do evento emitido:<pre><code class="language-json">{
  "readerKey": "digicon-dg710",
  "cardId": "12345678",
  "rawHex": "BC614E",
  "protocol": "mifare",
  "timestamp": "2026-04-02T10:00:00.000Z"
}
</code></pre>
</li>
</ul>
<hr />
<h4><code>DigiconFacade.ts</code></h4>
<p dir="auto"><strong>Descrição funcional:</strong></p>
<blockquote>
<p dir="auto">Camada que conversa diretamente com a DLL <code>DG710Facade.dll</code> do fabricante Digicon. É a ponte entre o código TypeScript e o hardware físico.</p>
</blockquote>
<p dir="auto"><strong>Descrição técnica:</strong></p>
<ul>
<li>Usa <strong>koffi</strong> para FFI (Foreign Function Interface) — chama funções C da DLL sem Node addons</li>
<li><strong>Mock mode</strong>: se a DLL não for encontrada ou não carregar, opera em modo simulação:
<ul>
<li><code>getChannels()</code> retorna <code>[710]</code></li>
<li><code>isCardPresent()</code> simula presença de cartão em intervalos aleatórios (a cada ~5 segundos)</li>
<li><code>readMifareCard()</code> retorna cardId aleatório</li>
</ul>
</li>
<li><code>resolveDllPath()</code>: busca <code>DG710Facade.dll</code> em ~9 caminhos candidatos; configurável via <code>DIGICON_DLL_PATH</code></li>
<li><code>initializeRegistry()</code>: inicia o registro Digicon (necessário antes de qualquer operação); idempotente</li>
<li><code>readMifareCard(handle)</code>: lê 4 bytes em <strong>little-endian</strong> → converte para hex big-endian → converte BigInt para decimal (10 dígitos)</li>
<li>Detecta incompatibilidade de arquitetura (x86 vs x64) e loga mensagem específica</li>
</ul>
<hr />
<h3><img src="http://insoft-docker1:4567/assets/plugins/nodebb-plugin-emoji/emoji/android/1f4c2.png?v=8k80dgruung" class="not-responsive emoji emoji-android emoji--open_file_folder" title=":open_file_folder:" alt="📂" /> <code>src/interfaces/</code> — Contratos de Dados</h3>
<table class="table table-bordered table-striped">
<thead>
<tr>
<th>Interface</th>
<th>Campos</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>ICardReader</code></td>
<td><code>key</code>, <code>getInfo()</code>, <code>getStatus()</code>, <code>initialize()</code>, <code>connect()</code>, <code>disconnect()</code>, <code>startListening()</code>, <code>stopListening()</code>, <code>onCardRead()</code>, <code>discover?()</code>, <code>applySettings?()</code></td>
</tr>
<tr>
<td><code>CardReadEvent</code></td>
<td><code>readerKey</code>, <code>cardId</code>, <code>protocol</code>, <code>timestamp</code>, <code>rawHex?</code></td>
</tr>
<tr>
<td><code>ReaderInfo</code></td>
<td><code>key</code>, <code>name</code>, <code>manufacturer</code>, <code>model</code>, <code>connected</code>, <code>listening</code>, <code>serial?</code>, <code>capabilities[]</code></td>
</tr>
<tr>
<td><code>ReaderStatus</code></td>
<td><code>key</code>, <code>connected</code>, <code>listening</code>, <code>lastError?</code></td>
</tr>
<tr>
<td><code>ReaderRuntimeSettings</code></td>
<td><code>prefix</code>, <code>suffix</code>, <code>intervalMs</code>, <code>appendNewLine</code>, <code>continuousRead</code></td>
</tr>
</tbody>
</table>
<hr />
<h2><img src="http://insoft-docker1:4567/assets/plugins/nodebb-plugin-emoji/emoji/android/1f4c2.png?v=8k80dgruung" class="not-responsive emoji emoji-android emoji--open_file_folder" title=":open_file_folder:" alt="📂" /> <code>insoft-readers-app/electron/</code> — Processo Principal Electron</h2>
<p dir="auto"><strong>Descrição funcional:</strong></p>
<blockquote>
<p dir="auto">É o "casca" desktop da aplicação. Cria a janela, o ícone na bandeja do sistema, garante que a API está rodando e implementa o auto-paste (cola o cartão no aplicativo ativo).</p>
</blockquote>
<hr />
<h3><img src="http://insoft-docker1:4567/assets/plugins/nodebb-plugin-emoji/emoji/android/1f4c2.png?v=8k80dgruung" class="not-responsive emoji emoji-android emoji--open_file_folder" title=":open_file_folder:" alt="📂" /> <code>electron/main/main.ts</code> — Ponto de Entrada do Electron</h3>
<p dir="auto"><strong>Responsabilidades:</strong></p>
<ul>
<li>Garantir instância única da aplicação (<code>app.requestSingleInstanceLock</code>)</li>
<li>Chamar <code>apiProcessManager.ensureApiRunning()</code> antes de criar a janela</li>
<li>Criar e gerenciar <code>BrowserWindow</code> (frameless, 1200×820)</li>
<li>Iniciar subscrição SSE aos eventos de cartão (<code>startCardEvents</code>)</li>
<li>Implementar <code>handleCardRead()</code>:
<ul>
<li>Se a janela estiver em foco: envia evento IPC <code>card:read</code> para a UI</li>
<li>Se a janela estiver em background: executa <code>pasteToExternalFocusedApp()</code></li>
</ul>
</li>
<li><code>pasteToExternalFocusedApp()</code>: escreve o <code>cardId</code> na área de transferência + executa VBScript pré-gerado para simular <code>Ctrl+V</code> (com ou sem <code>{ENTER}</code> conforme <code>appendNewLine</code>)</li>
<li>Manter cache local de <code>runtimeSettingsCache</code> (atualizado a cada 1.200ms)</li>
<li>Registrar handlers IPC: <code>window:minimize</code>, <code>window:maximize-toggle</code>, <code>window:close</code>, <code>window:get-state</code>, <code>reader-runtime-settings:update-cache</code></li>
<li>Gerenciar <code>AppControlServer</code> (porta 3792) e <code>pendingNotifications</code></li>
</ul>
<hr />
<h3><img src="http://insoft-docker1:4567/assets/plugins/nodebb-plugin-emoji/emoji/android/1f4c2.png?v=8k80dgruung" class="not-responsive emoji emoji-android emoji--open_file_folder" title=":open_file_folder:" alt="📂" /> <code>electron/main/services/ApiProcessManager.ts</code></h3>
<p dir="auto"><strong>Descrição funcional:</strong></p>
<blockquote>
<p dir="auto">Garante que a API backend está rodando. Verifica se já está ativa, tenta iniciar como Serviço Windows, e se nada funcionar, inicia como processo filho.</p>
</blockquote>
<p dir="auto"><strong>Descrição técnica:</strong></p>
<ul>
<li><code>ensureApiRunning()</code> — fluxo de tentativas em ordem:
<ol>
<li>Verifica <code>/health</code> → se OK, retorna</li>
<li>Verifica se serviço Windows está instalado → tenta <code>nssm start</code></li>
<li>Aguarda health check (12 tentativas × 800ms = ~9,6s)</li>
<li>Se ainda falhou: chama <code>spawnApiProcess()</code></li>
<li>Aguarda health check final → se falhar, lança <code>Error</code></li>
</ol>
</li>
<li>Em <strong>desenvolvimento</strong>: executa <code>npm run dev:once -w @insoft/backend</code></li>
<li>Em <strong>produção</strong>: executa <code>insoft-reader-api.exe</code> da pasta <code>resources/api/</code></li>
</ul>
<hr />
<h3><img src="http://insoft-docker1:4567/assets/plugins/nodebb-plugin-emoji/emoji/android/1f4c2.png?v=8k80dgruung" class="not-responsive emoji emoji-android emoji--open_file_folder" title=":open_file_folder:" alt="📂" /> <code>electron/main/services/AppControlServer.ts</code></h3>
<p dir="auto"><strong>Descrição funcional:</strong></p>
<blockquote>
<p dir="auto">Um mini servidor HTTP interno (porta 3792) que recebe notificações da API backend e as repassa para a interface gráfica.</p>
</blockquote>
<p dir="auto"><strong>Descrição técnica:</strong></p>
<ul>
<li>Servidor HTTP puro (<code>node:http</code>) sem dependências externas</li>
<li>Rotas: <code>GET /health</code> → <code>{ status: "ok" }</code> | <code>POST /notify</code> → dispara <code>onNotification(payload)</code></li>
<li><code>onNotification</code> é um callback injetado no construtor que envia <code>mainWindow.webContents.send("app:notification", payload)</code></li>
</ul>
<hr />
<h3><img src="http://insoft-docker1:4567/assets/plugins/nodebb-plugin-emoji/emoji/android/1f4c2.png?v=8k80dgruung" class="not-responsive emoji emoji-android emoji--open_file_folder" title=":open_file_folder:" alt="📂" /> <code>electron/main/services/HealthCheckService.ts</code></h3>
<p dir="auto"><strong>Descrição funcional:</strong></p>
<blockquote>
<p dir="auto">Verifica se a API está "viva" fazendo uma requisição ao endpoint <code>/health</code>.</p>
</blockquote>
<p dir="auto"><strong>Descrição técnica:</strong></p>
<ul>
<li><code>isHealthy(url)</code>: GET com timeout de 1.500ms; verifica <code>response.ok</code> e <code>payload.status === "ok"</code></li>
<li><code>waitForHealthy(url, retries, delayMs)</code>: loop com tentativas e espera</li>
</ul>
<hr />
<h3><img src="http://insoft-docker1:4567/assets/plugins/nodebb-plugin-emoji/emoji/android/1f4c2.png?v=8k80dgruung" class="not-responsive emoji emoji-android emoji--open_file_folder" title=":open_file_folder:" alt="📂" /> <code>electron/main/services/WindowsServiceManager.ts</code></h3>
<p dir="auto"><strong>Descrição funcional:</strong></p>
<blockquote>
<p dir="auto">Verifica e inicia o serviço Windows da API usando as ferramentas <code>sc</code> e <code>nssm</code>.</p>
</blockquote>
<p dir="auto"><strong>Descrição técnica:</strong></p>
<ul>
<li><code>isInstalled(name)</code>: executa <code>sc query &lt;name&gt;</code>; considera instalado se o comando retornar código 0 ou se a saída não contiver "does not exist"</li>
<li><code>start(name)</code>: executa <code>nssm start &lt;name&gt;</code>; retorna <code>true</code> se código de saída for 0</li>
<li>Funciona apenas em <code>win32</code>; retorna <code>false</code> em outras plataformas</li>
</ul>
<hr />
<h3><img src="http://insoft-docker1:4567/assets/plugins/nodebb-plugin-emoji/emoji/android/1f4c2.png?v=8k80dgruung" class="not-responsive emoji emoji-android emoji--open_file_folder" title=":open_file_folder:" alt="📂" /> <code>electron/main/tray.ts</code></h3>
<p dir="auto"><strong>Descrição funcional:</strong></p>
<blockquote>
<p dir="auto">Cria o ícone na bandeja do sistema (canto inferior direito da tela) com um menu de contexto.</p>
</blockquote>
<p dir="auto"><strong>Menu disponível:</strong></p>
<ul>
<li><strong>Abrir interface</strong> — exibe e foca a janela principal</li>
<li><strong>Reconectar leitores</strong> — chama <code>performReconnectReaders()</code></li>
<li><strong>Sair</strong> — encerra o aplicativo</li>
</ul>
<hr />
<h3><img src="http://insoft-docker1:4567/assets/plugins/nodebb-plugin-emoji/emoji/android/1f4c2.png?v=8k80dgruung" class="not-responsive emoji emoji-android emoji--open_file_folder" title=":open_file_folder:" alt="📂" /> <code>electron/main/uiActions.ts</code></h3>
<p dir="auto"><strong>Descrição funcional:</strong></p>
<blockquote>
<p dir="auto">Gerencia o tema visual (dark/light) persistindo em arquivo de preferências do usuário.</p>
</blockquote>
<p dir="auto"><strong>Descrição técnica:</strong></p>
<ul>
<li>Arquivo: <code>%APPDATA%\Insoft Readers App\preferences.json</code></li>
<li>Handlers IPC: <code>theme:get</code> e <code>theme:set</code></li>
<li>Aplica o tema ao sistema operacional via <code>nativeTheme.themeSource</code></li>
</ul>
<hr />
<h3><img src="http://insoft-docker1:4567/assets/plugins/nodebb-plugin-emoji/emoji/android/1f4c2.png?v=8k80dgruung" class="not-responsive emoji emoji-android emoji--open_file_folder" title=":open_file_folder:" alt="📂" /> <code>electron/ipc/reader.ipc.ts</code></h3>
<p dir="auto"><strong>Descrição funcional:</strong></p>
<blockquote>
<p dir="auto">Implementa o comando "Reconectar Leitores" que faz disconnect + connect + start em todos os leitores de uma vez.</p>
</blockquote>
<p dir="auto"><strong>Descrição técnica:</strong></p>
<ul>
<li><code>performReconnectReaders()</code>: GET <code>/readers</code> → para cada leitor: POST <code>/readers/disconnect</code> → POST <code>/readers/connect</code> → POST <code>/readers/start</code></li>
<li>Registra handler IPC <code>reader:reconnect</code> que chama a função acima</li>
</ul>
<hr />
<h3><img src="http://insoft-docker1:4567/assets/plugins/nodebb-plugin-emoji/emoji/android/1f4c2.png?v=8k80dgruung" class="not-responsive emoji emoji-android emoji--open_file_folder" title=":open_file_folder:" alt="📂" /> <code>electron/preload/preload.ts</code></h3>
<p dir="auto"><strong>Descrição funcional:</strong></p>
<blockquote>
<p dir="auto">Ponte segura entre o código da interface (React) e o Electron. Expõe apenas as funções necessárias para a UI, sem dar acesso completo ao Node.js.</p>
</blockquote>
<p dir="auto"><strong>APIs expostas via <code>contextBridge</code>:</strong></p>
<table class="table table-bordered table-striped">
<thead>
<tr>
<th>Namespace</th>
<th>Métodos</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>window.readerApi</code></td>
<td><code>reconnectReaders()</code>, <code>syncRuntimeSettings(settings)</code>, <code>onCardRead(callback)</code></td>
</tr>
<tr>
<td><code>window.appWindow</code></td>
<td><code>getState()</code>, <code>onStateChange(callback)</code></td>
</tr>
<tr>
<td><code>window.appTheme</code></td>
<td><code>get()</code>, <code>set(theme)</code></td>
</tr>
<tr>
<td><code>window.electron</code></td>
<td><code>minimizeApp()</code>, <code>maximizeRestoreApp()</code>, <code>closeApp()</code>, <code>getWindowState()</code>, <code>onWindowStateChange(callback)</code></td>
</tr>
<tr>
<td><code>window.appNotifications</code></td>
<td><code>onNotify(callback)</code></td>
</tr>
</tbody>
</table>
<hr />
<h2><img src="http://insoft-docker1:4567/assets/plugins/nodebb-plugin-emoji/emoji/android/1f4c2.png?v=8k80dgruung" class="not-responsive emoji emoji-android emoji--open_file_folder" title=":open_file_folder:" alt="📂" /> <code>insoft-readers-app/frontend/</code> — Interface Gráfica (React)</h2>
<p dir="auto"><strong>Descrição funcional:</strong></p>
<blockquote>
<p dir="auto">A tela do sistema. Exibe o status dos leitores, os cartões lidos, permite configurar o comportamento do leitor e monitorar eventos em tempo real.</p>
</blockquote>
<hr />
<h3><img src="http://insoft-docker1:4567/assets/plugins/nodebb-plugin-emoji/emoji/android/1f4c2.png?v=8k80dgruung" class="not-responsive emoji emoji-android emoji--open_file_folder" title=":open_file_folder:" alt="📂" /> <code>src/store/reader.store.ts</code> — Estado Global</h3>
<p dir="auto"><strong>Descrição técnica:</strong></p>
<ul>
<li>Usa <strong>Zustand</strong> (store reativo sem Context API do React)</li>
<li>Estado: <code>readers[]</code>, <code>statuses[]</code>, <code>recentCards[]</code>, <code>activeReaderKey</code></li>
<li><code>prependCard(event)</code>: adiciona na frente e mantém máximo 50 itens</li>
<li><code>setRecentCards(events)</code>: limita a 50 itens</li>
</ul>
<hr />
<h3><img src="http://insoft-docker1:4567/assets/plugins/nodebb-plugin-emoji/emoji/android/1f4c2.png?v=8k80dgruung" class="not-responsive emoji emoji-android emoji--open_file_folder" title=":open_file_folder:" alt="📂" /> <code>src/hooks/</code> — Hooks React</h3>
<h4><code>useReaders.ts</code></h4>
<ul>
<li>Busca dados do backend a cada chamada de <code>refresh()</code>: <code>fetchReaders()</code>, <code>fetchReaderStatus()</code>, <code>fetchRecentCards()</code> em paralelo</li>
<li>Expõe: <code>loading</code>, <code>error</code>, <code>connect(key?)</code>, <code>disconnect(key?)</code>, <code>start(key?)</code>, <code>stop(key?)</code>, <code>refresh()</code></li>
<li>Usa <code>activeReaderKey</code> do store quando <code>key</code> não for informado</li>
</ul>
<h4><code>useCardEvents.ts</code></h4>
<ul>
<li>Abre conexão <code>EventSource</code> para <code>GET /cards/events</code></li>
<li>Escuta evento <code>card-read</code> e chama <code>prependCard()</code> no store</li>
<li>Fecha a conexão no <code>useEffect</code> cleanup</li>
</ul>
<hr />
<h3><img src="http://insoft-docker1:4567/assets/plugins/nodebb-plugin-emoji/emoji/android/1f4c2.png?v=8k80dgruung" class="not-responsive emoji emoji-android emoji--open_file_folder" title=":open_file_folder:" alt="📂" /> <code>src/services/api.ts</code> — Cliente HTTP</h3>
<ul>
<li>Instância axios com <code>baseURL = VITE_BACKEND_URL ?? "http://127.0.0.1:3791"</code> e timeout de 5s</li>
<li>Funções exportadas: <code>fetchReaders</code>, <code>fetchReaderStatus</code>, <code>fetchRecentCards</code>, <code>connectReader</code>, <code>disconnectReader</code>, <code>startReader</code>, <code>stopReader</code>, <code>fetchReaderSettings</code>, <code>saveReaderSettings</code>, <code>buildCardEventsUrl</code></li>
</ul>
<hr />
<h3><img src="http://insoft-docker1:4567/assets/plugins/nodebb-plugin-emoji/emoji/android/1f4c2.png?v=8k80dgruung" class="not-responsive emoji emoji-android emoji--open_file_folder" title=":open_file_folder:" alt="📂" /> <code>src/pages/</code> — Páginas da Interface</h3>
<table class="table table-bordered table-striped">
<thead>
<tr>
<th>Página</th>
<th>Rota interna</th>
<th>Descrição</th>
</tr>
</thead>
<tbody>
<tr>
<td><code>Dashboard</code></td>
<td><code>dashboard</code></td>
<td>Visão geral: 4 cards de estatística, tabela de status, ações rápidas</td>
</tr>
<tr>
<td><code>Readers</code></td>
<td><code>readers</code></td>
<td>Gerencia leitores: seleção, connect/disconnect/start/stop</td>
</tr>
<tr>
<td><code>CardMonitoring</code></td>
<td><code>monitoring</code></td>
<td>Stream ao vivo de eventos SSE, métricas de cartões únicos</td>
</tr>
<tr>
<td><code>DeviceLogs</code></td>
<td><code>logs</code></td>
<td>Painel de logs baseado nos dados de status e eventos recentes</td>
</tr>
<tr>
<td><code>Settings</code></td>
<td><code>settings</code></td>
<td>Formulário de configurações de runtime do leitor</td>
</tr>
</tbody>
</table>
<hr />
<h3><img src="http://insoft-docker1:4567/assets/plugins/nodebb-plugin-emoji/emoji/android/1f4c2.png?v=8k80dgruung" class="not-responsive emoji emoji-android emoji--open_file_folder" title=":open_file_folder:" alt="📂" /> <code>src/components/settings/SettingsForm.tsx</code></h3>
<p dir="auto"><strong>Descrição funcional:</strong></p>
<blockquote>
<p dir="auto">Formulário para configurar como o leitor funciona: intervalo de leitura, prefixo/sufixo do código, modo contínuo, nova linha.</p>
</blockquote>
<p dir="auto"><strong>Descrição técnica:</strong></p>
<ul>
<li>Usa <code>react-hook-form</code> + <code>zodResolver</code> para validação</li>
<li>No load: busca <code>GET /settings/reader</code> e popula o formulário com <code>reset(settings)</code></li>
<li>No submit: chama <code>PUT /settings/reader</code> + <code>window.readerApi?.syncRuntimeSettings(updated)</code> para sincronizar o cache do processo Electron</li>
</ul>
<hr />
<h1><img src="http://insoft-docker1:4567/assets/plugins/nodebb-plugin-emoji/emoji/android/1f504.png?v=8k80dgruung" class="not-responsive emoji emoji-android emoji--arrows_counterclockwise" title=":arrows_counterclockwise:" alt="🔄" /> 4. FLUXOS PRINCIPAIS DO SISTEMA</h1>
<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 1: Inicialização do Sistema</h2>
<p dir="auto"><strong>Explicação simplificada (Suporte):</strong></p>
<blockquote>
<p dir="auto">Quando o usuário abre o Insoft Readers App, o programa verifica se a API está funcionando. Se não estiver, ele a inicia automaticamente. Depois cria a janela e começa a escutar os cartões.</p>
</blockquote>
<p dir="auto"><strong>Explicação técnica (Dev):</strong></p>
<pre><code>app.on("ready")
  │
  ├─► apiProcessManager.ensureApiRunning()
  │     ├─ GET /health → OK? → continua
  │     ├─ Não OK → verifica serviço Windows (sc query)
  │     │     ├─ Instalado → nssm start → aguarda health (12×800ms)
  │     │     └─ Não instalado → spawnApiProcess() → aguarda health
  │     └─ Falha total → app.quit()
  │
  ├─► createMainWindow(startInTray)
  │     ├─ BrowserWindow frameless 1200×820
  │     ├─ Carrega frontend (URL dev ou arquivo dist)
  │     ├─ registerReaderIpc(ipcMain)
  │     └─ createAppTray(...)
  │
  ├─► initializeUIActions()
  │     └─ Lê preferences.json → aplica tema
  │
  ├─► appControlServer.start() (porta 3792)
  │
  └─► startCardEvents()
        ├─ refreshRuntimeSettings() (busca /settings/reader)
        ├─ setInterval(refreshRuntimeSettings, 1200ms)
        └─ connectCardEvents() → GET /cards/events (SSE stream)
</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 2: Leitura de um Cartão — Ponta a Ponta</h2>
<p dir="auto"><strong>Explicação simplificada (Suporte):</strong></p>
<blockquote>
<p dir="auto">O usuário aproxima o cartão do leitor. O hardware detecta, o sistema processa e automaticamente cola o número do cartão no campo ativo do Windows. Se a janela estiver aberta, aparece uma janela popup com o número.</p>
</blockquote>
<p dir="auto"><strong>Explicação técnica (Dev):</strong></p>
<pre><code>[Hardware DG-710]
  │  Cartão aproximado
  ▼
DigiconFacade.isCardPresent(handle) → true
  │
DigiconFacade.readMifareCard(handle)
  │  rawHex = bytes[3..0] em hex big-endian
  │  cardId = BigInt(0x{rawHex}).toString(10)
  │  Exemplo: bytes = [0x4E, 0x61, 0xBC, 0x00]
  │           rawHex = "00BC614E"
  │           cardId = "12345678"
  ▼
DigiconDG710Reader.runPollingTick()
  │  continuousRead=false: só dispara na transição
  │  continuousRead=true: dispara a cada tick
  ▼
DeviceManager.emit("cardRead", rawEvent)
  ▼
CardService.onCardRead listener
  │  applyRuntimeSettingsToEvent(rawEvent)
  │  cardId = prefix + "12345678" + suffix
  │  recentReads.unshift(processedEvent)  ← histórico in-memory (max 50)
  ▼
CardEventBroker.broadcast(processedEvent)  ← notifica clientes SSE
  │
AppProcessManager.notifyApiUsage(...)      ← notifica interface
  │
  ├─► [Clientes SSE do frontend React]
  │     useCardEvents → prependCard(event) → store atualizado → UI re-renderiza
  │
  └─► [Electron main process — SSE stream]
        handleCardRead(event)
          ├─ janela em foco? → ipcMain.send("card:read", event) → CardReadModal abre
          └─ janela em background?
               ├─ clipboard.writeText(cardId)
               └─ spawn("wscript", ["//B", vbsPaste]) → Ctrl+V no app ativo
</code></pre>
<hr />
<h3>A documentação continua na parte 2, através da seguinte URL:</h3>
<h4><a href="https://forum.insoft4.com.br/topic/506/documenta%C3%A7%C3%A3o-t%C3%A9cnica-parte-2" rel="nofollow ugc">Documentação Técnica - Parte 2</a></h4>
]]></description><link>http://insoft-docker1:4567/topic/505/documentação-técnica-parte-1</link><generator>RSS for Node</generator><lastBuildDate>Sat, 06 Jun 2026 04:48:06 GMT</lastBuildDate><atom:link href="http://insoft-docker1:4567/topic/505.rss" rel="self" type="application/rss+xml"/><pubDate>Fri, 22 May 2026 17:00:24 GMT</pubDate><ttl>60</ttl></channel></rss>