commit a419ac97e7f05c9b70586ef811640ffde54070ad Author: bigtux Date: Tue Feb 10 15:46:03 2026 -0300 Initial commit: DocuAgro - Plataforma EUDR diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..74c8f45 --- /dev/null +++ b/.env.example @@ -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 diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..250865f --- /dev/null +++ b/.gitignore @@ -0,0 +1,11 @@ +node_modules/ +.next/ +dist/ +.env +.env.local +.env*.local +*.log +.DS_Store +coverage/ +.turbo/ +*.tsbuildinfo diff --git a/README.md b/README.md new file mode 100644 index 0000000..faae1fa --- /dev/null +++ b/README.md @@ -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. diff --git a/data/.gitkeep b/data/.gitkeep new file mode 100644 index 0000000..e69de29 diff --git a/data/docuagro.db b/data/docuagro.db new file mode 100644 index 0000000..4583fd7 Binary files /dev/null and b/data/docuagro.db differ diff --git a/docs/docuagro-apresentacao.html b/docs/docuagro-apresentacao.html new file mode 100644 index 0000000..d023eb3 --- /dev/null +++ b/docs/docuagro-apresentacao.html @@ -0,0 +1,1002 @@ + + + + + +DocuAgro - Apresentação + + + + + + + +
+ +

DocuAgro

+
Compliance do produtor, na palma da mão.
+ +
+ Plataforma inteligente para coleta, validação e organização
+ de documentação de produtores rurais para
+ compliance com o Regulamento EUDR +
+ +
🇪🇺 EUDR Compliance Ready
+ + +
+ + + + +
+
+ 🌱 DocuAgro +

O Problema

+ 02 +
+ +

🇪🇺 O que é o EUDR?

+

O Regulamento (UE) 2023/1115 — conhecido como EUDR (European Union Deforestation Regulation) — é a nova legislação europeia que proíbe a importação de commodities produzidas em áreas desmatadas após 31 de dezembro de 2020.

+ +
+

⚠️ Commodities afetadas

+

+ 🫘 Soja  •  ☕ Café  •  🍫 Cacau  •  🌴 Óleo de Palma  •  🌳 Madeira  •  🐄 Gado  •  🔧 Borracha +

+
+ +

📋 O desafio da "Última Milha"

+

Cooperativas e tradings precisam comprovar a conformidade de cada produtor individual da cadeia. Isso significa coletar e validar dezenas de documentos de milhares de produtores rurais, muitos deles:

+ + + +
+

💡 A oportunidade

+

O Brasil é um dos maiores exportadores mundiais dessas commodities. Sem compliance EUDR, produtores perdem acesso ao mercado europeu. O DocuAgro resolve esse gargalo de forma simples e escalável.

+
+ + +
+ + + + +
+
+ 🌱 DocuAgro +

A Solução

+ 03 +
+ +

O DocuAgro é uma plataforma que combina Bot Telegram + Inteligência Artificial + Painel Web para automatizar toda a coleta e validação documental dos produtores.

+ +

🔄 Como Funciona

+ +
+
+
📱
+
1
+
Produtor abre o Bot
+
Acessa pelo Telegram, sem instalar nada
+
+
+
+
🤖
+
2
+
IA guia a coleta
+
Linguagem simples do campo, passo a passo
+
+
+
+
📄
+
3
+
Envia documentos
+
Foto ou PDF, um por vez
+
+
+
+
🔍
+
4
+
Validação automática
+
OCR + IA verificam os dados
+
+
+
+
+
5
+
Dossiê pronto
+
PDF profissional para a cooperativa
+
+
+ +

🎯 Diferenciais

+ +
+
+
💬
+
Zero Fricção
+
Via Telegram, que o produtor já usa. Sem app novo, sem cadastro complexo.
+
+
+
🧠
+
IA Especialista
+
Fala a língua do campo. Guia, orienta e valida com paciência.
+
+
+
+
Automação Total
+
OCR extrai dados, IA valida, PDF é gerado automaticamente.
+
+
+
📊
+
Painel da Cooperativa
+
Dashboard em tempo real com status de cada produtor.
+
+
+
🔒
+
Dados Seguros
+
Armazenamento local, diretórios isolados por produtor.
+
+
+
📈
+
Escalável
+
Atende de 10 a 10.000 produtores sem aumentar equipe.
+
+
+ + +
+ + + + +
+
+ 🌱 DocuAgro +

Documentos e Validação

+ 04 +
+ +

O DocuAgro coleta e valida 8 documentos obrigatórios para compliance EUDR. Cada documento passa por OCR automático para extração e verificação de dados.

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
#DocumentoO que éValidação Automática
1CARCadastro Ambiental Rural (SICAR)Número SICAR + reconhecimento OCR
2CCIRCertificado do Imóvel Rural (INCRA)Código INCRA + OCR
3ITRImposto Territorial RuralCPF/CNPJ + Receita Federal
4GeorreferenciamentoCoordenadas GPS da propriedadeExtração de coordenadas
5Licença AmbientalAutorização do órgão ambientalOCR + verificação de validade
6Contrato de ArrendamentoSe não for proprietárioOCR (opcional — pode ser pulado)
7Nota Fiscal de VendaÚltima NF da produçãoOCR + extração de CPF/CNPJ
8Declaração de Não DesmatamentoAutodeclaração pós-dez/2020Gerada automaticamente pelo sistema
+ +

🔍 Processo de Validação

+ +
+
+
1. Recebimento
+
Produtor envia foto ou PDF pelo Telegram. O arquivo é salvo em diretório isolado.
+
+
+
2. OCR (Tesseract.js)
+
Texto é extraído automaticamente da imagem com reconhecimento em português.
+
+
+
3. Validação por Tipo
+
Algoritmo específico para cada tipo de documento verifica campos-chave (número CAR, código INCRA, CPF, datas, coordenadas).
+
+
+
4. Feedback ao Produtor
+
IA informa o resultado: ✅ Aprovado, 🟡 Em análise, ou ❌ Reenviar com orientações.
+
+
+
5. Próximo Documento
+
Sistema avança automaticamente para o próximo documento da sequência.
+
+
+ + +
+ + + + +
+
+ 🌱 DocuAgro +

Experiência do Produtor

+ 05 +
+ +

O produtor interage 100% via Telegram — um app que já está no celular da maioria dos brasileiros. Sem downloads, sem cadastros complicados.

+ +

📱 Comandos do Bot

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
ComandoFunçãoDescrição
/startIniciar cadastroComeça o onboarding. A IA coleta nome, CPF, propriedade, município e cultura.
/statusVer progressoMostra quais documentos já foram enviados e validados (✅ 🟡 ⬜).
/dossieGerar dossiê PDFCompila todos os documentos em um dossiê profissional para download.
/pularPular documentoSe o produtor não tem o documento agora, pula e continua para o próximo.
/ajudaMenu de ajudaLista de comandos e dicas para envio de documentos.
+ +

🤖 Inteligência Artificial Especialista

+

A IA do DocuAgro foi treinada para se comunicar de forma simples e acolhedora:

+ +
+
+
🗣️
+
Linguagem do Campo
+
Usa expressões como "tá certinho", "beleza", "mais um pouquinho e fica pronto". O produtor se sente à vontade.
+
+
+
🎯
+
Um de Cada Vez
+
Pede um documento por vez, sem sobrecarregar. Explica o que é e onde encontrar antes de pedir.
+
+
+
🔄
+
Orienta Correções
+
Se a foto está escura ou o documento errado, explica o problema e como resolver com dicas práticas.
+
+
+
📍
+
Onde Encontrar
+
Para cada documento, explica o site, órgão ou cartório onde obter, com passo a passo simples.
+
+
+ +
+

💬 Exemplo de Interação

+

+ Bot: 🌱 Beleza! Agora preciso do seu CAR — o Cadastro Ambiental Rural. É aquele registro do SICAR. Se você já tem, manda a foto ou o PDF pra mim. Se não tem, me avisa que eu explico como fazer! 👍

+ Produtor: [envia foto do CAR]

+ Bot: ✅ Recebi seu CAR! Tá sendo analisado, já te aviso o resultado. Enquanto isso, vamos pro próximo: o CCIR do INCRA... +

+
+ + +
+ + + + +
+
+ 🌱 DocuAgro +

Painel da Cooperativa

+ 06 +
+ +

A cooperativa/trading acompanha tudo pelo Painel Web, acessível de qualquer navegador. Visão completa em tempo real.

+ +

📊 Dashboard

+
+
+
👥
+
Total de Produtores
+
Quantos produtores estão cadastrados e em qual estágio do processo.
+
+
+
📈
+
Taxa de Compliance
+
Percentual de produtores com documentação completa e aprovada.
+
+
+
📄
+
Documentos Recebidos
+
Total de documentos por tipo e status (aprovado, pendente, rejeitado).
+
+
+
🔍
+
Busca Avançada
+
Busca por nome, CPF, propriedade ou município para encontrar produtores.
+
+
+ +

🌐 API REST

+

Todos os dados são acessíveis via API para integração com sistemas externos:

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
EndpointMétodoDescrição
/api/dashboardGETEstatísticas gerais do sistema
/api/produtoresGETListar todos os produtores
/api/produtores/:idGETDetalhes de um produtor + documentos
/api/produtores/:id/dossiePOSTGerar dossiê PDF do produtor
/api/exportar/csvGETExportar dados em CSV
+ +

📥 Exportação

+ + + +
+ + + + +
+
+ 🌱 DocuAgro +

Arquitetura Técnica

+ 07 +
+ +

🛠 Stack Tecnológica

+ +
+ Node.js 18+ + Telegraf (Bot) + Express.js (API) + OpenAI GPT-4o-mini + Tesseract.js (OCR) + PDFKit + SQLite + HTML/CSS/JS (Painel) +
+ +

📁 Estrutura do Projeto

+ +
+ docuagro/
+ ├── src/
+ │   ├── index.js              ← Entry point
+ │   ├── bot/
+ │   │   └── telegram-bot.js   ← Bot Telegram (Telegraf)
+ │   ├── api/
+ │   │   └── routes.js         ← API REST (Express)
+ │   └── services/
+ │       ├── ai-service.js    ← Integração OpenAI
+ │       ├── ocr-service.js   ← OCR (Tesseract.js)
+ │       ├── pdf-service.js   ← Geração de dossiê
+ │       ├── database.js      ← Operações SQLite
+ │       └── system-prompt.js ← Prompt da IA EUDR
+ ├── public/                 ← Painel web
+ ├── data/                   ← Banco SQLite
+ └── uploads/                ← Documentos (por produtor) +
+ +

🔐 Segurança

+ +
+
+
🗂️
+
Isolamento de Dados
+
Cada produtor tem seu próprio diretório de uploads. Documentos não se misturam.
+
+
+
🛡️
+
Helmet + CORS
+
Headers de segurança HTTP e controle de origem configurados no servidor Express.
+
+
+
💾
+
Armazenamento Local
+
Dados armazenados em servidor próprio. Sem dependência de cloud pública.
+
+
+
🔒
+
Roadmap de Segurança
+
JWT, HTTPS, rate limiting, criptografia de dados sensíveis e backup automático.
+
+
+ + +
+ + + + +
+
+ 🌱 DocuAgro +

Modelo de Negócio e Roadmap

+ 08 +
+ +

💰 Modelo de Negócio

+ +
+
+
🏢
+
B2B — Cooperativas e Tradings
+
Licenciamento por cooperativa/trading, com cobrança por produtor ativo ou por dossiê gerado. A cooperativa fornece o DocuAgro para seus associados.
+
+
+
📊
+
SaaS — Plano Mensal
+
Planos escaláveis baseados em volume de produtores: Starter (até 100), Pro (até 1.000), Enterprise (ilimitado).
+
+
+ +

🗺️ Roadmap de Produto

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
FaseEntregaStatus
MVPBot Telegram + OCR + PDF + Painel Web básico✅ Pronto
v1.1Autenticação JWT no painel + HTTPS🔜 Em breve
v1.2Notificações automáticas (documentos vencendo)📋 Planejado
v2.0Bot WhatsApp (alcance maior)📋 Planejado
v2.1Integração com SICAR / INCRA (APIs governamentais)📋 Planejado
v3.0Multi-tenant (SaaS completo) + Dashboard analytics avançado📋 Planejado
+ +

🎯 Por que DocuAgro?

+ +
+

+ "O EUDR não é opcional. É lei." +

+

+ Sem compliance, produtores brasileiros perdem o mercado europeu.
+ O DocuAgro transforma um processo burocrático e complexo em uma conversa simples no Telegram.

+ 📱 Simples para o produtor  •  📊 Poderoso para a cooperativa  •  🌱 Essencial para o agro brasileiro +

+
+ +
+

🌱 DocuAgro

+

Compliance do produtor, na palma da mão.

+

AI Vertice • aivertice.com • 2026

+
+ + +
+ + + diff --git a/docs/docuagro-apresentacao.pdf b/docs/docuagro-apresentacao.pdf new file mode 100644 index 0000000..4448277 Binary files /dev/null and b/docs/docuagro-apresentacao.pdf differ diff --git a/docs/guia-completo-vendas.md b/docs/guia-completo-vendas.md new file mode 100644 index 0000000..c89b772 --- /dev/null +++ b/docs/guia-completo-vendas.md @@ -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. 🌱* diff --git a/docs/guia-completo-vendas.pdf b/docs/guia-completo-vendas.pdf new file mode 100644 index 0000000..41713be Binary files /dev/null and b/docs/guia-completo-vendas.pdf differ diff --git a/docs/manual-docuagro.html b/docs/manual-docuagro.html new file mode 100644 index 0000000..c061a8a --- /dev/null +++ b/docs/manual-docuagro.html @@ -0,0 +1,769 @@ + + + + +DocuAgro - Manual Completo + + + + + +
+
🌱
+

DocuAgro

+
Compliance do produtor, na palma da mão.
+
📘 Manual Completo
+

+ Guia completo de uso da plataforma DocuAgro para produtores rurais e cooperativas. + Compliance EUDR via Bot Telegram + Inteligência Artificial + Painel Web. +

+
AI Vertice • Versão 1.0 • Fevereiro 2026
+
+ + +
+
+ 🌱 DocuAgro +

Sumário

+ 02 +
+ +
+ + + + + + + + + + + + + + + +
01O que é o DocuAgroPág. 03
02Para quem éPág. 03
03Guia do Produtor — Bot TelegramPág. 04
04Exemplo Completo de UsoPág. 06
05Documentos Coletados (8)Pág. 07
06Inteligência ArtificialPág. 08
07Guia da Cooperativa — Painel WebPág. 09
08API RESTPág. 10
09Dossiê PDFPág. 11
10Instalação e ConfiguraçãoPág. 12
11Arquitetura TécnicaPág. 13
12FAQ e TroubleshootingPág. 14
+
+ + +
+ + +
+
+ 🌱 DocuAgro +

1. O que é o DocuAgro

+ 03 +
+ +

O DocuAgro é uma plataforma que automatiza a coleta, validação e organização de documentação de produtores rurais para compliance com o EUDR (Regulamento da União Europeia contra Desmatamento — EU 2023/1115).

+ +

A plataforma combina três componentes:

+ + +
+

🇪🇺 O que é o 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 e Borracha. Produtores brasileiros que exportam precisam comprovar geolocalização, ausência de desmatamento e conformidade ambiental.

+
+ +

2. Para quem é

+ +

👨‍🌾 Produtores Rurais

+

Qualquer produtor que cultive commodities afetadas pelo EUDR e exporte (direta ou indiretamente via cooperativa/trading) para a Europa. O produtor interage 100% pelo Telegram, sem precisar instalar nenhum aplicativo novo.

+ +

🏢 Cooperativas e Tradings

+

Organizações que precisam comprovar a conformidade de dezenas a milhares de produtores associados. Acompanham tudo pelo Painel Web com dashboard, busca, dossiês e exportação CSV.

+ +

🎯 Mercado-Alvo

+ + + +
+ + +
+
+ 🌱 DocuAgro +

3. Guia do Produtor — Bot Telegram

+ 04 +
+ +

O produtor acessa o DocuAgro pelo Telegram, buscando por @docuagro_bot. Todo o processo é guiado por inteligência artificial em linguagem simples.

+ +

📱 Comandos Disponíveis

+ + + + + + + + + +
ComandoFunçãoQuando usar
/startIniciar cadastroPrimeira vez no bot
/statusVer progressoA qualquer momento
/dossieGerar dossiê PDFApós enviar documentos
/pularPular documentoSe não tem o doc agora
/ajudaMenu de ajudaSe tiver dúvidas
+ +

🔄 Fluxo Completo — Passo a Passo

+ +

Etapa 1: Cadastro (Onboarding)

+

Ao enviar /start, a IA faz perguntas simples para conhecer o produtor:

+ +
+
1
+
Nome completo — "Qual é o seu nome completo?"
+
2
+
CPF — Para identificação no sistema
+
3
+
Nome da propriedade — Ex: "Fazenda Boa Vista"
+
4
+
Município e estado — Ex: "Sorriso, MT"
+
5
+
Área em hectares — Aproximado é suficiente
+
6
+
Cultura principal — Soja, café, gado, etc.
+
+ +

Etapa 2: Coleta de Documentos

+

Após o cadastro, a IA pede um documento por vez, na seguinte ordem:

+
    +
  1. CAR (Cadastro Ambiental Rural)
  2. +
  3. CCIR (Certificado de Cadastro de Imóvel Rural)
  4. +
  5. ITR (Imposto Territorial Rural)
  6. +
  7. Georreferenciamento (coordenadas da propriedade)
  8. +
  9. Licença Ambiental
  10. +
  11. Contrato de Arrendamento (se aplicável)
  12. +
  13. Nota Fiscal de Venda
  14. +
  15. Declaração de Não Desmatamento (gerada automaticamente)
  16. +
+ +
+

📸 Dicas para enviar documentos

+
    +
  • Tire foto em lugar com boa iluminação
  • +
  • Enquadre o documento inteiro na foto
  • +
  • PDF é melhor que foto (mais legível)
  • +
  • Envie um documento por vez
  • +
+
+ +

Etapa 3: Dossiê

+

Quando todos os documentos forem enviados, o produtor pode gerar o dossiê com /dossie. O bot gera um PDF profissional e envia direto no chat.

+ + +
+ + +
+
+ 🌱 DocuAgro +

3. Guia do Produtor (continuação)

+ 05 +
+ +

📊 Verificando o Status

+

A qualquer momento, o produtor pode enviar /status para ver como está sua documentação:

+ +
+
📊 Status da sua documentação

+ 👤 João da Silva
+ 🏡 Fazenda Boa Vista

+ ✅ CAR (Cadastro Ambiental Rural)
+ ✅ CCIR (Certificado do INCRA)
+ 🟡 ITR (Imposto Territorial Rural)
+ ⬜ Georreferenciamento
+ ⬜ Licença Ambiental
+ ⬜ Contrato de Arrendamento
+ ⬜ Nota Fiscal de Venda
+ ⬜ Declaração de Não Desmatamento

+ 📈 Progresso: 2/8 +
+
+ +

Legenda dos ícones:

+ + + + + + + + + +
ÍconeSignificado
Documento aprovado
🟡Enviado, em análise
Rejeitado (precisa reenviar)
⚠️Vencido
Não enviado ainda
+ +

⏭️ Pulando um Documento

+

Se o produtor não tem um documento no momento, pode usar /pular para ir ao próximo. Ele poderá enviar o documento pulado depois.

+ +
+
/pular
+
⏭️ Pulei o Georreferenciamento da Propriedade. Pode enviar depois! Agora vamos pra Licença Ambiental...
+
+ +

❓ Pedindo Ajuda

+

O comando /ajuda mostra todos os comandos disponíveis e dicas de envio. Além disso, o produtor pode fazer qualquer pergunta em texto e a IA responderá com orientações sobre o que é cada documento e onde obtê-lo.

+ +
+
O que é o CAR?
+
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. 👍
+
+ + +
+ + +
+
+ 🌱 DocuAgro +

4. Exemplo Completo de Uso

+ 06 +
+ +

Veja abaixo uma conversa típica entre um produtor e o DocuAgro:

+ +
+
/start
+
🌱 Bem-vindo ao DocuAgro!

Oi! Eu sou o DocuAgro, seu assistente para organizar a documentação da sua propriedade rural.

📋 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.

São 8 documentos no total. Vamos começar?

Primeiro, me conta: qual é o seu nome completo?
+
João da Silva Ferreira
+
Beleza, João! 👍 Agora me passa seu CPF (é só pra identificação no sistema).
+
123.456.789-00
+
Certinho! Qual o nome da sua propriedade?
+
Fazenda Boa Vista
+
Boa! Em qual município e estado fica a Fazenda Boa Vista?
+
Sorriso, Mato Grosso
+
Sorriso, MT — anotado! Qual a área aproximada em hectares?
+
500 hectares
+
500 ha, tá ótimo! E qual a cultura principal? (soja, café, gado...)
+
Soja
+
Perfeito, João! Tudo anotado. Agora vamos pros documentos. 📄

O primeiro é o CAR — Cadastro Ambiental Rural. É 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!
+
[ João envia foto do CAR ]
+
✅ Recebi seu CAR! Tá sendo analisado, já te aviso o resultado. 📝 Número SICAR encontrado: MT-5107248-ABC123...

Agora vamos pro próximo: o CCIR (Certificado de Cadastro de Imóvel Rural do INCRA)...
+
+ +
+

💡 Observações

+
    +
  • A IA extrai dados automaticamente via OCR (número do CAR, código INCRA, CPF, datas, etc.)
  • +
  • Se a foto estiver ilegível, o bot pede para reenviar com dicas práticas
  • +
  • O processo completo leva em média 15-30 minutos se o produtor tiver todos os documentos em mãos
  • +
  • O produtor pode parar a qualquer momento e continuar depois — o progresso é salvo
  • +
+
+ + +
+ + +
+
+ 🌱 DocuAgro +

5. Documentos Coletados

+ 07 +
+ +

O DocuAgro coleta e valida 8 documentos obrigatórios para compliance EUDR:

+ + + + + + + + + + + + + +
#DocumentoO que éOnde obterValidação OCR
1CARCadastro Ambiental Ruralcar.gov.br (SICAR)Número SICAR
2CCIRCertificado Imóvel Ruralsncr.serpro.gov.br (INCRA)Código INCRA
3ITRImposto Territorial RuralReceita FederalCPF/CNPJ
4GeoGeorreferenciamentoTécnico agrimensorCoordenadas GPS
5LicençaLicença AmbientalÓrgão estadual (SEMA/IMA)Validade + OCR
6ContratoContrato de ArrendamentoCartório (se não for dono)OCR geral
7NFNota Fiscal de VendaÚltima NF-e ou NF produtorCPF/CNPJ + dados
8DeclaraçãoNão DesmatamentoGerada automaticamenteAutomática ✅
+ +

🔍 Como a Validação Funciona

+

Cada documento enviado passa por um pipeline de validação:

+ +
    +
  1. Recebimento: Arquivo salvo em diretório isolado do produtor
  2. +
  3. OCR (Tesseract.js): Extração de texto em português com análise de confiança
  4. +
  5. Validação por tipo: Algoritmo específico busca campos-chave: +
      +
    • CAR → procura padrão do número SICAR (XX-XXXXXXX-XXXX...)
    • +
    • CCIR → procura menção ao INCRA + código do imóvel
    • +
    • ITR → procura "Receita Federal" ou "ITR" + CPF/CNPJ
    • +
    • Georreferenciamento → extrai coordenadas geográficas
    • +
    • Licença → busca "Licença" + datas de validade
    • +
    • NF → procura "Nota Fiscal" / "DANFE" + CPF/CNPJ
    • +
    +
  6. +
  7. Resultado: Aprovado ✅, Em análise 🟡, ou Reenviar ❌
  8. +
  9. Dados extraídos: Salvos no banco para inclusão no dossiê
  10. +
+ +
+

⚠️ Documento 6 — Contrato de Arrendamento

+

Este documento só é necessário se o produtor não for proprietário da terra. Se for dono, pode usar /pular para ir ao próximo.

+
+ +
+

✨ Documento 8 — Declaração de Não Desmatamento

+

Este documento é gerado automaticamente pelo sistema. O produtor não precisa providenciar — o DocuAgro cria a autodeclaração com base nos dados cadastrados.

+
+ + +
+ + +
+
+ 🌱 DocuAgro +

6. Inteligência Artificial

+ 08 +
+ +

O DocuAgro utiliza o modelo GPT-4o-mini da OpenAI, configurado como um especialista em documentação agrícola brasileira e compliance EUDR.

+ +

🧠 Como a IA funciona

+ +

Personalidade

+ + +

Contexto Dinâmico

+

A cada mensagem, a IA recebe o contexto completo do produtor:

+ + +

Extração Inteligente de Dados

+

Durante o onboarding, a IA usa uma segunda chamada ao GPT para extrair dados estruturados das mensagens do produtor (nome, CPF, município, etc.) e salvar automaticamente no banco.

+ +

Orientação para Obter Documentos

+

A IA sabe orientar o produtor sobre como e onde obter cada documento:

+ +
+
Não tenho o CCIR, como faço?
+
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? 😊
+
+ +

🔧 Configuração Técnica

+ + + + + + + + +
ParâmetroValorMotivo
Modelogpt-4o-miniCusto baixo, boa qualidade
Temperature0.7Respostas naturais mas consistentes
Max tokens800Respostas curtas e diretas
Presence penalty0.1Evita repetição
+ + +
+ + +
+
+ 🌱 DocuAgro +

7. Guia da Cooperativa — Painel Web

+ 09 +
+ +

A cooperativa ou trading acompanha todos os produtores pelo Painel Web, acessível de qualquer navegador.

+ +

📊 Dashboard

+

A tela inicial mostra 5 indicadores em tempo real:

+ + + + + + + + + +
IndicadorDescrição
👥 Total de ProdutoresQuantos produtores estão cadastrados
✅ CompletosProdutores com todos os docs aprovados
📋 Em AndamentoProdutores enviando documentos
⚠️ PendentesProdutores que não iniciaram ou pararam
📄 Documentos RecebidosTotal de documentos no sistema
+ +

Abaixo do dashboard há a barra de Compliance EUDR Geral — mostra o percentual de produtores em conformidade.

+ +

👥 Lista de Produtores

+

Tabela com todos os produtores cadastrados, mostrando:

+ + +

🔍 Busca

+

Campo de busca no topo da lista permite filtrar por nome, CPF, propriedade ou município.

+ +

📥 Exportações

+ +

Dossiê PDF

+

Ao clicar em "Dossiê" na lista de produtores, o sistema gera um PDF profissional com:

+ + +

Exportar CSV

+

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.

+ + +
+ + +
+
+ 🌱 DocuAgro +

8. API REST

+ 10 +
+ +

Todos os dados do DocuAgro são acessíveis via API REST para integração com sistemas externos.

+ + + + + + + + + + + + +
EndpointMétodoDescriçãoRetorno
/api/healthGETHealth check do sistemaStatus, versão, timestamp
/api/dashboardGETEstatísticas geraisTotal produtores, completos, pendentes, docs
/api/produtoresGETListar todos os produtoresArray com dados e status de cada um
/api/produtores/:idGETDetalhe de um produtorDados + documentos + último dossiê
/api/produtores/:id/dossiePOSTGerar dossiê PDFCaminho do arquivo gerado
/api/dossie/download/:arquivoGETDownload do dossiêArquivo PDF
/api/exportar/csvGETExportar todos em CSVArquivo CSV
+ +

📡 Exemplos de Uso

+ +
+ # Health check
+ curl http://localhost:3100/api/health

+ # Dashboard
+ curl http://localhost:3100/api/dashboard

+ # Listar produtores
+ curl http://localhost:3100/api/produtores

+ # Gerar dossiê de um produtor
+ curl -X POST http://localhost:3100/api/produtores/PROD_ID/dossie

+ # Exportar CSV
+ curl -o produtores.csv http://localhost:3100/api/exportar/csv +
+ +
+

⚠️ Segurança da API (MVP)

+

No MVP, a API não possui autenticação. Para produção, será necessário implementar JWT, rate limiting e HTTPS. Consulte o roadmap de segurança.

+
+ + +
+ + +
+
+ 🌱 DocuAgro +

9. Dossiê PDF

+ 11 +
+ +

O dossiê é o produto final do DocuAgro — um PDF profissional que reúne toda a documentação do produtor para fins de compliance EUDR.

+ +

📄 Estrutura do Dossiê

+ + + + + + + + + + +
SeçãoConteúdo
CapaLogo DocuAgro, nome do produtor, propriedade, status de compliance, data
Dados do ProdutorNome, CPF, propriedade, município, estado, área, cultura, data de cadastro
Resumo dos DocumentosTabela com todos os 8 documentos, status e data de envio
Detalhes (por doc)Status, arquivo, dados extraídos pelo OCR, resultado da validação, miniatura
Declaração FinalDeclaração de conformidade EUDR com espaço para assinatura
RodapéNúmero da página em todas as páginas
+ +

🎨 Visual

+

O dossiê usa um tema profissional verde com:

+ + +

📋 Geração

+

O dossiê pode ser gerado de 3 formas:

+
    +
  1. Pelo produtor: comando /dossie no bot Telegram
  2. +
  3. Pela cooperativa: botão "Dossiê" no painel web
  4. +
  5. Via API: POST /api/produtores/:id/dossie
  6. +
+ +

O PDF é gerado com PDFKit e salvo em uploads/dossies/. O nome do arquivo segue o padrão: dossie_[CPF]_[data_hora].pdf

+ + +
+ + +
+
+ 🌱 DocuAgro +

10. Instalação e Configuração

+ 12 +
+ +

📋 Pré-requisitos

+ + +

🚀 Setup

+
+ # 1. Clonar repositório
+ git clone http://137.184.77.7:3000/bigtux/docuagro.git
+ cd docuagro

+ # 2. Instalar dependências
+ npm install

+ # 3. Configurar variáveis de ambiente
+ cp .env.example .env
+ nano .env   # Preencher tokens

+ # 4. Criar banco de dados
+ npm run setup

+ # 5. Iniciar
+ npm start +
+ +

⚙️ Arquivo .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

+ # Uploads
+ UPLOAD_DIR=./uploads +
+ +

🔄 Rodar com PM2 (Produção)

+
+ # Iniciar com PM2
+ npx pm2 start src/index.js --name docuagro

+ # Ver logs
+ npx pm2 logs docuagro

+ # Reiniciar
+ npx pm2 restart docuagro

+ # Parar
+ npx pm2 stop docuagro +
+ + +
+ + +
+
+ 🌱 DocuAgro +

11. Arquitetura Técnica

+ 13 +
+ +

🛠 Stack

+
+ Node.js 18+ + Telegraf 4.16 + Express.js 4 + OpenAI GPT-4o-mini + Tesseract.js + PDFKit + SQLite (better-sqlite3) + HTML/CSS/JS +
+ +

📁 Estrutura de Pastas

+
+ docuagro/
+ ├── src/
+ │   ├── index.js              ← Entry point (Express + Bot)
+ │   ├── setup-db.js          ← Criação do banco SQLite
+ │   ├── bot/
+ │   │   └── telegram-bot.js   ← Bot Telegram (Telegraf)
+ │   ├── api/
+ │   │   └── routes.js         ← API REST (Express)
+ │   └── services/
+ │       ├── ai-service.js    ← Integração OpenAI
+ │       ├── ocr-service.js   ← OCR (Tesseract.js)
+ │       ├── pdf-service.js   ← Geração de dossiê PDF
+ │       ├── database.js      ← Operações SQLite
+ │       └── system-prompt.js ← Prompt EUDR da IA
+ ├── public/                 ← Painel web (HTML/CSS/JS)
+ ├── data/                   ← Banco SQLite
+ ├── uploads/                ← Documentos dos produtores
+ ├── logos/                  ← Logo oficial
+ └── docs/                   ← Documentação e manuais +
+ +

💾 Banco de Dados (SQLite)

+ + + + + + + + + +
TabelaFunçãoCampos principais
cooperativasCooperativas cadastradasid, nome, cnpj, contato
produtoresProdutores ruraisid, nome, cpf, propriedade, município, cultura, etapa_atual, status
documentosDocumentos enviadosid, produtor_id, tipo, status, arquivo_path, dados_extraidos
conversasHistórico de mensagensid, produtor_id, role, conteudo, timestamp
dossiesDossiês geradosid, produtor_id, arquivo_path, docs_incluidos
+ +

🔐 Segurança

+ + + +
+ + +
+
+ 🌱 DocuAgro +

12. FAQ e Troubleshooting

+ 14 +
+ +

❓ Perguntas Frequentes

+ +

O produtor precisa instalar algum app?

+

Não. O DocuAgro funciona 100% pelo Telegram, que a maioria já tem instalado.

+ +

Funciona pelo WhatsApp?

+

No momento não. O WhatsApp está no roadmap (v2.0). Atualmente funciona apenas pelo Telegram.

+ +

Quanto custa para o produtor?

+

O modelo é B2B — a cooperativa paga e oferece o serviço aos seus associados. O produtor não paga nada.

+ +

Os documentos ficam seguros?

+

Sim. São armazenados em servidor próprio, em diretórios isolados por produtor. Não são enviados para cloud pública.

+ +

E se o produtor parar no meio?

+

O progresso é salvo automaticamente. Ele pode voltar a qualquer momento e continuar de onde parou.

+ +

A IA pode errar na validação?

+

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.

+ +

Quantos produtores o sistema aguenta?

+

O SQLite suporta até centenas de milhares de registros. Para escala maior (10k+ produtores simultâneos), recomenda-se migrar para PostgreSQL.

+ +

🔧 Troubleshooting

+ + + + + + + + + + + + +
ProblemaSolução
Bot não respondeVerificar se o processo está rodando: npx pm2 status
Erro 409 (Conflict)Duas instâncias rodando. Matar todas e reiniciar: npx pm2 delete docuagro && npx pm2 start src/index.js --name docuagro
Token inválido (401)Verificar TELEGRAM_BOT_TOKEN no arquivo .env
IA não respondeVerificar OPENAI_API_KEY e saldo da conta OpenAI
OCR com confiança baixaPedir ao produtor foto com melhor iluminação e enquadramento
Painel não carregaVerificar se a porta 3100 está acessível: curl localhost:3100/api/health
Banco corrompidoFazer backup e recriar: npm run setup
+ +
+

🌱 DocuAgro

+

Compliance do produtor, na palma da mão.

+

AI Vertice • aivertice.com • 2026

+

Dúvidas? Suporte técnico disponível.

+
+ + +
+ + + diff --git a/docs/manual-docuagro.pdf b/docs/manual-docuagro.pdf new file mode 100644 index 0000000..26ac8cb Binary files /dev/null and b/docs/manual-docuagro.pdf differ diff --git a/docs/pitch-deck-docuagro.html b/docs/pitch-deck-docuagro.html new file mode 100644 index 0000000..de59e77 --- /dev/null +++ b/docs/pitch-deck-docuagro.html @@ -0,0 +1,690 @@ + + + + +DocuAgro - Pitch Deck + + + + + +
+
🌱
+

DocuAgro

+
Compliance do produtor, na palma da mão.
+
+ Plataforma de compliance EUDR para o agronegócio brasileiro
+ Bot Telegram + IA + Painel Web +
+
AI Vertice • Pitch Confidencial • Fevereiro 2026
+
+ + +
+
+
01 — O Problema
+

O EUDR vai bloquear
exportações brasileiras

+
+ +

O Regulamento (UE) 2023/1115 proíbe a importação na Europa de commodities produzidas em áreas desmatadas após dezembro de 2020. Sem compliance, sem exportação.

+ +
+
+
🫘
+
Soja
+
Maior exportação BR para UE
+
+
+
+
Café
+
Brasil = #1 mundial
+
+
+
🐄
+
Gado
+
Carne bovina, couro
+
+
+
🌳
+
+ Cacau, Madeira, Borracha, Óleo de Palma
+
7 commodities afetadas
+
+
+ +

😰 O desafio da "Última Milha"

+
+
+
📋
+
8 documentos por produtor
+
CAR, CCIR, ITR, Georreferenciamento, Licença Ambiental, Contrato, NF, Declaração — de CADA produtor individual.
+
+
+
👨‍🌾
+
Milhares de produtores
+
Uma cooperativa grande tem 2.000-10.000 associados. Coletar documentos de todos manualmente? Impossível.
+
+
+ +
+

"Uma cooperativa com 1.000 produtores precisaria de 8.000 documentos coletados, validados e organizados. Manualmente, isso levaria meses e custaria uma fortuna."

+
+ + +
+ + +
+
+
02 — O Mercado
+

Um mercado bilionário
que precisa de solução

+
+ +
+
+
5.7M
+
Propriedades rurais no Brasil
+
+
+
1.500+
+
Cooperativas agropecuárias
+
+
+
€8 bi
+
Exportações BR→UE afetadas
+
+
+
2026
+
Prazo de conformidade
+
+
+ +

🎯 Mercado-Alvo Imediato

+ + + + + + + + +
SegmentoCooperativasProdutoresTicket médio/mês
Soja (MT, PR, GO, MS)~200~500KR$ 1.500 - 4.000
Café (MG, SP, ES, PR)~150~300KR$ 1.000 - 3.000
Gado (MT, GO, MS, PA)~100~200KR$ 1.500 - 4.000
Cacau/Madeira/Outros~100~100KR$ 500 - 2.000
+ +
+
R$ 50M+/ano
+
TAM (Total Addressable Market) estimado para compliance EUDR no Brasil
+
+ + +
+ + +
+
+
03 — A Solução
+

DocuAgro: compliance EUDR
numa conversa de Telegram

+
+ +

Transformamos um processo burocrático de semanas em uma conversa de 30 minutos pelo Telegram.

+ +
+
+
📱
+
1
+
Produtor abre o Bot
+
Telegram, sem instalar nada
+
+
+
+
🤖
+
2
+
IA guia a coleta
+
Linguagem do campo
+
+
+
+
📄
+
3
+
Envia documentos
+
Foto ou PDF, um por vez
+
+
+
+
🔍
+
4
+
Validação OCR
+
Automática por IA
+
+
+
+
+
5
+
Dossiê PDF
+
Pronto para auditoria
+
+
+ +

💡 Por que funciona

+
+
+
💬
+
Zero Fricção
+
Via Telegram — o produtor já tem. Sem app novo, sem login complicado, sem treinamento.
+
+
+
🧠
+
IA que fala "do campo"
+
A inteligência artificial usa linguagem simples: "tá certinho", "beleza". Guia, orienta e valida com paciência.
+
+
+
📊
+
Painel da Cooperativa
+
Dashboard em tempo real. A cooperativa vê o status de cada produtor, gera dossiês e exporta relatórios.
+
+
+ +
+

"O produtor manda foto pelo Telegram. A IA valida. A cooperativa recebe o dossiê pronto. Simples assim."

+
+ + +
+ + +
+
+
04 — O Produto
+

Dois lados, uma plataforma

+
+ +
+
+
👨‍🌾
+
Lado do Produtor
+
+ Bot Telegram @docuagro_bot

+ ✅ Cadastro guiado por IA (nome, CPF, propriedade)
+ ✅ Coleta de 8 documentos, um por vez
+ ✅ Orientação sobre cada documento (o que é, onde obter)
+ ✅ Validação automática via OCR
+ ✅ Feedback em tempo real (aprovado/reenviar)
+ ✅ Geração de dossiê PDF no chat
+ ✅ Pode parar e continuar depois

+ Tempo médio: 30 minutos
+ Treinamento necessário: zero +
+
+
+
🏢
+
Lado da Cooperativa
+
+ Painel Web (browser)

+ ✅ Dashboard com KPIs em tempo real
+ ✅ % de compliance EUDR geral
+ ✅ Lista de todos os produtores + busca
+ ✅ Status detalhado de cada produtor
+ ✅ Download de dossiê PDF individual
+ ✅ Exportação CSV para ERP/planilha
+ ✅ API REST para integração

+ Visibilidade total
+ Sem trabalho manual +
+
+
+ +

📄 8 Documentos Coletados e Validados

+
+ 1. CAR (SICAR) + 2. CCIR (INCRA) + 3. ITR (Receita) + 4. Georreferenciamento + 5. Licença Ambiental + 6. Contrato Arrendamento + 7. Nota Fiscal + 8. Declaração (auto-gerada) +
+ + +
+ + +
+
+
05 — Diferencial
+

Por que DocuAgro
e não a concorrência?

+
+ + + + + + + + + + + + + +
DocuAgroConsultorias tradicionaisPlanilhas + email
Canal✅ Telegram (produtor já tem)❌ Presencial ou telefone❌ Email (produtor não usa)
Custo por produtor✅ R$ 2-5/mês❌ R$ 50-200/produtor❌ R$ 20-50 (mão de obra)
Escala✅ 10.000+ produtores❌ 50-100 por consultor❌ 200-500 com equipe
Validação✅ OCR automático🟡 Manual❌ Nenhuma
Dossiê✅ PDF automático🟡 Manual (dias)❌ Não gera
Tempo setup✅ Mesmo dia❌ Semanas🟡 Dias
IA integrada✅ Guia e orienta❌ Não❌ Não
Dashboard✅ Tempo real❌ Relatórios mensais❌ Planilha manual
+ +
+

🏆 Vantagens Únicas

+
+
+
+
10x mais rápido
+
30 min vs semanas
+
+
+
💰
+
10x mais barato
+
R$2/produtor vs R$50+
+
+
+
📈
+
100x mais escalável
+
10.000+ vs 100 por pessoa
+
+
+
+ + +
+ + +
+
+
06 — Modelo de Negócio
+

SaaS B2B — A cooperativa paga,
o produtor usa grátis

+
+ +

Modelo de assinatura mensal por volume de produtores. A cooperativa fornece o DocuAgro como serviço aos seus associados.

+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
PlanoProdutoresPreço/mêsPor produtorInclui
🌱 Starteraté 100R$ 497R$ 4,97/produtorBot + Painel + Dossiês
🌿 Proaté 500R$ 1.497R$ 2,99/produtorTudo do Starter + API + CSV + Suporte
🌳 Enterpriseaté 2.000R$ 3.997R$ 2,00/produtorTudo do Pro + White-label + Dedicado
🏔️ Custom2.000+Sob consultaNegociávelPersonalização total
+ +

💡 Argumento de Venda

+
+

"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."

+
+ +

🔄 Estratégia de Entrada

+
+
+
🎁
+
1. Piloto Grátis
+
30 dias grátis com até 50 produtores. Sem compromisso. A cooperativa testa na prática.
+
+
+
📊
+
2. Apresentar Resultados
+
Após 30 dias: "50 dossiês gerados, X% de compliance alcançado em 1 mês."
+
+
+
🚀
+
3. Converter em Plano
+
Com case real, converter para assinatura mensal. Resultados vendem sozinhos.
+
+
+ + +
+ + +
+
+
07 — Projeção Financeira
+

Caminho para R$ 500K/ano
em 12 meses

+
+ +

📈 Projeção Conservadora (12 meses)

+ + + + + + + + + +
MêsCooperativasPlano médioMRRARR
Mês 1-32 (piloto)GrátisR$ 0
Mês 42 → pagantesPro (R$ 1.497)R$ 2.994R$ 36K
Mês 65Mix Starter/ProR$ 5.985R$ 72K
Mês 910Mix Pro/EnterpriseR$ 19.970R$ 240K
Mês 1215Mix todosR$ 35.000R$ 420K
+ +

💰 Estrutura de Custos

+
+
+
Custos Fixos (~R$ 2.500/mês)
+
+ • Servidor DigitalOcean: R$ 150/mês
+ • API OpenAI (gpt-4o-mini): R$ 500-1.500/mês
+ • Domínio + Cloudflare: R$ 10/mês
+ • Ferramentas: R$ 200/mês +
+
+
+
Margem Bruta
+
+ MRR R$ 35K - Custos R$ 2.5K
+ = 93% margem

+ SaaS puro. Sem equipe de campo. Sem estoque. Escala com custo marginal mínimo. +
+
+
+ +
+
R$ 420K
+
ARR projetado no mês 12 (cenário conservador com 15 cooperativas)
+
+ + +
+ + +
+
+
08 — Roadmap
+

Do MVP ao mercado

+
+ +
+
+
✅ MVP Pronto (Agora)
+
Bot Telegram + IA (GPT-4o-mini) + OCR + PDF + Painel Web + API REST. Funcional e testado.
+
+
+
🔜 v1.1 — Segurança (Mês 1)
+
Autenticação JWT no painel, HTTPS obrigatório, rate limiting na API.
+
+
+
🔜 v1.2 — Piloto (Mês 1-3)
+
Deploy em produção. 2 cooperativas piloto (soja + café). Coleta de feedback real.
+
+
+
📋 v1.5 — Notificações (Mês 4)
+
Alertas automáticos: documentos vencendo, produtores inativos, relatórios semanais para cooperativa.
+
+
+
📋 v2.0 — WhatsApp (Mês 6)
+
Bot WhatsApp Business. Alcance 3x maior que Telegram no interior do Brasil.
+
+
+
📋 v2.5 — Integrações Gov (Mês 9)
+
APIs do SICAR, INCRA e Receita Federal para validação cruzada automática.
+
+
+
📋 v3.0 — Multi-tenant SaaS (Mês 12)
+
Cada cooperativa com ambiente isolado. Dashboard analytics avançado. White-label completo.
+
+
+ + +
+ + +
+
+
09 — Go-to-Market
+

Como vamos chegar
nas cooperativas

+
+ +
+
+
🎯
+
Canal Direto
+
+ Abordagem ativa para cooperativas:

+ • Contato direto com setor de compliance/exportação
+ • LinkedIn dos diretores de cooperativas
+ • Webinar gratuito: "EUDR: sua cooperativa está preparada?"
+ • Cold email segmentado por região e commodity
+ • Visitas presenciais nas cooperativas prioritárias +
+
+
+
🤝
+
Via Associações
+
+ Parcerias estratégicas:

+ • OCB (Organização das Cooperativas Brasileiras)
+ • CNA (Confederação da Agricultura)
+ • APROSOJA, CECAFÉ, associações por commodity
+ • Feiras: Agrishow, Show Rural, Expozebu
+ • Consultorias de compliance como parceiros de revenda +
+
+
+ +

📍 Prioridade Geográfica

+ + + + + + + + +
FaseRegiãoCommodityPor quê
Fase 1Mato Grosso / ParanáSojaMaior volume de exportação EUDR
Fase 2Minas Gerais / São PauloCaféAlta concentração de cooperativas
Fase 3Goiás / Mato Grosso do SulGadoPecuária de exportação
Fase 4Bahia / ParáCacau + MadeiraComplementar o portfólio
+ + +
+ + +
+
+
10 — Tecnologia e Equipe
+

Stack robusta,
equipe enxuta

+
+ +

🛠 Tecnologia

+
+
+
🤖
+
IA (GPT-4o-mini)
+
Guia produtores, extrai dados, valida documentos
+
+
+
🔍
+
OCR (Tesseract)
+
Extrai texto de fotos e PDFs automaticamente
+
+
+
📱
+
Bot (Telegraf)
+
Interface do produtor via Telegram
+
+
+
📊
+
Painel (Express)
+
Dashboard da cooperativa + API REST
+
+
+ +
+ Node.js 18+ + Telegraf 4.16 + Express.js + OpenAI API + Tesseract.js + PDFKit + SQLite + Helmet + CORS +
+ +

👥 Equipe

+
+
+
Eduardo Kislanski
+
+ Fundador & CEO
+ Analista de Infraestrutura Cloud. Experiência com Azure DevOps, Docker, CI/CD. Responsável pela visão de produto e estratégia comercial.

+ Também fundador da AI Vertice — empresa de soluções com IA (LexMind, MetisClass, ArgusFinance, Strix). +
+
+
+
JARVIS (IA)
+
+ CTO & Desenvolvimento
+ Sistema de IA proprietário que desenvolveu 100% do código do DocuAgro. Responsável por arquitetura, desenvolvimento, deploy e manutenção contínua.

+ Custo de equipe de dev: R$ 0/mês 🚀 +
+
+
+ + +
+ + +
+ +
🌱
+

DocuAgro

+

Compliance do produtor, na palma da mão.

+ +
+

Próximos Passos

+

+ ✅ MVP pronto e funcional
+ 🤝 Piloto grátis para sua cooperativa
+ 📊 Resultados em 30 dias
+ 🚀 Escale para toda a base de produtores +

+
+ +

+ Quer ver uma demonstração ao vivo? +

+ +
+

Eduardo Kislanski

+

AI Vertice • aivertice.com

+

m171c0@gmail.com • @docuagro_bot

+
+ + +
+ + + diff --git a/docs/pitch-deck-docuagro.pdf b/docs/pitch-deck-docuagro.pdf new file mode 100644 index 0000000..d50a7e5 Binary files /dev/null and b/docs/pitch-deck-docuagro.pdf differ diff --git a/logos/001-modern-logo-icon-for-docuagro-agricultur.png b/logos/001-modern-logo-icon-for-docuagro-agricultur.png new file mode 100644 index 0000000..018664d Binary files /dev/null and b/logos/001-modern-logo-icon-for-docuagro-agricultur.png differ diff --git a/logos/001-professional-minimalist-logo-for-docuagr.png b/logos/001-professional-minimalist-logo-for-docuagr.png new file mode 100644 index 0000000..bdf29d0 Binary files /dev/null and b/logos/001-professional-minimalist-logo-for-docuagr.png differ diff --git a/logos/002-modern-logo-icon-for-docuagro-agricultur.png b/logos/002-modern-logo-icon-for-docuagro-agricultur.png new file mode 100644 index 0000000..c9f7f63 Binary files /dev/null and b/logos/002-modern-logo-icon-for-docuagro-agricultur.png differ diff --git a/logos/index.html b/logos/index.html new file mode 100644 index 0000000..163b567 --- /dev/null +++ b/logos/index.html @@ -0,0 +1,25 @@ + + +openai-image-gen + +

openai-image-gen

+

Output: /home/kernelpanic/projetos_jarvis/docuagro/logos

+
+
+ +
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.
+
+
+ +
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.
+
+
diff --git a/logos/logo-docuagro.png b/logos/logo-docuagro.png new file mode 100644 index 0000000..c9f7f63 Binary files /dev/null and b/logos/logo-docuagro.png differ diff --git a/logos/prompts.json b/logos/prompts.json new file mode 100644 index 0000000..f97d3f2 --- /dev/null +++ b/logos/prompts.json @@ -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" + } +] \ No newline at end of file diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..e215b41 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,3698 @@ +{ + "name": "docuagro", + "version": "1.0.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "docuagro", + "version": "1.0.0", + "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" + } + }, + "node_modules/@emnapi/runtime": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/@emnapi/runtime/-/runtime-1.8.1.tgz", + "integrity": "sha512-mehfKSMWjjNol8659Z8KxEMrdSJDDot5SXMq00dM8BN4o+CLNXQ0xH2V7EchNHV4RmbZLmmPdEaXZc5H2FXmDg==", + "license": "MIT", + "optional": true, + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@img/sharp-darwin-arm64": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-arm64/-/sharp-darwin-arm64-0.33.5.tgz", + "integrity": "sha512-UT4p+iz/2H4twwAoLCqfA9UH5pI6DggwKEGuaPy7nCVQ8ZsiY5PIcrRvD1DzuY3qYL07NtIQcWnBSY/heikIFQ==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-arm64": "1.0.4" + } + }, + "node_modules/@img/sharp-darwin-x64": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-darwin-x64/-/sharp-darwin-x64-0.33.5.tgz", + "integrity": "sha512-fyHac4jIc1ANYGRDxtiqelIbdWkIuQaI84Mv45KvGRRxSAa7o7d1ZKAOBaYbnepLC1WqxfpimdeWfvqqSGwR2Q==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-darwin-x64": "1.0.4" + } + }, + "node_modules/@img/sharp-libvips-darwin-arm64": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-arm64/-/sharp-libvips-darwin-arm64-1.0.4.tgz", + "integrity": "sha512-XblONe153h0O2zuFfTAbQYAX2JhYmDHeWikp1LM9Hul9gVPjFY427k6dFEcOL72O01QxQsWi761svJ/ev9xEDg==", + "cpu": [ + "arm64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "darwin" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-darwin-x64": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-darwin-x64/-/sharp-libvips-darwin-x64-1.0.4.tgz", + "integrity": "sha512-xnGR8YuZYfJGmWPvmlunFaWJsb9T/AO2ykoP3Fz/0X5XV2aoYBPkX6xqCQvUTKKiLddarLaxpzNe+b1hjeWHAQ==", + "cpu": [ + "x64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "darwin" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-arm": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm/-/sharp-libvips-linux-arm-1.0.5.tgz", + "integrity": "sha512-gvcC4ACAOPRNATg/ov8/MnbxFDJqf/pDePbBnuBDcjsI8PssmjoKMAz4LtLaVi+OnSb5FK/yIOamqDwGmXW32g==", + "cpu": [ + "arm" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-arm64": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-arm64/-/sharp-libvips-linux-arm64-1.0.4.tgz", + "integrity": "sha512-9B+taZ8DlyyqzZQnoeIvDVR/2F4EbMepXMc/NdVbkzsJbzkUjhXv/70GQJ7tdLA4YJgNP25zukcxpX2/SueNrA==", + "cpu": [ + "arm64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-s390x": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-s390x/-/sharp-libvips-linux-s390x-1.0.4.tgz", + "integrity": "sha512-u7Wz6ntiSSgGSGcjZ55im6uvTrOxSIS8/dgoVMoiGE9I6JAfU50yH5BoDlYA1tcuGS7g/QNtetJnxA6QEsCVTA==", + "cpu": [ + "s390x" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linux-x64": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linux-x64/-/sharp-libvips-linux-x64-1.0.4.tgz", + "integrity": "sha512-MmWmQ3iPFZr0Iev+BAgVMb3ZyC4KeFc3jFxnNbEPas60e1cIfevbtuyf9nDGIzOaW9PdnDciJm+wFFaTlj5xYw==", + "cpu": [ + "x64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linuxmusl-arm64": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-arm64/-/sharp-libvips-linuxmusl-arm64-1.0.4.tgz", + "integrity": "sha512-9Ti+BbTYDcsbp4wfYib8Ctm1ilkugkA/uscUn6UXK1ldpC1JjiXbLfFZtRlBhjPZ5o1NCLiDbg8fhUPKStHoTA==", + "cpu": [ + "arm64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-libvips-linuxmusl-x64": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/@img/sharp-libvips-linuxmusl-x64/-/sharp-libvips-linuxmusl-x64-1.0.4.tgz", + "integrity": "sha512-viYN1KX9m+/hGkJtvYYp+CCLgnJXwiQB39damAO7WMdKWlIhmYTfHjwSbQeUK/20vY154mwezd9HflVFM1wVSw==", + "cpu": [ + "x64" + ], + "license": "LGPL-3.0-or-later", + "optional": true, + "os": [ + "linux" + ], + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-linux-arm": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm/-/sharp-linux-arm-0.33.5.tgz", + "integrity": "sha512-JTS1eldqZbJxjvKaAkxhZmBqPRGmxgu+qFKSInv8moZ2AmT5Yib3EQ1c6gp493HvrvV8QgdOXdyaIBrhvFhBMQ==", + "cpu": [ + "arm" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm": "1.0.5" + } + }, + "node_modules/@img/sharp-linux-arm64": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-arm64/-/sharp-linux-arm64-0.33.5.tgz", + "integrity": "sha512-JMVv+AMRyGOHtO1RFBiJy/MBsgz0x4AWrT6QoEVVTyh1E39TrCUpTRI7mx9VksGX4awWASxqCYLCV4wBZHAYxA==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-arm64": "1.0.4" + } + }, + "node_modules/@img/sharp-linux-s390x": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-s390x/-/sharp-linux-s390x-0.33.5.tgz", + "integrity": "sha512-y/5PCd+mP4CA/sPDKl2961b+C9d+vPAveS33s6Z3zfASk2j5upL6fXVPZi7ztePZ5CuH+1kW8JtvxgbuXHRa4Q==", + "cpu": [ + "s390x" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-s390x": "1.0.4" + } + }, + "node_modules/@img/sharp-linux-x64": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linux-x64/-/sharp-linux-x64-0.33.5.tgz", + "integrity": "sha512-opC+Ok5pRNAzuvq1AG0ar+1owsu842/Ab+4qvU879ippJBHvyY5n2mxF1izXqkPYlGuP/M556uh53jRLJmzTWA==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linux-x64": "1.0.4" + } + }, + "node_modules/@img/sharp-linuxmusl-arm64": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-arm64/-/sharp-linuxmusl-arm64-0.33.5.tgz", + "integrity": "sha512-XrHMZwGQGvJg2V/oRSUfSAfjfPxO+4DkiRh6p2AFjLQztWUuY/o8Mq0eMQVIY7HJ1CDQUJlxGGZRw1a5bqmd1g==", + "cpu": [ + "arm64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-arm64": "1.0.4" + } + }, + "node_modules/@img/sharp-linuxmusl-x64": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-linuxmusl-x64/-/sharp-linuxmusl-x64-0.33.5.tgz", + "integrity": "sha512-WT+d/cgqKkkKySYmqoZ8y3pxx7lx9vVejxW/W4DOFMYVSkErR+w7mf2u8m/y4+xHe7yY9DAXQMWQhpnMuFfScw==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0", + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-libvips-linuxmusl-x64": "1.0.4" + } + }, + "node_modules/@img/sharp-wasm32": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-wasm32/-/sharp-wasm32-0.33.5.tgz", + "integrity": "sha512-ykUW4LVGaMcU9lu9thv85CbRMAwfeadCJHRsg2GmeRa/cJxsVY9Rbd57JcMxBkKHag5U/x7TSBpScF4U8ElVzg==", + "cpu": [ + "wasm32" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later AND MIT", + "optional": true, + "dependencies": { + "@emnapi/runtime": "^1.2.0" + }, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-ia32": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-ia32/-/sharp-win32-ia32-0.33.5.tgz", + "integrity": "sha512-T36PblLaTwuVJ/zw/LaH0PdZkRz5rd3SmMHX8GSmR7vtNSP5Z6bQkExdSK7xGWyxLw4sUknBuugTelgw2faBbQ==", + "cpu": [ + "ia32" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@img/sharp-win32-x64": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/@img/sharp-win32-x64/-/sharp-win32-x64-0.33.5.tgz", + "integrity": "sha512-MpY/o8/8kj+EcnxwvrP4aTJSWw/aZ7JIGR4aBeZkZw5B7/Jn+tY9/VNwtcoGmdT7GfggGIU4kygOMSbYnOrAbg==", + "cpu": [ + "x64" + ], + "license": "Apache-2.0 AND LGPL-3.0-or-later", + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + } + }, + "node_modules/@swc/helpers": { + "version": "0.3.17", + "resolved": "https://registry.npmjs.org/@swc/helpers/-/helpers-0.3.17.tgz", + "integrity": "sha512-tb7Iu+oZ+zWJZ3HJqwx8oNwSDIU440hmVMDPhpACWQWnrZHK99Bxs70gT1L2dnr5Hg50ZRWEFkQCAnOVVV0z1Q==", + "license": "MIT", + "dependencies": { + "tslib": "^2.4.0" + } + }, + "node_modules/@telegraf/types": { + "version": "7.1.0", + "resolved": "https://registry.npmjs.org/@telegraf/types/-/types-7.1.0.tgz", + "integrity": "sha512-kGevOIbpMcIlCDeorKGpwZmdH7kHbqlk/Yj6dEpJMKEQw5lk0KVQY0OLXaCswy8GqlIVLd5625OB+rAntP9xVw==", + "license": "MIT" + }, + "node_modules/@types/node": { + "version": "18.19.130", + "resolved": "https://registry.npmjs.org/@types/node/-/node-18.19.130.tgz", + "integrity": "sha512-GRaXQx6jGfL8sKfaIDD6OupbIHBr9jv7Jnaml9tB7l4v068PAOXqfcujMMo5PhbIs6ggR1XODELqahT2R8v0fg==", + "license": "MIT", + "dependencies": { + "undici-types": "~5.26.4" + } + }, + "node_modules/@types/node-fetch": { + "version": "2.6.13", + "resolved": "https://registry.npmjs.org/@types/node-fetch/-/node-fetch-2.6.13.tgz", + "integrity": "sha512-QGpRVpzSaUs30JBSGPjOg4Uveu384erbHBoT1zeONvyCfwQxIkUshLAOqN/k9EjGviPRmWTTe6aH2qySWKTVSw==", + "license": "MIT", + "dependencies": { + "@types/node": "*", + "form-data": "^4.0.4" + } + }, + "node_modules/abort-controller": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/abort-controller/-/abort-controller-3.0.0.tgz", + "integrity": "sha512-h8lQ8tacZYnR3vNQTgibj+tODHI5/+l06Au2Pcriv/Gmet0eaj4TwWH41sO9wnHDiQsEj19q0drzdWdeAHtweg==", + "license": "MIT", + "dependencies": { + "event-target-shim": "^5.0.0" + }, + "engines": { + "node": ">=6.5" + } + }, + "node_modules/accepts": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/accepts/-/accepts-1.3.8.tgz", + "integrity": "sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==", + "license": "MIT", + "dependencies": { + "mime-types": "~2.1.34", + "negotiator": "0.6.3" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/accepts/node_modules/negotiator": { + "version": "0.6.3", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.3.tgz", + "integrity": "sha512-+EUsqGPLsM+j/zdChZjsnX51g4XrHFOIXwfnCVPGlQk/k5giakcKsuxCObBRu6DSm9opw/O6slWbJdghQM4bBg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/agentkeepalive": { + "version": "4.6.0", + "resolved": "https://registry.npmjs.org/agentkeepalive/-/agentkeepalive-4.6.0.tgz", + "integrity": "sha512-kja8j7PjmncONqaTsB8fQ+wE2mSU2DJ9D4XKoJ5PFWIdRMa6SLSN1ff4mOr4jCbfRSsxR4keIiySJU0N9T5hIQ==", + "license": "MIT", + "dependencies": { + "humanize-ms": "^1.2.1" + }, + "engines": { + "node": ">= 8.0.0" + } + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "license": "ISC", + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/append-field": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/append-field/-/append-field-1.0.0.tgz", + "integrity": "sha512-klpgFSWLW1ZEs8svjfb7g4qWY0YS5imI82dTg+QahUvJ8YqAY0P10Uk8tTyh9ZGuYEZEMaeJYCF5BFuX552hsw==", + "license": "MIT" + }, + "node_modules/array-buffer-byte-length": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.2.tgz", + "integrity": "sha512-LHE+8BuR7RYGDKvnrmcuSq3tDcKv9OFEXQt/HpbZhY7V6h0zlUXutnAD82GiFx9rdieCMjkvtcsPqBwgUl1Iiw==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "is-array-buffer": "^3.0.5" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/array-flatten": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/array-flatten/-/array-flatten-1.1.1.tgz", + "integrity": "sha512-PCVAQswWemu6UdxsDFFX/+gVeYqKAod3D3UVm91jHwynguOwAvYPhx8nNlM++NqRcK6CxxpUafjmhIdKiHibqg==", + "license": "MIT" + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "license": "MIT" + }, + "node_modules/available-typed-arrays": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.7.tgz", + "integrity": "sha512-wvUjBtSGN7+7SjNpq/9M2Tg350UZD3q62IFZLbRAR1bSMlCo1ZaeW+BJ+D090e4hIIZLBcTDWe4Mh4jvUDajzQ==", + "license": "MIT", + "dependencies": { + "possible-typed-array-names": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "license": "MIT" + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/bcryptjs": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/bcryptjs/-/bcryptjs-3.0.3.tgz", + "integrity": "sha512-GlF5wPWnSa/X5LKM1o0wz0suXIINz1iHRLvTS+sLyi7XPbe5ycmYI3DlZqVGZZtDgl4DmasFg7gOB3JYbphV5g==", + "license": "BSD-3-Clause", + "bin": { + "bcrypt": "bin/bcrypt" + } + }, + "node_modules/better-sqlite3": { + "version": "11.10.0", + "resolved": "https://registry.npmjs.org/better-sqlite3/-/better-sqlite3-11.10.0.tgz", + "integrity": "sha512-EwhOpyXiOEL/lKzHz9AW1msWFNzGc/z+LzeB3/jnFJpxu+th2yqvzsSWas1v9jgs9+xiXJcD5A8CJxAG2TaghQ==", + "hasInstallScript": true, + "license": "MIT", + "dependencies": { + "bindings": "^1.5.0", + "prebuild-install": "^7.1.1" + } + }, + "node_modules/binary-extensions": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.3.0.tgz", + "integrity": "sha512-Ceh+7ox5qe7LJuLHoY0feh3pHuUDHAcRUeyL2VYghZwfpkNIy/+8Ocg0a3UuSoYzavmylwuLWQOf3hl0jjMMIw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/bindings": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/bindings/-/bindings-1.5.0.tgz", + "integrity": "sha512-p2q/t/mhvuOj/UeLlV6566GD/guowlr0hHxClI0W9m7MWYkL1F0hLo+0Aexs9HSPCtR1SXQ0TD3MMKrXZajbiQ==", + "license": "MIT", + "dependencies": { + "file-uri-to-path": "1.0.0" + } + }, + "node_modules/bl": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", + "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", + "license": "MIT", + "dependencies": { + "buffer": "^5.5.0", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + } + }, + "node_modules/bl/node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/bmp-js": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/bmp-js/-/bmp-js-0.1.0.tgz", + "integrity": "sha512-vHdS19CnY3hwiNdkaqk93DvjVLfbEcI8mys4UjuWrlX1haDmroo8o4xCzh4wD6DGV6HxRCyauwhHRqMTfERtjw==", + "license": "MIT" + }, + "node_modules/body-parser": { + "version": "1.20.4", + "resolved": "https://registry.npmjs.org/body-parser/-/body-parser-1.20.4.tgz", + "integrity": "sha512-ZTgYYLMOXY9qKU/57FAo8F+HA2dGX7bqGc71txDRC1rS4frdFI5R7NhluHxH6M0YItAP0sHB4uqAOcYKxO6uGA==", + "license": "MIT", + "dependencies": { + "bytes": "~3.1.2", + "content-type": "~1.0.5", + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "~1.2.0", + "http-errors": "~2.0.1", + "iconv-lite": "~0.4.24", + "on-finished": "~2.4.1", + "qs": "~6.14.0", + "raw-body": "~2.5.3", + "type-is": "~1.6.18", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/brace-expansion": { + "version": "1.1.12", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.12.tgz", + "integrity": "sha512-9T9UjW3r0UW5c1Q7GTwllptXwhvYmEzFhzMfZ9H7FQWt+uZePjZPjBP/W1ZEyZ1twGWom5/56TF4lPcqjnDHcg==", + "dev": true, + "license": "MIT", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.3.tgz", + "integrity": "sha512-yQbXgO/OSZVD2IsiLlro+7Hf6Q18EJrKSEsdoMzKePKXct3gvD8oLcOQdIzGupr5Fj+EDe8gO/lxc1BzfMpxvA==", + "dev": true, + "license": "MIT", + "dependencies": { + "fill-range": "^7.1.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/brotli": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/brotli/-/brotli-1.3.3.tgz", + "integrity": "sha512-oTKjJdShmDuGW94SyyaoQvAjf30dZaHnjJ8uAF+u2/vGJkJbJPJAT1gDiOJP5v1Zb6f9KEyW/1HpuaWIXtGHPg==", + "license": "MIT", + "dependencies": { + "base64-js": "^1.1.2" + } + }, + "node_modules/buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, + "node_modules/buffer-alloc": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/buffer-alloc/-/buffer-alloc-1.2.0.tgz", + "integrity": "sha512-CFsHQgjtW1UChdXgbyJGtnm+O/uLQeZdtbDo8mfUgYXCHSM1wgrVxXm6bSyrUuErEb+4sYVGCzASBRot7zyrow==", + "license": "MIT", + "dependencies": { + "buffer-alloc-unsafe": "^1.1.0", + "buffer-fill": "^1.0.0" + } + }, + "node_modules/buffer-alloc-unsafe": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/buffer-alloc-unsafe/-/buffer-alloc-unsafe-1.1.0.tgz", + "integrity": "sha512-TEM2iMIEQdJ2yjPJoSIsldnleVaAk1oW3DBVUykyOLsEsFmEc9kn+SFFPz+gl54KQNxlDnAwCXosOS9Okx2xAg==", + "license": "MIT" + }, + "node_modules/buffer-equal-constant-time": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/buffer-equal-constant-time/-/buffer-equal-constant-time-1.0.1.tgz", + "integrity": "sha512-zRpUiDwd/xk6ADqPMATG8vc9VPrkck7T07OIx0gnjmJAnHnTVXNQG3vfvWNuiZIkwu9KrKdA1iJKfsfTVxE6NA==", + "license": "BSD-3-Clause" + }, + "node_modules/buffer-fill": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/buffer-fill/-/buffer-fill-1.0.0.tgz", + "integrity": "sha512-T7zexNBwiiaCOGDg9xNX9PBmjrubblRkENuptryuI64URkXDFum9il/JGL8Lm8wYfAXpredVXXZz7eMHilimiQ==", + "license": "MIT" + }, + "node_modules/buffer-from": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.2.tgz", + "integrity": "sha512-E+XQCRwSbaaiChtv6k6Dwgc+bx+Bs6vuKJHHl5kox/BaKbhiXzqQOwK4cO22yElGp2OCmjwVhT3HmxgyPGnJfQ==", + "license": "MIT" + }, + "node_modules/busboy": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/busboy/-/busboy-1.6.0.tgz", + "integrity": "sha512-8SFQbg/0hQ9xy3UNTB0YEnsNBbWfhf7RtnzpL7TkBiTBRfrQ9Fxcnz7VJsleJpyp6rVLvXiuORqjlHi5q+PYuA==", + "dependencies": { + "streamsearch": "^1.1.0" + }, + "engines": { + "node": ">=10.16.0" + } + }, + "node_modules/bytes": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/bytes/-/bytes-3.1.2.tgz", + "integrity": "sha512-/Nf7TyzTx6S3yRJObOAV7956r8cr2+Oj8AC5dt8wSP3BQAoeX58NoHyCU8P8zGkNXStjTSi6fzO6F0pBdcYbEg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/call-bind": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.8.tgz", + "integrity": "sha512-oKlSFMcMwpUg2ednkhQ454wfWiU/ul3CkJe/PEHcTKuiX6RpbehUiFMXu13HalGZxfUwCQzZG747YXBn1im9ww==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.0", + "es-define-property": "^1.0.0", + "get-intrinsic": "^1.2.4", + "set-function-length": "^1.2.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/call-bound": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/call-bound/-/call-bound-1.0.4.tgz", + "integrity": "sha512-+ys997U96po4Kx/ABpBCqhA9EuxJaQWDQg7295H4hBphv3IZg0boBKuwYpt4YXp6MZ5AmZQnU/tyMTlRpaSejg==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "get-intrinsic": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/chokidar": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.6.0.tgz", + "integrity": "sha512-7VT13fmjotKpGipCW9JEQAusEPE+Ei8nl6/g4FBAmIm0GOOLMua9NDDo/DWp0ZAxCr3cPq5ZpBqmPAQgDda2Pw==", + "dev": true, + "license": "MIT", + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "funding": { + "url": "https://paulmillr.com/funding/" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/chownr": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz", + "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg==", + "license": "ISC" + }, + "node_modules/clone": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/clone/-/clone-2.1.2.tgz", + "integrity": "sha512-3Pe/CF1Nn94hyhIYpjtiLhdCoEoz0DqQ+988E9gmeEdQZlojxnOb74wctFyuwWQHzqyf9X7C7MG8juUpqBJT8w==", + "license": "MIT", + "engines": { + "node": ">=0.8" + } + }, + "node_modules/color": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/color/-/color-4.2.3.tgz", + "integrity": "sha512-1rXeuUUiGGrykh+CeBdu5Ie7OJwinCgQY0bc7GCRxy5xVHy+moaqkpL/jqQq0MtQOeYcrqEz4abc5f0KtU7W4A==", + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1", + "color-string": "^1.9.0" + }, + "engines": { + "node": ">=12.5.0" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "license": "MIT" + }, + "node_modules/color-string": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/color-string/-/color-string-1.9.1.tgz", + "integrity": "sha512-shrVawQFojnZv6xM40anx4CkoDP+fZsw/ZerEMsW/pyzsRbElpsL/DBVW7q3ExxwusdNXI3lXpuhEZkzs8p5Eg==", + "license": "MIT", + "dependencies": { + "color-name": "^1.0.0", + "simple-swizzle": "^0.2.2" + } + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/compressible": { + "version": "2.0.18", + "resolved": "https://registry.npmjs.org/compressible/-/compressible-2.0.18.tgz", + "integrity": "sha512-AF3r7P5dWxL8MxyITRMlORQNaOA2IkAFaTr4k7BUumjPtRpGDTZpl0Pb1XCO6JeDCBdp126Cgs9sMxqSjgYyRg==", + "license": "MIT", + "dependencies": { + "mime-db": ">= 1.43.0 < 2" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/compression": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/compression/-/compression-1.8.1.tgz", + "integrity": "sha512-9mAqGPHLakhCLeNyxPkK4xVo746zQ/czLH1Ky+vkitMnWfWZps8r0qXuwhwizagCRttsL4lfG4pIOvaWLpAP0w==", + "license": "MIT", + "dependencies": { + "bytes": "3.1.2", + "compressible": "~2.0.18", + "debug": "2.6.9", + "negotiator": "~0.6.4", + "on-headers": "~1.1.0", + "safe-buffer": "5.2.1", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true, + "license": "MIT" + }, + "node_modules/concat-stream": { + "version": "1.6.2", + "resolved": "https://registry.npmjs.org/concat-stream/-/concat-stream-1.6.2.tgz", + "integrity": "sha512-27HBghJxjiZtIk3Ycvn/4kbJk/1uZuJFfuPEns6LaEvpvG1f0hTea8lilrouyo9mVc2GWdcEZ8OLoGmSADlrCw==", + "engines": [ + "node >= 0.8" + ], + "license": "MIT", + "dependencies": { + "buffer-from": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^2.2.2", + "typedarray": "^0.0.6" + } + }, + "node_modules/content-disposition": { + "version": "0.5.4", + "resolved": "https://registry.npmjs.org/content-disposition/-/content-disposition-0.5.4.tgz", + "integrity": "sha512-FveZTNuGw04cxlAiWbzi6zTAL/lhehaWbTtgluJh4/E95DqMwTmha3KZN1aAWA8cFIhHzMZUvLevkw5Rqk+tSQ==", + "license": "MIT", + "dependencies": { + "safe-buffer": "5.2.1" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/content-type": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/content-type/-/content-type-1.0.5.tgz", + "integrity": "sha512-nTjqfcBFEipKdXCv4YDQWCfmcLZKm81ldF0pAopTvyrFGVbcR6P/VAAd5G7N+0tTr8QqiU0tFadD6FK4NtJwOA==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.7.2.tgz", + "integrity": "sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/cookie-signature": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/cookie-signature/-/cookie-signature-1.0.7.tgz", + "integrity": "sha512-NXdYc3dLr47pBkpUCHtKSwIOQXLVn8dZEuywboCOJY/osA0wFSLlSawr3KN8qXJEyX66FcONTH8EIlVuK0yyFA==", + "license": "MIT" + }, + "node_modules/core-util-is": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.3.tgz", + "integrity": "sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==", + "license": "MIT" + }, + "node_modules/cors": { + "version": "2.8.6", + "resolved": "https://registry.npmjs.org/cors/-/cors-2.8.6.tgz", + "integrity": "sha512-tJtZBBHA6vjIAaF6EnIaq6laBBP9aq/Y3ouVJjEfoHbRBcHBAHYcMh/w8LDrk2PvIMMq8gmopa5D4V8RmbrxGw==", + "license": "MIT", + "dependencies": { + "object-assign": "^4", + "vary": "^1" + }, + "engines": { + "node": ">= 0.10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/crypto-js": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/crypto-js/-/crypto-js-4.2.0.tgz", + "integrity": "sha512-KALDyEYgpY+Rlob/iriUtjV6d5Eq+Y191A5g4UqLAi8CyGP9N1+FdVbkc1SxKc2r4YAYqG8JzO2KGL+AizD70Q==", + "license": "MIT" + }, + "node_modules/dayjs": { + "version": "1.11.19", + "resolved": "https://registry.npmjs.org/dayjs/-/dayjs-1.11.19.tgz", + "integrity": "sha512-t5EcLVS6QPBNqM2z8fakk/NKel+Xzshgt8FFKAn+qwlD1pzZWxh0nVCrvFK7ZDb6XucZeF9z8C7CBWTRIVApAw==", + "license": "MIT" + }, + "node_modules/debug": { + "version": "2.6.9", + "resolved": "https://registry.npmjs.org/debug/-/debug-2.6.9.tgz", + "integrity": "sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA==", + "license": "MIT", + "dependencies": { + "ms": "2.0.0" + } + }, + "node_modules/decompress-response": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-6.0.0.tgz", + "integrity": "sha512-aW35yZM6Bb/4oJlZncMH2LCoZtJXTRxES17vE3hoRiowU2kWHaJKFkSBDnDR+cm9J+9QhXmREyIfv0pji9ejCQ==", + "license": "MIT", + "dependencies": { + "mimic-response": "^3.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/deep-equal": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-2.2.3.tgz", + "integrity": "sha512-ZIwpnevOurS8bpT4192sqAowWM76JDKSHYzMLty3BZGSswgq6pBaH3DhCSW5xVAZICZyKdOBPjwww5wfgT/6PA==", + "license": "MIT", + "dependencies": { + "array-buffer-byte-length": "^1.0.0", + "call-bind": "^1.0.5", + "es-get-iterator": "^1.1.3", + "get-intrinsic": "^1.2.2", + "is-arguments": "^1.1.1", + "is-array-buffer": "^3.0.2", + "is-date-object": "^1.0.5", + "is-regex": "^1.1.4", + "is-shared-array-buffer": "^1.0.2", + "isarray": "^2.0.5", + "object-is": "^1.1.5", + "object-keys": "^1.1.1", + "object.assign": "^4.1.4", + "regexp.prototype.flags": "^1.5.1", + "side-channel": "^1.0.4", + "which-boxed-primitive": "^1.0.2", + "which-collection": "^1.0.1", + "which-typed-array": "^1.1.13" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/deep-extend": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/deep-extend/-/deep-extend-0.6.0.tgz", + "integrity": "sha512-LOHxIOaPYdHlJRtCQfDIVZtfw/ufM8+rVj649RIHzcm/vGwQRXFt6OPqIFWsm2XEMrNIEtWR64sY1LEKD2vAOA==", + "license": "MIT", + "engines": { + "node": ">=4.0.0" + } + }, + "node_modules/define-data-property": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/define-data-property/-/define-data-property-1.1.4.tgz", + "integrity": "sha512-rBMvIzlpA8v6E+SJZoo++HAYqsLrkg7MSfIinMPFhmkorw7X+dOXVJQs+QT69zGkzMyfDnIMN2Wid1+NbL3T+A==", + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0", + "es-errors": "^1.3.0", + "gopd": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/define-properties": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.1.tgz", + "integrity": "sha512-8QmQKqEASLd5nx0U1B1okLElbUuuttJ/AnYmRXbbbGDWh6uS208EjD4Xqq/I9wK7u0v6O08XhTWnt5XtEbR6Dg==", + "license": "MIT", + "dependencies": { + "define-data-property": "^1.0.1", + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/depd": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/depd/-/depd-2.0.0.tgz", + "integrity": "sha512-g7nH6P6dyDioJogAAGprGpCtVImJhpPk/roCzdb3fIh61/s/nPsfR6onyMwkCAR/OlC3yBC0lESvUoQEAssIrw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/destroy": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/destroy/-/destroy-1.2.0.tgz", + "integrity": "sha512-2sJGJTaXIIaR1w4iJSNoN0hnMY7Gpc/n8D4qSCJw8QqFWXf7cuAgnEHxBpweaVcPevC2l3KpjYCx3NypQQgaJg==", + "license": "MIT", + "engines": { + "node": ">= 0.8", + "npm": "1.2.8000 || >= 1.4.16" + } + }, + "node_modules/detect-libc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.1.2.tgz", + "integrity": "sha512-Btj2BOOO83o3WyH59e8MgXsxEQVcarkUOpEYrubB0urwnN10yQ364rsiByU11nZlqWYZm05i/of7io4mzihBtQ==", + "license": "Apache-2.0", + "engines": { + "node": ">=8" + } + }, + "node_modules/dfa": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/dfa/-/dfa-1.2.0.tgz", + "integrity": "sha512-ED3jP8saaweFTjeGX8HQPjeC1YYyZs98jGNZx6IiBvxW7JG5v492kamAQB3m2wop07CvU/RQmzcKr6bgcC5D/Q==", + "license": "MIT" + }, + "node_modules/dotenv": { + "version": "16.6.1", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.6.1.tgz", + "integrity": "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/ecdsa-sig-formatter": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/ecdsa-sig-formatter/-/ecdsa-sig-formatter-1.0.11.tgz", + "integrity": "sha512-nagl3RYrbNv6kQkeJIpt6NJZy8twLB/2vtz6yN9Z4vRKHN4/QZJIEbqohALSgwKdnksuY3k5Addp5lg8sVoVcQ==", + "license": "Apache-2.0", + "dependencies": { + "safe-buffer": "^5.0.1" + } + }, + "node_modules/ee-first": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/ee-first/-/ee-first-1.1.1.tgz", + "integrity": "sha512-WMwm9LhRUo+WUaRN+vRuETqG89IgZphVSNkdFgeb6sS/E4OrDIN7t48CAewSHXc6C8lefD8KKfr5vY61brQlow==", + "license": "MIT" + }, + "node_modules/encodeurl": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/encodeurl/-/encodeurl-2.0.0.tgz", + "integrity": "sha512-Q0n9HRi4m6JuGIV1eFlmvJB7ZEVxu93IrMyiMsGC0lrMJMWzRgx6WGquyfQgZVb31vhGgXnfmPNNXmxnOkRBrg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/end-of-stream": { + "version": "1.4.5", + "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.5.tgz", + "integrity": "sha512-ooEGc6HP26xXq/N+GCGOT0JKCLDGrq2bQUZrQ7gyrJiZANJ/8YDTxTpQBXGMn+WbIQXNVpyWymm7KYVICQnyOg==", + "license": "MIT", + "dependencies": { + "once": "^1.4.0" + } + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-get-iterator": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/es-get-iterator/-/es-get-iterator-1.1.3.tgz", + "integrity": "sha512-sPZmqHBe6JIiTfN5q2pEi//TwxmAFHwj/XEuYjTuse78i8KxaqMTTzxPoFKuzRpDpTJ+0NAbpfenkmH2rePtuw==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.1.3", + "has-symbols": "^1.0.3", + "is-arguments": "^1.1.1", + "is-map": "^2.0.2", + "is-set": "^2.0.2", + "is-string": "^1.0.7", + "isarray": "^2.0.5", + "stop-iteration-iterator": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/escape-html": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/escape-html/-/escape-html-1.0.3.tgz", + "integrity": "sha512-NiSupZ4OeuGwr68lGIeym/ksIZMJodUGOSCZ/FSnTxcrekbvqrgdUxlJOMpijaKZVjAJrWrGs/6Jy8OMuyj9ow==", + "license": "MIT" + }, + "node_modules/etag": { + "version": "1.8.1", + "resolved": "https://registry.npmjs.org/etag/-/etag-1.8.1.tgz", + "integrity": "sha512-aIL5Fx7mawVa300al2BnEE4iNvo1qETxLrPI/o05L7z6go7fCw1J6EQmbK4FmJ2AS7kgVF/KEZWufBfdClMcPg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/event-target-shim": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/event-target-shim/-/event-target-shim-5.0.1.tgz", + "integrity": "sha512-i/2XbnSz/uxRCU6+NdVJgKWDTM427+MqYbkQzD321DuCQJUqOuJKIA0IM2+W2xtYHdKOmZ4dR6fExsd4SXL+WQ==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/expand-template": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/expand-template/-/expand-template-2.0.3.tgz", + "integrity": "sha512-XYfuKMvj4O35f/pOXLObndIRvyQ+/+6AhODh+OKWj9S9498pHHn/IMszH+gt0fBCRWMNfk1ZSp5x3AifmnI2vg==", + "license": "(MIT OR WTFPL)", + "engines": { + "node": ">=6" + } + }, + "node_modules/express": { + "version": "4.22.1", + "resolved": "https://registry.npmjs.org/express/-/express-4.22.1.tgz", + "integrity": "sha512-F2X8g9P1X7uCPZMA3MVf9wcTqlyNp7IhH5qPCI0izhaOIYXaW9L535tGA3qmjRzpH+bZczqq7hVKxTR4NWnu+g==", + "license": "MIT", + "dependencies": { + "accepts": "~1.3.8", + "array-flatten": "1.1.1", + "body-parser": "~1.20.3", + "content-disposition": "~0.5.4", + "content-type": "~1.0.4", + "cookie": "~0.7.1", + "cookie-signature": "~1.0.6", + "debug": "2.6.9", + "depd": "2.0.0", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "finalhandler": "~1.3.1", + "fresh": "~0.5.2", + "http-errors": "~2.0.0", + "merge-descriptors": "1.0.3", + "methods": "~1.1.2", + "on-finished": "~2.4.1", + "parseurl": "~1.3.3", + "path-to-regexp": "~0.1.12", + "proxy-addr": "~2.0.7", + "qs": "~6.14.0", + "range-parser": "~1.2.1", + "safe-buffer": "5.2.1", + "send": "~0.19.0", + "serve-static": "~1.16.2", + "setprototypeof": "1.2.0", + "statuses": "~2.0.1", + "type-is": "~1.6.18", + "utils-merge": "1.0.1", + "vary": "~1.1.2" + }, + "engines": { + "node": ">= 0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/file-uri-to-path": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/file-uri-to-path/-/file-uri-to-path-1.0.0.tgz", + "integrity": "sha512-0Zt+s3L7Vf1biwWZ29aARiVYLx7iMGnEUl9x33fbB/j3jR81u/O2LbqK+Bm1CDSNDKVtJ/YjwY7TUd5SkeLQLw==", + "license": "MIT" + }, + "node_modules/fill-range": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.1.1.tgz", + "integrity": "sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==", + "dev": true, + "license": "MIT", + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/finalhandler": { + "version": "1.3.2", + "resolved": "https://registry.npmjs.org/finalhandler/-/finalhandler-1.3.2.tgz", + "integrity": "sha512-aA4RyPcd3badbdABGDuTXCMTtOneUCAYH/gxoYRTZlIJdF0YPWuGqiAsIrhNnnqdXGswYk6dGujem4w80UJFhg==", + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "on-finished": "~2.4.1", + "parseurl": "~1.3.3", + "statuses": "~2.0.2", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/fontkit": { + "version": "1.9.0", + "resolved": "https://registry.npmjs.org/fontkit/-/fontkit-1.9.0.tgz", + "integrity": "sha512-HkW/8Lrk8jl18kzQHvAw9aTHe1cqsyx5sDnxncx652+CIfhawokEPkeM3BoIC+z/Xv7a0yMr0f3pRRwhGH455g==", + "license": "MIT", + "dependencies": { + "@swc/helpers": "^0.3.13", + "brotli": "^1.3.2", + "clone": "^2.1.2", + "deep-equal": "^2.0.5", + "dfa": "^1.2.0", + "restructure": "^2.0.1", + "tiny-inflate": "^1.0.3", + "unicode-properties": "^1.3.1", + "unicode-trie": "^2.0.0" + } + }, + "node_modules/for-each": { + "version": "0.3.5", + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.5.tgz", + "integrity": "sha512-dKx12eRCVIzqCxFGplyFKJMPvLEWgmNtUrpTiJIR5u97zEhRG8ySrtboPHZXx7daLxQVrl643cTzbab2tkQjxg==", + "license": "MIT", + "dependencies": { + "is-callable": "^1.2.7" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/form-data": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz", + "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==", + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/form-data-encoder": { + "version": "1.7.2", + "resolved": "https://registry.npmjs.org/form-data-encoder/-/form-data-encoder-1.7.2.tgz", + "integrity": "sha512-qfqtYan3rxrnCk1VYaA4H+Ms9xdpPqvLZa6xmMgFvhO32x7/3J/ExcTd6qpxM0vH2GdMI+poehyBZvqfMTto8A==", + "license": "MIT" + }, + "node_modules/formdata-node": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/formdata-node/-/formdata-node-4.4.1.tgz", + "integrity": "sha512-0iirZp3uVDjVGt9p49aTaqjk84TrglENEDuqfdlZQ1roC9CWlPk6Avf8EEnZNcAqPonwkG35x4n3ww/1THYAeQ==", + "license": "MIT", + "dependencies": { + "node-domexception": "1.0.0", + "web-streams-polyfill": "4.0.0-beta.3" + }, + "engines": { + "node": ">= 12.20" + } + }, + "node_modules/forwarded": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/forwarded/-/forwarded-0.2.0.tgz", + "integrity": "sha512-buRG0fpBtRHSTCOASe6hD258tEubFoRLb4ZNA6NxMVHNw2gOcwHo9wyablzMzOA5z9xA9L1KNjk/Nt6MT9aYow==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fresh": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/fresh/-/fresh-0.5.2.tgz", + "integrity": "sha512-zJ2mQYM18rEFOudeV4GShTGIQ7RbzA7ozbU9I/XBpm7kqgMywgmylMwXHxZJmkVoYkna9d2pVXVXPdYTP9ej8Q==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/fs-constants": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz", + "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow==", + "license": "MIT" + }, + "node_modules/fsevents": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", + "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", + "dev": true, + "hasInstallScript": true, + "license": "MIT", + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/functions-have-names": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", + "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/github-from-package": { + "version": "0.0.0", + "resolved": "https://registry.npmjs.org/github-from-package/-/github-from-package-0.0.0.tgz", + "integrity": "sha512-SyHy3T1v2NUXn29OsWdxmK6RwHD+vkj3v8en8AOBZ1wBQ/hCAQ5bAQTD02kW4W9tUp/3Qh6J8r9EvntiyCmOOw==", + "license": "MIT" + }, + "node_modules/glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "license": "ISC", + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-bigints": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.1.0.tgz", + "integrity": "sha512-R3pbpkcIqv2Pm3dUwgjclDRVmWpTJW2DcMzcIhEXEx1oh/CEMObMm3KLmRJOdvhM7o4uQBnwr8pzRK2sJWIqfg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/has-property-descriptors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.2.tgz", + "integrity": "sha512-55JNKuIW+vq4Ke1BjOTjM2YctQIvCT7GFzHwmfZPGo5wnrgkid0YQtnAleFSqumZm4az3n2BS+erby5ipJdgrg==", + "license": "MIT", + "dependencies": { + "es-define-property": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/helmet": { + "version": "8.1.0", + "resolved": "https://registry.npmjs.org/helmet/-/helmet-8.1.0.tgz", + "integrity": "sha512-jOiHyAZsmnr8LqoPGmCjYAaiuWwjAPLgY8ZX2XrmHawt99/u1y6RgrZMTeoPfpUbV96HOalYgz1qzkRbw54Pmg==", + "license": "MIT", + "engines": { + "node": ">=18.0.0" + } + }, + "node_modules/http-errors": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.1.tgz", + "integrity": "sha512-4FbRdAX+bSdmo4AUFuS0WNiPz8NgFt+r8ThgNWmlrjQjt1Q7ZR9+zTlce2859x4KSXrwIsaeTqDoKQmtP8pLmQ==", + "license": "MIT", + "dependencies": { + "depd": "~2.0.0", + "inherits": "~2.0.4", + "setprototypeof": "~1.2.0", + "statuses": "~2.0.2", + "toidentifier": "~1.0.1" + }, + "engines": { + "node": ">= 0.8" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/humanize-ms": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/humanize-ms/-/humanize-ms-1.2.1.tgz", + "integrity": "sha512-Fl70vYtsAFb/C06PTS9dZBo7ihau+Tu/DNCk/OyHhea07S+aeMWpFFkUaXRa8fI+ScZbEI8dfSxwY7gxZ9SAVQ==", + "license": "MIT", + "dependencies": { + "ms": "^2.0.0" + } + }, + "node_modules/iconv-lite": { + "version": "0.4.24", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.4.24.tgz", + "integrity": "sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/idb-keyval": { + "version": "6.2.2", + "resolved": "https://registry.npmjs.org/idb-keyval/-/idb-keyval-6.2.2.tgz", + "integrity": "sha512-yjD9nARJ/jb1g+CvD0tlhUHOrJ9Sy0P8T9MF3YaLlHnSRpwPfpTX0XIvpmw3gAJUmEu3FiICLBDPXVwyEvrleg==", + "license": "Apache-2.0" + }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/ignore-by-default": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/ignore-by-default/-/ignore-by-default-1.0.1.tgz", + "integrity": "sha512-Ius2VYcGNk7T90CppJqcIkS5ooHUZyIQK+ClZfMfMNFEF9VSE73Fq+906u/CWu92x4gzZMWOwfFYckPObzdEbA==", + "dev": true, + "license": "ISC" + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" + }, + "node_modules/ini": { + "version": "1.3.8", + "resolved": "https://registry.npmjs.org/ini/-/ini-1.3.8.tgz", + "integrity": "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew==", + "license": "ISC" + }, + "node_modules/internal-slot": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.1.0.tgz", + "integrity": "sha512-4gd7VpWNQNB4UKKCFFVcp1AVv+FMOgs9NKzjHKusc8jTMhd5eL1NqQqOpE0KzMds804/yHlglp3uxgluOqAPLw==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "hasown": "^2.0.2", + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/ipaddr.js": { + "version": "1.9.1", + "resolved": "https://registry.npmjs.org/ipaddr.js/-/ipaddr.js-1.9.1.tgz", + "integrity": "sha512-0KI/607xoxSToH7GjN1FfSbLoU0+btTicjsQSWQlh/hZykN8KpmMf7uYwPW3R+akZ6R/w18ZlXSHBYXiYUPO3g==", + "license": "MIT", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/is-arguments": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.2.0.tgz", + "integrity": "sha512-7bVbi0huj/wrIAOzb8U1aszg9kdi3KN/CyU19CTI7tAoZYEZoL9yCDXpbXN+uPsuWnP02cyug1gleqq+TU+YCA==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-array-buffer": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.5.tgz", + "integrity": "sha512-DDfANUiiG2wC1qawP66qlTugJeL5HyzMpfr8lLK+jMQirGzNod0B12cFB/9q838Ru27sBwfw78/rdoU7RERz6A==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "get-intrinsic": "^1.2.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-arrayish": { + "version": "0.3.4", + "resolved": "https://registry.npmjs.org/is-arrayish/-/is-arrayish-0.3.4.tgz", + "integrity": "sha512-m6UrgzFVUYawGBh1dUsWR5M2Clqic9RVXC/9f8ceNlv2IcO9j9J/z8UoCLPqtsPBFNzEpfR3xftohbfqDx8EQA==", + "license": "MIT" + }, + "node_modules/is-bigint": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.1.0.tgz", + "integrity": "sha512-n4ZT37wG78iz03xPRKJrHTdZbe3IicyucEtdRsV5yglwc3GyUfbAfpSeD0FJ41NbUNSt5wbhqfp1fS+BgnvDFQ==", + "license": "MIT", + "dependencies": { + "has-bigints": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "license": "MIT", + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-boolean-object": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.2.2.tgz", + "integrity": "sha512-wa56o2/ElJMYqjCjGkXri7it5FbebW5usLw/nPmCMs5DeZ7eziSYZhSmPRn0txqeW4LnAmQQU7FgqLpsEFKM4A==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-callable": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", + "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-date-object": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.1.0.tgz", + "integrity": "sha512-PwwhEakHVKTdRNVOw+/Gyh0+MzlCl4R6qKvkhuvLtPMggI1WAHt9sOwZxQLSGpUaDnrdyDsomoRgNnCfKNSXXg==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-electron": { + "version": "2.2.2", + "resolved": "https://registry.npmjs.org/is-electron/-/is-electron-2.2.2.tgz", + "integrity": "sha512-FO/Rhvz5tuw4MCWkpMzHFKWD2LsfHzIb7i6MdPYZ/KW7AlxawyLkqdy+jPZP1WubqEADE3O4FUENlJHDfQASRg==", + "license": "MIT" + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-map": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.3.tgz", + "integrity": "sha512-1Qed0/Hr2m+YqxnM09CjA2d/i6YZNfF6R2oRAOj36eUdS6qIV/huPJNSEpKbupewFs+ZsJlxsjjPbc0/afW6Lw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-number-object": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.1.1.tgz", + "integrity": "sha512-lZhclumE1G6VYD8VHe35wFaIif+CTy5SJIi5+3y4psDgWu4wPDoBhF8NxUOinEc7pHgiTsT6MaBb92rKhhD+Xw==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-regex": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.2.1.tgz", + "integrity": "sha512-MjYsKHO5O7mCsmRGxWcLWheFqN9DJ/2TmngvjKXihe6efViPqc274+Fx/4fYj/r03+ESvBdTXK0V6tA3rgez1g==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-set": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.3.tgz", + "integrity": "sha512-iPAjerrse27/ygGLxw+EBR9agv9Y6uLeYVJMu+QNCoouJ1/1ri0mGrcWpfCqFZuzzx3WjtwxG098X+n4OuRkPg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-shared-array-buffer": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.4.tgz", + "integrity": "sha512-ISWac8drv4ZGfwKl5slpHG9OwPNty4jOWPRIhBpxOoD+hqITiwuipOQ2bNthAzwA3B4fIjO4Nln74N0S9byq8A==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-string": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.1.1.tgz", + "integrity": "sha512-BtEeSsoaQjlSPBemMQIrY1MY0uM6vnS1g5fmufYOtnxLGUZM2178PKbhsk7Ffv58IX+ZtcvoGwccYsh0PglkAA==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-symbol": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.1.1.tgz", + "integrity": "sha512-9gGx6GTtCQM73BgmHQXfDmLtfjjTUDSyoxTCbp5WtoixAhfgsDirWIcVQ/IHpvI5Vgd5i/J5F7B9cN/WlVbC/w==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "has-symbols": "^1.1.0", + "safe-regex-test": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-url": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/is-url/-/is-url-1.2.4.tgz", + "integrity": "sha512-ITvGim8FhRiYe4IQ5uHSkj7pVaPDrCTkNd3yq3cV7iZAcJdHTUMPMEHcqSOy9xZ9qFenQCvi+2wjH9a1nXqHww==", + "license": "MIT" + }, + "node_modules/is-weakmap": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.2.tgz", + "integrity": "sha512-K5pXYOm9wqY1RgjpL3YTkF39tni1XajUIkawTLUo9EZEVUFga5gSQJF8nNS7ZwJQ02y+1YCNYcMh+HIf1ZqE+w==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakset": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.4.tgz", + "integrity": "sha512-mfcwb6IzQyOKTs84CQMrOwW4gQcaTOAWJ0zzJCl2WSPDrWk/OzDaImWFH3djXhb24g4eudZfLRozAvPGw4d9hQ==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.3", + "get-intrinsic": "^1.2.6" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", + "license": "MIT" + }, + "node_modules/jpeg-exif": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/jpeg-exif/-/jpeg-exif-1.1.4.tgz", + "integrity": "sha512-a+bKEcCjtuW5WTdgeXFzswSrdqi0jk4XlEtZlx5A94wCoBpFjfFTbo/Tra5SpNCl/YFZPvcV1dJc+TAYeg6ROQ==", + "deprecated": "Package no longer supported. Contact Support at https://www.npmjs.com/support for more info.", + "license": "MIT" + }, + "node_modules/jsonwebtoken": { + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/jsonwebtoken/-/jsonwebtoken-9.0.3.tgz", + "integrity": "sha512-MT/xP0CrubFRNLNKvxJ2BYfy53Zkm++5bX9dtuPbqAeQpTVe0MQTFhao8+Cp//EmJp244xt6Drw/GVEGCUj40g==", + "license": "MIT", + "dependencies": { + "jws": "^4.0.1", + "lodash.includes": "^4.3.0", + "lodash.isboolean": "^3.0.3", + "lodash.isinteger": "^4.0.4", + "lodash.isnumber": "^3.0.3", + "lodash.isplainobject": "^4.0.6", + "lodash.isstring": "^4.0.1", + "lodash.once": "^4.0.0", + "ms": "^2.1.1", + "semver": "^7.5.4" + }, + "engines": { + "node": ">=12", + "npm": ">=6" + } + }, + "node_modules/jsonwebtoken/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/jwa": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/jwa/-/jwa-2.0.1.tgz", + "integrity": "sha512-hRF04fqJIP8Abbkq5NKGN0Bbr3JxlQ+qhZufXVr0DvujKy93ZCbXZMHDL4EOtodSbCWxOqR8MS1tXA5hwqCXDg==", + "license": "MIT", + "dependencies": { + "buffer-equal-constant-time": "^1.0.1", + "ecdsa-sig-formatter": "1.0.11", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/jws": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/jws/-/jws-4.0.1.tgz", + "integrity": "sha512-EKI/M/yqPncGUUh44xz0PxSidXFr/+r0pA70+gIYhjv+et7yxM+s29Y+VGDkovRofQem0fs7Uvf4+YmAdyRduA==", + "license": "MIT", + "dependencies": { + "jwa": "^2.0.1", + "safe-buffer": "^5.0.1" + } + }, + "node_modules/linebreak": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/linebreak/-/linebreak-1.1.0.tgz", + "integrity": "sha512-MHp03UImeVhB7XZtjd0E4n6+3xr5Dq/9xI/5FptGk5FrbDR3zagPa2DS6U8ks/3HjbKWG9Q1M2ufOzxV2qLYSQ==", + "license": "MIT", + "dependencies": { + "base64-js": "0.0.8", + "unicode-trie": "^2.0.0" + } + }, + "node_modules/linebreak/node_modules/base64-js": { + "version": "0.0.8", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-0.0.8.tgz", + "integrity": "sha512-3XSA2cR/h/73EzlXXdU6YNycmYI7+kicTxks4eJg2g39biHR84slg2+des+p7iHYhbRg/udIS4TD53WabcOUkw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/lodash.includes": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/lodash.includes/-/lodash.includes-4.3.0.tgz", + "integrity": "sha512-W3Bx6mdkRTGtlJISOvVD/lbqjTlPPUDTMnlXZFnVwi9NKJ6tiAk6LVdlhZMm17VZisqhKcgzpO5Wz91PCt5b0w==", + "license": "MIT" + }, + "node_modules/lodash.isboolean": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isboolean/-/lodash.isboolean-3.0.3.tgz", + "integrity": "sha512-Bz5mupy2SVbPHURB98VAcw+aHh4vRV5IPNhILUCsOzRmsTmSQ17jIuqopAentWoehktxGd9e/hbIXq980/1QJg==", + "license": "MIT" + }, + "node_modules/lodash.isinteger": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/lodash.isinteger/-/lodash.isinteger-4.0.4.tgz", + "integrity": "sha512-DBwtEWN2caHQ9/imiNeEA5ys1JoRtRfY3d7V9wkqtbycnAmTvRRmbHKDV4a0EYc678/dia0jrte4tjYwVBaZUA==", + "license": "MIT" + }, + "node_modules/lodash.isnumber": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/lodash.isnumber/-/lodash.isnumber-3.0.3.tgz", + "integrity": "sha512-QYqzpfwO3/CWf3XP+Z+tkQsfaLL/EnUlXWVkIk5FUPc4sBdTehEqZONuyRt2P67PXAk+NXmTBcc97zw9t1FQrw==", + "license": "MIT" + }, + "node_modules/lodash.isplainobject": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/lodash.isplainobject/-/lodash.isplainobject-4.0.6.tgz", + "integrity": "sha512-oSXzaWypCMHkPC3NvBEaPHf0KsA5mvPrOPgQWDsbg8n7orZ290M0BmC/jgRZ4vcJ6DTAhjrsSYgdsW/F+MFOBA==", + "license": "MIT" + }, + "node_modules/lodash.isstring": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/lodash.isstring/-/lodash.isstring-4.0.1.tgz", + "integrity": "sha512-0wJxfxH1wgO3GrbuP+dTTk7op+6L41QCXbGINEmD+ny/G/eCqGzxyCsh7159S+mgDDcoarnBw6PC1PS5+wUGgw==", + "license": "MIT" + }, + "node_modules/lodash.once": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/lodash.once/-/lodash.once-4.1.1.tgz", + "integrity": "sha512-Sb487aTOCr9drQVL8pIxOzVhafOjZN9UU54hiN8PU3uAiSV7lx1yYNpbNmex2PK6dSJoNTSJUUswT651yww3Mg==", + "license": "MIT" + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/media-typer": { + "version": "0.3.0", + "resolved": "https://registry.npmjs.org/media-typer/-/media-typer-0.3.0.tgz", + "integrity": "sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/merge-descriptors": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/merge-descriptors/-/merge-descriptors-1.0.3.tgz", + "integrity": "sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/methods": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/methods/-/methods-1.1.2.tgz", + "integrity": "sha512-iclAHeNqNm68zFtnZ0e+1L2yUIdvzNoauKU4WBA3VvH/vPFieF7qfRlwUZU+DA9P9bPXIS90ulxoUoCH23sV2w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/mime/-/mime-1.6.0.tgz", + "integrity": "sha512-x0Vn8spI+wuJ1O6S7gnbaQg8Pxh4NNHb7KSINmEWKiPE4RKOplvijn+NkmYmmRgP68mc70j2EbeTFRsrswaQeg==", + "license": "MIT", + "bin": { + "mime": "cli.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/mime-db": { + "version": "1.54.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.54.0.tgz", + "integrity": "sha512-aU5EJuIN2WDemCcAp2vFBfp/m4EAhWJnUNSSw0ixs7/kXbd6Pg64EmwJkNdFhB8aWt1sH2CTXrLxo/iAGV3oPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types/node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mimic-response": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-3.1.0.tgz", + "integrity": "sha512-z0yWI+4FDrrweS8Zmt4Ej5HdJmky15+L2e6Wgn3+iK5fWzb6T3fhNFq2+MeTRb064c6Wr4N/wv0DzQTjNzHNGQ==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "license": "ISC", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/mkdirp": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", + "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", + "license": "MIT", + "dependencies": { + "minimist": "^1.2.6" + }, + "bin": { + "mkdirp": "bin/cmd.js" + } + }, + "node_modules/mkdirp-classic": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz", + "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A==", + "license": "MIT" + }, + "node_modules/mri": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/mri/-/mri-1.2.0.tgz", + "integrity": "sha512-tzzskb3bG8LvYGFF/mDTpq3jpI6Q9wc3LEmBaghu+DdCssd1FakN7Bc0hVNmEyGq1bq3RgfkCb3cmQLpNPOroA==", + "license": "MIT", + "engines": { + "node": ">=4" + } + }, + "node_modules/ms": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", + "integrity": "sha512-Tpp60P6IUJDTuOq/5Z8cdskzJujfwqfOTkrwIwj7IRISpnkJnT6SyJ4PCPnGMoFjC9ddhal5KVIYtAt97ix05A==", + "license": "MIT" + }, + "node_modules/multer": { + "version": "1.4.5-lts.2", + "resolved": "https://registry.npmjs.org/multer/-/multer-1.4.5-lts.2.tgz", + "integrity": "sha512-VzGiVigcG9zUAoCNU+xShztrlr1auZOlurXynNvO9GiWD1/mTBbUljOKY+qMeazBqXgRnjzeEgJI/wyjJUHg9A==", + "deprecated": "Multer 1.x is impacted by a number of vulnerabilities, which have been patched in 2.x. You should upgrade to the latest 2.x version.", + "license": "MIT", + "dependencies": { + "append-field": "^1.0.0", + "busboy": "^1.0.0", + "concat-stream": "^1.5.2", + "mkdirp": "^0.5.4", + "object-assign": "^4.1.1", + "type-is": "^1.6.4", + "xtend": "^4.0.0" + }, + "engines": { + "node": ">= 6.0.0" + } + }, + "node_modules/napi-build-utils": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/napi-build-utils/-/napi-build-utils-2.0.0.tgz", + "integrity": "sha512-GEbrYkbfF7MoNaoh2iGG84Mnf/WZfB0GdGEsM8wz7Expx/LlWf5U8t9nvJKXSp3qr5IsEbK04cBGhol/KwOsWA==", + "license": "MIT" + }, + "node_modules/negotiator": { + "version": "0.6.4", + "resolved": "https://registry.npmjs.org/negotiator/-/negotiator-0.6.4.tgz", + "integrity": "sha512-myRT3DiWPHqho5PrJaIRyaMv2kgYf0mUVgBNOYMuCH5Ki1yEiQaf/ZJuQ62nvpc44wL5WDbTX7yGJi1Neevw8w==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/node-abi": { + "version": "3.87.0", + "resolved": "https://registry.npmjs.org/node-abi/-/node-abi-3.87.0.tgz", + "integrity": "sha512-+CGM1L1CgmtheLcBuleyYOn7NWPVu0s0EJH2C4puxgEZb9h8QpR9G2dBfZJOAUhi7VQxuBPMd0hiISWcTyiYyQ==", + "license": "MIT", + "dependencies": { + "semver": "^7.3.5" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/node-domexception": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/node-domexception/-/node-domexception-1.0.0.tgz", + "integrity": "sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==", + "deprecated": "Use your platform's native DOMException instead", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/jimmywarting" + }, + { + "type": "github", + "url": "https://paypal.me/jimmywarting" + } + ], + "license": "MIT", + "engines": { + "node": ">=10.5.0" + } + }, + "node_modules/node-fetch": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz", + "integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==", + "license": "MIT", + "dependencies": { + "whatwg-url": "^5.0.0" + }, + "engines": { + "node": "4.x || >=6.0.0" + }, + "peerDependencies": { + "encoding": "^0.1.0" + }, + "peerDependenciesMeta": { + "encoding": { + "optional": true + } + } + }, + "node_modules/nodemon": { + "version": "3.1.11", + "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.11.tgz", + "integrity": "sha512-is96t8F/1//UHAjNPHpbsNY46ELPpftGUoSVNXwUfMk/qdjSylYrWSu1XavVTBOn526kFiOR733ATgNBCQyH0g==", + "dev": true, + "license": "MIT", + "dependencies": { + "chokidar": "^3.5.2", + "debug": "^4", + "ignore-by-default": "^1.0.1", + "minimatch": "^3.1.2", + "pstree.remy": "^1.1.8", + "semver": "^7.5.3", + "simple-update-notifier": "^2.0.0", + "supports-color": "^5.5.0", + "touch": "^3.1.0", + "undefsafe": "^2.0.5" + }, + "bin": { + "nodemon": "bin/nodemon.js" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/nodemon" + } + }, + "node_modules/nodemon/node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "dev": true, + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/nodemon/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "dev": true, + "license": "MIT" + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-inspect": { + "version": "1.13.4", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.13.4.tgz", + "integrity": "sha512-W67iLl4J2EXEGTbfeHCffrjDfitvLANg0UlX3wFUUSTx92KXRFegMHUVgSqE+wvhAbi4WqjGg9czysTV2Epbew==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object-is": { + "version": "1.1.6", + "resolved": "https://registry.npmjs.org/object-is/-/object-is-1.1.6.tgz", + "integrity": "sha512-F8cZ+KfGlSGi09lJT7/Nd6KJZ9ygtvYC0/UYYLI9nmQKLMnydpB9yvbv9K1uSkEu7FU9vYPmVwLg328tX+ot3Q==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.7", + "define-properties": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.assign": { + "version": "4.1.7", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.7.tgz", + "integrity": "sha512-nK28WOo+QIjBkDduTINE4JkF/UJJKyf2EJxvJKfblDpyg0Q+pkOHNTL0Qwy6NP6FhE/EnzV73BxxqcJaXY9anw==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "call-bound": "^1.0.3", + "define-properties": "^1.2.1", + "es-object-atoms": "^1.0.0", + "has-symbols": "^1.1.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/on-finished": { + "version": "2.4.1", + "resolved": "https://registry.npmjs.org/on-finished/-/on-finished-2.4.1.tgz", + "integrity": "sha512-oVlzkg3ENAhCk2zdv7IJwd/QUD4z2RxRwpkcGY8psCVcCYZNq4wYnVWALHM+brtuJjePWiYF/ClmuDr8Ch5+kg==", + "license": "MIT", + "dependencies": { + "ee-first": "1.1.1" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/on-headers": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/on-headers/-/on-headers-1.1.0.tgz", + "integrity": "sha512-737ZY3yNnXy37FHkQxPzt4UZ2UWPWiCZWLvFZ4fu5cueciegX0zGPnrlY6bwRg4FdQOe9YU8MkmJwGhoMybl8A==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "license": "ISC", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/openai": { + "version": "4.104.0", + "resolved": "https://registry.npmjs.org/openai/-/openai-4.104.0.tgz", + "integrity": "sha512-p99EFNsA/yX6UhVO93f5kJsDRLAg+CTA2RBqdHK4RtK8u5IJw32Hyb2dTGKbnnFmnuoBv5r7Z2CURI9sGZpSuA==", + "license": "Apache-2.0", + "dependencies": { + "@types/node": "^18.11.18", + "@types/node-fetch": "^2.6.4", + "abort-controller": "^3.0.0", + "agentkeepalive": "^4.2.1", + "form-data-encoder": "1.7.2", + "formdata-node": "^4.3.2", + "node-fetch": "^2.6.7" + }, + "bin": { + "openai": "bin/cli" + }, + "peerDependencies": { + "ws": "^8.18.0", + "zod": "^3.23.8" + }, + "peerDependenciesMeta": { + "ws": { + "optional": true + }, + "zod": { + "optional": true + } + } + }, + "node_modules/opencollective-postinstall": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/opencollective-postinstall/-/opencollective-postinstall-2.0.3.tgz", + "integrity": "sha512-8AV/sCtuzUeTo8gQK5qDZzARrulB3egtLzFgteqB2tcT4Mw7B8Kt7JcDHmltjz6FOAHsvTevk70gZEbhM4ZS9Q==", + "license": "MIT", + "bin": { + "opencollective-postinstall": "index.js" + } + }, + "node_modules/p-timeout": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-timeout/-/p-timeout-4.1.0.tgz", + "integrity": "sha512-+/wmHtzJuWii1sXn3HCuH/FTwGhrp4tmJTxSKJbfS+vkipci6osxXM5mY0jUiRzWKMTgUT8l7HFbeSwZAynqHw==", + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/pako": { + "version": "0.2.9", + "resolved": "https://registry.npmjs.org/pako/-/pako-0.2.9.tgz", + "integrity": "sha512-NUcwaKxUxWrZLpDG+z/xZaCgQITkA/Dv4V/T6bw7VON6l1Xz/VnrBqrYjZQ12TamKHzITTfOEIYUj48y2KXImA==", + "license": "MIT" + }, + "node_modules/parseurl": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/parseurl/-/parseurl-1.3.3.tgz", + "integrity": "sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/path-to-regexp": { + "version": "0.1.12", + "resolved": "https://registry.npmjs.org/path-to-regexp/-/path-to-regexp-0.1.12.tgz", + "integrity": "sha512-RA1GjUVMnvYFxuqovrEqZoxxW5NUZqbwKtYz/Tt7nXerk0LbLblQmrsgdeOxV5SFHf0UDggjS/bSeOZwt1pmEQ==", + "license": "MIT" + }, + "node_modules/pdfkit": { + "version": "0.15.2", + "resolved": "https://registry.npmjs.org/pdfkit/-/pdfkit-0.15.2.tgz", + "integrity": "sha512-s3GjpdBFSCaeDSX/v73MI5UsPqH1kjKut2AXCgxQ5OH10lPVOu5q5vLAG0OCpz/EYqKsTSw1WHpENqMvp43RKg==", + "license": "MIT", + "dependencies": { + "crypto-js": "^4.2.0", + "fontkit": "^1.8.1", + "jpeg-exif": "^1.1.4", + "linebreak": "^1.0.2", + "png-js": "^1.0.0" + } + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "license": "MIT", + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/png-js": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/png-js/-/png-js-1.0.0.tgz", + "integrity": "sha512-k+YsbhpA9e+EFfKjTCH3VW6aoKlyNYI6NYdTfDL4CIvFnvsuO84ttonmZE7rc+v23SLTH8XX+5w/Ak9v0xGY4g==" + }, + "node_modules/possible-typed-array-names": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/possible-typed-array-names/-/possible-typed-array-names-1.1.0.tgz", + "integrity": "sha512-/+5VFTchJDoVj3bhoqi6UeymcD00DAwb1nJwamzPvHEszJ4FpF6SNNbUbOS8yI56qHzdV8eK0qEfOSiodkTdxg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/prebuild-install": { + "version": "7.1.3", + "resolved": "https://registry.npmjs.org/prebuild-install/-/prebuild-install-7.1.3.tgz", + "integrity": "sha512-8Mf2cbV7x1cXPUILADGI3wuhfqWvtiLA1iclTDbFRZkgRQS0NqsPZphna9V+HyTEadheuPmjaJMsbzKQFOzLug==", + "license": "MIT", + "dependencies": { + "detect-libc": "^2.0.0", + "expand-template": "^2.0.3", + "github-from-package": "0.0.0", + "minimist": "^1.2.3", + "mkdirp-classic": "^0.5.3", + "napi-build-utils": "^2.0.0", + "node-abi": "^3.3.0", + "pump": "^3.0.0", + "rc": "^1.2.7", + "simple-get": "^4.0.0", + "tar-fs": "^2.0.0", + "tunnel-agent": "^0.6.0" + }, + "bin": { + "prebuild-install": "bin.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/process-nextick-args": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/process-nextick-args/-/process-nextick-args-2.0.1.tgz", + "integrity": "sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==", + "license": "MIT" + }, + "node_modules/proxy-addr": { + "version": "2.0.7", + "resolved": "https://registry.npmjs.org/proxy-addr/-/proxy-addr-2.0.7.tgz", + "integrity": "sha512-llQsMLSUDUPT44jdrU/O37qlnifitDP+ZwrmmZcoSKyLKvtZxpyV0n2/bD/N4tBAAZ/gJEdZU7KMraoK1+XYAg==", + "license": "MIT", + "dependencies": { + "forwarded": "0.2.0", + "ipaddr.js": "1.9.1" + }, + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/pstree.remy": { + "version": "1.1.8", + "resolved": "https://registry.npmjs.org/pstree.remy/-/pstree.remy-1.1.8.tgz", + "integrity": "sha512-77DZwxQmxKnu3aR542U+X8FypNzbfJ+C5XQDk3uWjWxn6151aIMGthWYRXTqT1E5oJvg+ljaa2OJi+VfvCOQ8w==", + "dev": true, + "license": "MIT" + }, + "node_modules/pump": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.3.tgz", + "integrity": "sha512-todwxLMY7/heScKmntwQG8CXVkWUOdYxIvY2s0VWAAMh/nd8SoYiRaKjlr7+iCs984f2P8zvrfWcDDYVb73NfA==", + "license": "MIT", + "dependencies": { + "end-of-stream": "^1.1.0", + "once": "^1.3.1" + } + }, + "node_modules/qs": { + "version": "6.14.1", + "resolved": "https://registry.npmjs.org/qs/-/qs-6.14.1.tgz", + "integrity": "sha512-4EK3+xJl8Ts67nLYNwqw/dsFVnCf+qR7RgXSK9jEEm9unao3njwMDdmsdvoKBKHzxd7tCYz5e5M+SnMjdtXGQQ==", + "license": "BSD-3-Clause", + "dependencies": { + "side-channel": "^1.1.0" + }, + "engines": { + "node": ">=0.6" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/range-parser": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/range-parser/-/range-parser-1.2.1.tgz", + "integrity": "sha512-Hrgsx+orqoygnmhFbKaHE6c296J+HTAQXoxEF6gNupROmmGJRoyzfG3ccAveqCBrwr/2yxQ5BVd/GTl5agOwSg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/raw-body": { + "version": "2.5.3", + "resolved": "https://registry.npmjs.org/raw-body/-/raw-body-2.5.3.tgz", + "integrity": "sha512-s4VSOf6yN0rvbRZGxs8Om5CWj6seneMwK3oDb4lWDH0UPhWcxwOWw5+qk24bxq87szX1ydrwylIOp2uG1ojUpA==", + "license": "MIT", + "dependencies": { + "bytes": "~3.1.2", + "http-errors": "~2.0.1", + "iconv-lite": "~0.4.24", + "unpipe": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/rc": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/rc/-/rc-1.2.8.tgz", + "integrity": "sha512-y3bGgqKj3QBdxLbLkomlohkvsA8gdAiUQlSBJnBhfn+BPxg4bc62d8TcBW15wavDfgexCgccckhcZvywyQYPOw==", + "license": "(BSD-2-Clause OR MIT OR Apache-2.0)", + "dependencies": { + "deep-extend": "^0.6.0", + "ini": "~1.3.0", + "minimist": "^1.2.0", + "strip-json-comments": "~2.0.1" + }, + "bin": { + "rc": "cli.js" + } + }, + "node_modules/readable-stream": { + "version": "2.3.8", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-2.3.8.tgz", + "integrity": "sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==", + "license": "MIT", + "dependencies": { + "core-util-is": "~1.0.0", + "inherits": "~2.0.3", + "isarray": "~1.0.0", + "process-nextick-args": "~2.0.0", + "safe-buffer": "~5.1.1", + "string_decoder": "~1.1.1", + "util-deprecate": "~1.0.1" + } + }, + "node_modules/readable-stream/node_modules/isarray": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-1.0.0.tgz", + "integrity": "sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==", + "license": "MIT" + }, + "node_modules/readable-stream/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "license": "MIT" + }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "license": "MIT", + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/regenerator-runtime": { + "version": "0.13.11", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz", + "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg==", + "license": "MIT" + }, + "node_modules/regexp.prototype.flags": { + "version": "1.5.4", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.4.tgz", + "integrity": "sha512-dYqgNSZbDwkaJ2ceRd9ojCGjBq+mOm9LmtXnAnEGyHhN/5R7iDW2TRw3h+o/jCFxus3P2LfWIIiwowAjANm7IA==", + "license": "MIT", + "dependencies": { + "call-bind": "^1.0.8", + "define-properties": "^1.2.1", + "es-errors": "^1.3.0", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "set-function-name": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/restructure": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/restructure/-/restructure-2.0.1.tgz", + "integrity": "sha512-e0dOpjm5DseomnXx2M5lpdZ5zoHqF1+bqdMJUohoYVVQa7cBdnk7fdmeI6byNWP/kiME72EeTiSypTCVnpLiDg==", + "license": "MIT" + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/safe-compare": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/safe-compare/-/safe-compare-1.1.4.tgz", + "integrity": "sha512-b9wZ986HHCo/HbKrRpBJb2kqXMK9CEWIE1egeEvZsYn69ay3kdfl9nG3RyOcR+jInTDf7a86WQ1d4VJX7goSSQ==", + "license": "MIT", + "dependencies": { + "buffer-alloc": "^1.2.0" + } + }, + "node_modules/safe-regex-test": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.1.0.tgz", + "integrity": "sha512-x/+Cz4YrimQxQccJf5mKEbIa1NzeCRNI5Ecl/ekmlYaampdNLPalVyIcCZNNH3MvmqBugV5TMYZXv0ljslUlaw==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "is-regex": "^1.2.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "license": "MIT" + }, + "node_modules/sandwich-stream": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/sandwich-stream/-/sandwich-stream-2.0.2.tgz", + "integrity": "sha512-jLYV0DORrzY3xaz/S9ydJL6Iz7essZeAfnAavsJ+zsJGZ1MOnsS52yRjU3uF3pJa/lla7+wisp//fxOwOH8SKQ==", + "license": "Apache-2.0", + "engines": { + "node": ">= 0.10" + } + }, + "node_modules/semver": { + "version": "7.7.3", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.7.3.tgz", + "integrity": "sha512-SdsKMrI9TdgjdweUSR9MweHA4EJ8YxHn8DFaDisvhVlUOe4BF1tLD7GAj0lIqWVl+dPb/rExr0Btby5loQm20Q==", + "license": "ISC", + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/send": { + "version": "0.19.2", + "resolved": "https://registry.npmjs.org/send/-/send-0.19.2.tgz", + "integrity": "sha512-VMbMxbDeehAxpOtWJXlcUS5E8iXh6QmN+BkRX1GARS3wRaXEEgzCcB10gTQazO42tpNIya8xIyNx8fll1OFPrg==", + "license": "MIT", + "dependencies": { + "debug": "2.6.9", + "depd": "2.0.0", + "destroy": "1.2.0", + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "etag": "~1.8.1", + "fresh": "~0.5.2", + "http-errors": "~2.0.1", + "mime": "1.6.0", + "ms": "2.1.3", + "on-finished": "~2.4.1", + "range-parser": "~1.2.1", + "statuses": "~2.0.2" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/send/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/serve-static": { + "version": "1.16.3", + "resolved": "https://registry.npmjs.org/serve-static/-/serve-static-1.16.3.tgz", + "integrity": "sha512-x0RTqQel6g5SY7Lg6ZreMmsOzncHFU7nhnRWkKgWuMTu5NN0DR5oruckMqRvacAN9d5w6ARnRBXl9xhDCgfMeA==", + "license": "MIT", + "dependencies": { + "encodeurl": "~2.0.0", + "escape-html": "~1.0.3", + "parseurl": "~1.3.3", + "send": "~0.19.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/set-function-length": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/set-function-length/-/set-function-length-1.2.2.tgz", + "integrity": "sha512-pgRc4hJ4/sNjWCSS9AmnS40x3bNMDTknHgL5UaMBTMyJnU90EgWh1Rz+MC9eFu4BuN/UwZjKQuY/1v3rM7HMfg==", + "license": "MIT", + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "function-bind": "^1.1.2", + "get-intrinsic": "^1.2.4", + "gopd": "^1.0.1", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/set-function-name": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/set-function-name/-/set-function-name-2.0.2.tgz", + "integrity": "sha512-7PGFlmtwsEADb0WYyvCMa1t+yke6daIG4Wirafur5kcf+MhUnPms1UeR0CKQdTZD81yESwMHbtn+TR+dMviakQ==", + "license": "MIT", + "dependencies": { + "define-data-property": "^1.1.4", + "es-errors": "^1.3.0", + "functions-have-names": "^1.2.3", + "has-property-descriptors": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/setprototypeof": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/setprototypeof/-/setprototypeof-1.2.0.tgz", + "integrity": "sha512-E5LDX7Wrp85Kil5bhZv46j8jOeboKq5JMmYM3gVGdGH8xFpPWXUMsNrlODCrkoxMEeNi/XZIwuRvY4XNwYMJpw==", + "license": "ISC" + }, + "node_modules/sharp": { + "version": "0.33.5", + "resolved": "https://registry.npmjs.org/sharp/-/sharp-0.33.5.tgz", + "integrity": "sha512-haPVm1EkS9pgvHrQ/F3Xy+hgcuMV0Wm9vfIBSiwZ05k+xgb0PkBQpGsAA/oWdDobNaZTH5ppvHtzCFbnSEwHVw==", + "hasInstallScript": true, + "license": "Apache-2.0", + "dependencies": { + "color": "^4.2.3", + "detect-libc": "^2.0.3", + "semver": "^7.6.3" + }, + "engines": { + "node": "^18.17.0 || ^20.3.0 || >=21.0.0" + }, + "funding": { + "url": "https://opencollective.com/libvips" + }, + "optionalDependencies": { + "@img/sharp-darwin-arm64": "0.33.5", + "@img/sharp-darwin-x64": "0.33.5", + "@img/sharp-libvips-darwin-arm64": "1.0.4", + "@img/sharp-libvips-darwin-x64": "1.0.4", + "@img/sharp-libvips-linux-arm": "1.0.5", + "@img/sharp-libvips-linux-arm64": "1.0.4", + "@img/sharp-libvips-linux-s390x": "1.0.4", + "@img/sharp-libvips-linux-x64": "1.0.4", + "@img/sharp-libvips-linuxmusl-arm64": "1.0.4", + "@img/sharp-libvips-linuxmusl-x64": "1.0.4", + "@img/sharp-linux-arm": "0.33.5", + "@img/sharp-linux-arm64": "0.33.5", + "@img/sharp-linux-s390x": "0.33.5", + "@img/sharp-linux-x64": "0.33.5", + "@img/sharp-linuxmusl-arm64": "0.33.5", + "@img/sharp-linuxmusl-x64": "0.33.5", + "@img/sharp-wasm32": "0.33.5", + "@img/sharp-win32-ia32": "0.33.5", + "@img/sharp-win32-x64": "0.33.5" + } + }, + "node_modules/side-channel": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.1.0.tgz", + "integrity": "sha512-ZX99e6tRweoUXqR+VBrslhda51Nh5MTQwou5tnUDgbtyM0dBgmhEDtWGP/xbKn6hqfPRHujUNwz5fy/wbbhnpw==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3", + "side-channel-list": "^1.0.0", + "side-channel-map": "^1.0.1", + "side-channel-weakmap": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-list": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/side-channel-list/-/side-channel-list-1.0.0.tgz", + "integrity": "sha512-FCLHtRD/gnpCiCHEiJLOwdmFP+wzCmDEkc9y7NsYxeF4u7Btsn1ZuwgwJGxImImHicJArLP4R0yX4c2KCrMrTA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-map": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/side-channel-map/-/side-channel-map-1.0.1.tgz", + "integrity": "sha512-VCjCNfgMsby3tTdo02nbjtM/ewra6jPHmpThenkTYh8pG9ucZ/1P8So4u4FGBek/BjpOVsDCMoLA/iuBKIFXRA==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/side-channel-weakmap": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/side-channel-weakmap/-/side-channel-weakmap-1.0.2.tgz", + "integrity": "sha512-WPS/HvHQTYnHisLo9McqBHOJk2FkHO/tlpvldyrnem4aeQp4hai3gythswg6p01oSoTl58rcpiFAjF2br2Ak2A==", + "license": "MIT", + "dependencies": { + "call-bound": "^1.0.2", + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.5", + "object-inspect": "^1.13.3", + "side-channel-map": "^1.0.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/simple-concat": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz", + "integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/simple-get": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/simple-get/-/simple-get-4.0.1.tgz", + "integrity": "sha512-brv7p5WgH0jmQJr1ZDDfKDOSeWWg+OVypG99A/5vYGPqJ6pxiaHLy8nxtFjBA7oMa01ebA9gfh1uMCFqOuXxvA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "decompress-response": "^6.0.0", + "once": "^1.3.1", + "simple-concat": "^1.0.0" + } + }, + "node_modules/simple-swizzle": { + "version": "0.2.4", + "resolved": "https://registry.npmjs.org/simple-swizzle/-/simple-swizzle-0.2.4.tgz", + "integrity": "sha512-nAu1WFPQSMNr2Zn9PGSZK9AGn4t/y97lEm+MXTtUDwfP0ksAIX4nO+6ruD9Jwut4C49SB1Ws+fbXsm/yScWOHw==", + "license": "MIT", + "dependencies": { + "is-arrayish": "^0.3.1" + } + }, + "node_modules/simple-update-notifier": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/simple-update-notifier/-/simple-update-notifier-2.0.0.tgz", + "integrity": "sha512-a2B9Y0KlNXl9u/vsW6sTIu9vGEpfKu2wRV6l1H3XEas/0gUIzGzBoP/IouTcUQbm9JWZLH3COxyn03TYlFax6w==", + "dev": true, + "license": "MIT", + "dependencies": { + "semver": "^7.5.3" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/statuses": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/statuses/-/statuses-2.0.2.tgz", + "integrity": "sha512-DvEy55V3DB7uknRo+4iOGT5fP1slR8wQohVdknigZPMpMstaKJQWhwiYBACJE3Ul2pTnATihhBYnRhZQHGBiRw==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/stop-iteration-iterator": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/stop-iteration-iterator/-/stop-iteration-iterator-1.1.0.tgz", + "integrity": "sha512-eLoXW/DHyl62zxY4SCaIgnRhuMr6ri4juEYARS8E6sCEqzKpOiE521Ucofdx+KnDZl5xmvGYaaKCk5FEOxJCoQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "internal-slot": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/streamsearch": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/streamsearch/-/streamsearch-1.1.0.tgz", + "integrity": "sha512-Mcc5wHehp9aXz1ax6bZUyY5afg9u2rv5cqQI3mRrYkGC8rW2hM02jWuwjtL++LS5qinSyhj2QfLyNsuc+VsExg==", + "engines": { + "node": ">=10.0.0" + } + }, + "node_modules/string_decoder": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.1.1.tgz", + "integrity": "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==", + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.1.0" + } + }, + "node_modules/string_decoder/node_modules/safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "license": "MIT" + }, + "node_modules/strip-json-comments": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-2.0.1.tgz", + "integrity": "sha512-4gB8na07fecVVkOI6Rs4e7T6NOTki5EmL7TUduTs6bu3EdnSycntVJ4re8kgZA+wx9IueI2Y11bfbgwtzuE0KQ==", + "license": "MIT", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "license": "MIT", + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/tar-fs": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.4.tgz", + "integrity": "sha512-mDAjwmZdh7LTT6pNleZ05Yt65HC3E+NiQzl672vQG38jIrehtJk/J3mNwIg+vShQPcLF/LV7CMnDW6vjj6sfYQ==", + "license": "MIT", + "dependencies": { + "chownr": "^1.1.1", + "mkdirp-classic": "^0.5.2", + "pump": "^3.0.0", + "tar-stream": "^2.1.4" + } + }, + "node_modules/tar-stream": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz", + "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==", + "license": "MIT", + "dependencies": { + "bl": "^4.0.3", + "end-of-stream": "^1.4.1", + "fs-constants": "^1.0.0", + "inherits": "^2.0.3", + "readable-stream": "^3.1.1" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/tar-stream/node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/telegraf": { + "version": "4.16.3", + "resolved": "https://registry.npmjs.org/telegraf/-/telegraf-4.16.3.tgz", + "integrity": "sha512-yjEu2NwkHlXu0OARWoNhJlIjX09dRktiMQFsM678BAH/PEPVwctzL67+tvXqLCRQQvm3SDtki2saGO9hLlz68w==", + "license": "MIT", + "dependencies": { + "@telegraf/types": "^7.1.0", + "abort-controller": "^3.0.0", + "debug": "^4.3.4", + "mri": "^1.2.0", + "node-fetch": "^2.7.0", + "p-timeout": "^4.1.0", + "safe-compare": "^1.1.4", + "sandwich-stream": "^2.0.2" + }, + "bin": { + "telegraf": "lib/cli.mjs" + }, + "engines": { + "node": "^12.20.0 || >=14.13.1" + } + }, + "node_modules/telegraf/node_modules/debug": { + "version": "4.4.3", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.4.3.tgz", + "integrity": "sha512-RGwwWnwQvkVfavKVt22FGLw+xYSdzARwm0ru6DhTVA3umU5hZc28V3kO4stgYryrTlLpuvgI9GiijltAjNbcqA==", + "license": "MIT", + "dependencies": { + "ms": "^2.1.3" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/telegraf/node_modules/ms": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz", + "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==", + "license": "MIT" + }, + "node_modules/tesseract.js": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/tesseract.js/-/tesseract.js-5.1.1.tgz", + "integrity": "sha512-lzVl/Ar3P3zhpUT31NjqeCo1f+D5+YfpZ5J62eo2S14QNVOmHBTtbchHm/YAbOOOzCegFnKf4B3Qih9LuldcYQ==", + "hasInstallScript": true, + "license": "Apache-2.0", + "dependencies": { + "bmp-js": "^0.1.0", + "idb-keyval": "^6.2.0", + "is-electron": "^2.2.2", + "is-url": "^1.2.4", + "node-fetch": "^2.6.9", + "opencollective-postinstall": "^2.0.3", + "regenerator-runtime": "^0.13.3", + "tesseract.js-core": "^5.1.1", + "wasm-feature-detect": "^1.2.11", + "zlibjs": "^0.3.1" + } + }, + "node_modules/tesseract.js-core": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/tesseract.js-core/-/tesseract.js-core-5.1.1.tgz", + "integrity": "sha512-KX3bYSU5iGcO1XJa+QGPbi+Zjo2qq6eBhNjSGR5E5q0JtzkoipJKOUQD7ph8kFyteCEfEQ0maWLu8MCXtvX5uQ==", + "license": "Apache-2.0" + }, + "node_modules/tiny-inflate": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/tiny-inflate/-/tiny-inflate-1.0.3.tgz", + "integrity": "sha512-pkY1fj1cKHb2seWDy0B16HeWyczlJA9/WW3u3c4z/NiWDsO3DOU5D7nhTLE9CF0yXv/QZFY7sEJmj24dK+Rrqw==", + "license": "MIT" + }, + "node_modules/to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "license": "MIT", + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/toidentifier": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/toidentifier/-/toidentifier-1.0.1.tgz", + "integrity": "sha512-o5sSPKEkg/DIQNmH43V0/uerLrpzVedkUh8tGNvaeXpfpuwjKenlSox/2O/BTlZUtEe+JG7s5YhEz608PlAHRA==", + "license": "MIT", + "engines": { + "node": ">=0.6" + } + }, + "node_modules/touch": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/touch/-/touch-3.1.1.tgz", + "integrity": "sha512-r0eojU4bI8MnHr8c5bNo7lJDdI2qXlWWJk6a9EAFG7vbhTjElYhBVS3/miuE0uOuoLdb8Mc/rVfsmm6eo5o9GA==", + "dev": true, + "license": "ISC", + "bin": { + "nodetouch": "bin/nodetouch.js" + } + }, + "node_modules/tr46": { + "version": "0.0.3", + "resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz", + "integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==", + "license": "MIT" + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/tunnel-agent": { + "version": "0.6.0", + "resolved": "https://registry.npmjs.org/tunnel-agent/-/tunnel-agent-0.6.0.tgz", + "integrity": "sha512-McnNiV1l8RYeY8tBgEpuodCC1mLUdbSN+CYBL7kJsJNInOP8UjDDEwdk6Mw60vdLLrr5NHKZhMAOSrR2NZuQ+w==", + "license": "Apache-2.0", + "dependencies": { + "safe-buffer": "^5.0.1" + }, + "engines": { + "node": "*" + } + }, + "node_modules/type-is": { + "version": "1.6.18", + "resolved": "https://registry.npmjs.org/type-is/-/type-is-1.6.18.tgz", + "integrity": "sha512-TkRKr9sUTxEH8MdfuCSP7VizJyzRNMjj2J2do2Jr3Kym598JVdEksuzPQCnlFPW4ky9Q+iA+ma9BGm06XQBy8g==", + "license": "MIT", + "dependencies": { + "media-typer": "0.3.0", + "mime-types": "~2.1.24" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/typedarray": { + "version": "0.0.6", + "resolved": "https://registry.npmjs.org/typedarray/-/typedarray-0.0.6.tgz", + "integrity": "sha512-/aCDEGatGvZ2BIk+HmLf4ifCJFwvKFNb9/JeZPMulfgFracn9QFcAf5GO8B/mweUjSoblS5In0cWhqpfs/5PQA==", + "license": "MIT" + }, + "node_modules/undefsafe": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz", + "integrity": "sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==", + "dev": true, + "license": "MIT" + }, + "node_modules/undici-types": { + "version": "5.26.5", + "resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz", + "integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==", + "license": "MIT" + }, + "node_modules/unicode-properties": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/unicode-properties/-/unicode-properties-1.4.1.tgz", + "integrity": "sha512-CLjCCLQ6UuMxWnbIylkisbRj31qxHPAurvena/0iwSVbQ2G1VY5/HjV0IRabOEbDHlzZlRdCrD4NhB0JtU40Pg==", + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.0", + "unicode-trie": "^2.0.0" + } + }, + "node_modules/unicode-trie": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/unicode-trie/-/unicode-trie-2.0.0.tgz", + "integrity": "sha512-x7bc76x0bm4prf1VLg79uhAzKw8DVboClSN5VxJuQ+LKDOVEW9CdH+VY7SP+vX7xCYQqzzgQpFqz15zeLvAtZQ==", + "license": "MIT", + "dependencies": { + "pako": "^0.2.5", + "tiny-inflate": "^1.0.0" + } + }, + "node_modules/unpipe": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/unpipe/-/unpipe-1.0.0.tgz", + "integrity": "sha512-pjy2bYhSsufwWlKwPc+l3cN7+wuJlK6uz0YdJEOlQDbl6jo/YlPi4mb8agUkVC8BF7V8NuzeyPNqRksA3hztKQ==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "license": "MIT" + }, + "node_modules/utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha512-pMZTvIkT1d+TFGvDOqodOclx0QWkkgi6Tdoa8gC8ffGAAqz9pzPTZWAybbsHHoED/ztMtkv/VoYTYyShUn81hA==", + "license": "MIT", + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/uuid": { + "version": "10.0.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-10.0.0.tgz", + "integrity": "sha512-8XkAphELsDnEGrDxUOHB3RGvXz6TeuYSGEZBOjtTtPm2lwhGBjLgOzLHB63IUWfBpNucQjND6d3AOudO+H3RWQ==", + "funding": [ + "https://github.com/sponsors/broofa", + "https://github.com/sponsors/ctavan" + ], + "license": "MIT", + "bin": { + "uuid": "dist/bin/uuid" + } + }, + "node_modules/vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha512-BNGbWLfd0eUPabhkXUVm0j8uuvREyTh5ovRa/dyow/BqAbZJyC+5fU+IzQOzmAKzYqYRAISoRhdQr3eIZ/PXqg==", + "license": "MIT", + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/wasm-feature-detect": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/wasm-feature-detect/-/wasm-feature-detect-1.8.0.tgz", + "integrity": "sha512-zksaLKM2fVlnB5jQQDqKXXwYHLQUVH9es+5TOOHwGOVJOCeRBCiPjwSg+3tN2AdTCzjgli4jijCH290kXb/zWQ==", + "license": "Apache-2.0" + }, + "node_modules/web-streams-polyfill": { + "version": "4.0.0-beta.3", + "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-4.0.0-beta.3.tgz", + "integrity": "sha512-QW95TCTaHmsYfHDybGMwO5IJIM93I/6vTRk+daHTWFPhwh+C8Cg7j7XyKrwrj8Ib6vYXe0ocYNrmzY4xAAN6ug==", + "license": "MIT", + "engines": { + "node": ">= 14" + } + }, + "node_modules/webidl-conversions": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz", + "integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==", + "license": "BSD-2-Clause" + }, + "node_modules/whatwg-url": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz", + "integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==", + "license": "MIT", + "dependencies": { + "tr46": "~0.0.3", + "webidl-conversions": "^3.0.0" + } + }, + "node_modules/which-boxed-primitive": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.1.1.tgz", + "integrity": "sha512-TbX3mj8n0odCBFVlY8AxkqcHASw3L60jIuF8jFP78az3C2YhmGvqbHBpAjTRH2/xqYunrJ9g1jSyjCjpoWzIAA==", + "license": "MIT", + "dependencies": { + "is-bigint": "^1.1.0", + "is-boolean-object": "^1.2.1", + "is-number-object": "^1.1.1", + "is-string": "^1.1.1", + "is-symbol": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-collection": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/which-collection/-/which-collection-1.0.2.tgz", + "integrity": "sha512-K4jVyjnBdgvc86Y6BkaLZEN933SwYOuBFkdmBu9ZfkcAbdVbpITnDmjvZ/aQjRXQrv5EPkTnD1s39GiiqbngCw==", + "license": "MIT", + "dependencies": { + "is-map": "^2.0.3", + "is-set": "^2.0.3", + "is-weakmap": "^2.0.2", + "is-weakset": "^2.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-typed-array": { + "version": "1.1.20", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.20.tgz", + "integrity": "sha512-LYfpUkmqwl0h9A2HL09Mms427Q1RZWuOHsukfVcKRq9q95iQxdw0ix1JQrqbcDR9PH1QDwf5Qo8OZb5lksZ8Xg==", + "license": "MIT", + "dependencies": { + "available-typed-arrays": "^1.0.7", + "call-bind": "^1.0.8", + "call-bound": "^1.0.4", + "for-each": "^0.3.5", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-tostringtag": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "license": "ISC" + }, + "node_modules/xtend": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/xtend/-/xtend-4.0.2.tgz", + "integrity": "sha512-LKYU1iAXJXUgAXn9URjiu+MWhyUXHsvfp7mcuYm9dSUKK0/CjtrUwFAxD82/mCWbtLsGjFIad0wIsod4zrTAEQ==", + "license": "MIT", + "engines": { + "node": ">=0.4" + } + }, + "node_modules/zlibjs": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/zlibjs/-/zlibjs-0.3.1.tgz", + "integrity": "sha512-+J9RrgTKOmlxFSDHo0pI1xM6BLVUv+o0ZT9ANtCxGkjIVCCUdx9alUF8Gm+dGLKbkkkidWIHFDZHDMpfITt4+w==", + "license": "MIT", + "engines": { + "node": "*" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..936cf08 --- /dev/null +++ b/package.json @@ -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" + } +} diff --git a/public/index.html b/public/index.html new file mode 100644 index 0000000..7f66f54 --- /dev/null +++ b/public/index.html @@ -0,0 +1,1098 @@ + + + + + + DocuAgro - Painel da Cooperativa + + + + + +
+ +
+ + +
+ +
+

🌱 DocuAgro Painel Cooperativa

+
+
👤
+ 📥 Exportar CSV + + +
+
+ + +
+ +
+
+
👥
+
-
+
Total Produtores
+
+
+
+
-
+
Completos
+
+
+
🔄
+
-
+
Em Andamento
+
+
+
⚠️
+
-
+
Pendentes
+
+
+
📄
+
-
+
Documentos Recebidos
+
+
+ + +
+
+

📊 Compliance EUDR Geral

+
0%
+
+
+
+
+
+ + +
+
+

👨‍🌾 Produtores

+ +
+
+
+
+ Carregando... +
+
+
+
+
+ + + + + + + diff --git a/src/api/routes.js b/src/api/routes.js new file mode 100644 index 0000000..ed479d3 --- /dev/null +++ b/src/api/routes.js @@ -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; diff --git a/src/bot/telegram-bot.js b/src/bot/telegram-bot.js new file mode 100644 index 0000000..ce34ecc --- /dev/null +++ b/src/bot/telegram-bot.js @@ -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 }; diff --git a/src/index.js b/src/index.js new file mode 100644 index 0000000..4494618 --- /dev/null +++ b/src/index.js @@ -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); +}); diff --git a/src/services/ai-service.js b/src/services/ai-service.js new file mode 100644 index 0000000..2234f03 --- /dev/null +++ b/src/services/ai-service.js @@ -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 +}; diff --git a/src/services/auth-service.js b/src/services/auth-service.js new file mode 100644 index 0000000..ba9ca32 --- /dev/null +++ b/src/services/auth-service.js @@ -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 +}; diff --git a/src/services/database.js b/src/services/database.js new file mode 100644 index 0000000..0726cf2 --- /dev/null +++ b/src/services/database.js @@ -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 +}; diff --git a/src/services/ocr-service.js b/src/services/ocr-service.js new file mode 100644 index 0000000..4e6d5a7 --- /dev/null +++ b/src/services/ocr-service.js @@ -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 +}; diff --git a/src/services/pdf-service.js b/src/services/pdf-service.js new file mode 100644 index 0000000..f2ff866 --- /dev/null +++ b/src/services/pdf-service.js @@ -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 +}; diff --git a/src/services/system-prompt.js b/src/services/system-prompt.js new file mode 100644 index 0000000..4410d77 --- /dev/null +++ b/src/services/system-prompt.js @@ -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 +}; diff --git a/src/setup-db.js b/src/setup-db.js new file mode 100644 index 0000000..62cc653 --- /dev/null +++ b/src/setup-db.js @@ -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(); diff --git a/uploads/.gitkeep b/uploads/.gitkeep new file mode 100644 index 0000000..e69de29