Validaciones y constraints

Intermedio
Flask
Flask
Actualizado: 04/05/2026

Diagrama: tutorial-flask-model-validation

Validaciones de campo

Las validaciones de campo en SQLAlchemy permiten garantizar la integridad de los datos directamente en el modelo, antes de que lleguen a la base de datos. Estas validaciones se ejecutan automáticamente cuando intentamos crear o modificar registros, proporcionando una capa de seguridad adicional que complementa las restricciones de la base de datos.

El siguiente diagrama resume visualmente los conceptos clave introducidos en esta sección:

graph TD
    A[Validación SQLAlchemy] --> B[Restricciones declarativas]
    A --> C[validates decorator]
    A --> D[Eventos before_insert]
    B --> E[nullable False]
    B --> F[unique True]
    B --> G[CheckConstraint]
    C --> H[Función custom por columna]
    H --> I[ValueError si falla]
    D --> J[Antes de INSERT]
    D --> K[Antes de UPDATE]
    A --> L[Marshmallow capa serializa]

Validación de modelos Flask: restricciones y validadores

SQLAlchemy ofrece múltiples mecanismos para implementar validaciones, desde constraints básicos hasta validaciones personalizadas más complejas. La ventaja de implementar estas validaciones a nivel de modelo es que se aplican consistentemente independientemente de cómo se acceda a los datos.

Validaciones con el decorador validates

El decorador @validates es la forma más directa de implementar validaciones personalizadas en SQLAlchemy. Este decorador permite definir funciones que se ejecutan automáticamente cuando se asigna un valor a un campo específico.

from sqlalchemy import Column, Integer, String, DateTime
from sqlalchemy.orm import validates
from datetime import datetime, timezone
import re

class Usuario(db.Model):
    __tablename__ = 'usuarios'
    
    id = Column(Integer, primary_key=True)
    email = Column(String(120), nullable=False, unique=True)
    edad = Column(Integer)
    telefono = Column(String(15))
    fecha_registro = Column(DateTime, default=lambda: datetime.now(timezone.utc))
    
    @validates('email')
    def validar_email(self, key, email):
        patron = r'^[a-zA-Z0-9._%+-]+@[a-zA-Z0-9.-]+\.[a-zA-Z]{2,}$'
        if not re.match(patron, email):
            raise ValueError("El formato del email no es válido")
        return email.lower()
    
    @validates('edad')
    def validar_edad(self, key, edad):
        if edad is not None and (edad < 0 or edad > 120):
            raise ValueError("La edad debe estar entre 0 y 120 años")
        return edad

En este ejemplo, las funciones de validación reciben tres parámetros: self (la instancia del modelo), key (el nombre del campo) y el valor que se está asignando. La función debe retornar el valor validado o lanzar una excepción si la validación falla.

Validaciones múltiples y transformaciones

El decorador @validates también permite validar múltiples campos con una sola función y realizar transformaciones de datos durante la asignación:

class Producto(db.Model):
    __tablename__ = 'productos'
    
    id = Column(Integer, primary_key=True)
    nombre = Column(String(100), nullable=False)
    precio = Column(Integer)  # Precio en centavos
    codigo = Column(String(20), unique=True)
    
    @validates('nombre', 'codigo')
    def validar_texto(self, key, valor):
        if not valor or not valor.strip():
            raise ValueError(f"El campo {key} no puede estar vacío")
        
        # Transformación: convertir a mayúsculas para códigos
        if key == 'codigo':
            return valor.upper().strip()
        
        return valor.strip()
    
    @validates('precio')
    def validar_precio(self, key, precio):
        if precio is not None and precio <= 0:
            raise ValueError("El precio debe ser mayor que cero")
        return precio

Validaciones con hybrid_property

Los hybrid properties ofrecen una alternativa elegante para campos que requieren validaciones complejas o cálculos dinámicos:

from sqlalchemy.ext.hybrid import hybrid_property
from sqlalchemy import Column, String, Integer

class Empleado(db.Model):
    __tablename__ = 'empleados'
    
    id = Column(Integer, primary_key=True)
    _salario_base = Column('salario_base', Integer)  # Almacenado en centavos
    dni = Column(String(9), unique=True)
    
    @hybrid_property
    def salario_base(self):
        return self._salario_base / 100 if self._salario_base else 0
    
    @salario_base.setter
    def salario_base(self, valor):
        if valor < 0:
            raise ValueError("El salario no puede ser negativo")
        if valor > 1000000:  # 10,000 euros
            raise ValueError("El salario excede el límite máximo")
        self._salario_base = int(valor * 100)
    
    @validates('dni')
    def validar_dni(self, key, dni):
        if not dni or len(dni) != 9:
            raise ValueError("El DNI debe tener exactamente 9 caracteres")
        
        # Validación básica del formato DNI español
        numeros = dni[:8]
        letra = dni[8].upper()
        
        if not numeros.isdigit():
            raise ValueError("Los primeros 8 caracteres del DNI deben ser números")
        
        letras_validas = "TRWAGMYFPDXBNJZSQVHLCKE"
        letra_esperada = letras_validas[int(numeros) % 23]
        
        if letra != letra_esperada:
            raise ValueError("La letra del DNI no es correcta")
        
        return dni.upper()

Validaciones condicionales y dependientes

En ocasiones necesitamos validaciones que dependen del valor de otros campos o del estado del objeto:

from sqlalchemy import Column, String, Integer, Boolean, DateTime
from datetime import datetime, timedelta

class Reserva(db.Model):
    __tablename__ = 'reservas'
    
    id = Column(Integer, primary_key=True)
    fecha_inicio = Column(DateTime, nullable=False)
    fecha_fin = Column(DateTime, nullable=False)
    confirmada = Column(Boolean, default=False)
    precio_total = Column(Integer)
    descuento_aplicado = Column(Integer, default=0)
    
    @validates('fecha_fin')
    def validar_fecha_fin(self, key, fecha_fin):
        if self.fecha_inicio and fecha_fin <= self.fecha_inicio:
            raise ValueError("La fecha de fin debe ser posterior a la fecha de inicio")
        
        # No permitir reservas de más de 30 días
        if self.fecha_inicio and (fecha_fin - self.fecha_inicio).days > 30:
            raise ValueError("Las reservas no pueden exceder 30 días")
        
        return fecha_fin
    
    @validates('descuento_aplicado')
    def validar_descuento(self, key, descuento):
        if descuento < 0 or descuento > 100:
            raise ValueError("El descuento debe estar entre 0 y 100")
        
        # Solo permitir descuentos altos en reservas confirmadas
        if descuento > 50 and not self.confirmada:
            raise ValueError("Descuentos superiores al 50% requieren confirmación previa")
        
        return descuento

Manejo de errores en validaciones

Es importante capturar y manejar adecuadamente los errores de validación en nuestras rutas de Flask:

from flask import request, jsonify
from sqlalchemy.exc import IntegrityError

@app.route('/usuarios', methods=['POST'])
def crear_usuario():
    try:
        datos = request.get_json()
        
        usuario = Usuario(
            email=datos.get('email'),
            edad=datos.get('edad'),
            telefono=datos.get('telefono')
        )
        
        db.session.add(usuario)
        db.session.commit()
        
        return jsonify({
            'mensaje': 'Usuario creado exitosamente',
            'id': usuario.id
        }), 201
        
    except ValueError as e:
        db.session.rollback()
        return jsonify({'error': str(e)}), 400
        
    except IntegrityError as e:
        db.session.rollback()
        return jsonify({
            'error': 'Error de integridad en la base de datos',
            'detalle': 'Posible duplicado en campo único'
        }), 409

Las validaciones de campo proporcionan una capa sólida de protección que garantiza la calidad de los datos almacenados. Al combinar el decorador @validates con hybrid properties y un manejo adecuado de errores, podemos crear modelos que no solo almacenen datos, sino que también los validen y transformen según las reglas de negocio de nuestra aplicación.

Fuentes y referencias

Documentación oficial y recursos externos para profundizar en Flask

Documentación oficial de Flask
Alan Sastre - Autor del tutorial

Alan Sastre

Ingeniero de Software y formador, CEO en CertiDevs

Ingeniero de software especializado en Full Stack y en Inteligencia Artificial. Como CEO de CertiDevs, Flask es una de sus áreas de expertise. Con más de 15 años programando, 6K seguidores en LinkedIn y experiencia como formador, Alan se dedica a crear contenido educativo de calidad para desarrolladores de todos los niveles.

Más tutoriales de Flask

Explora más contenido relacionado con Flask y continúa aprendiendo con nuestros tutoriales gratuitos.

Aprendizajes de esta lección

Comprender la importancia de las validaciones de campo en SQLAlchemy para mantener la integridad de los datos. Aprender a utilizar el decorador @validates para implementar validaciones personalizadas en modelos. Conocer cómo validar múltiples campos y realizar transformaciones durante la asignación de valores. Entender el uso de hybrid_property para validaciones complejas y cálculos dinámicos. Saber manejar errores de validación y excepciones para asegurar un flujo robusto en aplicaciones Flask.