Commit inicial: Sales Bot - Sistema de Automatización de Ventas

- Stack completo con Mattermost, NocoDB y Sales Bot
- Procesamiento OCR de tickets con Tesseract
- Sistema de comisiones por tubos de tinte
- Comandos slash /metas y /ranking
- Documentación completa del proyecto

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-01-18 02:41:53 +00:00
commit 5d9cbd4812
21 changed files with 4625 additions and 0 deletions

View File

@@ -0,0 +1,201 @@
import requests
import logging
import os
logger = logging.getLogger(__name__)
class MattermostClient:
def __init__(self, url, token):
self.url = url.rstrip('/')
self.token = token
self.api_url = f"{self.url}/api/v4"
self.headers = {
'Authorization': f'Bearer {self.token}',
'Content-Type': 'application/json'
}
self.webhook_url = os.getenv('MATTERMOST_WEBHOOK_URL')
def test_connection(self):
"""Prueba la conexión con Mattermost"""
try:
response = requests.get(
f"{self.api_url}/users/me",
headers=self.headers,
timeout=10
)
response.raise_for_status()
user = response.json()
logger.info(f"Conexión exitosa con Mattermost. Bot: {user.get('username')}")
return {
'status': 'success',
'bot_username': user.get('username'),
'bot_id': user.get('id')
}
except Exception as e:
logger.error(f"Error conectando con Mattermost: {str(e)}")
return {'status': 'error', 'message': str(e)}
def get_channel_by_name(self, team_name, channel_name):
"""Obtiene información de un canal por nombre"""
try:
# Primero obtener el team
response = requests.get(
f"{self.api_url}/teams/name/{team_name}",
headers=self.headers,
timeout=10
)
response.raise_for_status()
team = response.json()
team_id = team['id']
# Luego obtener el canal
response = requests.get(
f"{self.api_url}/teams/{team_id}/channels/name/{channel_name}",
headers=self.headers,
timeout=10
)
response.raise_for_status()
return response.json()
except Exception as e:
logger.error(f"Error obteniendo canal {channel_name}: {str(e)}")
return None
def post_message(self, channel_id, message, props=None):
"""Publica un mensaje en un canal"""
try:
payload = {
'channel_id': channel_id,
'message': message
}
if props:
payload['props'] = props
response = requests.post(
f"{self.api_url}/posts",
headers=self.headers,
json=payload,
timeout=10
)
response.raise_for_status()
logger.info(f"Mensaje publicado en canal {channel_id}")
return response.json()
except Exception as e:
logger.error(f"Error publicando mensaje: {str(e)}")
return None
def post_message_webhook(self, message, username=None, icon_emoji=None):
"""Publica un mensaje usando webhook incoming"""
try:
payload = {'text': message}
if username:
payload['username'] = username
if icon_emoji:
payload['icon_emoji'] = icon_emoji
response = requests.post(
self.webhook_url,
json=payload,
timeout=10
)
response.raise_for_status()
logger.info("Mensaje publicado via webhook")
return True
except Exception as e:
logger.error(f"Error publicando via webhook: {str(e)}")
return False
def get_file(self, file_id):
"""Descarga un archivo de Mattermost"""
try:
response = requests.get(
f"{self.api_url}/files/{file_id}",
headers=self.headers,
timeout=30
)
response.raise_for_status()
return response.content
except Exception as e:
logger.error(f"Error descargando archivo {file_id}: {str(e)}")
return None
def get_file_info(self, file_id):
"""Obtiene información de un archivo"""
try:
response = requests.get(
f"{self.api_url}/files/{file_id}/info",
headers=self.headers,
timeout=10
)
response.raise_for_status()
return response.json()
except Exception as e:
logger.error(f"Error obteniendo info de archivo {file_id}: {str(e)}")
return None
def upload_file(self, channel_id, file_content, filename):
"""Sube un archivo a Mattermost"""
try:
files = {
'files': (filename, file_content)
}
data = {
'channel_id': channel_id
}
headers = {
'Authorization': f'Bearer {self.token}'
}
response = requests.post(
f"{self.api_url}/files",
headers=headers,
files=files,
data=data,
timeout=30
)
response.raise_for_status()
return response.json()
except Exception as e:
logger.error(f"Error subiendo archivo: {str(e)}")
return None
def add_reaction(self, post_id, emoji_name):
"""Agrega una reacción a un post"""
try:
# Obtener el user_id del bot
me = requests.get(
f"{self.api_url}/users/me",
headers=self.headers,
timeout=10
).json()
payload = {
'user_id': me['id'],
'post_id': post_id,
'emoji_name': emoji_name
}
response = requests.post(
f"{self.api_url}/reactions",
headers=self.headers,
json=payload,
timeout=10
)
response.raise_for_status()
return True
except Exception as e:
logger.error(f"Error agregando reacción: {str(e)}")
return False
def get_user_by_username(self, username):
"""Obtiene información de un usuario por username"""
try:
response = requests.get(
f"{self.api_url}/users/username/{username}",
headers=self.headers,
timeout=10
)
response.raise_for_status()
return response.json()
except Exception as e:
logger.error(f"Error obteniendo usuario {username}: {str(e)}")
return None