mirror of
https://github.com/marcogll/telegram_new_socias.git
synced 2026-01-13 13:15:16 +00:00
refactor: Remove printing feature, enhance onboarding derivations, and update bot commands.
This commit is contained in:
4
main.py
4
main.py
@@ -27,7 +27,7 @@ async def menu_principal(update: Update, context: ContextTypes.DEFAULT_TYPE):
|
|||||||
texto = (
|
texto = (
|
||||||
"👩💼 **Hola, soy Vanessa. ¿En qué puedo ayudarte hoy?**\n\n"
|
"👩💼 **Hola, soy Vanessa. ¿En qué puedo ayudarte hoy?**\n\n"
|
||||||
"Comandos rápidos:\n"
|
"Comandos rápidos:\n"
|
||||||
"/welcome — Onboarding\n"
|
"/welcome — Registro de nuevas empleadas\n"
|
||||||
"/vacaciones — Solicitud de vacaciones\n"
|
"/vacaciones — Solicitud de vacaciones\n"
|
||||||
"/permiso — Solicitud de permiso por horas\n\n"
|
"/permiso — Solicitud de permiso por horas\n\n"
|
||||||
"También tienes los botones rápidos abajo 👇"
|
"También tienes los botones rápidos abajo 👇"
|
||||||
@@ -42,7 +42,7 @@ async def post_init(application: Application):
|
|||||||
# Mantén los comandos rápidos disponibles en el menú de Telegram
|
# Mantén los comandos rápidos disponibles en el menú de Telegram
|
||||||
await application.bot.set_my_commands([
|
await application.bot.set_my_commands([
|
||||||
BotCommand("start", "Mostrar menú principal"),
|
BotCommand("start", "Mostrar menú principal"),
|
||||||
BotCommand("welcome", "Iniciar onboarding"),
|
BotCommand("welcome", "Registro de nuevas empleadas"),
|
||||||
BotCommand("vacaciones", "Solicitar vacaciones"),
|
BotCommand("vacaciones", "Solicitar vacaciones"),
|
||||||
BotCommand("permiso", "Solicitar permiso por horas"),
|
BotCommand("permiso", "Solicitar permiso por horas"),
|
||||||
BotCommand("cancelar", "Cancelar flujo actual"),
|
BotCommand("cancelar", "Cancelar flujo actual"),
|
||||||
|
|||||||
@@ -25,16 +25,17 @@ def _send_webhooks(urls: list, payload: dict):
|
|||||||
|
|
||||||
# Estados de conversación
|
# Estados de conversación
|
||||||
(
|
(
|
||||||
VAC_ANIO,
|
|
||||||
INICIO_DIA,
|
INICIO_DIA,
|
||||||
INICIO_MES,
|
INICIO_MES,
|
||||||
|
INICIO_ANIO,
|
||||||
FIN_DIA,
|
FIN_DIA,
|
||||||
FIN_MES,
|
FIN_MES,
|
||||||
|
FIN_ANIO,
|
||||||
PERMISO_CUANDO,
|
PERMISO_CUANDO,
|
||||||
PERMISO_ANIO,
|
PERMISO_ANIO,
|
||||||
HORARIO,
|
HORARIO,
|
||||||
MOTIVO,
|
MOTIVO,
|
||||||
) = range(9)
|
) = range(10)
|
||||||
|
|
||||||
# Teclados de apoyo
|
# Teclados de apoyo
|
||||||
MESES = [
|
MESES = [
|
||||||
@@ -71,12 +72,12 @@ def _parse_anio(texto: str) -> int:
|
|||||||
|
|
||||||
def _build_dates(datos: dict) -> dict:
|
def _build_dates(datos: dict) -> dict:
|
||||||
"""Construye fechas ISO; si fin < inicio, se ajusta a inicio."""
|
"""Construye fechas ISO; si fin < inicio, se ajusta a inicio."""
|
||||||
year = datos.get("anio") or datetime.now().year
|
|
||||||
try:
|
try:
|
||||||
inicio = date(year, datos["inicio_mes"], datos["inicio_dia"])
|
inicio = date(datos.get("inicio_anio", ANIO_ACTUAL), datos["inicio_mes"], datos["inicio_dia"])
|
||||||
fin_dia = datos.get("fin_dia", datos.get("inicio_dia"))
|
fin_dia = datos.get("fin_dia", datos.get("inicio_dia"))
|
||||||
fin_mes = datos.get("fin_mes", datos.get("inicio_mes"))
|
fin_mes = datos.get("fin_mes", datos.get("inicio_mes"))
|
||||||
fin = date(year, fin_mes, fin_dia)
|
fin_anio = datos.get("fin_anio", datos.get("inicio_anio", inicio.year))
|
||||||
|
fin = date(fin_anio, fin_mes, fin_dia)
|
||||||
if fin < inicio:
|
if fin < inicio:
|
||||||
fin = inicio
|
fin = inicio
|
||||||
return {"inicio": inicio, "fin": fin}
|
return {"inicio": inicio, "fin": fin}
|
||||||
@@ -111,9 +112,8 @@ async def start_vacaciones(update: Update, context: ContextTypes.DEFAULT_TYPE) -
|
|||||||
log_request(user.id, user.username, "vacaciones", update.message.text)
|
log_request(user.id, user.username, "vacaciones", update.message.text)
|
||||||
context.user_data.clear()
|
context.user_data.clear()
|
||||||
context.user_data['tipo'] = 'VACACIONES'
|
context.user_data['tipo'] = 'VACACIONES'
|
||||||
context.user_data["anio"] = ANIO_ACTUAL
|
|
||||||
await update.message.reply_text(
|
await update.message.reply_text(
|
||||||
"🌴 **Solicitud de Vacaciones**\n\nUsaré el año actual. ¿En qué *día* inicia tu descanso? (número, ej: 10)",
|
"🌴 **Solicitud de Vacaciones**\n\nVamos a registrar tu descanso. ¿Qué *día* inicia? (número, ej: 10)",
|
||||||
reply_markup=ReplyKeyboardRemove(),
|
reply_markup=ReplyKeyboardRemove(),
|
||||||
)
|
)
|
||||||
return INICIO_DIA
|
return INICIO_DIA
|
||||||
@@ -131,13 +131,13 @@ async def start_permiso(update: Update, context: ContextTypes.DEFAULT_TYPE) -> i
|
|||||||
return PERMISO_CUANDO
|
return PERMISO_CUANDO
|
||||||
|
|
||||||
# --- Selección de año / cuando ---
|
# --- Selección de año / cuando ---
|
||||||
async def recibir_anio_vacaciones(update: Update, context: ContextTypes.DEFAULT_TYPE) -> int:
|
async def recibir_inicio_anio(update: Update, context: ContextTypes.DEFAULT_TYPE) -> int:
|
||||||
anio = _parse_anio(update.message.text)
|
anio = _parse_anio(update.message.text)
|
||||||
if anio not in (ANIO_ACTUAL, ANIO_ACTUAL + 1):
|
if anio not in (ANIO_ACTUAL, ANIO_ACTUAL + 1):
|
||||||
await update.message.reply_text("Elige el año del teclado (actual o siguiente).", reply_markup=TECLADO_ANIOS)
|
await update.message.reply_text("Elige el año del teclado (actual o siguiente).", reply_markup=TECLADO_ANIOS)
|
||||||
return VAC_ANIO
|
return INICIO_ANIO
|
||||||
context.user_data["anio"] = anio
|
context.user_data["inicio_anio"] = anio
|
||||||
await update.message.reply_text("¿Qué *día* termina?", reply_markup=ReplyKeyboardRemove())
|
await update.message.reply_text("¿Qué *día* termina tu descanso?", reply_markup=ReplyKeyboardRemove())
|
||||||
return FIN_DIA
|
return FIN_DIA
|
||||||
|
|
||||||
async def recibir_cuando_permiso(update: Update, context: ContextTypes.DEFAULT_TYPE) -> int:
|
async def recibir_cuando_permiso(update: Update, context: ContextTypes.DEFAULT_TYPE) -> int:
|
||||||
@@ -147,7 +147,8 @@ async def recibir_cuando_permiso(update: Update, context: ContextTypes.DEFAULT_T
|
|||||||
if texto in offset_map:
|
if texto in offset_map:
|
||||||
delta = offset_map[texto]
|
delta = offset_map[texto]
|
||||||
fecha = hoy.fromordinal(hoy.toordinal() + delta)
|
fecha = hoy.fromordinal(hoy.toordinal() + delta)
|
||||||
context.user_data["anio"] = fecha.year
|
context.user_data["inicio_anio"] = fecha.year
|
||||||
|
context.user_data["fin_anio"] = fecha.year
|
||||||
context.user_data["inicio_dia"] = fecha.day
|
context.user_data["inicio_dia"] = fecha.day
|
||||||
context.user_data["inicio_mes"] = fecha.month
|
context.user_data["inicio_mes"] = fecha.month
|
||||||
context.user_data["fin_dia"] = fecha.day
|
context.user_data["fin_dia"] = fecha.day
|
||||||
@@ -155,9 +156,8 @@ async def recibir_cuando_permiso(update: Update, context: ContextTypes.DEFAULT_T
|
|||||||
await update.message.reply_text("¿Cuál es el horario? Ej: `09:00-11:00` o `Todo el día`.", reply_markup=ReplyKeyboardRemove())
|
await update.message.reply_text("¿Cuál es el horario? Ej: `09:00-11:00` o `Todo el día`.", reply_markup=ReplyKeyboardRemove())
|
||||||
return HORARIO
|
return HORARIO
|
||||||
if "fecha" in texto:
|
if "fecha" in texto:
|
||||||
context.user_data["anio"] = ANIO_ACTUAL
|
await update.message.reply_text("¿Para qué año es el permiso? (elige el actual o el siguiente)", reply_markup=TECLADO_ANIOS)
|
||||||
await update.message.reply_text("¿En qué *día* inicia el permiso? (número, ej: 12)", reply_markup=ReplyKeyboardRemove())
|
return PERMISO_ANIO
|
||||||
return INICIO_DIA
|
|
||||||
await update.message.reply_text("Elige una opción: Hoy, Mañana, Pasado mañana o Fecha específica.", reply_markup=TECLADO_PERMISO_CUANDO)
|
await update.message.reply_text("Elige una opción: Hoy, Mañana, Pasado mañana o Fecha específica.", reply_markup=TECLADO_PERMISO_CUANDO)
|
||||||
return PERMISO_CUANDO
|
return PERMISO_CUANDO
|
||||||
|
|
||||||
@@ -166,9 +166,13 @@ async def recibir_anio_permiso(update: Update, context: ContextTypes.DEFAULT_TYP
|
|||||||
if anio not in (ANIO_ACTUAL, ANIO_ACTUAL + 1):
|
if anio not in (ANIO_ACTUAL, ANIO_ACTUAL + 1):
|
||||||
await update.message.reply_text("Elige el año del teclado (actual o siguiente).", reply_markup=TECLADO_ANIOS)
|
await update.message.reply_text("Elige el año del teclado (actual o siguiente).", reply_markup=TECLADO_ANIOS)
|
||||||
return PERMISO_ANIO
|
return PERMISO_ANIO
|
||||||
context.user_data["anio"] = anio
|
context.user_data["inicio_anio"] = anio
|
||||||
|
context.user_data["fin_anio"] = anio
|
||||||
|
if "inicio_dia" in context.user_data:
|
||||||
await update.message.reply_text("¿Qué *día* termina?", reply_markup=ReplyKeyboardRemove())
|
await update.message.reply_text("¿Qué *día* termina?", reply_markup=ReplyKeyboardRemove())
|
||||||
return FIN_DIA
|
return FIN_DIA
|
||||||
|
await update.message.reply_text("¿En qué *día* inicia el permiso? (número, ej: 12)", reply_markup=ReplyKeyboardRemove())
|
||||||
|
return INICIO_DIA
|
||||||
|
|
||||||
# --- Captura de fechas ---
|
# --- Captura de fechas ---
|
||||||
async def recibir_inicio_dia(update: Update, context: ContextTypes.DEFAULT_TYPE) -> int:
|
async def recibir_inicio_dia(update: Update, context: ContextTypes.DEFAULT_TYPE) -> int:
|
||||||
@@ -186,16 +190,21 @@ async def recibir_inicio_mes(update: Update, context: ContextTypes.DEFAULT_TYPE)
|
|||||||
await update.message.reply_text("Elige un mes del teclado o escríbelo igual que aparece.", reply_markup=TECLADO_MESES)
|
await update.message.reply_text("Elige un mes del teclado o escríbelo igual que aparece.", reply_markup=TECLADO_MESES)
|
||||||
return INICIO_MES
|
return INICIO_MES
|
||||||
context.user_data["inicio_mes"] = mes
|
context.user_data["inicio_mes"] = mes
|
||||||
context.user_data.setdefault("anio", ANIO_ACTUAL)
|
if context.user_data.get("tipo") == "VACACIONES":
|
||||||
|
await update.message.reply_text("¿De qué *año* inicia?", reply_markup=TECLADO_ANIOS)
|
||||||
|
return INICIO_ANIO
|
||||||
|
|
||||||
|
context.user_data.setdefault("inicio_anio", ANIO_ACTUAL)
|
||||||
|
context.user_data.setdefault("fin_anio", context.user_data.get("inicio_anio", ANIO_ACTUAL))
|
||||||
|
|
||||||
try:
|
try:
|
||||||
inicio_candidato = date(context.user_data["anio"], mes, context.user_data["inicio_dia"])
|
inicio_candidato = date(context.user_data["inicio_anio"], mes, context.user_data["inicio_dia"])
|
||||||
if inicio_candidato < date.today():
|
if inicio_candidato < date.today():
|
||||||
await update.message.reply_text(
|
await update.message.reply_text(
|
||||||
"Esa fecha ya pasó este año. ¿Para qué año la agendamos?",
|
"Esa fecha ya pasó este año. ¿Para qué año la agendamos?",
|
||||||
reply_markup=TECLADO_ANIOS
|
reply_markup=TECLADO_ANIOS
|
||||||
)
|
)
|
||||||
return VAC_ANIO if context.user_data.get("tipo") == "VACACIONES" else PERMISO_ANIO
|
return PERMISO_ANIO
|
||||||
except Exception:
|
except Exception:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
@@ -219,9 +228,19 @@ async def recibir_fin_mes(update: Update, context: ContextTypes.DEFAULT_TYPE) ->
|
|||||||
context.user_data["fin_mes"] = mes
|
context.user_data["fin_mes"] = mes
|
||||||
|
|
||||||
if context.user_data.get("tipo") == "PERMISO":
|
if context.user_data.get("tipo") == "PERMISO":
|
||||||
|
context.user_data.setdefault("fin_anio", context.user_data.get("inicio_anio", ANIO_ACTUAL))
|
||||||
await update.message.reply_text("¿Cuál es el horario? Ej: `09:00-11:00` o `Todo el día`.", reply_markup=ReplyKeyboardRemove())
|
await update.message.reply_text("¿Cuál es el horario? Ej: `09:00-11:00` o `Todo el día`.", reply_markup=ReplyKeyboardRemove())
|
||||||
return HORARIO
|
return HORARIO
|
||||||
|
|
||||||
|
await update.message.reply_text("¿De qué *año* termina tu descanso?", reply_markup=TECLADO_ANIOS)
|
||||||
|
return FIN_ANIO
|
||||||
|
|
||||||
|
async def recibir_fin_anio(update: Update, context: ContextTypes.DEFAULT_TYPE) -> int:
|
||||||
|
anio = _parse_anio(update.message.text)
|
||||||
|
if anio not in (ANIO_ACTUAL, ANIO_ACTUAL + 1):
|
||||||
|
await update.message.reply_text("Elige el año del teclado (actual o siguiente).", reply_markup=TECLADO_ANIOS)
|
||||||
|
return FIN_ANIO
|
||||||
|
context.user_data["fin_anio"] = anio
|
||||||
await update.message.reply_text("Entendido. ¿Cuál es el motivo o comentario adicional?", reply_markup=ReplyKeyboardRemove())
|
await update.message.reply_text("Entendido. ¿Cuál es el motivo o comentario adicional?", reply_markup=ReplyKeyboardRemove())
|
||||||
return MOTIVO
|
return MOTIVO
|
||||||
|
|
||||||
@@ -273,18 +292,25 @@ async def recibir_motivo_fin(update: Update, context: ContextTypes.DEFAULT_TYPE)
|
|||||||
payload["metricas"] = metrics
|
payload["metricas"] = metrics
|
||||||
|
|
||||||
dias = metrics["dias_totales"]
|
dias = metrics["dias_totales"]
|
||||||
if dias <= 5:
|
anticipacion = metrics.get("dias_anticipacion", 0)
|
||||||
|
if anticipacion < 0:
|
||||||
|
status = "RECHAZADO"
|
||||||
|
mensaje = "🔴 No puedo agendar vacaciones en el pasado. Ajusta tus fechas."
|
||||||
|
elif anticipacion > 30:
|
||||||
|
status = "RECHAZADO"
|
||||||
|
mensaje = "🔴 Debes solicitar vacaciones con máximo 30 días de anticipación."
|
||||||
|
elif dias < 6:
|
||||||
status = "RECHAZADO"
|
status = "RECHAZADO"
|
||||||
mensaje = f"🔴 {dias} días es un periodo muy corto. Las vacaciones deben ser de al menos 6 días."
|
mensaje = f"🔴 {dias} días es un periodo muy corto. Las vacaciones deben ser de al menos 6 días."
|
||||||
elif dias > 30:
|
elif dias > 30:
|
||||||
status = "RECHAZADO"
|
status = "RECHAZADO"
|
||||||
mensaje = "🔴 Las vacaciones no pueden exceder 30 días. Ajusta tus fechas, por favor."
|
mensaje = "🔴 Las vacaciones no pueden exceder 30 días. Ajusta tus fechas, por favor."
|
||||||
elif 6 <= dias <= 11:
|
elif 6 <= dias <= 11:
|
||||||
status = "REVISION_MANUAL"
|
status = "APROBACION_ESPECIAL"
|
||||||
mensaje = f"🟡 Solicitud de {dias} días recibida. Tu manager la revisará pronto."
|
mensaje = f"🟠 Solicitud de {dias} días: requiere aprobación especial."
|
||||||
else: # 12-30
|
else: # 12-30
|
||||||
status = "PRE_APROBADO"
|
status = "EN_ESPERA_APROBACION"
|
||||||
mensaje = f"🟢 ¡Excelente planeación! Tu solicitud de {dias} días ha sido pre-aprobada (ideal: 12 días)."
|
mensaje = f"🟡 Solicitud de {dias} días registrada. Queda en espera de aprobación."
|
||||||
|
|
||||||
payload["status_inicial"] = status
|
payload["status_inicial"] = status
|
||||||
await update.message.reply_text(mensaje)
|
await update.message.reply_text(mensaje)
|
||||||
@@ -334,11 +360,12 @@ async def cancelar(update: Update, context: ContextTypes.DEFAULT_TYPE) -> int:
|
|||||||
vacaciones_handler = ConversationHandler(
|
vacaciones_handler = ConversationHandler(
|
||||||
entry_points=[CommandHandler("vacaciones", start_vacaciones)],
|
entry_points=[CommandHandler("vacaciones", start_vacaciones)],
|
||||||
states={
|
states={
|
||||||
VAC_ANIO: [MessageHandler(filters.TEXT & ~filters.COMMAND, recibir_anio_vacaciones)],
|
|
||||||
INICIO_DIA: [MessageHandler(filters.TEXT & ~filters.COMMAND, recibir_inicio_dia)],
|
INICIO_DIA: [MessageHandler(filters.TEXT & ~filters.COMMAND, recibir_inicio_dia)],
|
||||||
INICIO_MES: [MessageHandler(filters.TEXT & ~filters.COMMAND, recibir_inicio_mes)],
|
INICIO_MES: [MessageHandler(filters.TEXT & ~filters.COMMAND, recibir_inicio_mes)],
|
||||||
|
INICIO_ANIO: [MessageHandler(filters.TEXT & ~filters.COMMAND, recibir_inicio_anio)],
|
||||||
FIN_DIA: [MessageHandler(filters.TEXT & ~filters.COMMAND, recibir_fin_dia)],
|
FIN_DIA: [MessageHandler(filters.TEXT & ~filters.COMMAND, recibir_fin_dia)],
|
||||||
FIN_MES: [MessageHandler(filters.TEXT & ~filters.COMMAND, recibir_fin_mes)],
|
FIN_MES: [MessageHandler(filters.TEXT & ~filters.COMMAND, recibir_fin_mes)],
|
||||||
|
FIN_ANIO: [MessageHandler(filters.TEXT & ~filters.COMMAND, recibir_fin_anio)],
|
||||||
MOTIVO: [MessageHandler(filters.TEXT & ~filters.COMMAND, recibir_motivo_fin)]
|
MOTIVO: [MessageHandler(filters.TEXT & ~filters.COMMAND, recibir_motivo_fin)]
|
||||||
},
|
},
|
||||||
fallbacks=[CommandHandler("cancelar", cancelar)]
|
fallbacks=[CommandHandler("cancelar", cancelar)]
|
||||||
|
|||||||
Reference in New Issue
Block a user