Saltar a contenido

17 — Troubleshooting

El widget no carga (botón flotante invisible)

Diagnóstico

Abre la consola del browser en la página donde está embebido el widget.

1. ¿El script blimx-v3-chatbot-loader.js carga (200)?
   • Si NO → Network tab muestra error de CORS / 404 / 502
   • Si SÍ → continúa
2. ¿Aparece el div #blimx-chatbot-launcher en el DOM?
   • Si NO → window.blimxV3ChatbotLoaderLoaded ya estaba en true → el script se cargó dos veces
3. ¿La altura/anchura del launcher es > 0?
   • Si NO → CSS del cliente lo está ocultando (z-index, display:none)

Causas comunes

Síntoma Causa Solución
Loader.js 404 Path incorrecto en el snippet Verifica https://widget.tudominio.com/static/v3/blimx-v3-chatbot-loader.js (sin /dist/)
Loader.js 403 Permisos de filesystem Ver 06-nginx.md sección permisos
Loader.js 502 Django caído sudo systemctl status blimx-django
Botón invisible pero en DOM CSS del cliente con z-index muy alto El script setea 2147483000 por default; ajusta data-z-index
Botón funciona pero modal vacío /v3/frontend/config 404 Verifica que existe tenant_frontend_content para ese bot_slug

El modal abre pero las FAQs no aparecen

Diagnóstico

Network tab del browser, filtrar por /v3/.

GET /v3/frontend/config?tenant=acme    → 200, ui_config OK
GET /v3/faq?tenant=acme&language=es    → ?
Resultado de /v3/faq Causa Solución
200 con categories: [] No hay FAQs en ese idioma Sube FAQs desde el dashboard del cliente
200 con categories: [...] pero el modal sigue vacío Bug del frontend del widget Limpia caché del browser; verifica que el bundle es la versión última (node build.js en static/v3)
400 "X-Client-ID required" Falta data-tenant en el snippet Añade data-tenant="<slug>"
500 Error en Django sudo journalctl -u blimx-django -f

Login del dashboard devuelve 401 inmediatamente

Causa Solución
Password incorrecta Reset desde /forgot-password
Usuario en status='pending_verification' Reenviar email de verificación
Usuario en status != 'active' (suspended) El superadmin lo desactivó; reactivar desde /superadmin/users/
Modo prelaunch activo y user no tiene código Solicitar invitación o desactivar prelaunch
SECRET_KEY cambió desde el último login Logout y vuelve a entrar
Account.closed_at superó los 30 días de gracia La cuenta está cerrada permanentemente, no se puede reactivar

Login devuelve 500

Casi siempre es porque el usuario que intenta entrar tiene un estado inconsistente entre saas_users y users. Ver la siguiente entrada.

Login OK pero el dashboard responde 403 en todos los endpoints (/api/dashboard/*)

Este síntoma — login exitoso, JWT recibido, pero cada llamada del dashboard devuelve 403 — significa que el JWT se emitió con un role que el dashboard rechaza (p.ej. staff o support en lugar de owner/admin).

Causa raíz

El login tiene precedencia explícita: busca primero en saas_users (tabla de operadores de la plataforma) y solo si no encuentra match busca en users (tabla de compradores). Si el mismo email existe en las dos tablas, el saas_users gana y emite un JWT con user_type='saas' que el dashboard del comprador no acepta.

Diagnóstico

psql -d blimx_ai_agent_mvp_v3 -c "
SELECT 'saas_users' AS tabla, id, email, role FROM saas_users WHERE email='<email>'
UNION ALL
SELECT 'users',      id, email, role FROM users      WHERE email='<email>';
"

Si aparecen dos filas (una por tabla), tienes el conflicto.

Solución

Decide en qué tabla debe vivir ese email:

Caso A — es un comprador del SaaS (debe vivir en users):

psql -d blimx_ai_agent_mvp_v3 <<'SQL'
BEGIN;
-- 1) Borra primero los refresh tokens del SaasUser duplicado
--    (token_blacklist tiene FK con on_delete=CASCADE solo desde Django 5+; en 4.2 hay que borrar a mano)
DELETE FROM token_blacklist_blacklistedtoken
WHERE token_id IN (
    SELECT id FROM token_blacklist_outstandingtoken
    WHERE user_id = (SELECT id FROM saas_users WHERE email='<email>')
);
DELETE FROM token_blacklist_outstandingtoken
WHERE user_id = (SELECT id FROM saas_users WHERE email='<email>');

-- 2) Borra el SaasUser duplicado
DELETE FROM saas_users WHERE email='<email>';
COMMIT;
SQL

Caso B — es un operador de la plataforma (debe vivir en saas_users):

Borra el registro de users tras limpiar refs en bot_user_permissions y social_auth_links.user_id.

Tras el cleanup el usuario tiene que cerrar sesión y volver a entrar — el JWT viejo sigue siendo válido hasta que expire y conserva el role equivocado.

Prevención

  • El signup público (/api/auth/signup/) solo crea filas en users; los operadores se crean desde /api/sa/admin-users/ que solo escribe en saas_users. No hay forma de duplicar emails accidentalmente desde la UI.
  • Si haces seeds manuales en producción, valida antes con la query de diagnóstico de arriba.

Hard refresh (Cmd+R / F5) en una ruta del dashboard devuelve 404

Síntoma: navegando dentro del dashboard todo funciona, pero al recargar en una URL como /dashboard/products/agents/plans aparece el 404 default de nginx/Apache.

Causa

El dashboard es una SPA React: solo existe físicamente /index.html + bundle. Cualquier ruta interna la resuelve React Router en cliente. Si nginx no tiene fallback al index.html, el filesystem no encuentra /dashboard/... y devuelve 404.

Solución

En el vhost del dashboard, el bloque location / debe tener try_files:

location / {
    root /var/www/.../apps/dashboard/dist;
    try_files $uri $uri/ /index.html;
    index index.html;
}

Si tienes paths del dashboard servidos desde un subpath distinto (ej. /password-reset), añade un location explícito:

location ~ ^/(dashboard|password-reset|verify-email)(/.*)?$ {
    root /var/www/.../apps/dashboard/dist;
    try_files /index.html =404;
}

Verificación:

curl -sI https://app.tu-dominio.com/dashboard/anything/here | head -1
# debe responder: HTTP/2 200

Stripe webhook devuelve 400 "Invalid signature"

Causa Solución
STRIPE_WEBHOOK_SECRET mal copiado Copia el whsec_ exacto desde el dashboard de Stripe
Body modificado por nginx (gzip) Stripe webhooks NO deben recibir gzip; nginx normalmente no comprime POSTs, pero verifica
Webhook test desde Stripe CLI con secret distinto Usa el secret que devuelve stripe listen para tests locales

Para validar manualmente:

sudo journalctl -u blimx-django -n 200 | grep -i webhook

Busca Webhook signature verification failed.

Permisos: nginx 403 al servir /static/

sudo -u nginx ls /home/blimxapp/saas/chatbot-v3-workspace/static/v3/
# Si "Permission denied" → arreglar permisos

Pasos:

  1. Ver qué usuario corre nginx:

    ps -eo user,comm | grep nginx | grep -v grep | head -1
    

  2. Añadir ese usuario al grupo del owner de la app:

    sudo usermod -a -G blimxapp nginx
    sudo systemctl restart nginx     # restart, NO reload — los workers heredan grupos al fork
    

  3. Verificar que el path es atravesable:

    sudo chmod 750 /home/blimxapp
    ls -la /home/blimxapp     # group debe ser blimxapp
    

  4. Verificar que no hay try_files $uri =404 en el location /static/ (causa double-path con alias).

Migrate falla: relation "..." already exists

Pasó probablemente porque migraron tablas a mano y el state de migrations no lo refleja.

cd backend_django && source venv/bin/activate

# Marca la migración como aplicada SIN ejecutarla (si la tabla ya existe)
python manage.py migrate api 0001_initial --fake

# Re-corre las migraciones reales
python manage.py migrate

Django arranca pero responde 500 en todo

sudo journalctl -u blimx-django -n 100 -f

Causas frecuentes:

Error en el log Causa
KeyError: 'SECRET_KEY' .env no se está cargando — verifica que WorkingDirectory del systemd unit apunta a backend_django/ (donde está .env o desde donde sube a buscarlo)
psycopg2.OperationalError DB inalcanzable; sudo systemctl status postgresql
ProgrammingError: column "..." does not exist Falta migrate o el código está adelantado al schema
ImproperlyConfigured: STRIPE_SECRET_KEY Variable Stripe vacía en .env
OSError: [Errno 24] Too many open files Límite de file descriptors; verifica LimitNOFILE=65536 en el systemd unit

El dashboard SPA muestra pantalla blanca

# Abre la consola del browser
# Si dice "Failed to fetch dynamically imported module" → el build está corrupto
cd apps/dashboard
rm -rf dist node_modules
npm install
npm run build

Verifica que nginx está apuntando al dist/ correcto:

ls /home/blimxapp/saas/chatbot-v3-workspace/apps/dashboard/dist/
# debe contener index.html, assets/

Los emails (verificación, reset) no llegan

sudo journalctl -u blimx-django -n 200 | grep -i 'email\|smtp\|mail'

Causas:

Error Causa
SMTPAuthenticationError EMAIL_HOST_PASSWORD incorrecta. Para Gmail usa App Password (no la password normal)
SMTPRecipientsRefused El DEFAULT_FROM_EMAIL no está verificado en SendGrid/SES
ConnectionRefusedError: [Errno 111] Firewall bloquea el puerto 587 saliente
Email enviado pero no llega Está en spam; configura SPF/DKIM en tu dominio

Test manual:

cd backend_django && source venv/bin/activate
python manage.py shell <<'PY'
from django.core.mail import send_mail
send_mail('Test', 'Test body', 'noreply@tudominio.com', ['tu-email@gmail.com'])
PY

Bot creado pero no aparece en la lista del dashboard

-- Verifica que existe en BD
SELECT id, slug, account_id, status FROM bots WHERE slug='nuevo-bot';

-- Verifica que el user tiene acceso vía bot_user_permissions o por ser owner del Account
SELECT bup.* FROM bot_user_permissions bup WHERE bup.bot_id='<bot_id>';

Si el bot existe pero no se ve, normalmente es:

  • status='deleted' (filter del API lo excluye).
  • El user logueado pertenece a otro account_id.
  • BotUserPermission no se creó (corre el seed manualmente).

Plan limit no se aplica correctamente

Si el frontend permite crear más FAQs que el plan:

cd backend_django && source venv/bin/activate
python manage.py shell <<'PY'
from api.models import Bot, Plan, Subscription
from api.services.plan_policy_engine import evaluate_quota

bot = Bot.objects.get(slug='acme')
print('Plan type:', bot.plan_type)
print('Sub:', Subscription.objects.filter(bot_id=bot.id, status='active').first())
print('Plan:', Plan.objects.get(name=bot.plan_type).features)
print('FAQs quota:', evaluate_quota(bot, 'faqs'))
PY

Si el Plan.features.limits.faqs_per_language está mal seteado, edítalo:

UPDATE plans
SET features = '{"limits": {"faqs_per_language": 100, "languages": 2, "branding": true}}'
WHERE name = 'Starter';

"Relación X no existe" tras pull de código nuevo

cd backend_django && source venv/bin/activate
python manage.py migrate
sudo systemctl restart blimx-django

Si las migraciones están en conflicto:

python manage.py showmigrations
# Mira cuál falta
python manage.py migrate api <numero_de_migracion>

Cómo recoger info para soporte

Cuando reportes un bug a soporte, incluye:

# 1. Versión del SaaS
git log --oneline -5

# 2. Estado de servicios
sudo systemctl status blimx-django nginx postgresql

# 3. Últimas 200 líneas de log
sudo journalctl -u blimx-django -n 200 > /tmp/django.log
sudo tail -n 200 /var/log/nginx/blimx_error.log > /tmp/nginx.log

# 4. Versión del bundle del widget
ls -la static/v3/dist/

# 5. .env sin secretos
sed 's/=.*/=<REDACTED>/' .env

# 6. Output de health
curl -sI http://127.0.0.1:8000/health
curl -sI http://127.0.0.1:8000/v3/health