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:
166
sales-bot/utils.py
Normal file
166
sales-bot/utils.py
Normal file
@@ -0,0 +1,166 @@
|
||||
"""
|
||||
Utilidades para el Sales Bot
|
||||
"""
|
||||
import re
|
||||
import os
|
||||
import logging
|
||||
|
||||
logger = logging.getLogger(__name__)
|
||||
|
||||
|
||||
def validar_token_outgoing(token):
|
||||
"""
|
||||
Valida el token de webhooks salientes de Mattermost
|
||||
"""
|
||||
if not token:
|
||||
return False
|
||||
|
||||
expected_tokens = [
|
||||
os.getenv('MATTERMOST_WEBHOOK_SECRET'),
|
||||
os.getenv('MATTERMOST_OUTGOING_TOKEN'),
|
||||
]
|
||||
|
||||
return token in [t for t in expected_tokens if t]
|
||||
|
||||
|
||||
def extraer_monto(texto):
|
||||
"""
|
||||
Extrae el monto de una venta del texto del mensaje.
|
||||
Soporta formatos:
|
||||
- @monto 1500
|
||||
- $1,500.00
|
||||
- $1500
|
||||
- 1500 pesos
|
||||
"""
|
||||
if not texto:
|
||||
return None
|
||||
|
||||
texto = texto.lower()
|
||||
|
||||
# Buscar formato @monto XXXX
|
||||
patron_monto = r'@monto\s+\$?([\d,]+\.?\d*)'
|
||||
match = re.search(patron_monto, texto)
|
||||
if match:
|
||||
monto_str = match.group(1).replace(',', '')
|
||||
try:
|
||||
return float(monto_str)
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
# Buscar formato $X,XXX.XX o $XXXX
|
||||
patron_dinero = r'\$\s*([\d,]+\.?\d*)'
|
||||
match = re.search(patron_dinero, texto)
|
||||
if match:
|
||||
monto_str = match.group(1).replace(',', '')
|
||||
try:
|
||||
return float(monto_str)
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
# Buscar formato XXXX pesos
|
||||
patron_pesos = r'([\d,]+\.?\d*)\s*pesos'
|
||||
match = re.search(patron_pesos, texto)
|
||||
if match:
|
||||
monto_str = match.group(1).replace(',', '')
|
||||
try:
|
||||
return float(monto_str)
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def extraer_cliente(texto):
|
||||
"""
|
||||
Extrae el nombre del cliente del texto del mensaje.
|
||||
Soporta formatos:
|
||||
- @cliente Juan Pérez
|
||||
- cliente: Juan Pérez
|
||||
- a Juan Pérez
|
||||
"""
|
||||
if not texto:
|
||||
return None
|
||||
|
||||
# Buscar formato @cliente NOMBRE
|
||||
patron_cliente = r'@cliente\s+([^\n@$]+)'
|
||||
match = re.search(patron_cliente, texto, re.IGNORECASE)
|
||||
if match:
|
||||
cliente = match.group(1).strip()
|
||||
# Limpiar palabras clave que no son parte del nombre
|
||||
cliente = re.sub(r'\s*@monto.*$', '', cliente, flags=re.IGNORECASE)
|
||||
return cliente.strip() if cliente.strip() else None
|
||||
|
||||
# Buscar formato "cliente: NOMBRE" o "cliente NOMBRE"
|
||||
patron_cliente2 = r'cliente[:\s]+([^\n@$]+)'
|
||||
match = re.search(patron_cliente2, texto, re.IGNORECASE)
|
||||
if match:
|
||||
cliente = match.group(1).strip()
|
||||
cliente = re.sub(r'\s*@monto.*$', '', cliente, flags=re.IGNORECASE)
|
||||
return cliente.strip() if cliente.strip() else None
|
||||
|
||||
# Buscar formato "a NOMBRE" (después de un monto)
|
||||
patron_a = r'\$[\d,\.]+\s+a\s+([^\n@$]+)'
|
||||
match = re.search(patron_a, texto, re.IGNORECASE)
|
||||
if match:
|
||||
cliente = match.group(1).strip()
|
||||
return cliente.strip() if cliente.strip() else None
|
||||
|
||||
return None
|
||||
|
||||
|
||||
def formatear_moneda(valor, simbolo='$', decimales=2):
|
||||
"""
|
||||
Formatea un número como moneda.
|
||||
Ejemplo: 1500.5 -> $1,500.50
|
||||
"""
|
||||
if valor is None:
|
||||
return f"{simbolo}0.00"
|
||||
|
||||
try:
|
||||
valor = float(valor)
|
||||
return f"{simbolo}{valor:,.{decimales}f}"
|
||||
except (ValueError, TypeError):
|
||||
return f"{simbolo}0.00"
|
||||
|
||||
|
||||
def extraer_tubos(texto):
|
||||
"""
|
||||
Extrae la cantidad de tubos del texto del mensaje.
|
||||
Soporta formatos:
|
||||
- @tubos 5
|
||||
- tubos: 5
|
||||
- 5 tubos
|
||||
"""
|
||||
if not texto:
|
||||
return None
|
||||
|
||||
texto = texto.lower()
|
||||
|
||||
# Buscar formato @tubos XXXX
|
||||
patron_tubos = r'@tubos\s+(\d+)'
|
||||
match = re.search(patron_tubos, texto)
|
||||
if match:
|
||||
try:
|
||||
return int(match.group(1))
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
# Buscar formato "tubos: X" o "tubos X"
|
||||
patron_tubos2 = r'tubos[:\s]+(\d+)'
|
||||
match = re.search(patron_tubos2, texto)
|
||||
if match:
|
||||
try:
|
||||
return int(match.group(1))
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
# Buscar formato "X tubos"
|
||||
patron_tubos3 = r'(\d+)\s*tubos?'
|
||||
match = re.search(patron_tubos3, texto)
|
||||
if match:
|
||||
try:
|
||||
return int(match.group(1))
|
||||
except ValueError:
|
||||
pass
|
||||
|
||||
return None
|
||||
Reference in New Issue
Block a user