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.

In Hábito., a habit is the core tracking unit. Each habit is stored in the habitos table and belongs to exactly one user. The Habito model captures the habit’s name, an optional description, the days of the week it should be performed (dias_semana), an activo boolean flag to suspend a habit without deleting it, and two streak counters: racha_actual (current consecutive-day streak) and mejor_racha (all-time best streak). The model also defines an optional horario column (a time-of-day label such as 'Mañana' or 'Tarde'), but this field is not read by the creation route — it is reserved for future use and is always NULL for newly created habits. Every time a user ticks off a habit for the day, a RegistroHabito record is written to a companion table that drives streaks, the heat-map calendar, and the progress charts.

Creating a Habit

Route: POST /habitos/crear The creation form lives on the dashboard. It submits nombre, an optional descripcion, and one or more dias checkboxes to this route. Validation ensures that both the habit name and at least one day are provided before any database write is attempted.
1

Submit the form

The route extracts form values and converts the list of day letters into a comma-separated string, because SQLite stores dias_semana as plain text:
nombre = request.form.get('nombre')
descripcion = request.form.get('descripcion')
dias_lista = request.form.getlist('dias')  # nos devuelve una lista de Python

# convertimos la lista de 'dias_lista' porque
# la BD no conoce el tipo lista, solo string
dias_texto = ",".join(dias_lista) if dias_lista else ""

if not nombre or not dias_lista:
    flash("El nombre del hábito y los dias de la semana son obligatorios.", "error")
    return redirect(url_for('pag_principal.inicio'))
2

Persist the habit

HabitoRepository.crear_habito builds the ORM object and commits it to the database:
@staticmethod
def crear_habito(nombre: str,
                 usuario_id: int,
                 dias_semana: str,
                 descripcion: Optional[str] = None) -> Habito:
    nuevo_habito = Habito(
        nombre=nombre,
        descripcion=descripcion,
        usuario_id=usuario_id,
        dias_semana=dias_semana
    )

    db.session.add(nuevo_habito)
    db.session.commit()

    return nuevo_habito
Form fields:
FieldTypeRequiredNotes
nombreTextMax 100 characters
descripcionTextareaOptional; stored as TEXT
diasCheckboxes✅ At least oneMulti-select; see day letters below
Days of the week are stored as a comma-separated string in the dias_semana column — for example, "L,M,X,J,V" for a weekday-only habit. The letter codes are:
LetterDay
LLunes (Monday)
MMartes (Tuesday)
XMiércoles (Wednesday)
JJueves (Thursday)
VViernes (Friday)
SSábado (Saturday)
DDomingo (Sunday)

Listing Habits

Route: GET /habitos/mis_habitos?estado={activos|inactivos|todos} The habit management screen supports three filter states controlled by the estado query parameter. When estado is omitted it defaults to 'activos'. HabitoRepository.obtener_habitos_por_usuario applies the filter at the query level:
@staticmethod
def obtener_habitos_por_usuario(usuario_id: int, estado: str = 'todos') -> List[Habito]:
    # Consulta moderna de SQLAlchemy 2.0
    consulta = db.select(Habito).where(Habito.usuario_id == usuario_id)

    # Agregamos filtros extra dependiendo de lo que pida la ruta
    if estado == 'activos':
        consulta = consulta.where(Habito.activo == True)
    elif estado == 'inactivos':
        consulta = consulta.where(Habito.activo == False)

    # .scalars().all() devuelve la lista de objetos listos para usar
    return db.session.execute(consulta).scalars().all()
Filter states:
estado valueSQL conditionUse case
activosactivo = TRUEDefault — habits currently being tracked
inactivosactivo = FALSEHabits that have been paused
todos(no filter)Full history of all habits

Editing a Habit

Route: GET /habitos/editar/<id>The route fetches the habit by ID and verifies ownership before rendering the edit form:
habito = HabitoRepository.obtener_habito_por_id(id_habito)
if not habito or habito.usuario_id != current_user.id:
    flash("Habito no encontrado o no tienes permiso para editarlo", "error")
    return redirect(url_for('habitos.mis_habitos'))
If the usuario_id on the habit does not match current_user.id, the request is rejected and the user is sent back to their habit list — no data is returned.

Deleting a Habit

Route: POST /habitos/eliminar/<id> Before deletion, the route performs the same ownership check as the edit flow. Only after the check passes does it call the repository:
@staticmethod
def eliminar_habito(id_habito: int) -> None:
    # Buscar el hábito en la BD
    habito = db.session.get(Habito, id_habito)

    # Si el hábito existe lo eliminamos
    if habito:
        db.session.delete(habito)
        db.session.commit()
Deleting a habit permanently removes all of its completion history. The Habito model defines its registros relationship with cascade="all, delete-orphan":
registros: Mapped[List["RegistroHabito"]] = relationship(
    "RegistroHabito",
    back_populates="habito",
    cascade="all, delete-orphan"
)
SQLAlchemy will issue DELETE statements for every RegistroHabito row tied to the habit before deleting the habit itself. This action cannot be undone.

Daily Completion Toggle

Route: POST /habitos/completar/<id> Each habit card on the dashboard includes a completion button. Tapping it calls this route, which toggles the habit’s completion state for today. HabitoRepository.marcar_completado_hoy checks whether a RegistroHabito already exists for today’s date and either creates or deletes it accordingly:
@staticmethod
def marcar_completado_hoy(id_habito: int) -> bool:
    hoy = date.today()  # ej. yyyy-mm-dd

    # Buscamos si ya existe un registro para hoy
    consulta = db.select(RegistroHabito).where(
        RegistroHabito.habito_id == id_habito,
        RegistroHabito.fecha == hoy
    )
    # Solo traemos 1 registro o None
    registro_existente = db.session.execute(consulta).scalar_one_or_none()

    # Obtenemos el hábito para poder modificar sus rachas
    habito = db.session.get(Habito, id_habito)

    # Si ya estaba completado
    if registro_existente:
        db.session.delete(registro_existente)
        # Si deshace la acción, le restamos 1 a su racha (evitando que baje de cero)
        habito.racha_actual = max(0, habito.racha_actual - 1)
        db.session.commit()
        return False  # Para avisar que acabamos de "apagar" o "desmarcar" el hábito.
    else:
        # Equivalente a: INSERT INTO registro_habitos (habito_id, fecha, completado) VALUES (...)
        nuevo_registro = RegistroHabito(habito_id=id_habito, fecha=hoy, completado=True)
        db.session.add(nuevo_registro)
        # Sumamos 1 a la racha
        habito.racha_actual += 1
        # Verificamos si rompió su récord histórico
        if habito.racha_actual > habito.mejor_racha:
            habito.mejor_racha = habito.racha_actual
        db.session.commit()
        return True
Streak logic:

Marking complete ✅

racha_actual is incremented by 1. If the new value exceeds mejor_racha, mejor_racha is updated to match — preserving the all-time record.

Unchecking ↩️

The RegistroHabito row for today is deleted and racha_actual is decremented by 1, with a floor of 0 so it never goes negative: max(0, racha_actual - 1).
The RegistroHabito table enforces a UniqueConstraint('habito_id', 'fecha', name='uq_habito_fecha_diaria'). This constraint is the database-level guarantee that a habit cannot be marked complete twice on the same day, even if two requests arrive simultaneously.

Active vs Inactive Habits

Every habit has an activo boolean column (default True). When a user marks a habit inactive — either via the edit form or by toggling the activo checkbox — the habit disappears from the dashboard’s daily view but remains in the database with its full completion history intact.

Active habits

Shown on the dashboard (/pagina_principal/inicio) and in mis_habitos?estado=activos. These are habits the user is currently tracking.

Inactive habits

Hidden from the daily dashboard. Accessible at mis_habitos?estado=inactivos. Can be reactivated at any time through the edit form — all historical RegistroHabito records are preserved.
Setting a habit to inactive is the recommended alternative to deleting it when you want to take a break without losing your streak history.

Build docs developers (and LLMs) love