01 — Infra técnica

Índice de la página
  1. 01Cliente: Martín Rieznik / LevantArte
  2. Área: DevOps / Backend / Plataforma
  3. 020. Definición de "done" para esta checklist
  4. 031. Pre-requisitos
  5. 042. Tareas
  6. 2.1 DNS + dominio paraguas
  7. 2.2 Repo tooaudience-paraguas
  8. 2.3 Supabase project
  9. 2.4 Railway project (workers + cron)
  10. 2.5 Env vars + secrets
  11. 2.6 Naming convention multi-tenant
  12. 2.7 Validación end-to-end
  13. 053. Variables y posibilidades a anticipar
  14. 064. Multi-tenant: cómo se replica al cliente #2
  15. 075. Recursos y archivos relacionados
  16. 086. Notas y aprendizajes (post-mortem)
  17. 09CHANGELOG

Cliente: Martín Rieznik / LevantArte

Área: DevOps / Backend / Plataforma

Provisionar y dejar lista toda la infraestructura base del sistema multi-tenant TooAudience: dominio deacademia.com apuntando a Vercel, repo tooaudience-paraguas, proyecto Supabase con schema completo + RLS, proyecto Railway con workers + cron, env vars y secrets organizados, y naming convention multi-tenant validada con grep. Sin esta checklist verde, nada del resto se sostiene.

Última actualización: 2026-05-05
Responsable principal (R): Eric
Aprobador (A): Jesús
Deadline: 2026-05-18
Depende de:
Bloquea a: 02 (Landings), 10 (Bot WA), 11 (WA API), 12 (Grupos WA), 13 (Pixel & Tracking), 16 (Pasarelas), 17 (Transferencia)


0. Definición de "done" para esta checklist

  • https://deacademia.com resuelve a Vercel con SSL verde válido (incluye www. y wildcard listo para subdominios futuros tipo api. y i.).
  • Repo tooaudience-paraguas creado en GitHub org TooAudience con scaffold Next.js 14 (app router) + Tailwind + shadcn/ui + Tailscale Vercel deploy automático en main.
  • Proyecto Supabase tooaudience-martin activo, schema (8 tablas) deployado, RLS habilitado en todas, service_role key únicamente en Railway env vars (nunca en cliente).
  • Proyecto Railway tooaudience-martin con al menos 2 servicios corriendo (worker bot WA + cron jobs) y health checks verdes.
  • grep -r -i "martin\|rieznik\|levantarte" repo/ no retorna nada hardcodeado fuera de seeds/fixtures (todo lee de clients.config_json).

1. Pre-requisitos

# Pre-requisito Provisto por Estado
1 Acceso GitHub org TooAudience (permiso admin) para Eric Jesús
2 Acceso GoDaddy de TooAudience (login para tocar DNS de deacademia.com) Jesús
3 Acceso/invite a Vercel team TooAudience con rol owner o developer Jesús → Eric ✅ (compartir)
4 Acceso/invite a Supabase org TooAudience Jesús → Eric ✅ (compartir)
5 Acceso/invite a Railway workspace TooAudience Jesús → Eric ✅ (compartir)
6 Decisión de región preferida (cdg1 París por proximidad a usuarios LATAM) Eric
7 OpenAI API key (para Whisper + system prompts del bot) TooAudience

2. Tareas

2.1 DNS + dominio paraguas

  • Loguearse a GoDaddy y verificar que deacademia.com está en la cuenta de TooAudience (no en cuenta personal). R: Jesús. Done cuando: dominio visible en panel + fecha de expiración > 6 meses.
  • Verificar también presencia y estado de grupodeacademia.com y valleacademico.com (dominios de respaldo, deben quedar dormidos pero pagos). R: Jesús. Done cuando: ambos visibles + auto-renew activado + sin DNS apuntando a nada (parking page de GoDaddy ok).
  • Crear proyecto Vercel tooaudience-paraguas con preset Next.js 14, region cdg1, framework auto-detected. R: Eric. Done cuando: proyecto visible en dashboard + build de placeholder verde.
  • Conectar repo GitHub tooaudience-paraguas al proyecto Vercel con auto-deploy en main y preview deploys en cada PR. R: Eric. Done cuando: PR de prueba dispara preview URL automáticamente.
  • En Vercel proyecto > Domains, agregar deacademia.com y www.deacademia.com (Vercel devuelve A record + CNAME). R: Eric. Done cuando: ambos dominios listados en pending verification con records visibles.
  • En GoDaddy DNS de deacademia.com: borrar records existentes A/CNAME que apunten a parking + crear A record @ → IP que indica Vercel + CNAME wwwcname.vercel-dns.com (o el que indique Vercel). R: Eric. Done cuando: zona DNS limpia con solo los 2 records de Vercel + TTL 600s.
  • Esperar propagación (24-48h) y verificar con dig deacademia.com +short desde varias regiones (LATAM y Europa). R: Eric. Done cuando: respuesta consistente desde 3 puntos geográficos diferentes.
  • Confirmar que Vercel emitió certificado SSL automático Let's Encrypt para deacademia.com y www.deacademia.com. R: Eric. Done cuando: candado verde en navegador + cert válido al menos 60 días.
  • Configurar redirect 301 de www.deacademia.comdeacademia.com (canonical sin www) en Vercel project settings. R: Eric. Done cuando: visitar www.deacademia.com redirige automáticamente.
  • Pre-aprobar wildcard SSL para subdominios futuros (api.deacademia.com, i.deacademia.com, bot.deacademia.com) agregándolos como dominios placeholder en Vercel. R: Eric. Done cuando: 3 subdominios listados aunque sin contenido aún.

2.2 Repo tooaudience-paraguas

  • Crear repo tooaudience-paraguas en GitHub org TooAudience, privado, default branch main, con .gitignore Node + README mínimo. R: Eric. Done cuando: repo accesible con permisos de equipo configurados (Eric admin, Jesús maintain).
  • Scaffold Next.js 14 con app router: npx create-next-app@latest --ts --tailwind --app --src-dir --import-alias "@/*". R: Eric. Done cuando: npm run dev levanta en localhost:3000.
  • Instalar dependencias base: @supabase/supabase-js, @supabase/ssr, shadcn/ui (init), zod, dayjs con dayjs/plugin/timezone. R: Eric. Done cuando: package.json tiene las 5 dependencias + npm install limpio.
  • Estructura de carpetas inicial: src/app/[client_slug]/, src/lib/supabase/, src/lib/precio_dinamico.ts, src/lib/tracking/, src/components/ui/, src/types/. R: Eric. Done cuando: árbol creado con .gitkeep en carpetas vacías.
  • Crear src/lib/supabase/server.ts y src/lib/supabase/client.ts con clientes tipados (server con service role, client con anon key). R: Eric. Done cuando: imports funcionan + tipos generados con supabase gen types.
  • Crear archivo clients.config.example.json documentando el schema de config_json por cliente (slug, marca, colores, pixel_ids, voz_bot, oferta). R: Eric + Cortex. Done cuando: archivo en repo con todos los campos esperados + comentarios.
  • Configurar ESLint + Prettier + Husky pre-commit con lint + typecheck. R: Eric. Done cuando: commit con código mal formateado falla automáticamente.
  • Crear template de PR + issue (.github/PULL_REQUEST_TEMPLATE.md). R: Eric. Done cuando: nuevo PR abre con template pre-cargado.

2.3 Supabase project

  • Crear proyecto Supabase tooaudience-martin región sa-east-1 (São Paulo, más cerca de usuarios LATAM), plan Free (upgrade a Pro cuando volumen lo justifique). R: Eric. Done cuando: proyecto verde + URL + anon key + service role key copiados a 1Password.
  • Crear tabla clients (id, slug, nombre, config_json jsonb, created_at) + insertar fila Martín con slug='martin' y config_json cargado desde plantilla. R: Eric. Done cuando: select * from clients where slug='martin' retorna 1 fila.
  • Crear tabla leads (id, client_id FK, ta_uid uuid unique, email, telefono, nombre, pais, utm_source, utm_medium, utm_campaign, utm_content, ab_variant, calificado bool, created_at). R: Eric. Done cuando: schema deployado + index en (client_id, created_at) y unique en (client_id, email).
  • Crear tabla eventos (id, client_id, lead_id, tipo enum, data_json, created_at) con enum de 13 tipos: page_view, quiz_started, quiz_completed, registered_calificado, registered_no_calificado, whatsapp_sent, whatsapp_replied, joined_group, attended_webinar, dropped_webinar, checkout_clicked, purchase_completed, refunded. R: Eric. Done cuando: enum creado + tabla con índices (client_id, tipo, created_at).
  • Crear tabla compras (id, client_id, lead_id, sku, precio numeric, pasarela enum, transaction_id_externo, status enum, refund_at, created_at). R: Eric. Done cuando: tabla deployada con enums pasarela (5 valores) y status (pending/completed/refunded/disputed).
  • Crear tabla mensajes_wa (id, client_id, lead_id, direction enum in/out, contenido text, intent_detectado, created_at). R: Eric. Done cuando: tabla + index en (lead_id, created_at desc).
  • Crear tabla miembros_grupo (id, client_id, lead_id, grupo_tipo enum, grupo_id, joined_at, removed_at). R: Eric. Done cuando: enum grupo_tipo con 3 valores: pre_webinar, compradores_taller, miembros_anuales.
  • Crear tabla variantes (id, slug, name, peso int, activa bool) para A/B testing. R: Eric. Done cuando: tabla + check constraint peso between 0 and 100.
  • Habilitar RLS en TODAS las 8 tablas + escribir policies: lectura solo via service_role desde server, ninguna policy permisiva por defecto. R: Eric. Done cuando: query con anon key falla con 401 + query con service role funciona.
  • Generar tipos TypeScript con supabase gen types typescript --project-id <id> > src/types/supabase.ts y commitear. R: Eric. Done cuando: tipos importables en frontend.
  • Configurar backups automáticos diarios + Point-in-time recovery (si plan lo permite, sino agendar dump manual semanal a S3/Drive). R: Eric. Done cuando: primer backup verde + restore test exitoso.

2.4 Railway project (workers + cron)

  • Crear proyecto Railway tooaudience-martin plan Hobby ($5/mes). R: Eric. Done cuando: workspace visible en Railway dashboard.
  • Crear servicio worker-bot-wa desde repo tooaudience-bot-whatsapp (que Jesús pasó a Eric — bloqueante B1), build automático. R: Eric. Done cuando: deploy verde + logs sin errores fatales en startup.
  • Crear servicio worker-cron para tareas programadas (recordatorios webinar, cleanup eventos viejos, refresh tokens Meta, etc.). R: Eric. Done cuando: cron base con 1 job de health check corriendo cada 5 min.
  • Configurar cron jobs base: (a) recordatorio sábado 10hs ARG, (b) recordatorio domingo 10hs ARG, (c) recordatorio lunes 14hs ARG, (d) link webinar lunes 19:30 ARG, (e) cambio de precio taller diario 00:00 ARG. R: Eric. Done cuando: 5 cron jobs registrados con timezone America/Argentina/Buenos_Aires.
  • Health check endpoints en cada servicio (/health retorna 200 con timestamp + db ping). R: Eric. Done cuando: Railway Health Check verde + uptime monitor (UptimeRobot free) configurado.
  • Configurar log retention 7 días + alertas a email de Eric ante crashes. R: Eric. Done cuando: log streaming visible + test de alerta dispara mail.

2.5 Env vars + secrets

  • Crear .env.local.example en repo tooaudience-paraguas listando TODAS las vars necesarias (sin valores reales). R: Eric. Done cuando: archivo committeado con al menos: NEXT_PUBLIC_SUPABASE_URL, NEXT_PUBLIC_SUPABASE_ANON_KEY, SUPABASE_SERVICE_ROLE_KEY, META_PIXEL_ACCESS_TOKEN_MARTIN, META_PIXEL_ID_MARTIN, TIKTOK_PIXEL_ID_MARTIN, GOOGLE_ADS_ID_MARTIN, OPENAI_API_KEY, REDIS_URL (si aplica), VERCEL_URL, NODE_ENV.
  • Cargar todas las env vars en Vercel project settings (production + preview + development separados). R: Eric. Done cuando: panel de env vars de Vercel muestra cada variable en los 3 environments.
  • Cargar env vars del worker (WhatsApp tokens, OpenAI key, Supabase service role) en Railway, scoped por servicio. R: Eric. Done cuando: cada servicio tiene solo las vars que necesita (principio de mínimo privilegio).
  • NUNCA committear .env.local real al repo. Verificar .gitignore lo cubre. R: Eric. Done cuando: git status no lista .env.local + .env.*.local en .gitignore.
  • Crear vault compartido en 1Password "TooAudience-Martin-Infra" con todas las credenciales (Supabase keys, Railway tokens, Meta tokens cuando lleguen, OpenAI key, GoDaddy creds). R: Eric + Jesús. Done cuando: vault accesible para Eric y Jesús, ningún secret en chats/email.

2.6 Naming convention multi-tenant

  • Documentar reglas de naming en docs/MULTI_TENANT_NAMING.md del repo: nada de "martin"/"rieznik"/"levantarte" en código fuente; todo se lee de clients.config_json. R: Eric. Done cuando: doc commiteada con ejemplos de DO/DON'T.
  • Establecer convención de slugs: [a-z0-9-]+, máx 20 chars, sin espacios ni acentos. Validar con regex en API. R: Eric. Done cuando: helper validateClientSlug(s) exportado desde src/lib/.
  • Crear script scripts/check-no-hardcoded.sh que corre grep -r -i "martin\|rieznik\|levantarte" src/ --exclude-dir=node_modules y falla si hay matches fuera de fixtures. R: Eric. Done cuando: script verde + agregado a CI (GitHub Actions) como check obligatorio en PRs.
  • Convención de tablas Supabase: TODAS las queries filtradas por client_id. Helper withClient(slug) obligatorio en todo query. R: Eric. Done cuando: ESLint rule custom o code review checklist obliga el patrón.
  • Convención de assets: imágenes/logos en public/clients/[slug]/... (no en raíz). R: Eric. Done cuando: estructura creada + carpeta public/clients/martin/ con placeholder logo.

2.7 Validación end-to-end

  • Healthcheck integrado: Vercel + Supabase + Railway responden en /api/healthcheck-all con JSON {vercel: ok, supabase: ok, railway: ok, dns: ok}. R: Eric. Done cuando: endpoint responde 200 con todos los componentes verdes.
  • Smoke test: insertar lead de prueba via API, verificar que aparece en Supabase, dispara evento, y se borra (cleanup). R: Eric. Done cuando: test pasa end-to-end + agregado a CI.
  • Demo a Jesús del stack completo funcionando (call de 30 min screen-share). R: Eric. Done cuando: Jesús aprueba en mensaje + aprobador firma checklist.

3. Variables y posibilidades a anticipar

Escenario Plan B
Propagación DNS demora > 48h en algunos ISPs Verificar registro de cache en whatsmydns.net. Si > 72h, contactar soporte GoDaddy. Workaround: hosts file local para seguir trabajando.
Vercel no emite SSL automático (raro) Forzar re-issue desde dashboard Vercel > Domains. Si persiste, abrir ticket. Fallback: usar Cloudflare Free como proxy SSL.
Supabase Free agota límites (500MB DB / 2GB egress / 50K MAU) Upgrade a Pro ($25/mes) cuando hits 80%. Pre-configurar webhook de alerta de uso a Eric.
Railway Hobby plan se queda sin créditos ($5/mes incluidos) Upgrade a Pro pay-as-you-go. Monitorear uso diario primera semana.
GoDaddy DNS panel rompe records al guardar (bug histórico) Usar Cloudflare como DNS authoritative en lugar de GoDaddy (cambiar nameservers). Más control + cache mejor.
Repo bot WhatsApp (B1) no llega a tiempo de Jesús Eric puede provisionar todo lo demás en paralelo. Bot queda pendiente hasta que llegue, no bloquea infra base.
Acceso compartido Vercel/Supabase/Railway no llega a Eric Jesús crea cuentas service específicas para Eric en cada plataforma. Documentar en 1Password.
Service role key se filtra accidentalmente (logs, frontend) Rotar inmediato desde Supabase dashboard. Auditar todos los lugares donde se usa. Revisar git history.
Cron jobs Railway no respetan timezone Argentina Hardcodear todos los crons en UTC y convertir en código con dayjs-tz. Documentar mapeos UTC ↔ ART.
Sandbox Vercel preview deploys exponen datos de Martín públicamente Configurar Vercel Password Protection en preview branches. O usar feature flag para datos reales solo en main.
GitHub Actions consume minutos free + falla CI Self-host runner si volumen lo justifica. O scope CI a solo PR a main, no a cada push.

4. Multi-tenant: cómo se replica al cliente #2

Esta sección alimenta sop/lanzar-cliente/01_INFRA_NEW_CLIENT.md.

  • Variables a externalizar (NUNCA hardcodear, todas leen de clients.config_json o env vars scoped por cliente):
  • client_slug (ej "martin", "cliente2") → carpeta de rutas en app/[client_slug]/
  • client_nombre ("Martín Rieznik") → branding visible
  • client_marca ("LevantArte") → títulos, footers
  • colores_brand (primary, secondary, accent) → Tailwind theme override
  • logo_url → ruta en public/clients/[slug]/logo.svg
  • voz_bot → system prompt del bot WA (string largo en config_json)
  • tz → timezone del cliente (ej "America/Argentina/Buenos_Aires")
  • pixel_ids{meta, tiktok, google_ads} por cliente
  • oferta_actual → JSON con SKUs, precios, fechas, bonus

  • Templates a guardar en sop/lanzar-cliente/templates/:

  • clients.config.template.json — esqueleto de config por cliente
  • supabase-schema.sql — DDL completo de las 8 tablas + RLS policies
  • railway-services.template.json — configuración de servicios + crons base
  • vercel-env-vars.template.txt — lista de env vars con placeholders ${CLIENT_SLUG}_*
  • next-app-scaffold/ — carpetas base de app/[client_slug]/

  • Sustituciones automáticas (script clone_client.py en repo tooaudience-clone-script):

  • Crear nuevo proyecto Supabase via API → guardar URL + keys
  • Crear nuevo proyecto Railway via API → deploy mismos servicios
  • Insertar fila en clients con nuevo slug + config_json desde template
  • Crear carpeta app/[nuevo_slug]/ copiando estructura de Martín
  • Reemplazar placeholders {{CLIENT_SLUG}}, {{CLIENT_NOMBRE}}, {{TZ}} con valores del nuevo cliente
  • Cargar env vars en Vercel via API
  • Output: PR automático a tooaudience-paraguas con todo el scaffold del nuevo cliente

  • Tiempo objetivo cliente #2: < 2 días desde acuerdo firmado a infra-listo.


5. Recursos y archivos relacionados


6. Notas y aprendizajes (post-mortem)

Llenar después del primer ciclo. Estos aprendizajes son los que se llevan al SOP.

  • (vacío hasta primera ejecución)

CHANGELOG

  • 2026-05-05 — Cortex (agente A) — Creación inicial de la checklist con base en plan interno § 1.