mirror of
https://github.com/marcogll/telegram_new_socias.git
synced 2026-01-13 13:15:16 +00:00
feat: Implementar registro de usuarios en base de datos dual
Este commit introduce las siguientes mejoras en el proceso de onboarding:
1. **Registro en Dos Fases**: El flujo de onboarding ahora registra a las nuevas usuarias en dos bases de datos distintas para mejorar la seguridad y la integridad de los datos:
* ****: Se crea un registro básico para la autenticación y el control de acceso del bot.
* ****: Se guarda un perfil completo y detallado de la empleada en la tabla .
2. **Modelos SQLAlchemy**: Se han actualizado los modelos de SQLAlchemy ( y ) para reflejar la estructura de las tablas de la base de datos.
3. **Lógica de Base de Datos Centralizada**: El módulo ahora contiene la lógica para gestionar el registro dual, asegurando que ambas operaciones se realicen de forma atómica.
4. **Flujo de Onboarding Actualizado**: El script de ha sido modificado para recopilar la información necesaria y pasarla al nuevo sistema de registro.
5. **Configuración de Docker**: Se ha ajustado el y los scripts de inicialización de la base de datos ( y ) para soportar el nuevo esquema de base de datos dual.
Estos cambios aseguran un proceso de registro más robusto, seguro y escalable, sentando las bases para futuras funcionalidades de RRHH.
This commit is contained in:
28
db/init/00-bootstrap.sh
Executable file
28
db/init/00-bootstrap.sh
Executable file
@@ -0,0 +1,28 @@
|
|||||||
|
#!/bin/bash
|
||||||
|
set -euo pipefail
|
||||||
|
|
||||||
|
: "${MYSQL_ROOT_PASSWORD:?MYSQL_ROOT_PASSWORD is required}"
|
||||||
|
|
||||||
|
APP_USER="${MYSQL_USER:-user}"
|
||||||
|
APP_PASSWORD="${MYSQL_PASSWORD:-password}"
|
||||||
|
|
||||||
|
escape_sql() {
|
||||||
|
printf "%s" "$1" | sed "s/'/''/g"
|
||||||
|
}
|
||||||
|
|
||||||
|
DB_USER_ESCAPED=$(escape_sql "$APP_USER")
|
||||||
|
DB_PASS_ESCAPED=$(escape_sql "$APP_PASSWORD")
|
||||||
|
|
||||||
|
mysql -uroot -p"${MYSQL_ROOT_PASSWORD}" <<-EOSQL
|
||||||
|
CREATE DATABASE IF NOT EXISTS USERS_ALMA;
|
||||||
|
CREATE DATABASE IF NOT EXISTS vanity_hr;
|
||||||
|
CREATE DATABASE IF NOT EXISTS vanity_attendance;
|
||||||
|
|
||||||
|
CREATE USER IF NOT EXISTS '${DB_USER_ESCAPED}'@'%';
|
||||||
|
ALTER USER '${DB_USER_ESCAPED}'@'%' IDENTIFIED WITH mysql_native_password BY '${DB_PASS_ESCAPED}';
|
||||||
|
|
||||||
|
GRANT ALL PRIVILEGES ON USERS_ALMA.* TO '${DB_USER_ESCAPED}'@'%';
|
||||||
|
GRANT ALL PRIVILEGES ON vanity_hr.* TO '${DB_USER_ESCAPED}'@'%';
|
||||||
|
GRANT ALL PRIVILEGES ON vanity_attendance.* TO '${DB_USER_ESCAPED}'@'%';
|
||||||
|
FLUSH PRIVILEGES;
|
||||||
|
EOSQL
|
||||||
@@ -2,10 +2,6 @@ CREATE DATABASE IF NOT EXISTS USERS_ALMA;
|
|||||||
CREATE DATABASE IF NOT EXISTS vanity_hr;
|
CREATE DATABASE IF NOT EXISTS vanity_hr;
|
||||||
CREATE DATABASE IF NOT EXISTS vanity_attendance;
|
CREATE DATABASE IF NOT EXISTS vanity_attendance;
|
||||||
|
|
||||||
GRANT ALL PRIVILEGES ON USERS_ALMA.* TO 'user'@'%';
|
|
||||||
GRANT ALL PRIVILEGES ON vanity_hr.* TO 'user'@'%';
|
|
||||||
GRANT ALL PRIVILEGES ON vanity_attendance.* TO 'user'@'%';
|
|
||||||
|
|
||||||
USE USERS_ALMA;
|
USE USERS_ALMA;
|
||||||
|
|
||||||
CREATE TABLE IF NOT EXISTS users (
|
CREATE TABLE IF NOT EXISTS users (
|
||||||
|
|||||||
@@ -3,6 +3,7 @@ version: "3.8"
|
|||||||
services:
|
services:
|
||||||
bot:
|
bot:
|
||||||
build: .
|
build: .
|
||||||
|
image: marcogll/vanessa-bot:1.8
|
||||||
container_name: vanessa_bot
|
container_name: vanessa_bot
|
||||||
restart: always
|
restart: always
|
||||||
env_file:
|
env_file:
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import logging
|
import logging
|
||||||
import os
|
import os
|
||||||
|
from datetime import datetime, date
|
||||||
from sqlalchemy import create_engine
|
from sqlalchemy import create_engine
|
||||||
from sqlalchemy.orm import sessionmaker
|
from sqlalchemy.orm import sessionmaker
|
||||||
from models.users_alma_models import Base as BaseUsersAlma, User
|
from models.users_alma_models import Base as BaseUsersAlma, User
|
||||||
@@ -38,6 +39,73 @@ SessionVanityAttendance = sessionmaker(autocommit=False, autoflush=False, bind=e
|
|||||||
# --- GOOGLE SHEETS SETUP (REMOVED) ---
|
# --- GOOGLE SHEETS SETUP (REMOVED) ---
|
||||||
# Duplicate checking is now done via database.
|
# Duplicate checking is now done via database.
|
||||||
|
|
||||||
|
def _parse_date(value):
|
||||||
|
if not value:
|
||||||
|
return None
|
||||||
|
if isinstance(value, datetime):
|
||||||
|
return value.date()
|
||||||
|
if isinstance(value, date):
|
||||||
|
return value
|
||||||
|
for parser in (
|
||||||
|
lambda v: datetime.fromisoformat(v),
|
||||||
|
lambda v: datetime.strptime(v, "%Y-%m-%d"),
|
||||||
|
lambda v: datetime.strptime(v, "%d/%m/%Y"),
|
||||||
|
):
|
||||||
|
try:
|
||||||
|
return parser(str(value)).date()
|
||||||
|
except Exception:
|
||||||
|
continue
|
||||||
|
return None
|
||||||
|
|
||||||
|
def _parse_datetime(value):
|
||||||
|
if not value:
|
||||||
|
return None
|
||||||
|
if isinstance(value, datetime):
|
||||||
|
return value
|
||||||
|
if isinstance(value, date):
|
||||||
|
return datetime.combine(value, datetime.min.time())
|
||||||
|
try:
|
||||||
|
return datetime.fromisoformat(str(value))
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
try:
|
||||||
|
return datetime.strptime(str(value), "%Y-%m-%d %H:%M:%S")
|
||||||
|
except Exception:
|
||||||
|
return None
|
||||||
|
|
||||||
|
def _build_full_address(domicilio: dict) -> str:
|
||||||
|
if not domicilio:
|
||||||
|
return ""
|
||||||
|
partes = []
|
||||||
|
calle = domicilio.get("calle")
|
||||||
|
num_ext = domicilio.get("num_ext")
|
||||||
|
num_int = domicilio.get("num_int")
|
||||||
|
colonia = domicilio.get("colonia")
|
||||||
|
cp = domicilio.get("cp")
|
||||||
|
ciudad = domicilio.get("ciudad")
|
||||||
|
estado = domicilio.get("estado")
|
||||||
|
|
||||||
|
if calle:
|
||||||
|
linea = f"{calle}"
|
||||||
|
if num_ext:
|
||||||
|
linea += f" {num_ext}"
|
||||||
|
if num_int and num_int not in ("0", "N/A"):
|
||||||
|
linea += f" Int {num_int}"
|
||||||
|
partes.append(linea)
|
||||||
|
if colonia:
|
||||||
|
partes.append(colonia)
|
||||||
|
if ciudad or estado:
|
||||||
|
partes.append(", ".join(filter(None, [ciudad, estado])))
|
||||||
|
if cp:
|
||||||
|
partes.append(f"CP {cp}")
|
||||||
|
return " - ".join(partes)
|
||||||
|
|
||||||
|
def _references_with_padding(referencias: list) -> list:
|
||||||
|
refs = referencias or []
|
||||||
|
if len(refs) < 3:
|
||||||
|
refs = refs + [{}] * (3 - len(refs))
|
||||||
|
return refs[:3]
|
||||||
|
|
||||||
def chat_id_exists(chat_id: int) -> bool:
|
def chat_id_exists(chat_id: int) -> bool:
|
||||||
"""Checks if a Telegram chat_id already exists in the USERS_ALMA.users table."""
|
"""Checks if a Telegram chat_id already exists in the USERS_ALMA.users table."""
|
||||||
if not SessionUsersAlma:
|
if not SessionUsersAlma:
|
||||||
@@ -55,30 +123,149 @@ def chat_id_exists(chat_id: int) -> bool:
|
|||||||
session.close()
|
session.close()
|
||||||
|
|
||||||
def register_user(user_data: dict) -> bool:
|
def register_user(user_data: dict) -> bool:
|
||||||
"""Registers a new user in the USERS_ALMA.users table."""
|
"""
|
||||||
if not SessionUsersAlma:
|
Persists a new colaboradora across the USERS_ALMA.users and vanity_hr.data_empleadas tables.
|
||||||
logging.warning("SessionUsersAlma not initialized. Cannot register user.")
|
|
||||||
|
Expected structure (all keys optional but recommended):
|
||||||
|
{
|
||||||
|
"meta": {...}, "metadata": {...}, "candidato": {...}, "contacto": {...},
|
||||||
|
"domicilio": {...}, "laboral": {...}, "referencias": [...], "emergencia": {...}
|
||||||
|
}
|
||||||
|
"""
|
||||||
|
if not SessionUsersAlma or not SessionVanityHr:
|
||||||
|
logging.warning("Database sessions not initialized. Cannot register user.")
|
||||||
return False
|
return False
|
||||||
|
|
||||||
session = SessionUsersAlma()
|
meta = user_data.get("meta") or {}
|
||||||
|
metadata = user_data.get("metadata") or {}
|
||||||
|
candidato = user_data.get("candidato") or {}
|
||||||
|
contacto = user_data.get("contacto") or {}
|
||||||
|
domicilio = user_data.get("domicilio") or {}
|
||||||
|
laboral = user_data.get("laboral") or {}
|
||||||
|
referencias = _references_with_padding(user_data.get("referencias") or [])
|
||||||
|
emergencia = user_data.get("emergencia") or {}
|
||||||
|
|
||||||
|
telegram_id = metadata.get("chat_id") or meta.get("telegram_id")
|
||||||
|
if not telegram_id:
|
||||||
|
logging.error("register_user: missing telegram_id; aborting persist.")
|
||||||
|
return False
|
||||||
|
|
||||||
|
# --- USERS_ALMA.users ---
|
||||||
|
session_users = SessionUsersAlma()
|
||||||
try:
|
try:
|
||||||
new_user = User(
|
user_record = session_users.query(User).filter(User.telegram_id == str(telegram_id)).first()
|
||||||
telegram_id=str(user_data.get("chat_id")),
|
if user_record:
|
||||||
username=user_data.get("telegram_user"),
|
user_record.username = metadata.get("telegram_user") or meta.get("username")
|
||||||
first_name=user_data.get("first_name"),
|
user_record.first_name = candidato.get("nombre_preferido") or meta.get("first_name")
|
||||||
last_name=f"{user_data.get('apellido_paterno', '')} {user_data.get('apellido_materno', '')}".strip(),
|
apellidos = f"{candidato.get('apellido_paterno', '')} {candidato.get('apellido_materno', '')}".strip()
|
||||||
email=user_data.get("email"),
|
user_record.last_name = apellidos or user_record.last_name
|
||||||
cell_phone=user_data.get("celular"),
|
user_record.email = contacto.get("email") or user_record.email
|
||||||
role='user' # Default role
|
user_record.cell_phone = contacto.get("celular") or user_record.cell_phone
|
||||||
|
else:
|
||||||
|
user_record = User(
|
||||||
|
telegram_id=str(telegram_id),
|
||||||
|
username=metadata.get("telegram_user") or meta.get("username"),
|
||||||
|
first_name=candidato.get("nombre_preferido") or meta.get("first_name"),
|
||||||
|
last_name=f"{candidato.get('apellido_paterno', '')} {candidato.get('apellido_materno', '')}".strip(),
|
||||||
|
email=contacto.get("email"),
|
||||||
|
cell_phone=contacto.get("celular"),
|
||||||
|
role='user'
|
||||||
)
|
)
|
||||||
session.add(new_user)
|
session_users.add(user_record)
|
||||||
session.commit()
|
session_users.commit()
|
||||||
logging.info(f"User {user_data.get('chat_id')} registered successfully in DB.")
|
except Exception as exc:
|
||||||
return True
|
session_users.rollback()
|
||||||
except Exception as e:
|
logging.error(f"Error persisting user in USERS_ALMA: {exc}")
|
||||||
session.rollback()
|
|
||||||
logging.error(f"Error registering user in DB: {e}")
|
|
||||||
return False
|
return False
|
||||||
finally:
|
finally:
|
||||||
session.close()
|
session_users.close()
|
||||||
|
|
||||||
|
# --- vanity_hr.data_empleadas ---
|
||||||
|
numero_empleado = laboral.get("numero_empleado") or f"T{telegram_id}"
|
||||||
|
fecha_registro = _parse_datetime(metadata.get("fecha_registro")) or datetime.utcnow()
|
||||||
|
fecha_procesamiento = datetime.utcnow()
|
||||||
|
fecha_ingreso = _parse_date(laboral.get("fecha_inicio"))
|
||||||
|
fecha_nacimiento = _parse_date(candidato.get("fecha_nacimiento"))
|
||||||
|
tiempo_registro_minutos = None
|
||||||
|
try:
|
||||||
|
duracion_segundos = float(metadata.get("duracion_segundos", 0))
|
||||||
|
tiempo_registro_minutos = int(round(duracion_segundos / 60))
|
||||||
|
except Exception:
|
||||||
|
tiempo_registro_minutos = None
|
||||||
|
|
||||||
|
nombre = candidato.get("nombre_oficial") or ""
|
||||||
|
apellido_paterno = candidato.get("apellido_paterno") or ""
|
||||||
|
apellido_materno = candidato.get("apellido_materno") or ""
|
||||||
|
nombre_completo = " ".join(filter(None, [nombre, apellido_paterno, apellido_materno])).strip()
|
||||||
|
|
||||||
|
domicilio_completo = _build_full_address(domicilio)
|
||||||
|
telegram_username = metadata.get("telegram_user") or meta.get("username")
|
||||||
|
try:
|
||||||
|
telegram_chat_id = int(telegram_id)
|
||||||
|
except Exception:
|
||||||
|
telegram_chat_id = None
|
||||||
|
|
||||||
|
empleada_payload = {
|
||||||
|
"numero_empleado": numero_empleado,
|
||||||
|
"puesto": laboral.get("rol_id"),
|
||||||
|
"sucursal": laboral.get("sucursal_id"),
|
||||||
|
"fecha_ingreso": fecha_ingreso,
|
||||||
|
"estatus": "activo",
|
||||||
|
"nombre_completo": nombre_completo or nombre,
|
||||||
|
"nombre": nombre or meta.get("first_name"),
|
||||||
|
"nombre_preferido": candidato.get("nombre_preferido"),
|
||||||
|
"apellido_paterno": apellido_paterno,
|
||||||
|
"apellido_materno": apellido_materno,
|
||||||
|
"fecha_nacimiento": fecha_nacimiento,
|
||||||
|
"lugar_nacimiento": candidato.get("lugar_nacimiento"),
|
||||||
|
"rfc": candidato.get("rfc"),
|
||||||
|
"curp": candidato.get("curp"),
|
||||||
|
"email": contacto.get("email"),
|
||||||
|
"telefono_celular": contacto.get("celular"),
|
||||||
|
"domicilio_calle": domicilio.get("calle"),
|
||||||
|
"domicilio_numero_exterior": domicilio.get("num_ext"),
|
||||||
|
"domicilio_numero_interior": domicilio.get("num_int"),
|
||||||
|
"domicilio_numero_texto": domicilio.get("num_ext_texto"),
|
||||||
|
"domicilio_colonia": domicilio.get("colonia"),
|
||||||
|
"domicilio_codigo_postal": domicilio.get("cp"),
|
||||||
|
"domicilio_ciudad": domicilio.get("ciudad"),
|
||||||
|
"domicilio_estado": domicilio.get("estado"),
|
||||||
|
"domicilio_completo": domicilio_completo,
|
||||||
|
"emergencia_nombre": emergencia.get("nombre"),
|
||||||
|
"emergencia_telefono": emergencia.get("telefono"),
|
||||||
|
"emergencia_parentesco": emergencia.get("relacion"),
|
||||||
|
"referencia_1_nombre": referencias[0].get("nombre"),
|
||||||
|
"referencia_1_telefono": referencias[0].get("telefono"),
|
||||||
|
"referencia_1_tipo": referencias[0].get("relacion"),
|
||||||
|
"referencia_2_nombre": referencias[1].get("nombre"),
|
||||||
|
"referencia_2_telefono": referencias[1].get("telefono"),
|
||||||
|
"referencia_2_tipo": referencias[1].get("relacion"),
|
||||||
|
"referencia_3_nombre": referencias[2].get("nombre"),
|
||||||
|
"referencia_3_telefono": referencias[2].get("telefono"),
|
||||||
|
"referencia_3_tipo": referencias[2].get("relacion"),
|
||||||
|
"origen_registro": "telegram_bot",
|
||||||
|
"telegram_usuario": telegram_username,
|
||||||
|
"telegram_chat_id": telegram_chat_id,
|
||||||
|
"bot_version": metadata.get("bot_version"),
|
||||||
|
"fecha_registro": fecha_registro,
|
||||||
|
"tiempo_registro_minutos": tiempo_registro_minutos,
|
||||||
|
"fecha_procesamiento": fecha_procesamiento
|
||||||
|
}
|
||||||
|
|
||||||
|
session_hr = SessionVanityHr()
|
||||||
|
try:
|
||||||
|
existing = session_hr.get(DataEmpleadas, numero_empleado)
|
||||||
|
if existing:
|
||||||
|
for field, value in empleada_payload.items():
|
||||||
|
setattr(existing, field, value)
|
||||||
|
else:
|
||||||
|
session_hr.add(DataEmpleadas(**empleada_payload))
|
||||||
|
session_hr.commit()
|
||||||
|
logging.info(f"User {telegram_id} registered in vanity_hr.data_empleadas as {numero_empleado}.")
|
||||||
|
return True
|
||||||
|
except Exception as exc:
|
||||||
|
session_hr.rollback()
|
||||||
|
logging.error(f"Error persisting colaboradora in vanity_hr: {exc}")
|
||||||
|
return False
|
||||||
|
finally:
|
||||||
|
session_hr.close()
|
||||||
|
|||||||
@@ -406,16 +406,15 @@ async def finalizar(update: Update, context: ContextTypes.DEFAULT_TYPE) -> int:
|
|||||||
|
|
||||||
# --- REGISTRO EN BASE DE DATOS ---
|
# --- REGISTRO EN BASE DE DATOS ---
|
||||||
db_ok = register_user({
|
db_ok = register_user({
|
||||||
**meta,
|
"meta": meta,
|
||||||
**payload["metadata"],
|
**payload
|
||||||
**payload["candidato"],
|
|
||||||
**payload["contacto"]
|
|
||||||
})
|
})
|
||||||
|
|
||||||
|
chat_id_log = payload.get("metadata", {}).get("chat_id", meta.get("telegram_id"))
|
||||||
if db_ok:
|
if db_ok:
|
||||||
logging.info(f"Usuario {meta['chat_id']} registrado en la base de datos.")
|
logging.info(f"Usuario {chat_id_log} registrado en la base de datos.")
|
||||||
else:
|
else:
|
||||||
logging.error(f"Fallo al registrar usuario {meta['chat_id']} en la base de datos.")
|
logging.error(f"Fallo al registrar usuario {chat_id_log} en la base de datos.")
|
||||||
|
|
||||||
if enviado:
|
if enviado:
|
||||||
await update.message.reply_text(
|
await update.message.reply_text(
|
||||||
|
|||||||
Reference in New Issue
Block a user