diff --git a/.gitignore b/.gitignore index eed0be1..a704355 100644 --- a/.gitignore +++ b/.gitignore @@ -7,3 +7,16 @@ src-tauri/gen/ *.msi *.dmg .DS_Store + +# Binarios compilados +*.exe +*.tar.gz +*.zip +fechamento-caixa.exe +fechamento-caixa-latest.tar.gz +WebView2Loader.dll +src-tauri/target/x86_64-pc-windows-gnu/release/*.exe +src-tauri/target/x86_64-pc-windows-gnu/release/*.dll + +# Logs +*.log diff --git a/README.md b/README.md new file mode 100644 index 0000000..63ee10c --- /dev/null +++ b/README.md @@ -0,0 +1,140 @@ +# Fechamento de Caixa — O Frangão + +App desktop nativo para fechamento de caixa com impressão em impressora térmica 80mm. + +## Stack + +- **Backend:** Tauri 2 + Rust +- **Frontend:** HTML + CSS + JavaScript (vanilla, ~1700 linhas) +- **Storage local:** SQLite (rusqlite) — funciona offline +- **Sync:** Supabase REST API +- **Impressão:** ESC/POS via USB (TM-T20 e compatíveis, Windows) + +## Estrutura do Projeto + +``` +fechamento-caixa/ +├── src/ +│ └── index.html # Frontend completo (HTML + CSS + JS inline) +├── src-tauri/ +│ ├── Cargo.toml # Dependências Rust +│ ├── tauri.conf.json # Config do app Tauri +│ └── src/ +│ ├── main.rs # Entry point — registra comandos Tauri +│ └── plugins/ +│ ├── supabase.rs # Integração Supabase (REST API) +│ ├── printer.rs # Geração ESC/POS + envio USB +│ ├── storage.rs # SQLite local +│ ├── app.rs # Config da app (loja, operador default) +│ └── escpos.rs # Helpers ESC/POS (INIT, CUT, divider, etc.) +└── README.md +``` + +## Pré-requisitos + +- Rust 1.70+ (`rustup install stable`) +- Node.js 18+ (para build do frontend Tauri) +- Windows 10/11 (impressão USB só funciona no Windows) + +## Setup + +### 1. Clonar o repositório + +```bash +git clone https://git.ofrangao.com.br/filipe/fechamento-caixa.git +cd fechamento-caixa +``` + +### 2. Configurar chaves do Supabase + +Crie o arquivo `config.json` em `%LOCALAPPDATA%\FechamentoCaixa\config.json` (Windows): + +```json +{ + "SUPABASE_ANON_KEY": "sua_chave_anon_aqui", + "SUPABASE_SERVICE_KEY": "sua_chave_service_role_aqui" +} +``` + +O app procura esse arquivo na inicialização. Sem ele, tenta ler das variáveis de ambiente `SUPABASE_ANON_KEY` e `SUPABASE_SERVICE_KEY`. + +### 3. Build + +```bash +cd src-tauri +cargo build --release --target x86_64-pc-windows-gnu +``` + +O executável fica em: +``` +src-tauri/target/x86_64-pc-windows-gnu/release/fechamento-caixa.exe +``` + +Para gerar o pacote de distribuição: +```bash +cp src-tauri/target/x86_64-pc-windows-gnu/release/fechamento-caixa.exe . +cp src-tauri/target/x86_64-pc-windows-gnu/release/WebView2Loader.dll . +tar -czf fechamento-caixa-vX.X.X.tar.gz fechamento-caixa.exe WebView2Loader.dll +``` + +## Códigos de comando Tauri + +O frontend chama esses comandos via `window.__TAURI__.core.invoke()`: + +| Comando | Arquivo | Descrição | +|---------|---------|-----------| +| `salvar_fechamento` | storage.rs | Salva no SQLite local | +| `carregar_rascunho` | storage.rs | Busca rascunho por loja+data | +| `listar_fechamentos` | storage.rs | Lista histórico do SQLite | +| `buscar_fechamento_por_id` | storage.rs | Busca por ID no SQLite | +| `sb_salvar_fechamento` | supabase.rs | Envia para Supabase | +| `sb_carregar_rascunho` | supabase.rs | Busca rascunho no Supabase | +| `sb_listar_recentes` | supabase.rs | Lista fechamentos recentes | +| `sb_buscar_por_id` | supabase.rs | Busca por ID no Supabase | +| `sb_salvar_listas` | supabase.rs | Salva operadores/gerentes | +| `sb_carregar_listas` | supabase.rs | Carrega listas do servidor | +| `sb_online` | supabase.rs | Verifica conectividade | +| `imprimir_recibo` | printer.rs | Envia bytes ESC/POS pra USB | +| `detectar_impressora` | printer.rs | Detecta porta USB | +| `configurar_impressora` | printer.rs | Define porta manual | +| `teste_impressora` | printer.rs | Imprime página de teste | +| `get_loja` / `set_loja` | app.rs | Loja atual | +| `get_config` / `set_config` | app.rs | Config completo | + +## Fluxo de dados + +``` +[Usuário preenche formulário] + │ + ▼ (auto-save a cada 1.5s) + SQLite local (fechamento.db) + │ + ├── [Botão Enviar] ──► Supabase (fechamentos_web) + │ + └── [Botão Recibo] ──► printer.rs ──► to_escpos() ──► USB + │ + ├── Sucesso: imprime + └── Falha: openPrintPreview() ──► iframe ──► window.print() +``` + +## Campos do formulário + +| Campo | Tabela Supabase | Notas | +|-------|----------------|-------| +| uuid, loja, data, operador, turno | fechamentos_web | PK composto | +| saldo_troco, saldo_esperado, fechamento, diferenca | fechamentos_web | diferenca = fechamento - saldo_esperado | +| despesas[], sangrias[], vales[], receber[], pixcnpj[], cancelamentos[] | dados (JSON) | Arrays de {desc/nome, valor, ...} | +| cartoes{credito[],debito[],alimentacao[],pix[]} | dados (JSON) | Arrays de {valor} | +| operadores[], gerentes[], despesas_custom[], clientes[] | listas_personalizadas | Listas por loja | + +## Problemas conhecidos + +- Impressão USB só funciona no Windows +- `service_role_key` no cliente desktop é risco de segurança — usar apenas anon_key + RLS +- Sem testes automatizados + +## Version History + +- **v1.2.7** — 2026-06-24: 4 bugs críticos concertados (impressão, enviar, dia anterior, localStorage) +- **v1.2.6** — 2026-06-24: WebView2Loader.dll incluso; init() restaura operador+turno +- **v1.2.5** — 2026-06-24: diferenca = fechamento - esperado (não mais saldo_troco - esperado) diff --git a/WebView2Loader.dll b/WebView2Loader.dll deleted file mode 100644 index 429a122..0000000 Binary files a/WebView2Loader.dll and /dev/null differ diff --git a/fechamento-caixa-latest.tar.gz b/fechamento-caixa-latest.tar.gz deleted file mode 100644 index c6df5dc..0000000 Binary files a/fechamento-caixa-latest.tar.gz and /dev/null differ diff --git a/fechamento-caixa-v1.2.0.tar.gz b/fechamento-caixa-v1.2.0.tar.gz deleted file mode 100644 index 5f6974a..0000000 Binary files a/fechamento-caixa-v1.2.0.tar.gz and /dev/null differ diff --git a/fechamento-caixa-v1.2.4.tar.gz b/fechamento-caixa-v1.2.4.tar.gz deleted file mode 100644 index 56cd44b..0000000 Binary files a/fechamento-caixa-v1.2.4.tar.gz and /dev/null differ diff --git a/fechamento-caixa-v1.2.5.tar.gz b/fechamento-caixa-v1.2.5.tar.gz deleted file mode 100644 index 4081831..0000000 Binary files a/fechamento-caixa-v1.2.5.tar.gz and /dev/null differ diff --git a/fechamento-caixa-v1.2.6.tar.gz b/fechamento-caixa-v1.2.6.tar.gz deleted file mode 100644 index 7ff6281..0000000 Binary files a/fechamento-caixa-v1.2.6.tar.gz and /dev/null differ diff --git a/fechamento-caixa-v1.2.7.tar.gz b/fechamento-caixa-v1.2.7.tar.gz deleted file mode 100644 index c7b0680..0000000 Binary files a/fechamento-caixa-v1.2.7.tar.gz and /dev/null differ diff --git a/fechamento-caixa.exe b/fechamento-caixa.exe deleted file mode 100755 index 2bc5af6..0000000 Binary files a/fechamento-caixa.exe and /dev/null differ diff --git a/src/index.html b/src/index.html index cb246b2..05338d0 100644 --- a/src/index.html +++ b/src/index.html @@ -1329,37 +1329,44 @@ ${data.observacoes ? `
Obs: ${data.observacoes}
` : ''} } function openPrintPreview(data) { - // Gera HTML e injeta no body de uma div temporária para window.print() + // Cria iframe oculto para impressão — NÃO substitui o DOM do app + const iframe = document.createElement('iframe'); + iframe.style.cssText = 'position:fixed;right:0;bottom:0;width:0;height:0;border:none;'; + iframe.id = 'print-iframe'; + document.body.appendChild(iframe); + const html = buildPrintHTML(data); - const style = document.createElement('style'); - style.textContent = ` - @media print { - body { width: 80mm; margin: 0 auto; font-family: 'Courier New', monospace; font-size: 13px; } - .no-print { display: none !important; } - } - .print-actions { position: fixed; top: 10px; right: 10px; z-index: 9999; } - .print-actions button { padding: 8px 16px; cursor: pointer; background: #2563eb; color: #fff; border: none; border-radius: 4px; font-size: 14px; } - .print-actions button:hover { background: #1d4ed8; } + const printStyle = ` + `; - const actions = document.createElement('div'); - actions.className = 'print-actions no-print'; - actions.innerHTML = ''; - // Guarda conteúdo original - const originalBody = document.body.innerHTML; + const iframeDoc = iframe.contentWindow.document; + iframeDoc.open(); + iframeDoc.write(`Recibo${printStyle}${html}`); + iframeDoc.close(); - document.body.innerHTML = html; - document.head.appendChild(style); - document.body.insertBefore(actions, document.body.firstChild); + // Focus e imprime + iframe.contentWindow.focus(); + iframe.contentWindow.print(); - // Quando fechar print dialog, restaura - const onAfterPrint = () => { - document.body.innerHTML = originalBody; - window.removeEventListener('afterprint', onAfterPrint); - // Re-carrega scripts essenciais - location.reload(); + // Remove iframe após impressão + iframe.contentWindow.onafterprint = () => { + document.body.removeChild(iframe); }; - window.addEventListener('afterprint', onAfterPrint); + // Fallback: remove iframe após 5s (onafterprint pode não disparar em alguns browsers) + setTimeout(() => { + if (document.getElementById('print-iframe')) { + document.body.removeChild(iframe); + } + }, 5000); } function triggerWindowPrint() { @@ -1569,7 +1576,14 @@ document.getElementById('btn-recibo').addEventListener('click', async () => { document.getElementById('btn-limpar').addEventListener('click', () => { if (!confirm('Limpar rascunho? Esta acção não pode ser revertida.')) return; localStorage.removeItem(LS_KEY); - location.reload(); + // Reseta estado e re-renderiza sem reload + estado = newEstado(); + estado.loja = val('f-loja') || 'União'; + estado.data = val('f-data') || new Date().toISOString().split('T')[0]; + calcular(); + renderAllTables(); + syncUI(); + showToast('Rascunho limpo.', 'info'); }); document.getElementById('btn-anterior').addEventListener('click', async () => {