Variables de entorno
Hay un único .env por máquina, compartido entre backend y frontend, que vive en la carpeta de la
aplicación en el servidor y nunca se versiona. La plantilla es
.env.example, y esta página se genera
a partir de ella.
Para documentar una variable nueva, escribe el comentario encima de ella en .env.example: eso es
lo que aparece en la columna de descripción.
Las variables marcadas con candado son secretos y su valor no se publica ni siquiera como ejemplo. Para saber de dónde sacar cada uno, mira Operaciones.
Dos detalles que rompen despliegues
NEXT_PUBLIC_* se congela en el build. Esas variables las lee el navegador, así que se
incrustan en el bundle al construir la imagen del frontend. Cambiarlas en el .env del servidor no
tiene ningún efecto hasta que se reconstruye: hace falta ./deploy.sh --build, no basta con
reiniciar el contenedor.
El puerto del frontend se fuerza en compose. El .env unificado trae PORT=3000 para Nest. El
servicio del frontend define PORT=3001 en docker-compose.yml para no heredarlo; sin eso, Next
escucharía en el 3000 y el mapeo de puertos quedaría vacío.
Todas las variables
.env.example al construir el sitio. No se edita a mano: se actualiza con yarn gen en docs/.Backend
| Variable | Ejemplo | Para qué sirve |
|---|---|---|
PORT | 3000 | Puerto del API Nest (docker-compose backend). El servicio frontend fuerza PORT=3001 en compose para no pisar este valor. |
CORS_ORIGINS | vacío | Orígenes del frontend permitidos (fetch desde el navegador). Separados por coma. Obligatorio si app y api son dominios distintos. Ejemplo prod: CORS_ORIGINS=https://app.xenpia.com |
REDIS_HOST | redis | Redis (BullMQ, rate-limit, caché del bot) |
REDIS_PORT | 6379 | — |
REDIS_PASSWORD 🔒 | vacío | — |
BOT_CACHE_EMBED_TTL_SECONDS | 3600 | Caché de embeddings / catálogo / knowledge / agenda (segundos). 0 desactiva esa capa. |
BOT_CACHE_SEARCH_TTL_SECONDS | 45 | — |
BOT_CACHE_KNOWLEDGE_TTL_SECONDS | 120 | — |
BOT_CACHE_SCHED_TTL_SECONDS | 60 | — |
BOT_CACHE_INDEXED_TTL_SECONDS | 60 | — |
BOT_CACHE_MAX_BYTES | 262144 | — |
BOT_CACHE_API_MAX_TTL_SECONDS | 300 | — |
BOT_METERING_RAW_RETENTION_DAYS | 180 | Días que se guarda la medición CRUDA de cada turno de bot (bot_turns, bot_node_runs, bot_tool_calls): la que alimenta la capa de calor del editor y el p95 por nodo. Los resúmenes diarios (bot_*_daily) no se borran nunca. Mínimo 7; por defecto 180. |
LANGGRAPH_CHECKPOINT_DATABASE_URL 🔒 | vacío | Memoria de las conversaciones con los bots (checkpointer de LangGraph) en Postgres, esquema `langgraph` (migración 20261020000003). Conexión DIRECTA al Postgres de Supabase con el usuario dueño de las tablas, por el pooler en modo SESIÓN (puerto 5432, no el 6543 de transacciones). Vacío = memoria en RAM: se pierde en cada deploy y no se comparte entre instancias. Lleva contraseña: solo en el .env del servidor, nunca en el repo. |
LANGGRAPH_CHECKPOINT_RETENTION_DAYS | 90 | Poda diaria: conversaciones sin actividad en estos días se olvidan (mínimo 7; por defecto 90)… |
LANGGRAPH_CHECKPOINT_KEEP_LAST | 20 | …y en las vivas se conservan los últimos N checkpoints (por defecto 20). Cada turno guarda el historial entero, así que sin poda la tabla crece de forma cuadrática. |
LANGSMITH_TRACING | false | Trazas de LangChain/LangGraph en LangSmith. Apagado por defecto y NO en PROD sin acuerdo de datos. Si se enciende, el backend fuerza LANGSMITH_HIDE_INPUTS/OUTPUTS=true al arrancar (hay PHI): se ven llamadas, tiempos y errores, nunca el texto. La API key solo en el .env del servidor. |
DEBUG_LOGS | false | Trazas de depuración del agente y de la agenda (1/true/yes/on para encender). Imprime, con prefijo [dbg:...]: qué herramientas resolvió el bot y cuáles se cayeron por módulo/canal/disponibilidad, los argumentos exactos con que el modelo llamó a cada una, las excepciones que hoy se convierten en una frase, y cada rama de salida de createAppointment con el código de error de Postgres. docker compose -f docker-compose.yml logs backend | grep '\[dbg:' Encendida imprime datos reales de conversación (nombres, teléfonos, correos): es para depurar un rato, no para dejarla puesta. Las claves y tokens se tapan. |
API_CALL_DEBUG_BOTS | vacío | Traza de lo que devuelve la API de los nodos "Llamada a API" de un flujo, solo para los bots nombrados (ids separados por coma, o * para todos). Vacía = apagada. Una línea por llamada con URL, cuerpo enviado, estado, ms y la respuesta recortada a 4000 caracteres. Nunca imprime cabeceras (ahí viaja el token). docker logs -f xenpia-chatbot-backend-1 2>&1 | grep '\[api_call:debug\]' La respuesta puede traer datos del cliente: es para depurar un bot un rato. |
Captcha (Cloudflare Turnstile) en alta, login y recuperar contraseña
| Variable | Ejemplo | Para qué sirve |
|---|---|---|
NEXT_PUBLIC_TURNSTILE_SITE_KEY 🔒 | vacío | Site key pública de Turnstile. Vacía = sin captcha. Si se activa el captcha en Supabase Auth (Authentication → Attack Protection), esta variable TIENE que estar puesta: Supabase lo exige también en el login y en recuperar contraseña. |
Cobros Place to Pay (vitrina XENPIA)
| Variable | Ejemplo | Para qué sirve |
|---|---|---|
PAYMENTS_ENABLED | vacío | Fail-closed: ausente / vacío / distinto de true|1 = APAGADO (404 en API y sin UI). Hay que setear las DOS variables para que UI y API coincidan. |
NEXT_PUBLIC_PAYMENTS_ENABLED | vacío | — |
PUBLIC_APP_URL | vacío | URL pública del frontend (returnUrl de Place to Pay). Ej: https://test.xenpia.com |
Dashboard bot (si aplica)
| Variable | Ejemplo | Para qué sirve |
|---|---|---|
NEXT_PUBLIC_DASHBOARD_BOT_ID | vacío | — |
DASHBOARD_BOT_ID | vacío | — |
Data sync prod → test (superadmin; UI visible en prod y test)
| Variable | Ejemplo | Para qué sirve |
|---|---|---|
GCP_OAUTH_CLIENT_ID | XXXXXX | GOOGLE CLOUD |
GCP_OAUTH_CLIENT_SECRET 🔒 | vacío | — |
ODOO_API_KEY 🔒 | vacío | Xenpdoo |
ODOO_BASE_URL | vacío | — |
ODOO_WEBHOOK_SECRET 🔒 | vacío | — |
BILLING_RECONCILE_INTERVAL_MINUTES | 15 | Cada cuántos minutos se reconcilia la copia de planes/suscripciones con Odoo (red de seguridad si se pierde un webhook). También corre 30 s después de arrancar. 0 = desactivada. |
FACTURACION_EC_ODOO_BASE_URL | vacío | URL del Odoo que aloja el motor de facturación electrónica (módulo invoicing, BD facturacionEc). Es distinto del Odoo de billing SaaS: nunca reutilizar ODOO_* aquí. Vacío = módulo sin motor (503). |
FACTURACION_EC_ODOO_HOST_HEADER | facturacionEc.xenpdoo.com | Cabecera Host con la que Odoo elige la BD del motor. Opcional: vacía usa el host de FACTURACION_EC_ODOO_BASE_URL. Test: facturacionec.testxenpia.com |
FACTURACION_EC_ODOO_API_KEY 🔒 | vacío | API key del usuario de integración en la BD facturacionEc. Secreto: solo en el backend. |
FACTURACION_EC_WEBHOOK_SECRET 🔒 | vacío | Secreto de los avisos del motor al cambiar el estado SRI de una factura (POST /api/invoicing/webhook/engine). Mismo valor que el parámetro xemp_invoicing_ec.webhook_secret de la BD facturacionEc. Vacío = se rechazan los avisos y solo queda la reconciliación cada 10 min. |
Email saliente (SMTP genérico)
| Variable | Ejemplo | Para qué sirve |
|---|---|---|
SMTP_HOST | vacío | SMTP propio de Xenpia, usado como respaldo cuando un tenant no configuró el suyo en el canal de email (channel_account → email_smtp_credentials). Sirve cualquier proveedor SMTP: Gmail Workspace, Outlook, un dominio propio, etc. |
SMTP_PORT | 587 | — |
SMTP_SECURE | false | 'true' para 465 (TLS implícito); 'false' para 587/25 (STARTTLS). |
SMTP_USER | vacío | — |
SMTP_PASSWORD 🔒 | vacío | — |
SMTP_FROM_EMAIL | vacío | — |
SMTP_FROM_NAME | Xenpia | — |
Email webhooks (si aplica)
| Variable | Ejemplo | Para qué sirve |
|---|---|---|
EMAIL_DEFAULT_PROVIDER | smtp | — |
EMAIL_INBOUND_SECRET 🔒 | vacío | — |
Guardián de seguridad de los bots (ADR 0007)
| Variable | Ejemplo | Para qué sirve |
|---|---|---|
BOT_SAFETY_MODE | observe | observe (por defecto): revisa y avisa en el log, pero los bots existentes siguen respondiendo. enforce: el registro no carga bots en revisión o bloqueados. El onboarding aplica enforce siempre. |
Límites de cuenta nueva (tenants.trust_level = 'new')
| Variable | Ejemplo | Para qué sirve |
|---|---|---|
BOT_NEW_TENANT_DAILY_CAP | 500 | Mensajes salientes al día de un tenant nuevo. |
NEW_TENANT_DAILY_TEMPLATES | 50 | Plantillas al día de un tenant nuevo (escriben a quien no escribió primero: vector de spam). |
NEW_TENANT_TRUST_DAYS | 14 | Días sin suspensión tras los que un tenant nuevo pasa solo a verificado. |
Meta / WhatsApp Cloud API (WhatsApp channel + embedded signup)
| Variable | Ejemplo | Para qué sirve |
|---|---|---|
META_APP_ID | TU_FACEBOOK_APP_ID | — |
META_VERIFY_TOKEN 🔒 | vacío | — |
META_APP_SECRET 🔒 | vacío | — |
META_WEBHOOK_SIGNATURE | enforce | Firma X-Hub-Signature-256 de los webhooks de Meta (/api/webhook/whatsapp y /api/webhooks/meta). enforce (por defecto) = responde 401 a lo que no esté firmado con META_APP_SECRET o con alguno de META_WEBHOOK_EXTRA_SECRETS; log = procesa igual pero lo registra (solo para la transición); off = no comprueba (nunca en producción). |
META_WEBHOOK_EXTRA_SECRETS 🔒 | vacío | Secretos de otras apps de Meta que también envían webhooks aquí (p. ej. la app de Instagram Login o una app de pruebas), separados por comas. Vacío si solo hay una app. |
WHATSAPP_ONBOARDING_WEBHOOK_OVERRIDE_URL | vacío | Solo en Test: URL a la que Meta debe entregar los webhooks de las WABA que se conecten desde este entorno por Embedded Signup (se aplica con override_callback_uri al suscribirlas). Hay una sola app de Meta para prod y Test; sin esto, lo conectado desde Test entregaría a prod. Ej.: https://apitest.xenpia.com/api/webhook/whatsapp. Vacía en producción. Con o sin ella, el onboarding nunca borra un override que ya exista en Meta. |
WHATSAPP_ACCESS_TOKEN 🔒 | vacío | WhatsApp Business (required para que el backend arranque) |
WHATSAPP_PHONE_NUMBER_ID | TU_PHONE_NUMBER_ID | — |
WHATSAPP_WABA_ID | TU_WABA_ID_DE_XENPIA | WABA y Business Manager propios de Xenpia: los usan los números que Xenpia crea o presta a un tenant (asistente "número de Xenpia") y el listado de plantillas cuando el bot no tiene WABA propia. |
WHATSAPP_BUSINESS_MANAGER_ID | TU_BUSINESS_MANAGER_ID | — |
WHATSAPP_API_VERSION | v24.0 | Versión de la Graph API para WhatsApp, Messenger e Instagram (formato vNN.N). Si falta o está mal escrita se usa la del código (v24.0). Probar en DEV antes de subirla. |
META_QUALITY_MONITOR_ENABLED | true | Monitor de calidad de los números de WhatsApp (consulta periódica a Meta). Activo salvo que se ponga false (útil en local para no gastar llamadas a Meta). |
NEXT_PUBLIC_META_APP_ID | TU_FACEBOOK_APP_ID | Embedded Signup (frontend) |
NEXT_PUBLIC_META_EMBEDDED_SIGNUP_CONFIG_ID | TU_EMBEDDED_SIGNUP_CONFIG_ID | — |
NEXT_PUBLIC_META_INSTAGRAM_SIGNUP_CONFIG_ID | TU_INSTAGRAM_SIGNUP_CONFIG_ID | Config IDs de Embedded Signup por canal (Meta App Dashboard → Facebook Login for Business → Configurations). Si no se definen, Instagram y Messenger caen al config_id de WhatsApp. |
NEXT_PUBLIC_META_MESSENGER_SIGNUP_CONFIG_ID | TU_MESSENGER_SIGNUP_CONFIG_ID | — |
NEXT_PUBLIC_META_BUSINESS_MANAGER_ID | TU_BUSINESS_MANAGER_ID | ID del Business Manager de Xenpia (business.facebook.com → Configuración del negocio). Solo lo usan los asistentes de Instagram y Messenger. El de WhatsApp NO lo envía: en Embedded Signup, `setup.business.id` es el portafolio del CLIENTE, y con el de Xenpia una cuenta con acceso a él creaba la WABA del cliente dentro de Xenpia. |
META_DEBUG_OAUTH_LOGS | false | Debug: registra el resultado del canje del code de Embedded Signup (tipo y caducidad del token, nunca el token ni el code). |
NEXT_PUBLIC_META_DEBUG_EMBEDDED_SIGNUP | false | Debug del navegador: vuelca en la consola los eventos WA_EMBEDDED_SIGNUP de Meta. En NODE_ENV=development se activa solo. |
META_DEBUG_EMBEDDED_SIGNUP | false | Debug: imprime en logs del backend cada paso del Embedded Signup (exchange de código, lookup de WABA asignada, phone-numbers). Útil para diagnosticar el error de "waba_id vacío". |
Modelo por nodo IA (ADR 0011)
| Variable | Ejemplo | Para qué sirve |
|---|---|---|
ANTHROPIC_API_KEY 🔒 | vacío | Claves de plataforma para los nodos del flujo que eligen un modelo de Anthropic o Google. El modelo del bot sigue siendo OpenAI. Sin la clave, un nodo que pida ese proveedor usa el modelo del bot. |
GOOGLE_API_KEY 🔒 | vacío | — |
Modo de canales (pack sandbox vs live)
| Variable | Ejemplo | Para qué sirve |
|---|---|---|
CHANNEL_MODE | live | live = producción: cada tenant usa sus números/páginas/tokens. sandbox = test/staging: TODOS los canales reales se bypasean al pack de prueba. La UI de configuración del pack SOLO aparece en sandbox (ni siquiera para superadmin en prod). En prod pon live; en el deploy de test, sandbox. |
NEXT_PUBLIC_CHANNEL_MODE | live | — |
Opcionales: Auth0
| Variable | Ejemplo | Para qué sirve |
|---|---|---|
NEXT_PUBLIC_AUTH0_DOMAIN | vacío | — |
NEXT_PUBLIC_AUTH0_CLIENT_ID | vacío | — |
NEXT_PUBLIC_AUTH0_CALLBACK_URL | vacío | — |
Opcionales: AWS Amplify
| Variable | Ejemplo | Para qué sirve |
|---|---|---|
NEXT_PUBLIC_AWS_AMPLIFY_USER_POOL_ID | vacío | — |
NEXT_PUBLIC_AWS_AMPLIFY_USER_POOL_WEB_CLIENT_ID | vacío | — |
NEXT_PUBLIC_AWS_AMPLIFY_REGION | vacío | — |
Opcionales: Firebase
| Variable | Ejemplo | Para qué sirve |
|---|---|---|
NEXT_PUBLIC_FIREBASE_API_KEY 🔒 | vacío | — |
NEXT_PUBLIC_FIREBASE_AUTH_DOMAIN | vacío | — |
NEXT_PUBLIC_FIREBASE_PROJECT_ID | vacío | — |
NEXT_PUBLIC_FIREBASE_STORAGE_BUCKET | vacío | — |
NEXT_PUBLIC_FIREBASE_MESSAGING_SENDER_ID | vacío | — |
NEXT_PUBLIC_FIREBASE_APPID | vacío | — |
NEXT_PUBLIC_FIREBASE_MEASUREMENT_ID | vacío | — |
OpenAI (orchestrator/bots)
| Variable | Ejemplo | Para qué sirve |
|---|---|---|
OPENAI_DEFAULT_MODEL | gpt-4.1-mini | — |
OPENAI_DEFAULT_API_KEY 🔒 | vacío | — |
Orquestador (para modo local si lo necesitas)
| Variable | Ejemplo | Para qué sirve |
|---|---|---|
ORCHESTRATOR_BASE_URL | http://TU_IP:3000 | — |
BACKEND_INTERNAL_URL | http://TU_IP:3000 | Server Actions (Next → Nest). En Docker Compose se fuerza http://backend:3000. |
Storage bucket (opcional)
| Variable | Ejemplo | Para qué sirve |
|---|---|---|
NEXT_PUBLIC_BUCKET | vacío | — |
BUCKET | vacío | — |
Supabase (mismo proyecto para front y back)
| Variable | Ejemplo | Para qué sirve |
|---|---|---|
SUPABASE_URL | https://TU_PROYECTO.supabase.co | — |
SUPABASE_SERVICE_ROLE_KEY 🔒 | vacío | — |
NEXT_PUBLIC_SUPABASE_URL | https://TU_PROYECTO.supabase.co | — |
NEXT_PUBLIC_SUPABASE_ANON_KEY 🔒 | vacío | — |
SUPABASE_ANON_KEY 🔒 | vacío | Clave anónima para el backend (copiloto: cliente por petición con JWT del usuario + RLS). Puede ser la misma que NEXT_PUBLIC_SUPABASE_ANON_KEY. |
SUPABASE_KEY 🔒 | vacío | backend usa SUPABASE_KEY |
URLs públicas (frontend, navegador)
| Variable | Ejemplo | Para qué sirve |
|---|---|---|
NEXT_PUBLIC_SERVER_URL | http://TU_IP:3001 | Si usas solo IP, pon el IP real y el puerto del frontend (3001). |
BACKEND_URL | http://TU_IP:3000/api | — |
NEXT_PUBLIC_BACKEND_URL | http://TU_IP:3000 | URL del Nest que el navegador (y el widget de webchat) puede alcanzar. Primaria para getBackendPublicUrl() / snippet de instalación. Rebuild del frontend si cambia. |
NEXT_PUBLIC_ORCHESTRATOR_URL | http://TU_IP:3000 | — |
NEXT_PUBLIC_ASSETS_DIR | vacío | Para assets si aplica |
X (Twitter) webhooks (si aplica)
| Variable | Ejemplo | Para qué sirve |
|---|---|---|
X_WEBHOOK_SECRET 🔒 | vacío | — |
🔒 = secreto: su valor no se publica aquí ni se commitea en ningún sitio.