Manejo de errores y códigos de estado HTTP

Intermedio
Flask
Flask
Actualizado: 20/06/2025

¡Desbloquea el curso de Flask completo!

IA
Ejercicios
Certificado
Entrar

Mira la lección en vídeo

Accede al vídeo completo de esta lección y a más contenido exclusivo con el Plan Plus.

Desbloquear Plan Plus

Códigos de estado principales

Los códigos de estado HTTP son números de tres dígitos que el servidor envía al cliente para indicar el resultado de una petición. En Flask, estos códigos se organizan en cinco categorías principales, cada una con un propósito específico para comunicar el estado de las operaciones de tu API.

Códigos 2xx - Respuestas exitosas

Los códigos de la serie 200 indican que la petición se procesó correctamente. Son los más utilizados en APIs REST cuando las operaciones se completan sin problemas.

200 OK es el código más común y se usa cuando una operación se ejecuta correctamente:

from flask import Flask, jsonify

app = Flask(__name__)

@app.route('/usuarios/<int:user_id>')
def obtener_usuario(user_id):
    usuario = {'id': user_id, 'nombre': 'Juan', 'email': 'juan@email.com'}
    return jsonify(usuario), 200  # Código explícito

201 Created se utiliza específicamente cuando se crea un nuevo recurso:

@app.route('/usuarios', methods=['POST'])
def crear_usuario():
    # Lógica para crear usuario
    nuevo_usuario = {'id': 123, 'nombre': 'Ana', 'email': 'ana@email.com'}
    return jsonify(nuevo_usuario), 201

204 No Content es útil para operaciones que se completan exitosamente pero no devuelven datos:

@app.route('/usuarios/<int:user_id>', methods=['DELETE'])
def eliminar_usuario(user_id):
    # Lógica para eliminar usuario
    return '', 204  # Sin contenido en la respuesta

Códigos 4xx - Errores del cliente

Los códigos de la serie 400 indican que el problema está en la petición del cliente. Son fundamentales para proporcionar retroalimentación clara sobre errores de entrada.

400 Bad Request se usa cuando los datos enviados son incorrectos o están mal formateados:

from flask import request

@app.route('/usuarios', methods=['POST'])
def crear_usuario():
    data = request.get_json()
    
    if not data or 'email' not in data:
        return jsonify({'error': 'Email es requerido'}), 400
    
    # Continuar con la creación
    return jsonify({'mensaje': 'Usuario creado'}), 201

404 Not Found indica que el recurso solicitado no existe:

@app.route('/usuarios/<int:user_id>')
def obtener_usuario(user_id):
    # Simular búsqueda en base de datos
    usuarios = {1: 'Juan', 2: 'Ana'}
    
    if user_id not in usuarios:
        return jsonify({'error': 'Usuario no encontrado'}), 404
    
    return jsonify({'id': user_id, 'nombre': usuarios[user_id]}), 200

409 Conflict se utiliza cuando hay un conflicto con el estado actual del recurso:

@app.route('/usuarios', methods=['POST'])
def crear_usuario():
    data = request.get_json()
    email = data.get('email')
    
    # Simular verificación de email duplicado
    emails_existentes = ['juan@email.com', 'ana@email.com']
    
    if email in emails_existentes:
        return jsonify({'error': 'El email ya está registrado'}), 409
    
    return jsonify({'mensaje': 'Usuario creado'}), 201

Códigos 5xx - Errores del servidor

Los códigos de la serie 500 indican problemas internos del servidor. Aunque idealmente deberían evitarse, son importantes para manejar situaciones imprevistas.

500 Internal Server Error se usa para errores generales del servidor:

@app.route('/usuarios/<int:user_id>')
def obtener_usuario(user_id):
    try:
        # Operación que puede fallar
        resultado = operacion_compleja(user_id)
        return jsonify(resultado), 200
    except Exception as e:
        return jsonify({'error': 'Error interno del servidor'}), 500

503 Service Unavailable indica que el servicio no está disponible temporalmente:

@app.route('/usuarios')
def listar_usuarios():
    # Verificar si el servicio está disponible
    if not servicio_disponible():
        return jsonify({'error': 'Servicio temporalmente no disponible'}), 503
    
    return jsonify({'usuarios': []}), 200

Implementación práctica en Flask

Flask permite especificar códigos de estado de múltiples formas. La más directa es devolver una tupla con la respuesta y el código:

@app.route('/productos/<int:producto_id>', methods=['PUT'])
def actualizar_producto(producto_id):
    data = request.get_json()
    
    # Validar datos de entrada
    if not data:
        return jsonify({'error': 'Datos requeridos'}), 400
    
    # Verificar si el producto existe
    if not producto_existe(producto_id):
        return jsonify({'error': 'Producto no encontrado'}), 404
    
    # Actualizar producto
    producto_actualizado = actualizar_datos(producto_id, data)
    return jsonify(producto_actualizado), 200

También puedes usar el objeto Response de Flask para mayor control:

from flask import Response

@app.route('/health')
def health_check():
    return Response(
        response=jsonify({'status': 'healthy'}).data,
        status=200,
        mimetype='application/json'
    )

La consistencia en el uso de códigos de estado es crucial para que los clientes de tu API puedan manejar las respuestas de manera predecible. Cada código debe utilizarse según su propósito específico, evitando usar códigos genéricos cuando existe uno más apropiado para la situación.

Cuándo usar cada código

Guarda tu progreso

Inicia sesión para no perder tu progreso y accede a miles de tutoriales, ejercicios prácticos y nuestro asistente de IA.

Progreso guardado
Asistente IA
Ejercicios
Iniciar sesión gratis

Más de 25.000 desarrolladores ya confían en CertiDevs

La selección correcta del código de estado HTTP depende del contexto específico de cada operación en tu API. Cada situación requiere un análisis cuidadoso para elegir el código que mejor comunique el resultado al cliente.

Criterios para operaciones GET

En las consultas de datos, el código de estado debe reflejar si la operación de lectura fue exitosa y si se encontraron los datos solicitados.

Usa 200 OK cuando la consulta se ejecuta correctamente y devuelve datos:

@app.route('/productos')
def listar_productos():
    productos = obtener_todos_los_productos()
    # Siempre 200, incluso si la lista está vacía
    return jsonify({'productos': productos, 'total': len(productos)}), 200

Aplica 404 Not Found únicamente cuando el recurso específico no existe, no cuando una colección está vacía:

@app.route('/categorias/<int:categoria_id>/productos')
def productos_por_categoria(categoria_id):
    # Primero verificar si la categoría existe
    if not categoria_existe(categoria_id):
        return jsonify({'error': 'Categoría no encontrada'}), 404
    
    # Si existe pero no tiene productos, devolver 200 con lista vacía
    productos = obtener_productos_categoria(categoria_id)
    return jsonify({'productos': productos}), 200

Criterios para operaciones POST

Las operaciones de creación requieren distinguir entre diferentes tipos de éxito y fallo según el resultado de la operación.

Utiliza 201 Created exclusivamente cuando se crea un nuevo recurso:

@app.route('/pedidos', methods=['POST'])
def crear_pedido():
    data = request.get_json()
    
    # Validar datos requeridos
    if not data or 'cliente_id' not in data:
        return jsonify({'error': 'ID del cliente es obligatorio'}), 400
    
    # Verificar que el cliente existe
    if not cliente_existe(data['cliente_id']):
        return jsonify({'error': 'Cliente no encontrado'}), 404
    
    # Crear el pedido
    nuevo_pedido = crear_nuevo_pedido(data)
    return jsonify(nuevo_pedido), 201

Emplea 409 Conflict cuando intentas crear algo que ya existe o viola restricciones de unicidad:

@app.route('/usuarios', methods=['POST'])
def registrar_usuario():
    data = request.get_json()
    
    # Verificar email único
    if email_ya_registrado(data['email']):
        return jsonify({
            'error': 'Ya existe un usuario con este email',
            'codigo': 'EMAIL_DUPLICADO'
        }), 409
    
    return jsonify(crear_usuario(data)), 201

Criterios para operaciones PUT y PATCH

Las operaciones de actualización deben considerar si el recurso existe y si la modificación es válida.

Usa 200 OK para actualizaciones exitosas que devuelven el recurso modificado:

@app.route('/productos/<int:producto_id>', methods=['PUT'])
def actualizar_producto_completo(producto_id):
    data = request.get_json()
    
    if not producto_existe(producto_id):
        return jsonify({'error': 'Producto no encontrado'}), 404
    
    # Actualización completa del recurso
    producto_actualizado = reemplazar_producto(producto_id, data)
    return jsonify(producto_actualizado), 200

Aplica 204 No Content cuando la actualización es exitosa pero no necesitas devolver datos:

@app.route('/usuarios/<int:user_id>/configuracion', methods=['PATCH'])
def actualizar_configuracion(user_id):
    data = request.get_json()
    
    if not usuario_existe(user_id):
        return jsonify({'error': 'Usuario no encontrado'}), 404
    
    # Actualización parcial sin respuesta
    actualizar_configuracion_usuario(user_id, data)
    return '', 204

Criterios para operaciones DELETE

Las operaciones de eliminación requieren manejar tanto el éxito como los casos donde el recurso no existe.

Utiliza 204 No Content para eliminaciones exitosas:

@app.route('/comentarios/<int:comentario_id>', methods=['DELETE'])
def eliminar_comentario(comentario_id):
    if not comentario_existe(comentario_id):
        return jsonify({'error': 'Comentario no encontrado'}), 404
    
    eliminar_comentario_bd(comentario_id)
    return '', 204

Considera 200 OK si necesitas confirmar la eliminación con información adicional:

@app.route('/archivos/<int:archivo_id>', methods=['DELETE'])
def eliminar_archivo(archivo_id):
    archivo = obtener_archivo(archivo_id)
    
    if not archivo:
        return jsonify({'error': 'Archivo no encontrado'}), 404
    
    eliminar_archivo_sistema(archivo_id)
    return jsonify({
        'mensaje': 'Archivo eliminado correctamente',
        'nombre': archivo['nombre'],
        'fecha_eliminacion': datetime.now().isoformat()
    }), 200

Manejo de errores de validación

Los errores de validación requieren códigos específicos según el tipo de problema detectado.

Usa 400 Bad Request para errores de formato o datos faltantes:

@app.route('/facturas', methods=['POST'])
def crear_factura():
    data = request.get_json()
    
    # Validar estructura de datos
    campos_requeridos = ['cliente_id', 'fecha', 'items']
    campos_faltantes = [campo for campo in campos_requeridos if campo not in data]
    
    if campos_faltantes:
        return jsonify({
            'error': 'Campos requeridos faltantes',
            'campos': campos_faltantes
        }), 400
    
    # Validar formato de fecha
    try:
        datetime.fromisoformat(data['fecha'])
    except ValueError:
        return jsonify({'error': 'Formato de fecha inválido'}), 400
    
    return jsonify(procesar_factura(data)), 201

Aplica 422 Unprocessable Entity para datos con formato correcto pero lógicamente inválidos:

@app.route('/reservas', methods=['POST'])
def crear_reserva():
    data = request.get_json()
    
    fecha_inicio = datetime.fromisoformat(data['fecha_inicio'])
    fecha_fin = datetime.fromisoformat(data['fecha_fin'])
    
    # Datos bien formateados pero lógicamente incorrectos
    if fecha_inicio >= fecha_fin:
        return jsonify({
            'error': 'La fecha de inicio debe ser anterior a la fecha de fin'
        }), 422
    
    if fecha_inicio < datetime.now():
        return jsonify({
            'error': 'No se pueden crear reservas en el pasado'
        }), 422
    
    return jsonify(crear_nueva_reserva(data)), 201

Situaciones especiales

Algunas situaciones específicas requieren códigos menos comunes pero igualmente importantes.

Usa 429 Too Many Requests para implementar límites de velocidad:

from functools import wraps
from time import time

# Simulador simple de rate limiting
request_counts = {}

def rate_limit(max_requests=10, window=60):
    def decorator(f):
        @wraps(f)
        def decorated_function(*args, **kwargs):
            client_ip = request.remote_addr
            current_time = time()
            
            if client_ip not in request_counts:
                request_counts[client_ip] = []
            
            # Limpiar requests antiguos
            request_counts[client_ip] = [
                req_time for req_time in request_counts[client_ip]
                if current_time - req_time < window
            ]
            
            if len(request_counts[client_ip]) >= max_requests:
                return jsonify({
                    'error': 'Demasiadas peticiones',
                    'retry_after': window
                }), 429
            
            request_counts[client_ip].append(current_time)
            return f(*args, **kwargs)
        return decorated_function
    return decorator

@app.route('/api/buscar')
@rate_limit(max_requests=5, window=60)
def buscar():
    return jsonify({'resultados': []})

La coherencia en la aplicación de estos criterios es fundamental para crear una API predecible. Los clientes de tu API deben poder confiar en que cada código de estado significa exactamente lo mismo en todas las operaciones, facilitando el manejo de errores y la lógica de reintentos.

Aprendizajes de esta lección de Flask

  • Comprender las categorías principales de códigos de estado HTTP y su significado.
  • Aprender a implementar códigos de estado HTTP en respuestas de Flask.
  • Identificar cuándo usar códigos 2xx, 4xx y 5xx según el contexto de la operación.
  • Aplicar criterios específicos para operaciones GET, POST, PUT, PATCH y DELETE.
  • Manejar errores de validación y situaciones especiales como límites de peticiones (rate limiting).

Completa este curso de Flask y certifícate

Únete a nuestra plataforma de cursos de programación y accede a miles de tutoriales, ejercicios prácticos, proyectos reales y nuestro asistente de IA personalizado para acelerar tu aprendizaje.

Asistente IA

Resuelve dudas al instante

Ejercicios

Practica con proyectos reales

Certificados

Valida tus conocimientos

Más de 25.000 desarrolladores ya se han certificado con CertiDevs

⭐⭐⭐⭐⭐
4.9/5 valoración