Saltar al contenido principal

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.

Aquí no van valores reales

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​

Generado automáticamente desde .env.example al construir el sitio. No se edita a mano: se actualiza con yarn gen en docs/.

Backend

VariableEjemploPara qué sirve
PORT3000Puerto del API Nest (docker-compose backend). El servicio frontend fuerza PORT=3001 en compose para no pisar este valor.
CORS_ORIGINSvacíoOrí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_HOSTredisRedis (BullMQ, rate-limit, caché del bot)
REDIS_PORT6379—
REDIS_PASSWORD 🔒vacío—
BOT_CACHE_EMBED_TTL_SECONDS3600Caché de embeddings / catálogo / knowledge / agenda (segundos). 0 desactiva esa capa.
BOT_CACHE_SEARCH_TTL_SECONDS45—
BOT_CACHE_KNOWLEDGE_TTL_SECONDS120—
BOT_CACHE_SCHED_TTL_SECONDS60—
BOT_CACHE_INDEXED_TTL_SECONDS60—
BOT_CACHE_MAX_BYTES262144—
BOT_CACHE_API_MAX_TTL_SECONDS300—
BOT_METERING_RAW_RETENTION_DAYS180Dí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íoMemoria 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_DAYS90Poda diaria: conversaciones sin actividad en estos días se olvidan (mínimo 7; por defecto 90)…
LANGGRAPH_CHECKPOINT_KEEP_LAST20…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_TRACINGfalseTrazas 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_LOGSfalseTrazas 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_BOTSvacíoTraza 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

VariableEjemploPara qué sirve
NEXT_PUBLIC_TURNSTILE_SITE_KEY 🔒vacíoSite 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)

VariableEjemploPara qué sirve
PAYMENTS_ENABLEDvacíoFail-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_ENABLEDvacío—
PUBLIC_APP_URLvacíoURL pública del frontend (returnUrl de Place to Pay). Ej: https://test.xenpia.com

Dashboard bot (si aplica)

VariableEjemploPara qué sirve
NEXT_PUBLIC_DASHBOARD_BOT_IDvacío—
DASHBOARD_BOT_IDvacío—

Data sync prod → test (superadmin; UI visible en prod y test)

VariableEjemploPara qué sirve
GCP_OAUTH_CLIENT_IDXXXXXXGOOGLE CLOUD
GCP_OAUTH_CLIENT_SECRET 🔒vacío—
ODOO_API_KEY 🔒vacíoXenpdoo
ODOO_BASE_URLvacío—
ODOO_WEBHOOK_SECRET 🔒vacío—
BILLING_RECONCILE_INTERVAL_MINUTES15Cada 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_URLvacíoURL 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_HEADERfacturacionEc.xenpdoo.comCabecera 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íoAPI key del usuario de integración en la BD facturacionEc. Secreto: solo en el backend.
FACTURACION_EC_WEBHOOK_SECRET 🔒vacíoSecreto 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)

VariableEjemploPara qué sirve
SMTP_HOSTvacíoSMTP 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_PORT587—
SMTP_SECUREfalse'true' para 465 (TLS implícito); 'false' para 587/25 (STARTTLS).
SMTP_USERvacío—
SMTP_PASSWORD 🔒vacío—
SMTP_FROM_EMAILvacío—
SMTP_FROM_NAMEXenpia—

Email webhooks (si aplica)

VariableEjemploPara qué sirve
EMAIL_DEFAULT_PROVIDERsmtp—
EMAIL_INBOUND_SECRET 🔒vacío—

Guardián de seguridad de los bots (ADR 0007)

VariableEjemploPara qué sirve
BOT_SAFETY_MODEobserveobserve (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')

VariableEjemploPara qué sirve
BOT_NEW_TENANT_DAILY_CAP500Mensajes salientes al día de un tenant nuevo.
NEW_TENANT_DAILY_TEMPLATES50Plantillas al día de un tenant nuevo (escriben a quien no escribió primero: vector de spam).
NEW_TENANT_TRUST_DAYS14Días sin suspensión tras los que un tenant nuevo pasa solo a verificado.

Meta / WhatsApp Cloud API (WhatsApp channel + embedded signup)

VariableEjemploPara qué sirve
META_APP_IDTU_FACEBOOK_APP_ID—
META_VERIFY_TOKEN 🔒vacío—
META_APP_SECRET 🔒vacío—
META_WEBHOOK_SIGNATUREenforceFirma 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íoSecretos 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_URLvacíoSolo 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íoWhatsApp Business (required para que el backend arranque)
WHATSAPP_PHONE_NUMBER_IDTU_PHONE_NUMBER_ID—
WHATSAPP_WABA_IDTU_WABA_ID_DE_XENPIAWABA 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_IDTU_BUSINESS_MANAGER_ID—
WHATSAPP_API_VERSIONv24.0Versió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_ENABLEDtrueMonitor 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_IDTU_FACEBOOK_APP_IDEmbedded Signup (frontend)
NEXT_PUBLIC_META_EMBEDDED_SIGNUP_CONFIG_IDTU_EMBEDDED_SIGNUP_CONFIG_ID—
NEXT_PUBLIC_META_INSTAGRAM_SIGNUP_CONFIG_IDTU_INSTAGRAM_SIGNUP_CONFIG_IDConfig 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_IDTU_MESSENGER_SIGNUP_CONFIG_ID—
NEXT_PUBLIC_META_BUSINESS_MANAGER_IDTU_BUSINESS_MANAGER_IDID 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_LOGSfalseDebug: 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_SIGNUPfalseDebug del navegador: vuelca en la consola los eventos WA_EMBEDDED_SIGNUP de Meta. En NODE_ENV=development se activa solo.
META_DEBUG_EMBEDDED_SIGNUPfalseDebug: 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)

VariableEjemploPara qué sirve
ANTHROPIC_API_KEY 🔒vacíoClaves 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)

VariableEjemploPara qué sirve
CHANNEL_MODElivelive = 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_MODElive—

Opcionales: Auth0

VariableEjemploPara qué sirve
NEXT_PUBLIC_AUTH0_DOMAINvacío—
NEXT_PUBLIC_AUTH0_CLIENT_IDvacío—
NEXT_PUBLIC_AUTH0_CALLBACK_URLvacío—

Opcionales: AWS Amplify

VariableEjemploPara qué sirve
NEXT_PUBLIC_AWS_AMPLIFY_USER_POOL_IDvacío—
NEXT_PUBLIC_AWS_AMPLIFY_USER_POOL_WEB_CLIENT_IDvacío—
NEXT_PUBLIC_AWS_AMPLIFY_REGIONvacío—

Opcionales: Firebase

VariableEjemploPara qué sirve
NEXT_PUBLIC_FIREBASE_API_KEY 🔒vacío—
NEXT_PUBLIC_FIREBASE_AUTH_DOMAINvacío—
NEXT_PUBLIC_FIREBASE_PROJECT_IDvacío—
NEXT_PUBLIC_FIREBASE_STORAGE_BUCKETvacío—
NEXT_PUBLIC_FIREBASE_MESSAGING_SENDER_IDvacío—
NEXT_PUBLIC_FIREBASE_APPIDvacío—
NEXT_PUBLIC_FIREBASE_MEASUREMENT_IDvacío—

OpenAI (orchestrator/bots)

VariableEjemploPara qué sirve
OPENAI_DEFAULT_MODELgpt-4.1-mini—
OPENAI_DEFAULT_API_KEY 🔒vacío—

Orquestador (para modo local si lo necesitas)

VariableEjemploPara qué sirve
ORCHESTRATOR_BASE_URLhttp://TU_IP:3000—
BACKEND_INTERNAL_URLhttp://TU_IP:3000Server Actions (Next → Nest). En Docker Compose se fuerza http://backend:3000.

Storage bucket (opcional)

VariableEjemploPara qué sirve
NEXT_PUBLIC_BUCKETvacío—
BUCKETvacío—

Supabase (mismo proyecto para front y back)

VariableEjemploPara qué sirve
SUPABASE_URLhttps://TU_PROYECTO.supabase.co—
SUPABASE_SERVICE_ROLE_KEY 🔒vacío—
NEXT_PUBLIC_SUPABASE_URLhttps://TU_PROYECTO.supabase.co—
NEXT_PUBLIC_SUPABASE_ANON_KEY 🔒vacío—
SUPABASE_ANON_KEY 🔒vacíoClave 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íobackend usa SUPABASE_KEY

URLs públicas (frontend, navegador)

VariableEjemploPara qué sirve
NEXT_PUBLIC_SERVER_URLhttp://TU_IP:3001Si usas solo IP, pon el IP real y el puerto del frontend (3001).
BACKEND_URLhttp://TU_IP:3000/api—
NEXT_PUBLIC_BACKEND_URLhttp://TU_IP:3000URL 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_URLhttp://TU_IP:3000—
NEXT_PUBLIC_ASSETS_DIRvacíoPara assets si aplica

X (Twitter) webhooks (si aplica)

VariableEjemploPara qué sirve
X_WEBHOOK_SECRET 🔒vacío—

🔒 = secreto: su valor no se publica aquí ni se commitea en ningún sitio.