Skip to main content

Documentation Index

Fetch the complete documentation index at: https://mintlify.com/BrandonCVale/SISTEMA-HABITOS/llms.txt

Use this file to discover all available pages before exploring further.

Hábito. handles all user authentication through a combination of bcrypt password hashing and Flask-Login session management. When a user registers, their password is hashed with a unique bcrypt salt before being stored — the plaintext password never touches the database. On login, Flask-Login issues a secure, signed session cookie that persists across requests, and every protected route checks this cookie automatically before rendering a response.

Registration

Users create an account by submitting the registration form, which posts to /auth/registro. Route: POST /auth/registro
If a user who is already logged in visits /auth/registro, the route detects the active session via current_user.is_authenticated and immediately redirects them to the dashboard (pag_principal.inicio) — the registration form is never shown.
1

Collect form fields

The route reads three fields from the HTML form: correo (email), contrasena (password), and nombre_usuario (username). If any of these fields is empty, the request is rejected immediately with a flash error and the user is redirected back to the form.
2

Hash the password with bcrypt

UsuarioRepository.crear_usuario is called with the raw values. Inside that method, bcrypt generates a salt and produces a hash before any data reaches the database:
@staticmethod
def crear_usuario(correo: str, contrasena_plana: str, nombre_usuario: str) -> Usuario:
    # Encriptar la contrasena
    # Genera un texto aleatorio
    salt = bcrypt.gensalt()
    hash_contrasena = bcrypt.hashpw(contrasena_plana.encode('utf-8'), salt)

    # Crear el objeto Usuario con el hash
    # Decodificamos el hash a string para guardarlo en la BD
    nuevo_usuario = Usuario(correo=correo,
                            contrasena=hash_contrasena.decode('utf-8'),
                            nombre_usuario=nombre_usuario)

    # Guardamos en la BD
    db.session.add(nuevo_usuario)
    db.session.commit()

    return nuevo_usuario
3

Handle duplicate accounts

If the email or username is already taken, SQLAlchemy raises an IntegrityError (because both columns carry unique=True constraints). The route catches this exception and flashes a friendly error without exposing database details.
try:
    UsuarioRepository.crear_usuario(correo=correo, contrasena_plana=contrasena, nombre_usuario=usuario)
    flash("¡Registro exitoso! Ahora puedes iniciar sesión.", "success")
    return redirect(url_for('auth.registro'))
except IntegrityError:
    flash("Este correo electrónico o nombre de usuario ya está registrado.", "error")
    return redirect(url_for('auth.registro'))
Validation rules:
FieldRequiredConstraint
correoUnique per user, max 120 chars
contrasenaStored as bcrypt hash, max 128 chars
nombre_usuarioUnique per user, max 50 chars
Both correo and nombre_usuario have unique=True defined directly on the SQLAlchemy mapped_column. The database enforces uniqueness at the engine level, so the IntegrityError is raised even if application-level checks are bypassed.

Login

Registered users authenticate by submitting the login form, which posts to /auth/inicio_sesion. Route: POST /auth/inicio_sesion The route delegates credential verification to UsuarioRepository.verificar_credenciales, which first looks up the user by email, then uses bcrypt.checkpw to compare the submitted plaintext password against the stored hash:
@staticmethod
def verificar_credenciales(correo: str, contrasena_plana: str) -> Optional[Usuario]:
    # 1. Buscar al usuario en la BD por su correo
    usuario = UsuarioRepository.obtener_usuario_por_correo(correo)
    if not usuario:
        return None  # El correo no existe

    # 2. Comparamos la contrasena plana con el hash guardado en la BD
    # IMPORTANTE: bcrypt.checkpw requiere que ambos textos estén en formato 'bytes'
    contrasena_bytes = contrasena_plana.encode('utf-8')
    hash_bytes = usuario.contrasena.encode('utf-8')

    if bcrypt.checkpw(contrasena_bytes, hash_bytes):
        return usuario  # Credenciales correctas
    return None  # Contraseña incorrecta
If credentials are valid, the route calls login_user(usuario). Flask-Login serialises the user’s id into a signed session cookie and attaches it to the response. Every subsequent request carries that cookie, allowing Flask-Login to identify the user without hitting the database on every endpoint.
usuario = UsuarioRepository.verificar_credenciales(correo, contrasena)
if usuario:
    # FLASK-LOGIN: Le pasamos el objeto entero y él gestiona la cookie de forma segura
    login_user(usuario)
    flash(f"¡Bienvenido, {usuario.correo}!", "success")
    return redirect(url_for('pag_principal.inicio'))
else:
    flash("Correo o contraseña incorrectos.", "error")
    return redirect(url_for('auth.inicio_sesion'))
If the user is already authenticated when they visit /auth/inicio_sesion, they are redirected straight to the dashboard to avoid redundant logins.

Logout

Route: GET /auth/cerrar_sesion The logout route is protected by @login_required — an unauthenticated request is redirected to the login page before the route body ever executes. Inside the route, logout_user() removes the session cookie, ending the session immediately:
@auth_bp.route('/cerrar_sesion')
@login_required
def cerrar_sesion():
    """Cierra la sesión del usuario actual."""

    # FLASK-LOGIN: Borra la sesión de este usuario específicamente
    logout_user()

    flash('Has cerrado la sesión exitosamente', 'success')
    return redirect(url_for('auth.inicio_sesion'))
After logout the user is redirected to /auth/inicio_sesion. No personal data remains in the browser session.

Route Protection

Hábito. uses Flask-Login’s @login_required decorator to guard every route that requires an authenticated user. Any request to a protected route without a valid session cookie is automatically intercepted. How the redirect works LoginManager is configured in app/__init__.py to redirect unauthenticated visitors to the login page:
login_manager.login_view = 'auth.inicio_sesion'
login_manager.login_message = 'Por favor inicia sesión para acceder.'
login_manager.login_message_category = 'error'
When the @login_required check fails, Flask-Login flashes the configured message and redirects to auth.inicio_sesion — no route logic is executed and no data is exposed. The load_user callback On every request to a protected route, Flask-Login reads the user id from the session cookie and calls the user_loader callback to rehydrate a full Usuario object from the database. This callback is registered inside create_app():
from app.models.usuario import Usuario

@login_manager.user_loader
def load_user(user_id: str) -> Optional[Usuario]:
    """
    Recupera un usuario de la base de datos usando el ID almacenado en la cookie de sesión.
    Args:
        user_id (str): El ID del usuario extraído de la cookie (Flask-Login siempre lo envía como texto).
    Returns:
        Optional[Usuario]: El objeto Usuario si es encontrado, o None si no existe en la base de datos.
    """
    # Convertimos el ID de texto a número entero para buscarlo por su llave primaria
    return db.session.get(Usuario, int(user_id))
The result is made available throughout the request as current_user, the Flask-Login proxy used in routes, repositories, and templates.
The SECRET_KEY in app/__init__.py is currently set to a hardcoded string ('sistema_habitos_76Fu-39+'). Flask uses this key to cryptographically sign session cookies — anyone who knows the key can forge a valid session. Before deploying to production, replace it with a securely generated random value, for example:
import secrets
app.config['SECRET_KEY'] = secrets.token_hex(32)
Store the production key in an environment variable or a secrets manager, never in source control.

Build docs developers (and LLMs) love