13
Pasos totales
9
Archivos anclados
3
Directorios
100%
Paths verificados
Estructura del repositorio — cultivalead-api/
📁 src/
↳ 📄 index.ts — entry point
↳ 📁 config/
↳ 📄 env.ts — validación de env vars con zod
↳ 📁 routes/
↳ 📄 leads.ts · qualify.ts
↳ 📁 services/
↳ 📄 leadService.ts · aiService.ts
↳ 📁 middleware/
↳ 📄 auth.ts · rateLimit.ts
↳ 📁 models/
↳ 📄 Lead.ts · Tenant.ts
📁 tests/
↳ leads.test.ts · aiService.test.ts
Fórmula SMIG:
S
Situación — ¿qué estás mirando?
M
Mecanismo — ¿cómo funciona?
I
Implicación — ¿por qué importa?
G
Gotcha — ¿qué te pillaría desprevenido?
Orientación
Pasos 1–3
1
Mapa del territorio
DIRECTORY
src/
S
El directorio
src/ es todo el servidor. Cuatro carpetas, una responsabilidad cada una.M
routes/ define los endpoints HTTP. services/ contiene la lógica de negocio. middleware/ intercepta cada petición antes de que llegue a las rutas. models/ habla con la base de datos.I
Si recibes un bug de "lead duplicado", miras
services/leadService.ts. Si el bug es "token inválido", miras middleware/auth.ts. La carpeta te dice quién es el culpable.G
config/ no es configuración de Express — es validación de variables de entorno con zod. Si falta una env var la app explota en arranque con un mensaje claro. Eso es intencionado.2
El punto de entrada
FILE
src/index.ts
:1
S
Aquí arranca todo. Express, middlewares globales, registro de rutas.
M
El orden de
app.use() importa. Primero rateLimit, luego auth, luego las rutas. Si lo inviertes, las rutas responden sin autenticar.I
Como nuevo joiner, si necesitas añadir un middleware global (logging, tracing), este es el archivo. Añádelo antes de las rutas.
G
El servidor escucha en
process.env.PORT ?? 3000. En producción PORT viene del orquestador (Railway). En local usa el .env. Nunca hardcodees el puerto aquí.3
Variables de entorno con contrato
FILE
src/config/env.ts
:1
S
Este archivo define el contrato de configuración de toda la app usando
zod.M
envSchema.parse(process.env) se ejecuta al importar este módulo. Si falta DATABASE_URL o OPENAI_API_KEY, la app lanza un ZodError con el campo exacto que falta — no un undefined is not a function misterioso a las 3am.I
Cuando añadas una nueva variable de entorno, declárala aquí primero. Así el error es inmediato y legible.
G
Nunca importes
process.env.ALGO directamente fuera de este archivo. Importa el objeto env exportado. Así el tipado es correcto y el fallo es temprano.
Ruta crítica: Autenticación y dominio
Pasos 4–9
4
Autenticación: JWT + API key dual
FILE
src/middleware/auth.ts
:1
S
CultivaLead soporta dos modos de autenticación: JWT (para el dashboard web) y API key (para integraciones de agencias).
M
El middleware detecta el tipo por el header:
Authorization: Bearer eyJ... → JWT, X-API-Key: ck_live_... → API key. Ambos caminos inyectan req.tenant con el tenantId resuelto.I
Todo lo que viene después puede confiar en
req.tenant. Si un endpoint necesita saber qué agencia hace la petición, es req.tenant.id.G
Las API keys se almacenan hasheadas (bcrypt). La comparación es async — si ves código que compara API keys con
===, hay un bug de seguridad. La función correcta es verifyApiKey() en este mismo archivo.5
Rate limiting por tenant y plan
FILE
src/middleware/rateLimit.ts
:1
S
Cada tenant tiene un límite diferente según su plan: 100 req/min (free) o 1000 req/min (pro).
M
Usa
express-rate-limit con una función keyGenerator que devuelve tenantId. Los límites se leen de req.tenant.plan — por eso rateLimit se aplica DESPUÉS de auth en index.ts.I
Si un cliente se queja de 429s inesperados, revisa su
plan en base de datos. El límite por defecto es free si el campo está null.G
El store de contadores es Redis en producción, in-memory en desarrollo. En local puedes disparar 1000 peticiones sin problema. Eso puede enmascarar bugs de concurrencia — testea siempre con
REDIS_URL en .env.8
El pipeline de cualificación IA
FILE
src/services/aiService.ts
:1
S
Este servicio llama a OpenAI GPT-4 para asignar un score de 0–100 a cada lead.
M
Construye un prompt estructurado con los datos del lead (nombre, empresa, cargo, email domain). GPT-4 devuelve un JSON con
score, tier (hot/warm/cold) y reasoning. El resultado se parsea con zod — si GPT devuelve JSON inválido, se reintenta hasta 3 veces.I
El coste por lead cualificado es ~$0.002 (4k tokens GPT-4). A escala importa. Si un tenant abusa, el rate limit del paso 5 lo frena antes de llegar aquí.
G
El prompt tiene un
systemMessage con instrucciones estrictas de formato JSON. Si alguien modifica ese system message sin testear, el parser zod fallará silenciosamente y el lead quedará con score: null. Hay un test específico para esto en tests/aiService.test.ts.
Cierre
Paso 13
13
¿Qué puedes hacer ahora?
DIRECTORY
tests/
Has completado el tour de onboarding. Ahora puedes:
📝
Añadir un campo a Lead: modifica
models/Lead.ts + routes/leads.ts (schema zod) + crea una migración Sequelize
🤖
Cambiar el prompt de cualificación: edita
services/aiService.ts y ejecuta npm run test:ai para verificar el output
🔍
Depurar un 429: revisa el plan del tenant en DB y los contadores en Redis con
redis-cli monitor
👀
Revisar un PR: busca cambios en
services/ — ahí está la lógica que puede romper clientes
Siguiente tour recomendado: "PR Reviewer — CultivaLead" para entender los invariantes antes de revisar código de otros.
Generado con
tour-codigo-codetour
skill de CULTIVA IA
Archivo:
.tours/new-joiner-onboarding.tour
Extensión:
VS Code CodeTour