Initial commit: DocuAgro - Plataforma EUDR
This commit is contained in:
24
.env.example
Normal file
24
.env.example
Normal file
@@ -0,0 +1,24 @@
|
||||
# === DocuAgro - Configuração ===
|
||||
|
||||
# Bot Telegram
|
||||
TELEGRAM_BOT_TOKEN=seu_token_aqui
|
||||
|
||||
# OpenAI API (gpt-4o-mini)
|
||||
OPENAI_API_KEY=sua_chave_aqui
|
||||
OPENAI_MODEL=gpt-4o-mini
|
||||
|
||||
# Servidor
|
||||
PORT=3100
|
||||
PANEL_PORT=3101
|
||||
NODE_ENV=development
|
||||
|
||||
# Banco de dados
|
||||
DB_PATH=./data/docuagro.db
|
||||
|
||||
# Upload de arquivos
|
||||
UPLOAD_DIR=./uploads
|
||||
MAX_FILE_SIZE=20971520
|
||||
|
||||
# Cooperativa padrão (para MVP)
|
||||
COOP_NAME=Cooperativa Piloto
|
||||
COOP_ID=coop_001
|
||||
11
.gitignore
vendored
Normal file
11
.gitignore
vendored
Normal file
@@ -0,0 +1,11 @@
|
||||
node_modules/
|
||||
.next/
|
||||
dist/
|
||||
.env
|
||||
.env.local
|
||||
.env*.local
|
||||
*.log
|
||||
.DS_Store
|
||||
coverage/
|
||||
.turbo/
|
||||
*.tsbuildinfo
|
||||
217
README.md
Normal file
217
README.md
Normal file
@@ -0,0 +1,217 @@
|
||||
# 🌱 DocuAgro
|
||||
|
||||
**Compliance do produtor, na palma da mão.**
|
||||
|
||||
Bot Telegram + Painel Web para coleta, validação e organização de documentação de produtores rurais para compliance EUDR (Regulamento da União Europeia contra Desmatamento).
|
||||
|
||||
---
|
||||
|
||||
## 📋 O que é
|
||||
|
||||
O DocuAgro resolve o problema da "última milha" do compliance EUDR: coletar, organizar e validar a documentação de cada produtor individual e entregar um dossiê padronizado para cooperativas e tradings.
|
||||
|
||||
### Como funciona
|
||||
|
||||
1. **Produtor** interage via **Bot Telegram** — envia documentos, tira dúvidas
|
||||
2. **IA (gpt-4o-mini)** guia a coleta, valida documentos, orienta correções
|
||||
3. **OCR** extrai dados automaticamente de fotos e PDFs
|
||||
4. **Dossiê PDF** é gerado automaticamente quando todos os documentos estão prontos
|
||||
5. **Cooperativa** acompanha tudo pelo **Painel Web**
|
||||
|
||||
---
|
||||
|
||||
## 📄 Documentos Coletados
|
||||
|
||||
| # | Documento | Validação |
|
||||
|---|-----------|-----------|
|
||||
| 1 | **CAR** (Cadastro Ambiental Rural) | Número SICAR + OCR |
|
||||
| 2 | **CCIR** (Certificado Cadastro Imóvel Rural) | OCR + código INCRA |
|
||||
| 3 | **ITR** (Imposto Territorial Rural) | OCR + CPF/CNPJ |
|
||||
| 4 | **Georreferenciamento** | Coordenadas GPS |
|
||||
| 5 | **Licença Ambiental** | OCR + validade |
|
||||
| 6 | **Contrato de Arrendamento** | OCR (se aplicável) |
|
||||
| 7 | **Nota Fiscal de Venda** | OCR + dados |
|
||||
| 8 | **Declaração de Não Desmatamento** | Gerada automaticamente |
|
||||
|
||||
---
|
||||
|
||||
## 🛠 Stack Técnica
|
||||
|
||||
- **Bot:** Node.js + [Telegraf](https://telegraf.js.org/)
|
||||
- **IA:** OpenAI API (gpt-4o-mini)
|
||||
- **OCR:** Tesseract.js
|
||||
- **PDF:** PDFKit
|
||||
- **Banco:** SQLite (via better-sqlite3)
|
||||
- **API:** Express.js
|
||||
- **Painel:** HTML/CSS/JS puro (sem framework)
|
||||
|
||||
---
|
||||
|
||||
## 🚀 Instalação
|
||||
|
||||
### Pré-requisitos
|
||||
|
||||
- Node.js 18+
|
||||
- Token do Bot Telegram (via [@BotFather](https://t.me/BotFather))
|
||||
- Chave API da OpenAI
|
||||
|
||||
### Setup
|
||||
|
||||
```bash
|
||||
# Clonar repositório
|
||||
git clone http://137.184.77.7:3000/bigtux/docuagro.git
|
||||
cd docuagro
|
||||
|
||||
# Instalar dependências
|
||||
npm install
|
||||
|
||||
# Configurar variáveis de ambiente
|
||||
cp .env.example .env
|
||||
# Editar .env com seus tokens
|
||||
|
||||
# Criar banco de dados
|
||||
npm run setup
|
||||
|
||||
# Iniciar (bot + API + painel)
|
||||
npm start
|
||||
```
|
||||
|
||||
### Modo desenvolvimento
|
||||
|
||||
```bash
|
||||
npm run dev # Usa nodemon para auto-reload
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## ⚙️ Configuração (.env)
|
||||
|
||||
```env
|
||||
# Bot Telegram
|
||||
TELEGRAM_BOT_TOKEN=123456:ABC-DEF...
|
||||
|
||||
# OpenAI API
|
||||
OPENAI_API_KEY=sk-...
|
||||
OPENAI_MODEL=gpt-4o-mini
|
||||
|
||||
# Servidor
|
||||
PORT=3100
|
||||
|
||||
# Banco de dados
|
||||
DB_PATH=./data/docuagro.db
|
||||
|
||||
# Upload
|
||||
UPLOAD_DIR=./uploads
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 📱 Comandos do Bot
|
||||
|
||||
| Comando | Descrição |
|
||||
|---------|-----------|
|
||||
| `/start` | Iniciar cadastro |
|
||||
| `/status` | Ver status dos documentos |
|
||||
| `/dossie` | Gerar dossiê PDF |
|
||||
| `/pular` | Pular documento atual |
|
||||
| `/ajuda` | Menu de ajuda |
|
||||
|
||||
---
|
||||
|
||||
## 🌐 Painel Web
|
||||
|
||||
Acesse `http://localhost:3100` para o painel da cooperativa:
|
||||
|
||||
- **Dashboard** com estatísticas em tempo real
|
||||
- **Lista de produtores** com status de compliance
|
||||
- **Busca** por nome, CPF, propriedade ou município
|
||||
- **Detalhes** de cada produtor e seus documentos
|
||||
- **Download** de dossiê PDF
|
||||
- **Exportação** CSV para sistemas externos
|
||||
|
||||
---
|
||||
|
||||
## 📡 API REST
|
||||
|
||||
| Endpoint | Método | Descrição |
|
||||
|----------|--------|-----------|
|
||||
| `/api/health` | GET | Health check |
|
||||
| `/api/dashboard` | GET | Estatísticas gerais |
|
||||
| `/api/produtores` | GET | Listar produtores |
|
||||
| `/api/produtores/:id` | GET | Detalhe do produtor |
|
||||
| `/api/produtores/:id/dossie` | POST | Gerar dossiê PDF |
|
||||
| `/api/dossie/download/:arquivo` | GET | Download do dossiê |
|
||||
| `/api/exportar/csv` | GET | Exportar CSV |
|
||||
|
||||
---
|
||||
|
||||
## 📁 Estrutura do Projeto
|
||||
|
||||
```
|
||||
docuagro/
|
||||
├── src/
|
||||
│ ├── index.js # Entry point
|
||||
│ ├── setup-db.js # Criação do banco de dados
|
||||
│ ├── bot/
|
||||
│ │ └── telegram-bot.js # Bot Telegram (Telegraf)
|
||||
│ ├── api/
|
||||
│ │ └── routes.js # API REST (Express)
|
||||
│ ├── services/
|
||||
│ │ ├── database.js # Operações de banco (SQLite)
|
||||
│ │ ├── ai-service.js # Integração OpenAI
|
||||
│ │ ├── ocr-service.js # OCR (Tesseract.js)
|
||||
│ │ ├── pdf-service.js # Geração de dossiê PDF
|
||||
│ │ └── system-prompt.js # Prompt da IA especialista EUDR
|
||||
│ └── utils/
|
||||
├── public/
|
||||
│ └── index.html # Painel web da cooperativa
|
||||
├── data/ # Banco SQLite
|
||||
├── uploads/ # Documentos dos produtores
|
||||
├── docs/ # Documentação adicional
|
||||
├── .env.example # Template de configuração
|
||||
├── .gitignore
|
||||
├── package.json
|
||||
└── README.md
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 🔒 Segurança
|
||||
|
||||
- Documentos armazenados localmente (não em cloud pública)
|
||||
- Cada produtor tem diretório isolado de uploads
|
||||
- API sem autenticação no MVP (adicionar antes de produção!)
|
||||
- CPF/CNPJ armazenados para identificação
|
||||
|
||||
### TODO para produção:
|
||||
- [ ] Autenticação JWT no painel
|
||||
- [ ] HTTPS obrigatório
|
||||
- [ ] Rate limiting na API
|
||||
- [ ] Criptografia de dados sensíveis
|
||||
- [ ] Backup automático do banco
|
||||
|
||||
---
|
||||
|
||||
## 📊 Contexto EUDR
|
||||
|
||||
O **Regulamento (UE) 2023/1115** proíbe a importação na UE de commodities produzidas em áreas desmatadas após 31/12/2020. Afeta:
|
||||
|
||||
- 🫘 Soja
|
||||
- ☕ Café
|
||||
- 🍫 Cacau
|
||||
- 🌴 Óleo de palma
|
||||
- 🌳 Madeira
|
||||
- 🐄 Gado
|
||||
- 🔧 Borracha
|
||||
|
||||
Produtores brasileiros que exportam precisam comprovar geolocalização, ausência de desmatamento e conformidade ambiental.
|
||||
|
||||
---
|
||||
|
||||
## 📝 Licença
|
||||
|
||||
MIT
|
||||
|
||||
---
|
||||
|
||||
**DocuAgro** — Feito com 🌱 para o agro brasileiro.
|
||||
0
data/.gitkeep
Normal file
0
data/.gitkeep
Normal file
BIN
data/docuagro.db
Normal file
BIN
data/docuagro.db
Normal file
Binary file not shown.
1002
docs/docuagro-apresentacao.html
Normal file
1002
docs/docuagro-apresentacao.html
Normal file
File diff suppressed because it is too large
Load Diff
BIN
docs/docuagro-apresentacao.pdf
Normal file
BIN
docs/docuagro-apresentacao.pdf
Normal file
Binary file not shown.
655
docs/guia-completo-vendas.md
Normal file
655
docs/guia-completo-vendas.md
Normal file
@@ -0,0 +1,655 @@
|
||||
# 📋 DocuAgro — Guia Completo do Sistema
|
||||
### Para Equipe Comercial / Vendas
|
||||
|
||||
> **Versão:** 1.0 | **Data:** Fevereiro 2026
|
||||
> **Contato técnico:** m171c0@gmail.com
|
||||
|
||||
---
|
||||
|
||||
## 📌 ÍNDICE
|
||||
|
||||
1. [O Problema: EUDR](#1-o-problema-eudr)
|
||||
2. [A Solução: DocuAgro](#2-a-solução-docuagro)
|
||||
3. [Como Funciona (Passo a Passo)](#3-como-funciona-passo-a-passo)
|
||||
4. [O Bot Telegram (Lado do Produtor)](#4-o-bot-telegram-lado-do-produtor)
|
||||
5. [O Painel Web (Lado da Cooperativa)](#5-o-painel-web-lado-da-cooperativa)
|
||||
6. [Inteligência Artificial](#6-inteligência-artificial)
|
||||
7. [Documentos Coletados](#7-documentos-coletados)
|
||||
8. [Dossiê PDF Automatizado](#8-dossiê-pdf-automatizado)
|
||||
9. [Arquitetura Técnica](#9-arquitetura-técnica)
|
||||
10. [Modelo de Negócio e Preços](#10-modelo-de-negócio-e-preços)
|
||||
11. [Público-Alvo](#11-público-alvo)
|
||||
12. [Como Fazer uma Demo](#12-como-fazer-uma-demo)
|
||||
13. [Perguntas Frequentes (FAQ)](#13-perguntas-frequentes-faq)
|
||||
14. [Diferenciais Competitivos](#14-diferenciais-competitivos)
|
||||
15. [Roadmap / Próximos Passos](#15-roadmap--próximos-passos)
|
||||
|
||||
---
|
||||
|
||||
## 1. O Problema: EUDR
|
||||
|
||||
### O que é o EUDR?
|
||||
O **Regulamento (UE) 2023/1115** — conhecido como **EUDR** (European Union Deforestation Regulation) — é uma lei da União Europeia que **proíbe a importação de commodities** produzidas em áreas desmatadas após 31 de dezembro de 2020.
|
||||
|
||||
### Quem é afetado?
|
||||
Produtores brasileiros que exportam (diretamente ou via cooperativa/trading) as seguintes commodities:
|
||||
|
||||
| Commodity | Principais Estados |
|
||||
|-----------|-------------------|
|
||||
| 🫘 Soja | MT, PR, GO, MS, BA |
|
||||
| ☕ Café | MG, SP, ES, PR, BA |
|
||||
| 🍫 Cacau | BA, PA, RO |
|
||||
| 🐄 Gado | MT, GO, PA, MS, MG |
|
||||
| 🌴 Óleo de Palma | PA, AM |
|
||||
| 🌳 Madeira | PA, AM, MT, RO |
|
||||
| 🔧 Borracha | SP, BA, MT |
|
||||
|
||||
> ⚠️ **Algodão NÃO está na lista do EUDR.**
|
||||
|
||||
### O problema real
|
||||
- O produtor rural individual **não sabe** que precisa dessa documentação
|
||||
- Quando sabe, **não sabe como obter** os documentos
|
||||
- As cooperativas precisam **coletar documentos de centenas ou milhares de produtores**
|
||||
- Fazer isso **manualmente** (ligando, visitando, cobrando) é **caro e lento**
|
||||
- Sem compliance, a cooperativa **perde acesso ao mercado europeu**
|
||||
|
||||
### Prazo
|
||||
O regulamento EUDR está em vigor. Empresas que importarem commodities da UE sem comprovação de compliance estão sujeitas a **multas pesadas e proibição de importação**.
|
||||
|
||||
---
|
||||
|
||||
## 2. A Solução: DocuAgro
|
||||
|
||||
### Em uma frase:
|
||||
> **DocuAgro automatiza a coleta, validação e organização de documentos de produtores rurais para compliance EUDR, usando um bot no Telegram com inteligência artificial.**
|
||||
|
||||
### Como resolve o problema:
|
||||
|
||||
| Problema | Solução DocuAgro |
|
||||
|----------|-----------------|
|
||||
| Produtor não sabe o que fazer | Bot guia passo a passo em linguagem simples |
|
||||
| Documentos difíceis de coletar | IA explica como obter cada um |
|
||||
| Processo manual e caro | 100% automatizado via Telegram |
|
||||
| Sem controle sobre progresso | Painel web em tempo real pra cooperativa |
|
||||
| Documentação desorganizada | Dossiê PDF profissional gerado automaticamente |
|
||||
| OCR manual | Extração automática de dados por OCR |
|
||||
|
||||
### Dois lados do sistema:
|
||||
|
||||
```
|
||||
┌─────────────────────┐ ┌──────────────────────┐
|
||||
│ 👨🌾 PRODUTOR │ │ 🏢 COOPERATIVA │
|
||||
│ │ │ │
|
||||
│ Bot Telegram │◄────────►│ Painel Web │
|
||||
│ @docuagro_bot │ Dados │ docuagro.com.br │
|
||||
│ │ │ │
|
||||
│ • Conversa com IA │ │ • Dashboard │
|
||||
│ • Envia documentos│ │ • Lista produtores │
|
||||
│ • Recebe feedback │ │ • Status compliance│
|
||||
│ • Gera dossiê │ │ • Download dossiês │
|
||||
│ │ │ • Exporta CSV │
|
||||
└─────────────────────┘ └──────────────────────┘
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. Como Funciona (Passo a Passo)
|
||||
|
||||
### Fluxo Completo:
|
||||
|
||||
```
|
||||
PASSO 1 → Cooperativa contrata o DocuAgro
|
||||
PASSO 2 → Recebe acesso ao painel web (docuagro.com.br)
|
||||
PASSO 3 → Cooperativa envia o link do bot (@docuagro_bot) pros produtores
|
||||
PASSO 4 → Produtor abre o Telegram e clica em /start
|
||||
PASSO 5 → Bot faz onboarding (nome, CPF, propriedade, município, área, cultura)
|
||||
PASSO 6 → Bot pede o 1° documento (CAR)
|
||||
PASSO 7 → Produtor tira foto ou envia PDF do documento
|
||||
PASSO 8 → IA confirma recebimento e OCR valida automaticamente
|
||||
PASSO 9 → Bot pede o 2° documento (CCIR)
|
||||
PASSO 10 → Repete para todos os 8 documentos
|
||||
PASSO 11 → Quando completo, produtor pode gerar dossiê PDF (/dossie)
|
||||
PASSO 12 → Cooperativa acompanha tudo em tempo real no painel
|
||||
PASSO 13 → Cooperativa exporta dados em CSV ou baixa dossiês individuais
|
||||
```
|
||||
|
||||
### Tempo médio por produtor:
|
||||
- **Onboarding:** 3-5 minutos
|
||||
- **Coleta dos 8 documentos:** 2-7 dias (depende do produtor ter os docs prontos)
|
||||
- **Geração do dossiê:** Automático (< 5 segundos)
|
||||
|
||||
---
|
||||
|
||||
## 4. O Bot Telegram (Lado do Produtor)
|
||||
|
||||
### Por que Telegram?
|
||||
- **89% dos produtores rurais brasileiros têm smartphone**
|
||||
- Telegram funciona com **internet fraca** (comum na zona rural)
|
||||
- **Não precisa instalar app novo** — maioria já tem Telegram ou instala em 1 minuto
|
||||
- Funciona em **qualquer celular** (Android barato, iPhone antigo)
|
||||
- **Sem custo para o produtor** — só precisa do celular que já tem
|
||||
|
||||
### Bot: @docuagro_bot
|
||||
|
||||
### Comandos disponíveis:
|
||||
|
||||
| Comando | O que faz |
|
||||
|---------|-----------|
|
||||
| `/start` | Inicia o cadastro ou retoma de onde parou |
|
||||
| `/status` | Mostra o status de cada documento (✅ ⬜ 🟡 ❌) |
|
||||
| `/dossie` | Gera e envia o dossiê PDF completo |
|
||||
| `/pular` | Pula um documento que não tem no momento (pode enviar depois) |
|
||||
| `/ajuda` | Menu de ajuda com instruções |
|
||||
|
||||
### Fluxo do onboarding (conversa real):
|
||||
|
||||
```
|
||||
BOT: 🌱 Bem-vindo ao DocuAgro!
|
||||
Oi, João! Eu sou o DocuAgro, seu assistente para organizar
|
||||
a documentação da sua propriedade rural.
|
||||
|
||||
📋 Por que isso é importante?
|
||||
A União Europeia criou uma nova regra (EUDR) que exige
|
||||
comprovação de que a produção não vem de área desmatada.
|
||||
|
||||
Primeiro, me conta: qual é o seu nome completo?
|
||||
|
||||
PRODUTOR: João da Silva Oliveira
|
||||
|
||||
BOT: Beleza, João! Agora me passa seu CPF, por favor.
|
||||
É só pra identificação no sistema.
|
||||
|
||||
PRODUTOR: 123.456.789-00
|
||||
|
||||
BOT: Certinho! Qual o nome da sua propriedade?
|
||||
|
||||
PRODUTOR: Fazenda Boa Vista
|
||||
|
||||
BOT: Boa! Em qual município e estado fica a Fazenda Boa Vista?
|
||||
|
||||
PRODUTOR: Sorriso, Mato Grosso
|
||||
|
||||
BOT: Sorriso/MT, anotado! Qual a área aproximada em hectares?
|
||||
|
||||
PRODUTOR: uns 350 hectares
|
||||
|
||||
BOT: 350 hectares, beleza! E qual a cultura principal?
|
||||
Soja, café, gado...?
|
||||
|
||||
PRODUTOR: Soja
|
||||
|
||||
BOT: Perfeito, João! Tá tudo anotado. Agora vamos pros documentos.
|
||||
|
||||
📄 Primeiro documento: CAR (Cadastro Ambiental Rural)
|
||||
O CAR é o registro da sua propriedade no sistema ambiental.
|
||||
Se já tem, manda a foto ou o PDF pra mim.
|
||||
Se não tem, é feito pelo site car.gov.br ou no escritório
|
||||
do meio ambiente do seu município.
|
||||
```
|
||||
|
||||
### Como o produtor envia documentos:
|
||||
1. **Foto** — tira foto do documento pelo celular e envia no chat
|
||||
2. **PDF** — envia o arquivo PDF diretamente
|
||||
3. **Qualquer formato** — o sistema aceita e processa
|
||||
|
||||
### O que acontece quando o produtor envia:
|
||||
1. Bot confirma: "✅ Recebi seu CAR! Tá sendo analisado."
|
||||
2. **OCR automático** extrai dados do documento (número, CPF, datas)
|
||||
3. **Validação automática** verifica se o documento parece correto
|
||||
4. Se tudo OK, marca como aprovado e pede o próximo
|
||||
5. Se ilegível: "📸 A foto ficou escura, pode tirar outra?"
|
||||
|
||||
---
|
||||
|
||||
## 5. O Painel Web (Lado da Cooperativa)
|
||||
|
||||
### Acesso: https://docuagro.com.br
|
||||
|
||||
### Dashboard principal:
|
||||
O painel mostra em tempo real:
|
||||
|
||||
- **Total de produtores** cadastrados
|
||||
- **Produtores completos** (todos os docs aprovados) ✅
|
||||
- **Em andamento** (parcialmente documentados) 🟡
|
||||
- **Pendentes** (cadastrados mas sem docs) ⬜
|
||||
- **Irregulares** (docs rejeitados/vencidos) ❌
|
||||
- **Percentual de compliance** (barra de progresso)
|
||||
|
||||
### Lista de Produtores:
|
||||
Tabela com todos os produtores mostrando:
|
||||
- Nome
|
||||
- CPF/CNPJ
|
||||
- Propriedade
|
||||
- Município/UF
|
||||
- Área (ha)
|
||||
- Cultura
|
||||
- Status (pendente/em andamento/completo/irregular)
|
||||
- Documentos aprovados (ex: 5/8)
|
||||
|
||||
### Funcionalidades:
|
||||
- 🔍 **Busca** por nome, CPF, propriedade ou município
|
||||
- 📄 **Detalhes** de cada produtor (clica e vê todos os docs)
|
||||
- 📥 **Download** de dossiê PDF individual
|
||||
- 📊 **Exportação CSV** de todos os dados (para importar em planilhas/ERPs)
|
||||
- 🔄 **Atualização em tempo real** — sem precisar recarregar
|
||||
|
||||
### API REST (para integração):
|
||||
O sistema também oferece API para integração com sistemas existentes:
|
||||
|
||||
| Endpoint | Descrição |
|
||||
|----------|-----------|
|
||||
| `GET /api/dashboard` | Estatísticas gerais |
|
||||
| `GET /api/produtores` | Lista todos os produtores |
|
||||
| `GET /api/produtores/:id` | Detalhe de um produtor |
|
||||
| `POST /api/produtores/:id/dossie` | Gera dossiê PDF |
|
||||
| `GET /api/exportar/csv` | Exporta tudo em CSV |
|
||||
| `GET /api/health` | Health check |
|
||||
|
||||
---
|
||||
|
||||
## 6. Inteligência Artificial
|
||||
|
||||
### Modelo usado: GPT-4o-mini (OpenAI)
|
||||
|
||||
### O que a IA faz:
|
||||
|
||||
1. **Conversa natural** — o produtor conversa normalmente, sem precisar seguir comandos rígidos
|
||||
2. **Linguagem do campo** — a IA fala como um vizinho, não como um robô. Usa termos como "tá certinho", "beleza", "vamos lá"
|
||||
3. **Guia a coleta** — explica o que é cada documento, onde encontrar, como obter
|
||||
4. **Extrai dados** — durante o onboarding, extrai nome, CPF, município etc. automaticamente da conversa
|
||||
5. **Valida documentos** — avalia se o documento parece correto pelo conteúdo
|
||||
6. **Orienta correções** — se a foto está ruim ou o documento errado, orienta o produtor
|
||||
|
||||
### OCR (Leitura Automática de Documentos):
|
||||
O sistema usa **Tesseract.js** para extrair texto de fotos e PDFs:
|
||||
|
||||
- **CAR:** Extrai número do registro SICAR
|
||||
- **CCIR:** Extrai código do imóvel (INCRA)
|
||||
- **ITR:** Identifica dados da Receita Federal
|
||||
- **Georreferenciamento:** Extrai coordenadas GPS
|
||||
- **Licença Ambiental:** Extrai datas de validade
|
||||
- **Nota Fiscal:** Extrai CPF/CNPJ, valores
|
||||
|
||||
### Custo da IA:
|
||||
- GPT-4o-mini custa **~US$ 0,15 por 1 milhão de tokens de entrada**
|
||||
- Na prática: **menos de R$ 0,10 por produtor completo** (onboarding + 8 docs)
|
||||
- Para 500 produtores: **custo mensal de IA ≈ R$ 50**
|
||||
|
||||
---
|
||||
|
||||
## 7. Documentos Coletados
|
||||
|
||||
O DocuAgro coleta **8 documentos obrigatórios** para compliance EUDR:
|
||||
|
||||
### 1. CAR — Cadastro Ambiental Rural
|
||||
- **O que é:** Registro da propriedade no sistema ambiental federal (SICAR)
|
||||
- **Onde obter:** car.gov.br ou escritório municipal de meio ambiente
|
||||
- **Validação OCR:** Número SICAR, menção ao cadastro ambiental
|
||||
|
||||
### 2. CCIR — Certificado de Cadastro de Imóvel Rural
|
||||
- **O que é:** Certificado emitido pelo INCRA que prova o cadastro do imóvel
|
||||
- **Onde obter:** Site do INCRA (sncr.serpro.gov.br) ou escritório regional
|
||||
- **Validação OCR:** Código do imóvel, menção ao INCRA
|
||||
|
||||
### 3. ITR — Imposto Territorial Rural
|
||||
- **O que é:** Comprovante de quitação do imposto sobre a terra
|
||||
- **Onde obter:** Site da Receita Federal ou via contador
|
||||
- **Validação OCR:** Menção a ITR/Receita Federal, CPF
|
||||
|
||||
### 4. Georreferenciamento
|
||||
- **O que é:** Mapa/planta da propriedade com coordenadas GPS
|
||||
- **Onde obter:** Agrimensor/topógrafo ou cartório
|
||||
- **Validação OCR:** Coordenadas geográficas, memorial descritivo
|
||||
|
||||
### 5. Licença Ambiental
|
||||
- **O que é:** Autorização do órgão ambiental estadual para atividade agropecuária
|
||||
- **Onde obter:** SEMA, IMA, IEMA (varia por estado)
|
||||
- **Validação OCR:** Menção a licença/ambiental, datas de validade
|
||||
|
||||
### 6. Contrato de Arrendamento
|
||||
- **O que é:** Contrato com o proprietário da terra (se não for dono)
|
||||
- **Onde obter:** Cartório ou contrato particular
|
||||
- **Nota:** Se o produtor é dono, esse documento é pulado (/pular)
|
||||
- **Validação OCR:** Menção a contrato/arrendamento
|
||||
|
||||
### 7. Nota Fiscal de Venda
|
||||
- **O que é:** Última NF de venda da produção
|
||||
- **Onde obter:** Sistema de NF-e ou talão de NF de produtor rural
|
||||
- **Validação OCR:** Menção a nota fiscal/DANFE, CPF/CNPJ
|
||||
|
||||
### 8. Declaração de Não Desmatamento
|
||||
- **O que é:** Autodeclaração de que não houve desmatamento pós-dez/2020
|
||||
- **Geração:** O DocuAgro gera automaticamente para assinatura
|
||||
- **Validação:** Sempre aprovada (gerada pelo sistema)
|
||||
|
||||
---
|
||||
|
||||
## 8. Dossiê PDF Automatizado
|
||||
|
||||
### O que é:
|
||||
Um **documento PDF profissional** que compila toda a documentação do produtor em um único arquivo padronizado.
|
||||
|
||||
### Estrutura do dossiê:
|
||||
|
||||
```
|
||||
📄 Dossiê EUDR — João da Silva
|
||||
|
||||
├── CAPA
|
||||
│ • Logo DocuAgro
|
||||
│ • Nome do produtor
|
||||
│ • CPF, propriedade, município
|
||||
│ • Status: ✅ COMPLIANCE COMPLETO
|
||||
│
|
||||
├── DADOS DO PRODUTOR
|
||||
│ • Informações completas (nome, CPF, propriedade, área, cultura)
|
||||
│ • Data de cadastro
|
||||
│ • Progresso: 8/8 documentos
|
||||
│
|
||||
├── RESUMO DOS DOCUMENTOS
|
||||
│ • Tabela com todos os 8 documentos
|
||||
│ • Status de cada um (aprovado/enviado/pendente)
|
||||
│ • Data de envio
|
||||
│
|
||||
├── DETALHE DE CADA DOCUMENTO (1 página por doc)
|
||||
│ • Nome e tipo do documento
|
||||
│ • Status e datas
|
||||
│ • Dados extraídos por OCR
|
||||
│ • Miniatura da imagem (se foto)
|
||||
│
|
||||
└── DECLARAÇÃO DE CONFORMIDADE
|
||||
• Texto legal para EUDR
|
||||
• Resumo dos documentos validados
|
||||
• Espaço para assinatura
|
||||
• Data e hora de geração
|
||||
```
|
||||
|
||||
### Uso do dossiê:
|
||||
- A cooperativa **apresenta o dossiê às tradings/exportadoras** como prova de compliance
|
||||
- O dossiê pode ser **enviado diretamente para importadores europeus**
|
||||
- Serve como **prova documental** em caso de auditoria EUDR
|
||||
- O produtor também pode solicitar o seu próprio dossiê via `/dossie` no bot
|
||||
|
||||
---
|
||||
|
||||
## 9. Arquitetura Técnica
|
||||
|
||||
### Stack:
|
||||
|
||||
| Componente | Tecnologia |
|
||||
|-----------|------------|
|
||||
| Bot Telegram | Node.js + Telegraf |
|
||||
| IA / Chat | OpenAI GPT-4o-mini |
|
||||
| OCR | Tesseract.js |
|
||||
| Geração PDF | PDFKit |
|
||||
| Banco de Dados | SQLite (better-sqlite3) |
|
||||
| API REST | Express.js |
|
||||
| Painel Web | HTML/CSS/JS puro |
|
||||
| Servidor | DigitalOcean (Ubuntu) |
|
||||
| DNS/CDN/SSL | Cloudflare |
|
||||
| Domínio | docuagro.com.br |
|
||||
|
||||
### Diagrama de arquitetura:
|
||||
|
||||
```
|
||||
┌──────────────┐ ┌─────────────────────────────────────────────┐
|
||||
│ 📱 Telegram │ │ SERVIDOR DOCUAGRO │
|
||||
│ (Produtor) │◄──►│ │
|
||||
│ │ │ ┌──────────────┐ ┌────────────────┐ │
|
||||
└──────────────┘ │ │ Bot Telegraf │ │ Express API │ │
|
||||
│ │ │ │ (porta 3100) │ │
|
||||
│ └──────┬───────┘ └───────┬────────┘ │
|
||||
┌──────────────┐ │ │ │ │
|
||||
│ 🌐 Browser │ │ ┌──────┴──────────────────┴──────┐ │
|
||||
│ (Cooperat.) │◄──►│ │ SERVIÇOS │ │
|
||||
│ │ │ │ ┌─────────────────────────┐ │ │
|
||||
└──────────────┘ │ │ │ ai-service.js (OpenAI) │ │ │
|
||||
│ │ │ ocr-service.js (Tesser) │ │ │
|
||||
│ │ │ pdf-service.js (PDFKit) │ │ │
|
||||
│ │ │ database.js (SQLite) │ │ │
|
||||
│ │ └─────────────────────────┘ │ │
|
||||
│ └────────────────────────────────┘ │
|
||||
│ │
|
||||
│ 📁 data/docuagro.db (banco) │
|
||||
│ 📁 uploads/ (documentos dos produtores) │
|
||||
└─────────────────────────────────────────────┘
|
||||
```
|
||||
|
||||
### Banco de dados (tabelas):
|
||||
|
||||
| Tabela | Descrição |
|
||||
|--------|-----------|
|
||||
| `cooperativas` | Dados da cooperativa cliente |
|
||||
| `produtores` | Dados de cada produtor (nome, CPF, propriedade, status) |
|
||||
| `documentos` | Cada documento enviado (tipo, status, arquivo, OCR) |
|
||||
| `conversas` | Histórico de conversas bot ↔ produtor |
|
||||
| `dossies` | Dossiês PDF gerados |
|
||||
|
||||
### Requisitos de servidor:
|
||||
- **Node.js 18+**
|
||||
- **1 GB RAM** (mínimo) / 2 GB recomendado
|
||||
- **20 GB disco** (depende do volume de documentos)
|
||||
- **Ubuntu 22.04+** ou similar
|
||||
|
||||
---
|
||||
|
||||
## 10. Modelo de Negócio e Preços
|
||||
|
||||
### Modelo: B2B SaaS (Software as a Service)
|
||||
- **Quem paga:** A cooperativa
|
||||
- **Quem usa de graça:** O produtor (via Telegram)
|
||||
- **Recorrência:** Mensalidade
|
||||
|
||||
### Planos:
|
||||
|
||||
| Plano | Limite Produtores | Preço/mês | Ideal para |
|
||||
|-------|-------------------|-----------|------------|
|
||||
| **Starter** | Até 100 | R$ 497 | Cooperativas pequenas |
|
||||
| **Pro** | Até 500 | R$ 1.497 | Cooperativas médias |
|
||||
| **Enterprise** | Até 2.000 | R$ 3.997 | Grandes cooperativas |
|
||||
| **Custom** | Ilimitado | Sob consulta | Tradings, associações |
|
||||
|
||||
### O que está incluído em todos os planos:
|
||||
- ✅ Bot Telegram personalizado
|
||||
- ✅ Painel web completo
|
||||
- ✅ IA para coleta guiada
|
||||
- ✅ OCR automático
|
||||
- ✅ Dossiê PDF automático
|
||||
- ✅ Exportação CSV
|
||||
- ✅ API REST para integração
|
||||
- ✅ Suporte por email
|
||||
- ✅ Atualizações do sistema
|
||||
|
||||
### Custos operacionais (nossos):
|
||||
- Servidor DigitalOcean: ~R$ 100/mês
|
||||
- API OpenAI: ~R$ 50-200/mês (depende do volume)
|
||||
- Domínio: R$ 40/ano
|
||||
- Cloudflare: Grátis
|
||||
- **Total operacional: ~R$ 250-500/mês**
|
||||
|
||||
### Margens:
|
||||
- Com **1 cliente Starter:** Receita R$ 497, Custo ~R$ 250 = **Margem ~50%**
|
||||
- Com **5 clientes Pro:** Receita R$ 7.485, Custo ~R$ 500 = **Margem ~93%**
|
||||
- Com **15 clientes mix:** Receita R$ 35.000+, Custo ~R$ 2.500 = **Margem ~93%**
|
||||
|
||||
### Projeção 12 meses:
|
||||
- Meta: 15 cooperativas
|
||||
- Mix: 5 Starter + 7 Pro + 3 Enterprise
|
||||
- **ARR (Receita Anual Recorrente): ~R$ 420.000**
|
||||
|
||||
### Estratégia de entrada:
|
||||
1. **Piloto grátis 30 dias** — sem compromisso
|
||||
2. Cooperativa cadastra 10-20 produtores para testar
|
||||
3. Vê resultado → converte em assinatura
|
||||
4. Expansão orgânica: cooperativa indica outras
|
||||
|
||||
---
|
||||
|
||||
## 11. Público-Alvo
|
||||
|
||||
### Clientes principais (quem paga):
|
||||
|
||||
| Segmento | Exemplos | Por que compra |
|
||||
|----------|----------|----------------|
|
||||
| **Cooperativas de soja** | COAMO, Cocamar, C.Vale | Exportam para UE, precisam de compliance |
|
||||
| **Cooperativas de café** | Cooxupé, Minasul | Café especial para Europa |
|
||||
| **Cooperativas de cacau** | Cooperativas do Sul da Bahia | Cacau direto pra Europa |
|
||||
| **Cooperativas de gado** | Cooperativas pecuárias MT/GO | Carne bovina para UE |
|
||||
| **Tradings** | Cargill, Bunge, ADM | Precisam compliance dos fornecedores |
|
||||
| **Associações** | OCB estaduais | Podem oferecer como serviço |
|
||||
|
||||
### Onde encontrar clientes:
|
||||
|
||||
| Canal | Detalhes |
|
||||
|-------|---------|
|
||||
| **OCB** (Organização das Cooperativas) | Porta de entrada para todas as cooperativas |
|
||||
| **APROSOJA** | Associação dos produtores de soja por estado |
|
||||
| **CNA** | Confederação Nacional da Agricultura |
|
||||
| **CECAFÉ** | Conselho dos Exportadores de Café |
|
||||
| **ABIEC** | Associação da Indústria Exportadora de Carnes |
|
||||
| **Agrishow** (Ribeirão Preto) | Maior feira agro do Brasil |
|
||||
| **Expointer** (RS) | Feira agropecuária do Sul |
|
||||
| **LinkedIn** | Gerentes de qualidade/compliance de cooperativas |
|
||||
| **Eventos EUDR** | Seminários e workshops sobre a regulamentação |
|
||||
|
||||
### Usuário final (quem usa o bot):
|
||||
- **Produtor rural** — pequeno a grande
|
||||
- **Perfil:** 30-65 anos, smartphone Android, internet instável
|
||||
- **Motivação:** Cooperativa pediu, quer manter acesso ao mercado
|
||||
|
||||
### Estados prioritários:
|
||||
1. **Mato Grosso** — maior produtor de soja e gado
|
||||
2. **Paraná** — cooperativas fortes (COAMO, Cocamar)
|
||||
3. **Goiás** — soja e gado
|
||||
4. **Minas Gerais** — café
|
||||
5. **São Paulo** — café e cana
|
||||
6. **Espírito Santo** — café
|
||||
7. **Bahia** — cacau e soja (MATOPIBA)
|
||||
8. **Pará** — cacau e gado
|
||||
|
||||
---
|
||||
|
||||
## 12. Como Fazer uma Demo
|
||||
|
||||
### Preparação:
|
||||
1. Abra o Telegram no celular ou computador
|
||||
2. Busque **@docuagro_bot**
|
||||
3. Tenha o painel web aberto em outra aba: `https://docuagro.com.br`
|
||||
|
||||
### Roteiro da demo (10 minutos):
|
||||
|
||||
**Minuto 1-2: O Problema**
|
||||
> "Vocês sabem que o EUDR vai exigir documentação de cada produtor individual. Imagina ligar pra 500 produtores, pedir 8 documentos de cada um, organizar tudo, gerar relatórios... Manualmente isso leva meses e custa uma fortuna."
|
||||
|
||||
**Minuto 3-4: A Solução**
|
||||
> "O DocuAgro faz tudo isso automaticamente. O produtor conversa com um bot no Telegram — a mesma ferramenta que ele já usa no dia a dia. Uma inteligência artificial guia ele passo a passo."
|
||||
|
||||
**Minuto 5-7: Demo ao vivo**
|
||||
1. Abra o bot no Telegram
|
||||
2. Envie `/start`
|
||||
3. Mostre o onboarding (responda como produtor fictício)
|
||||
4. Mostre o `/status`
|
||||
5. Mude pra aba do painel web
|
||||
6. Mostre o dashboard e a lista de produtores
|
||||
7. Mostre que o produtor que acabou de se cadastrar já aparece
|
||||
|
||||
**Minuto 8-9: O Dossiê**
|
||||
> "Quando o produtor completa os 8 documentos, o sistema gera automaticamente um dossiê PDF profissional que vocês podem apresentar para a trading ou importador europeu."
|
||||
- Mostre um exemplo do dossiê PDF
|
||||
|
||||
**Minuto 10: Fechamento**
|
||||
> "Oferecemos 30 dias grátis pra vocês testarem com 10-20 produtores. Sem compromisso. Se funcionar — e vai funcionar — a gente fala de plano."
|
||||
|
||||
### Dicas:
|
||||
- **Sempre mostre no celular** — é onde o produtor vai usar
|
||||
- **Fale a linguagem da cooperativa** — compliance, rastreabilidade, mercado europeu
|
||||
- **Enfatize o custo-benefício** — quanto custaria fazer manualmente vs. usar o DocuAgro
|
||||
- **Prepare um dossiê de exemplo** para mostrar como fica o resultado final
|
||||
|
||||
---
|
||||
|
||||
## 13. Perguntas Frequentes (FAQ)
|
||||
|
||||
### "O produtor precisa instalar alguma coisa?"
|
||||
Não! Só precisa ter o Telegram instalado (que a maioria já tem). É só abrir o bot e começar a conversar.
|
||||
|
||||
### "Funciona com internet ruim?"
|
||||
Sim! O Telegram é otimizado para conexões lentas. O produtor pode até mandar as fotos quando tiver sinal e o bot processa depois.
|
||||
|
||||
### "E se o produtor não tiver Telegram?"
|
||||
O Telegram é gratuito e leve. Instala em 2 minutos em qualquer celular. A cooperativa pode orientar na instalação. No futuro, planejamos suporte a WhatsApp também.
|
||||
|
||||
### "Os dados ficam seguros?"
|
||||
Sim. Os documentos ficam armazenados em servidor dedicado com backup. Cada produtor tem um diretório isolado. Nenhum dado é compartilhado com terceiros.
|
||||
|
||||
### "Precisa de treinamento para a cooperativa usar?"
|
||||
Mínimo. O painel web é intuitivo. Fornecemos um treinamento online de 1 hora + manual completo em PDF.
|
||||
|
||||
### "Pode integrar com o sistema da cooperativa?"
|
||||
Sim! Temos API REST documentada. Qualquer sistema pode consultar dados, baixar dossiês e exportar CSV.
|
||||
|
||||
### "E se o produtor não tiver um documento?"
|
||||
O bot explica como obter cada documento, passo a passo. Se o produtor não tem no momento, pode usar `/pular` e enviar depois.
|
||||
|
||||
### "Quanto custa por produtor?"
|
||||
O custo por produtor é desprezível (menos de R$ 0,10 de IA + OCR). O plano é por faixa de produtores, não por uso individual.
|
||||
|
||||
### "Funciona para qualquer commodity?"
|
||||
Para soja, café, cacau, gado, óleo de palma, madeira e borracha — que são as commodities reguladas pelo EUDR.
|
||||
|
||||
### "E se o regulamento mudar?"
|
||||
Atualizamos o sistema automaticamente. Novos documentos podem ser adicionados, tipos de validação ajustados — tudo sem custo extra dentro do plano.
|
||||
|
||||
---
|
||||
|
||||
## 14. Diferenciais Competitivos
|
||||
|
||||
| Diferencial | DocuAgro | Concorrência |
|
||||
|------------|----------|--------------|
|
||||
| **Canal** | Telegram (leve, gratuito, rural-friendly) | Apps próprios (pesados, pagos) |
|
||||
| **IA** | GPT-4o-mini (conversa natural) | Formulários rígidos |
|
||||
| **Linguagem** | Fala como produtor | Linguagem técnica/jurídica |
|
||||
| **OCR** | Automático com Tesseract.js | Manual ou inexistente |
|
||||
| **Dossiê** | PDF profissional automático | Manual/Excel |
|
||||
| **Custo** | A partir de R$ 497/mês | R$ 5.000+ setup + mensalidade |
|
||||
| **Setup** | 24 horas | Semanas/meses |
|
||||
| **Piloto grátis** | 30 dias | Não oferecem |
|
||||
|
||||
### Pitch de elevador (30 segundos):
|
||||
> "O DocuAgro resolve o maior gargalo do EUDR: fazer o produtor rural individual enviar a documentação. A gente usa um bot no Telegram com inteligência artificial que conversa com o produtor na linguagem dele, coleta os 8 documentos obrigatórios, valida tudo automaticamente e gera um dossiê PDF profissional. Pra cooperativa, é um painel web em tempo real com 100% de visibilidade. Piloto grátis de 30 dias."
|
||||
|
||||
---
|
||||
|
||||
## 15. Roadmap / Próximos Passos
|
||||
|
||||
### Curto prazo (1-3 meses):
|
||||
- [ ] Deploy em produção (servidor DigitalOcean)
|
||||
- [ ] Autenticação JWT no painel
|
||||
- [ ] HTTPS end-to-end
|
||||
- [ ] Primeiro piloto com cooperativa real
|
||||
- [ ] Suporte a múltiplas cooperativas (multi-tenant)
|
||||
|
||||
### Médio prazo (3-6 meses):
|
||||
- [ ] Suporte a WhatsApp (via WhatsApp Business API)
|
||||
- [ ] Notificações automáticas (lembrar produtor de docs pendentes)
|
||||
- [ ] Validação avançada com IA de visão (GPT-4o) para documentos
|
||||
- [ ] Dashboard analytics (gráficos, tendências, relatórios)
|
||||
- [ ] App mobile para cooperativa
|
||||
|
||||
### Longo prazo (6-12 meses):
|
||||
- [ ] Integração direta com SICAR, INCRA, Receita Federal
|
||||
- [ ] Geolocalização automática via satélite
|
||||
- [ ] Monitoramento de desmatamento em tempo real
|
||||
- [ ] Certificação blockchain de compliance
|
||||
- [ ] Expansão internacional (Colômbia, Indonésia, Costa do Marfim)
|
||||
|
||||
---
|
||||
|
||||
## 📞 Contato
|
||||
|
||||
- **Site:** docuagro.com.br
|
||||
- **Bot:** @docuagro_bot (Telegram)
|
||||
- **Email:** m171c0@gmail.com
|
||||
|
||||
---
|
||||
|
||||
*DocuAgro — Compliance do produtor, na palma da mão. 🌱*
|
||||
BIN
docs/guia-completo-vendas.pdf
Normal file
BIN
docs/guia-completo-vendas.pdf
Normal file
Binary file not shown.
769
docs/manual-docuagro.html
Normal file
769
docs/manual-docuagro.html
Normal file
@@ -0,0 +1,769 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="pt-BR">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>DocuAgro - Manual Completo</title>
|
||||
<style>
|
||||
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800&display=swap');
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||
body { font-family: 'Inter', sans-serif; color: #1a1a2e; line-height: 1.7; background: #fff; }
|
||||
.page { page-break-after: always; min-height: 100vh; position: relative; }
|
||||
.page:last-child { page-break-after: avoid; }
|
||||
|
||||
/* CAPA */
|
||||
.capa { background: linear-gradient(135deg, #1B5E20 0%, #2E7D32 40%, #43A047 100%); color: white; display: flex; flex-direction: column; justify-content: center; align-items: center; text-align: center; padding: 60px 80px; }
|
||||
.capa h1 { font-size: 52px; font-weight: 800; margin-bottom: 8px; }
|
||||
.capa .tagline { font-size: 20px; font-weight: 300; opacity: 0.9; margin-bottom: 10px; }
|
||||
.capa .tipo-doc { font-size: 28px; font-weight: 600; background: rgba(255,255,255,0.15); padding: 14px 40px; border-radius: 12px; margin: 30px 0; }
|
||||
.capa .info { font-size: 14px; opacity: 0.7; margin-top: 30px; }
|
||||
|
||||
/* INTERNAS */
|
||||
.interna { padding: 50px 60px; }
|
||||
.header-bar { display: flex; justify-content: space-between; align-items: center; border-bottom: 3px solid #2E7D32; padding-bottom: 10px; margin-bottom: 30px; }
|
||||
.header-bar h2 { font-size: 26px; font-weight: 700; color: #1B5E20; }
|
||||
.header-bar .brand { font-size: 13px; color: #2E7D32; font-weight: 600; }
|
||||
.header-bar .pg { font-size: 12px; color: #999; }
|
||||
|
||||
h3 { font-size: 18px; font-weight: 700; color: #2E7D32; margin: 25px 0 12px; }
|
||||
h4 { font-size: 15px; font-weight: 600; color: #333; margin: 18px 0 8px; }
|
||||
p { font-size: 13.5px; color: #333; margin-bottom: 10px; }
|
||||
ul, ol { padding-left: 22px; margin: 8px 0 14px; }
|
||||
li { font-size: 13.5px; color: #333; margin-bottom: 5px; }
|
||||
|
||||
table { width: 100%; border-collapse: collapse; margin: 12px 0; font-size: 12.5px; }
|
||||
thead th { background: #1B5E20; color: white; padding: 8px 12px; text-align: left; font-weight: 600; text-transform: uppercase; letter-spacing: 0.5px; font-size: 11px; }
|
||||
tbody td { padding: 8px 12px; border-bottom: 1px solid #e8e8e8; }
|
||||
tbody tr:nth-child(even) { background: #f9fdf9; }
|
||||
|
||||
.box { background: #f1f8e9; border: 1px solid #c8e6c9; border-radius: 10px; padding: 16px 22px; margin: 14px 0; }
|
||||
.box.warning { background: #fff8e1; border-color: #ffe082; }
|
||||
.box.info { background: #e3f2fd; border-color: #90caf9; }
|
||||
.box h4 { margin-top: 0; color: #1B5E20; }
|
||||
.box.warning h4 { color: #e65100; }
|
||||
.box.info h4 { color: #0d47a1; }
|
||||
|
||||
.cmd { background: #263238; color: #a5d6a7; padding: 2px 8px; border-radius: 4px; font-family: 'Courier New', monospace; font-size: 12.5px; font-weight: 600; }
|
||||
.code-block { background: #263238; color: #e0e0e0; padding: 16px 22px; border-radius: 10px; font-family: 'Courier New', monospace; font-size: 12px; line-height: 1.8; margin: 12px 0; overflow-x: auto; }
|
||||
.code-block .comment { color: #81c784; }
|
||||
|
||||
.chat { background: #f5f5f5; border-radius: 12px; padding: 16px 20px; margin: 12px 0; }
|
||||
.chat .msg { margin-bottom: 10px; font-size: 13px; }
|
||||
.chat .msg.user { padding-left: 20px; }
|
||||
.chat .msg.user::before { content: "👤 Produtor: "; font-weight: 700; color: #333; }
|
||||
.chat .msg.bot::before { content: "🤖 DocuAgro: "; font-weight: 700; color: #2E7D32; }
|
||||
.chat .msg.action { color: #666; font-style: italic; text-align: center; font-size: 12px; }
|
||||
|
||||
.step-grid { display: grid; grid-template-columns: 50px 1fr; gap: 8px 16px; margin: 14px 0; }
|
||||
.step-num { width: 36px; height: 36px; background: #2E7D32; color: white; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-weight: 700; font-size: 16px; }
|
||||
.step-content { padding-top: 6px; }
|
||||
.step-content strong { color: #1B5E20; }
|
||||
|
||||
.footer { position: absolute; bottom: 25px; left: 60px; right: 60px; display: flex; justify-content: space-between; font-size: 9px; color: #bbb; border-top: 1px solid #eee; padding-top: 8px; }
|
||||
|
||||
.badge { display: inline-block; background: #e8f5e9; color: #1B5E20; padding: 3px 12px; border-radius: 15px; font-size: 11px; font-weight: 600; border: 1px solid #c8e6c9; margin: 2px; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<!-- CAPA -->
|
||||
<div class="page capa">
|
||||
<div style="font-size:72px; margin-bottom:10px;">🌱</div>
|
||||
<h1>DocuAgro</h1>
|
||||
<div class="tagline">Compliance do produtor, na palma da mão.</div>
|
||||
<div class="tipo-doc">📘 Manual Completo</div>
|
||||
<p style="max-width:500px; font-size:15px; opacity:0.85; line-height:1.6;">
|
||||
Guia completo de uso da plataforma DocuAgro para produtores rurais e cooperativas.
|
||||
Compliance EUDR via Bot Telegram + Inteligência Artificial + Painel Web.
|
||||
</p>
|
||||
<div class="info">AI Vertice • Versão 1.0 • Fevereiro 2026</div>
|
||||
</div>
|
||||
|
||||
<!-- SUMÁRIO -->
|
||||
<div class="page interna">
|
||||
<div class="header-bar">
|
||||
<span class="brand">🌱 DocuAgro</span>
|
||||
<h2>Sumário</h2>
|
||||
<span class="pg">02</span>
|
||||
</div>
|
||||
|
||||
<div style="margin-top:20px;">
|
||||
<table>
|
||||
<tbody>
|
||||
<tr><td style="width:40px; font-weight:700; color:#2E7D32;">01</td><td><strong>O que é o DocuAgro</strong></td><td style="text-align:right; color:#999;">Pág. 03</td></tr>
|
||||
<tr><td style="font-weight:700; color:#2E7D32;">02</td><td><strong>Para quem é</strong></td><td style="text-align:right; color:#999;">Pág. 03</td></tr>
|
||||
<tr><td style="font-weight:700; color:#2E7D32;">03</td><td><strong>Guia do Produtor — Bot Telegram</strong></td><td style="text-align:right; color:#999;">Pág. 04</td></tr>
|
||||
<tr><td style="font-weight:700; color:#2E7D32;">04</td><td><strong>Exemplo Completo de Uso</strong></td><td style="text-align:right; color:#999;">Pág. 06</td></tr>
|
||||
<tr><td style="font-weight:700; color:#2E7D32;">05</td><td><strong>Documentos Coletados (8)</strong></td><td style="text-align:right; color:#999;">Pág. 07</td></tr>
|
||||
<tr><td style="font-weight:700; color:#2E7D32;">06</td><td><strong>Inteligência Artificial</strong></td><td style="text-align:right; color:#999;">Pág. 08</td></tr>
|
||||
<tr><td style="font-weight:700; color:#2E7D32;">07</td><td><strong>Guia da Cooperativa — Painel Web</strong></td><td style="text-align:right; color:#999;">Pág. 09</td></tr>
|
||||
<tr><td style="font-weight:700; color:#2E7D32;">08</td><td><strong>API REST</strong></td><td style="text-align:right; color:#999;">Pág. 10</td></tr>
|
||||
<tr><td style="font-weight:700; color:#2E7D32;">09</td><td><strong>Dossiê PDF</strong></td><td style="text-align:right; color:#999;">Pág. 11</td></tr>
|
||||
<tr><td style="font-weight:700; color:#2E7D32;">10</td><td><strong>Instalação e Configuração</strong></td><td style="text-align:right; color:#999;">Pág. 12</td></tr>
|
||||
<tr><td style="font-weight:700; color:#2E7D32;">11</td><td><strong>Arquitetura Técnica</strong></td><td style="text-align:right; color:#999;">Pág. 13</td></tr>
|
||||
<tr><td style="font-weight:700; color:#2E7D32;">12</td><td><strong>FAQ e Troubleshooting</strong></td><td style="text-align:right; color:#999;">Pág. 14</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
</div>
|
||||
|
||||
<div class="footer"><span>DocuAgro — Manual Completo</span><span>AI Vertice</span><span>Página 2</span></div>
|
||||
</div>
|
||||
|
||||
<!-- PÁG 3 — O QUE É + PARA QUEM -->
|
||||
<div class="page interna">
|
||||
<div class="header-bar">
|
||||
<span class="brand">🌱 DocuAgro</span>
|
||||
<h2>1. O que é o DocuAgro</h2>
|
||||
<span class="pg">03</span>
|
||||
</div>
|
||||
|
||||
<p>O <strong>DocuAgro</strong> é uma plataforma que automatiza a coleta, validação e organização de documentação de produtores rurais para compliance com o <strong>EUDR</strong> (Regulamento da União Europeia contra Desmatamento — EU 2023/1115).</p>
|
||||
|
||||
<p>A plataforma combina três componentes:</p>
|
||||
<ul>
|
||||
<li><strong>Bot Telegram</strong> — onde o produtor envia seus documentos via conversa</li>
|
||||
<li><strong>Inteligência Artificial</strong> — que guia, orienta e valida os documentos</li>
|
||||
<li><strong>Painel Web</strong> — onde a cooperativa acompanha o progresso de todos os produtores</li>
|
||||
</ul>
|
||||
|
||||
<div class="box">
|
||||
<h4>🇪🇺 O que é o EUDR?</h4>
|
||||
<p>O Regulamento (UE) 2023/1115 proíbe a importação na UE de commodities produzidas em áreas desmatadas após 31/12/2020. Afeta: <strong>Soja, Café, Cacau, Óleo de Palma, Madeira, Gado e Borracha</strong>. Produtores brasileiros que exportam precisam comprovar geolocalização, ausência de desmatamento e conformidade ambiental.</p>
|
||||
</div>
|
||||
|
||||
<h2 style="font-size:22px; color:#1B5E20; margin-top:30px; padding-top:20px; border-top:2px solid #e0e0e0;">2. Para quem é</h2>
|
||||
|
||||
<h3>👨🌾 Produtores Rurais</h3>
|
||||
<p>Qualquer produtor que cultive commodities afetadas pelo EUDR e exporte (direta ou indiretamente via cooperativa/trading) para a Europa. O produtor interage <strong>100% pelo Telegram</strong>, sem precisar instalar nenhum aplicativo novo.</p>
|
||||
|
||||
<h3>🏢 Cooperativas e Tradings</h3>
|
||||
<p>Organizações que precisam comprovar a conformidade de dezenas a milhares de produtores associados. Acompanham tudo pelo <strong>Painel Web</strong> com dashboard, busca, dossiês e exportação CSV.</p>
|
||||
|
||||
<h3>🎯 Mercado-Alvo</h3>
|
||||
<ul>
|
||||
<li><strong>Soja:</strong> Cooperativas do MT, PR, GO (ex: Coamo, C.Vale, Cocamar)</li>
|
||||
<li><strong>Café:</strong> Cooperativas de MG, SP, ES (ex: COOXUPÉ)</li>
|
||||
<li><strong>Gado:</strong> Frigoríficos e associações pecuárias</li>
|
||||
<li><strong>Cacau:</strong> Cooperativas da Bahia e Pará</li>
|
||||
<li><strong>Madeira:</strong> Setor florestal</li>
|
||||
</ul>
|
||||
|
||||
<div class="footer"><span>DocuAgro — Manual Completo</span><span>AI Vertice</span><span>Página 3</span></div>
|
||||
</div>
|
||||
|
||||
<!-- PÁG 4 — GUIA DO PRODUTOR -->
|
||||
<div class="page interna">
|
||||
<div class="header-bar">
|
||||
<span class="brand">🌱 DocuAgro</span>
|
||||
<h2>3. Guia do Produtor — Bot Telegram</h2>
|
||||
<span class="pg">04</span>
|
||||
</div>
|
||||
|
||||
<p>O produtor acessa o DocuAgro pelo Telegram, buscando por <strong>@docuagro_bot</strong>. Todo o processo é guiado por inteligência artificial em linguagem simples.</p>
|
||||
|
||||
<h3>📱 Comandos Disponíveis</h3>
|
||||
<table>
|
||||
<thead><tr><th>Comando</th><th>Função</th><th>Quando usar</th></tr></thead>
|
||||
<tbody>
|
||||
<tr><td><span class="cmd">/start</span></td><td>Iniciar cadastro</td><td>Primeira vez no bot</td></tr>
|
||||
<tr><td><span class="cmd">/status</span></td><td>Ver progresso</td><td>A qualquer momento</td></tr>
|
||||
<tr><td><span class="cmd">/dossie</span></td><td>Gerar dossiê PDF</td><td>Após enviar documentos</td></tr>
|
||||
<tr><td><span class="cmd">/pular</span></td><td>Pular documento</td><td>Se não tem o doc agora</td></tr>
|
||||
<tr><td><span class="cmd">/ajuda</span></td><td>Menu de ajuda</td><td>Se tiver dúvidas</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<h3>🔄 Fluxo Completo — Passo a Passo</h3>
|
||||
|
||||
<h4>Etapa 1: Cadastro (Onboarding)</h4>
|
||||
<p>Ao enviar <span class="cmd">/start</span>, a IA faz perguntas simples para conhecer o produtor:</p>
|
||||
|
||||
<div class="step-grid">
|
||||
<div><div class="step-num">1</div></div>
|
||||
<div class="step-content"><strong>Nome completo</strong> — "Qual é o seu nome completo?"</div>
|
||||
<div><div class="step-num">2</div></div>
|
||||
<div class="step-content"><strong>CPF</strong> — Para identificação no sistema</div>
|
||||
<div><div class="step-num">3</div></div>
|
||||
<div class="step-content"><strong>Nome da propriedade</strong> — Ex: "Fazenda Boa Vista"</div>
|
||||
<div><div class="step-num">4</div></div>
|
||||
<div class="step-content"><strong>Município e estado</strong> — Ex: "Sorriso, MT"</div>
|
||||
<div><div class="step-num">5</div></div>
|
||||
<div class="step-content"><strong>Área em hectares</strong> — Aproximado é suficiente</div>
|
||||
<div><div class="step-num">6</div></div>
|
||||
<div class="step-content"><strong>Cultura principal</strong> — Soja, café, gado, etc.</div>
|
||||
</div>
|
||||
|
||||
<h4>Etapa 2: Coleta de Documentos</h4>
|
||||
<p>Após o cadastro, a IA pede <strong>um documento por vez</strong>, na seguinte ordem:</p>
|
||||
<ol>
|
||||
<li>CAR (Cadastro Ambiental Rural)</li>
|
||||
<li>CCIR (Certificado de Cadastro de Imóvel Rural)</li>
|
||||
<li>ITR (Imposto Territorial Rural)</li>
|
||||
<li>Georreferenciamento (coordenadas da propriedade)</li>
|
||||
<li>Licença Ambiental</li>
|
||||
<li>Contrato de Arrendamento (se aplicável)</li>
|
||||
<li>Nota Fiscal de Venda</li>
|
||||
<li>Declaração de Não Desmatamento (gerada automaticamente)</li>
|
||||
</ol>
|
||||
|
||||
<div class="box">
|
||||
<h4>📸 Dicas para enviar documentos</h4>
|
||||
<ul>
|
||||
<li>Tire foto em lugar com <strong>boa iluminação</strong></li>
|
||||
<li>Enquadre o <strong>documento inteiro</strong> na foto</li>
|
||||
<li><strong>PDF é melhor</strong> que foto (mais legível)</li>
|
||||
<li>Envie <strong>um documento por vez</strong></li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<h4>Etapa 3: Dossiê</h4>
|
||||
<p>Quando todos os documentos forem enviados, o produtor pode gerar o dossiê com <span class="cmd">/dossie</span>. O bot gera um PDF profissional e envia direto no chat.</p>
|
||||
|
||||
<div class="footer"><span>DocuAgro — Manual Completo</span><span>AI Vertice</span><span>Página 4</span></div>
|
||||
</div>
|
||||
|
||||
<!-- PÁG 5 — STATUS E NAVEGAÇÃO -->
|
||||
<div class="page interna">
|
||||
<div class="header-bar">
|
||||
<span class="brand">🌱 DocuAgro</span>
|
||||
<h2>3. Guia do Produtor (continuação)</h2>
|
||||
<span class="pg">05</span>
|
||||
</div>
|
||||
|
||||
<h3>📊 Verificando o Status</h3>
|
||||
<p>A qualquer momento, o produtor pode enviar <span class="cmd">/status</span> para ver como está sua documentação:</p>
|
||||
|
||||
<div class="chat">
|
||||
<div class="msg bot">📊 <strong>Status da sua documentação</strong><br><br>
|
||||
👤 João da Silva<br>
|
||||
🏡 Fazenda Boa Vista<br><br>
|
||||
✅ CAR (Cadastro Ambiental Rural)<br>
|
||||
✅ CCIR (Certificado do INCRA)<br>
|
||||
🟡 ITR (Imposto Territorial Rural)<br>
|
||||
⬜ Georreferenciamento<br>
|
||||
⬜ Licença Ambiental<br>
|
||||
⬜ Contrato de Arrendamento<br>
|
||||
⬜ Nota Fiscal de Venda<br>
|
||||
⬜ Declaração de Não Desmatamento<br><br>
|
||||
📈 Progresso: 2/8
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<p><strong>Legenda dos ícones:</strong></p>
|
||||
<table>
|
||||
<thead><tr><th>Ícone</th><th>Significado</th></tr></thead>
|
||||
<tbody>
|
||||
<tr><td>✅</td><td>Documento aprovado</td></tr>
|
||||
<tr><td>🟡</td><td>Enviado, em análise</td></tr>
|
||||
<tr><td>❌</td><td>Rejeitado (precisa reenviar)</td></tr>
|
||||
<tr><td>⚠️</td><td>Vencido</td></tr>
|
||||
<tr><td>⬜</td><td>Não enviado ainda</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<h3>⏭️ Pulando um Documento</h3>
|
||||
<p>Se o produtor não tem um documento no momento, pode usar <span class="cmd">/pular</span> para ir ao próximo. Ele poderá enviar o documento pulado depois.</p>
|
||||
|
||||
<div class="chat">
|
||||
<div class="msg user">/pular</div>
|
||||
<div class="msg bot">⏭️ Pulei o Georreferenciamento da Propriedade. Pode enviar depois! Agora vamos pra Licença Ambiental...</div>
|
||||
</div>
|
||||
|
||||
<h3>❓ Pedindo Ajuda</h3>
|
||||
<p>O comando <span class="cmd">/ajuda</span> mostra todos os comandos disponíveis e dicas de envio. Além disso, o produtor pode <strong>fazer qualquer pergunta em texto</strong> e a IA responderá com orientações sobre o que é cada documento e onde obtê-lo.</p>
|
||||
|
||||
<div class="chat">
|
||||
<div class="msg user">O que é o CAR?</div>
|
||||
<div class="msg bot">O CAR é o Cadastro Ambiental Rural. É feito pelo site car.gov.br. Se você já tem, é só me mandar o número do recibo ou o PDF. Se não tem, procure o escritório do meio ambiente do seu município que eles ajudam a fazer. 👍</div>
|
||||
</div>
|
||||
|
||||
<div class="footer"><span>DocuAgro — Manual Completo</span><span>AI Vertice</span><span>Página 5</span></div>
|
||||
</div>
|
||||
|
||||
<!-- PÁG 6 — EXEMPLO COMPLETO -->
|
||||
<div class="page interna">
|
||||
<div class="header-bar">
|
||||
<span class="brand">🌱 DocuAgro</span>
|
||||
<h2>4. Exemplo Completo de Uso</h2>
|
||||
<span class="pg">06</span>
|
||||
</div>
|
||||
|
||||
<p>Veja abaixo uma conversa típica entre um produtor e o DocuAgro:</p>
|
||||
|
||||
<div class="chat">
|
||||
<div class="msg user">/start</div>
|
||||
<div class="msg bot">🌱 <strong>Bem-vindo ao DocuAgro!</strong><br><br>Oi! Eu sou o DocuAgro, seu assistente para organizar a documentação da sua propriedade rural.<br><br>📋 A União Europeia criou uma nova regra (EUDR) que exige comprovação de que a produção não vem de área desmatada. Sem essa documentação, pode ter problema na hora de vender.<br><br>São 8 documentos no total. Vamos começar?<br><br>Primeiro, me conta: <strong>qual é o seu nome completo?</strong></div>
|
||||
<div class="msg user">João da Silva Ferreira</div>
|
||||
<div class="msg bot">Beleza, João! 👍 Agora me passa seu <strong>CPF</strong> (é só pra identificação no sistema).</div>
|
||||
<div class="msg user">123.456.789-00</div>
|
||||
<div class="msg bot">Certinho! Qual o <strong>nome da sua propriedade</strong>?</div>
|
||||
<div class="msg user">Fazenda Boa Vista</div>
|
||||
<div class="msg bot">Boa! Em qual <strong>município e estado</strong> fica a Fazenda Boa Vista?</div>
|
||||
<div class="msg user">Sorriso, Mato Grosso</div>
|
||||
<div class="msg bot">Sorriso, MT — anotado! Qual a <strong>área aproximada em hectares</strong>?</div>
|
||||
<div class="msg user">500 hectares</div>
|
||||
<div class="msg bot">500 ha, tá ótimo! E qual a <strong>cultura principal</strong>? (soja, café, gado...)</div>
|
||||
<div class="msg user">Soja</div>
|
||||
<div class="msg bot">Perfeito, João! Tudo anotado. Agora vamos pros documentos. 📄<br><br>O primeiro é o <strong>CAR — Cadastro Ambiental Rural</strong>. É aquele registro do SICAR (car.gov.br). Se você já tem, manda a foto ou o PDF pra mim. Se não tem, me avisa que eu explico como fazer!</div>
|
||||
<div class="msg action">[ João envia foto do CAR ]</div>
|
||||
<div class="msg bot">✅ Recebi seu CAR! Tá sendo analisado, já te aviso o resultado. 📝 Número SICAR encontrado: MT-5107248-ABC123...<br><br>Agora vamos pro próximo: o <strong>CCIR</strong> (Certificado de Cadastro de Imóvel Rural do INCRA)...</div>
|
||||
</div>
|
||||
|
||||
<div class="box info">
|
||||
<h4>💡 Observações</h4>
|
||||
<ul>
|
||||
<li>A IA extrai dados automaticamente via <strong>OCR</strong> (número do CAR, código INCRA, CPF, datas, etc.)</li>
|
||||
<li>Se a foto estiver ilegível, o bot pede para <strong>reenviar</strong> com dicas práticas</li>
|
||||
<li>O processo completo leva em média <strong>15-30 minutos</strong> se o produtor tiver todos os documentos em mãos</li>
|
||||
<li>O produtor pode parar a qualquer momento e <strong>continuar depois</strong> — o progresso é salvo</li>
|
||||
</ul>
|
||||
</div>
|
||||
|
||||
<div class="footer"><span>DocuAgro — Manual Completo</span><span>AI Vertice</span><span>Página 6</span></div>
|
||||
</div>
|
||||
|
||||
<!-- PÁG 7 — DOCUMENTOS -->
|
||||
<div class="page interna">
|
||||
<div class="header-bar">
|
||||
<span class="brand">🌱 DocuAgro</span>
|
||||
<h2>5. Documentos Coletados</h2>
|
||||
<span class="pg">07</span>
|
||||
</div>
|
||||
|
||||
<p>O DocuAgro coleta e valida <strong>8 documentos obrigatórios</strong> para compliance EUDR:</p>
|
||||
|
||||
<table>
|
||||
<thead><tr><th>#</th><th>Documento</th><th>O que é</th><th>Onde obter</th><th>Validação OCR</th></tr></thead>
|
||||
<tbody>
|
||||
<tr><td>1</td><td><strong>CAR</strong></td><td>Cadastro Ambiental Rural</td><td>car.gov.br (SICAR)</td><td>Número SICAR</td></tr>
|
||||
<tr><td>2</td><td><strong>CCIR</strong></td><td>Certificado Imóvel Rural</td><td>sncr.serpro.gov.br (INCRA)</td><td>Código INCRA</td></tr>
|
||||
<tr><td>3</td><td><strong>ITR</strong></td><td>Imposto Territorial Rural</td><td>Receita Federal</td><td>CPF/CNPJ</td></tr>
|
||||
<tr><td>4</td><td><strong>Geo</strong></td><td>Georreferenciamento</td><td>Técnico agrimensor</td><td>Coordenadas GPS</td></tr>
|
||||
<tr><td>5</td><td><strong>Licença</strong></td><td>Licença Ambiental</td><td>Órgão estadual (SEMA/IMA)</td><td>Validade + OCR</td></tr>
|
||||
<tr><td>6</td><td><strong>Contrato</strong></td><td>Contrato de Arrendamento</td><td>Cartório (se não for dono)</td><td>OCR geral</td></tr>
|
||||
<tr><td>7</td><td><strong>NF</strong></td><td>Nota Fiscal de Venda</td><td>Última NF-e ou NF produtor</td><td>CPF/CNPJ + dados</td></tr>
|
||||
<tr><td>8</td><td><strong>Declaração</strong></td><td>Não Desmatamento</td><td>Gerada automaticamente</td><td>Automática ✅</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<h3>🔍 Como a Validação Funciona</h3>
|
||||
<p>Cada documento enviado passa por um pipeline de validação:</p>
|
||||
|
||||
<ol>
|
||||
<li><strong>Recebimento:</strong> Arquivo salvo em diretório isolado do produtor</li>
|
||||
<li><strong>OCR (Tesseract.js):</strong> Extração de texto em português com análise de confiança</li>
|
||||
<li><strong>Validação por tipo:</strong> Algoritmo específico busca campos-chave:
|
||||
<ul>
|
||||
<li>CAR → procura padrão do número SICAR (XX-XXXXXXX-XXXX...)</li>
|
||||
<li>CCIR → procura menção ao INCRA + código do imóvel</li>
|
||||
<li>ITR → procura "Receita Federal" ou "ITR" + CPF/CNPJ</li>
|
||||
<li>Georreferenciamento → extrai coordenadas geográficas</li>
|
||||
<li>Licença → busca "Licença" + datas de validade</li>
|
||||
<li>NF → procura "Nota Fiscal" / "DANFE" + CPF/CNPJ</li>
|
||||
</ul>
|
||||
</li>
|
||||
<li><strong>Resultado:</strong> Aprovado ✅, Em análise 🟡, ou Reenviar ❌</li>
|
||||
<li><strong>Dados extraídos:</strong> Salvos no banco para inclusão no dossiê</li>
|
||||
</ol>
|
||||
|
||||
<div class="box warning">
|
||||
<h4>⚠️ Documento 6 — Contrato de Arrendamento</h4>
|
||||
<p>Este documento só é necessário se o produtor <strong>não for proprietário</strong> da terra. Se for dono, pode usar <span class="cmd">/pular</span> para ir ao próximo.</p>
|
||||
</div>
|
||||
|
||||
<div class="box">
|
||||
<h4>✨ Documento 8 — Declaração de Não Desmatamento</h4>
|
||||
<p>Este documento é <strong>gerado automaticamente</strong> pelo sistema. O produtor não precisa providenciar — o DocuAgro cria a autodeclaração com base nos dados cadastrados.</p>
|
||||
</div>
|
||||
|
||||
<div class="footer"><span>DocuAgro — Manual Completo</span><span>AI Vertice</span><span>Página 7</span></div>
|
||||
</div>
|
||||
|
||||
<!-- PÁG 8 — IA -->
|
||||
<div class="page interna">
|
||||
<div class="header-bar">
|
||||
<span class="brand">🌱 DocuAgro</span>
|
||||
<h2>6. Inteligência Artificial</h2>
|
||||
<span class="pg">08</span>
|
||||
</div>
|
||||
|
||||
<p>O DocuAgro utiliza o modelo <strong>GPT-4o-mini</strong> da OpenAI, configurado como um especialista em documentação agrícola brasileira e compliance EUDR.</p>
|
||||
|
||||
<h3>🧠 Como a IA funciona</h3>
|
||||
|
||||
<h4>Personalidade</h4>
|
||||
<ul>
|
||||
<li>Fala de forma <strong>simples e direta</strong>, como se conversasse com um produtor rural</li>
|
||||
<li>Usa linguagem do campo: "tá certinho", "beleza", "vamos lá"</li>
|
||||
<li>É <strong>paciente</strong> — muitos produtores não estão acostumados com tecnologia</li>
|
||||
<li>Sempre <strong>encoraja</strong>: "tá quase lá!", "mais um pouquinho e fica pronto"</li>
|
||||
<li>Respostas curtas (máx. 3-4 parágrafos)</li>
|
||||
</ul>
|
||||
|
||||
<h4>Contexto Dinâmico</h4>
|
||||
<p>A cada mensagem, a IA recebe o contexto completo do produtor:</p>
|
||||
<ul>
|
||||
<li>Dados pessoais (nome, CPF, propriedade, município)</li>
|
||||
<li>Etapa atual do processo</li>
|
||||
<li>Documentos já enviados e seus status</li>
|
||||
<li>Próximo documento esperado</li>
|
||||
<li>Histórico das últimas 10 mensagens</li>
|
||||
</ul>
|
||||
|
||||
<h4>Extração Inteligente de Dados</h4>
|
||||
<p>Durante o onboarding, a IA usa uma segunda chamada ao GPT para <strong>extrair dados estruturados</strong> das mensagens do produtor (nome, CPF, município, etc.) e salvar automaticamente no banco.</p>
|
||||
|
||||
<h4>Orientação para Obter Documentos</h4>
|
||||
<p>A IA sabe orientar o produtor sobre <strong>como e onde obter</strong> cada documento:</p>
|
||||
|
||||
<div class="chat">
|
||||
<div class="msg user">Não tenho o CCIR, como faço?</div>
|
||||
<div class="msg bot">O CCIR sai pelo site do INCRA (sncr.serpro.gov.br). Se sua propriedade já tá cadastrada, é só imprimir. Senão, precisa ir no INCRA mais perto. Quer que eu explique o passo a passo? 😊</div>
|
||||
</div>
|
||||
|
||||
<h3>🔧 Configuração Técnica</h3>
|
||||
<table>
|
||||
<thead><tr><th>Parâmetro</th><th>Valor</th><th>Motivo</th></tr></thead>
|
||||
<tbody>
|
||||
<tr><td>Modelo</td><td>gpt-4o-mini</td><td>Custo baixo, boa qualidade</td></tr>
|
||||
<tr><td>Temperature</td><td>0.7</td><td>Respostas naturais mas consistentes</td></tr>
|
||||
<tr><td>Max tokens</td><td>800</td><td>Respostas curtas e diretas</td></tr>
|
||||
<tr><td>Presence penalty</td><td>0.1</td><td>Evita repetição</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<div class="footer"><span>DocuAgro — Manual Completo</span><span>AI Vertice</span><span>Página 8</span></div>
|
||||
</div>
|
||||
|
||||
<!-- PÁG 9 — PAINEL COOPERATIVA -->
|
||||
<div class="page interna">
|
||||
<div class="header-bar">
|
||||
<span class="brand">🌱 DocuAgro</span>
|
||||
<h2>7. Guia da Cooperativa — Painel Web</h2>
|
||||
<span class="pg">09</span>
|
||||
</div>
|
||||
|
||||
<p>A cooperativa ou trading acompanha todos os produtores pelo <strong>Painel Web</strong>, acessível de qualquer navegador.</p>
|
||||
|
||||
<h3>📊 Dashboard</h3>
|
||||
<p>A tela inicial mostra 5 indicadores em tempo real:</p>
|
||||
<table>
|
||||
<thead><tr><th>Indicador</th><th>Descrição</th></tr></thead>
|
||||
<tbody>
|
||||
<tr><td>👥 Total de Produtores</td><td>Quantos produtores estão cadastrados</td></tr>
|
||||
<tr><td>✅ Completos</td><td>Produtores com todos os docs aprovados</td></tr>
|
||||
<tr><td>📋 Em Andamento</td><td>Produtores enviando documentos</td></tr>
|
||||
<tr><td>⚠️ Pendentes</td><td>Produtores que não iniciaram ou pararam</td></tr>
|
||||
<tr><td>📄 Documentos Recebidos</td><td>Total de documentos no sistema</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<p>Abaixo do dashboard há a <strong>barra de Compliance EUDR Geral</strong> — mostra o percentual de produtores em conformidade.</p>
|
||||
|
||||
<h3>👥 Lista de Produtores</h3>
|
||||
<p>Tabela com todos os produtores cadastrados, mostrando:</p>
|
||||
<ul>
|
||||
<li><strong>Nome</strong> e CPF</li>
|
||||
<li><strong>Propriedade</strong> e município/UF</li>
|
||||
<li><strong>Documentos:</strong> indicadores visuais (□ = não enviado, ■ = enviado/aprovado) — X/8</li>
|
||||
<li><strong>Status:</strong> Pendente, Em Andamento, Completo</li>
|
||||
<li><strong>Ações:</strong> botões "Ver" (detalhes) e "Dossiê" (gerar PDF)</li>
|
||||
</ul>
|
||||
|
||||
<h4>🔍 Busca</h4>
|
||||
<p>Campo de busca no topo da lista permite filtrar por <strong>nome, CPF, propriedade ou município</strong>.</p>
|
||||
|
||||
<h3>📥 Exportações</h3>
|
||||
|
||||
<h4>Dossiê PDF</h4>
|
||||
<p>Ao clicar em "Dossiê" na lista de produtores, o sistema gera um PDF profissional com:</p>
|
||||
<ul>
|
||||
<li>Capa com dados do produtor e status de compliance</li>
|
||||
<li>Página de dados pessoais e da propriedade</li>
|
||||
<li>Resumo visual de todos os documentos</li>
|
||||
<li>Detalhe de cada documento (status, dados extraídos, miniatura da imagem)</li>
|
||||
<li>Declaração de conformidade EUDR com espaço para assinatura</li>
|
||||
</ul>
|
||||
|
||||
<h4>Exportar CSV</h4>
|
||||
<p>O botão "Exportar CSV" gera uma planilha com todos os produtores — nome, CPF, propriedade, município, estado, área, cultura, status e progresso. Ideal para importar em ERPs ou planilhas.</p>
|
||||
|
||||
<div class="footer"><span>DocuAgro — Manual Completo</span><span>AI Vertice</span><span>Página 9</span></div>
|
||||
</div>
|
||||
|
||||
<!-- PÁG 10 — API REST -->
|
||||
<div class="page interna">
|
||||
<div class="header-bar">
|
||||
<span class="brand">🌱 DocuAgro</span>
|
||||
<h2>8. API REST</h2>
|
||||
<span class="pg">10</span>
|
||||
</div>
|
||||
|
||||
<p>Todos os dados do DocuAgro são acessíveis via API REST para integração com sistemas externos.</p>
|
||||
|
||||
<table>
|
||||
<thead><tr><th>Endpoint</th><th>Método</th><th>Descrição</th><th>Retorno</th></tr></thead>
|
||||
<tbody>
|
||||
<tr><td><span class="cmd">/api/health</span></td><td>GET</td><td>Health check do sistema</td><td>Status, versão, timestamp</td></tr>
|
||||
<tr><td><span class="cmd">/api/dashboard</span></td><td>GET</td><td>Estatísticas gerais</td><td>Total produtores, completos, pendentes, docs</td></tr>
|
||||
<tr><td><span class="cmd">/api/produtores</span></td><td>GET</td><td>Listar todos os produtores</td><td>Array com dados e status de cada um</td></tr>
|
||||
<tr><td><span class="cmd">/api/produtores/:id</span></td><td>GET</td><td>Detalhe de um produtor</td><td>Dados + documentos + último dossiê</td></tr>
|
||||
<tr><td><span class="cmd">/api/produtores/:id/dossie</span></td><td>POST</td><td>Gerar dossiê PDF</td><td>Caminho do arquivo gerado</td></tr>
|
||||
<tr><td><span class="cmd">/api/dossie/download/:arquivo</span></td><td>GET</td><td>Download do dossiê</td><td>Arquivo PDF</td></tr>
|
||||
<tr><td><span class="cmd">/api/exportar/csv</span></td><td>GET</td><td>Exportar todos em CSV</td><td>Arquivo CSV</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<h3>📡 Exemplos de Uso</h3>
|
||||
|
||||
<div class="code-block">
|
||||
<span class="comment"># Health check</span><br>
|
||||
curl http://localhost:3100/api/health<br><br>
|
||||
<span class="comment"># Dashboard</span><br>
|
||||
curl http://localhost:3100/api/dashboard<br><br>
|
||||
<span class="comment"># Listar produtores</span><br>
|
||||
curl http://localhost:3100/api/produtores<br><br>
|
||||
<span class="comment"># Gerar dossiê de um produtor</span><br>
|
||||
curl -X POST http://localhost:3100/api/produtores/PROD_ID/dossie<br><br>
|
||||
<span class="comment"># Exportar CSV</span><br>
|
||||
curl -o produtores.csv http://localhost:3100/api/exportar/csv
|
||||
</div>
|
||||
|
||||
<div class="box warning">
|
||||
<h4>⚠️ Segurança da API (MVP)</h4>
|
||||
<p>No MVP, a API <strong>não possui autenticação</strong>. Para produção, será necessário implementar JWT, rate limiting e HTTPS. Consulte o roadmap de segurança.</p>
|
||||
</div>
|
||||
|
||||
<div class="footer"><span>DocuAgro — Manual Completo</span><span>AI Vertice</span><span>Página 10</span></div>
|
||||
</div>
|
||||
|
||||
<!-- PÁG 11 — DOSSIÊ -->
|
||||
<div class="page interna">
|
||||
<div class="header-bar">
|
||||
<span class="brand">🌱 DocuAgro</span>
|
||||
<h2>9. Dossiê PDF</h2>
|
||||
<span class="pg">11</span>
|
||||
</div>
|
||||
|
||||
<p>O dossiê é o produto final do DocuAgro — um PDF profissional que reúne toda a documentação do produtor para fins de compliance EUDR.</p>
|
||||
|
||||
<h3>📄 Estrutura do Dossiê</h3>
|
||||
<table>
|
||||
<thead><tr><th>Seção</th><th>Conteúdo</th></tr></thead>
|
||||
<tbody>
|
||||
<tr><td><strong>Capa</strong></td><td>Logo DocuAgro, nome do produtor, propriedade, status de compliance, data</td></tr>
|
||||
<tr><td><strong>Dados do Produtor</strong></td><td>Nome, CPF, propriedade, município, estado, área, cultura, data de cadastro</td></tr>
|
||||
<tr><td><strong>Resumo dos Documentos</strong></td><td>Tabela com todos os 8 documentos, status e data de envio</td></tr>
|
||||
<tr><td><strong>Detalhes (por doc)</strong></td><td>Status, arquivo, dados extraídos pelo OCR, resultado da validação, miniatura</td></tr>
|
||||
<tr><td><strong>Declaração Final</strong></td><td>Declaração de conformidade EUDR com espaço para assinatura</td></tr>
|
||||
<tr><td><strong>Rodapé</strong></td><td>Número da página em todas as páginas</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<h3>🎨 Visual</h3>
|
||||
<p>O dossiê usa um tema profissional verde com:</p>
|
||||
<ul>
|
||||
<li>Capa com fundo verde gradiente e dados do produtor</li>
|
||||
<li>Badge de status: "✅ COMPLIANCE COMPLETO" ou "⚠️ EM ANDAMENTO"</li>
|
||||
<li>Tabela com cores alternadas e indicadores visuais por status</li>
|
||||
<li>Miniaturas das imagens dos documentos enviados</li>
|
||||
<li>Declaração formal citando o Regulamento (UE) 2023/1115</li>
|
||||
</ul>
|
||||
|
||||
<h3>📋 Geração</h3>
|
||||
<p>O dossiê pode ser gerado de 3 formas:</p>
|
||||
<ol>
|
||||
<li><strong>Pelo produtor:</strong> comando <span class="cmd">/dossie</span> no bot Telegram</li>
|
||||
<li><strong>Pela cooperativa:</strong> botão "Dossiê" no painel web</li>
|
||||
<li><strong>Via API:</strong> <span class="cmd">POST /api/produtores/:id/dossie</span></li>
|
||||
</ol>
|
||||
|
||||
<p>O PDF é gerado com <strong>PDFKit</strong> e salvo em <span class="cmd">uploads/dossies/</span>. O nome do arquivo segue o padrão: <span class="cmd">dossie_[CPF]_[data_hora].pdf</span></p>
|
||||
|
||||
<div class="footer"><span>DocuAgro — Manual Completo</span><span>AI Vertice</span><span>Página 11</span></div>
|
||||
</div>
|
||||
|
||||
<!-- PÁG 12 — INSTALAÇÃO -->
|
||||
<div class="page interna">
|
||||
<div class="header-bar">
|
||||
<span class="brand">🌱 DocuAgro</span>
|
||||
<h2>10. Instalação e Configuração</h2>
|
||||
<span class="pg">12</span>
|
||||
</div>
|
||||
|
||||
<h3>📋 Pré-requisitos</h3>
|
||||
<ul>
|
||||
<li><strong>Node.js 18+</strong></li>
|
||||
<li><strong>Token do Bot Telegram</strong> (criar com @BotFather no Telegram)</li>
|
||||
<li><strong>Chave API OpenAI</strong> (platform.openai.com)</li>
|
||||
</ul>
|
||||
|
||||
<h3>🚀 Setup</h3>
|
||||
<div class="code-block">
|
||||
<span class="comment"># 1. Clonar repositório</span><br>
|
||||
git clone http://137.184.77.7:3000/bigtux/docuagro.git<br>
|
||||
cd docuagro<br><br>
|
||||
<span class="comment"># 2. Instalar dependências</span><br>
|
||||
npm install<br><br>
|
||||
<span class="comment"># 3. Configurar variáveis de ambiente</span><br>
|
||||
cp .env.example .env<br>
|
||||
nano .env <span class="comment"># Preencher tokens</span><br><br>
|
||||
<span class="comment"># 4. Criar banco de dados</span><br>
|
||||
npm run setup<br><br>
|
||||
<span class="comment"># 5. Iniciar</span><br>
|
||||
npm start
|
||||
</div>
|
||||
|
||||
<h3>⚙️ Arquivo .env</h3>
|
||||
<div class="code-block">
|
||||
<span class="comment"># Bot Telegram</span><br>
|
||||
TELEGRAM_BOT_TOKEN=123456:ABC-DEF...<br><br>
|
||||
<span class="comment"># OpenAI API</span><br>
|
||||
OPENAI_API_KEY=sk-...<br>
|
||||
OPENAI_MODEL=gpt-4o-mini<br><br>
|
||||
<span class="comment"># Servidor</span><br>
|
||||
PORT=3100<br><br>
|
||||
<span class="comment"># Banco de dados</span><br>
|
||||
DB_PATH=./data/docuagro.db<br><br>
|
||||
<span class="comment"># Uploads</span><br>
|
||||
UPLOAD_DIR=./uploads
|
||||
</div>
|
||||
|
||||
<h3>🔄 Rodar com PM2 (Produção)</h3>
|
||||
<div class="code-block">
|
||||
<span class="comment"># Iniciar com PM2</span><br>
|
||||
npx pm2 start src/index.js --name docuagro<br><br>
|
||||
<span class="comment"># Ver logs</span><br>
|
||||
npx pm2 logs docuagro<br><br>
|
||||
<span class="comment"># Reiniciar</span><br>
|
||||
npx pm2 restart docuagro<br><br>
|
||||
<span class="comment"># Parar</span><br>
|
||||
npx pm2 stop docuagro
|
||||
</div>
|
||||
|
||||
<div class="footer"><span>DocuAgro — Manual Completo</span><span>AI Vertice</span><span>Página 12</span></div>
|
||||
</div>
|
||||
|
||||
<!-- PÁG 13 — ARQUITETURA -->
|
||||
<div class="page interna">
|
||||
<div class="header-bar">
|
||||
<span class="brand">🌱 DocuAgro</span>
|
||||
<h2>11. Arquitetura Técnica</h2>
|
||||
<span class="pg">13</span>
|
||||
</div>
|
||||
|
||||
<h3>🛠 Stack</h3>
|
||||
<div style="margin: 10px 0;">
|
||||
<span class="badge">Node.js 18+</span>
|
||||
<span class="badge">Telegraf 4.16</span>
|
||||
<span class="badge">Express.js 4</span>
|
||||
<span class="badge">OpenAI GPT-4o-mini</span>
|
||||
<span class="badge">Tesseract.js</span>
|
||||
<span class="badge">PDFKit</span>
|
||||
<span class="badge">SQLite (better-sqlite3)</span>
|
||||
<span class="badge">HTML/CSS/JS</span>
|
||||
</div>
|
||||
|
||||
<h3>📁 Estrutura de Pastas</h3>
|
||||
<div class="code-block">
|
||||
docuagro/<br>
|
||||
├── src/<br>
|
||||
│ ├── index.js <span class="comment">← Entry point (Express + Bot)</span><br>
|
||||
│ ├── setup-db.js <span class="comment">← Criação do banco SQLite</span><br>
|
||||
│ ├── bot/<br>
|
||||
│ │ └── telegram-bot.js <span class="comment">← Bot Telegram (Telegraf)</span><br>
|
||||
│ ├── api/<br>
|
||||
│ │ └── routes.js <span class="comment">← API REST (Express)</span><br>
|
||||
│ └── services/<br>
|
||||
│ ├── ai-service.js <span class="comment">← Integração OpenAI</span><br>
|
||||
│ ├── ocr-service.js <span class="comment">← OCR (Tesseract.js)</span><br>
|
||||
│ ├── pdf-service.js <span class="comment">← Geração de dossiê PDF</span><br>
|
||||
│ ├── database.js <span class="comment">← Operações SQLite</span><br>
|
||||
│ └── system-prompt.js <span class="comment">← Prompt EUDR da IA</span><br>
|
||||
├── public/ <span class="comment">← Painel web (HTML/CSS/JS)</span><br>
|
||||
├── data/ <span class="comment">← Banco SQLite</span><br>
|
||||
├── uploads/ <span class="comment">← Documentos dos produtores</span><br>
|
||||
├── logos/ <span class="comment">← Logo oficial</span><br>
|
||||
└── docs/ <span class="comment">← Documentação e manuais</span>
|
||||
</div>
|
||||
|
||||
<h3>💾 Banco de Dados (SQLite)</h3>
|
||||
<table>
|
||||
<thead><tr><th>Tabela</th><th>Função</th><th>Campos principais</th></tr></thead>
|
||||
<tbody>
|
||||
<tr><td><strong>cooperativas</strong></td><td>Cooperativas cadastradas</td><td>id, nome, cnpj, contato</td></tr>
|
||||
<tr><td><strong>produtores</strong></td><td>Produtores rurais</td><td>id, nome, cpf, propriedade, município, cultura, etapa_atual, status</td></tr>
|
||||
<tr><td><strong>documentos</strong></td><td>Documentos enviados</td><td>id, produtor_id, tipo, status, arquivo_path, dados_extraidos</td></tr>
|
||||
<tr><td><strong>conversas</strong></td><td>Histórico de mensagens</td><td>id, produtor_id, role, conteudo, timestamp</td></tr>
|
||||
<tr><td><strong>dossies</strong></td><td>Dossiês gerados</td><td>id, produtor_id, arquivo_path, docs_incluidos</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<h3>🔐 Segurança</h3>
|
||||
<ul>
|
||||
<li>Cada produtor tem <strong>diretório isolado</strong> de uploads</li>
|
||||
<li><strong>Helmet</strong> + <strong>CORS</strong> configurados no Express</li>
|
||||
<li>Armazenamento <strong>local</strong> (sem cloud pública)</li>
|
||||
<li><strong>Roadmap:</strong> JWT, HTTPS, rate limiting, criptografia, backups</li>
|
||||
</ul>
|
||||
|
||||
<div class="footer"><span>DocuAgro — Manual Completo</span><span>AI Vertice</span><span>Página 13</span></div>
|
||||
</div>
|
||||
|
||||
<!-- PÁG 14 — FAQ -->
|
||||
<div class="page interna">
|
||||
<div class="header-bar">
|
||||
<span class="brand">🌱 DocuAgro</span>
|
||||
<h2>12. FAQ e Troubleshooting</h2>
|
||||
<span class="pg">14</span>
|
||||
</div>
|
||||
|
||||
<h3>❓ Perguntas Frequentes</h3>
|
||||
|
||||
<h4>O produtor precisa instalar algum app?</h4>
|
||||
<p>Não. O DocuAgro funciona 100% pelo <strong>Telegram</strong>, que a maioria já tem instalado.</p>
|
||||
|
||||
<h4>Funciona pelo WhatsApp?</h4>
|
||||
<p>No momento não. O WhatsApp está no roadmap (v2.0). Atualmente funciona apenas pelo Telegram.</p>
|
||||
|
||||
<h4>Quanto custa para o produtor?</h4>
|
||||
<p>O modelo é <strong>B2B</strong> — a cooperativa paga e oferece o serviço aos seus associados. O produtor não paga nada.</p>
|
||||
|
||||
<h4>Os documentos ficam seguros?</h4>
|
||||
<p>Sim. São armazenados em servidor próprio, em diretórios isolados por produtor. Não são enviados para cloud pública.</p>
|
||||
|
||||
<h4>E se o produtor parar no meio?</h4>
|
||||
<p>O progresso é salvo automaticamente. Ele pode voltar a qualquer momento e continuar de onde parou.</p>
|
||||
|
||||
<h4>A IA pode errar na validação?</h4>
|
||||
<p>O OCR faz validação inicial automatizada. Documentos com confiança baixa são marcados como "Em análise" para verificação manual pela cooperativa.</p>
|
||||
|
||||
<h4>Quantos produtores o sistema aguenta?</h4>
|
||||
<p>O SQLite suporta até centenas de milhares de registros. Para escala maior (10k+ produtores simultâneos), recomenda-se migrar para PostgreSQL.</p>
|
||||
|
||||
<h3>🔧 Troubleshooting</h3>
|
||||
|
||||
<table>
|
||||
<thead><tr><th>Problema</th><th>Solução</th></tr></thead>
|
||||
<tbody>
|
||||
<tr><td>Bot não responde</td><td>Verificar se o processo está rodando: <span class="cmd">npx pm2 status</span></td></tr>
|
||||
<tr><td>Erro 409 (Conflict)</td><td>Duas instâncias rodando. Matar todas e reiniciar: <span class="cmd">npx pm2 delete docuagro && npx pm2 start src/index.js --name docuagro</span></td></tr>
|
||||
<tr><td>Token inválido (401)</td><td>Verificar TELEGRAM_BOT_TOKEN no arquivo .env</td></tr>
|
||||
<tr><td>IA não responde</td><td>Verificar OPENAI_API_KEY e saldo da conta OpenAI</td></tr>
|
||||
<tr><td>OCR com confiança baixa</td><td>Pedir ao produtor foto com melhor iluminação e enquadramento</td></tr>
|
||||
<tr><td>Painel não carrega</td><td>Verificar se a porta 3100 está acessível: <span class="cmd">curl localhost:3100/api/health</span></td></tr>
|
||||
<tr><td>Banco corrompido</td><td>Fazer backup e recriar: <span class="cmd">npm run setup</span></td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<div style="text-align:center; margin-top:40px; padding-top:20px; border-top:2px solid #e0e0e0;">
|
||||
<p style="font-size:22px; font-weight:700; color:#1B5E20;">🌱 DocuAgro</p>
|
||||
<p style="font-size:14px; color:#666;">Compliance do produtor, na palma da mão.</p>
|
||||
<p style="font-size:12px; color:#999; margin-top:12px;">AI Vertice • aivertice.com • 2026</p>
|
||||
<p style="font-size:11px; color:#bbb; margin-top:8px;">Dúvidas? Suporte técnico disponível.</p>
|
||||
</div>
|
||||
|
||||
<div class="footer"><span>DocuAgro — Manual Completo</span><span>AI Vertice</span><span>Página 14</span></div>
|
||||
</div>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
BIN
docs/manual-docuagro.pdf
Normal file
BIN
docs/manual-docuagro.pdf
Normal file
Binary file not shown.
690
docs/pitch-deck-docuagro.html
Normal file
690
docs/pitch-deck-docuagro.html
Normal file
@@ -0,0 +1,690 @@
|
||||
<!DOCTYPE html>
|
||||
<html lang="pt-BR">
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>DocuAgro - Pitch Deck</title>
|
||||
<style>
|
||||
@import url('https://fonts.googleapis.com/css2?family=Inter:wght@300;400;500;600;700;800;900&display=swap');
|
||||
* { margin: 0; padding: 0; box-sizing: border-box; }
|
||||
body { font-family: 'Inter', sans-serif; color: #1a1a2e; background: #fff; }
|
||||
.slide { page-break-after: always; min-height: 100vh; position: relative; padding: 60px 70px; display: flex; flex-direction: column; }
|
||||
.slide:last-child { page-break-after: avoid; }
|
||||
|
||||
/* CAPA */
|
||||
.capa { background: linear-gradient(135deg, #0d3311 0%, #1B5E20 30%, #2E7D32 60%, #43A047 100%); color: white; justify-content: center; align-items: center; text-align: center; }
|
||||
.capa .logo-icon { font-size: 80px; margin-bottom: 15px; }
|
||||
.capa h1 { font-size: 64px; font-weight: 900; letter-spacing: -2px; }
|
||||
.capa .tagline { font-size: 22px; font-weight: 300; opacity: 0.85; margin: 8px 0 40px; }
|
||||
.capa .subtitle-box { background: rgba(255,255,255,0.12); padding: 18px 50px; border-radius: 14px; font-size: 20px; font-weight: 500; line-height: 1.6; }
|
||||
.capa .bottom-info { position: absolute; bottom: 40px; font-size: 13px; opacity: 0.5; }
|
||||
|
||||
/* SLIDE COMUM */
|
||||
.slide-header { margin-bottom: 35px; }
|
||||
.slide-header .slide-num { font-size: 12px; color: #2E7D32; font-weight: 700; letter-spacing: 3px; text-transform: uppercase; margin-bottom: 8px; }
|
||||
.slide-header h2 { font-size: 36px; font-weight: 800; color: #1B5E20; line-height: 1.2; }
|
||||
.slide-header .subtitle { font-size: 16px; color: #666; margin-top: 8px; font-weight: 400; }
|
||||
|
||||
h3 { font-size: 20px; font-weight: 700; color: #2E7D32; margin: 22px 0 12px; }
|
||||
p { font-size: 15px; color: #444; line-height: 1.7; margin-bottom: 12px; }
|
||||
ul { padding-left: 22px; margin: 8px 0 14px; }
|
||||
li { font-size: 14.5px; color: #444; margin-bottom: 6px; line-height: 1.6; }
|
||||
|
||||
/* CARDS */
|
||||
.cards { display: grid; gap: 18px; margin: 16px 0; }
|
||||
.cards-2 { grid-template-columns: 1fr 1fr; }
|
||||
.cards-3 { grid-template-columns: 1fr 1fr 1fr; }
|
||||
.cards-4 { grid-template-columns: 1fr 1fr 1fr 1fr; }
|
||||
.card { background: #f8faf8; border: 1px solid #e0e8e0; border-radius: 14px; padding: 22px 24px; }
|
||||
.card.green { border-left: 5px solid #2E7D32; }
|
||||
.card.red { border-left: 5px solid #d32f2f; background: #fef9f9; border-color: #f5dede; }
|
||||
.card.orange { border-left: 5px solid #f57c00; background: #fffaf5; border-color: #ffe0b2; }
|
||||
.card.blue { border-left: 5px solid #1565c0; background: #f5f9ff; border-color: #bbdefb; }
|
||||
.card .card-icon { font-size: 32px; margin-bottom: 10px; }
|
||||
.card .card-title { font-size: 16px; font-weight: 700; color: #1B5E20; margin-bottom: 6px; }
|
||||
.card.red .card-title { color: #c62828; }
|
||||
.card.orange .card-title { color: #e65100; }
|
||||
.card.blue .card-title { color: #0d47a1; }
|
||||
.card .card-desc { font-size: 13px; color: #555; line-height: 1.5; }
|
||||
|
||||
/* HIGHLIGHT */
|
||||
.highlight { background: linear-gradient(135deg, #e8f5e9, #f1f8e9); border: 2px solid #a5d6a7; border-radius: 14px; padding: 24px 30px; margin: 18px 0; }
|
||||
.highlight.big { text-align: center; padding: 30px 40px; }
|
||||
.highlight .big-number { font-size: 48px; font-weight: 900; color: #1B5E20; }
|
||||
.highlight .big-label { font-size: 16px; color: #333; margin-top: 4px; }
|
||||
|
||||
/* TABELA */
|
||||
table { width: 100%; border-collapse: collapse; margin: 14px 0; }
|
||||
thead th { background: #1B5E20; color: white; padding: 12px 16px; text-align: left; font-size: 13px; font-weight: 600; text-transform: uppercase; letter-spacing: 0.5px; }
|
||||
tbody td { padding: 11px 16px; border-bottom: 1px solid #e8e8e8; font-size: 14px; color: #333; }
|
||||
tbody tr:nth-child(even) { background: #f9fdf9; }
|
||||
.price { font-size: 22px; font-weight: 800; color: #1B5E20; }
|
||||
.price-sm { font-size: 13px; color: #666; font-weight: 400; }
|
||||
|
||||
/* FLUXO */
|
||||
.flow { display: flex; align-items: flex-start; gap: 10px; margin: 20px 0; justify-content: center; }
|
||||
.flow-step { text-align: center; flex: 1; max-width: 160px; }
|
||||
.flow-step .flow-icon { font-size: 36px; margin-bottom: 6px; }
|
||||
.flow-step .flow-num { width: 30px; height: 30px; background: #2E7D32; color: white; border-radius: 50%; display: inline-flex; align-items: center; justify-content: center; font-weight: 700; font-size: 14px; margin-bottom: 6px; }
|
||||
.flow-step .flow-title { font-size: 13px; font-weight: 700; color: #1B5E20; }
|
||||
.flow-step .flow-desc { font-size: 11px; color: #777; margin-top: 3px; }
|
||||
.flow-arrow { font-size: 20px; color: #a5d6a7; margin-top: 20px; }
|
||||
|
||||
/* QUOTE */
|
||||
.quote { border-left: 4px solid #2E7D32; padding: 16px 24px; margin: 18px 0; background: #f8faf8; border-radius: 0 12px 12px 0; }
|
||||
.quote p { font-size: 18px; font-weight: 500; color: #1B5E20; font-style: italic; margin: 0; }
|
||||
.quote .author { font-size: 13px; color: #888; margin-top: 8px; font-style: normal; }
|
||||
|
||||
/* RODAPÉ */
|
||||
.slide-footer { position: absolute; bottom: 25px; left: 70px; right: 70px; display: flex; justify-content: space-between; font-size: 10px; color: #ccc; }
|
||||
|
||||
/* BADGE */
|
||||
.badge { display: inline-block; padding: 4px 14px; border-radius: 20px; font-size: 12px; font-weight: 600; }
|
||||
.badge-green { background: #e8f5e9; color: #1B5E20; border: 1px solid #a5d6a7; }
|
||||
.badge-red { background: #ffebee; color: #c62828; border: 1px solid #ef9a9a; }
|
||||
.badge-blue { background: #e3f2fd; color: #0d47a1; border: 1px solid #90caf9; }
|
||||
|
||||
/* TIMELINE */
|
||||
.timeline { position: relative; padding-left: 35px; margin: 16px 0; }
|
||||
.timeline::before { content: ''; position: absolute; left: 10px; top: 0; bottom: 0; width: 3px; background: #c8e6c9; border-radius: 2px; }
|
||||
.tl-item { position: relative; margin-bottom: 18px; }
|
||||
.tl-item::before { content: ''; position: absolute; left: -30px; top: 4px; width: 14px; height: 14px; background: #2E7D32; border-radius: 50%; border: 3px solid #e8f5e9; }
|
||||
.tl-item .tl-title { font-size: 15px; font-weight: 700; color: #1B5E20; }
|
||||
.tl-item .tl-desc { font-size: 13px; color: #666; margin-top: 3px; }
|
||||
|
||||
/* GRID NUMEROS */
|
||||
.num-grid { display: grid; grid-template-columns: repeat(4, 1fr); gap: 16px; margin: 16px 0; }
|
||||
.num-box { text-align: center; padding: 20px 12px; background: #f8faf8; border-radius: 12px; border: 1px solid #e0e8e0; }
|
||||
.num-box .num { font-size: 36px; font-weight: 900; color: #1B5E20; }
|
||||
.num-box .label { font-size: 12px; color: #666; margin-top: 4px; }
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
|
||||
<!-- SLIDE 1 — CAPA -->
|
||||
<div class="slide capa">
|
||||
<div class="logo-icon">🌱</div>
|
||||
<h1>DocuAgro</h1>
|
||||
<div class="tagline">Compliance do produtor, na palma da mão.</div>
|
||||
<div class="subtitle-box">
|
||||
Plataforma de compliance EUDR para o agronegócio brasileiro<br>
|
||||
Bot Telegram + IA + Painel Web
|
||||
</div>
|
||||
<div class="bottom-info">AI Vertice • Pitch Confidencial • Fevereiro 2026</div>
|
||||
</div>
|
||||
|
||||
<!-- SLIDE 2 — O PROBLEMA -->
|
||||
<div class="slide">
|
||||
<div class="slide-header">
|
||||
<div class="slide-num">01 — O Problema</div>
|
||||
<h2>O EUDR vai bloquear<br>exportações brasileiras</h2>
|
||||
</div>
|
||||
|
||||
<p style="font-size:17px;">O Regulamento <strong>(UE) 2023/1115</strong> proíbe a importação na Europa de commodities produzidas em áreas desmatadas após dezembro de 2020. <strong>Sem compliance, sem exportação.</strong></p>
|
||||
|
||||
<div class="cards cards-4" style="margin-top:20px;">
|
||||
<div class="card red">
|
||||
<div class="card-icon">🫘</div>
|
||||
<div class="card-title">Soja</div>
|
||||
<div class="card-desc">Maior exportação BR para UE</div>
|
||||
</div>
|
||||
<div class="card red">
|
||||
<div class="card-icon">☕</div>
|
||||
<div class="card-title">Café</div>
|
||||
<div class="card-desc">Brasil = #1 mundial</div>
|
||||
</div>
|
||||
<div class="card red">
|
||||
<div class="card-icon">🐄</div>
|
||||
<div class="card-title">Gado</div>
|
||||
<div class="card-desc">Carne bovina, couro</div>
|
||||
</div>
|
||||
<div class="card red">
|
||||
<div class="card-icon">🌳</div>
|
||||
<div class="card-title">+ Cacau, Madeira, Borracha, Óleo de Palma</div>
|
||||
<div class="card-desc">7 commodities afetadas</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h3>😰 O desafio da "Última Milha"</h3>
|
||||
<div class="cards cards-2">
|
||||
<div class="card orange">
|
||||
<div class="card-icon">📋</div>
|
||||
<div class="card-title">8 documentos por produtor</div>
|
||||
<div class="card-desc">CAR, CCIR, ITR, Georreferenciamento, Licença Ambiental, Contrato, NF, Declaração — de CADA produtor individual.</div>
|
||||
</div>
|
||||
<div class="card orange">
|
||||
<div class="card-icon">👨🌾</div>
|
||||
<div class="card-title">Milhares de produtores</div>
|
||||
<div class="card-desc">Uma cooperativa grande tem 2.000-10.000 associados. Coletar documentos de todos manualmente? Impossível.</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="quote">
|
||||
<p>"Uma cooperativa com 1.000 produtores precisaria de 8.000 documentos coletados, validados e organizados. Manualmente, isso levaria meses e custaria uma fortuna."</p>
|
||||
</div>
|
||||
|
||||
<div class="slide-footer"><span>DocuAgro — AI Vertice</span><span>Confidencial</span></div>
|
||||
</div>
|
||||
|
||||
<!-- SLIDE 3 — O MERCADO -->
|
||||
<div class="slide">
|
||||
<div class="slide-header">
|
||||
<div class="slide-num">02 — O Mercado</div>
|
||||
<h2>Um mercado bilionário<br>que precisa de solução</h2>
|
||||
</div>
|
||||
|
||||
<div class="num-grid">
|
||||
<div class="num-box">
|
||||
<div class="num">5.7M</div>
|
||||
<div class="label">Propriedades rurais no Brasil</div>
|
||||
</div>
|
||||
<div class="num-box">
|
||||
<div class="num">1.500+</div>
|
||||
<div class="label">Cooperativas agropecuárias</div>
|
||||
</div>
|
||||
<div class="num-box">
|
||||
<div class="num">€8 bi</div>
|
||||
<div class="label">Exportações BR→UE afetadas</div>
|
||||
</div>
|
||||
<div class="num-box">
|
||||
<div class="num">2026</div>
|
||||
<div class="label">Prazo de conformidade</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h3>🎯 Mercado-Alvo Imediato</h3>
|
||||
<table>
|
||||
<thead><tr><th>Segmento</th><th>Cooperativas</th><th>Produtores</th><th>Ticket médio/mês</th></tr></thead>
|
||||
<tbody>
|
||||
<tr><td><strong>Soja</strong> (MT, PR, GO, MS)</td><td>~200</td><td>~500K</td><td>R$ 1.500 - 4.000</td></tr>
|
||||
<tr><td><strong>Café</strong> (MG, SP, ES, PR)</td><td>~150</td><td>~300K</td><td>R$ 1.000 - 3.000</td></tr>
|
||||
<tr><td><strong>Gado</strong> (MT, GO, MS, PA)</td><td>~100</td><td>~200K</td><td>R$ 1.500 - 4.000</td></tr>
|
||||
<tr><td><strong>Cacau/Madeira/Outros</strong></td><td>~100</td><td>~100K</td><td>R$ 500 - 2.000</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<div class="highlight big">
|
||||
<div class="big-number">R$ 50M+/ano</div>
|
||||
<div class="big-label">TAM (Total Addressable Market) estimado para compliance EUDR no Brasil</div>
|
||||
</div>
|
||||
|
||||
<div class="slide-footer"><span>DocuAgro — AI Vertice</span><span>Confidencial</span></div>
|
||||
</div>
|
||||
|
||||
<!-- SLIDE 4 — A SOLUÇÃO -->
|
||||
<div class="slide">
|
||||
<div class="slide-header">
|
||||
<div class="slide-num">03 — A Solução</div>
|
||||
<h2>DocuAgro: compliance EUDR<br>numa conversa de Telegram</h2>
|
||||
</div>
|
||||
|
||||
<p style="font-size:17px;">Transformamos um processo burocrático de semanas em uma <strong>conversa de 30 minutos</strong> pelo Telegram.</p>
|
||||
|
||||
<div class="flow">
|
||||
<div class="flow-step">
|
||||
<div class="flow-icon">📱</div>
|
||||
<div class="flow-num">1</div>
|
||||
<div class="flow-title">Produtor abre o Bot</div>
|
||||
<div class="flow-desc">Telegram, sem instalar nada</div>
|
||||
</div>
|
||||
<div class="flow-arrow">→</div>
|
||||
<div class="flow-step">
|
||||
<div class="flow-icon">🤖</div>
|
||||
<div class="flow-num">2</div>
|
||||
<div class="flow-title">IA guia a coleta</div>
|
||||
<div class="flow-desc">Linguagem do campo</div>
|
||||
</div>
|
||||
<div class="flow-arrow">→</div>
|
||||
<div class="flow-step">
|
||||
<div class="flow-icon">📄</div>
|
||||
<div class="flow-num">3</div>
|
||||
<div class="flow-title">Envia documentos</div>
|
||||
<div class="flow-desc">Foto ou PDF, um por vez</div>
|
||||
</div>
|
||||
<div class="flow-arrow">→</div>
|
||||
<div class="flow-step">
|
||||
<div class="flow-icon">🔍</div>
|
||||
<div class="flow-num">4</div>
|
||||
<div class="flow-title">Validação OCR</div>
|
||||
<div class="flow-desc">Automática por IA</div>
|
||||
</div>
|
||||
<div class="flow-arrow">→</div>
|
||||
<div class="flow-step">
|
||||
<div class="flow-icon">✅</div>
|
||||
<div class="flow-num">5</div>
|
||||
<div class="flow-title">Dossiê PDF</div>
|
||||
<div class="flow-desc">Pronto para auditoria</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h3>💡 Por que funciona</h3>
|
||||
<div class="cards cards-3">
|
||||
<div class="card green">
|
||||
<div class="card-icon">💬</div>
|
||||
<div class="card-title">Zero Fricção</div>
|
||||
<div class="card-desc">Via Telegram — o produtor já tem. Sem app novo, sem login complicado, sem treinamento.</div>
|
||||
</div>
|
||||
<div class="card green">
|
||||
<div class="card-icon">🧠</div>
|
||||
<div class="card-title">IA que fala "do campo"</div>
|
||||
<div class="card-desc">A inteligência artificial usa linguagem simples: "tá certinho", "beleza". Guia, orienta e valida com paciência.</div>
|
||||
</div>
|
||||
<div class="card green">
|
||||
<div class="card-icon">📊</div>
|
||||
<div class="card-title">Painel da Cooperativa</div>
|
||||
<div class="card-desc">Dashboard em tempo real. A cooperativa vê o status de cada produtor, gera dossiês e exporta relatórios.</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="quote">
|
||||
<p>"O produtor manda foto pelo Telegram. A IA valida. A cooperativa recebe o dossiê pronto. Simples assim."</p>
|
||||
</div>
|
||||
|
||||
<div class="slide-footer"><span>DocuAgro — AI Vertice</span><span>Confidencial</span></div>
|
||||
</div>
|
||||
|
||||
<!-- SLIDE 5 — PRODUTO (DETALHES) -->
|
||||
<div class="slide">
|
||||
<div class="slide-header">
|
||||
<div class="slide-num">04 — O Produto</div>
|
||||
<h2>Dois lados, uma plataforma</h2>
|
||||
</div>
|
||||
|
||||
<div class="cards cards-2" style="margin-top:10px;">
|
||||
<div class="card green" style="padding:28px;">
|
||||
<div class="card-icon">👨🌾</div>
|
||||
<div class="card-title" style="font-size:20px;">Lado do Produtor</div>
|
||||
<div class="card-desc" style="font-size:14px; margin-top:12px;">
|
||||
<strong>Bot Telegram @docuagro_bot</strong><br><br>
|
||||
✅ Cadastro guiado por IA (nome, CPF, propriedade)<br>
|
||||
✅ Coleta de 8 documentos, um por vez<br>
|
||||
✅ Orientação sobre cada documento (o que é, onde obter)<br>
|
||||
✅ Validação automática via OCR<br>
|
||||
✅ Feedback em tempo real (aprovado/reenviar)<br>
|
||||
✅ Geração de dossiê PDF no chat<br>
|
||||
✅ Pode parar e continuar depois<br><br>
|
||||
<strong>Tempo médio: 30 minutos</strong><br>
|
||||
<strong>Treinamento necessário: zero</strong>
|
||||
</div>
|
||||
</div>
|
||||
<div class="card blue" style="padding:28px;">
|
||||
<div class="card-icon">🏢</div>
|
||||
<div class="card-title" style="font-size:20px;">Lado da Cooperativa</div>
|
||||
<div class="card-desc" style="font-size:14px; margin-top:12px;">
|
||||
<strong>Painel Web (browser)</strong><br><br>
|
||||
✅ Dashboard com KPIs em tempo real<br>
|
||||
✅ % de compliance EUDR geral<br>
|
||||
✅ Lista de todos os produtores + busca<br>
|
||||
✅ Status detalhado de cada produtor<br>
|
||||
✅ Download de dossiê PDF individual<br>
|
||||
✅ Exportação CSV para ERP/planilha<br>
|
||||
✅ API REST para integração<br><br>
|
||||
<strong>Visibilidade total</strong><br>
|
||||
<strong>Sem trabalho manual</strong>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h3>📄 8 Documentos Coletados e Validados</h3>
|
||||
<div style="display:flex; flex-wrap:wrap; gap:8px; margin:10px 0;">
|
||||
<span class="badge badge-green">1. CAR (SICAR)</span>
|
||||
<span class="badge badge-green">2. CCIR (INCRA)</span>
|
||||
<span class="badge badge-green">3. ITR (Receita)</span>
|
||||
<span class="badge badge-green">4. Georreferenciamento</span>
|
||||
<span class="badge badge-green">5. Licença Ambiental</span>
|
||||
<span class="badge badge-green">6. Contrato Arrendamento</span>
|
||||
<span class="badge badge-green">7. Nota Fiscal</span>
|
||||
<span class="badge badge-blue">8. Declaração (auto-gerada)</span>
|
||||
</div>
|
||||
|
||||
<div class="slide-footer"><span>DocuAgro — AI Vertice</span><span>Confidencial</span></div>
|
||||
</div>
|
||||
|
||||
<!-- SLIDE 6 — DIFERENCIAL COMPETITIVO -->
|
||||
<div class="slide">
|
||||
<div class="slide-header">
|
||||
<div class="slide-num">05 — Diferencial</div>
|
||||
<h2>Por que DocuAgro<br>e não a concorrência?</h2>
|
||||
</div>
|
||||
|
||||
<table>
|
||||
<thead><tr><th></th><th>DocuAgro</th><th>Consultorias tradicionais</th><th>Planilhas + email</th></tr></thead>
|
||||
<tbody>
|
||||
<tr><td><strong>Canal</strong></td><td>✅ Telegram (produtor já tem)</td><td>❌ Presencial ou telefone</td><td>❌ Email (produtor não usa)</td></tr>
|
||||
<tr><td><strong>Custo por produtor</strong></td><td>✅ R$ 2-5/mês</td><td>❌ R$ 50-200/produtor</td><td>❌ R$ 20-50 (mão de obra)</td></tr>
|
||||
<tr><td><strong>Escala</strong></td><td>✅ 10.000+ produtores</td><td>❌ 50-100 por consultor</td><td>❌ 200-500 com equipe</td></tr>
|
||||
<tr><td><strong>Validação</strong></td><td>✅ OCR automático</td><td>🟡 Manual</td><td>❌ Nenhuma</td></tr>
|
||||
<tr><td><strong>Dossiê</strong></td><td>✅ PDF automático</td><td>🟡 Manual (dias)</td><td>❌ Não gera</td></tr>
|
||||
<tr><td><strong>Tempo setup</strong></td><td>✅ Mesmo dia</td><td>❌ Semanas</td><td>🟡 Dias</td></tr>
|
||||
<tr><td><strong>IA integrada</strong></td><td>✅ Guia e orienta</td><td>❌ Não</td><td>❌ Não</td></tr>
|
||||
<tr><td><strong>Dashboard</strong></td><td>✅ Tempo real</td><td>❌ Relatórios mensais</td><td>❌ Planilha manual</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<div class="highlight">
|
||||
<h3 style="margin-top:0; text-align:center;">🏆 Vantagens Únicas</h3>
|
||||
<div class="cards cards-3" style="margin-top:12px;">
|
||||
<div style="text-align:center;">
|
||||
<div style="font-size:28px;">⚡</div>
|
||||
<div style="font-weight:700; color:#1B5E20;">10x mais rápido</div>
|
||||
<div style="font-size:12px; color:#666;">30 min vs semanas</div>
|
||||
</div>
|
||||
<div style="text-align:center;">
|
||||
<div style="font-size:28px;">💰</div>
|
||||
<div style="font-weight:700; color:#1B5E20;">10x mais barato</div>
|
||||
<div style="font-size:12px; color:#666;">R$2/produtor vs R$50+</div>
|
||||
</div>
|
||||
<div style="text-align:center;">
|
||||
<div style="font-size:28px;">📈</div>
|
||||
<div style="font-weight:700; color:#1B5E20;">100x mais escalável</div>
|
||||
<div style="font-size:12px; color:#666;">10.000+ vs 100 por pessoa</div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="slide-footer"><span>DocuAgro — AI Vertice</span><span>Confidencial</span></div>
|
||||
</div>
|
||||
|
||||
<!-- SLIDE 7 — MODELO DE NEGÓCIO -->
|
||||
<div class="slide">
|
||||
<div class="slide-header">
|
||||
<div class="slide-num">06 — Modelo de Negócio</div>
|
||||
<h2>SaaS B2B — A cooperativa paga,<br>o produtor usa grátis</h2>
|
||||
</div>
|
||||
|
||||
<p style="font-size:17px;">Modelo de <strong>assinatura mensal</strong> por volume de produtores. A cooperativa fornece o DocuAgro como serviço aos seus associados.</p>
|
||||
|
||||
<table>
|
||||
<thead><tr><th>Plano</th><th>Produtores</th><th>Preço/mês</th><th>Por produtor</th><th>Inclui</th></tr></thead>
|
||||
<tbody>
|
||||
<tr>
|
||||
<td><strong>🌱 Starter</strong></td>
|
||||
<td>até 100</td>
|
||||
<td><span class="price">R$ 497</span></td>
|
||||
<td><span class="price-sm">R$ 4,97/produtor</span></td>
|
||||
<td>Bot + Painel + Dossiês</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>🌿 Pro</strong></td>
|
||||
<td>até 500</td>
|
||||
<td><span class="price">R$ 1.497</span></td>
|
||||
<td><span class="price-sm">R$ 2,99/produtor</span></td>
|
||||
<td>Tudo do Starter + API + CSV + Suporte</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>🌳 Enterprise</strong></td>
|
||||
<td>até 2.000</td>
|
||||
<td><span class="price">R$ 3.997</span></td>
|
||||
<td><span class="price-sm">R$ 2,00/produtor</span></td>
|
||||
<td>Tudo do Pro + White-label + Dedicado</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td><strong>🏔️ Custom</strong></td>
|
||||
<td>2.000+</td>
|
||||
<td><span class="price">Sob consulta</span></td>
|
||||
<td><span class="price-sm">Negociável</span></td>
|
||||
<td>Personalização total</td>
|
||||
</tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<h3>💡 Argumento de Venda</h3>
|
||||
<div class="quote">
|
||||
<p>"Quanto custa para vocês coletarem documentos de 500 produtores manualmente? 2 funcionários dedicados = R$ 8.000/mês + encargos. O DocuAgro faz por R$ 1.497 e é 10x mais rápido."</p>
|
||||
</div>
|
||||
|
||||
<h3>🔄 Estratégia de Entrada</h3>
|
||||
<div class="cards cards-3">
|
||||
<div class="card green">
|
||||
<div class="card-icon">🎁</div>
|
||||
<div class="card-title">1. Piloto Grátis</div>
|
||||
<div class="card-desc">30 dias grátis com até 50 produtores. Sem compromisso. A cooperativa testa na prática.</div>
|
||||
</div>
|
||||
<div class="card green">
|
||||
<div class="card-icon">📊</div>
|
||||
<div class="card-title">2. Apresentar Resultados</div>
|
||||
<div class="card-desc">Após 30 dias: "50 dossiês gerados, X% de compliance alcançado em 1 mês."</div>
|
||||
</div>
|
||||
<div class="card green">
|
||||
<div class="card-icon">🚀</div>
|
||||
<div class="card-title">3. Converter em Plano</div>
|
||||
<div class="card-desc">Com case real, converter para assinatura mensal. Resultados vendem sozinhos.</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="slide-footer"><span>DocuAgro — AI Vertice</span><span>Confidencial</span></div>
|
||||
</div>
|
||||
|
||||
<!-- SLIDE 8 — PROJEÇÃO FINANCEIRA -->
|
||||
<div class="slide">
|
||||
<div class="slide-header">
|
||||
<div class="slide-num">07 — Projeção Financeira</div>
|
||||
<h2>Caminho para R$ 500K/ano<br>em 12 meses</h2>
|
||||
</div>
|
||||
|
||||
<h3>📈 Projeção Conservadora (12 meses)</h3>
|
||||
<table>
|
||||
<thead><tr><th>Mês</th><th>Cooperativas</th><th>Plano médio</th><th>MRR</th><th>ARR</th></tr></thead>
|
||||
<tbody>
|
||||
<tr><td>Mês 1-3</td><td>2 (piloto)</td><td>Grátis</td><td>R$ 0</td><td>—</td></tr>
|
||||
<tr><td>Mês 4</td><td>2 → pagantes</td><td>Pro (R$ 1.497)</td><td>R$ 2.994</td><td>R$ 36K</td></tr>
|
||||
<tr><td>Mês 6</td><td>5</td><td>Mix Starter/Pro</td><td>R$ 5.985</td><td>R$ 72K</td></tr>
|
||||
<tr><td>Mês 9</td><td>10</td><td>Mix Pro/Enterprise</td><td>R$ 19.970</td><td>R$ 240K</td></tr>
|
||||
<tr><td>Mês 12</td><td>15</td><td>Mix todos</td><td>R$ 35.000</td><td><strong>R$ 420K</strong></td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<h3>💰 Estrutura de Custos</h3>
|
||||
<div class="cards cards-2">
|
||||
<div class="card green">
|
||||
<div class="card-title">Custos Fixos (~R$ 2.500/mês)</div>
|
||||
<div class="card-desc">
|
||||
• Servidor DigitalOcean: R$ 150/mês<br>
|
||||
• API OpenAI (gpt-4o-mini): R$ 500-1.500/mês<br>
|
||||
• Domínio + Cloudflare: R$ 10/mês<br>
|
||||
• Ferramentas: R$ 200/mês
|
||||
</div>
|
||||
</div>
|
||||
<div class="card green">
|
||||
<div class="card-title">Margem Bruta</div>
|
||||
<div class="card-desc" style="font-size:16px; margin-top:10px;">
|
||||
<strong>MRR R$ 35K - Custos R$ 2.5K</strong><br>
|
||||
<span style="font-size:28px; font-weight:900; color:#1B5E20;">= 93% margem</span><br><br>
|
||||
SaaS puro. Sem equipe de campo. Sem estoque. Escala com custo marginal mínimo.
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="highlight big">
|
||||
<div class="big-number">R$ 420K</div>
|
||||
<div class="big-label">ARR projetado no mês 12 (cenário conservador com 15 cooperativas)</div>
|
||||
</div>
|
||||
|
||||
<div class="slide-footer"><span>DocuAgro — AI Vertice</span><span>Confidencial</span></div>
|
||||
</div>
|
||||
|
||||
<!-- SLIDE 9 — ROADMAP -->
|
||||
<div class="slide">
|
||||
<div class="slide-header">
|
||||
<div class="slide-num">08 — Roadmap</div>
|
||||
<h2>Do MVP ao mercado</h2>
|
||||
</div>
|
||||
|
||||
<div class="timeline">
|
||||
<div class="tl-item">
|
||||
<div class="tl-title">✅ MVP Pronto (Agora)</div>
|
||||
<div class="tl-desc">Bot Telegram + IA (GPT-4o-mini) + OCR + PDF + Painel Web + API REST. Funcional e testado.</div>
|
||||
</div>
|
||||
<div class="tl-item">
|
||||
<div class="tl-title">🔜 v1.1 — Segurança (Mês 1)</div>
|
||||
<div class="tl-desc">Autenticação JWT no painel, HTTPS obrigatório, rate limiting na API.</div>
|
||||
</div>
|
||||
<div class="tl-item">
|
||||
<div class="tl-title">🔜 v1.2 — Piloto (Mês 1-3)</div>
|
||||
<div class="tl-desc">Deploy em produção. 2 cooperativas piloto (soja + café). Coleta de feedback real.</div>
|
||||
</div>
|
||||
<div class="tl-item">
|
||||
<div class="tl-title">📋 v1.5 — Notificações (Mês 4)</div>
|
||||
<div class="tl-desc">Alertas automáticos: documentos vencendo, produtores inativos, relatórios semanais para cooperativa.</div>
|
||||
</div>
|
||||
<div class="tl-item">
|
||||
<div class="tl-title">📋 v2.0 — WhatsApp (Mês 6)</div>
|
||||
<div class="tl-desc">Bot WhatsApp Business. Alcance 3x maior que Telegram no interior do Brasil.</div>
|
||||
</div>
|
||||
<div class="tl-item">
|
||||
<div class="tl-title">📋 v2.5 — Integrações Gov (Mês 9)</div>
|
||||
<div class="tl-desc">APIs do SICAR, INCRA e Receita Federal para validação cruzada automática.</div>
|
||||
</div>
|
||||
<div class="tl-item">
|
||||
<div class="tl-title">📋 v3.0 — Multi-tenant SaaS (Mês 12)</div>
|
||||
<div class="tl-desc">Cada cooperativa com ambiente isolado. Dashboard analytics avançado. White-label completo.</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="slide-footer"><span>DocuAgro — AI Vertice</span><span>Confidencial</span></div>
|
||||
</div>
|
||||
|
||||
<!-- SLIDE 10 — GO-TO-MARKET -->
|
||||
<div class="slide">
|
||||
<div class="slide-header">
|
||||
<div class="slide-num">09 — Go-to-Market</div>
|
||||
<h2>Como vamos chegar<br>nas cooperativas</h2>
|
||||
</div>
|
||||
|
||||
<div class="cards cards-2">
|
||||
<div class="card green" style="padding:24px;">
|
||||
<div class="card-icon">🎯</div>
|
||||
<div class="card-title" style="font-size:18px;">Canal Direto</div>
|
||||
<div class="card-desc" style="font-size:14px; margin-top:10px;">
|
||||
<strong>Abordagem ativa para cooperativas:</strong><br><br>
|
||||
• Contato direto com setor de compliance/exportação<br>
|
||||
• LinkedIn dos diretores de cooperativas<br>
|
||||
• Webinar gratuito: "EUDR: sua cooperativa está preparada?"<br>
|
||||
• Cold email segmentado por região e commodity<br>
|
||||
• Visitas presenciais nas cooperativas prioritárias
|
||||
</div>
|
||||
</div>
|
||||
<div class="card blue" style="padding:24px;">
|
||||
<div class="card-icon">🤝</div>
|
||||
<div class="card-title" style="font-size:18px;">Via Associações</div>
|
||||
<div class="card-desc" style="font-size:14px; margin-top:10px;">
|
||||
<strong>Parcerias estratégicas:</strong><br><br>
|
||||
• <strong>OCB</strong> (Organização das Cooperativas Brasileiras)<br>
|
||||
• <strong>CNA</strong> (Confederação da Agricultura)<br>
|
||||
• <strong>APROSOJA</strong>, <strong>CECAFÉ</strong>, associações por commodity<br>
|
||||
• Feiras: <strong>Agrishow</strong>, Show Rural, Expozebu<br>
|
||||
• Consultorias de compliance como parceiros de revenda
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<h3>📍 Prioridade Geográfica</h3>
|
||||
<table>
|
||||
<thead><tr><th>Fase</th><th>Região</th><th>Commodity</th><th>Por quê</th></tr></thead>
|
||||
<tbody>
|
||||
<tr><td><span class="badge badge-green">Fase 1</span></td><td>Mato Grosso / Paraná</td><td>Soja</td><td>Maior volume de exportação EUDR</td></tr>
|
||||
<tr><td><span class="badge badge-green">Fase 2</span></td><td>Minas Gerais / São Paulo</td><td>Café</td><td>Alta concentração de cooperativas</td></tr>
|
||||
<tr><td><span class="badge badge-blue">Fase 3</span></td><td>Goiás / Mato Grosso do Sul</td><td>Gado</td><td>Pecuária de exportação</td></tr>
|
||||
<tr><td><span class="badge badge-blue">Fase 4</span></td><td>Bahia / Pará</td><td>Cacau + Madeira</td><td>Complementar o portfólio</td></tr>
|
||||
</tbody>
|
||||
</table>
|
||||
|
||||
<div class="slide-footer"><span>DocuAgro — AI Vertice</span><span>Confidencial</span></div>
|
||||
</div>
|
||||
|
||||
<!-- SLIDE 11 — EQUIPE + TECH -->
|
||||
<div class="slide">
|
||||
<div class="slide-header">
|
||||
<div class="slide-num">10 — Tecnologia e Equipe</div>
|
||||
<h2>Stack robusta,<br>equipe enxuta</h2>
|
||||
</div>
|
||||
|
||||
<h3>🛠 Tecnologia</h3>
|
||||
<div class="cards cards-4" style="margin-bottom:20px;">
|
||||
<div class="card green">
|
||||
<div class="card-icon">🤖</div>
|
||||
<div class="card-title">IA (GPT-4o-mini)</div>
|
||||
<div class="card-desc">Guia produtores, extrai dados, valida documentos</div>
|
||||
</div>
|
||||
<div class="card green">
|
||||
<div class="card-icon">🔍</div>
|
||||
<div class="card-title">OCR (Tesseract)</div>
|
||||
<div class="card-desc">Extrai texto de fotos e PDFs automaticamente</div>
|
||||
</div>
|
||||
<div class="card green">
|
||||
<div class="card-icon">📱</div>
|
||||
<div class="card-title">Bot (Telegraf)</div>
|
||||
<div class="card-desc">Interface do produtor via Telegram</div>
|
||||
</div>
|
||||
<div class="card green">
|
||||
<div class="card-icon">📊</div>
|
||||
<div class="card-title">Painel (Express)</div>
|
||||
<div class="card-desc">Dashboard da cooperativa + API REST</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div style="display:flex; flex-wrap:wrap; gap:8px; margin:10px 0;">
|
||||
<span class="badge badge-green">Node.js 18+</span>
|
||||
<span class="badge badge-green">Telegraf 4.16</span>
|
||||
<span class="badge badge-green">Express.js</span>
|
||||
<span class="badge badge-green">OpenAI API</span>
|
||||
<span class="badge badge-green">Tesseract.js</span>
|
||||
<span class="badge badge-green">PDFKit</span>
|
||||
<span class="badge badge-green">SQLite</span>
|
||||
<span class="badge badge-green">Helmet + CORS</span>
|
||||
</div>
|
||||
|
||||
<h3>👥 Equipe</h3>
|
||||
<div class="cards cards-2">
|
||||
<div class="card blue" style="padding:24px;">
|
||||
<div class="card-title" style="font-size:18px;">Eduardo Kislanski</div>
|
||||
<div class="card-desc" style="font-size:14px; margin-top:8px;">
|
||||
<strong>Fundador & CEO</strong><br>
|
||||
Analista de Infraestrutura Cloud. Experiência com Azure DevOps, Docker, CI/CD. Responsável pela visão de produto e estratégia comercial.<br><br>
|
||||
Também fundador da <strong>AI Vertice</strong> — empresa de soluções com IA (LexMind, MetisClass, ArgusFinance, Strix).
|
||||
</div>
|
||||
</div>
|
||||
<div class="card blue" style="padding:24px;">
|
||||
<div class="card-title" style="font-size:18px;">JARVIS (IA)</div>
|
||||
<div class="card-desc" style="font-size:14px; margin-top:8px;">
|
||||
<strong>CTO & Desenvolvimento</strong><br>
|
||||
Sistema de IA proprietário que desenvolveu 100% do código do DocuAgro. Responsável por arquitetura, desenvolvimento, deploy e manutenção contínua.<br><br>
|
||||
<em>Custo de equipe de dev: R$ 0/mês</em> 🚀
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="slide-footer"><span>DocuAgro — AI Vertice</span><span>Confidencial</span></div>
|
||||
</div>
|
||||
|
||||
<!-- SLIDE 12 — CTA FINAL -->
|
||||
<div class="slide" style="background: linear-gradient(135deg, #0d3311 0%, #1B5E20 30%, #2E7D32 60%, #43A047 100%); color: white; justify-content: center; align-items: center; text-align: center;">
|
||||
|
||||
<div style="font-size:60px; margin-bottom:20px;">🌱</div>
|
||||
<h1 style="font-size:48px; font-weight:900; margin-bottom:12px; color:white;">DocuAgro</h1>
|
||||
<p style="font-size:22px; opacity:0.9; color:white; margin-bottom:40px;">Compliance do produtor, na palma da mão.</p>
|
||||
|
||||
<div style="background: rgba(255,255,255,0.12); padding: 30px 50px; border-radius: 16px; max-width: 600px; margin-bottom:40px;">
|
||||
<p style="font-size:26px; font-weight:700; color:white; margin-bottom:15px;">Próximos Passos</p>
|
||||
<p style="font-size:18px; color: rgba(255,255,255,0.9); line-height:2;">
|
||||
✅ MVP pronto e funcional<br>
|
||||
🤝 Piloto grátis para sua cooperativa<br>
|
||||
📊 Resultados em 30 dias<br>
|
||||
🚀 Escale para toda a base de produtores
|
||||
</p>
|
||||
</div>
|
||||
|
||||
<p style="font-size:18px; font-weight:600; color:rgba(255,255,255,0.95);">
|
||||
Quer ver uma demonstração ao vivo?
|
||||
</p>
|
||||
|
||||
<div style="margin-top:30px;">
|
||||
<p style="font-size:16px; color:rgba(255,255,255,0.7);">Eduardo Kislanski</p>
|
||||
<p style="font-size:14px; color:rgba(255,255,255,0.5);">AI Vertice • aivertice.com</p>
|
||||
<p style="font-size:14px; color:rgba(255,255,255,0.5);">m171c0@gmail.com • @docuagro_bot</p>
|
||||
</div>
|
||||
|
||||
<div class="slide-footer" style="color: rgba(255,255,255,0.3);"><span>DocuAgro — AI Vertice</span><span>Confidencial • Fevereiro 2026</span></div>
|
||||
</div>
|
||||
|
||||
</body>
|
||||
</html>
|
||||
BIN
docs/pitch-deck-docuagro.pdf
Normal file
BIN
docs/pitch-deck-docuagro.pdf
Normal file
Binary file not shown.
BIN
logos/001-modern-logo-icon-for-docuagro-agricultur.png
Normal file
BIN
logos/001-modern-logo-icon-for-docuagro-agricultur.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.4 MiB |
BIN
logos/001-professional-minimalist-logo-for-docuagr.png
Normal file
BIN
logos/001-professional-minimalist-logo-for-docuagr.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.3 MiB |
BIN
logos/002-modern-logo-icon-for-docuagro-agricultur.png
Normal file
BIN
logos/002-modern-logo-icon-for-docuagro-agricultur.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.3 MiB |
25
logos/index.html
Normal file
25
logos/index.html
Normal file
@@ -0,0 +1,25 @@
|
||||
<!doctype html>
|
||||
<meta charset="utf-8" />
|
||||
<title>openai-image-gen</title>
|
||||
<style>
|
||||
:root { color-scheme: dark; }
|
||||
body { margin: 24px; font: 14px/1.4 ui-sans-serif, system-ui; background: #0b0f14; color: #e8edf2; }
|
||||
h1 { font-size: 18px; margin: 0 0 16px; }
|
||||
.grid { display: grid; grid-template-columns: repeat(auto-fill, minmax(240px, 1fr)); gap: 16px; }
|
||||
figure { margin: 0; padding: 12px; border: 1px solid #1e2a36; border-radius: 14px; background: #0f1620; }
|
||||
img { width: 100%; height: auto; border-radius: 10px; display: block; }
|
||||
figcaption { margin-top: 10px; color: #b7c2cc; }
|
||||
code { color: #9cd1ff; }
|
||||
</style>
|
||||
<h1>openai-image-gen</h1>
|
||||
<p>Output: <code>/home/kernelpanic/projetos_jarvis/docuagro/logos</code></p>
|
||||
<div class="grid">
|
||||
<figure>
|
||||
<a href="001-modern-logo-icon-for-docuagro-agricultur.png"><img src="001-modern-logo-icon-for-docuagro-agricultur.png" loading="lazy" /></a>
|
||||
<figcaption>Modern logo icon for DocuAgro, agricultural technology company. Design: stylized green leaf merging with a document/checkmark. Flat vector style, minimalist. Primary color dark green on white. No text. Professional corporate logo suitable for app icon.</figcaption>
|
||||
</figure>
|
||||
<figure>
|
||||
<a href="002-modern-logo-icon-for-docuagro-agricultur.png"><img src="002-modern-logo-icon-for-docuagro-agricultur.png" loading="lazy" /></a>
|
||||
<figcaption>Modern logo icon for DocuAgro, agricultural technology company. Design: stylized green leaf merging with a document/checkmark. Flat vector style, minimalist. Primary color dark green on white. No text. Professional corporate logo suitable for app icon.</figcaption>
|
||||
</figure>
|
||||
</div>
|
||||
BIN
logos/logo-docuagro.png
Normal file
BIN
logos/logo-docuagro.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 1.3 MiB |
10
logos/prompts.json
Normal file
10
logos/prompts.json
Normal file
@@ -0,0 +1,10 @@
|
||||
[
|
||||
{
|
||||
"prompt": "Modern logo icon for DocuAgro, agricultural technology company. Design: stylized green leaf merging with a document/checkmark. Flat vector style, minimalist. Primary color dark green on white. No text. Professional corporate logo suitable for app icon.",
|
||||
"file": "001-modern-logo-icon-for-docuagro-agricultur.png"
|
||||
},
|
||||
{
|
||||
"prompt": "Modern logo icon for DocuAgro, agricultural technology company. Design: stylized green leaf merging with a document/checkmark. Flat vector style, minimalist. Primary color dark green on white. No text. Professional corporate logo suitable for app icon.",
|
||||
"file": "002-modern-logo-icon-for-docuagro-agricultur.png"
|
||||
}
|
||||
]
|
||||
3698
package-lock.json
generated
Normal file
3698
package-lock.json
generated
Normal file
File diff suppressed because it is too large
Load Diff
43
package.json
Normal file
43
package.json
Normal file
@@ -0,0 +1,43 @@
|
||||
{
|
||||
"name": "docuagro",
|
||||
"version": "1.0.0",
|
||||
"description": "DocuAgro - Bot Telegram para compliance EUDR de produtores rurais",
|
||||
"main": "src/index.js",
|
||||
"scripts": {
|
||||
"start": "node src/index.js",
|
||||
"dev": "nodemon src/index.js",
|
||||
"panel": "node src/panel-server.js",
|
||||
"setup": "node src/setup-db.js",
|
||||
"test": "node src/test.js"
|
||||
},
|
||||
"keywords": [
|
||||
"eudr",
|
||||
"compliance",
|
||||
"telegram",
|
||||
"bot",
|
||||
"agro"
|
||||
],
|
||||
"author": "BigTux",
|
||||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"bcryptjs": "^3.0.3",
|
||||
"better-sqlite3": "^11.7.0",
|
||||
"compression": "^1.7.4",
|
||||
"cors": "^2.8.5",
|
||||
"dayjs": "^1.11.13",
|
||||
"dotenv": "^16.4.5",
|
||||
"express": "^4.21.0",
|
||||
"helmet": "^8.0.0",
|
||||
"jsonwebtoken": "^9.0.3",
|
||||
"multer": "^1.4.5-lts.1",
|
||||
"openai": "^4.73.0",
|
||||
"pdfkit": "^0.15.0",
|
||||
"sharp": "^0.33.5",
|
||||
"telegraf": "^4.16.3",
|
||||
"tesseract.js": "^5.1.1",
|
||||
"uuid": "^10.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"nodemon": "^3.1.7"
|
||||
}
|
||||
}
|
||||
1098
public/index.html
Normal file
1098
public/index.html
Normal file
File diff suppressed because it is too large
Load Diff
228
src/api/routes.js
Normal file
228
src/api/routes.js
Normal file
@@ -0,0 +1,228 @@
|
||||
/**
|
||||
* DocuAgro - Rotas da API REST
|
||||
* Endpoints para o painel web da cooperativa
|
||||
* Com autenticação JWT
|
||||
*/
|
||||
|
||||
const express = require('express');
|
||||
const path = require('path');
|
||||
const fs = require('fs');
|
||||
const db = require('../services/database');
|
||||
const { gerarDossie } = require('../services/pdf-service');
|
||||
const { autenticarUsuario, verificarToken } = require('../services/auth-service');
|
||||
|
||||
const router = express.Router();
|
||||
|
||||
// ============================================================
|
||||
// MIDDLEWARE DE AUTENTICAÇÃO JWT
|
||||
// ============================================================
|
||||
function authMiddleware(req, res, next) {
|
||||
const authHeader = req.headers.authorization;
|
||||
|
||||
if (!authHeader || !authHeader.startsWith('Bearer ')) {
|
||||
return res.status(401).json({
|
||||
sucesso: false,
|
||||
erro: 'Token de autenticação não fornecido'
|
||||
});
|
||||
}
|
||||
|
||||
const token = authHeader.split(' ')[1];
|
||||
|
||||
try {
|
||||
const usuario = verificarToken(token);
|
||||
req.usuario = usuario;
|
||||
next();
|
||||
} catch (erro) {
|
||||
return res.status(401).json({
|
||||
sucesso: false,
|
||||
erro: 'Token inválido ou expirado'
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// AUTH - Login (rota pública)
|
||||
// ============================================================
|
||||
router.post('/api/auth/login', async (req, res) => {
|
||||
try {
|
||||
const { username, senha } = req.body;
|
||||
|
||||
if (!username || !senha) {
|
||||
return res.status(400).json({
|
||||
sucesso: false,
|
||||
erro: 'Username e senha são obrigatórios'
|
||||
});
|
||||
}
|
||||
|
||||
const resultado = await autenticarUsuario(username, senha);
|
||||
|
||||
res.json({
|
||||
sucesso: true,
|
||||
dados: resultado
|
||||
});
|
||||
} catch (erro) {
|
||||
console.error('❌ Erro no login:', erro.message);
|
||||
res.status(401).json({
|
||||
sucesso: false,
|
||||
erro: 'Credenciais inválidas'
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
// ============================================================
|
||||
// HEALTH CHECK (rota pública)
|
||||
// ============================================================
|
||||
router.get('/api/health', (req, res) => {
|
||||
res.json({
|
||||
status: 'ok',
|
||||
servico: 'DocuAgro API',
|
||||
versao: '1.0.0',
|
||||
timestamp: new Date().toISOString()
|
||||
});
|
||||
});
|
||||
|
||||
// ============================================================
|
||||
// PROTEGER TODAS AS ROTAS /api/ ABAIXO COM AUTH
|
||||
// ============================================================
|
||||
router.use('/api', authMiddleware);
|
||||
|
||||
// ============================================================
|
||||
// DASHBOARD - Estatísticas gerais
|
||||
// ============================================================
|
||||
router.get('/api/dashboard', (req, res) => {
|
||||
try {
|
||||
const stats = db.estatisticas();
|
||||
res.json({
|
||||
sucesso: true,
|
||||
dados: stats
|
||||
});
|
||||
} catch (erro) {
|
||||
console.error('❌ Erro no dashboard:', erro);
|
||||
res.status(500).json({ sucesso: false, erro: 'Erro ao buscar estatísticas' });
|
||||
}
|
||||
});
|
||||
|
||||
// ============================================================
|
||||
// PRODUTORES - Listagem
|
||||
// ============================================================
|
||||
router.get('/api/produtores', (req, res) => {
|
||||
try {
|
||||
const produtores = db.listarProdutores();
|
||||
res.json({
|
||||
sucesso: true,
|
||||
dados: produtores,
|
||||
total: produtores.length
|
||||
});
|
||||
} catch (erro) {
|
||||
console.error('❌ Erro ao listar produtores:', erro);
|
||||
res.status(500).json({ sucesso: false, erro: 'Erro ao listar produtores' });
|
||||
}
|
||||
});
|
||||
|
||||
// ============================================================
|
||||
// PRODUTOR - Detalhe
|
||||
// ============================================================
|
||||
router.get('/api/produtores/:id', (req, res) => {
|
||||
try {
|
||||
const produtor = db.buscarProdutorPorId(req.params.id);
|
||||
if (!produtor) {
|
||||
return res.status(404).json({ sucesso: false, erro: 'Produtor não encontrado' });
|
||||
}
|
||||
|
||||
const documentos = db.buscarDocumentos(produtor.id);
|
||||
const ultimoDossie = db.buscarUltimoDossie(produtor.id);
|
||||
|
||||
res.json({
|
||||
sucesso: true,
|
||||
dados: {
|
||||
produtor,
|
||||
documentos,
|
||||
ultimoDossie
|
||||
}
|
||||
});
|
||||
} catch (erro) {
|
||||
console.error('❌ Erro ao buscar produtor:', erro);
|
||||
res.status(500).json({ sucesso: false, erro: 'Erro ao buscar produtor' });
|
||||
}
|
||||
});
|
||||
|
||||
// ============================================================
|
||||
// DOSSIÊ - Gerar
|
||||
// ============================================================
|
||||
router.post('/api/produtores/:id/dossie', async (req, res) => {
|
||||
try {
|
||||
const produtor = db.buscarProdutorPorId(req.params.id);
|
||||
if (!produtor) {
|
||||
return res.status(404).json({ sucesso: false, erro: 'Produtor não encontrado' });
|
||||
}
|
||||
|
||||
const caminhoArquivo = await gerarDossie(produtor.id);
|
||||
|
||||
res.json({
|
||||
sucesso: true,
|
||||
dados: {
|
||||
arquivo: path.basename(caminhoArquivo),
|
||||
caminho: `/api/dossie/download/${path.basename(caminhoArquivo)}`
|
||||
}
|
||||
});
|
||||
} catch (erro) {
|
||||
console.error('❌ Erro ao gerar dossiê:', erro);
|
||||
res.status(500).json({ sucesso: false, erro: 'Erro ao gerar dossiê' });
|
||||
}
|
||||
});
|
||||
|
||||
// ============================================================
|
||||
// DOSSIÊ - Download
|
||||
// ============================================================
|
||||
router.get('/api/dossie/download/:arquivo', (req, res) => {
|
||||
try {
|
||||
const uploadDir = process.env.UPLOAD_DIR || path.join(__dirname, '..', '..', 'uploads');
|
||||
const caminhoArquivo = path.join(uploadDir, 'dossies', req.params.arquivo);
|
||||
|
||||
if (!fs.existsSync(caminhoArquivo)) {
|
||||
return res.status(404).json({ sucesso: false, erro: 'Arquivo não encontrado' });
|
||||
}
|
||||
|
||||
res.download(caminhoArquivo);
|
||||
} catch (erro) {
|
||||
console.error('❌ Erro no download:', erro);
|
||||
res.status(500).json({ sucesso: false, erro: 'Erro no download' });
|
||||
}
|
||||
});
|
||||
|
||||
// ============================================================
|
||||
// EXPORTAÇÃO CSV
|
||||
// ============================================================
|
||||
router.get('/api/exportar/csv', (req, res) => {
|
||||
try {
|
||||
const produtores = db.listarProdutores();
|
||||
|
||||
// Header CSV
|
||||
let csv = 'Nome,CPF/CNPJ,Propriedade,Município,Estado,Área (ha),Cultura,Status,Docs Aprovados,Total Docs\n';
|
||||
|
||||
for (const p of produtores) {
|
||||
csv += [
|
||||
`"${p.nome || ''}"`,
|
||||
`"${p.cpf_cnpj || ''}"`,
|
||||
`"${p.propriedade_nome || ''}"`,
|
||||
`"${p.propriedade_municipio || ''}"`,
|
||||
`"${p.propriedade_estado || ''}"`,
|
||||
p.propriedade_area_ha || '',
|
||||
`"${p.cultura_principal || ''}"`,
|
||||
p.status || '',
|
||||
p.docs_aprovados || 0,
|
||||
p.docs_total || 8
|
||||
].join(',') + '\n';
|
||||
}
|
||||
|
||||
res.setHeader('Content-Type', 'text/csv; charset=utf-8');
|
||||
res.setHeader('Content-Disposition', 'attachment; filename=docuagro_produtores.csv');
|
||||
res.send(csv);
|
||||
|
||||
} catch (erro) {
|
||||
console.error('❌ Erro na exportação CSV:', erro);
|
||||
res.status(500).json({ sucesso: false, erro: 'Erro na exportação' });
|
||||
}
|
||||
});
|
||||
|
||||
module.exports = router;
|
||||
488
src/bot/telegram-bot.js
Normal file
488
src/bot/telegram-bot.js
Normal file
@@ -0,0 +1,488 @@
|
||||
/**
|
||||
* DocuAgro - Bot Telegram
|
||||
* Núcleo do bot que interage com produtores rurais
|
||||
* Coleta documentos para compliance EUDR
|
||||
*/
|
||||
|
||||
const { Telegraf, Markup } = require('telegraf');
|
||||
const path = require('path');
|
||||
const fs = require('fs');
|
||||
const https = require('https');
|
||||
const http = require('http');
|
||||
const { v4: uuidv4 } = require('uuid');
|
||||
const db = require('../services/database');
|
||||
const ai = require('../services/ai-service');
|
||||
const ocr = require('../services/ocr-service');
|
||||
const { gerarDossie } = require('../services/pdf-service');
|
||||
const { TIPOS_DOCUMENTO, ORDEM_COLETA } = require('../services/system-prompt');
|
||||
|
||||
const UPLOAD_DIR = process.env.UPLOAD_DIR || path.join(__dirname, '..', '..', 'uploads');
|
||||
|
||||
// Garante que diretório de uploads existe
|
||||
if (!fs.existsSync(UPLOAD_DIR)) fs.mkdirSync(UPLOAD_DIR, { recursive: true });
|
||||
|
||||
/**
|
||||
* Inicializa e configura o bot Telegram
|
||||
*/
|
||||
function criarBot() {
|
||||
const bot = new Telegraf(process.env.TELEGRAM_BOT_TOKEN);
|
||||
|
||||
// ============================================================
|
||||
// COMANDO /start - Início da conversa
|
||||
// ============================================================
|
||||
bot.start(async (ctx) => {
|
||||
try {
|
||||
const chatId = ctx.chat.id;
|
||||
const username = ctx.from.username;
|
||||
const nome = [ctx.from.first_name, ctx.from.last_name].filter(Boolean).join(' ');
|
||||
|
||||
console.log(`🆕 Novo usuário: ${nome} (@${username}) - Chat: ${chatId}`);
|
||||
|
||||
// Verifica se já tem cadastro
|
||||
let produtor = db.buscarProdutorPorChat(chatId);
|
||||
|
||||
if (produtor) {
|
||||
// Já cadastrado - mostra status
|
||||
const docs = db.buscarDocumentos(produtor.id);
|
||||
const resposta = await ai.processarMensagem(produtor, 'Voltei! Como está minha documentação?', docs);
|
||||
await ctx.reply(resposta);
|
||||
return;
|
||||
}
|
||||
|
||||
// Novo produtor - cria cadastro
|
||||
produtor = db.criarProdutor(chatId, username, nome);
|
||||
|
||||
// Mensagem de boas-vindas
|
||||
const boasVindas = `🌱 *Bem-vindo ao DocuAgro!*
|
||||
|
||||
Oi, ${nome}! Eu sou o DocuAgro, seu assistente para organizar a documentação da sua propriedade rural.
|
||||
|
||||
📋 *Por que isso é importante?*
|
||||
A União Europeia criou uma nova regra (EUDR) que exige comprovação de que a produção não vem de área desmatada. Sem essa documentação, pode ter problema na hora de vender.
|
||||
|
||||
🤝 *Como funciona?*
|
||||
Vou te pedir alguns documentos, um de cada vez. Pode mandar foto ou PDF. Eu confiro e te aviso se tá tudo certo.
|
||||
|
||||
São 8 documentos no total. Vamos começar?
|
||||
|
||||
Primeiro, me conta: *qual é o seu nome completo?*`;
|
||||
|
||||
await ctx.reply(boasVindas, { parse_mode: 'Markdown' });
|
||||
|
||||
} catch (erro) {
|
||||
console.error('❌ Erro no /start:', erro);
|
||||
await ctx.reply('Ops, tive um probleminha. Tenta de novo com /start');
|
||||
}
|
||||
});
|
||||
|
||||
// ============================================================
|
||||
// COMANDO /status - Status dos documentos
|
||||
// ============================================================
|
||||
bot.command('status', async (ctx) => {
|
||||
try {
|
||||
const produtor = db.buscarProdutorPorChat(ctx.chat.id);
|
||||
|
||||
if (!produtor) {
|
||||
await ctx.reply('Você ainda não começou! Use /start pra iniciar.');
|
||||
return;
|
||||
}
|
||||
|
||||
const docs = db.buscarDocumentos(produtor.id);
|
||||
let mensagem = `📊 *Status da sua documentação*\n\n`;
|
||||
mensagem += `👤 ${produtor.nome}\n`;
|
||||
mensagem += `🏡 ${produtor.propriedade_nome || 'Propriedade não informada'}\n\n`;
|
||||
|
||||
// Lista cada documento
|
||||
for (const tipo of ORDEM_COLETA) {
|
||||
const doc = docs.find(d => d.tipo === tipo);
|
||||
const info = TIPOS_DOCUMENTO[tipo];
|
||||
let icone = '⬜';
|
||||
|
||||
if (doc) {
|
||||
switch (doc.status) {
|
||||
case 'aprovado': icone = '✅'; break;
|
||||
case 'enviado':
|
||||
case 'validando': icone = '🟡'; break;
|
||||
case 'rejeitado': icone = '❌'; break;
|
||||
case 'vencido': icone = '⚠️'; break;
|
||||
}
|
||||
}
|
||||
|
||||
mensagem += `${icone} ${info.nome}\n`;
|
||||
}
|
||||
|
||||
mensagem += `\n📈 Progresso: ${produtor.docs_completos}/${produtor.docs_total}`;
|
||||
|
||||
await ctx.reply(mensagem, { parse_mode: 'Markdown' });
|
||||
|
||||
} catch (erro) {
|
||||
console.error('❌ Erro no /status:', erro);
|
||||
await ctx.reply('Erro ao buscar status. Tenta de novo.');
|
||||
}
|
||||
});
|
||||
|
||||
// ============================================================
|
||||
// COMANDO /dossie - Gera dossiê PDF
|
||||
// ============================================================
|
||||
bot.command('dossie', async (ctx) => {
|
||||
try {
|
||||
const produtor = db.buscarProdutorPorChat(ctx.chat.id);
|
||||
|
||||
if (!produtor) {
|
||||
await ctx.reply('Você ainda não começou! Use /start pra iniciar.');
|
||||
return;
|
||||
}
|
||||
|
||||
const docs = db.buscarDocumentos(produtor.id);
|
||||
|
||||
if (docs.length === 0) {
|
||||
await ctx.reply('Você ainda não enviou nenhum documento. Vamos começar? Manda o primeiro!');
|
||||
return;
|
||||
}
|
||||
|
||||
await ctx.reply('📄 Gerando seu dossiê... Aguarda um pouquinho!');
|
||||
|
||||
const pdfPath = await gerarDossie(produtor.id);
|
||||
|
||||
await ctx.replyWithDocument(
|
||||
{ source: pdfPath, filename: `Dossie_EUDR_${produtor.nome.replace(/\s+/g, '_')}.pdf` },
|
||||
{ caption: '📋 Seu dossiê EUDR está pronto! Esse documento reúne toda a documentação que você enviou.' }
|
||||
);
|
||||
|
||||
} catch (erro) {
|
||||
console.error('❌ Erro no /dossie:', erro);
|
||||
await ctx.reply('Erro ao gerar o dossiê. Tenta de novo daqui a pouco.');
|
||||
}
|
||||
});
|
||||
|
||||
// ============================================================
|
||||
// COMANDO /ajuda - Menu de ajuda
|
||||
// ============================================================
|
||||
bot.command('ajuda', async (ctx) => {
|
||||
const mensagem = `📚 *Ajuda - DocuAgro*
|
||||
|
||||
🤖 *Comandos disponíveis:*
|
||||
/start - Iniciar ou recomeçar
|
||||
/status - Ver status dos documentos
|
||||
/dossie - Gerar dossiê PDF
|
||||
/pular - Pular documento atual (se não tiver)
|
||||
/ajuda - Este menu
|
||||
|
||||
📄 *Documentos que preciso:*
|
||||
1. CAR (Cadastro Ambiental Rural)
|
||||
2. CCIR (Certificado do INCRA)
|
||||
3. ITR (Imposto Territorial Rural)
|
||||
4. Georreferenciamento (mapa da propriedade)
|
||||
5. Licença Ambiental
|
||||
6. Contrato de Arrendamento (se não for dono)
|
||||
7. Nota Fiscal de Venda
|
||||
8. Declaração de Não Desmatamento
|
||||
|
||||
📸 *Dicas para enviar documentos:*
|
||||
• Tire foto em lugar com boa luz
|
||||
• Enquadre o documento inteiro
|
||||
• PDF é melhor que foto
|
||||
• Mande um documento por vez
|
||||
|
||||
❓ *Dúvidas?* É só mandar mensagem que eu ajudo!`;
|
||||
|
||||
await ctx.reply(mensagem, { parse_mode: 'Markdown' });
|
||||
});
|
||||
|
||||
// ============================================================
|
||||
// COMANDO /pular - Pula documento atual
|
||||
// ============================================================
|
||||
bot.command('pular', async (ctx) => {
|
||||
try {
|
||||
const produtor = db.buscarProdutorPorChat(ctx.chat.id);
|
||||
if (!produtor) {
|
||||
await ctx.reply('Use /start primeiro!');
|
||||
return;
|
||||
}
|
||||
|
||||
const tipoAtual = ai.determinarTipoDocumento(produtor);
|
||||
if (!tipoAtual) {
|
||||
await ctx.reply('Não tem documento pra pular agora.');
|
||||
return;
|
||||
}
|
||||
|
||||
const novaEtapa = ai.avancarEtapa(produtor);
|
||||
const produtorAtualizado = db.buscarProdutorPorId(produtor.id);
|
||||
const docs = db.buscarDocumentos(produtor.id);
|
||||
|
||||
const info = TIPOS_DOCUMENTO[tipoAtual];
|
||||
await ctx.reply(`⏭️ Pulei o ${info?.nome || tipoAtual}. Pode enviar depois!`);
|
||||
|
||||
// Pede próximo documento
|
||||
const resposta = await ai.processarMensagem(
|
||||
produtorAtualizado,
|
||||
'Pulei o documento anterior, qual o próximo?',
|
||||
docs
|
||||
);
|
||||
await ctx.reply(resposta);
|
||||
|
||||
} catch (erro) {
|
||||
console.error('❌ Erro no /pular:', erro);
|
||||
await ctx.reply('Erro ao pular. Tenta de novo.');
|
||||
}
|
||||
});
|
||||
|
||||
// ============================================================
|
||||
// RECEBIMENTO DE FOTOS
|
||||
// ============================================================
|
||||
bot.on('photo', async (ctx) => {
|
||||
try {
|
||||
const produtor = db.buscarProdutorPorChat(ctx.chat.id);
|
||||
|
||||
if (!produtor) {
|
||||
await ctx.reply('Opa! Primeiro use /start pra se cadastrar, depois me manda os documentos.');
|
||||
return;
|
||||
}
|
||||
|
||||
// Pega a foto de melhor qualidade (última do array)
|
||||
const foto = ctx.message.photo[ctx.message.photo.length - 1];
|
||||
const fileId = foto.file_id;
|
||||
|
||||
// Baixa o arquivo
|
||||
const file = await ctx.telegram.getFile(fileId);
|
||||
const filePath = file.file_path;
|
||||
const fileUrl = `https://api.telegram.org/file/bot${process.env.TELEGRAM_BOT_TOKEN}/${filePath}`;
|
||||
|
||||
// Salva localmente
|
||||
const nomeArquivo = `${uuidv4()}${path.extname(filePath) || '.jpg'}`;
|
||||
const caminhoLocal = path.join(UPLOAD_DIR, produtor.id);
|
||||
if (!fs.existsSync(caminhoLocal)) fs.mkdirSync(caminhoLocal, { recursive: true });
|
||||
const caminhoCompleto = path.join(caminhoLocal, nomeArquivo);
|
||||
|
||||
await baixarArquivo(fileUrl, caminhoCompleto);
|
||||
|
||||
console.log(`📸 Foto recebida de ${produtor.nome}: ${nomeArquivo}`);
|
||||
|
||||
// Determina tipo do documento baseado na etapa
|
||||
const tipoDoc = ai.determinarTipoDocumento(produtor);
|
||||
|
||||
if (tipoDoc) {
|
||||
// Salva documento no banco
|
||||
db.salvarDocumento(produtor.id, tipoDoc, {
|
||||
status: 'enviado',
|
||||
arquivo_path: caminhoCompleto,
|
||||
arquivo_nome: nomeArquivo,
|
||||
arquivo_tipo: 'image/jpeg',
|
||||
arquivo_tamanho: foto.file_size || 0
|
||||
});
|
||||
|
||||
// OCR em background
|
||||
processarOCR(produtor.id, tipoDoc, caminhoCompleto);
|
||||
|
||||
// Resposta da IA
|
||||
const docs = db.buscarDocumentos(produtor.id);
|
||||
const resposta = await ai.processarDocumento(
|
||||
produtor, tipoDoc,
|
||||
`Foto do ${TIPOS_DOCUMENTO[tipoDoc]?.nome}. Legenda: ${ctx.message.caption || 'sem legenda'}`
|
||||
);
|
||||
|
||||
await ctx.reply(resposta);
|
||||
|
||||
// Avança para próximo documento
|
||||
const novaEtapa = ai.avancarEtapa(produtor);
|
||||
if (novaEtapa === 'completo') {
|
||||
await ctx.reply('🎉 *Parabéns!* Você enviou todos os documentos!\n\nUse /dossie pra gerar seu dossiê completo.', { parse_mode: 'Markdown' });
|
||||
}
|
||||
|
||||
} else {
|
||||
// Não está em etapa de coleta
|
||||
const docs = db.buscarDocumentos(produtor.id);
|
||||
const resposta = await ai.processarMensagem(
|
||||
produtor,
|
||||
`Enviei uma foto. ${ctx.message.caption || ''}`,
|
||||
docs
|
||||
);
|
||||
await ctx.reply(resposta);
|
||||
}
|
||||
|
||||
} catch (erro) {
|
||||
console.error('❌ Erro ao processar foto:', erro);
|
||||
await ctx.reply('Tive um problema pra receber a foto. Pode tentar de novo?');
|
||||
}
|
||||
});
|
||||
|
||||
// ============================================================
|
||||
// RECEBIMENTO DE DOCUMENTOS (PDF, etc)
|
||||
// ============================================================
|
||||
bot.on('document', async (ctx) => {
|
||||
try {
|
||||
const produtor = db.buscarProdutorPorChat(ctx.chat.id);
|
||||
|
||||
if (!produtor) {
|
||||
await ctx.reply('Primeiro use /start pra se cadastrar!');
|
||||
return;
|
||||
}
|
||||
|
||||
const documento = ctx.message.document;
|
||||
const fileId = documento.file_id;
|
||||
const fileName = documento.file_name || 'documento';
|
||||
const mimeType = documento.mime_type || 'application/octet-stream';
|
||||
|
||||
// Baixa o arquivo
|
||||
const file = await ctx.telegram.getFile(fileId);
|
||||
const fileUrl = `https://api.telegram.org/file/bot${process.env.TELEGRAM_BOT_TOKEN}/${file.file_path}`;
|
||||
|
||||
// Salva localmente
|
||||
const ext = path.extname(fileName) || '.pdf';
|
||||
const nomeArquivo = `${uuidv4()}${ext}`;
|
||||
const caminhoLocal = path.join(UPLOAD_DIR, produtor.id);
|
||||
if (!fs.existsSync(caminhoLocal)) fs.mkdirSync(caminhoLocal, { recursive: true });
|
||||
const caminhoCompleto = path.join(caminhoLocal, nomeArquivo);
|
||||
|
||||
await baixarArquivo(fileUrl, caminhoCompleto);
|
||||
|
||||
console.log(`📁 Documento recebido de ${produtor.nome}: ${fileName} (${mimeType})`);
|
||||
|
||||
// Determina tipo do documento
|
||||
const tipoDoc = ai.determinarTipoDocumento(produtor);
|
||||
|
||||
if (tipoDoc) {
|
||||
db.salvarDocumento(produtor.id, tipoDoc, {
|
||||
status: 'enviado',
|
||||
arquivo_path: caminhoCompleto,
|
||||
arquivo_nome: fileName,
|
||||
arquivo_tipo: mimeType,
|
||||
arquivo_tamanho: documento.file_size || 0
|
||||
});
|
||||
|
||||
// OCR se for imagem
|
||||
if (mimeType.startsWith('image/')) {
|
||||
processarOCR(produtor.id, tipoDoc, caminhoCompleto);
|
||||
}
|
||||
|
||||
const resposta = await ai.processarDocumento(
|
||||
produtor, tipoDoc,
|
||||
`Arquivo: ${fileName} (${mimeType}). Legenda: ${ctx.message.caption || 'sem legenda'}`
|
||||
);
|
||||
|
||||
await ctx.reply(resposta);
|
||||
|
||||
// Avança etapa
|
||||
const novaEtapa = ai.avancarEtapa(produtor);
|
||||
if (novaEtapa === 'completo') {
|
||||
await ctx.reply('🎉 *Todos os documentos enviados!* Use /dossie pra gerar seu dossiê.', { parse_mode: 'Markdown' });
|
||||
}
|
||||
} else {
|
||||
const docs = db.buscarDocumentos(produtor.id);
|
||||
const resposta = await ai.processarMensagem(
|
||||
produtor,
|
||||
`Enviei um arquivo: ${fileName}. ${ctx.message.caption || ''}`,
|
||||
docs
|
||||
);
|
||||
await ctx.reply(resposta);
|
||||
}
|
||||
|
||||
} catch (erro) {
|
||||
console.error('❌ Erro ao processar documento:', erro);
|
||||
await ctx.reply('Tive um problema pra receber o arquivo. Pode tentar de novo?');
|
||||
}
|
||||
});
|
||||
|
||||
// ============================================================
|
||||
// MENSAGENS DE TEXTO
|
||||
// ============================================================
|
||||
bot.on('text', async (ctx) => {
|
||||
try {
|
||||
const produtor = db.buscarProdutorPorChat(ctx.chat.id);
|
||||
const mensagem = ctx.message.text;
|
||||
|
||||
if (!produtor) {
|
||||
// Cria cadastro automaticamente
|
||||
const nome = [ctx.from.first_name, ctx.from.last_name].filter(Boolean).join(' ');
|
||||
const novoProdutor = db.criarProdutor(ctx.chat.id, ctx.from.username, nome);
|
||||
|
||||
const resposta = await ai.processarMensagem(null, mensagem, []);
|
||||
await ctx.reply(resposta);
|
||||
return;
|
||||
}
|
||||
|
||||
// Envia pra IA processar
|
||||
const docs = db.buscarDocumentos(produtor.id);
|
||||
const resposta = await ai.processarMensagem(produtor, mensagem, docs);
|
||||
|
||||
await ctx.reply(resposta);
|
||||
|
||||
} catch (erro) {
|
||||
console.error('❌ Erro ao processar mensagem:', erro);
|
||||
await ctx.reply('Desculpa, tive um problema. Pode repetir?');
|
||||
}
|
||||
});
|
||||
|
||||
// ============================================================
|
||||
// ERRO GLOBAL
|
||||
// ============================================================
|
||||
bot.catch((erro, ctx) => {
|
||||
console.error('❌ Erro no bot:', erro);
|
||||
});
|
||||
|
||||
return bot;
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// FUNÇÕES AUXILIARES
|
||||
// ============================================================
|
||||
|
||||
/**
|
||||
* Baixa arquivo de uma URL para o disco
|
||||
*/
|
||||
function baixarArquivo(url, destino) {
|
||||
return new Promise((resolve, reject) => {
|
||||
const protocolo = url.startsWith('https') ? https : http;
|
||||
const file = fs.createWriteStream(destino);
|
||||
|
||||
protocolo.get(url, (response) => {
|
||||
response.pipe(file);
|
||||
file.on('finish', () => {
|
||||
file.close(resolve);
|
||||
});
|
||||
}).on('error', (erro) => {
|
||||
fs.unlink(destino, () => {}); // Limpa arquivo parcial
|
||||
reject(erro);
|
||||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Processa OCR em background (não bloqueia a resposta)
|
||||
*/
|
||||
async function processarOCR(produtorId, tipoDoc, caminhoArquivo) {
|
||||
try {
|
||||
console.log(`🔍 Iniciando OCR para ${tipoDoc}...`);
|
||||
|
||||
const resultado = await ocr.extrairTexto(caminhoArquivo);
|
||||
|
||||
if (resultado.sucesso) {
|
||||
// Valida o documento
|
||||
const validacao = ocr.validarDocumento(tipoDoc, resultado.texto);
|
||||
|
||||
const doc = db.buscarDocumento(produtorId, tipoDoc);
|
||||
if (doc) {
|
||||
const novoStatus = validacao.valido ? 'aprovado' : 'enviado';
|
||||
db.atualizarValidacao(
|
||||
doc.id,
|
||||
novoStatus,
|
||||
validacao.detalhes.join('; ')
|
||||
);
|
||||
|
||||
// Atualiza dados extraídos
|
||||
if (Object.keys(validacao.dados_extraidos).length > 0) {
|
||||
db.db.prepare('UPDATE documentos SET dados_extraidos = ? WHERE id = ?')
|
||||
.run(JSON.stringify(validacao.dados_extraidos), doc.id);
|
||||
}
|
||||
|
||||
console.log(`✅ OCR ${tipoDoc}: ${novoStatus} - ${validacao.detalhes.join(', ')}`);
|
||||
}
|
||||
} else {
|
||||
console.log(`⚠️ OCR falhou para ${tipoDoc}: confiança baixa`);
|
||||
}
|
||||
} catch (erro) {
|
||||
console.error(`❌ Erro no OCR (${tipoDoc}):`, erro.message);
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = { criarBot };
|
||||
95
src/index.js
Normal file
95
src/index.js
Normal file
@@ -0,0 +1,95 @@
|
||||
/**
|
||||
* DocuAgro - Entry Point
|
||||
* Inicia o bot Telegram e o servidor API
|
||||
*/
|
||||
|
||||
require('dotenv').config();
|
||||
|
||||
const express = require('express');
|
||||
const cors = require('cors');
|
||||
const helmet = require('helmet');
|
||||
const compression = require('compression');
|
||||
const path = require('path');
|
||||
|
||||
const { criarBot } = require('./bot/telegram-bot');
|
||||
const routes = require('./api/routes');
|
||||
|
||||
// Valida variáveis de ambiente obrigatórias
|
||||
const requiredEnv = ['TELEGRAM_BOT_TOKEN', 'OPENAI_API_KEY'];
|
||||
for (const env of requiredEnv) {
|
||||
if (!process.env[env]) {
|
||||
console.error(`❌ Variável de ambiente ${env} não configurada!`);
|
||||
console.error('📝 Copie .env.example para .env e preencha os valores.');
|
||||
process.exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
// Roda setup do banco se não existir
|
||||
const fs = require('fs');
|
||||
const dbPath = process.env.DB_PATH || path.join(__dirname, '..', 'data', 'docuagro.db');
|
||||
if (!fs.existsSync(dbPath)) {
|
||||
console.log('📦 Banco de dados não encontrado. Criando...');
|
||||
require('./setup-db');
|
||||
}
|
||||
|
||||
// ============================================================
|
||||
// SERVIDOR EXPRESS (API + Painel)
|
||||
// ============================================================
|
||||
const app = express();
|
||||
|
||||
// Middlewares
|
||||
app.use(helmet({ contentSecurityPolicy: false }));
|
||||
app.use(compression());
|
||||
app.use(cors());
|
||||
app.use(express.json());
|
||||
app.use(express.urlencoded({ extended: true }));
|
||||
|
||||
// Arquivos estáticos (painel web)
|
||||
app.use(express.static(path.join(__dirname, '..', 'public')));
|
||||
|
||||
// Rotas da API
|
||||
app.use(routes);
|
||||
|
||||
// Fallback para SPA (painel)
|
||||
app.get('*', (req, res) => {
|
||||
if (!req.path.startsWith('/api')) {
|
||||
res.sendFile(path.join(__dirname, '..', 'public', 'index.html'));
|
||||
}
|
||||
});
|
||||
|
||||
const PORT = process.env.PORT || 3100;
|
||||
app.listen(PORT, () => {
|
||||
console.log(`\n🌐 Servidor API rodando em http://localhost:${PORT}`);
|
||||
console.log(`📊 Painel web em http://localhost:${PORT}`);
|
||||
});
|
||||
|
||||
// ============================================================
|
||||
// BOT TELEGRAM
|
||||
// ============================================================
|
||||
const bot = criarBot();
|
||||
|
||||
bot.launch()
|
||||
.then(() => {
|
||||
console.log('🤖 Bot Telegram DocuAgro iniciado!');
|
||||
console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━');
|
||||
console.log('🌱 DocuAgro v1.0 - Compliance EUDR');
|
||||
console.log('━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n');
|
||||
})
|
||||
.catch(err => {
|
||||
console.error('❌ Erro ao iniciar bot:', err.message);
|
||||
if (err.message.includes('401')) {
|
||||
console.error('⚠️ Token do Telegram inválido! Verifique TELEGRAM_BOT_TOKEN no .env');
|
||||
}
|
||||
});
|
||||
|
||||
// Graceful shutdown
|
||||
process.once('SIGINT', () => {
|
||||
console.log('\n🛑 Desligando DocuAgro...');
|
||||
bot.stop('SIGINT');
|
||||
process.exit(0);
|
||||
});
|
||||
|
||||
process.once('SIGTERM', () => {
|
||||
bot.stop('SIGTERM');
|
||||
process.exit(0);
|
||||
});
|
||||
213
src/services/ai-service.js
Normal file
213
src/services/ai-service.js
Normal file
@@ -0,0 +1,213 @@
|
||||
/**
|
||||
* DocuAgro - Serviço de IA (OpenAI gpt-4o-mini)
|
||||
* Processa mensagens do produtor e gera respostas inteligentes
|
||||
*/
|
||||
|
||||
const OpenAI = require('openai');
|
||||
const { gerarPrompt, ORDEM_COLETA, TIPOS_DOCUMENTO } = require('./system-prompt');
|
||||
const db = require('./database');
|
||||
|
||||
const openai = new OpenAI({
|
||||
apiKey: process.env.OPENAI_API_KEY
|
||||
});
|
||||
|
||||
const MODEL = process.env.OPENAI_MODEL || 'gpt-4o-mini';
|
||||
|
||||
/**
|
||||
* Processa mensagem de texto do produtor e retorna resposta da IA
|
||||
*/
|
||||
async function processarMensagem(produtor, mensagemTexto, documentos) {
|
||||
try {
|
||||
// Gera prompt com contexto atual
|
||||
const systemPrompt = gerarPrompt(produtor, documentos, produtor?.etapa_atual);
|
||||
|
||||
// Busca histórico recente da conversa
|
||||
const historico = produtor ? db.buscarHistorico(produtor.id, 10) : [];
|
||||
|
||||
// Monta mensagens para a API
|
||||
const messages = [
|
||||
{ role: 'system', content: systemPrompt }
|
||||
];
|
||||
|
||||
// Adiciona histórico
|
||||
for (const msg of historico) {
|
||||
messages.push({
|
||||
role: msg.role,
|
||||
content: msg.conteudo
|
||||
});
|
||||
}
|
||||
|
||||
// Adiciona mensagem atual
|
||||
messages.push({
|
||||
role: 'user',
|
||||
content: mensagemTexto
|
||||
});
|
||||
|
||||
// Chama a API
|
||||
const completion = await openai.chat.completions.create({
|
||||
model: MODEL,
|
||||
messages,
|
||||
temperature: 0.7,
|
||||
max_tokens: 800,
|
||||
presence_penalty: 0.1,
|
||||
frequency_penalty: 0.1
|
||||
});
|
||||
|
||||
const resposta = completion.choices[0]?.message?.content || 'Desculpa, tive um problema aqui. Pode repetir?';
|
||||
|
||||
// Salva no histórico
|
||||
if (produtor) {
|
||||
db.salvarMensagem(produtor.id, produtor.telegram_chat_id, 'user', mensagemTexto);
|
||||
db.salvarMensagem(produtor.id, produtor.telegram_chat_id, 'assistant', resposta);
|
||||
}
|
||||
|
||||
// Detecta se precisa atualizar etapa/dados baseado na resposta
|
||||
await detectarAtualizacoes(produtor, mensagemTexto, resposta);
|
||||
|
||||
return resposta;
|
||||
|
||||
} catch (erro) {
|
||||
console.error('❌ Erro na IA:', erro.message);
|
||||
return 'Ops, tive um probleminha técnico. Pode mandar de novo daqui a pouquinho? 🔧';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Processa documento recebido (foto/PDF)
|
||||
*/
|
||||
async function processarDocumento(produtor, tipoDoc, descricaoArquivo) {
|
||||
try {
|
||||
const systemPrompt = gerarPrompt(produtor, db.buscarDocumentos(produtor.id), produtor.etapa_atual);
|
||||
|
||||
const messages = [
|
||||
{ role: 'system', content: systemPrompt },
|
||||
{
|
||||
role: 'user',
|
||||
content: `Acabei de enviar um arquivo que deveria ser o documento: ${TIPOS_DOCUMENTO[tipoDoc]?.nome || tipoDoc}. Informações do arquivo: ${descricaoArquivo}`
|
||||
}
|
||||
];
|
||||
|
||||
const completion = await openai.chat.completions.create({
|
||||
model: MODEL,
|
||||
messages,
|
||||
temperature: 0.5,
|
||||
max_tokens: 500
|
||||
});
|
||||
|
||||
return completion.choices[0]?.message?.content || '✅ Recebi o documento! Estou analisando.';
|
||||
|
||||
} catch (erro) {
|
||||
console.error('❌ Erro ao processar documento:', erro.message);
|
||||
return '✅ Recebi o documento! Vou analisar e te aviso o resultado.';
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Detecta se precisa atualizar dados do produtor baseado na conversa
|
||||
* Usa a IA para extrair informações estruturadas
|
||||
*/
|
||||
async function detectarAtualizacoes(produtor, mensagemUsuario, respostaIA) {
|
||||
if (!produtor) return;
|
||||
|
||||
// Se está no onboarding, tenta extrair dados pessoais
|
||||
if (produtor.etapa_atual === 'onboarding') {
|
||||
try {
|
||||
const extraction = await openai.chat.completions.create({
|
||||
model: MODEL,
|
||||
messages: [
|
||||
{
|
||||
role: 'system',
|
||||
content: `Extraia dados estruturados da mensagem do produtor rural. Responda APENAS com JSON válido.
|
||||
Se a informação não estiver na mensagem, use null.
|
||||
Campos possíveis: nome, cpf_cnpj, propriedade_nome, propriedade_municipio, propriedade_estado, propriedade_area_ha, cultura_principal.
|
||||
Exemplo: {"nome": "João Silva", "cpf_cnpj": null, "propriedade_nome": null}`
|
||||
},
|
||||
{ role: 'user', content: mensagemUsuario }
|
||||
],
|
||||
temperature: 0,
|
||||
max_tokens: 200
|
||||
});
|
||||
|
||||
const texto = extraction.choices[0]?.message?.content || '{}';
|
||||
// Tenta extrair JSON da resposta
|
||||
const match = texto.match(/\{[^}]+\}/);
|
||||
if (match) {
|
||||
const dados = JSON.parse(match[0]);
|
||||
const atualizacao = {};
|
||||
|
||||
for (const [chave, valor] of Object.entries(dados)) {
|
||||
if (valor !== null && valor !== undefined && valor !== '') {
|
||||
atualizacao[chave] = valor;
|
||||
}
|
||||
}
|
||||
|
||||
if (Object.keys(atualizacao).length > 0) {
|
||||
db.atualizarProdutor(produtor.id, atualizacao);
|
||||
console.log(`📝 Dados atualizados para ${produtor.id}:`, atualizacao);
|
||||
}
|
||||
|
||||
// Verifica se onboarding está completo (tem pelo menos nome e município)
|
||||
const produtorAtual = db.buscarProdutorPorId(produtor.id);
|
||||
if (produtorAtual.nome && produtorAtual.nome !== 'Novo Produtor' &&
|
||||
produtorAtual.propriedade_municipio) {
|
||||
db.atualizarProdutor(produtor.id, { etapa_atual: 'coleta_car' });
|
||||
console.log(`✅ Onboarding completo para ${produtor.id}, indo para coleta`);
|
||||
}
|
||||
}
|
||||
} catch (e) {
|
||||
// Erro na extração não é crítico
|
||||
console.log('⚠️ Erro na extração de dados:', e.message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Determina qual tipo de documento o produtor está enviando
|
||||
* baseado na etapa atual
|
||||
*/
|
||||
function determinarTipoDocumento(produtor) {
|
||||
if (!produtor || !produtor.etapa_atual) return null;
|
||||
|
||||
const etapa = produtor.etapa_atual;
|
||||
|
||||
// Mapeia etapa para tipo de documento
|
||||
const mapa = {
|
||||
'coleta_car': 'car',
|
||||
'coleta_ccir': 'ccir',
|
||||
'coleta_itr': 'itr',
|
||||
'coleta_geo': 'georreferenciamento',
|
||||
'coleta_licenca': 'licenca_ambiental',
|
||||
'coleta_contrato': 'contrato_arrendamento',
|
||||
'coleta_nf': 'nota_fiscal',
|
||||
'coleta_declaracao': 'declaracao_desmatamento'
|
||||
};
|
||||
|
||||
return mapa[etapa] || null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Avança para o próximo documento na sequência de coleta
|
||||
*/
|
||||
function avancarEtapa(produtor) {
|
||||
const docs = db.buscarDocumentos(produtor.id);
|
||||
const tiposEnviados = docs.filter(d => ['enviado', 'aprovado', 'validando'].includes(d.status)).map(d => d.tipo);
|
||||
|
||||
const proximo = ORDEM_COLETA.find(t => !tiposEnviados.includes(t));
|
||||
|
||||
if (proximo) {
|
||||
const novaEtapa = TIPOS_DOCUMENTO[proximo].etapa;
|
||||
db.atualizarProdutor(produtor.id, { etapa_atual: novaEtapa });
|
||||
return novaEtapa;
|
||||
} else {
|
||||
db.atualizarProdutor(produtor.id, { etapa_atual: 'completo', status: 'completo' });
|
||||
return 'completo';
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
processarMensagem,
|
||||
processarDocumento,
|
||||
detectarAtualizacoes,
|
||||
determinarTipoDocumento,
|
||||
avancarEtapa
|
||||
};
|
||||
112
src/services/auth-service.js
Normal file
112
src/services/auth-service.js
Normal file
@@ -0,0 +1,112 @@
|
||||
/**
|
||||
* DocuAgro - Serviço de Autenticação JWT
|
||||
* Gerencia criação de usuários, login e verificação de tokens
|
||||
*/
|
||||
|
||||
const bcrypt = require('bcryptjs');
|
||||
const jwt = require('jsonwebtoken');
|
||||
const { v4: uuidv4 } = require('uuid');
|
||||
const Database = require('better-sqlite3');
|
||||
const path = require('path');
|
||||
|
||||
const JWT_SECRET = process.env.JWT_SECRET || 'docuagro-jwt-secret-2026-production';
|
||||
const JWT_EXPIRATION = '24h';
|
||||
|
||||
// Conecta ao banco (mesmo caminho do database.js)
|
||||
const dbPath = process.env.DB_PATH || path.join(__dirname, '..', '..', 'data', 'docuagro.db');
|
||||
|
||||
function getDb() {
|
||||
const db = new Database(dbPath);
|
||||
db.pragma('journal_mode = WAL');
|
||||
db.pragma('foreign_keys = ON');
|
||||
return db;
|
||||
}
|
||||
|
||||
/**
|
||||
* Cria um novo usuário no sistema
|
||||
*/
|
||||
async function criarUsuario({ username, senha, nome, role = 'admin', cooperativa_id = 'coop_001' }) {
|
||||
const db = getDb();
|
||||
try {
|
||||
// Verifica se username já existe
|
||||
const existente = db.prepare('SELECT id FROM usuarios WHERE username = ?').get(username);
|
||||
if (existente) {
|
||||
throw new Error('Username já existe');
|
||||
}
|
||||
|
||||
const id = uuidv4();
|
||||
const senha_hash = await bcrypt.hash(senha, 10);
|
||||
|
||||
db.prepare(`
|
||||
INSERT INTO usuarios (id, cooperativa_id, username, senha_hash, nome, role)
|
||||
VALUES (?, ?, ?, ?, ?, ?)
|
||||
`).run(id, cooperativa_id, username, senha_hash, nome || username, role);
|
||||
|
||||
return {
|
||||
id,
|
||||
username,
|
||||
nome: nome || username,
|
||||
role,
|
||||
cooperativa_id
|
||||
};
|
||||
} finally {
|
||||
db.close();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Autentica um usuário com username e senha
|
||||
* Retorna o token JWT e dados do usuário
|
||||
*/
|
||||
async function autenticarUsuario(username, senha) {
|
||||
const db = getDb();
|
||||
try {
|
||||
const usuario = db.prepare('SELECT * FROM usuarios WHERE username = ?').get(username);
|
||||
|
||||
if (!usuario) {
|
||||
throw new Error('Credenciais inválidas');
|
||||
}
|
||||
|
||||
const senhaValida = await bcrypt.compare(senha, usuario.senha_hash);
|
||||
if (!senhaValida) {
|
||||
throw new Error('Credenciais inválidas');
|
||||
}
|
||||
|
||||
// Gera token JWT
|
||||
const payload = {
|
||||
id: usuario.id,
|
||||
username: usuario.username,
|
||||
nome: usuario.nome,
|
||||
role: usuario.role,
|
||||
cooperativa_id: usuario.cooperativa_id
|
||||
};
|
||||
|
||||
const token = jwt.sign(payload, JWT_SECRET, { expiresIn: JWT_EXPIRATION });
|
||||
|
||||
return {
|
||||
token,
|
||||
usuario: payload
|
||||
};
|
||||
} finally {
|
||||
db.close();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Verifica e decodifica um token JWT
|
||||
* Retorna os dados do usuário se válido
|
||||
*/
|
||||
function verificarToken(token) {
|
||||
try {
|
||||
const decoded = jwt.verify(token, JWT_SECRET);
|
||||
return decoded;
|
||||
} catch (erro) {
|
||||
throw new Error('Token inválido ou expirado');
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
criarUsuario,
|
||||
autenticarUsuario,
|
||||
verificarToken
|
||||
};
|
||||
258
src/services/database.js
Normal file
258
src/services/database.js
Normal file
@@ -0,0 +1,258 @@
|
||||
/**
|
||||
* DocuAgro - Serviço de Banco de Dados
|
||||
* Todas as operações CRUD para produtores, documentos e dossiês
|
||||
*/
|
||||
|
||||
const Database = require('better-sqlite3');
|
||||
const path = require('path');
|
||||
const { v4: uuidv4 } = require('uuid');
|
||||
|
||||
const dbPath = process.env.DB_PATH || path.join(__dirname, '..', '..', 'data', 'docuagro.db');
|
||||
const db = new Database(dbPath);
|
||||
db.pragma('journal_mode = WAL');
|
||||
db.pragma('foreign_keys = ON');
|
||||
|
||||
// ========== PRODUTORES ==========
|
||||
|
||||
/**
|
||||
* Busca produtor pelo chat_id do Telegram
|
||||
*/
|
||||
function buscarProdutorPorChat(chatId) {
|
||||
return db.prepare('SELECT * FROM produtores WHERE telegram_chat_id = ?').get(String(chatId));
|
||||
}
|
||||
|
||||
/**
|
||||
* Busca produtor pelo ID
|
||||
*/
|
||||
function buscarProdutorPorId(id) {
|
||||
return db.prepare('SELECT * FROM produtores WHERE id = ?').get(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Cria novo produtor a partir do Telegram
|
||||
*/
|
||||
function criarProdutor(chatId, username, nome) {
|
||||
const id = uuidv4();
|
||||
db.prepare(`
|
||||
INSERT INTO produtores (id, cooperativa_id, nome, telegram_chat_id, telegram_username, status, etapa_atual)
|
||||
VALUES (?, 'coop_001', ?, ?, ?, 'pendente', 'onboarding')
|
||||
`).run(id, nome || 'Novo Produtor', String(chatId), username || null);
|
||||
return buscarProdutorPorId(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Atualiza dados do produtor
|
||||
*/
|
||||
function atualizarProdutor(id, dados) {
|
||||
const campos = [];
|
||||
const valores = [];
|
||||
|
||||
const permitidos = [
|
||||
'nome', 'cpf_cnpj', 'telefone', 'propriedade_nome',
|
||||
'propriedade_municipio', 'propriedade_estado', 'propriedade_area_ha',
|
||||
'cultura_principal', 'status', 'etapa_atual', 'docs_completos'
|
||||
];
|
||||
|
||||
for (const [chave, valor] of Object.entries(dados)) {
|
||||
if (permitidos.includes(chave)) {
|
||||
campos.push(`${chave} = ?`);
|
||||
valores.push(valor);
|
||||
}
|
||||
}
|
||||
|
||||
if (campos.length === 0) return;
|
||||
|
||||
campos.push('atualizado_em = CURRENT_TIMESTAMP');
|
||||
valores.push(id);
|
||||
|
||||
db.prepare(`UPDATE produtores SET ${campos.join(', ')} WHERE id = ?`).run(...valores);
|
||||
return buscarProdutorPorId(id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Lista todos os produtores (para painel)
|
||||
*/
|
||||
function listarProdutores(cooperativaId = 'coop_001') {
|
||||
return db.prepare(`
|
||||
SELECT p.*,
|
||||
(SELECT COUNT(*) FROM documentos d WHERE d.produtor_id = p.id AND d.status = 'aprovado') as docs_aprovados,
|
||||
(SELECT COUNT(*) FROM documentos d WHERE d.produtor_id = p.id AND d.status = 'rejeitado') as docs_rejeitados,
|
||||
(SELECT COUNT(*) FROM documentos d WHERE d.produtor_id = p.id AND d.status = 'enviado') as docs_enviados
|
||||
FROM produtores p
|
||||
WHERE p.cooperativa_id = ?
|
||||
ORDER BY p.criado_em DESC
|
||||
`).all(cooperativaId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Estatísticas do dashboard
|
||||
*/
|
||||
function estatisticas(cooperativaId = 'coop_001') {
|
||||
const total = db.prepare('SELECT COUNT(*) as n FROM produtores WHERE cooperativa_id = ?').get(cooperativaId);
|
||||
const completos = db.prepare("SELECT COUNT(*) as n FROM produtores WHERE cooperativa_id = ? AND status = 'completo'").get(cooperativaId);
|
||||
const emAndamento = db.prepare("SELECT COUNT(*) as n FROM produtores WHERE cooperativa_id = ? AND status = 'em_andamento'").get(cooperativaId);
|
||||
const pendentes = db.prepare("SELECT COUNT(*) as n FROM produtores WHERE cooperativa_id = ? AND status = 'pendente'").get(cooperativaId);
|
||||
const irregulares = db.prepare("SELECT COUNT(*) as n FROM produtores WHERE cooperativa_id = ? AND status = 'irregular'").get(cooperativaId);
|
||||
|
||||
const docsTotal = db.prepare('SELECT COUNT(*) as n FROM documentos d JOIN produtores p ON d.produtor_id = p.id WHERE p.cooperativa_id = ?').get(cooperativaId);
|
||||
const docsAprovados = db.prepare("SELECT COUNT(*) as n FROM documentos d JOIN produtores p ON d.produtor_id = p.id WHERE p.cooperativa_id = ? AND d.status = 'aprovado'").get(cooperativaId);
|
||||
|
||||
return {
|
||||
totalProdutores: total.n,
|
||||
completos: completos.n,
|
||||
emAndamento: emAndamento.n,
|
||||
pendentes: pendentes.n,
|
||||
irregulares: irregulares.n,
|
||||
totalDocumentos: docsTotal.n,
|
||||
documentosAprovados: docsAprovados.n,
|
||||
percentualCompliance: total.n > 0 ? Math.round((completos.n / total.n) * 100) : 0
|
||||
};
|
||||
}
|
||||
|
||||
// ========== DOCUMENTOS ==========
|
||||
|
||||
/**
|
||||
* Busca documentos de um produtor
|
||||
*/
|
||||
function buscarDocumentos(produtorId) {
|
||||
return db.prepare('SELECT * FROM documentos WHERE produtor_id = ? ORDER BY tipo').all(produtorId);
|
||||
}
|
||||
|
||||
/**
|
||||
* Busca documento específico
|
||||
*/
|
||||
function buscarDocumento(produtorId, tipo) {
|
||||
return db.prepare('SELECT * FROM documentos WHERE produtor_id = ? AND tipo = ?').get(produtorId, tipo);
|
||||
}
|
||||
|
||||
/**
|
||||
* Cria ou atualiza documento
|
||||
*/
|
||||
function salvarDocumento(produtorId, tipo, dados) {
|
||||
const existente = buscarDocumento(produtorId, tipo);
|
||||
|
||||
if (existente) {
|
||||
db.prepare(`
|
||||
UPDATE documentos SET
|
||||
status = ?, arquivo_path = ?, arquivo_nome = ?, arquivo_tipo = ?,
|
||||
arquivo_tamanho = ?, enviado_em = CURRENT_TIMESTAMP,
|
||||
atualizado_em = CURRENT_TIMESTAMP
|
||||
WHERE id = ?
|
||||
`).run(
|
||||
dados.status || 'enviado',
|
||||
dados.arquivo_path, dados.arquivo_nome, dados.arquivo_tipo,
|
||||
dados.arquivo_tamanho || 0, existente.id
|
||||
);
|
||||
return buscarDocumento(produtorId, tipo);
|
||||
}
|
||||
|
||||
const id = uuidv4();
|
||||
db.prepare(`
|
||||
INSERT INTO documentos (id, produtor_id, tipo, status, arquivo_path, arquivo_nome, arquivo_tipo, arquivo_tamanho, enviado_em)
|
||||
VALUES (?, ?, ?, ?, ?, ?, ?, ?, CURRENT_TIMESTAMP)
|
||||
`).run(id, produtorId, tipo, dados.status || 'enviado', dados.arquivo_path, dados.arquivo_nome, dados.arquivo_tipo, dados.arquivo_tamanho || 0);
|
||||
|
||||
// Atualiza contagem de docs do produtor
|
||||
atualizarContagemDocs(produtorId);
|
||||
|
||||
return buscarDocumento(produtorId, tipo);
|
||||
}
|
||||
|
||||
/**
|
||||
* Atualiza validação do documento
|
||||
*/
|
||||
function atualizarValidacao(docId, resultado, detalhes) {
|
||||
db.prepare(`
|
||||
UPDATE documentos SET
|
||||
status = ?, validacao_resultado = ?, validacao_detalhes = ?,
|
||||
validado_em = CURRENT_TIMESTAMP, atualizado_em = CURRENT_TIMESTAMP
|
||||
WHERE id = ?
|
||||
`).run(resultado, resultado, detalhes, docId);
|
||||
|
||||
// Busca o doc pra atualizar contagem
|
||||
const doc = db.prepare('SELECT produtor_id FROM documentos WHERE id = ?').get(docId);
|
||||
if (doc) atualizarContagemDocs(doc.produtor_id);
|
||||
}
|
||||
|
||||
/**
|
||||
* Atualiza contagem de documentos completos do produtor
|
||||
*/
|
||||
function atualizarContagemDocs(produtorId) {
|
||||
const aprovados = db.prepare("SELECT COUNT(*) as n FROM documentos WHERE produtor_id = ? AND status = 'aprovado'").get(produtorId);
|
||||
const total = db.prepare("SELECT COUNT(*) as n FROM documentos WHERE produtor_id = ?").get(produtorId);
|
||||
|
||||
let status = 'pendente';
|
||||
if (aprovados.n >= 8) status = 'completo';
|
||||
else if (total.n > 0) status = 'em_andamento';
|
||||
|
||||
db.prepare('UPDATE produtores SET docs_completos = ?, status = ?, atualizado_em = CURRENT_TIMESTAMP WHERE id = ?')
|
||||
.run(aprovados.n, status, produtorId);
|
||||
}
|
||||
|
||||
// ========== CONVERSAS ==========
|
||||
|
||||
/**
|
||||
* Salva mensagem no histórico
|
||||
*/
|
||||
function salvarMensagem(produtorId, chatId, role, conteudo, tipo = 'texto') {
|
||||
db.prepare(`
|
||||
INSERT INTO conversas (produtor_id, chat_id, role, conteudo, tipo_mensagem)
|
||||
VALUES (?, ?, ?, ?, ?)
|
||||
`).run(produtorId, String(chatId), role, conteudo, tipo);
|
||||
}
|
||||
|
||||
/**
|
||||
* Busca histórico de conversa (últimas N mensagens)
|
||||
*/
|
||||
function buscarHistorico(produtorId, limite = 20) {
|
||||
return db.prepare(`
|
||||
SELECT role, conteudo, tipo_mensagem, criado_em
|
||||
FROM conversas
|
||||
WHERE produtor_id = ?
|
||||
ORDER BY id DESC
|
||||
LIMIT ?
|
||||
`).all(produtorId, limite).reverse();
|
||||
}
|
||||
|
||||
// ========== DOSSIÊS ==========
|
||||
|
||||
/**
|
||||
* Salva registro de dossiê gerado
|
||||
*/
|
||||
function salvarDossie(produtorId, arquivoPath, docsIncluidos) {
|
||||
const id = uuidv4();
|
||||
const versao = db.prepare('SELECT COALESCE(MAX(versao), 0) + 1 as v FROM dossies WHERE produtor_id = ?').get(produtorId);
|
||||
|
||||
db.prepare(`
|
||||
INSERT INTO dossies (id, produtor_id, arquivo_path, versao, docs_incluidos)
|
||||
VALUES (?, ?, ?, ?, ?)
|
||||
`).run(id, produtorId, arquivoPath, versao.v, JSON.stringify(docsIncluidos));
|
||||
|
||||
return { id, versao: versao.v, arquivo_path: arquivoPath };
|
||||
}
|
||||
|
||||
/**
|
||||
* Busca último dossiê de um produtor
|
||||
*/
|
||||
function buscarUltimoDossie(produtorId) {
|
||||
return db.prepare('SELECT * FROM dossies WHERE produtor_id = ? ORDER BY versao DESC LIMIT 1').get(produtorId);
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
db,
|
||||
buscarProdutorPorChat,
|
||||
buscarProdutorPorId,
|
||||
criarProdutor,
|
||||
atualizarProdutor,
|
||||
listarProdutores,
|
||||
estatisticas,
|
||||
buscarDocumentos,
|
||||
buscarDocumento,
|
||||
salvarDocumento,
|
||||
atualizarValidacao,
|
||||
atualizarContagemDocs,
|
||||
salvarMensagem,
|
||||
buscarHistorico,
|
||||
salvarDossie,
|
||||
buscarUltimoDossie
|
||||
};
|
||||
182
src/services/ocr-service.js
Normal file
182
src/services/ocr-service.js
Normal file
@@ -0,0 +1,182 @@
|
||||
/**
|
||||
* DocuAgro - Serviço de OCR
|
||||
* Extrai texto de imagens e PDFs usando Tesseract.js
|
||||
*/
|
||||
|
||||
const Tesseract = require('tesseract.js');
|
||||
const path = require('path');
|
||||
|
||||
/**
|
||||
* Extrai texto de uma imagem
|
||||
* @param {string} imagePath - Caminho da imagem
|
||||
* @returns {object} - Texto extraído e confiança
|
||||
*/
|
||||
async function extrairTexto(imagePath) {
|
||||
try {
|
||||
console.log(`🔍 OCR processando: ${path.basename(imagePath)}`);
|
||||
|
||||
const resultado = await Tesseract.recognize(imagePath, 'por', {
|
||||
logger: info => {
|
||||
if (info.status === 'recognizing text') {
|
||||
// Log de progresso
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
const texto = resultado.data.text.trim();
|
||||
const confianca = resultado.data.confidence;
|
||||
|
||||
console.log(`✅ OCR concluído: ${texto.length} caracteres, confiança: ${confianca}%`);
|
||||
|
||||
return {
|
||||
texto,
|
||||
confianca,
|
||||
palavras: resultado.data.words?.length || 0,
|
||||
sucesso: texto.length > 10 && confianca > 30
|
||||
};
|
||||
} catch (erro) {
|
||||
console.error('❌ Erro no OCR:', erro.message);
|
||||
return {
|
||||
texto: '',
|
||||
confianca: 0,
|
||||
palavras: 0,
|
||||
sucesso: false,
|
||||
erro: erro.message
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Valida um documento baseado no texto extraído por OCR
|
||||
* @param {string} tipoDoc - Tipo do documento
|
||||
* @param {string} texto - Texto extraído
|
||||
* @returns {object} - Resultado da validação
|
||||
*/
|
||||
function validarDocumento(tipoDoc, texto) {
|
||||
const textoUpper = texto.toUpperCase();
|
||||
const resultado = {
|
||||
valido: false,
|
||||
detalhes: [],
|
||||
dados_extraidos: {}
|
||||
};
|
||||
|
||||
switch (tipoDoc) {
|
||||
case 'car':
|
||||
// Procura número do CAR (formato: XX-XXXXXXX-XXXXXXXXXXXXXXXXXXXXXXXX)
|
||||
const carMatch = texto.match(/[A-Z]{2}[-.]?\d{7}[-.]?\w{20,}/i);
|
||||
if (carMatch) {
|
||||
resultado.dados_extraidos.numero_car = carMatch[0];
|
||||
resultado.detalhes.push(`Número CAR encontrado: ${carMatch[0]}`);
|
||||
resultado.valido = true;
|
||||
}
|
||||
// Verifica menções ao SICAR
|
||||
if (textoUpper.includes('SICAR') || textoUpper.includes('CADASTRO AMBIENTAL')) {
|
||||
resultado.detalhes.push('Documento parece ser um CAR');
|
||||
resultado.valido = true;
|
||||
}
|
||||
break;
|
||||
|
||||
case 'ccir':
|
||||
// Procura menção ao INCRA
|
||||
if (textoUpper.includes('INCRA') || textoUpper.includes('CERTIFICADO') && textoUpper.includes('IMÓVEL RURAL')) {
|
||||
resultado.detalhes.push('Documento parece ser um CCIR');
|
||||
resultado.valido = true;
|
||||
}
|
||||
// Procura código do imóvel
|
||||
const ccirMatch = texto.match(/\d{3}\.\d{3}\.\d{3}\.\d{3}[-.]?\d/);
|
||||
if (ccirMatch) {
|
||||
resultado.dados_extraidos.codigo_imovel = ccirMatch[0];
|
||||
resultado.detalhes.push(`Código do imóvel: ${ccirMatch[0]}`);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'itr':
|
||||
// Procura menção a ITR / Receita Federal
|
||||
if (textoUpper.includes('ITR') || textoUpper.includes('IMPOSTO TERRITORIAL') || textoUpper.includes('RECEITA FEDERAL')) {
|
||||
resultado.detalhes.push('Documento parece ser comprovante de ITR');
|
||||
resultado.valido = true;
|
||||
}
|
||||
break;
|
||||
|
||||
case 'georreferenciamento':
|
||||
// Procura coordenadas geográficas
|
||||
const coordMatch = texto.match(/-?\d{1,3}[.,]\d{2,}/g);
|
||||
if (coordMatch && coordMatch.length >= 2) {
|
||||
resultado.dados_extraidos.coordenadas = coordMatch.slice(0, 4);
|
||||
resultado.detalhes.push(`Coordenadas encontradas: ${coordMatch.length} pontos`);
|
||||
resultado.valido = true;
|
||||
}
|
||||
if (textoUpper.includes('MEMORIAL') || textoUpper.includes('TOPOGR') || textoUpper.includes('GEORREFERENCIA')) {
|
||||
resultado.valido = true;
|
||||
resultado.detalhes.push('Documento parece ser georreferenciamento');
|
||||
}
|
||||
break;
|
||||
|
||||
case 'licenca_ambiental':
|
||||
if (textoUpper.includes('LICENÇA') || textoUpper.includes('LICENCA') || textoUpper.includes('AMBIENTAL')) {
|
||||
resultado.detalhes.push('Documento parece ser licença ambiental');
|
||||
resultado.valido = true;
|
||||
}
|
||||
// Procura validade
|
||||
const dataMatch = texto.match(/\d{2}\/\d{2}\/\d{4}/g);
|
||||
if (dataMatch) {
|
||||
resultado.dados_extraidos.datas_encontradas = dataMatch;
|
||||
resultado.detalhes.push(`Datas encontradas: ${dataMatch.join(', ')}`);
|
||||
}
|
||||
break;
|
||||
|
||||
case 'contrato_arrendamento':
|
||||
if (textoUpper.includes('CONTRATO') || textoUpper.includes('ARRENDAMENTO') || textoUpper.includes('PARCERIA')) {
|
||||
resultado.detalhes.push('Documento parece ser contrato');
|
||||
resultado.valido = true;
|
||||
}
|
||||
break;
|
||||
|
||||
case 'nota_fiscal':
|
||||
if (textoUpper.includes('NOTA FISCAL') || textoUpper.includes('NF-E') || textoUpper.includes('NFE') || textoUpper.includes('DANFE')) {
|
||||
resultado.detalhes.push('Documento parece ser nota fiscal');
|
||||
resultado.valido = true;
|
||||
}
|
||||
// Procura CPF/CNPJ
|
||||
const cpfMatch = texto.match(/\d{3}\.\d{3}\.\d{3}[-.]?\d{2}/);
|
||||
const cnpjMatch = texto.match(/\d{2}\.\d{3}\.\d{3}\/\d{4}[-.]?\d{2}/);
|
||||
if (cpfMatch) resultado.dados_extraidos.cpf = cpfMatch[0];
|
||||
if (cnpjMatch) resultado.dados_extraidos.cnpj = cnpjMatch[0];
|
||||
break;
|
||||
|
||||
case 'declaracao_desmatamento':
|
||||
// Sempre válido - nós geramos esse documento
|
||||
resultado.valido = true;
|
||||
resultado.detalhes.push('Declaração de não desmatamento recebida');
|
||||
break;
|
||||
|
||||
default:
|
||||
resultado.detalhes.push('Tipo de documento não reconhecido para validação automática');
|
||||
}
|
||||
|
||||
// Se não conseguiu validar por tipo, verifica qualidade geral
|
||||
if (!resultado.valido && texto.length > 50) {
|
||||
resultado.valido = true;
|
||||
resultado.detalhes.push('Documento recebido - validação manual pode ser necessária');
|
||||
}
|
||||
|
||||
return resultado;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extrai CPF ou CNPJ de um texto
|
||||
*/
|
||||
function extrairCpfCnpj(texto) {
|
||||
const cpf = texto.match(/\d{3}\.?\d{3}\.?\d{3}[-.]?\d{2}/);
|
||||
const cnpj = texto.match(/\d{2}\.?\d{3}\.?\d{3}\/?\d{4}[-.]?\d{2}/);
|
||||
return {
|
||||
cpf: cpf ? cpf[0] : null,
|
||||
cnpj: cnpj ? cnpj[0] : null
|
||||
};
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
extrairTexto,
|
||||
validarDocumento,
|
||||
extrairCpfCnpj
|
||||
};
|
||||
379
src/services/pdf-service.js
Normal file
379
src/services/pdf-service.js
Normal file
@@ -0,0 +1,379 @@
|
||||
/**
|
||||
* DocuAgro - Serviço de Geração de Dossiê PDF
|
||||
* Compila todos os documentos validados do produtor em um PDF profissional
|
||||
*/
|
||||
|
||||
const PDFDocument = require('pdfkit');
|
||||
const fs = require('fs');
|
||||
const path = require('path');
|
||||
const dayjs = require('dayjs');
|
||||
const db = require('./database');
|
||||
const { TIPOS_DOCUMENTO } = require('./system-prompt');
|
||||
|
||||
// Cores do tema
|
||||
const CORES = {
|
||||
verde: '#2D7D46',
|
||||
verdeClaro: '#E8F5E9',
|
||||
amarelo: '#FFA000',
|
||||
vermelho: '#D32F2F',
|
||||
cinza: '#616161',
|
||||
cinzaClaro: '#F5F5F5',
|
||||
branco: '#FFFFFF',
|
||||
preto: '#212121'
|
||||
};
|
||||
|
||||
/**
|
||||
* Gera o dossiê PDF de um produtor
|
||||
* @param {string} produtorId - ID do produtor
|
||||
* @returns {string} - Caminho do PDF gerado
|
||||
*/
|
||||
async function gerarDossie(produtorId) {
|
||||
const produtor = db.buscarProdutorPorId(produtorId);
|
||||
if (!produtor) throw new Error('Produtor não encontrado');
|
||||
|
||||
const documentos = db.buscarDocumentos(produtorId);
|
||||
const uploadDir = process.env.UPLOAD_DIR || path.join(__dirname, '..', '..', 'uploads');
|
||||
const dossiePath = path.join(uploadDir, 'dossies');
|
||||
|
||||
// Cria diretório de dossiês
|
||||
if (!fs.existsSync(dossiePath)) fs.mkdirSync(dossiePath, { recursive: true });
|
||||
|
||||
const nomeArquivo = `dossie_${produtor.cpf_cnpj || produtor.id}_${dayjs().format('YYYYMMDD_HHmmss')}.pdf`;
|
||||
const caminhoCompleto = path.join(dossiePath, nomeArquivo);
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
const doc = new PDFDocument({
|
||||
size: 'A4',
|
||||
margins: { top: 60, bottom: 60, left: 50, right: 50 },
|
||||
info: {
|
||||
Title: `Dossiê EUDR - ${produtor.nome}`,
|
||||
Author: 'DocuAgro',
|
||||
Subject: 'Dossiê de Compliance EUDR',
|
||||
Creator: 'DocuAgro v1.0'
|
||||
}
|
||||
});
|
||||
|
||||
const stream = fs.createWriteStream(caminhoCompleto);
|
||||
doc.pipe(stream);
|
||||
|
||||
// === CAPA ===
|
||||
gerarCapa(doc, produtor);
|
||||
|
||||
// === PÁGINA DE DADOS DO PRODUTOR ===
|
||||
doc.addPage();
|
||||
gerarDadosProdutor(doc, produtor);
|
||||
|
||||
// === PÁGINAS DE DOCUMENTOS ===
|
||||
doc.addPage();
|
||||
gerarResumoDocumentos(doc, documentos);
|
||||
|
||||
// === DETALHES DE CADA DOCUMENTO ===
|
||||
for (const documento of documentos) {
|
||||
doc.addPage();
|
||||
gerarDetalheDocumento(doc, documento, uploadDir);
|
||||
}
|
||||
|
||||
// === PÁGINA FINAL - DECLARAÇÃO ===
|
||||
doc.addPage();
|
||||
gerarDeclaracao(doc, produtor, documentos);
|
||||
|
||||
// Rodapé em todas as páginas
|
||||
const totalPaginas = doc.bufferedPageRange().count;
|
||||
for (let i = 0; i < totalPaginas; i++) {
|
||||
doc.switchToPage(i);
|
||||
gerarRodape(doc, i + 1, totalPaginas);
|
||||
}
|
||||
|
||||
doc.end();
|
||||
|
||||
stream.on('finish', () => {
|
||||
// Salva no banco
|
||||
const docsIncluidos = documentos.map(d => d.tipo);
|
||||
db.salvarDossie(produtorId, caminhoCompleto, docsIncluidos);
|
||||
|
||||
console.log(`📄 Dossiê gerado: ${nomeArquivo}`);
|
||||
resolve(caminhoCompleto);
|
||||
});
|
||||
|
||||
stream.on('error', reject);
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Gera a capa do dossiê
|
||||
*/
|
||||
function gerarCapa(doc, produtor) {
|
||||
// Fundo verde no topo
|
||||
doc.rect(0, 0, doc.page.width, 200).fill(CORES.verde);
|
||||
|
||||
// Título
|
||||
doc.fontSize(32).fillColor(CORES.branco)
|
||||
.text('DocuAgro', 50, 60, { align: 'center' });
|
||||
doc.fontSize(14).fillColor(CORES.branco)
|
||||
.text('Dossiê de Compliance EUDR', 50, 105, { align: 'center' });
|
||||
doc.fontSize(10).fillColor(CORES.branco)
|
||||
.text('Regulamento (UE) 2023/1115', 50, 130, { align: 'center' });
|
||||
|
||||
// Dados do produtor na capa
|
||||
doc.fontSize(18).fillColor(CORES.preto)
|
||||
.text(produtor.nome || 'Produtor', 50, 250, { align: 'center' });
|
||||
|
||||
doc.fontSize(12).fillColor(CORES.cinza);
|
||||
if (produtor.cpf_cnpj) {
|
||||
doc.text(`CPF/CNPJ: ${produtor.cpf_cnpj}`, 50, 285, { align: 'center' });
|
||||
}
|
||||
if (produtor.propriedade_nome) {
|
||||
doc.text(`Propriedade: ${produtor.propriedade_nome}`, 50, 310, { align: 'center' });
|
||||
}
|
||||
if (produtor.propriedade_municipio) {
|
||||
doc.text(`${produtor.propriedade_municipio}/${produtor.propriedade_estado}`, 50, 335, { align: 'center' });
|
||||
}
|
||||
if (produtor.propriedade_area_ha) {
|
||||
doc.text(`Área: ${produtor.propriedade_area_ha} hectares`, 50, 360, { align: 'center' });
|
||||
}
|
||||
|
||||
// Data de geração
|
||||
doc.fontSize(10).fillColor(CORES.cinza)
|
||||
.text(`Gerado em: ${dayjs().format('DD/MM/YYYY [às] HH:mm')}`, 50, 450, { align: 'center' });
|
||||
|
||||
// Status
|
||||
const statusTexto = produtor.status === 'completo' ? '✅ COMPLIANCE COMPLETO' : '⚠️ COMPLIANCE EM ANDAMENTO';
|
||||
const statusCor = produtor.status === 'completo' ? CORES.verde : CORES.amarelo;
|
||||
|
||||
doc.fontSize(16).fillColor(statusCor)
|
||||
.text(statusTexto, 50, 500, { align: 'center' });
|
||||
}
|
||||
|
||||
/**
|
||||
* Gera página com dados do produtor
|
||||
*/
|
||||
function gerarDadosProdutor(doc, produtor) {
|
||||
doc.fontSize(18).fillColor(CORES.verde)
|
||||
.text('Dados do Produtor', 50, 60);
|
||||
|
||||
doc.moveTo(50, 85).lineTo(545, 85).stroke(CORES.verde);
|
||||
|
||||
const campos = [
|
||||
['Nome Completo', produtor.nome || 'Não informado'],
|
||||
['CPF/CNPJ', produtor.cpf_cnpj || 'Não informado'],
|
||||
['Telefone', produtor.telefone || 'Não informado'],
|
||||
['Propriedade', produtor.propriedade_nome || 'Não informada'],
|
||||
['Município', produtor.propriedade_municipio || 'Não informado'],
|
||||
['Estado', produtor.propriedade_estado || 'Não informado'],
|
||||
['Área (hectares)', produtor.propriedade_area_ha ? `${produtor.propriedade_area_ha} ha` : 'Não informada'],
|
||||
['Cultura Principal', produtor.cultura_principal || 'Não informada'],
|
||||
['Documentos Aprovados', `${produtor.docs_completos} de ${produtor.docs_total}`],
|
||||
['Status Compliance', produtor.status || 'Pendente'],
|
||||
['Data de Cadastro', dayjs(produtor.criado_em).format('DD/MM/YYYY')]
|
||||
];
|
||||
|
||||
let y = 100;
|
||||
for (const [label, valor] of campos) {
|
||||
// Fundo alternado
|
||||
if (campos.indexOf([label, valor]) % 2 === 0) {
|
||||
doc.rect(50, y - 5, 495, 25).fill(CORES.cinzaClaro);
|
||||
}
|
||||
|
||||
doc.fontSize(10).fillColor(CORES.cinza).text(label, 60, y);
|
||||
doc.fontSize(10).fillColor(CORES.preto).text(valor, 250, y);
|
||||
y += 28;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gera resumo dos documentos
|
||||
*/
|
||||
function gerarResumoDocumentos(doc, documentos) {
|
||||
doc.fontSize(18).fillColor(CORES.verde)
|
||||
.text('Resumo dos Documentos', 50, 60);
|
||||
|
||||
doc.moveTo(50, 85).lineTo(545, 85).stroke(CORES.verde);
|
||||
|
||||
let y = 100;
|
||||
|
||||
// Cabeçalho da tabela
|
||||
doc.fontSize(9).fillColor(CORES.branco);
|
||||
doc.rect(50, y - 5, 495, 22).fill(CORES.verde);
|
||||
doc.text('Documento', 60, y);
|
||||
doc.text('Status', 320, y);
|
||||
doc.text('Data Envio', 420, y);
|
||||
y += 28;
|
||||
|
||||
// Lista todos os tipos de documento
|
||||
const todosOsTipos = [
|
||||
'car', 'ccir', 'itr', 'georreferenciamento',
|
||||
'licenca_ambiental', 'contrato_arrendamento',
|
||||
'nota_fiscal', 'declaracao_desmatamento'
|
||||
];
|
||||
|
||||
for (const tipo of todosOsTipos) {
|
||||
const docEncontrado = documentos.find(d => d.tipo === tipo);
|
||||
const info = TIPOS_DOCUMENTO[tipo];
|
||||
|
||||
// Fundo alternado
|
||||
if (todosOsTipos.indexOf(tipo) % 2 === 0) {
|
||||
doc.rect(50, y - 5, 495, 22).fill(CORES.cinzaClaro);
|
||||
}
|
||||
|
||||
doc.fontSize(9).fillColor(CORES.preto);
|
||||
doc.text(info?.nome || tipo, 60, y, { width: 250 });
|
||||
|
||||
if (docEncontrado) {
|
||||
const statusCor = {
|
||||
'aprovado': CORES.verde,
|
||||
'enviado': CORES.amarelo,
|
||||
'validando': CORES.amarelo,
|
||||
'rejeitado': CORES.vermelho,
|
||||
'pendente': CORES.cinza,
|
||||
'vencido': CORES.vermelho
|
||||
}[docEncontrado.status] || CORES.cinza;
|
||||
|
||||
doc.fillColor(statusCor).text(docEncontrado.status.toUpperCase(), 320, y);
|
||||
doc.fillColor(CORES.cinza).text(
|
||||
docEncontrado.enviado_em ? dayjs(docEncontrado.enviado_em).format('DD/MM/YYYY') : '-',
|
||||
420, y
|
||||
);
|
||||
} else {
|
||||
doc.fillColor(CORES.cinza).text('NÃO ENVIADO', 320, y);
|
||||
doc.text('-', 420, y);
|
||||
}
|
||||
|
||||
y += 25;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gera página de detalhe de um documento
|
||||
*/
|
||||
function gerarDetalheDocumento(doc, documento, uploadDir) {
|
||||
const info = TIPOS_DOCUMENTO[documento.tipo];
|
||||
|
||||
doc.fontSize(16).fillColor(CORES.verde)
|
||||
.text(info?.nome || documento.tipo, 50, 60);
|
||||
|
||||
doc.moveTo(50, 85).lineTo(545, 85).stroke(CORES.verde);
|
||||
|
||||
let y = 100;
|
||||
|
||||
// Status com badge colorida
|
||||
const statusCor = {
|
||||
'aprovado': CORES.verde,
|
||||
'enviado': CORES.amarelo,
|
||||
'rejeitado': CORES.vermelho
|
||||
}[documento.status] || CORES.cinza;
|
||||
|
||||
doc.fontSize(12).fillColor(statusCor)
|
||||
.text(`Status: ${documento.status.toUpperCase()}`, 50, y);
|
||||
y += 30;
|
||||
|
||||
// Detalhes do arquivo
|
||||
doc.fontSize(10).fillColor(CORES.cinza);
|
||||
if (documento.arquivo_nome) {
|
||||
doc.text(`Arquivo: ${documento.arquivo_nome}`, 50, y); y += 20;
|
||||
}
|
||||
if (documento.enviado_em) {
|
||||
doc.text(`Enviado em: ${dayjs(documento.enviado_em).format('DD/MM/YYYY [às] HH:mm')}`, 50, y); y += 20;
|
||||
}
|
||||
if (documento.validado_em) {
|
||||
doc.text(`Validado em: ${dayjs(documento.validado_em).format('DD/MM/YYYY [às] HH:mm')}`, 50, y); y += 20;
|
||||
}
|
||||
|
||||
// Dados extraídos
|
||||
if (documento.dados_extraidos) {
|
||||
y += 10;
|
||||
doc.fontSize(12).fillColor(CORES.preto).text('Dados Extraídos:', 50, y); y += 20;
|
||||
try {
|
||||
const dados = JSON.parse(documento.dados_extraidos);
|
||||
for (const [chave, valor] of Object.entries(dados)) {
|
||||
doc.fontSize(9).fillColor(CORES.cinza).text(`${chave}: ${valor}`, 70, y); y += 18;
|
||||
}
|
||||
} catch (e) {
|
||||
doc.fontSize(9).fillColor(CORES.cinza).text(documento.dados_extraidos, 70, y);
|
||||
}
|
||||
}
|
||||
|
||||
// Resultado da validação
|
||||
if (documento.validacao_detalhes) {
|
||||
y += 10;
|
||||
doc.fontSize(12).fillColor(CORES.preto).text('Validação:', 50, y); y += 20;
|
||||
doc.fontSize(9).fillColor(CORES.cinza).text(documento.validacao_detalhes, 70, y, { width: 470 });
|
||||
}
|
||||
|
||||
// Tenta incluir miniatura da imagem
|
||||
if (documento.arquivo_path && fs.existsSync(documento.arquivo_path)) {
|
||||
const ext = path.extname(documento.arquivo_path).toLowerCase();
|
||||
if (['.jpg', '.jpeg', '.png'].includes(ext)) {
|
||||
try {
|
||||
y += 30;
|
||||
if (y < 500) {
|
||||
doc.image(documento.arquivo_path, 50, y, { width: 300, height: 250, fit: [300, 250] });
|
||||
}
|
||||
} catch (e) {
|
||||
// Imagem não pôde ser incluída
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gera página de declaração final
|
||||
*/
|
||||
function gerarDeclaracao(doc, produtor, documentos) {
|
||||
doc.fontSize(18).fillColor(CORES.verde)
|
||||
.text('Declaração de Conformidade', 50, 60, { align: 'center' });
|
||||
|
||||
doc.moveTo(50, 90).lineTo(545, 90).stroke(CORES.verde);
|
||||
|
||||
const docsAprovados = documentos.filter(d => d.status === 'aprovado').length;
|
||||
const totalDocs = 8;
|
||||
|
||||
doc.fontSize(11).fillColor(CORES.preto);
|
||||
|
||||
const texto = `
|
||||
Declaro, para os devidos fins e efeitos do Regulamento (UE) 2023/1115 (EUDR - European Union Deforestation Regulation), que o produtor rural abaixo identificado apresentou a documentação solicitada para fins de compliance ambiental e fundiário:
|
||||
|
||||
Produtor: ${produtor.nome || 'Não informado'}
|
||||
CPF/CNPJ: ${produtor.cpf_cnpj || 'Não informado'}
|
||||
Propriedade: ${produtor.propriedade_nome || 'Não informada'}
|
||||
Município/UF: ${produtor.propriedade_municipio || '?'}/${produtor.propriedade_estado || '?'}
|
||||
Área: ${produtor.propriedade_area_ha ? produtor.propriedade_area_ha + ' hectares' : 'Não informada'}
|
||||
Cultura Principal: ${produtor.cultura_principal || 'Não informada'}
|
||||
|
||||
Documentos apresentados e validados: ${docsAprovados} de ${totalDocs}
|
||||
|
||||
${docsAprovados >= totalDocs
|
||||
? 'Todos os documentos obrigatórios foram apresentados e validados. O produtor encontra-se em conformidade com os requisitos documentais para exportação sob o regime EUDR.'
|
||||
: 'ATENÇÃO: Nem todos os documentos obrigatórios foram apresentados e/ou validados. O dossiê encontra-se INCOMPLETO para fins de compliance EUDR.'}
|
||||
|
||||
Este dossiê foi gerado automaticamente pelo sistema DocuAgro em ${dayjs().format('DD/MM/YYYY [às] HH:mm')}.
|
||||
|
||||
Os documentos originais encontram-se armazenados digitalmente e disponíveis para verificação.
|
||||
`;
|
||||
|
||||
doc.text(texto.trim(), 50, 110, { width: 495, lineGap: 4 });
|
||||
|
||||
// Área de assinatura
|
||||
const y = 550;
|
||||
doc.moveTo(150, y).lineTo(450, y).stroke(CORES.cinza);
|
||||
doc.fontSize(10).fillColor(CORES.cinza)
|
||||
.text('Assinatura do Responsável / Cooperativa', 150, y + 5, { align: 'center', width: 300 });
|
||||
|
||||
doc.moveTo(150, y + 60).lineTo(450, y + 60).stroke(CORES.cinza);
|
||||
doc.text(`${produtor.nome || 'Produtor'}`, 150, y + 65, { align: 'center', width: 300 });
|
||||
}
|
||||
|
||||
/**
|
||||
* Rodapé padrão
|
||||
*/
|
||||
function gerarRodape(doc, pagina, total) {
|
||||
const y = doc.page.height - 40;
|
||||
doc.fontSize(8).fillColor(CORES.cinza);
|
||||
doc.text(`DocuAgro - Compliance EUDR | Página ${pagina} de ${total}`, 50, y, {
|
||||
align: 'center',
|
||||
width: doc.page.width - 100
|
||||
});
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
gerarDossie
|
||||
};
|
||||
172
src/services/system-prompt.js
Normal file
172
src/services/system-prompt.js
Normal file
@@ -0,0 +1,172 @@
|
||||
/**
|
||||
* DocuAgro - Prompt System da IA Especialista em EUDR
|
||||
* Este prompt transforma o gpt-4o-mini em um especialista em
|
||||
* documentação agrícola brasileira e compliance EUDR
|
||||
*/
|
||||
|
||||
const SYSTEM_PROMPT = `Você é o DocuAgro, um assistente virtual especializado em ajudar produtores rurais brasileiros a organizar sua documentação para compliance com o EUDR (Regulamento da União Europeia contra Desmatamento).
|
||||
|
||||
## SUA PERSONALIDADE
|
||||
- Fale de forma SIMPLES e DIRETA, como se estivesse conversando com um produtor rural
|
||||
- Use linguagem do campo: "tá certinho", "vamos lá", "beleza", "ficou bom"
|
||||
- Seja PACIENTE - muitos produtores não estão acostumados com tecnologia
|
||||
- Quando explicar algo técnico, use analogias do campo
|
||||
- Sempre encoraje o produtor: "tá quase lá!", "mais um pouquinho e fica pronto"
|
||||
- Use emojis com moderação: 📄 🌱 ✅ ❌ 📸 👍
|
||||
|
||||
## O QUE VOCÊ FAZ
|
||||
Você guia o produtor na coleta e envio de 8 documentos obrigatórios:
|
||||
|
||||
1. **CAR** (Cadastro Ambiental Rural) - Número do registro no SICAR
|
||||
2. **CCIR** (Certificado de Cadastro de Imóvel Rural) - Emitido pelo INCRA
|
||||
3. **ITR** (Comprovante de quitação do Imposto Territorial Rural)
|
||||
4. **Georreferenciamento** - Coordenadas/mapa da propriedade
|
||||
5. **Licença Ambiental** - Autorização de atividade agropecuária
|
||||
6. **Contrato de Arrendamento** - Se não for proprietário
|
||||
7. **Nota Fiscal de Venda** - Última NF de venda da produção
|
||||
8. **Declaração de Não Desmatamento** - Autodeclaração pós-dez/2020
|
||||
|
||||
## FLUXO DE CONVERSA
|
||||
|
||||
### Onboarding (etapa: onboarding)
|
||||
Quando o produtor chega pela primeira vez:
|
||||
1. Apresente-se de forma amigável
|
||||
2. Explique em 2-3 frases o que é o DocuAgro e por que é importante
|
||||
3. Pergunte o nome completo do produtor
|
||||
4. Pergunte o CPF (explique que é só pra identificação)
|
||||
5. Pergunte o nome da propriedade
|
||||
6. Pergunte município e estado
|
||||
7. Pergunte a área em hectares (aproximado tá bom)
|
||||
8. Pergunte a cultura principal (soja, café, algodão, etc.)
|
||||
|
||||
### Coleta de Documentos (etapa: coleta_NOMEDOC)
|
||||
Para cada documento:
|
||||
1. Explique O QUE é o documento em linguagem simples
|
||||
2. Explique ONDE encontrar (site, órgão, cartório)
|
||||
3. Peça para enviar FOTO ou PDF
|
||||
4. Quando receber, confirme o recebimento
|
||||
5. Se o documento parece ilegível ou errado, peça para enviar novamente
|
||||
|
||||
### Validação (após receber documento)
|
||||
- Se parece OK: "✅ Recebi seu [documento]! Tá sendo analisado, já te aviso o resultado."
|
||||
- Se ilegível: "📸 A foto ficou meio escura/cortada. Pode tirar outra? Dica: tire num lugar com bastante luz."
|
||||
- Se documento errado: "❌ Parece que esse documento não é o [esperado]. O [documento] é aquele que..."
|
||||
|
||||
## REGRAS IMPORTANTES
|
||||
|
||||
1. NUNCA invente informações sobre legislação
|
||||
2. Se não souber algo, diga "Vou verificar isso pra você"
|
||||
3. Um documento por vez - não peça vários de uma vez
|
||||
4. Se o produtor mandar mensagem que não tem a ver, responda educadamente e volte ao assunto
|
||||
5. Se o produtor disser que não tem um documento, explique como obter (passo a passo)
|
||||
6. Respostas CURTAS - máximo 3-4 parágrafos
|
||||
7. Sempre termine com uma pergunta ou próximo passo claro
|
||||
|
||||
## COMO ORIENTAR PARA OBTER DOCUMENTOS
|
||||
|
||||
### CAR
|
||||
"O CAR é feito pelo site car.gov.br. Se você já tem, é só me mandar o número do recibo ou o PDF. Se não tem, procure o escritório do meio ambiente do seu município que eles ajudam a fazer."
|
||||
|
||||
### CCIR
|
||||
"O CCIR sai pelo site do INCRA (sncr.serpro.gov.br). Se sua propriedade já tá cadastrada, é só imprimir. Senão, precisa ir no INCRA mais perto."
|
||||
|
||||
### ITR
|
||||
"O comprovante do ITR sai pelo site da Receita Federal. Se você declara todo ano, é só baixar o recibo. Pode pedir pro seu contador também."
|
||||
|
||||
### Georreferenciamento
|
||||
"O mapa da propriedade com as coordenadas GPS. Se você tem planta topográfica ou memorial descritivo, serve. Senão, um técnico agrimensor faz isso."
|
||||
|
||||
### Licença Ambiental
|
||||
"É a licença que autoriza a atividade na sua propriedade. Sai pelo órgão ambiental do estado (SEMA, IMA, IEMA, depende do estado)."
|
||||
|
||||
### Contrato de Arrendamento
|
||||
"Se a terra não é sua, precisa do contrato com o dono. Se é proprietário, não precisa desse documento - me avisa que eu pulo."
|
||||
|
||||
### Nota Fiscal
|
||||
"A última nota de venda da sua produção. Pode ser a NF-e (nota eletrônica) ou NF de produtor rural."
|
||||
|
||||
### Declaração de Não Desmatamento
|
||||
"Essa eu mesmo gero pra você! É uma declaração que diz que não teve desmatamento na sua propriedade depois de dezembro de 2020."
|
||||
|
||||
## CONTEXTO EUDR
|
||||
O Regulamento (UE) 2023/1115 (EUDR) proíbe a importação na UE de commodities (soja, café, cacau, óleo de palma, borracha, gado, madeira) produzidas em áreas desmatadas após 31/12/2020.
|
||||
Produtores brasileiros que exportam (direto ou via cooperativa/trading) precisam comprovar:
|
||||
- Geolocalização da produção
|
||||
- Que não houve desmatamento após dez/2020
|
||||
- Conformidade ambiental da propriedade
|
||||
|
||||
## FORMATO DAS RESPOSTAS
|
||||
Responda SEMPRE em texto puro (sem markdown complexo). Use apenas:
|
||||
- Negrito com * para destaque
|
||||
- Emojis com moderação
|
||||
- Quebras de linha para organizar
|
||||
|
||||
ESTADO ATUAL DO PRODUTOR (será injetado dinamicamente):
|
||||
{ESTADO_PRODUTOR}`;
|
||||
|
||||
// Mapa de tipos de documento para nomes amigáveis
|
||||
const TIPOS_DOCUMENTO = {
|
||||
car: { nome: 'CAR (Cadastro Ambiental Rural)', etapa: 'coleta_car' },
|
||||
ccir: { nome: 'CCIR (Certificado de Cadastro de Imóvel Rural)', etapa: 'coleta_ccir' },
|
||||
itr: { nome: 'ITR (Imposto Territorial Rural)', etapa: 'coleta_itr' },
|
||||
georreferenciamento: { nome: 'Georreferenciamento da Propriedade', etapa: 'coleta_geo' },
|
||||
licenca_ambiental: { nome: 'Licença Ambiental', etapa: 'coleta_licenca' },
|
||||
contrato_arrendamento: { nome: 'Contrato de Arrendamento', etapa: 'coleta_contrato' },
|
||||
nota_fiscal: { nome: 'Nota Fiscal de Venda', etapa: 'coleta_nf' },
|
||||
declaracao_desmatamento: { nome: 'Declaração de Não Desmatamento', etapa: 'coleta_declaracao' }
|
||||
};
|
||||
|
||||
// Ordem de coleta dos documentos
|
||||
const ORDEM_COLETA = [
|
||||
'car', 'ccir', 'itr', 'georreferenciamento',
|
||||
'licenca_ambiental', 'contrato_arrendamento',
|
||||
'nota_fiscal', 'declaracao_desmatamento'
|
||||
];
|
||||
|
||||
/**
|
||||
* Gera o prompt completo com estado do produtor injetado
|
||||
*/
|
||||
function gerarPrompt(produtor, documentos, etapaAtual) {
|
||||
let estado = '';
|
||||
|
||||
if (produtor) {
|
||||
estado += `\nNome: ${produtor.nome || 'Não informado'}`;
|
||||
estado += `\nCPF/CNPJ: ${produtor.cpf_cnpj || 'Não informado'}`;
|
||||
estado += `\nPropriedade: ${produtor.propriedade_nome || 'Não informada'}`;
|
||||
estado += `\nMunicípio/Estado: ${produtor.propriedade_municipio || '?'}/${produtor.propriedade_estado || '?'}`;
|
||||
estado += `\nÁrea: ${produtor.propriedade_area_ha ? produtor.propriedade_area_ha + ' ha' : 'Não informada'}`;
|
||||
estado += `\nCultura: ${produtor.cultura_principal || 'Não informada'}`;
|
||||
estado += `\nEtapa atual: ${etapaAtual || produtor.etapa_atual}`;
|
||||
estado += `\nDocumentos completos: ${produtor.docs_completos}/${produtor.docs_total}`;
|
||||
|
||||
if (documentos && documentos.length > 0) {
|
||||
estado += '\n\nDocumentos enviados:';
|
||||
documentos.forEach(doc => {
|
||||
const info = TIPOS_DOCUMENTO[doc.tipo];
|
||||
estado += `\n- ${info ? info.nome : doc.tipo}: ${doc.status}`;
|
||||
});
|
||||
}
|
||||
|
||||
// Próximo documento a coletar
|
||||
if (documentos) {
|
||||
const tiposEnviados = documentos.map(d => d.tipo);
|
||||
const proximo = ORDEM_COLETA.find(t => !tiposEnviados.includes(t));
|
||||
if (proximo) {
|
||||
estado += `\n\nPróximo documento a coletar: ${TIPOS_DOCUMENTO[proximo].nome}`;
|
||||
} else {
|
||||
estado += '\n\nTodos os documentos já foram enviados!';
|
||||
}
|
||||
}
|
||||
} else {
|
||||
estado = '\nProdutor novo - ainda não cadastrado. Iniciar onboarding.';
|
||||
}
|
||||
|
||||
return SYSTEM_PROMPT.replace('{ESTADO_PRODUTOR}', estado);
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
SYSTEM_PROMPT,
|
||||
TIPOS_DOCUMENTO,
|
||||
ORDEM_COLETA,
|
||||
gerarPrompt
|
||||
};
|
||||
179
src/setup-db.js
Normal file
179
src/setup-db.js
Normal file
@@ -0,0 +1,179 @@
|
||||
/**
|
||||
* DocuAgro - Setup do Banco de Dados SQLite
|
||||
* Cria todas as tabelas necessárias para o MVP
|
||||
*/
|
||||
|
||||
const Database = require('better-sqlite3');
|
||||
const path = require('path');
|
||||
const fs = require('fs');
|
||||
|
||||
// Garante que o diretório data existe
|
||||
const dataDir = path.join(__dirname, '..', 'data');
|
||||
if (!fs.existsSync(dataDir)) fs.mkdirSync(dataDir, { recursive: true });
|
||||
|
||||
const dbPath = process.env.DB_PATH || path.join(dataDir, 'docuagro.db');
|
||||
const db = new Database(dbPath);
|
||||
|
||||
// Habilita WAL para melhor performance
|
||||
db.pragma('journal_mode = WAL');
|
||||
db.pragma('foreign_keys = ON');
|
||||
|
||||
console.log('🌱 Criando banco de dados DocuAgro...\n');
|
||||
|
||||
// === Tabela: Cooperativas ===
|
||||
db.exec(`
|
||||
CREATE TABLE IF NOT EXISTS cooperativas (
|
||||
id TEXT PRIMARY KEY,
|
||||
nome TEXT NOT NULL,
|
||||
cnpj TEXT,
|
||||
email TEXT,
|
||||
telefone TEXT,
|
||||
senha_hash TEXT,
|
||||
criado_em DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
atualizado_em DATETIME DEFAULT CURRENT_TIMESTAMP
|
||||
)
|
||||
`);
|
||||
console.log('✅ Tabela cooperativas criada');
|
||||
|
||||
// === Tabela: Produtores ===
|
||||
db.exec(`
|
||||
CREATE TABLE IF NOT EXISTS produtores (
|
||||
id TEXT PRIMARY KEY,
|
||||
cooperativa_id TEXT NOT NULL,
|
||||
nome TEXT NOT NULL,
|
||||
cpf_cnpj TEXT,
|
||||
telefone TEXT,
|
||||
telegram_chat_id TEXT UNIQUE,
|
||||
telegram_username TEXT,
|
||||
propriedade_nome TEXT,
|
||||
propriedade_municipio TEXT,
|
||||
propriedade_estado TEXT,
|
||||
propriedade_area_ha REAL,
|
||||
cultura_principal TEXT,
|
||||
status TEXT DEFAULT 'pendente' CHECK(status IN ('pendente', 'em_andamento', 'completo', 'irregular')),
|
||||
etapa_atual TEXT DEFAULT 'onboarding',
|
||||
docs_completos INTEGER DEFAULT 0,
|
||||
docs_total INTEGER DEFAULT 8,
|
||||
criado_em DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
atualizado_em DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (cooperativa_id) REFERENCES cooperativas(id)
|
||||
)
|
||||
`);
|
||||
console.log('✅ Tabela produtores criada');
|
||||
|
||||
// === Tabela: Documentos ===
|
||||
db.exec(`
|
||||
CREATE TABLE IF NOT EXISTS documentos (
|
||||
id TEXT PRIMARY KEY,
|
||||
produtor_id TEXT NOT NULL,
|
||||
tipo TEXT NOT NULL CHECK(tipo IN (
|
||||
'car', 'ccir', 'itr', 'georreferenciamento',
|
||||
'licenca_ambiental', 'contrato_arrendamento',
|
||||
'nota_fiscal', 'declaracao_desmatamento'
|
||||
)),
|
||||
status TEXT DEFAULT 'pendente' CHECK(status IN (
|
||||
'pendente', 'enviado', 'validando', 'aprovado', 'rejeitado', 'vencido'
|
||||
)),
|
||||
arquivo_path TEXT,
|
||||
arquivo_nome TEXT,
|
||||
arquivo_tipo TEXT,
|
||||
arquivo_tamanho INTEGER,
|
||||
dados_extraidos TEXT,
|
||||
validacao_resultado TEXT,
|
||||
validacao_detalhes TEXT,
|
||||
validade DATE,
|
||||
enviado_em DATETIME,
|
||||
validado_em DATETIME,
|
||||
criado_em DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
atualizado_em DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (produtor_id) REFERENCES produtores(id)
|
||||
)
|
||||
`);
|
||||
console.log('✅ Tabela documentos criada');
|
||||
|
||||
// === Tabela: Conversas (histórico) ===
|
||||
db.exec(`
|
||||
CREATE TABLE IF NOT EXISTS conversas (
|
||||
id INTEGER PRIMARY KEY AUTOINCREMENT,
|
||||
produtor_id TEXT NOT NULL,
|
||||
chat_id TEXT NOT NULL,
|
||||
role TEXT NOT NULL CHECK(role IN ('user', 'assistant', 'system')),
|
||||
conteudo TEXT NOT NULL,
|
||||
tipo_mensagem TEXT DEFAULT 'texto',
|
||||
criado_em DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (produtor_id) REFERENCES produtores(id)
|
||||
)
|
||||
`);
|
||||
console.log('✅ Tabela conversas criada');
|
||||
|
||||
// === Tabela: Dossiês ===
|
||||
db.exec(`
|
||||
CREATE TABLE IF NOT EXISTS dossies (
|
||||
id TEXT PRIMARY KEY,
|
||||
produtor_id TEXT NOT NULL,
|
||||
arquivo_path TEXT NOT NULL,
|
||||
versao INTEGER DEFAULT 1,
|
||||
docs_incluidos TEXT,
|
||||
gerado_em DATETIME DEFAULT CURRENT_TIMESTAMP,
|
||||
FOREIGN KEY (produtor_id) REFERENCES produtores(id)
|
||||
)
|
||||
`);
|
||||
console.log('✅ Tabela dossiês criada');
|
||||
|
||||
// === Tabela: Usuarios (autenticação JWT) ===
|
||||
db.exec(`
|
||||
CREATE TABLE IF NOT EXISTS usuarios (
|
||||
id TEXT PRIMARY KEY,
|
||||
cooperativa_id TEXT,
|
||||
username TEXT UNIQUE NOT NULL,
|
||||
senha_hash TEXT NOT NULL,
|
||||
nome TEXT,
|
||||
role TEXT DEFAULT 'admin',
|
||||
criado_em DATETIME DEFAULT CURRENT_TIMESTAMP
|
||||
)
|
||||
`);
|
||||
console.log('✅ Tabela usuarios criada');
|
||||
|
||||
// === Índices ===
|
||||
db.exec(`
|
||||
CREATE INDEX IF NOT EXISTS idx_produtores_cooperativa ON produtores(cooperativa_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_produtores_telegram ON produtores(telegram_chat_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_produtores_status ON produtores(status);
|
||||
CREATE INDEX IF NOT EXISTS idx_documentos_produtor ON documentos(produtor_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_documentos_tipo ON documentos(tipo);
|
||||
CREATE INDEX IF NOT EXISTS idx_documentos_status ON documentos(status);
|
||||
CREATE INDEX IF NOT EXISTS idx_conversas_produtor ON conversas(produtor_id);
|
||||
CREATE INDEX IF NOT EXISTS idx_conversas_chat ON conversas(chat_id);
|
||||
`);
|
||||
console.log('✅ Índices criados');
|
||||
|
||||
// === Cooperativa padrão (MVP) ===
|
||||
const coopExiste = db.prepare('SELECT id FROM cooperativas WHERE id = ?').get('coop_001');
|
||||
if (!coopExiste) {
|
||||
db.prepare(`
|
||||
INSERT INTO cooperativas (id, nome, cnpj, email)
|
||||
VALUES (?, ?, ?, ?)
|
||||
`).run('coop_001', 'Cooperativa Piloto', '00.000.000/0001-00', 'contato@cooperativa.com');
|
||||
console.log('✅ Cooperativa piloto criada');
|
||||
}
|
||||
|
||||
// === Usuário admin padrão ===
|
||||
const bcrypt = require('bcryptjs');
|
||||
const { v4: uuidv4 } = require('uuid');
|
||||
|
||||
const adminExiste = db.prepare("SELECT id FROM usuarios WHERE username = 'admin'").get();
|
||||
if (!adminExiste) {
|
||||
const senhaHash = bcrypt.hashSync('DocuAgro2026!', 10);
|
||||
db.prepare(`
|
||||
INSERT INTO usuarios (id, cooperativa_id, username, senha_hash, nome, role)
|
||||
VALUES (?, 'coop_001', 'admin', ?, 'Administrador', 'admin')
|
||||
`).run(uuidv4(), senhaHash);
|
||||
console.log('✅ Usuário admin padrão criado (admin / DocuAgro2026!)');
|
||||
} else {
|
||||
console.log('ℹ️ Usuário admin já existe');
|
||||
}
|
||||
|
||||
console.log('\n🎉 Banco de dados configurado com sucesso!');
|
||||
console.log(`📁 Arquivo: ${dbPath}\n`);
|
||||
|
||||
db.close();
|
||||
0
uploads/.gitkeep
Normal file
0
uploads/.gitkeep
Normal file
Reference in New Issue
Block a user