Arquitectura de APIs para Flujos Multi-Nivel: Caso de Estudio con Flask y PostgreSQL

Arquitectura de APIs para Flujos Multi-Nivel: Caso de Estudio con Flask y PostgreSQL

Arquitectura de APIs para Flujos Multi-Nivel: Caso de Estudio con Flask y PostgreSQL

Introducción

Cuando desarrollamos aplicaciones que requieren que los usuarios naveguen por jerarquías de información (ciudad → sucursal → empleado, o categoría → subcategoría → producto), enfrentamos un desafío arquitectónico fundamental: ¿dónde colocamos la lógica de filtrado y navegación? La respuesta correcta puede significar la diferencia entre un sistema mantenible y uno que se convierte en un laberinto de código duplicado.

En este artículo, documentamos la implementación de una arquitectura backend completa para un sistema de asistencia que maneja 26 ciudades, múltiples sucursales y 78 empleados, demostrando cómo un diseño de API bien estructurado simplifica drásticamente la lógica del cliente.

El Problema: Flujos de Selección Jerárquicos

Imagina un sistema donde los usuarios deben:

  1. Seleccionar una ciudad de una lista
  2. Elegir una sucursal específica dentro de esa ciudad
  3. Identificarse como empleado de esa sucursal

La solución naive sería enviar toda la información al cliente y filtrar localmente. Con 25 sucursales y 78 empleados, esto podría parecer manejable. Pero consideremos:

  • Mantenibilidad: La lógica de filtrado existe en múltiples lugares (bot de Telegram, interfaz web, app móvil)
  • Seguridad: El cliente recibe información de todas las sucursales, incluso las que no necesita ver
  • Performance: Con crecimiento exponencial de datos, el payload inicial se vuelve prohibitivo

La Solución: Endpoints Especializados por Nivel

Nuestra arquitectura implementa tres endpoints REST que mapean directamente a cada nivel de la jerarquía:

1. Endpoint de Ciudades

GET /api/cities
X-API-Key: 

Este endpoint retorna exclusivamente las ciudades con sucursales activas:

{
"cities": [
"Altamira",
"Cd. Madero",
"Cd. Mante",
"Tampico",
"Villahermosa"
]
}
Optimización clave: La query SQL usa `DISTINCT` y filtra por `active = true`, garantizando que el usuario nunca vea opciones inválidas:
rows = db.execute(text(
"SELECT DISTINCT city FROM branches WHERE active = true ORDER BY city"
)).fetchall()

2. Endpoint de Sucursales por Ciudad

GET /api/branches/Tampico
X-API-Key: 

Una vez seleccionada la ciudad, el cliente solicita solo las sucursales relevantes:

{
"branches": [
{"id": 16, "name": "TAMPICO"},
{"id": 17, "name": "TAMPICO POPULAR"},
{"id": 18, "name": "TAMPICO NORTE"},
{"id": 19, "name": "TAMPICO SUR"},
{"id": 20, "name": "TAMPICO CENTRO"}
]
}
Por qué importa: En lugar de enviar las 25 sucursales y filtrar en el cliente, Tampico recibe exactamente 5 registros. Esto escala linealmente incluso con miles de ubicaciones.

3. Endpoint de Empleados por Sucursal

GET /api/employees/branch/21
X-API-Key: 

Finalmente, el usuario ve solo los empleados de su sucursal específica:

{
"employees": [
{"id": 53, "name": "ANA KAREN MEZA GONZALEZ"},
{"id": 59, "name": "ANA ROSA GOMEZ PEREZ"},
...
]
}
Ejemplo real: La sucursal 21 tiene 10 empleados. El cliente recibe 10 registros, no 78.

Optimizaciones de Base de Datos

Los endpoints son solo la mitad de la historia. Sin índices apropiados, estas queries se degradarían rápidamente:

-- Índice para el endpoint de sucursales
CREATE INDEX IF NOT EXISTS idx_branches_city
ON branches(city) WHERE active = true;
-- Índice para el endpoint de empleados
CREATE INDEX IF NOT EXISTS idx_employees_branch
ON employees(branch_id) WHERE active = true;

Estos índices parciales (con cláusula `WHERE`) son más pequeños y rápidos que índices completos, porque excluyen registros inactivos que nunca se consultarán.

Gestión de Estado

Para mantener el contexto del usuario a través de la navegación, agregamos:

ALTER TABLE user_states ADD COLUMN IF NOT EXISTS employee_id INTEGER;

Esto permite que el sistema recuerde no solo la sucursal seleccionada (`branch_id`) sino también el empleado específico (`employee_id`), crucial para flujos de registro de asistencia.

Implementación en Flask

La implementación en Flask es elegante y directa. Aquí está el endpoint de ciudades completo:

from sqlalchemy import text
@app.route('/api/cities', methods=['GET'])
@require_api_key
def get_cities():
"""Get distinct cities with active branches"""
db = get_db()
rows = db.execute(text(
"SELECT DISTINCT city FROM branches WHERE active = true ORDER BY city"
)).fetchall()
db.close()
return jsonify({"cities": [r[0] for r in rows]})
Patrón de seguridad: El decorador `@require_api_key` valida el header `X-API-Key` antes de ejecutar cualquier lógica, implementando autenticación a nivel de endpoint.

Beneficios Medibles

Esta arquitectura entrega resultados tangibles:

  1. Reducción de payload: El cliente que antes recibiría ~78 empleados ahora recibe ~10 (promedio), una reducción del 87%
  2. Simplicidad del cliente: El workflow de Telegram es un simple loop de "solicitar → mostrar → capturar selección", sin lógica de filtrado
  3. Escalabilidad: Agregar 100 sucursales nuevas no impacta el tiempo de respuesta para un usuario en Tampico
  4. Seguridad: Un empleado de Tampico nunca ve datos de Villahermosa, incluso si inspecciona el tráfico de red

Integración con Sistemas Externos

Estos endpoints se integran con:

  • n8n workflows: Mediante nodos HTTP Request con autenticación por header
  • Bots de Telegram: Usando inline keyboards dinámicos generados desde las respuestas JSON
  • Interfaces web: Mediante fetch/axios con el API key en headers

La consistencia del formato JSON garantiza que cualquier cliente nuevo puede integrarse en minutos.

Conclusión

La arquitectura de APIs REST no se trata solo de exponer datos, sino de diseñar contratos que simplifican la vida del consumidor. Al crear endpoints que mapean directamente a los flujos de usuario (ciudades → sucursales → empleados), logramos:

  • Backend como fuente única de verdad: La lógica de filtrado vive en un solo lugar
  • Performance predecible: Queries optimizadas con índices específicos
  • Escalabilidad horizontal: Agregar nuevos clientes (apps, bots) no requiere duplicar lógica

Esta implementación demuestra que con 26 ciudades, 25 sucursales y 78 empleados, un diseño cuidadoso de APIs puede crear una experiencia fluida tanto para desarrolladores como para usuarios finales. El código completo está en producción, sirviendo requests reales con tiempos de respuesta por debajo de 100ms.

Lección clave: Invierte tiempo en diseñar tus endpoints pensando en el flujo del usuario, no solo en la estructura de tu base de datos. Los beneficios se multiplican con cada cliente que integres.
Regresar al blog

Deja un comentario