feat: Implementar mejoras de funcionalidad y UX del Sales Bot

Nuevas funcionalidades:
- /cancelar: Cancelar ventas propias con motivo opcional
- /deshacer: Deshacer última venta (dentro de 5 minutos)
- /editar: Editar monto y cliente de ventas propias
- /comisiones: Historial de comisiones de últimos 6 meses
- /racha: Sistema de bonos por días consecutivos cumpliendo meta
- /exportar: Exportar ventas a Excel o CSV

Sistema de confirmación obligatoria:
- Todas las ventas requieren confirmación explícita (si/no)
- Preview de venta antes de registrar
- Timeout de 2 minutos para ventas pendientes

Scheduler de notificaciones:
- Recordatorio de mediodía para vendedores sin meta
- Resumen diario automático al final del día
- Resumen semanal los lunes

Otras mejoras:
- Soporte para múltiples imágenes en una venta
- Autocompletado de clientes frecuentes
- Metas personalizadas por vendedor
- Bonos por racha: $20 (3 días), $50 (5 días), $150 (10 días)

Archivos nuevos:
- export_utils.py: Generación de Excel y CSV
- scheduler.py: Tareas programadas con APScheduler

Dependencias nuevas:
- APScheduler==3.10.4
- openpyxl==3.1.2

Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
2026-01-19 02:57:27 +00:00
parent 5d9cbd4812
commit ed1658eb2b
8 changed files with 1808 additions and 148 deletions

View File

@@ -1,129 +1,284 @@
"""
Handlers para procesamiento de ventas en Sales Bot
Incluye sistema de confirmación interactiva obligatoria
"""
import logging
import re
from datetime import datetime
import os
from datetime import datetime, timedelta, timezone
from utils import extraer_monto, extraer_cliente, formatear_moneda, extraer_tubos
from ocr_processor import OCRProcessor
import os
logger = logging.getLogger(__name__)
# Zona horaria de México
TZ_MEXICO = timezone(timedelta(hours=-6))
# Cache de ventas pendientes de confirmación
# Formato: {username: {datos_venta, timestamp}}
VENTAS_PENDIENTES = {}
# Timeout para confirmación (en minutos)
CONFIRMACION_TIMEOUT = int(os.getenv('CONFIRMACION_TIMEOUT_MINUTOS', 2))
def limpiar_ventas_expiradas():
"""Elimina ventas pendientes que han expirado"""
ahora = datetime.now(TZ_MEXICO)
expiradas = []
for username, datos in VENTAS_PENDIENTES.items():
timestamp = datos.get('timestamp')
if timestamp:
if ahora - timestamp > timedelta(minutes=CONFIRMACION_TIMEOUT):
expiradas.append(username)
for username in expiradas:
del VENTAS_PENDIENTES[username]
logger.info(f"Venta pendiente de {username} expirada")
def handle_venta_message(data, mattermost, nocodb):
"""
Maneja mensajes de venta en Mattermost
NUEVO: Sistema de comisiones por tubos vendidos
Maneja mensajes de venta en Mattermost con confirmación interactiva obligatoria.
Flujo:
1. Usuario envía mensaje de venta
2. Bot muestra preview y pide confirmación
3. Usuario responde "si" o "no"
4. Bot registra la venta (si confirmada)
"""
try:
user_name = data.get('user_name')
text = data.get('text', '').strip()
text_lower = text.lower()
# Limpiar ventas expiradas
limpiar_ventas_expiradas()
# ==================== PROCESAR CONFIRMACIÓN ====================
# Si es confirmación de venta pendiente
if text_lower in ['si', '', 'confirmar', 'ok', 'yes'] and user_name in VENTAS_PENDIENTES:
venta_data = VENTAS_PENDIENTES.pop(user_name)
return registrar_venta_confirmada(venta_data, mattermost, nocodb)
# Si es cancelación de venta pendiente
if text_lower in ['no', 'cancelar', 'cancel'] and user_name in VENTAS_PENDIENTES:
VENTAS_PENDIENTES.pop(user_name)
mensaje = '❌ **Venta cancelada**\n\nPuedes registrar una nueva venta cuando quieras.'
mattermost.post_message_webhook(mensaje, username='Sales Bot', icon_emoji=':x:')
return {'text': mensaje}
# Si ya tiene venta pendiente, recordar
if user_name in VENTAS_PENDIENTES:
venta_pendiente = VENTAS_PENDIENTES[user_name]
tiempo_restante = CONFIRMACION_TIMEOUT - (datetime.now(TZ_MEXICO) - venta_pendiente['timestamp']).seconds // 60
mensaje = (
f"⏳ **Ya tienes una venta pendiente**\n\n"
f"**Monto:** ${venta_pendiente.get('monto', 0):,.2f}\n"
f"**Cliente:** {venta_pendiente.get('cliente', 'Sin especificar')}\n\n"
f"Responde **si** para confirmar o **no** para cancelar.\n"
f"_Expira en {tiempo_restante} minuto(s)_"
)
mattermost.post_message_webhook(mensaje, username='Sales Bot', icon_emoji=':hourglass:')
return {'text': mensaje}
# ==================== PROCESAR NUEVA VENTA ====================
channel_name = data.get('channel_name')
post_id = data.get('post_id')
file_ids = data.get('file_ids', '')
if file_ids and isinstance(file_ids, str):
file_ids = [f.strip() for f in file_ids.split(',') if f.strip()]
elif not file_ids:
file_ids = []
logger.info(f"Procesando venta de {user_name}: {text}, archivos: {file_ids}")
# Extraer información del texto
monto = extraer_monto(text)
cliente = extraer_cliente(text)
tubos_manual = extraer_tubos(text) # NUEVO: tubos manuales
# Procesar imágenes adjuntas
imagen_url = None
tubos_manual = extraer_tubos(text)
# ==================== PROCESAR IMÁGENES (MÚLTIPLES) ====================
imagenes_url = []
ocr_info = ""
productos_ocr = []
monto_ocr_total = 0
tubos_ocr_total = 0
if file_ids:
logger.info(f"Procesando {len(file_ids)} archivos adjuntos")
file_id = file_ids[0]
imagen_data = mattermost.get_file(file_id)
file_info = mattermost.get_file_info(file_id)
if file_info and imagen_data:
filename = file_info.get('name', 'ticket.jpg')
file_size = file_info.get('size', 0)
bot_token = os.getenv('MATTERMOST_BOT_TOKEN')
mattermost_url = os.getenv('MATTERMOST_URL')
imagen_url = f"{mattermost_url}/api/v4/files/{file_id}?access_token={bot_token}"
logger.info(f"Archivo adjunto: {filename}, tamaño: {file_size} bytes")
# Procesar con OCR
for file_id in file_ids:
try:
ocr = OCRProcessor()
resultado_ocr = ocr.procesar_ticket(imagen_data)
if resultado_ocr:
monto_ocr = resultado_ocr.get('monto_detectado')
fecha_ocr = resultado_ocr.get('fecha_detectada')
productos_ocr = resultado_ocr.get('productos', [])
if not monto and monto_ocr:
monto = monto_ocr
logger.info(f"Usando monto detectado por OCR: ${monto}")
ocr_info += f"\n💡 Monto detectado: ${monto:,.2f}"
elif monto and monto_ocr:
es_valido, mensaje = ocr.validar_monto_con_ocr(monto, monto_ocr, tolerancia=0.05)
ocr_info += f"\n{mensaje}"
if not es_valido:
logger.warning(mensaje)
if fecha_ocr:
ocr_info += f"\n📅 Fecha: {fecha_ocr}"
if productos_ocr:
# NUEVO: Contar tubos de tinte
tubos_tinte = sum(
p['cantidad'] for p in productos_ocr
if 'tinte' in p['marca'].lower() or 'tinte' in p['producto'].lower()
or 'cromatique' in p['marca'].lower()
)
ocr_info += f"\n🧪 Tubos de tinte: {tubos_tinte}"
ocr_info += f"\n📦 Total productos: {len(productos_ocr)}"
logger.info(f"Tubos de tinte detectados: {tubos_tinte}")
except Exception as ocr_error:
logger.error(f"Error en OCR: {str(ocr_error)}")
ocr_info = "\n⚠️ No se pudo leer el ticket"
productos_ocr = []
logger.info(f"URL de imagen: {imagen_url}")
imagen_data = mattermost.get_file(file_id)
file_info = mattermost.get_file_info(file_id)
if file_info and imagen_data:
filename = file_info.get('name', 'ticket.jpg')
file_size = file_info.get('size', 0)
bot_token = os.getenv('MATTERMOST_BOT_TOKEN')
mattermost_url = os.getenv('MATTERMOST_URL')
imagen_url = f"{mattermost_url}/api/v4/files/{file_id}?access_token={bot_token}"
imagenes_url.append(imagen_url)
logger.info(f"Archivo adjunto: {filename}, tamaño: {file_size} bytes")
# Procesar con OCR
try:
ocr = OCRProcessor()
resultado_ocr = ocr.procesar_ticket(imagen_data)
if resultado_ocr:
monto_ocr = resultado_ocr.get('monto_detectado', 0)
fecha_ocr = resultado_ocr.get('fecha_detectada')
productos = resultado_ocr.get('productos', [])
if monto_ocr:
monto_ocr_total += monto_ocr
if productos:
productos_ocr.extend(productos)
# Contar tubos de tinte
for p in productos:
marca = p.get('marca', '').lower()
producto = p.get('producto', '').lower()
if 'tinte' in marca or 'tinte' in producto or 'cromatique' in marca:
tubos_ocr_total += p.get('cantidad', 0)
if fecha_ocr and not ocr_info:
ocr_info += f"\n📅 Fecha ticket: {fecha_ocr}"
except Exception as ocr_error:
logger.error(f"Error en OCR para {file_id}: {str(ocr_error)}")
except Exception as file_error:
logger.error(f"Error procesando archivo {file_id}: {str(file_error)}")
# Resumen de OCR
if len(file_ids) > 1:
ocr_info += f"\n📷 Imágenes procesadas: {len(imagenes_url)}"
if monto_ocr_total > 0:
ocr_info += f"\n💡 Monto OCR: ${monto_ocr_total:,.2f}"
if not monto:
monto = monto_ocr_total
if tubos_ocr_total > 0:
ocr_info += f"\n🧪 Tubos detectados: {tubos_ocr_total}"
if productos_ocr:
ocr_info += f"\n📦 Productos: {len(productos_ocr)}"
# ==================== VALIDAR DATOS ====================
if not monto:
# Sugerir clientes frecuentes si no hay cliente
sugerencias = ""
if not cliente:
clientes_frecuentes = nocodb.get_clientes_frecuentes(user_name, limit=3)
if clientes_frecuentes:
lista = '\n'.join([f"{c}" for c in clientes_frecuentes])
sugerencias = f"\n\n💡 **Clientes frecuentes:**\n{lista}"
mensaje = (
f"@{user_name} Necesito el monto de la venta.\n"
f"@{user_name} Necesito el monto de la venta.\n\n"
"**Formatos válidos:**\n"
"• `venta @monto 1500 @cliente Juan Pérez`\n"
"• `vendí $1500 a Juan Pérez`\n"
"• `venta @monto 1500 @tubos 3`\n"
"• Adjunta foto del ticket"
f"{sugerencias}"
)
mattermost.post_message_webhook(mensaje, username='Sales Bot', icon_emoji=':moneybag:')
return {'text': mensaje}
if not cliente:
cliente = "Cliente sin nombre"
# Usar tubos manuales o de OCR
tubos_detectados = tubos_manual or tubos_ocr_total or 0
# ==================== CREAR PREVIEW Y PEDIR CONFIRMACIÓN ====================
# Guardar datos de la venta pendiente
VENTAS_PENDIENTES[user_name] = {
'user_name': user_name,
'monto': monto,
'cliente': cliente,
'tubos': tubos_detectados,
'tubos_manual': tubos_manual,
'productos_ocr': productos_ocr,
'imagenes_url': imagenes_url,
'ocr_info': ocr_info,
'channel_name': channel_name,
'post_id': post_id,
'text': text,
'timestamp': datetime.now(TZ_MEXICO)
}
# Construir mensaje de preview
mensaje_preview = f"📋 **Preview de Venta**\n\n"
mensaje_preview += f"**Vendedor:** @{user_name}\n"
mensaje_preview += f"**Monto:** ${monto:,.2f}\n"
mensaje_preview += f"**Cliente:** {cliente}\n"
if tubos_detectados > 0:
mensaje_preview += f"**Tubos:** {tubos_detectados} 🧪\n"
if imagenes_url:
mensaje_preview += f"**Tickets:** {len(imagenes_url)} imagen(es) 📸\n"
if ocr_info:
mensaje_preview += f"\n**Datos detectados:**{ocr_info}\n"
mensaje_preview += f"\n¿Confirmar esta venta? Responde **si** o **no**\n"
mensaje_preview += f"_(Expira en {CONFIRMACION_TIMEOUT} minutos)_"
mattermost.post_message_webhook(mensaje_preview, username='Sales Bot', icon_emoji=':clipboard:')
return {'text': mensaje_preview}
except Exception as e:
logger.error(f"Error en handle_venta_message: {str(e)}", exc_info=True)
mensaje_error = f"❌ Error: {str(e)}"
return {'text': mensaje_error}
def registrar_venta_confirmada(venta_data, mattermost, nocodb):
"""
Registra una venta después de ser confirmada por el usuario.
"""
try:
user_name = venta_data.get('user_name')
monto = venta_data.get('monto')
cliente = venta_data.get('cliente')
tubos_manual = venta_data.get('tubos_manual')
productos_ocr = venta_data.get('productos_ocr', [])
imagenes_url = venta_data.get('imagenes_url', [])
ocr_info = venta_data.get('ocr_info', '')
channel_name = venta_data.get('channel_name')
post_id = venta_data.get('post_id')
text = venta_data.get('text', '')
# Verificar/crear vendedor
vendedor = nocodb.get_vendedor(user_name)
if not vendedor:
user_info = mattermost.get_user_by_username(user_name)
email = user_info.get('email', f"{user_name}@consultoria-as.com") if user_info else f"{user_name}@consultoria-as.com"
nombre = user_info.get('first_name', user_name) if user_info else user_name
vendedor = nocodb.crear_vendedor(
username=user_name,
nombre_completo=nombre,
email=email,
meta_diaria_tubos=3 # NUEVO: Meta de 3 tubos diarios
meta_diaria_tubos=3
)
if vendedor:
mensaje_bienvenida = (
f"👋 ¡Bienvenido @{user_name}!\n"
@@ -133,7 +288,10 @@ def handle_venta_message(data, mattermost, nocodb):
f"¡Empieza a registrar tus ventas!"
)
mattermost.post_message_webhook(mensaje_bienvenida, username='Sales Bot', icon_emoji=':wave:')
# Usar primera imagen como principal
imagen_url = imagenes_url[0] if imagenes_url else None
# Registrar venta
venta = nocodb.registrar_venta(
vendedor_username=user_name,
@@ -144,17 +302,17 @@ def handle_venta_message(data, mattermost, nocodb):
canal=channel_name,
imagen_url=imagen_url
)
if venta:
venta_id = venta.get('Id')
# Guardar productos detectados por OCR
if productos_ocr:
productos_guardados = nocodb.guardar_productos_venta(venta_id, productos_ocr)
if productos_guardados:
logger.info(f"Guardados {len(productos_guardados)} productos para venta {venta_id}")
# NUEVO: Guardar tubos manuales si se especificaron
# Guardar tubos manuales si se especificaron
elif tubos_manual and tubos_manual > 0:
productos_manuales = [{
'producto': 'Tinte (registro manual)',
@@ -163,42 +321,44 @@ def handle_venta_message(data, mattermost, nocodb):
'precio_unitario': monto / tubos_manual if tubos_manual > 0 else 0,
'importe': monto
}]
productos_guardados = nocodb.guardar_productos_venta(venta_id, productos_manuales)
if productos_guardados:
logger.info(f"Guardados {tubos_manual} tubos manuales para venta {venta_id}")
nocodb.guardar_productos_venta(venta_id, productos_manuales)
logger.info(f"Guardados {tubos_manual} tubos manuales para venta {venta_id}")
# NUEVO: Actualizar tabla de metas
# Actualizar tabla de metas
try:
nocodb.actualizar_meta_vendedor(user_name)
logger.info(f"Metas actualizadas para {user_name}")
except Exception as meta_error:
logger.error(f"Error actualizando metas: {str(meta_error)}")
# Verificar racha
racha = nocodb.verificar_racha(user_name)
# Reacción de éxito
if post_id:
mattermost.add_reaction(post_id, 'white_check_mark')
# NUEVO: Obtener estadísticas del día
# Obtener estadísticas del día
stats_dia = nocodb.get_estadisticas_vendedor_dia(user_name)
# Construir mensaje
# Construir mensaje de confirmación
mensaje_confirmacion = (
f"✅ **Venta registrada**\n\n"
f"✅ **Venta #{venta_id} registrada**\n\n"
f"**Vendedor:** @{user_name}\n"
f"**Monto:** {formatear_moneda(monto)}\n"
f"**Cliente:** {cliente}\n"
)
if imagen_url:
mensaje_confirmacion += f"📸 **Ticket:** Guardado{ocr_info}\n"
# NUEVO: Mostrar estadísticas de tubos y comisiones
if imagenes_url:
mensaje_confirmacion += f"📸 **Tickets:** {len(imagenes_url)} guardado(s){ocr_info}\n"
# Mostrar estadísticas de tubos y comisiones
if stats_dia:
tubos_hoy = stats_dia.get('tubos_vendidos', 0)
comision_hoy = stats_dia.get('comision', 0)
meta = stats_dia.get('meta_diaria', 3)
tubos_comisionables = stats_dia.get('tubos_comisionables', 0)
# Determinar emoji según progreso
if tubos_hoy >= meta * 2:
emoji = '🔥'
@@ -212,13 +372,13 @@ def handle_venta_message(data, mattermost, nocodb):
else:
emoji = '📊'
mensaje_extra = '¡Sigue así!'
mensaje_confirmacion += (
f"\n**Resumen del día:** {emoji}\n"
f"• Tubos vendidos hoy: {tubos_hoy} 🧪\n"
f"• Meta diaria: {meta} tubos\n"
)
if tubos_hoy > meta:
mensaje_confirmacion += (
f"• Tubos con comisión: {tubos_comisionables}\n"
@@ -227,41 +387,46 @@ def handle_venta_message(data, mattermost, nocodb):
else:
faltan = meta - tubos_hoy
mensaje_confirmacion += f"• Faltan {faltan} tubos para comisión\n"
mensaje_confirmacion += f"{mensaje_extra}"
# Mostrar info de racha si aplica
if racha and racha.get('dias_consecutivos', 0) >= 3:
dias = racha['dias_consecutivos']
bonus = racha.get('bonus', 0)
mensaje_confirmacion += f"\n\n🔥 **Racha: {dias} días** "
if bonus > 0:
mensaje_confirmacion += f"(+${bonus:,.2f} bonus)"
# Enviar confirmación
mattermost.post_message_webhook(
mensaje_confirmacion,
username='Sales Bot',
icon_emoji=':moneybag:'
)
return {'text': mensaje_confirmacion}
else:
mensaje_error = f"❌ Error al registrar la venta. Intenta de nuevo."
mensaje_error = "❌ Error al registrar la venta. Intenta de nuevo."
mattermost.post_message_webhook(mensaje_error, username='Sales Bot', icon_emoji=':x:')
return {'text': mensaje_error}
except Exception as e:
logger.error(f"Error en handle_venta_message: {str(e)}", exc_info=True)
logger.error(f"Error en registrar_venta_confirmada: {str(e)}", exc_info=True)
mensaje_error = f"❌ Error: {str(e)}"
return {'text': mensaje_error}
def generar_reporte_diario(mattermost, nocodb):
"""
Genera reporte diario de ventas y comisiones
NUEVO: Muestra tubos vendidos y comisiones ganadas
"""
try:
import os
hoy = datetime.now().strftime('%Y-%m-%d')
mes_actual = datetime.now().strftime('%Y-%m')
hoy = datetime.now(TZ_MEXICO).strftime('%Y-%m-%d')
# Obtener todas las ventas del día
ventas_hoy = nocodb.get_ventas_dia()
# Agrupar por vendedor
vendedores_hoy = {}
for venta in ventas_hoy:
@@ -269,64 +434,70 @@ def generar_reporte_diario(mattermost, nocodb):
if vendedor not in vendedores_hoy:
vendedores_hoy[vendedor] = []
vendedores_hoy[vendedor].append(venta)
# Calcular estadísticas por vendedor
stats_vendedores = []
for vendedor in vendedores_hoy.keys():
stats = nocodb.get_estadisticas_vendedor_dia(vendedor, hoy)
if stats:
stats_vendedores.append(stats)
# Ordenar por tubos vendidos
stats_vendedores.sort(key=lambda x: x.get('tubos_vendidos', 0), reverse=True)
# Calcular totales
total_tubos_dia = sum(s.get('tubos_vendidos', 0) for s in stats_vendedores)
total_comisiones = sum(s.get('comision', 0) for s in stats_vendedores)
total_monto = sum(s.get('monto_total_dia', 0) for s in stats_vendedores)
# Construir mensaje
mensaje = (
f"📊 **Reporte Diario - {datetime.now().strftime('%d/%m/%Y')}**\n\n"
f"📊 **Reporte Diario - {datetime.now(TZ_MEXICO).strftime('%d/%m/%Y')}**\n\n"
f"**Resumen del día:**\n"
f"• Tubos vendidos: {total_tubos_dia} 🧪\n"
f"• Comisiones pagadas: {formatear_moneda(total_comisiones)} 💰\n"
f"• Monto total: {formatear_moneda(total_monto)}\n"
f"• Ventas: {len(ventas_hoy)}\n\n"
)
if stats_vendedores:
mensaje += "**Top Vendedores del Día:**\n"
for i, stats in enumerate(stats_vendedores[:5], 1):
vendedor = stats.get('vendedor')
tubos = stats.get('tubos_vendidos', 0)
comision = stats.get('comision', 0)
emoji = '🥇' if i == 1 else '🥈' if i == 2 else '🥉' if i == 3 else '🏅'
if comision > 0:
mensaje += f"{emoji} @{vendedor} - {tubos} tubos ({formatear_moneda(comision)} comisión)\n"
else:
mensaje += f"{emoji} @{vendedor} - {tubos} tubos\n"
# Obtener canal de reportes
team_name = os.getenv('MATTERMOST_TEAM_NAME')
channel_reportes = os.getenv('MATTERMOST_CHANNEL_REPORTES')
canal = mattermost.get_channel_by_name(team_name, channel_reportes)
if canal:
mattermost.post_message(canal['id'], mensaje)
logger.info("Reporte diario generado")
return {'status': 'success', 'message': 'Reporte generado'}
if channel_reportes:
canal = mattermost.get_channel_by_name(team_name, channel_reportes)
if canal:
mattermost.post_message(canal['id'], mensaje)
logger.info("Reporte diario generado")
return {'status': 'success', 'message': 'Reporte generado'}
else:
logger.warning(f"Canal {channel_reportes} no encontrado")
return {'status': 'error', 'message': 'Canal no encontrado'}
else:
logger.warning(f"Canal {channel_reportes} no encontrado")
return {'status': 'error', 'message': 'Canal no encontrado'}
# Enviar por webhook si no hay canal específico
mattermost.post_message_webhook(mensaje, username='Sales Bot', icon_emoji=':chart_with_upwards_trend:')
return {'status': 'success', 'message': 'Reporte enviado por webhook'}
except Exception as e:
logger.error(f"Error generando reporte diario: {str(e)}", exc_info=True)
return {'status': 'error', 'message': str(e)}
def comando_estadisticas(user_name, mattermost, nocodb):
"""
Muestra estadísticas personales del vendedor
@@ -335,26 +506,26 @@ def comando_estadisticas(user_name, mattermost, nocodb):
try:
# Estadísticas del día
stats_hoy = nocodb.get_estadisticas_vendedor_dia(user_name)
# Estadísticas del mes
stats_mes = nocodb.get_estadisticas_vendedor_mes(user_name)
if not stats_hoy and not stats_mes:
mensaje = f"@{user_name} Aún no tienes ventas registradas."
return mensaje
mensaje = f"📈 **Estadísticas de @{user_name}**\n\n"
# Hoy
if stats_hoy:
mensaje += (
f"**Hoy ({datetime.now().strftime('%d/%m')})**\n"
f"**Hoy ({datetime.now(TZ_MEXICO).strftime('%d/%m')})**\n"
f"• Tubos: {stats_hoy.get('tubos_vendidos', 0)} 🧪\n"
f"• Comisión: {formatear_moneda(stats_hoy.get('comision', 0))}\n"
f"• Monto: {formatear_moneda(stats_hoy.get('monto_total_dia', 0))}\n"
f"• Ventas: {stats_hoy.get('cantidad_ventas', 0)}\n\n"
)
# Mes
if stats_mes:
mensaje += (
@@ -367,10 +538,10 @@ def comando_estadisticas(user_name, mattermost, nocodb):
f"• Días con meta: {stats_mes.get('dias_meta_cumplida', 0)}\n"
f"• Promedio/día: {stats_mes.get('promedio_tubos_dia', 0):.1f} tubos\n"
)
mattermost.post_message_webhook(mensaje, username='Sales Bot', icon_emoji=':bar_chart:')
return mensaje
except Exception as e:
logger.error(f"Error en comando_estadisticas: {str(e)}", exc_info=True)
return f"❌ Error obteniendo estadísticas"
return "❌ Error obteniendo estadísticas"