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/.
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 enusers; los operadores se crean desde/api/sa/admin-users/que solo escribe ensaas_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:
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:
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:
-
Ver qué usuario corre nginx:
-
Añadir ese usuario al grupo del owner de la app:
-
Verificar que el path es atravesable:
-
Verificar que no hay
try_files $uri =404en ellocation /static/(causa double-path conalias).
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¶
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¶
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. BotUserPermissionno 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