Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
PORT=3000
TERABOX_CLIENT_ID=SEU_CLIENT_ID
TERABOX_CLIENT_SECRET=SEU_CLIENT_SECRET
TERABOX_REDIRECT_URI=http://localhost:3000/auth/terabox/callback
TERABOX_TARGET_DIR=/Apps/RecebimentoJSL
104 changes: 104 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,104 @@
# Sistema de Recebimento e Conferência (ADM x Operação)

Aplicação web (HTML/CSS/JS) com Supabase para operação de recebimento de NF-e.

## Principais pontos implementados

- Login/cadastro separados por telas (matrícula + senha).
- Perfis `adm` e `operacao` com visibilidade por permissão.
- Upload XML no ADM com extração automática de motorista, telefone, placa e DT/remessa.
- Conferência da operação com marcação direta de **avaria** e **faltando**.
- NQ somente no ADM, retornando linha pronta para Excel:
`Data | Placa | Remessa | NF | CD de Origem | SKU | Qtde NF | Qtd Rec. FISICO`.
- Cargas conferidas em abas no ADM, com botão para **fechar descarga**.
- Logs em página separada com abas (Todos, Divergências, Falta, Normais), busca e botões de baixar/apagar.
- Destaque visual: divergência em vermelho, faltas em amarelo.

## 1) Erro "Could not find the table 'public.usuarios'"

Esse erro significa que o schema do banco ainda não está aplicado no projeto Supabase.

### Correção definitiva
1. Abrir o **SQL Editor** do Supabase.
2. Executar o arquivo `supabase-schema.sql` (versão idempotente atualizada).
3. Recarregar o app.

> O app entra em **MODO LOCAL** automaticamente quando não encontra as tabelas, para não travar testes.

## 2) Configuração Supabase

1. Crie um projeto no Supabase.
2. Rode `supabase-schema.sql` no SQL Editor.
3. Ajuste RLS/policies conforme sua segurança.
4. (Opcional) configure funções para recuperação de senha:
- `send-sms` (Twilio);
- `send-email` (Resend/SMTP).

## 3) Executar localmente

```bash
python3 -m http.server 8080
```

Depois abra `http://localhost:8080`.

## 3.1) Backend Node para TeraBox (upload de TXT)

1. Copie o arquivo de ambiente:
```bash
cp .env.example .env
```
2. Preencha no `.env`:
- `TERABOX_CLIENT_ID`
- `TERABOX_CLIENT_SECRET`
- `TERABOX_REDIRECT_URI`
- `TERABOX_TARGET_DIR`
3. Instale dependências e suba o backend:
```bash
npm install
npm start
```
4. Faça autenticação no TeraBox:
- abra `http://localhost:3000/auth/terabox`
5. No painel ADM, página **Logs**, clique em **Guardar** para enviar o TXT de logs ao TeraBox.

### Usando somente Render (sem localhost)
Se front e backend estiverem em serviços Render/Netlify, não use `localhost`.

1. Backend no Render: copie a URL pública, ex.: `https://recebimento-api.onrender.com`.
2. Abra o front uma vez com:
`https://seu-front.onrender.com/?backend_url=https://recebimento-api.onrender.com`
3. O sistema salva essa URL em `localStorage` (`rcv-backend-url`) e o botão **Guardar** passa a usar o backend remoto.

> Fluxo implementado no backend: OAuth (authorization code) → pre-upload → upload da parte → create file.
> Nome do arquivo: `logs-carregamento-YYYYMMDD-HHMMSS.txt`.

## 4) Fluxo por perfil

### ADM
- Publica XML para conferência;
- acompanha cargas por abas (abertas/conferidas/NQ);
- fecha descarga;
- acessa página exclusiva de logs com busca, exportação e limpeza;
- copia linhas NQ para Excel.

### Operação
- Seleciona nota publicada;
- informa quantidades conferidas;
- marca se veio avariado e se veio faltando;
- envia conferência para retorno do ADM.

## 5) SMS e E-mail

Se SMS/e-mail não funcionar, normalmente faltam as edge functions no projeto Supabase.

Exemplo:
```bash
supabase functions deploy send-email
supabase functions deploy send-sms
supabase secrets set CHAVE=valor
```

## 6) Observação de segurança

Este MVP mantém senha em texto puro para simplicidade operacional. Para produção, usar Supabase Auth + hash de senha.
153 changes: 153 additions & 0 deletions index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,153 @@
<!doctype html>
<html lang="pt-BR">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Recebimento e Conferência</title>
<link rel="stylesheet" href="styles.css" />
<script src="https://cdn.jsdelivr.net/npm/@supabase/supabase-js@2"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/jspdf/2.5.1/jspdf.umd.min.js"></script>
<script src="https://cdnjs.cloudflare.com/ajax/libs/html2canvas/1.4.1/html2canvas.min.js"></script>
<script src="https://cdn.jsdelivr.net/npm/jsbarcode@3.11.6/dist/JsBarcode.all.min.js"></script>
</head>
<body>
<header>
<div class="brand">
<img src="https://upload.wikimedia.org/wikipedia/commons/thumb/7/73/JSL_SA_logo.svg/320px-JSL_SA_logo.svg.png" alt="Logo JSL" />
<h1>Recebimento de Notas</h1>
</div>
<div class="toolbar" id="globalToolbar">
<button id="btnDensity">Modo tablet</button>
<button id="btnLogs" class="hidden">Página de logs</button>
<button id="btnBackAdmin" class="hidden">Voltar ao ADM</button>
<button id="btnLogout">Sair</button>
</div>
</header>

<main>
<section id="authSection" class="card">
<h2>Acesso</h2>
<div class="auth-choice">
<button id="showLogin" type="button">Entrar</button>
<button id="showRegister" type="button">Cadastrar</button>
</div>

<form id="loginForm" class="stack auth-form">
<h3>Entrar</h3>
<label>Matrícula<input required name="matricula" /></label>
<label>Senha<input required name="senha" type="password" /></label>
<button type="submit">Entrar</button>
<div class="recovery">
<strong>Recuperar senha</strong>
<button type="button" id="recoverSms">Via SMS</button>
<button type="button" id="recoverEmail">Via Email</button>
</div>
<p class="hint">Para SMS/e-mail no Supabase, configure as edge functions <code>send-sms</code> e <code>send-email</code>.</p>
</form>

<form id="registerForm" class="stack auth-form hidden">
<h3>Cadastrar usuário</h3>
<label>Matrícula (login)<input required name="matricula" /></label>
<label>Senha<input required name="senha" type="password" /></label>
<label>Perfil
<select name="role" required>
<option value="operacao">Operação</option>
<option value="adm">ADM</option>
</select>
</label>
<label>Email<input name="email" type="email" /></label>
<label>Telefone<input name="telefone" placeholder="5511999999999" /></label>
<button type="submit">Cadastrar</button>
</form>
</section>

<section id="adminSection" class="card hidden">
<h2>Painel ADM</h2>

<div class="tabs">
<button class="tab-btn active" data-admin-tab="abertas">Cargas abertas</button>
<button class="tab-btn" data-admin-tab="conferidas">Cargas conferidas</button>
<button class="tab-btn" data-admin-tab="nq">NQ Excel</button>
</div>

<div id="tabAbertas" class="tab-content">
<div class="grid two">
<form id="uploadXmlForm" class="stack">
<h3>Subir XML de NF-e</h3>
<p id="duplicateWarning" class="warning hidden">⚠️ Esta NF já foi publicada anteriormente. Não publique novamente.</p>
<label>Arquivo XML<input type="file" id="xmlFile" accept=".xml,text/xml" required /></label>
<label>Motorista (extraído do XML)<input name="motorista" readonly /></label>
<label>Telefone motorista (extraído do XML)<input name="telefoneMotorista" readonly /></label>
<label>Placa (extraída do XML)<input name="placa" readonly /></label>
<button type="submit">Publicar nota para conferência</button>
</form>

<div class="stack">
<h3>Visualização da nota</h3>
<pre id="invoicePreview" class="preview">Nenhum XML carregado.</pre>
<button id="btnPrintInvoice" type="button">Gerar PDF (NF + Conferência + Etiquetas)</button>
</div>

<div class="stack">
<h3>Pré-visualização PDF</h3>
<div class="pdf-pages">
<div id="pdfPage1" class="pdf-page"></div>
<div id="pdfPage2" class="pdf-page"></div>
</div>
</div>
</div>
</div>

<div id="tabConferidas" class="tab-content hidden">
<h3>Conferências recebidas</h3>
<div id="adminConferencias"></div>
</div>

<div id="tabNq" class="tab-content hidden">
<h3>NQ para Excel (linha)</h3>
<p class="hint">A operação só marca <strong>avaria</strong> e <strong>falta</strong>. Aqui no ADM você copia as linhas prontas para Excel.</p>
<div id="adminNqContainer"></div>
</div>
</section>

<section id="logsSection" class="card hidden">
<h2>Página de Logs</h2>
<div class="toolbar logs-toolbar">
<input id="logSearch" placeholder="Pesquisar por matrícula, NF, código..." />
<button id="btnExportLogs">Baixar logs</button>
<button id="btnGuardarTerabox">Guardar</button>
<button id="btnClearLogs" class="danger">Apagar logs</button>
</div>
<div class="tabs">
<button class="tab-btn active" data-log-filter="all">Todos</button>
<button class="tab-btn" data-log-filter="divergente">Divergências</button>
<button class="tab-btn" data-log-filter="falta">Falta</button>
<button class="tab-btn" data-log-filter="ok">Normais</button>
</div>
<div id="logsContainer"></div>
</section>

<section id="operacaoSection" class="card hidden">
<h2>Painel Operação</h2>
<p id="operacaoStats" class="counter"></p>
<div class="stack">
<label>Escolha uma nota publicada<select id="notaSelect"></select></label>
<div id="notaInfo"></div>
<form id="conferenciaForm" class="stack"></form>
</div>
</section>
</main>

<button id="chatBubble" class="chat-bubble hidden">💬</button>
<section id="chatPanel" class="chat-panel hidden">
<h3>Chat</h3>
<div id="chatMessages" class="chat-messages"></div>
<form id="chatForm" class="chat-form">
<input id="chatInput" placeholder="Digite sua mensagem" required />
<button type="submit">Enviar</button>
</form>
</section>

<script type="module" src="main.js"></script>
</body>
</html>
Loading