01 — Infra técnica
Índice de la página
- 01Cliente: Martín Rieznik / LevantArte
- Área: DevOps / Backend / Plataforma
- 020. Definición de "done" para esta checklist
- 031. Pre-requisitos
- 042. Tareas
- 2.1 DNS + dominio paraguas
- 2.2 Repo tooaudience-paraguas
- 2.3 Supabase project
- 2.4 Railway project (workers + cron)
- 2.5 Env vars + secrets
- 2.6 Naming convention multi-tenant
- 2.7 Validación end-to-end
- 053. Variables y posibilidades a anticipar
- 064. Multi-tenant: cómo se replica al cliente #2
- 075. Recursos y archivos relacionados
- 086. Notas y aprendizajes (post-mortem)
- 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.comapuntando a Vercel, repotooaudience-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.comresuelve a Vercel con SSL verde válido (incluyewww.y wildcard listo para subdominios futuros tipoapi.yi.). - Repo
tooaudience-paraguascreado en GitHub org TooAudience con scaffold Next.js 14 (app router) + Tailwind + shadcn/ui + Tailscale Vercel deploy automático enmain. - Proyecto Supabase
tooaudience-martinactivo, schema (8 tablas) deployado, RLS habilitado en todas, service_role key únicamente en Railway env vars (nunca en cliente). - Proyecto Railway
tooaudience-martincon 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 declients.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.comestá 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.comyvalleacademico.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-paraguascon preset Next.js 14, regioncdg1, framework auto-detected. R: Eric. Done cuando: proyecto visible en dashboard + build de placeholder verde. - Conectar repo GitHub
tooaudience-paraguasal proyecto Vercel con auto-deploy enmainy preview deploys en cada PR. R: Eric. Done cuando: PR de prueba dispara preview URL automáticamente. - En Vercel proyecto > Domains, agregar
deacademia.comywww.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 + CNAMEwww→cname.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 +shortdesde 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.comywww.deacademia.com. R: Eric. Done cuando: candado verde en navegador + cert válido al menos 60 días. - Configurar redirect 301 de
www.deacademia.com→deacademia.com(canonical sin www) en Vercel project settings. R: Eric. Done cuando: visitarwww.deacademia.comredirige 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-paraguasen GitHub org TooAudience, privado, default branchmain, con.gitignoreNode + 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 devlevanta enlocalhost:3000. - Instalar dependencias base:
@supabase/supabase-js,@supabase/ssr,shadcn/ui(init),zod,dayjscondayjs/plugin/timezone. R: Eric. Done cuando:package.jsontiene las 5 dependencias +npm installlimpio. - 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.gitkeepen carpetas vacías. - Crear
src/lib/supabase/server.tsysrc/lib/supabase/client.tscon clientes tipados (server con service role, client con anon key). R: Eric. Done cuando: imports funcionan + tipos generados consupabase gen types. - Crear archivo
clients.config.example.jsondocumentando el schema deconfig_jsonpor 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-martinregiónsa-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 conslug='martin'yconfig_jsoncargado 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 enumspasarela(5 valores) ystatus(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: enumgrupo_tipocon 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 constraintpeso 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.tsy 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-martinplan Hobby ($5/mes). R: Eric. Done cuando: workspace visible en Railway dashboard. - Crear servicio
worker-bot-wadesde repotooaudience-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-cronpara 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 (
/healthretorna 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.exampleen repotooaudience-paraguaslistando 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.localreal al repo. Verificar.gitignorelo cubre. R: Eric. Done cuando:git statusno lista.env.local+.env.*.localen.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.mddel repo: nada de "martin"/"rieznik"/"levantarte" en código fuente; todo se lee declients.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: helpervalidateClientSlug(s)exportado desdesrc/lib/. - Crear script
scripts/check-no-hardcoded.shque corregrep -r -i "martin\|rieznik\|levantarte" src/ --exclude-dir=node_modulesy 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. HelperwithClient(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 + carpetapublic/clients/martin/con placeholder logo.
2.7 Validación end-to-end
- Healthcheck integrado: Vercel + Supabase + Railway responden en
/api/healthcheck-allcon 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_jsono env vars scoped por cliente): client_slug(ej "martin", "cliente2") → carpeta de rutas enapp/[client_slug]/client_nombre("Martín Rieznik") → branding visibleclient_marca("LevantArte") → títulos, footerscolores_brand(primary, secondary, accent) → Tailwind theme overridelogo_url→ ruta enpublic/clients/[slug]/logo.svgvoz_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 clientesupabase-schema.sql— DDL completo de las 8 tablas + RLS policiesrailway-services.template.json— configuración de servicios + crons basevercel-env-vars.template.txt— lista de env vars con placeholders${CLIENT_SLUG}_*-
next-app-scaffold/— carpetas base deapp/[client_slug]/ -
Sustituciones automáticas (script
clone_client.pyen repotooaudience-clone-script): - Crear nuevo proyecto Supabase via API → guardar URL + keys
- Crear nuevo proyecto Railway via API → deploy mismos servicios
- Insertar fila en
clientscon 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-paraguascon todo el scaffold del nuevo cliente -
Tiempo objetivo cliente #2: < 2 días desde acuerdo firmado a infra-listo.
5. Recursos y archivos relacionados
../00_CONTEXTO.md— visión multi-tenant + decisiones../02_PLAN_INTERNO_EQUIPO.md— § 1.1 a 1.7 (infra técnica detallada)02_LANDINGS_Y_PAGES.md— qué se construye encima de esta infra13_PIXEL_TRACKING.md— CAPI server-side corre sobre esta infra10_BOT_WHATSAPP_1A1.md— bot corre en Railway worker- Vercel docs: https://vercel.com/docs/projects/domains
- Supabase RLS: https://supabase.com/docs/guides/auth/row-level-security
- Railway crons: https://docs.railway.app/reference/cron-jobs
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.