Módulo 5: Migración de Frameworks y Lenguajes

Proyecto del Módulo: Migración Flask→FastAPI

Proyecto del Módulo: Migración Flask→FastAPI

Descripción del proyecto

Este es el proyecto que consolida todo lo que aprendiste en el módulo: planificar una migración, ejecutarla endpoint por endpoint, escribir tests de equivalencia, y producir una app FastAPI que hace exactamente lo mismo que la original Flask. No es un ejercicio teórico — es una migración completa con código funcional.

Vas a recibir una app Flask con 5 endpoints, validación manual, y manejo de errores. Tu trabajo es migrarla a FastAPI step-by-step: un endpoint a la vez, con tests de equivalencia en cada paso. Al final, ambas apps deben producir respuestas idénticas para los mismos inputs.

Este proyecto conecta con el Módulo 8 (Proyecto Integrador) donde la migración de framework puede ser parte de la modernización completa de un proyecto legacy.


Objetivo del Proyecto

Migrar una app Flask completa a FastAPI, verificando equivalencia en cada paso.

Al completar:

  • ✅ Habrás diseñado un migration plan con phases
  • ✅ Habrás migrado 5 endpoints Flask→FastAPI
  • ✅ Habrás escrito tests de equivalencia para cada endpoint
  • ✅ Habrás usado Pydantic para reemplazar validación manual
  • ✅ Habrás documentado el proceso completo

Especificaciones Técnicas

App Flask a Migrar

Usa la siguiente app Flask (o crea una similar):

# flask_app.py
from flask import Flask, request, jsonify
from datetime import datetime

app = Flask(__name__)
products = {}
orders = {}
pid_counter = 1
oid_counter = 1

@app.route("/health")
def health():
    return jsonify({"status": "ok", "timestamp": datetime.now().isoformat()})

@app.route("/products", methods=["GET"])
def list_products():
    return jsonify(list(products.values()))

@app.route("/products", methods=["POST"])
def create_product():
    data = request.get_json()
    if not data.get("name") or len(data["name"]) < 2:
        return jsonify({"error": "Name must be 2+ characters"}), 400
    if not isinstance(data.get("price"), (int, float)) or data["price"] <= 0:
        return jsonify({"error": "Price must be positive number"}), 400
    
    global pid_counter
    product = {"id": pid_counter, "name": data["name"],
               "price": round(float(data["price"]), 2),
               "created_at": datetime.now().isoformat()}
    products[pid_counter] = product
    pid_counter += 1
    return jsonify(product), 201

@app.route("/products/<int:product_id>")
def get_product(product_id):
    product = products.get(product_id)
    if not product:
        return jsonify({"error": "Product not found"}), 404
    return jsonify(product)

@app.route("/orders", methods=["POST"])
def create_order():
    data = request.get_json()
    if not data.get("items") or not isinstance(data["items"], list):
        return jsonify({"error": "Items must be a non-empty list"}), 400
    
    total = 0
    order_items = []
    for item in data["items"]:
        pid = item.get("product_id")
        qty = item.get("quantity", 1)
        if pid not in products:
            return jsonify({"error": f"Product {pid} not found"}), 404
        if qty < 1:
            return jsonify({"error": "Quantity must be >= 1"}), 400
        product = products[pid]
        line_total = product["price"] * qty
        total += line_total
        order_items.append({"product_id": pid, "name": product["name"],
                           "quantity": qty, "line_total": round(line_total, 2)})
    
    global oid_counter
    order = {"id": oid_counter, "items": order_items,
             "total": round(total, 2), "status": "confirmed",
             "created_at": datetime.now().isoformat()}
    orders[oid_counter] = order
    oid_counter += 1
    return jsonify(order), 201

Setup

mkdir flask-to-fastapi && cd flask-to-fastapi
python -m venv venv && source venv/bin/activate
pip install flask fastapi uvicorn pydantic pytest httpx

Entregables

1. Migration Plan (MIGRATION_PLAN.md)

# Migration Plan

## Assessment
- [Inventario de endpoints con complejidad]

## Phases
- [Phase 1: Foundation]
- [Phase 2: Simple endpoints]
- [Phase 3: Complex endpoints]
- [Phase 4: Cleanup]

## Rollback Strategy
- [Plan B para cada phase]

2. App FastAPI Migrada (fastapi_app.py)

La app FastAPI completa con todos los endpoints migrados, Pydantic models, y error handling.

3. Tests de Equivalencia (tests/test_equivalence.py)

Tests parametrizados que verifican que Flask y FastAPI producen las mismas respuestas.

4. Migration Log (MIGRATION_LOG.md)

# Migration Log

## Endpoint 1: GET /health
- **Flask:** 3 líneas, sin lógica
- **FastAPI:** 3 líneas, directo
- **Diferencias:** Ninguna
- **Tests:** ✅ Equivalencia verificada

## Endpoint 2: ...
[Repetir para cada endpoint]

## Métricas
| Métrica | Valor |
|---------|-------|
| Endpoints migrados | 5/5 |
| Tests de equivalencia | X |
| Tiempo total | X min |
| Líneas Flask | X |
| Líneas FastAPI | X |

Criterios de Éxito

  • ✅ 5/5 endpoints migrados a FastAPI
  • ✅ Pydantic models reemplazan validación manual
  • ✅ Tests de equivalencia pasan para todos los endpoints
  • ✅ Tests cubren happy paths Y error cases
  • ✅ Migration plan documentado
  • ✅ Migration log con detalle por endpoint

Rúbrica de Evaluación (100 puntos)

Migración (40 puntos)

  • (8 pts) GET /health migrado correctamente
  • (8 pts) GET /products migrado
  • (8 pts) POST /products con Pydantic model
  • (8 pts) GET /products/{id} con HTTPException
  • (8 pts) POST /orders con Pydantic + lógica

Testing (30 puntos)

  • (10 pts) Tests de equivalencia para happy paths
  • (10 pts) Tests de equivalencia para error cases
  • (10 pts) Tests parametrizados donde aplique

Documentación (20 puntos)

  • (10 pts) Migration plan con phases y rollback
  • (10 pts) Migration log con detalles por endpoint

Proceso (10 puntos)

  • (5 pts) Migración incremental (no big bang)
  • (5 pts) Tests ejecutados después de cada endpoint

Extra Credit (+10 puntos)

  • (+3 pts) Strangler fig proxy implementado
  • (+3 pts) Tests de contrato adicionales
  • (+2 pts) Pydantic validators custom
  • (+2 pts) Response models para todos los endpoints

Errores Comunes

Error 1: Migrar todo de golpe sin tests entre pasos

Migra un endpoint, ejecuta tests, migra el siguiente. No 5 de golpe.

Error 2: Cambiar la lógica de negocio durante la migración

El total se calcula igual. Las validaciones son las mismas. Solo cambia el framework.

Error 3: No manejar la diferencia 400 vs 422

FastAPI/Pydantic retorna 422 para validation errors. Decide si lo aceptas o lo cambias a 400.

Error 4: Olvidar status_code=201 en POST

Flask: return jsonify(data), 201. FastAPI: @app.post("/path", status_code=201).

Error 5: No comparar estructura, solo status code

Un test que solo verifica status code no detecta si el body cambió.


¿Qué Hacer si Te Atoras?

Si Pydantic rechaza un payload que Flask aceptaba:
  → Revisa los tipos en el modelo (Optional, Union)
  → Probablemente Flask era demasiado permisivo (eso es BIEN)
  → Decide: relajar Pydantic, o documentar el cambio en MIGRATION_LOG

Si los tests de equivalencia fallan en error cases:
  → Es esperado: Flask=400, FastAPI=422 por defecto
  → Compara la "clase" de error (4xx), no el código exacto
  → Si necesitas idempotencia exacta, customiza el exception handler

Si la lógica del cálculo de orden difiere por 1 centavo:
  → Diferencia de redondeo: round() vs Decimal
  → Mantén la misma técnica de Flask en FastAPI
  → No mezcles float y Decimal en el mismo flujo

Si Claude Code genera código FastAPI que no compila:
  → Probablemente version mismatch (Pydantic v1 vs v2)
  → Especifica Pydantic v2 explícitamente en el prompt
  → Pin la versión en requirements.txt: pydantic>=2.0,<3.0

Evidencia de Éxito (Auto-Verificación)

Antes de declarar el proyecto completo, valida que cumples estos checkpoints:

Funcionalidad

  • ✅ Los 5 endpoints responden con la misma estructura que Flask para inputs válidos
  • ✅ POST /products con {"name": "ab", "price": 10.5} retorna 201 con el producto
  • ✅ POST /orders con items inválidos retorna 4xx (no 500)
  • ✅ GET /products/9999 retorna 404 con mensaje legible

Equivalencia

  • ✅ Tests parametrizados cubren al menos 3 endpoints en GET y 2 en POST
  • ✅ Para cada endpoint, hay al menos 1 test de error case (no solo happy path)
  • ✅ La diferencia 400 vs 422 está documentada en MIGRATION_LOG (no es bug)

Documentación

  • ✅ MIGRATION_PLAN.md tiene phases con criterio de "done" por phase
  • ✅ MIGRATION_LOG.md documenta cada endpoint con diferencias y decisiones
  • ✅ Las métricas finales están en MIGRATION_LOG (líneas, tiempo, tests)

Proceso

  • ✅ Hay al menos 5 commits (uno por endpoint migrado, mínimo)
  • ✅ Cada commit tiene tests verdes en su momento

Si los 12 puntos están en su lugar, el proyecto está al nivel del bootcamp profesional. Si dudas en alguno, revisa la cápsula correspondiente antes de marcar el proyecto como completo.


Recursos para el Proyecto

  1. FastAPI Tutorial - Tutorial oficial paso a paso
  2. Pydantic v2 - Documentación de Pydantic
  3. pytest - Framework de testing
  4. httpx - HTTP client para tests async
  5. FastAPI TestClient - Testing de FastAPI
  6. FastAPI vs Flask Comparison - Tabla oficial de diferencias

Conexión con Siguiente Módulo

El Módulo 6: Context Management resuelve el problema que emerge cuando el proyecto es grande: ¿cómo manejas 50K+ líneas con el context window de Claude Code? Las técnicas de migración que aprendiste aquí funcionan para proyectos pequeños. El Módulo 6 te da las herramientas para escalar a proyectos reales — y el Módulo 8 (Proyecto Integrador) las aplica todas juntas en una migración de proyecto legacy completo.