Guía para arrancar JobIT en local sin sorpresas de configuración (CORS / base de datos). Resuelve el fallo típico de login bloqueado por CORS cuando el frontend corre en un puerto distinto al configurado en el backend.
El backend (apps/api) lee su configuración de variables de entorno
(NODE_ENV, PORT, CORS_ORIGIN, DATABASE_URL, DATABASE_URL_TEST,
JWT_ACCESS_SECRET, JWT_REFRESH_SECRET, JOOBLE_API_KEY). Al arrancar con
tsx/pnpm, dotenv carga el .env del directorio de trabajo (apps/api),
no el .env de la raíz. Por eso el backend necesita su propio apps/api/.env.
apps/api/.env.example es la plantilla versionada, sin secretos. El archivo
real apps/api/.env NO se commitea (está en .gitignore).
cp apps/api/.env.example apps/api/.env
# edita apps/api/.env con tus valores locales (DB, secretos, CORS)apps/api/.env está ignorado por Git (.env, .env.*; solo se versiona
.env.example). Nunca publiques sus valores ni los pegues en logs/PRs.
El backend permite CORS con credenciales solo desde CORS_ORIGIN. Ese valor
debe coincidir con el puerto real del frontend.
- El frontend por defecto usa el 3000.
- Si el 3000 está ocupado (p. ej. por un contenedor Docker), arráncalo en 3001.
- En ese caso,
CORS_ORIGINdebe serhttp://localhost:3001.
Si CORS_ORIGIN=http://localhost:3000 pero el web corre en :3001, el navegador
bloquea el preflight y el login falla con:
Access to fetch at 'http://localhost:4000/api/auth/login' from origin
'http://localhost:3001' has been blocked by CORS policy...
El apps/api/.env.example ya trae CORS_ORIGIN=http://localhost:3001 por este
motivo. Ajústalo si tu frontend corre en otro puerto.
# 1) Backend (lee apps/api/.env). Escucha en http://localhost:4000
pnpm --filter @jobit/api dev
# 2) Frontend. Si el 3000 está libre:
pnpm --filter @jobit/web dev
# Si el 3000 está ocupado, usa el 3001 (y pon CORS_ORIGIN=http://localhost:3001):
NEXT_PUBLIC_API_BASE_URL="http://localhost:4000" pnpm --filter @jobit/web exec next dev -p 3001El frontend necesita NEXT_PUBLIC_API_BASE_URL apuntando a la API
(http://localhost:4000 en dev).
Health del backend:
curl http://localhost:4000/health
# -> {"status":"ok","service":"jobit-api"}Preflight CORS para el origen del frontend (debe reflejar tu CORS_ORIGIN):
curl -i -X OPTIONS http://localhost:4000/api/auth/login \
-H "Origin: http://localhost:3001" \
-H "Access-Control-Request-Method: POST" \
-H "Access-Control-Request-Headers: content-type"Resultado esperado (cabecera):
Access-Control-Allow-Origin: http://localhost:3001
Si ves http://localhost:3000 ahí, tu CORS_ORIGIN no coincide con el puerto del
frontend: corrígelo en apps/api/.env y reinicia el backend.
Los tests de @jobit/api usan una base independiente (jobit_test): el
globalSetup de vitest ejecuta prisma migrate deploy y cada test trunca las
tablas. No apuntes DATABASE_URL_TEST a jobit_dev (borraría tus datos).
# crear la base de test una vez (ejemplo con el contenedor local)
docker exec -e PGPASSWORD=... jobit-postgres-dev \
psql -U <user> -d postgres -c "CREATE DATABASE jobit_test;"
# ejecutar los tests backend con la base de test
pnpm --filter @jobit/api testapps/api/.env con DATABASE_URL_TEST definido basta; si no, expórtalo antes de
lanzar los tests.
JobIT no consulta Jooble en cada request: la búsqueda (GET /api/jobs) lee la DB
local. Las ofertas Jooble se ingieren de forma controlada (backend-only, manual) y
quedan guardadas en la DB con source=JOOBLE y sourceUrl.
Variables (en apps/api/.env, nunca commitear el .env real):
JOOBLE_API_KEY: clave de Jooble. Solo backend; nunca se loguea ni se pega en chats/PRs. Sin ella, la ingesta aborta sin llamar a la red.JOOBLE_API_BASE_URL(opcional): host de la API. Defaulthttps://jooble.org/api. Algunas keys son regionales: la de España responde enhttps://es.jooble.org/api(el host global da403). Debe ser http/https.
Comprobar rápidamente que la key funciona (elige el host correcto; no pegues la key
en logs; sustituye <KEY> por la tuya):
curl -s -o /dev/null -w "%{http_code}\n" -X POST "https://es.jooble.org/api/<KEY>" \
-H "Content-Type: application/json" -d '{"keywords":"developer","location":"Bilbao"}'
# 200 = host correcto para tu key; 403 = host equivocado (prueba el global o el regional)Poblar ofertas reales en dev/staging (backend-only, no borra seed):
# Ajusta ING_LOCATION por plaza: Bilbao, Madrid, Barcelona, remoto, España…
JOOBLE_API_KEY=<KEY> JOOBLE_API_BASE_URL=https://es.jooble.org/api ING_LOCATION=Bilbao \
pnpm --filter @jobit/api exec tsx src/jobs/scripts/ingest-jooble.ts
# Imprime un resumen (keywords/location/fetched/created/updated/skipped); nunca la key.Poblar varias ubicaciones de una vez (en serie; continúa si una falla; exit 1 si
alguna falló):
JOOBLE_API_KEY=<KEY> JOOBLE_API_BASE_URL=https://es.jooble.org/api \
ING_LOCATIONS="Bilbao,Madrid,Barcelona,Remoto,España" ING_LIMIT=20 \
pnpm --filter @jobit/api exec tsx src/jobs/scripts/ingest-jooble-locations.ts
# Resumen por ubicacion + agregado (locations/ok/failed + totales). Nunca imprime la key.Los datos ingeridos son locales de dev y se pierden al reseedear (prisma db seed
vacía la tabla Job). No se commitean.
- No cambies la lógica de auth ni el CORS de producción desde aquí; esto es solo documentación de entorno local.
- En producción,
CORS_ORIGINdebe ser el dominio real del frontend, y el frontend necesitaNEXT_PUBLIC_PUBLIC_BASE_URLpara las URLs públicas/QR del portfolio.