Ir al contenido

Entorno local de desarrollo

Todo lo que necesitás instalado antes de escribir la primera línea de api/ o web/. Si el entorno no está, la Task 1 del plan de M0 falla en el primer paso.

Herramienta Versión Para qué
Node.js 22 LTS Runtime de api/ y web/
pnpm 11.x Gestor de paquetes de ambos repos
PostgreSQL 17.x Base de datos (dev + test)
Mailpit 1.30+ Servidor SMTP falso para probar emails en dev
Bruno CLI (bru) 4.x Correr la colección de API desde terminal y CI
Docker opcional Solo si preferís Postgres en contenedor
Ventana de terminal
# Base de datos + cliente psql
brew install postgresql@17
brew services start postgresql@17
# Servidor SMTP falso para dev (reemplaza a MailHog, sin mantenimiento desde 2020)
brew install mailpit
brew services start mailpit
# Cliente de API para exploración manual y CI
npm install -g @usebruno/cli

Esto no es ceremonia: es lo que hace que audit_log sea de verdad inmutable.

Rol Quién lo usa Puede
aquazaku drizzle-kit y el runner de migraciones Es dueño de las tablas. DDL completo
aquazaku_app el servidor de api/ en runtime Leer y escribir datos. Sobre audit_log, solo SELECT e INSERT

Si la aplicación se conectara como dueña, un bug o una inyección podrían correr ALTER TABLE audit_log DISABLE TRIGGER y después borrar la bitácora. Con un rol que no es dueño, no puede — ni siquiera teniendo control del proceso.

Los triggers frenan a todo el mundo, incluido el dueño; los permisos frenan a la aplicación aunque alguien desactive los triggers. Cada capa tapa el hueco de la otra. Ver ADR-0004.

Ventana de terminal
# Rol dueño: crea y altera tablas
psql -d postgres -c "CREATE ROLE aquazaku LOGIN PASSWORD 'aquazaku' CREATEDB;"
# Rol de la aplicación: sin CREATEDB, sin CREATEROLE, sin superuser
psql -d postgres -c "CREATE ROLE aquazaku_app LOGIN PASSWORD 'aquazaku_app';"
# Una base para desarrollo y otra que los tests truncan entre suites
createdb -O aquazaku aquazaku_dev
createdb -O aquazaku aquazaku_test
for db in aquazaku_dev aquazaku_test; do
psql -d postgres -c "GRANT CONNECT ON DATABASE $db TO aquazaku_app;"
psql -d "$db" -c 'GRANT USAGE ON SCHEMA public TO aquazaku_app;'
done

Los roles son objetos de cluster, no de base: por eso se crean acá y no en una migración. Los GRANT sobre tablas sí viven en la migración 0001_audit_append_only, porque dependen del schema.

Ventana de terminal
cd api && pnpm db:migrate && pnpm db:migrate:test

La migración crea la extensión citext sola — users.email la necesita para que el login sea case-insensitive sin depender de que cada query recuerde un LOWER(). No hace falta crearla a mano.

Servicio URL / DSN
Postgres — app, dev postgres://aquazaku_app:aquazaku_app@localhost:5432/aquazaku_dev
Postgres — migraciones, dev postgres://aquazaku:aquazaku@localhost:5432/aquazaku_dev
Postgres — app, test postgres://aquazaku_app:aquazaku_app@localhost:5432/aquazaku_test
Postgres — migraciones, test postgres://aquazaku:aquazaku@localhost:5432/aquazaku_test
Mailpit SMTP smtp://localhost:1025
Mailpit web UI http://localhost:8025
api/ http://localhost:3001
web/ http://localhost:3000

En dev los emails de recuperación de contraseña no salen a internet: van a Mailpit y se leen en su UI web. Resend solo se usa en producción.

Ventana de terminal
pg_isready
psql -h localhost -U aquazaku -d aquazaku_dev -c '\dx'
curl -s -o /dev/null -w '%{http_code}\n' http://localhost:8025
bru --version
node -v && pnpm -v

Esperado: accepting connections, la lista de extensiones con citext y pgcrypto, 200, 4.x, v22.x, 11.x.

Con eso la máquina está lista. De acá en adelante, cada vez que te sientes a trabajar: Arrancar a trabajar.

Postgres y Mailpit se instalaron con brew services, o sea que Homebrew les creó un LaunchAgent: arrancan solos cuando iniciás sesión en la Mac y sobreviven a un reinicio.

Fue una decisión, no un accidente. Casi toda task de M0 en adelante toca la base —migrations, seeds, tests de integración— y un servicio que hay que prender a mano es un servicio que un día olvidás prender y te comés diez minutos debuggeando un ECONNREFUSED que no era un bug.

Ventana de terminal
brew services list

Buscá postgresql@17 y mailpit en started.

Ventana de terminal
brew services start postgresql@17
Ventana de terminal
brew services stop postgresql@17
Ventana de terminal
brew services restart postgresql@17

Lo mismo con mailpit. stop además desactiva el LaunchAgent: el servicio deja de arrancar solo hasta que hagas start de nuevo.

Correr uno sin dejarlo instalado como servicio

Sección titulada «Correr uno sin dejarlo instalado como servicio»

Útil si querés el proceso en primer plano, ver sus logs en vivo, o simplemente no dejar nada corriendo de fondo:

Ventana de terminal
LC_ALL="en_US.UTF-8" /usr/local/opt/postgresql@17/bin/postgres -D /usr/local/var/postgresql@17
Ventana de terminal
/usr/local/opt/mailpit/bin/mailpit

Se cortan con Ctrl+C y no queda nada arrancando al iniciar sesión.

Puerto Quién Vive
5432 Postgres permanente (LaunchAgent)
1025 Mailpit — SMTP permanente (LaunchAgent)
8025 Mailpit — UI web permanente (LaunchAgent)
3001 api/ (pnpm dev) solo mientras lo corrés
3000 web/ (pnpm dev) solo mientras lo corrés

Los tres primeros están ocupados siempre y está bien. Los dos últimos deberían estar libres cuando no estás desarrollando: si aparecen ocupados, quedó un dev server colgado de una sesión anterior.

Ventana de terminal
for p in 3000 3001 5432 1025 8025; do printf '%-6s %s\n' "$p" "$(lsof -nP -iTCP:$p -sTCP:LISTEN 2>/dev/null | awk 'NR==2 {print $1" (pid "$2")"; f=1} END {if (!f) print "libre"}')"; done

Salida esperada con todo en orden y sin desarrollar:

3000 libre
3001 libre
5432 postgres (pid 33676)
1025 mailpit (pid 33762)
8025 mailpit (pid 33762)
Ventana de terminal
lsof -nP -iTCP:3000 -sTCP:LISTEN -t | xargs kill

Cambiá 3000 por 3001 para api/. Nunca apliques esto a 5432, 1025 ni 8025: esos son servicios gestionados, y se paran con brew services stop, que además desregistra el LaunchAgent.

Las versiones de npm se fijan en el package.json de cada repo, no acá. El criterio del proyecto es stable actual al momento de arrancar el módulo, no la última versión de cada semana: se actualiza deliberadamente entre módulos, nunca en medio de uno.

Ver ADR-0001 para el stack y su justificación.