diff --git a/.gitignore b/.gitignore
new file mode 100644
index 0000000..cc61f26
--- /dev/null
+++ b/.gitignore
@@ -0,0 +1,17 @@
+# Variáveis de ambiente
+.env
+.env.* # Ignora todas as variações (.env.local, .env.dev, etc.)
+
+# Dependências
+node_modules/
+
+# Logs e temporários
+npm-debug.log*
+yarn-debug.log*
+yarn-error.log*
+
+# IDE
+.vscode/
+
+#Testes
+test.js
diff --git a/README.md b/README.md
index 72aa510..27e6311 100644
--- a/README.md
+++ b/README.md
@@ -1,2 +1,185 @@
-# projeto_cli_desenvolve
-Projeto em equipe do Desenvolve do Grupo Boticário
+# 🤖 VingaDevs CLI — Análise de Sentimentos com DeepSeek via OpenRouter
+
+[](https://nodejs.org/)
+[](https://openrouter.ai/)
+[](LICENSE)
+[](https://github.com/LilianMS/projeto_cli_desenvolve)
+
+> **Projeto da equipe VingaDevs - Desenvolve Grupo Boticário** 🚀
+
+Este é um **CLI (Command Line Interface)** desenvolvido pela equipe **VingaDevs** que analisa sentimentos em textos em português utilizando o modelo **DeepSeek** via API **OpenRouter**, que oferece acesso gratuito a modelos avançados de IA.
+
+---
+
+## ✨ Funcionalidades Principais
+
+- 🎯 Classificação simples e rápida: Positivo, Neutro ou Negativo
+- 💬 Resultado com emojis e cores para melhor visualização
+- 🔄 Retry automático para lidar com falhas temporárias da API
+- 🧹 Sanitização do texto para melhorar a qualidade da análise
+- 🛡️ Validação e tratamento de erros na entrada e API
+- ⚙️ Configuração flexível para trocar modelos IA conforme necessidade
+- 📦 Código modular, organizado e fácil de manter
+
+---
+
+## 🧰 Tecnologias Utilizadas
+
+- **Node.js** v18+
+- **OpenRouter API** (modelo DeepSeek Chat)
+- **Commander** para interface de linha de comando
+- **Chalk** para cores e emojis no terminal
+- **Dotenv** para variáveis de ambiente
+- **Node-fetch** para requisições HTTP
+- Arquitetura modularizada para facilidade de manutenção
+
+---
+
+## 🚀 Passo a Passo: Instalação e Uso
+
+### 1. Clone o Repositório e Entre na Branch
+
+```bash
+git clone https://github.com/LilianMS/projeto_cli_desenvolve.git
+cd projeto_cli_desenvolve
+git checkout leticia
+```
+### 2. Instale as Dependências
+
+```bash
+npm install
+```
+### 3. Crie sua conta no OpenRouter e obtenha a API Key
+
+* Acesse o site oficial do OpenRouter: https://openrouter.ai/
+
+* Clique em Sign Up e crie sua conta gratuita
+
+* Após confirmar o email e fazer login, vá para o painel de usuário (Dashboard)
+
+* Na aba API Keys, clique em Create New API Key
+
+* Copie sua chave gerada (string alfanumérica)
+
+### 4. Configure a chave no projeto
+
+Na raiz do projeto, crie um arquivo .env com o seguinte conteúdo:
+
+```bash
+OPENROUTER_API_KEY="sua_chave_aqui"
+```
+### 5. Execute a análise de sentimento
+
+Você pode analisar o sentimento de um texto direto pelo terminal, assim:
+
+```bash
+node src/index.js "Estou muito feliz com o resultado!"
+```
+Saída esperada:
+
+```bash
+😄 Positivo (Confiança: 92%)
+```
+
+---
+
+### ⚙️ Como usar outro modelo IA
+
+Este projeto foi desenvolvido para ser flexível e permitir o uso de outros modelos IA via OpenRouter (ex: GPT-4, Claude) ou outros provedores.
+
+Para trocar o modelo:
+
+1. Abra o arquivo src/config/constants.js
+
+2. Modifique a linha do modelo, por exemplo:
+
+```js
+export const CONFIG = {
+ MODEL: "openrouter/deepseek-chat", // modelo padrão
+ // MODEL: "openai/gpt-4", // outro modelo disponível
+ MAX_RETRIES: 3,
+};
+
+```
+3. Ajuste, se necessário, a estrutura das chamadas na camada de serviço (arquivo services/sentimentService.js), pois diferentes modelos podem ter requisições e respostas distintas.
+
+Dica: Consulte a documentação do OpenRouter e do modelo escolhido para ajustar o payload da API.
+
+---
+
+### 🛠 Estrutura do Projeto
+
+```bash
+ src/
+├── commands/ # Comandos CLI e parsing de argumentos
+├── config/ # Constantes e configurações (chave, modelo, retries)
+├── services/ # Integração com API OpenRouter
+├── utils/ # Sanitização, validação e saída formatada
+├── index.js # Ponto de entrada do CLI
+
+```
+
+---
+
+### 💡 Exemplos de Uso
+
+| Comando | Saída Esperada |
+|---------------------------------------------|--------------------------|
+| `node src/index.js "Hoje foi um dia ótimo!"` | 😄 Positivo (92%) |
+| `node src/index.js "Não gostei do atendimento"` | 😠 Negativo (88%) |
+| `node src/index.js "Está tudo ok."` | 😐 Neutro (75%) |
+
+---
+
+### 🔧 Tratamento de Erros e Retry
+
+* Validação do texto para garantir entrada válida
+
+* Retry automático em até 3 tentativas para chamadas falhas na API
+
+* Mensagens claras para erros de rede ou chave inválida
+
+---
+
+## 🤝 Equipe VingaDevs
+
+- **Alicia** — UX e modo interativo
+- **Brenda** — Integração API e testes
+- **Davis** — Arquitetura e backend
+- **Leticia** — Documentação, requisitos e código (versão DeepSeek/OpenRouter)
+- **Lilian** — Organização e integração de APIs
+
+---
+
+## 🚧 Melhorias Futuras
+
+- 📁 Análise de arquivos de texto (`.txt`)
+- 🐛 Modo verboso para logs detalhados (`--verbose`)
+- 🧠 Cache local para reduzir chamadas repetidas
+- 🧪 Testes automatizados com Jest/Mocha
+- 📤 Exportação dos resultados para CSV/JSON
+
+---
+
+## 📄 Licença
+
+MIT License © 2024 - Equipe VingaDevs
+
+---
+
+## 🙏 Agradecimentos
+
+- **Desenvolve Grupo Boticário** pelo programa de capacitação
+- **OpenRouter** pela API gratuita e confiável
+- **Comunidade Node.js** e mantenedores das bibliotecas utilizadas
+
+---
+
+
+
+Feito com ❤️ pela equipe **VingaDevs** do Desenvolve Grupo Boticário
+
+[⬆️ Voltar ao topo](#)
+
+
+```
diff --git a/package-lock.json b/package-lock.json
new file mode 100644
index 0000000..664cb83
--- /dev/null
+++ b/package-lock.json
@@ -0,0 +1,175 @@
+{
+ "name": "sentimentos-cli",
+ "version": "1.0.0",
+ "lockfileVersion": 3,
+ "requires": true,
+ "packages": {
+ "": {
+ "name": "sentimentos-cli",
+ "version": "1.0.0",
+ "license": "ISC",
+ "dependencies": {
+ "chalk": "^5.4.1",
+ "commander": "^14.0.0",
+ "dotenv": "^17.2.1",
+ "node-fetch": "^3.3.2",
+ "prompt-sync": "^4.2.0"
+ },
+ "devDependencies": {}
+ },
+ "node_modules/ansi-regex": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-4.1.1.tgz",
+ "integrity": "sha512-ILlv4k/3f6vfQ4OoP2AGvirOktlQ98ZEL1k9FaQjxa3L1abBgbuTDAdPOpvbGncC0BTVQrl+OM8xZGK6tWXt7g==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/chalk": {
+ "version": "5.4.1",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.4.1.tgz",
+ "integrity": "sha512-zgVZuo2WcZgfUEmsn6eO3kINexW8RAE4maiQ8QNs8CtpPCSyMiYsULR3HQYkm3w8FIA3SberyMJMSldGsW+U3w==",
+ "license": "MIT",
+ "engines": {
+ "node": "^12.17.0 || ^14.13 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/chalk?sponsor=1"
+ }
+ },
+ "node_modules/commander": {
+ "version": "14.0.0",
+ "resolved": "https://registry.npmjs.org/commander/-/commander-14.0.0.tgz",
+ "integrity": "sha512-2uM9rYjPvyq39NwLRqaiLtWHyDC1FvryJDa2ATTVims5YAS4PupsEQsDvP14FqhFr0P49CYDugi59xaxJlTXRA==",
+ "license": "MIT",
+ "engines": {
+ "node": ">=20"
+ }
+ },
+ "node_modules/data-uri-to-buffer": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/data-uri-to-buffer/-/data-uri-to-buffer-4.0.1.tgz",
+ "integrity": "sha512-0R9ikRb668HB7QDxT1vkpuUBtqc53YyAwMwGeUFKRojY/NWKvdZ+9UYtRfGmhqNbRkTSVpMbmyhXipFFv2cb/A==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 12"
+ }
+ },
+ "node_modules/dotenv": {
+ "version": "17.2.1",
+ "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-17.2.1.tgz",
+ "integrity": "sha512-kQhDYKZecqnM0fCnzI5eIv5L4cAe/iRI+HqMbO/hbRdTAeXDG+M9FjipUxNfbARuEg4iHIbhnhs78BCHNbSxEQ==",
+ "license": "BSD-2-Clause",
+ "engines": {
+ "node": ">=12"
+ },
+ "funding": {
+ "url": "https://dotenvx.com"
+ }
+ },
+ "node_modules/fetch-blob": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/fetch-blob/-/fetch-blob-3.2.0.tgz",
+ "integrity": "sha512-7yAQpD2UMJzLi1Dqv7qFYnPbaPx7ZfFK6PiIxQ4PfkGPyNyl2Ugx+a/umUonmKqjhM4DnfbMvdX6otXq83soQQ==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/jimmywarting"
+ },
+ {
+ "type": "paypal",
+ "url": "https://paypal.me/jimmywarting"
+ }
+ ],
+ "license": "MIT",
+ "dependencies": {
+ "node-domexception": "^1.0.0",
+ "web-streams-polyfill": "^3.0.3"
+ },
+ "engines": {
+ "node": "^12.20 || >= 14.13"
+ }
+ },
+ "node_modules/formdata-polyfill": {
+ "version": "4.0.10",
+ "resolved": "https://registry.npmjs.org/formdata-polyfill/-/formdata-polyfill-4.0.10.tgz",
+ "integrity": "sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==",
+ "license": "MIT",
+ "dependencies": {
+ "fetch-blob": "^3.1.2"
+ },
+ "engines": {
+ "node": ">=12.20.0"
+ }
+ },
+ "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": "3.3.2",
+ "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-3.3.2.tgz",
+ "integrity": "sha512-dRB78srN/l6gqWulah9SrxeYnxeddIG30+GOqK/9OlLVyLg3HPnr6SqOWTWOXKRwC2eGYCkZ59NNuSgvSrpgOA==",
+ "license": "MIT",
+ "dependencies": {
+ "data-uri-to-buffer": "^4.0.0",
+ "fetch-blob": "^3.1.4",
+ "formdata-polyfill": "^4.0.10"
+ },
+ "engines": {
+ "node": "^12.20.0 || ^14.13.1 || >=16.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/node-fetch"
+ }
+ },
+ "node_modules/prompt-sync": {
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/prompt-sync/-/prompt-sync-4.2.0.tgz",
+ "integrity": "sha512-BuEzzc5zptP5LsgV5MZETjDaKSWfchl5U9Luiu8SKp7iZWD5tZalOxvNcZRwv+d2phNFr8xlbxmFNcRKfJOzJw==",
+ "license": "MIT",
+ "dependencies": {
+ "strip-ansi": "^5.0.0"
+ }
+ },
+ "node_modules/strip-ansi": {
+ "version": "5.2.0",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-5.2.0.tgz",
+ "integrity": "sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA==",
+ "license": "MIT",
+ "dependencies": {
+ "ansi-regex": "^4.1.0"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/web-streams-polyfill": {
+ "version": "3.3.3",
+ "resolved": "https://registry.npmjs.org/web-streams-polyfill/-/web-streams-polyfill-3.3.3.tgz",
+ "integrity": "sha512-d2JWLCivmZYTSIoge9MsgFCZrt571BikcWGYkjC1khllbTeDlGqZ2D8vD8E/lJa8WGWbb7Plm8/XJYV7IJHZZw==",
+ "license": "MIT",
+ "engines": {
+ "node": ">= 8"
+ }
+ }
+ }
+}
diff --git a/package.json b/package.json
new file mode 100644
index 0000000..6b1102a
--- /dev/null
+++ b/package.json
@@ -0,0 +1,22 @@
+{
+ "name": "sentimentos-cli",
+ "version": "1.0.0",
+ "description": "",
+ "main": "index.js",
+ "type": "module",
+ "scripts": {
+ "start": "node src/index.js",
+ "test": "node src/index.js 'Texto de teste'"
+ },
+ "keywords": [],
+ "author": "",
+ "license": "ISC",
+ "dependencies": {
+ "chalk": "^5.4.1",
+ "commander": "^14.0.0",
+ "dotenv": "^17.2.1",
+ "node-fetch": "^3.3.2",
+ "prompt-sync": "^4.2.0"
+ },
+ "devDependencies": {}
+}
diff --git a/src/commands/analyzeCmd.js b/src/commands/analyzeCmd.js
new file mode 100644
index 0000000..ff92b40
--- /dev/null
+++ b/src/commands/analyzeCmd.js
@@ -0,0 +1,56 @@
+import { analyzeSentiment } from '../services/sentimentService.js';
+import { validarTexto, sanitizarTexto } from '../utils/sentimentValidation.js';
+import { exibirResultado, exibirErro, exibirMensagemAnalise } from '../utils/display.js';
+import { CONFIG, MESSAGES } from '../config/constants.js';
+
+/**
+ * Executa a análise de sentimento.
+ * @param {string} texto
+ */
+export async function executarAnalise(textoBruto) {
+ try {
+ // 1. Validação da entrada
+ const { valido, erro } = validarTexto(textoBruto);
+ if (!valido) {
+ exibirErro(`${MESSAGES.INVALID_TEXT}: ${erro}`);
+ return;
+ }
+
+ // 2. Sanitização do texto
+ const textoSanitizado = sanitizarTexto(textoBruto);
+
+ // 3. Exibir mensagem de análise
+ exibirMensagemAnalise();
+
+ // 4. Chamar o serviço de análise de sentimento (com retries)
+ let resultadoAnalise = null;
+ let tentativas = 0;
+ while (tentativas < CONFIG.MAX_RETRIES) {
+ try {
+ resultadoAnalise = await analyzeSentiment(textoSanitizado);
+ break; // Sai do loop se a chamada for bem-sucedida
+ } catch (apiError) {
+ tentativas++;
+ if (tentativas < CONFIG.MAX_RETRIES) {
+ exibirErro(`Tentativa ${tentativas} falhou: ${apiError.message}. Retentando...`);
+ // Opcional: adicionar um delay antes de retentar
+ await new Promise(resolve => setTimeout(resolve, 1000 * tentativas)); // Espera 1s, depois 2s...
+ } else {
+ throw apiError; // Lança o erro após todas as tentativas falharem
+ }
+ }
+ }
+
+ // 5. Exibir resultado
+ if (resultadoAnalise) {
+ exibirResultado(resultadoAnalise.sentimento, resultadoAnalise.confianca);
+ } else {
+ exibirErro("Não foi possível obter um resultado da análise.");
+ }
+
+ } catch (error) {
+ // Tratamento de erros centralizado para qualquer falha não capturada anteriormente
+ exibirErro(error.message, error);
+ }
+}
+
diff --git a/src/config/constants.js b/src/config/constants.js
new file mode 100644
index 0000000..dbcb502
--- /dev/null
+++ b/src/config/constants.js
@@ -0,0 +1,31 @@
+//Mensagens em PT-BR (centralizadas para fácil manutenção)
+export const MESSAGES = {
+ API_ERROR: "Erro na API",
+ INVALID_TEXT: "Texto inválido",
+ TIMEOUT: "Tempo de resposta excedido",
+ AUTH_ERROR: " Chave da API inválida",
+ ANALYZING: 'Analisando sentimento... Aguarde 🧠',
+ RESULT_PREFIX: 'Resultado da análise:',
+
+};
+
+// Configurações Padrão
+export const CONFIG={
+ MODEL: "deepseek/deepseek-chat-v3-0324:free", // Corrigido para o ID correto do modelo
+ MAX_RETRIES: 2,
+ TIMEOUT_MS: 30000, // 30 segundos
+};
+
+export const SENTIMENT_EMOJIS = {
+ positive: '😄',
+ negative: '😠',
+ neutral: '😐',
+ unknown: '🤔'
+};
+
+export const SENTIMENT_LABELS = {
+ positive: 'Positivo',
+ negative: 'Negativo',
+ neutral: 'Neutro',
+ unknown: 'Indeterminado'
+};
diff --git a/src/index.js b/src/index.js
new file mode 100644
index 0000000..493c863
--- /dev/null
+++ b/src/index.js
@@ -0,0 +1,27 @@
+#!/usr/bin/env node
+import { program } from 'commander';
+import { executarAnalise } from './commands/analyzeCmd.js';
+import { exibirErro, exibirAjuda } from './utils/display.js';
+
+program
+ .description('Analisa sentimentos em PT-BR usando DeepSeek')
+ .argument('', 'Texto para análise')
+ .action(executarAnalise);
+
+// Exibe ajuda se nenhum argumento for fornecido
+if (process.argv.length < 3) {
+ exibirAjuda();
+ process.exit(0);
+}
+
+program.parseAsync(process.argv).catch((error) => {
+ if (error.code === 'commander.missingArgument') {
+ exibirErro(`Erro: ${error.message}`);
+ exibirAjuda();
+ } else {
+ exibirErro(error.message, error);
+ }
+ process.exit(1);
+});
+
+
diff --git a/src/services/sentimentService.js b/src/services/sentimentService.js
new file mode 100644
index 0000000..6ea084a
--- /dev/null
+++ b/src/services/sentimentService.js
@@ -0,0 +1,48 @@
+import 'dotenv/config';
+import { CONFIG, MESSAGES } from '../config/constants.js';
+import fetch from 'node-fetch'; // Importar node-fetch explicitamente
+
+// Configurações específicas para OpenRouter (API)
+const OPENROUTER_API_URL = 'https://openrouter.ai/api/v1/chat/completions';
+const OPENROUTER_API_KEY = process.env.OPENROUTER_API_KEY; // Renomeie no .env quando for usar outra API
+const HTTP_REFERER = 'https://github.com/LilianMS/projeto_cli_desenvolve/tree/leticia'; // Substitua pelo seu projeto
+
+export async function analyzeSentiment(texto) {
+ if (!OPENROUTER_API_KEY) throw new Error(MESSAGES.AUTH_ERROR);
+
+ try {
+ const response = await fetch(OPENROUTER_API_URL, {
+ method: 'POST',
+ headers: {
+ 'Authorization': `Bearer ${OPENROUTER_API_KEY}`,
+ 'HTTP-Referer': HTTP_REFERER, // Obrigatório para OpenRouter!
+ 'Content-Type': 'application/json',
+ 'X-Title': 'Análise de Sentimentos' // Opcional (identificação)
+ },
+ body: JSON.stringify({
+ model: CONFIG.MODEL, // Modelo via OpenRouter
+ messages: [{
+ role: "user",
+ content: `Analise o sentimento do texto (responda APENAS com "POSITIVO", "NEGATIVO" ou "NEUTRO"): "${texto}"`
+ }],
+ temperature: 0.3
+ })
+ });
+
+ if (!response.ok) {
+ const error = await response.json();
+ throw new Error(error.error?.message || 'Erro na API');
+ }
+
+ const data = await response.json();
+ const result = data.choices[0].message.content.trim().toUpperCase();
+
+ // Mapeamento simples
+ if (result.includes("POSITIVO")) return { sentimento: "positive", confianca: 0.9 };
+ if (result.includes("NEGATIVO")) return { sentimento: "negative", confianca: 0.9 };
+ return { sentimento: "neutral", confianca: 0.7 };
+
+ } catch (error) {
+ throw new Error(`${MESSAGES.API_ERROR}: ${error.message}`);
+ }
+}
diff --git a/src/utils/display.js b/src/utils/display.js
new file mode 100644
index 0000000..3811b9a
--- /dev/null
+++ b/src/utils/display.js
@@ -0,0 +1,71 @@
+import chalk from 'chalk';
+import { MESSAGES, SENTIMENT_EMOJIS, SENTIMENT_LABELS } from '../config/constants.js';
+
+/**
+ * Exibe mensagem de carregamento/análise.
+ */
+export function exibirMensagemAnalise() {
+ console.log(chalk.yellow(MESSAGES.ANALYZING));
+}
+
+/**
+ * Exibe o resultado da análise de sentimento.
+ * @param {string} sentimento - Sentimento analisado ('positive', 'negative', 'neutral', 'unknown')
+ * @param {number} confianca - Nível de confiança da análise (0-1)
+ */
+export function exibirResultado(sentimento, confianca = null) {
+ console.log(chalk.green(MESSAGES.RESULT_PREFIX));
+
+ const emoji = SENTIMENT_EMOJIS[sentimento] || SENTIMENT_EMOJIS.unknown;
+ const label = SENTIMENT_LABELS[sentimento] || SENTIMENT_LABELS.unknown;
+
+ console.log(`${emoji} ${chalk.bold(label)}`);
+
+ if (confianca !== null) {
+ const porcentagem = Math.round(confianca * 100);
+ const cor = porcentagem >= 80 ? 'green' : porcentagem >= 60 ? 'yellow' : 'red';
+ console.log(chalk[cor](`Confiança: ${porcentagem}%`));
+ }
+}
+
+/**
+ * Exibe mensagem de erro formatada.
+ * @param {string} mensagem - Mensagem de erro.
+ * @param {Error} erro - Objeto de erro (opcional).
+ */
+export function exibirErro(mensagem, erro = null) {
+ console.error(chalk.red(`❌ ${mensagem}`));
+
+ // Exibe o stack trace completo em ambiente de desenvolvimento
+ if (erro && process.env.NODE_ENV === 'development') {
+ console.error(chalk.gray(erro.stack));
+ }
+}
+
+/**
+ * Exibe informações sobre o uso da aplicação.
+ */
+export function exibirAjuda() {
+ console.log(chalk.cyan('\n📖 Como usar:'));
+ console.log(' node src/index.js "sua frase aqui"');
+ console.log('\n📝 Exemplos:');
+ console.log(' node src/index.js "Estou muito feliz hoje!"');
+ console.log(' node src/index.js "Que dia terrível..."');
+ console.log(' node src/index.js "O tempo está normal."');
+}
+
+/**
+ * Exibe mensagem de sucesso.
+ * @param {string} mensagem - Mensagem de sucesso.
+ */
+export function exibirSucesso(mensagem) {
+ console.log(chalk.green(`✅ ${mensagem}`));
+}
+
+/**
+ * Exibe mensagem de aviso.
+ * @param {string} mensagem - Mensagem de aviso.
+ */
+export function exibirAviso(mensagem) {
+ console.log(chalk.yellow(`⚠️ ${mensagem}`));
+}
\ No newline at end of file
diff --git a/src/utils/sentimentValidation.js b/src/utils/sentimentValidation.js
new file mode 100644
index 0000000..368869a
--- /dev/null
+++ b/src/utils/sentimentValidation.js
@@ -0,0 +1,81 @@
+/**
+ * Utilitários para validação de entrada
+ */
+
+/**
+ * Valida se o texto fornecido é válido para análise
+ * @param {string} texto - Texto a ser validado
+ * @returns {Object} - { valido: boolean, erro: string|null }
+ */
+export function validarTexto(texto) {
+ // Verifica se o texto existe
+ if (!texto) {
+ return {
+ valido: false,
+ erro: 'Texto não fornecido'
+ };
+ }
+
+ // Verifica se é uma string
+ if (typeof texto !== 'string') {
+ return {
+ valido: false,
+ erro: 'Texto deve ser uma string'
+ };
+ }
+
+ // Remove espaços em branco e verifica se não está vazio
+ const textoLimpo = texto.trim();
+ if (textoLimpo.length === 0) {
+ return {
+ valido: false,
+ erro: 'Texto não pode estar vazio'
+ };
+ }
+
+ // Verifica comprimento mínimo
+ if (textoLimpo.length < 2) {
+ return {
+ valido: false,
+ erro: 'Texto muito curto (mínimo 2 caracteres)'
+ };
+ }
+
+ // Verifica comprimento máximo (para evitar textos excessivamente longos)
+ if (textoLimpo.length > 1000) {
+ return {
+ valido: false,
+ erro: 'Texto muito longo (máximo 1000 caracteres)'
+ };
+ }
+
+ // Verifica se contém apenas espaços ou caracteres especiais
+ const textoSemEspacos = textoLimpo.replace(/\s+/g, '');
+ if (textoSemEspacos.length === 0) {
+ return {
+ valido: false,
+ erro: 'Texto deve conter pelo menos uma palavra'
+ };
+ }
+
+ return {
+ valido: true,
+ erro: null
+ };
+}
+
+/**
+ * Sanitiza o texto removendo caracteres desnecessários, espaços múltiplos / limpa o texto
+ * @param {string} texto - Texto a ser sanitizado
+ * @returns {string} - Texto sanitizado
+ */
+export function sanitizarTexto(texto) {
+ if (!texto || typeof texto !== 'string') {
+ return '';
+ }
+
+ return texto
+ .trim()
+ .replace(/\s+/g, ' ') // Remove espaços múltiplos
+ .replace(/[^\w\s\u00C0-\u017F.,!?;:\'"()-]/g, ''); // Remove caracteres especiais, mantendo acentos e pontuação básica
+}
\ No newline at end of file