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
- FastAPI Tutorial - Tutorial oficial paso a paso
- Pydantic v2 - Documentación de Pydantic
- pytest - Framework de testing
- httpx - HTTP client para tests async
- FastAPI TestClient - Testing de FastAPI
- 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.