Me enfoqué en la arquitectura del backend, en integrar las partes del equipo y en llevar el sistema a la nube con un despliegue que se actualiza solo con cada cambio.
El proyecto fue un trabajo de equipo: un compañero hizo el backend de auth, otro el frontend y otro la base de datos. Mi parte fue la que conecta todo — diseñar la estructura, integrar las piezas y armar el camino del código hasta producción.
Definí la estructura hexagonal del backend para que cada uno pudiera trabajar su parte sin pisarse.
Conecté el frontend con el backend: login con JWT y sincronización de datos reales.
Contenedoricé los servicios y armé el despliegue automático en GCP Cloud Run.
Detecté un merge que rompía el sistema y blindé el pipeline para que no vuelva a pasar.
Elegí esta arquitectura por una razón concreta: aislar la lógica de negocio de los detalles técnicos. Así la base de datos o el framework son piezas que se enchufan, no algo que contamina el código central.
Esto aplica el Principio de Inversión de Dependencias (la "D" de SOLID): el dominio define el contrato, la infraestructura lo cumple. Por eso puedo cambiar de SQLite a Supabase sin tocar la lógica.
Empaqueté cada servicio en su propia imagen de Docker. La app se lleva su entorno adentro (Python, dependencias, configuración), así corre igual en mi notebook que en la nube.
Un Dockerfile para el backend y otro para el frontend. Cada uno define cómo se
construye y arranca.
Un docker-compose.yml levanta los dos servicios juntos con un comando,
conectados en red y con la base persistente.
# Levanto todo el sistema con un solo comando
docker compose up --build
# Backend en :5001 · Frontend en :5000 · base persistente
Configuré dos flujos de trabajo (workflows) que se disparan solos con cada push a la
rama main. La idea: que pasar de "código nuevo" a "en producción" no dependa de que yo me acuerde
de hacer pasos a mano.
Corre los tests y verifica que la aplicación arranca. Si algo falla, frena ahí — no deja avanzar código roto.
Construye las imágenes, las sube al registro y las despliega en Cloud Run. Todo sin intervención manual.
Subo un cambio al repositorio.
GitHub corre los tests y el chequeo de arranque en un servidor limpio.
Se construyen las imágenes Docker etiquetadas con el hash del commit y se suben a Artifact Registry.
Se despliega la nueva versión. Si arranca sana, queda viva; si no, Cloud Run mantiene la anterior.
Para que GitHub pueda desplegar en mi proyecto de Google Cloud, necesita autenticarse. Lo resolví sin descargar ninguna clave secreta — usando federación de identidades.
Identity and Access Management: el sistema de Google Cloud que decide quién puede hacer qué. Creé una cuenta de servicio (una identidad no-humana) y le di solo los permisos justos: subir imágenes al registro y desplegar en Cloud Run. Principio de menor privilegio.
GitHub genera un token de identidad (OIDC) firmado. Configuré Google Cloud para confiar en ese token solo para mi repositorio. Google lo intercambia por un permiso temporal que actúa como la cuenta de servicio. No hay ninguna clave JSON que se pueda filtrar.
# Autenticación KEYLESS contra Google Cloud
- uses: google-github-actions/auth@v3
with:
workload_identity_provider: ${{ secrets.WIF_PROVIDER }}
service_account: ${{ secrets.WIF_SERVICE_ACCOUNT }}
token_format: access_token # token temporal, no una clave fija
Por qué importa: el método viejo era bajar un archivo de clave y guardarlo como secreto. Si se filtraba, te entraban a la nube. Con WIF no existe esa clave: el permiso dura minutos y solo se emite para mi repo.
Es el "depósito" de imágenes Docker dentro de Google Cloud. El pipeline construye cada imagen, la etiqueta con el hash del commit y la sube ahí. Cloud Run después tira de ese depósito para desplegar.
# Construyo y subo la imagen del backend, versionada por commit
docker build -f backend/Dockerfile -t $REGISTRY/$PROJECT/sistema-socios/backend:$SHA .
docker push $REGISTRY/$PROJECT/sistema-socios/backend:$SHA
Etiquetar con el hash del commit (:$SHA) me da
trazabilidad: cada imagen en la nube corresponde exactamente a una versión del código. Si algo
falla, sé qué commit se desplegó.
Es donde corren mis contenedores en producción. Le doy una imagen y él se encarga del resto: la ejecuta, la expone con HTTPS, y la escala según la demanda — incluso a cero cuando nadie la usa, así no pago de más.
No administro máquinas. Google maneja el sistema operativo, los parches y el escalado.
De 0 a N instancias según el tráfico. Paga por uso real.
El contenedor escucha el puerto que Cloud Run le indica por la variable PORT.
# Despliego el backend a Cloud Run
gcloud run deploy backend \
--image $REGISTRY/$PROJECT/sistema-socios/backend:$SHA \
--region us-central1 \
--platform managed \
--port 8080 \
--set-env-vars "SUPABASE_URL=...,SUPABASE_KEY=..."
Las claves (Supabase, la firma de los tokens, etc.) nunca van en el código. Hoy las guardo como GitHub Actions Secrets y se inyectan como variables de entorno al momento de desplegar.
Los secretos viven cifrados en la configuración del repositorio. El workflow los lee con
${{ secrets.X }} y se los pasa a Cloud Run con --set-env-vars. Nunca aparecen en
el código ni en los logs.
Google Cloud tiene un servicio dedicado: Secret Manager. Guarda secretos
versionados, con permisos por IAM y auditoría de quién los lee. Cloud Run los monta directo con
--set-secrets, sin pasar por variables de entorno en el deploy.
Por qué sería mejor: rotación de claves centralizada, control de acceso fino y registro de auditoría. Es la evolución natural de mi setup actual.