from flask import ( Blueprint, render_template, redirect, url_for, flash, request, jsonify, current_app, ) from flask_login import login_required, current_user from flask_wtf import FlaskForm from wtforms import ( StringField, PasswordField, SelectField, IntegerField, EmailField, BooleanField, SubmitField, ) from wtforms.validators import DataRequired, Email, Length, Optional, EqualTo from services.auth_service import ( get_all_users, get_user_by_username, create_user, update_user, delete_user, ) from services.user_service import ( filter_users, get_user_stats, check_username_availability, ) from utils.validators import validate_user_data from utils.security import permission_required from utils.logger import log_user_management # Definir Blueprint users_bp = Blueprint("users", __name__, url_prefix="/users") # Formularios class UserForm(FlaskForm): """Formulario para crear/editar usuarios.""" nombre = StringField("Nombre completo", validators=[DataRequired(), Length(1, 100)]) username = StringField( "Nombre de usuario", validators=[DataRequired(), Length(3, 20)] ) email = EmailField("Email", validators=[DataRequired(), Email()]) password = PasswordField("Contraseña", validators=[Optional(), Length(8, 64)]) password_confirm = PasswordField( "Confirmar contraseña", validators=[ Optional(), EqualTo("password", message="Las contraseñas deben coincidir"), ], ) nivel = IntegerField("Nivel de acceso", validators=[DataRequired()]) idioma = SelectField("Idioma", choices=[("es", "Español"), ("en", "Inglés")]) empresa = StringField("Empresa", validators=[Optional(), Length(0, 100)]) estado = SelectField( "Estado", choices=[("activo", "Activo"), ("inactivo", "Inactivo")] ) fecha_caducidad = StringField( "Fecha de caducidad (YYYY-MM-DD)", validators=[Optional()] ) submit = SubmitField("Guardar") class UserFilterForm(FlaskForm): """Formulario para filtrar usuarios.""" empresa = StringField("Empresa", validators=[Optional()]) estado = SelectField( "Estado", choices=[("", "Todos"), ("activo", "Activo"), ("inactivo", "Inactivo")], validators=[Optional()], ) nivel_min = IntegerField("Nivel mínimo", validators=[Optional()]) nivel_max = IntegerField("Nivel máximo", validators=[Optional()]) submit = SubmitField("Filtrar") # Rutas @users_bp.route("/") @login_required @permission_required(5000) # Assuming 5000+ is admin level permission def list(): """List all users.""" # Import here to avoid circular imports from services.user_service import get_all_users users = get_all_users() return render_template("users/list.html", users=users) @users_bp.route("/create", methods=["GET", "POST"]) @login_required @permission_required(9000) # Solo administradores def create(): """Crear nuevo usuario.""" form = UserForm() if form.validate_on_submit(): # Validar datos data = { "nombre": form.nombre.data, "username": form.username.data, "email": form.email.data, "password": form.password.data, "nivel": form.nivel.data, "idioma": form.idioma.data, "empresa": form.empresa.data, "estado": form.estado.data, "fecha_caducidad": ( form.fecha_caducidad.data if form.fecha_caducidad.data else None ), } is_valid, errors = validate_user_data(data) if not is_valid: for field, error in errors.items(): flash(f"Error en {field}: {error}", "danger") return render_template("users/create.html", form=form) # Crear usuario success, message = create_user( username=data["username"], nombre=data["nombre"], email=data["email"], password=data["password"], nivel=data["nivel"], idioma=data["idioma"], fecha_caducidad=data["fecha_caducidad"], empresa=data["empresa"], estado=data["estado"], ) if success: flash(message, "success") # Registrar actividad log_user_management( admin_id=current_user.id, target_user_id=data["username"], action="create", ) return redirect(url_for("users.list_users")) else: flash(message, "danger") return render_template("users/create.html", form=form) @users_bp.route("/edit/", methods=["GET", "POST"]) @login_required @permission_required(9000) # Solo administradores def edit(username): """Editar usuario existente.""" user = get_user_by_username(username) if not user: flash(f"Usuario {username} no encontrado.", "danger") return redirect(url_for("users.list_users")) form = UserForm(obj=user) if form.validate_on_submit(): # Validar datos data = { "nombre": form.nombre.data, "email": form.email.data, "password": form.password.data, "nivel": form.nivel.data, "idioma": form.idioma.data, "empresa": form.empresa.data, "estado": form.estado.data, "fecha_caducidad": ( form.fecha_caducidad.data if form.fecha_caducidad.data else None ), } is_valid, errors = validate_user_data(data, is_new_user=False) if not is_valid: for field, error in errors.items(): flash(f"Error en {field}: {error}", "danger") return render_template("users/edit.html", form=form, username=username) # Actualizar usuario success, message = update_user(username, data) if success: flash(message, "success") # Registrar actividad log_user_management( admin_id=current_user.id, target_user_id=username, action="update", details={"fields_updated": list(data.keys())}, ) return redirect(url_for("users.list_users")) else: flash(message, "danger") return render_template("users/edit.html", form=form, username=username) @users_bp.route("/delete/", methods=["POST"]) @login_required @permission_required(9000) # Solo administradores def delete(username): """Eliminar usuario.""" if username == current_user.id: flash("No puede eliminar su propio usuario.", "danger") return redirect(url_for("users.list_users")) if username == "admin": flash("No se puede eliminar el usuario administrador.", "danger") return redirect(url_for("users.list_users")) success, message = delete_user(username) if success: flash(message, "success") # Registrar actividad log_user_management( admin_id=current_user.id, target_user_id=username, action="delete" ) else: flash(message, "danger") return redirect(url_for("users.list_users")) # API para verificar disponibilidad de nombre de usuario @users_bp.route("/api/check_username", methods=["POST"]) @login_required @permission_required(9000) # Solo administradores def api_check_username(): """Verificar disponibilidad de nombre de usuario.""" data = request.get_json() if not data or "username" not in data: return jsonify({"error": "Se requiere nombre de usuario"}), 400 username = data["username"] available = check_username_availability(username) return jsonify({"available": available})