From c2c9a2924730797b1a6b7ff3592100be3f5ee44d Mon Sep 17 00:00:00 2001 From: root Date: Thu, 2 Jul 2026 22:26:57 +0000 Subject: [PATCH] docs: adicionar SPEC, SUPABASE_SCHEMA, API_INTEGRATION, BUILDS_FARM --- API_INTEGRATION.md | 139 +++++++++++++++++++++++++++ BUILDS_FARM.md | 186 ++++++++++++++++++++++++++++++++++++ README.md | 228 ++++++++++++++++++++++----------------------- SUPABASE_SCHEMA.md | 142 ++++++++++++++++++++++++++++ 4 files changed, 579 insertions(+), 116 deletions(-) create mode 100644 API_INTEGRATION.md create mode 100644 BUILDS_FARM.md create mode 100644 SUPABASE_SCHEMA.md diff --git a/API_INTEGRATION.md b/API_INTEGRATION.md new file mode 100644 index 0000000..a4ceeda --- /dev/null +++ b/API_INTEGRATION.md @@ -0,0 +1,139 @@ +# Integração Frontend ↔ Tauri (Rust) ↔ Supabase + +## Visão geral da comunicação + +``` +┌─────────────────┐ ┌─────────────────┐ ┌──────────────────┐ +│ JavaScript │ invoke │ Tauri/Rust │ HTTP │ Supabase │ +│ (index.html) │────────▶│ commands │────────▶│ (cloud) │ +│ │◀────────│ │◀────────│ │ +└─────────────────┘ response└─────────────────┘ response└──────────────────┘ +``` + +O frontend **nunca faz requisições HTTP direto** para o Supabase. Toda comunicação passa pelos comandos Tauri (Rust). + +--- + +## Comandos Tauri disponíveis + +### App +| Comando | O que faz | +|---------|-----------| +| `get_loja` | Retorna a loja configurada | +| `get_loja_fixa` | Retorna `"Uniao"`, `"Alianca"` ou `null` (se build fixa) | +| `set_loja(loja)` | Define a loja (bloqueado se build fixa) | +| `get_config` | Retorna config completo `{ loja, pdv_nome, operador_default, ... }` | +| `set_config(cfg)` | Salva config | + +### Storage (SQLite local) +| Comando | O que faz | +|---------|-----------| +| `salvar_fechamento(estado)` | Salva estado completo no SQLite local | +| `carregar_rascunho(loja, data)` | Retorna último rascunho para loja+data | +| `listar_fechamentos(loja, limite)` | Lista fechamentos de uma loja | +| `buscar_fechamento_por_id(id)` | Busca por ID local | +| `pendentes_sync` | Retorna fechamentos que ainda não foram syncados | + +### Supabase +| Comando | O que faz | +|---------|-----------| +| `sb_salvar_fechamento(estado)` | Upsert no Supabase via service_role JWT | +| `sb_carregar_rascunho(loja, data)` | Busca rascunho mais recente | +| `sb_listar_recentes(loja, limite)` | Lista fechamentos recentes | +| `sb_buscar_por_id(id)` | Busca por ID | +| `sb_buscar_por_uuid(uuid)` | Busca por UUID | +| `sb_buscar_por_id_fechamento(id_fechamento)` | Busca por chave natural | +| `sb_atualizar_fechamento(id_fechamento, updates)` | PATCH campos específicos | +| `sb_salvar_listas(loja, listas)` | Salva operadores/gerentes customizados | +| `sb_carregar_listas(loja)` | Carrega listas salvas | +| `sb_online` | Retorna `true`/`false` se Supabase está acessível | + +### Printer +| Comando | O que faz | +|---------|-----------| +| `detectar_impressora` | Detecta EPSON TM-T20 USB | +| `configurar_impressora(caminho)` | Define caminho da impressora | +| `caminho_impressora` | Retorna caminho configurado | +| `imprimir_recibo(html)` | Envia HTML puro para impressora | +| `teste_impressora` | Imprime página de teste | +| `ativar_modo_teste_impressao` | Ativa modo teste (salva PDF em vez de imprimir) | + +--- + +## Fluxo de salvar um fechamento + +```javascript +// No frontend (index.html), quando operador clica "Salvar": +async function salvarFechamento() { + const estado = buildEstado(); // coleta todos os campos do formulário + + // 1. Salva localmente (sempre funciona offline) + await invoke('salvar_fechamento', { estado }); + + // 2. Tenta sync com Supabase + try { + await invoke('sb_salvar_fechamento', { estado }); + toast('✅ Salvo e sincronizado'); + } catch(e) { + // Supabase offline — fica pendente no SQLite + toast('💾 Salvo localmente (sync pendente)'); + } +} +``` + +--- + +## Como o Rust conversa com o Supabase + +Em `supabase.rs`, o plugin usa `reqwest` (HTTP client) com: + +```rust +fn headers(&self, use_service: bool) -> reqwest::header::HeaderMap { + let key = if use_service { &self.service_role_key } else { &self.anon_key }; + let mut headers = reqwest::header::HeaderMap::new(); + headers.insert( + reqwest::header::AUTHORIZATION, + format!("Bearer {}", key).parse().unwrap(), + ); + headers +} +``` + +- **`use_service = true`:** para INSERT/UPDATE (salvar fechamento) +- **`use_service = false`:** para GET (listar, buscar) + +--- + +## Upsert — salvar sem duplicar + +O Supabase não tem UPSERT nativo via REST. O plugin usa um truque: + +```http +POST /fechamentos_web HTTP/1.1 +Prefer: resolution=merge-duplicates +``` + +Se o `id_fechamento` já existe, o PostgREST faz um UPDATE em vez de INSERT. Se não existe, faz INSERT. + +--- + +## UUID vs id_fechamento + +| Campo | O que é | Quem gera | +|-------|---------|-----------| +| `id` | ID sequencial do PostgreSQL | Supabase (auto) | +| `uuid` | UUID único do registro local | Frontend JS (`crypto.randomUUID()`) | +| `id_fechamento` | Chave natural: `{loja}_{data}_{operador}` | Frontend JS (construído na hora) | + +O `id_fechamento` é a chave de negócio. O `uuid` é para rastrear registros entre local e cloud. + +--- + +## Offline first — como funciona + +1. **Operador/edita** → `salvar_fechamento` → SQLite (sempre funciona) +2. **Operador/edita** → `sb_salvar_fechamento` → Supabase (só se online) +3. **Se offline:** o fechamento fica no SQLite com `sync_status = "local"` +4. **Quando conecta:** `pendentes_sync` retorna os pendentes → sync automático + +O sync automático é controlado por `sync_automatico` na config da app. diff --git a/BUILDS_FARM.md b/BUILDS_FARM.md new file mode 100644 index 0000000..f0e20ae --- /dev/null +++ b/BUILDS_FARM.md @@ -0,0 +1,186 @@ +# Builds — Como gerar fc-uniao.exe, fc-alianca.exe, fc-ambos.exe + +## Conceito + +O mesmo código gera 3 binários diferentes. A diferença entre eles é apenas o valor da variável de ambiente `DOBRADO_LOJA` passado na hora da compilação Rust. + +- **Build fixa (União ou Aliança):** o operador **não consegue trocar a loja** no formulário +- **Build livre (Ambós):** o operador escolhe entre União e Aliança no dropdown + +--- + +## Mecanismo: `option_env!` em Rust + +Em `src-tauri/src/plugins/app.rs`: + +```rust +/// DOBRADO_LOJA é uma variável de ambiente lida em TEMPO DE COMPILAÇÃO. +/// Se não definida, retorna None. +pub const LOJA_FIXA: Option<&'static str> = option_env!("DOBRADO_LOJA"); +``` + +O `option_env!` é uma macro built-in do Rust que avalia expressões const em tempo de compilação. Se `DOBRADO_LOJA` estiver definida como env var, `LOJA_FIXA` contém o valor. Se não, `None`. + +### Por que compile-time? + +Se fosse runtime (`std::env::var`), o valor estaría hardcoded no binário da mesma forma, mas compile-time é mais limpo porque o Rust elimina código morto (dead code elimination). Se `LOJA_FIXA` é `None`, o Rust pode optimizar o código de "loja fixa" para fora do binário final. + +--- + +## Comportamento por build + +| Build | `DOBRADO_LOJA` | Seletor loja | `set_loja` | `get_loja_fixa` | +|-------|----------------|-------------|-------------|-----------------| +| União | `Uniao` | **Escondido** | Bloqueado com erro | `"Uniao"` | +| Aliança | `Alianca` | **Escondido** | Bloqueado com erro | `"Alianca"` | +| Ambos | _(não definido)_ | Visível + funcional | Permitido | `null` | + +--- + +## Como buildar (Linux → Windows cross-compile) + +### Pré-requisitos + +```bash +# Instalar target cross-compile +rustup target add x86_64-pc-windows-gnu + +# Ou via apt (Ubuntu/Debian) +sudo apt install mingw-w64 +``` + +### Comandos + +```bash +cd src-tauri/ + +# ── UNIÃO ───────────────────────────────────────────── +DOBRADO_LOJA=Uniao \ + cargo build --release --target x86_64-pc-windows-gnu + +# Binário gerado: +# src-tauri/target/x86_64-pc-windows-gnu/release/fechamento-caixa.exe + +# ── ALIANÇA ────────────────────────────────────────── +DOBRADO_LOJA=Alianca \ + cargo build --release --target x86_64-pc-windows-gnu + +# ── AMBOS ──────────────────────────────────────────── +cargo build --release --target x86_64-pc-windows-gnu +# (sem DOBRADO_LOJA → loja livre) +``` + +### Local do binário + +``` +src-tauri/target/x86_64-pc-windows-gnu/release/fechamento-caixa.exe +``` + +Renomear para `fc-uniao.exe`, `fc-alianca.exe`, `fc-ambos.exe` antes de distribuir. + +--- + +## O que acontece no frontend quando a loja é fixa + +Em `src/index.html`, `DOMContentLoaded`: + +```javascript +document.addEventListener('DOMContentLoaded', async () => { + try { + const fixa = await window.__TAURI__.core.invoke('get_loja_fixa'); + if (fixa) { + // Build fixa: esconde o