Skip to content
Open
Show file tree
Hide file tree
Changes from 4 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.
166 changes: 166 additions & 0 deletions index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,166 @@
<!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>
<button class="tab-btn" data-admin-tab="planilha">Planilha descarga</button>
<button class="tab-btn" data-admin-tab="portaria">Portaria</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>

<div id="tabPlanilha" class="tab-content hidden">
<h3>Planilha de descarga</h3>
<div id="adminDescargasContainer"></div>
</div>

<div id="tabPortaria" class="tab-content hidden">
<h3>Portaria (linha para Excel)</h3>
<p class="hint">Formato: DATA | Hora Chegada | NF | PESO | REMESSA | Documento SAP | PORTARIA.</p>
<div id="adminPortariaContainer"></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