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

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
21 changes: 21 additions & 0 deletions chat-nlp-module/.env.example
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
# === Server ===
PORT=3001

# === Evolution API ===
EVOLUTION_API_URL=http://localhost:8080
EVOLUTION_API_KEY=your-evolution-api-key
EVOLUTION_INSTANCE_NAME=asesores-chat

# === Database (PostgreSQL) ===
DB_HOST=postgres
DB_PORT=5432
DB_NAME=chat_nlp
DB_USER=postgres
DB_PASSWORD=changeme

# === CRM API ===
CRM_API_URL=http://localhost:8081
CRM_API_KEY=your-crm-api-key

# === Frontend ===
FRONTEND_ORIGIN=http://localhost:3000
5 changes: 5 additions & 0 deletions chat-nlp-module/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
node_modules/
.env
logs/
build/
dist/
103 changes: 103 additions & 0 deletions chat-nlp-module/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
# Chat NLP Module - Plataforma de Asesores Auteco

Interfaz de chat integrada con extracción automática de datos (NLP) para asesores de ventas de motocicletas Auteco. Conecta con WhatsApp a través de **Evolution API** y sincroniza oportunidades con el CRM.

## Arquitectura

```
┌─────────────┐ ┌──────────────┐ ┌───────────────┐
│ Frontend │◄───►│ Backend │◄───►│ Evolution API │◄──► WhatsApp
│ React SPA │ │ Express.js │ │ (webhook) │
└─────────────┘ └──────┬───────┘ └───────────────┘
┌──────┴───────┐
│ NLP Module │
│ (regex) │
└──────┬───────┘
┌──────────┴──────────┐
│ │
┌──────┴──────┐ ┌───────┴───────┐
│ PostgreSQL │ │ CRM API │
│ / NocoDB │ │ (Oportunidades│
└─────────────┘ └───────────────┘
```

## Componentes

### Backend (`/backend`)
- **Express.js** con Socket.IO para comunicación en tiempo real
- **Webhook handler** para eventos de Evolution API (mensajes entrantes/salientes)
- **NLP Extractor**: detección automática de nombre, teléfono, cédula, email, profesión y modelo de moto Auteco
- **CRM Sync**: creación de oportunidades con datos extraídos

### Frontend (`/frontend`)
- **React** con Context API para estado global
- Panel de conversaciones (izquierda)
- Chat de mensajes tipo WhatsApp (centro)
- Panel de datos extraídos + inventario (derecha)
- Botón de acción rápida "Crear Oportunidad en CRM"

### NLP - Datos Extraídos
| Campo | Ejemplo detectado |
|-------|-------------------|
| Nombre | "mi nombre es Juan Pérez" |
| Teléfono | "cel 3101234567", "+57 310 123 4567" |
| Cédula | "CC 1234567890", "cédula: 1.234.567.890" |
| Email | "juan@example.com" |
| Profesión | "soy ingeniero", "trabajo como contador" |
| Modelo moto | "Pulsar NS 200", "Duke 390", "Dominar 400" |

## Inicio Rápido

### Con Docker Compose
```bash
cd chat-nlp-module
cp .env.example .env
# Editar .env con credenciales reales
docker compose up --build
```

### Desarrollo Local
```bash
# Backend
cd backend
npm install
npm run dev

# Frontend (otra terminal)
cd frontend
npm install
npm start
```

## Variables de Entorno

Ver `.env.example` para la lista completa. Las principales:

| Variable | Descripción |
|----------|-------------|
| `EVOLUTION_API_URL` | URL de la instancia de Evolution API |
| `EVOLUTION_API_KEY` | API key de Evolution |
| `EVOLUTION_INSTANCE_NAME` | Nombre de la instancia WhatsApp |
| `CRM_API_URL` | URL del CRM para crear oportunidades |
| `CRM_API_KEY` | API key del CRM |

## Configuración Evolution API

1. Crear instancia en Evolution API con nombre definido en `EVOLUTION_INSTANCE_NAME`
2. Configurar webhook apuntando a: `http://<backend-host>:3001/api/webhook/evolution`
3. Eventos requeridos: `messages.upsert`, `messages.update`

## Endpoints API

| Método | Ruta | Descripción |
|--------|------|-------------|
| POST | `/api/webhook/evolution` | Recibe eventos de Evolution API |
| POST | `/api/messages/send` | Envía mensaje de texto |
| POST | `/api/messages/send-media` | Envía archivo multimedia |
| GET | `/api/messages/contacts` | Lista conversaciones |
| GET | `/api/messages/history/:contactId` | Historial de mensajes |
| POST | `/api/crm/opportunity` | Crea oportunidad en CRM |
| GET | `/api/crm/inventory` | Consulta inventario de motos |
| GET | `/health` | Health check |
14 changes: 14 additions & 0 deletions chat-nlp-module/backend/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
FROM node:18-alpine

WORKDIR /app

COPY package*.json ./
RUN npm ci --only=production

COPY src/ ./src/

RUN mkdir -p logs

EXPOSE 3001

CMD ["node", "src/index.js"]
22 changes: 22 additions & 0 deletions chat-nlp-module/backend/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
{
"name": "chat-nlp-backend",
"version": "1.0.0",
"description": "Backend para interfaz de chat con NLP y Evolution API",
"main": "src/index.js",
"scripts": {
"start": "node src/index.js",
"dev": "nodemon src/index.js"
},
"dependencies": {
"axios": "^1.6.0",
"cors": "^2.8.5",
"dotenv": "^16.3.1",
"express": "^4.18.2",
"pg": "^8.11.3",
"socket.io": "^4.7.2",
"winston": "^3.11.0"
},
"devDependencies": {
"nodemon": "^3.0.2"
}
}
30 changes: 30 additions & 0 deletions chat-nlp-module/backend/src/config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
require('dotenv').config();

module.exports = {
port: process.env.PORT || 3001,

// Evolution API
evolutionApi: {
baseUrl: process.env.EVOLUTION_API_URL || 'http://localhost:8080',
apiKey: process.env.EVOLUTION_API_KEY || '',
instanceName: process.env.EVOLUTION_INSTANCE_NAME || 'asesores-chat',
},

// PostgreSQL / NocoDB
database: {
host: process.env.DB_HOST || 'localhost',
port: parseInt(process.env.DB_PORT, 10) || 5432,
name: process.env.DB_NAME || 'chat_nlp',
user: process.env.DB_USER || 'postgres',
password: process.env.DB_PASSWORD || '',
},

// CRM API
crm: {
baseUrl: process.env.CRM_API_URL || 'http://localhost:8081',
apiKey: process.env.CRM_API_KEY || '',
},

// Frontend origin for CORS
frontendOrigin: process.env.FRONTEND_ORIGIN || 'http://localhost:3000',
};
53 changes: 53 additions & 0 deletions chat-nlp-module/backend/src/index.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
const express = require('express');
const http = require('http');
const cors = require('cors');
const { Server } = require('socket.io');
const config = require('./config');
const logger = require('./utils/logger');
const webhookRoutes = require('./routes/webhook');
const messagesRoutes = require('./routes/messages');
const crmRoutes = require('./routes/crm');

const app = express();
const server = http.createServer(app);

const io = new Server(server, {
cors: { origin: config.frontendOrigin, methods: ['GET', 'POST'] },
});

// Middleware
app.use(cors({ origin: config.frontendOrigin }));
app.use(express.json());

// Expose io instance to routes
app.set('io', io);

// Routes
app.use('/api/webhook', webhookRoutes);
app.use('/api/messages', messagesRoutes);
app.use('/api/crm', crmRoutes);

// Health check
app.get('/health', (_req, res) => res.json({ status: 'ok' }));

// Socket.IO connection handling
io.on('connection', (socket) => {
logger.info(`Asesor conectado: ${socket.id}`);

socket.on('join_conversation', (contactId) => {
socket.join(`chat:${contactId}`);
logger.info(`Socket ${socket.id} unido a chat:${contactId}`);
});

socket.on('leave_conversation', (contactId) => {
socket.leave(`chat:${contactId}`);
});

socket.on('disconnect', () => {
logger.info(`Asesor desconectado: ${socket.id}`);
});
});

server.listen(config.port, () => {
logger.info(`Backend escuchando en puerto ${config.port}`);
});
62 changes: 62 additions & 0 deletions chat-nlp-module/backend/src/routes/crm.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
const express = require('express');
const router = express.Router();
const crmSync = require('../services/crmSync');
const logger = require('../utils/logger');

/**
* POST /api/crm/opportunity
* Crea una oportunidad en el CRM con los datos extraídos del chat
*/
router.post('/opportunity', async (req, res) => {
try {
const {
contactId,
clientName,
clientPhone,
clientEmail,
clientDocument,
profession,
motorcycleModel,
notes,
} = req.body;

if (!contactId || !clientName) {
return res
.status(400)
.json({ error: 'contactId y clientName son requeridos' });
}

const opportunity = await crmSync.createOpportunity({
contactId,
clientName,
clientPhone,
clientEmail,
clientDocument,
profession,
motorcycleModel,
notes,
});

res.json({ success: true, opportunity });
} catch (error) {
logger.error('Error creando oportunidad', { error: error.message });
res.status(500).json({ error: 'Error creando oportunidad en CRM' });
}
});

/**
* GET /api/crm/inventory
* Consulta el inventario de motocicletas desde el CRM/base de datos
*/
router.get('/inventory', async (req, res) => {
try {
const { brand, model } = req.query;
const inventory = await crmSync.getInventory({ brand, model });
res.json(inventory);
} catch (error) {
logger.error('Error consultando inventario', { error: error.message });
res.status(500).json({ error: 'Error consultando inventario' });
}
});

module.exports = router;
Loading