Ir al contenido

Checklist de despliegue

Esta página es la que se recorre cada vez que un cambio va a producción.

Es la compañera de Desplegar, paso a paso, y la división es esta:

Página Contesta
Desplegar, paso a paso Cómo se despliega: los tres casos de migración, la cadena de Supabase, el rollback
Esta Qué verifico en cada ambiente, en qué orden, y cómo protejo los datos

Lo que cambió, y por qué esta página existe

Sección titulada «Lo que cambió, y por qué esta página existe»

Producción dejó de ser un ambiente de prueba. Tiene clientes reales: nombres, teléfonos y direcciones de gente de Campo de la Cruz que confió esos datos en el mostrador.

Eso cambia el precio de un error. Antes, romper producción costaba una tarde. Ahora puede costar información que no se puede volver a pedir — nadie va a recorrer el pueblo preguntando otra vez.

Cinco fases. Cada una tiene una salida verificable: si no la ves, no seguís.

Fase Dónde Prueba de que pasó
0 Tu máquina local Todo lo que corre CI, en verde
1 El preview Vercel + Railway staging El flujo nuevo, hecho a mano
2 El respaldo Supabase → tu disco ✓ respaldo verificado ... N tablas
3 El despliegue GitHub → Railway + Vercel El caso A, B o C
4 La comprobación producción Una operación real, hecha por vos

Correr localmente todo lo que corre CI. No los tests solamente: todo. Un contrato de endpoint que cambia rompe la colección de Bruno aunque los tests unitarios estén verdes.

Ventana de terminal
cd ~/code/aquazaku/api && pnpm db:migrate
Ventana de terminal
cd ~/code/aquazaku/api && pnpm db:migrate:test

Son dos comandos porque son dos bases. Olvidarse del segundo cuesta media hora buscando un bug que no existe: los tests fallan por columnas inexistentes y parece del código.

El migrador anuncia el destino antes de tocar nada:

→ migrando aquazaku@localhost/aquazaku_test → schema "public"
✓ migraciones aplicadas
Ventana de terminal
cd ~/code/aquazaku/api && pnpm typecheck
Ventana de terminal
cd ~/code/aquazaku/api && pnpm test:coverage

Esos son exactamente los dos pasos del workflow, más las migraciones de arriba. Medido hoy, api pasa con holgura:

Real Umbral
Statements 91.25% —
Branches 81.45% 65%
Functions 94.60% 70%
Lines 93.12% 70%
Ventana de terminal
cd ~/code/aquazaku/web && pnpm typecheck
Ventana de terminal
cd ~/code/aquazaku/web && pnpm lint
Ventana de terminal
cd ~/code/aquazaku/web && pnpm test:coverage

El lint de web sí es ESLint de verdad, y ahí vive la regla que hace cumplir el patrón BFF: que el browser nunca le hable directo a api/ (ADR-0002). No te lo saltes.

Si cambiaste la forma de una petición o de una respuesta, la colección se rompe aunque todo lo demás esté verde. api-ci la corre en cada push; correla antes vos.

Necesita dos condiciones que la base de desarrollo no cumple —estar vacía y tener un solo administrador—, así que va contra la base de tests, en el 3002. Está la configuración api-pruebas en .claude/launch.json.


Un push a una PR despliega dos cosas más:

Plataforma Qué
Vercel La URL xxx-git-<rama>-aquazaku.vercel.app
Railway staging La api del preview, contra el schema preview

Acá es donde se usa el cambio con las manos. No es opcional y no lo reemplaza ningún test: la suite verde nunca dijo nada sobre lo que se ve ni sobre lo que se siente al usarlo.

Qué mirar:

  • El flujo nuevo, de punta a punta, como lo haría quien atiende.
  • El flujo viejo que está más cerca del cambio. Ahí viven las regresiones.
  • La consola del navegador. Un error rojo que nadie mira es un bug que se despliega.

Antes de cualquier migración contra producción. Sin excepciones, y menos ahora que hay clientes cargados.

Ventana de terminal
cd ~/code/aquazaku/api
DATABASE_MIGRATION_URL='<cadena de Supabase>' pnpm db:respaldo

Lo que tiene que imprimir:

→ respaldando postgres.<ref>@aws-0-us-east-1.pooler.supabase.com/postgres
✓ respaldo verificado de ... — 29 tablas · N MB
respaldos/aquazaku-<etiqueta>-<sello>.sql.gz

Tres cosas de esa salida:

  1. La primera línea es el destino. Leela. Si dice otra cosa que Supabase, el respaldo no sirve para lo que lo pediste.
  2. «verificado» no es adorno: el script compara el volcado contra la lista real de tablas del schema. Un pg_dump que devuelve un archivo vacío también termina en 0, y esa verificación es lo que lo atrapa.
  3. El archivo queda en api/respaldos/, que está en .gitignore. Ahí adentro hay datos de personas reales: nunca lo subas a un repo, ni lo pases por chat, ni lo adjuntes a un ticket.

El propio script lo imprime al terminar:

Ventana de terminal
gunzip -c respaldos/<archivo>.sql.gz | psql "$DATABASE_MIGRATION_URL"

Sobre una base vacía. Restaurar encima de una base con datos no es restaurar: es mezclar.


Acá se cruza a Desplegar, paso a paso, que tiene los tres casos con sus comandos. El resumen para decidir cuál:

¿Trae migración? ¿Qué hace? Orden
No — Mergear. Listo
Sí Agrega algo que el código nuevo todavía no usa Mergear → migrar
Sí Agrega algo que el código nuevo necesita para funcionar Migrar → mergear
Sí Renombra o convierte Migrar → mergear
Sí Rompe el código viejo — una columna NOT NULL sin default, un CHECK nuevo Migrar → mergear, y lo más pegado posible

El orden de los tres últimos está invertido a propósito, y no es preferencia: el otro orden tumba la planta.

Cuando la respuesta es que no, api igual se puede mergear antes de migrar —nadie llama todavía al endpoint nuevo— pero web no, porque es quien lo llama. El orden que sale de ahí:

  1. Mergear api
  2. Respaldo y migración
  3. Mergear web

Las migraciones no corren solas. El startCommand de producción está vacío a propósito (ADR-0011): el container solo levanta el servidor. Migrar es un acto separado y deliberado que hacés vos.


Ventana de terminal
curl -s https://api.aquazaku.com/health
{"status":"ok","service":"aquazaku-api","base":"ok","desdeHaceMs":19608}

Si aparece "esquema":"pendientes" con una lista en faltan, el código desplegado le está pidiendo a la base algo que no tiene.

4.2 · La comprobación que ninguna consola reemplaza

Sección titulada «4.2 · La comprobación que ninguna consola reemplaza»

Entrá a app.aquazaku.com y hacé una operación real: registrá un cliente, cargá una venta, lo que el cambio haya tocado.

Casi todos los defectos serios de este proyecto aparecieron al usar el sistema, no al leerlo ni al testearlo. /health en verde solo dice que el servidor está vivo.


Cinco reglas. Cada una está acá porque el costo de romperla no se puede deshacer.

El seed crea un usuario con acceso total y siembra datos de demostración. Tiene dos frenos —AQUAZAKU_ENV y SEED_CONFIRM=yes— justamente porque correrlo contra la planta sería un desastre silencioso.

Si alguna vez tenés que pasar SEED_CONFIRM=yes, parate y preguntate qué estás haciendo.

2 · Nunca cambies AQUAZAKU_ENV en el servicio de producción

Sección titulada «2 · Nunca cambies AQUAZAKU_ENV en el servicio de producción»

Esa variable decide qué schema lee la aplicación:

Valor Schema Servicio
production public producción
preview preview staging

Si producción quedara en preview, la app leería un schema sin usuarios y sin clientes. /health seguiría diciendo ok —preview tiene las mismas tablas— y el login diría «Credenciales inválidas» con la contraseña correcta.

Se ve como un problema de cuentas y es un problema de configuración.

3 · Nunca migres sin el respaldo de la Fase 2

Sección titulada «3 · Nunca migres sin el respaldo de la Fase 2»

Revertir el código no deshace una migración. Si ya corrió, hay que escribir otra que la deshaga explícitamente. Por eso el respaldo no es ceremonia: es la única vuelta atrás que existe para los datos.

Ese atajo corre tsx --env-file-if-exists=.env.produccion.local. Si ese archivo quedó apuntando a otro lado —pasa— migrás la base equivocada y el comando te dice «✓ migraciones aplicadas» igual.

La forma explícita no depende de ningún archivo:

Ventana de terminal
DATABASE_MIGRATION_URL='...' pnpm db:migrate

5 · Nunca pegues la cadena de conexión en un chat, un log o una captura

Sección titulada «5 · Nunca pegues la cadena de conexión en un chat, un log o una captura»

La cadena incluye la contraseña del rol postgres, que es dueño de la base: lee todo, borra todo, y puede saltarse los REVOKE que protegen el audit_log.

Por eso el migrador y el respaldo imprimen el destino sin la contraseña: esas líneas terminan en logs y en capturas de pantalla, y se escribieron sabiéndolo.


Síntoma Qué pasó Qué hacer
/health dice faltan: [...] Se mergeó sin migrar Correr la migración
500 en todas las pantallas de clientes Casi siempre el schema atrasado Mirar /health
«Credenciales inválidas» con la clave correcta Puede ser AQUAZAKU_ENV mal puesto Regla 2
ENOTFOUND db.<ref>.supabase.co Es la conexión directa, que es IPv6 Usar la del pooler
El migrador anuncia localhost La cadena no es la de producción Ctrl+C
El preview no acepta ningún usuario La api de staging no arrancó, o falta el seed Log del deploy en Railway
CI en rojo pero GitHub deja mergear main no tiene protección de rama Decidirlo aparte

Un solo administrador es un punto único de falla

Sección titulada «Un solo administrador es un punto único de falla»

Hoy producción tiene un usuario. Si esa cuenta se pierde —contraseña olvidada sin acceso al correo, cuenta desactivada por error— la planta queda afuera de su propio sistema.

Crear un segundo administrador es la clase de tarea que parece opcional hasta el día que no lo es.