Saltar al contenido principal

Runbooks

Procedimientos para cuando algo va mal en un entorno desplegado. Para la instalación y el despliegue normal, ver Operaciones y despliegue.

Un usuario dice que le salió un error​

  1. Pídele el código de referencia de la pantalla de error, si lo vio, y búscalo: select * from platform_error_events where id = '<código>';
  2. Si no lo tiene, busca por su tenant y la hora en platform_error_events o en platform_error_groups_v. La fila trae la pantalla, la acción, el código de Postgres y la traza.
  3. Detalle y consultas: Reporte de errores.

Un mensaje entrante no llegó​

docker compose logs backend --tail 200 | grep -i webhook

Por orden:

  1. ¿Llegó el webhook al backend? Si no aparece nada, el problema está antes: proveedor, DNS o nginx. Comprueba que el proveedor tiene registrada la URL correcta y que responde curl -I https://api.xenpia.com/api.
  2. ¿Se rechazó por firma? Meta firma sus peticiones. Un secreto mal configurado se ve como un rechazo sistemático de todos los webhooks.
  3. ¿El canal está activo para esa cuenta? Un canal desactivado en el panel descarta el mensaje.
  4. ¿Es un canal de los que encolan? X, correo, webchat y los de Meta encolan en inbound, que no tiene consumidor en este repositorio. Si el mensaje entró y no pasó nada más, es esto. Ver Visión general.

El bot no responde​

  1. status_bot de la conversación. Si alguien apagó el bot desde la bandeja, el bot está callado a propósito.
  2. Palabras de cumplimiento. Si el mensaje disparó una palabra de parada o de escalación, el silencio es el comportamiento correcto.
  3. Límite de consumo del tenant.
  4. Errores del modelo. Con DEBUG_LOGS=true se ven las excepciones que hoy se convierten en una frase amable de cara al usuario. Enciéndela un rato y vuelve a apagarla: imprime datos reales de conversación.
docker compose logs backend | grep '\[dbg:'

Redis caído​

BullMQ, el límite de peticiones y la caché del bot dependen de Redis.

docker compose ps redis
docker compose logs redis --tail 50
docker compose restart redis

WhatsApp y Telegram siguen respondiendo sin Redis, porque procesan de forma síncrona en el webhook. Lo que se degrada son las colas y la caché, así que el bot responde más lento y más caro (cada consulta al catálogo vuelve a calcularse).

Una migración falló en el despliegue​

El pipeline aplica las migraciones antes de desplegar, así que un fallo aquí deja el código sin desplegar pero la base a medias.

  1. Mira el log del job para ver qué migración y qué error.
  2. La causa habitual es una migración no idempotente que ya se aplicó parcialmente: un create policy sin su drop policy if exists delante.
  3. Arréglala en una migración nueva. No edites una ya aplicada: en el otro entorno ya corrió y el hash no coincidirá.

El frontend muestra datos viejos tras cambiar el .env​

Las variables NEXT_PUBLIC_* se congelan en el build. Reiniciar el contenedor no basta:

./deploy.sh --build

Un usuario no ve una pantalla​

Recorre la cadena entera antes de tocar nada:

  1. ¿El módulo está activo para ese tenant?
  2. ¿La entrada de menú existe y está activa?
  3. ¿El permiso de esa entrada está asignado a su rol?
  4. ¿La función menus_accessible la devuelve para ese usuario?

Casi siempre es el paso 2: se creó la página y se olvidó dar de alta la entrada de menú. Ver Frontend.

Certificados a punto de caducar​

Los dominios están tras Cloudflare. Revisa la sección de certificados de Operaciones y despliegue.

La documentación no refleja el último despliegue​

Los sitios son estáticos: el HTML se congela al construir la imagen. Si docs.xenpia.com o docstest.xenpia.com siguen mostrando lo de ayer, el contenedor está corriendo una imagen vieja.

docker compose ps docs devdocs # ¿están arriba?
docker compose up -d --build docs devdocs

Si tras reconstruir sigue igual, es caché de Cloudflare: purga la URL desde el panel. El HTML se sirve con Cache-Control: no-cache precisamente para que esto no pase, así que si se repite, revisa que no haya una regla de página en Cloudflare cacheando HTML.

Si el dominio ni siquiera resuelve o nginx responde 502, falta el DNS o el bloque de nginx en ese servidor: los contenedores solo escuchan en 127.0.0.1:3840 / 3841.

Entrar a devdocs / devdocstest da 403 sin pedir identificación​

El 403 lo devuelve el nginx del servidor, no Cloudflare: la petición no vino por el proxy. O el registro DNS dejó de estar proxied (nube gris en vez de naranja), o alguien entró por IP. Se comprueba en el panel de Cloudflare, y el detalle está en Operaciones y despliegue.

Si en cambio pide identificación y luego rechaza a alguien del equipo, es la política de Access: Zero Trust → Access → Applications → devdocs.xenpia.com / devdocstest.xenpia.com, revisar la lista de correos.

Guardar el plan de un tenant falla​

El plan se guarda en Odoo (fuente de verdad) desde Tenants → Editar → Plan: el panel llama a POST /api/billing/tenant-subscription del backend, que habla con el puente de Odoo (/xemp/ai/v1/*). En pantalla el usuario solo ve "No se puede actualizar el plan ahora" (a propósito no se menciona Odoo ni el detalle técnico). El código y el detalle real están en los logs: [billing] setTenantSubscription … en el frontend y BillingController en el backend. Cada llamada a Odoo corta a los 10 s (Odoo no respondió en 10 s) y la del panel al backend a los 45 s. Por código:

CódigoQué pasaQué revisar
not_configuredFalta ODOO_WEBHOOK_SECRET en el frontend, o ODOO_BASE_URL / ODOO_API_KEY en el backendEl .env que carga cada servicio
unauthorizedEl ODOO_WEBHOOK_SECRET del frontend y el del backend no coincidenEn local sin Docker, frontend/.env y backend/.env son archivos distintos
backend_unreachableEl frontend no llega a NestBACKEND_INTERNAL_URL (en Docker, http://backend:3000)
odoo_unreachableNest no llega a OdooODOO_BASE_URL visto desde el contenedor del backend: localhost ahí es el propio contenedor
odoo_errorOdoo respondió con un errorEl detalle trae el mensaje de Odoo
plan_not_foundEl código de plan no existe en OdooPlanes activos en Odoo y backfill POST /api/billing/sync
sync_failedOdoo aceptó, pero falló el espejo en SupabaseLogs del backend (BillingController)

El odoo_error más habitual es column ... does not exist: se desplegó código de un addon de Odoo con campos nuevos sin actualizar la base. Odoo carga el Python nuevo pero la tabla sigue vieja, y cualquier llamada al puente que lea ese modelo revienta. Se arregla actualizando el módulo en la base afectada:

docker compose exec -T odoo bash -c 'odoo -d <base> -u <modulo> --stop-after-init --http-port=8899 --max-cron-threads=0 --db_host="$HOST" --db_user="$USER" --db_password="$PASSWORD"'

Recoger información antes de escalar​

  • Rama y commit desplegado: git log -1 --oneline en el servidor.
  • docker compose ps y los últimos 200 logs del servicio afectado.
  • Tenant, canal y hora aproximada.
  • Si es de bots, un fragmento del log con DEBUG_LOGS activo.