Troubleshooting
Problemas comunes y sus soluciones, ordenados por frecuencia real. Consultar esta página antes de
escalar. Primera parada para logs: Grafana/Loki
(logs.zentto.net).
Grid vacío con la API respondiendo 200
La clase de bug más frecuente del ERP. No es un error de red: el frontend espera
otra forma de respuesta (items vs data, total vs
TotalCount, objeto vs array).
- Comparar la respuesta real (pestaña Network) contra lo que el hook desestructura
- El contrato OpenAPI es el árbitro (Contrato API)
- En strings numéricos del driver pg (
NUMERICllega como string): convertir antes de.toFixed()y similares
502 en un hostname dev con deploy verde
Trampa del wildcard DNS. Existe *.zentto.net → Hetzner; el ambiente
dev vive en el centro de datos dellxeon detrás de un
túnel Cloudflare. Un hostname dev sin CNAME explícito al túnel resuelve al wildcard → Hetzner
(donde dev está apagado) → 502 fantasma.
- Verificar que el hostname tiene CNAME explícito
→ <tunnel>.cfargotunnel.com(proxied) - El ingress del túnel apunta a
127.0.0.1:puertodentro del LXC — verificar que el contenedor publica en ese puerto
Cambié el env-file y el servicio sigue igual
docker restart no relee el env-file. Recrear el contenedor:
docker compose -f docker-compose.prod.yml up -d --force-recreate <servicio> Y si la variable es NEXT_PUBLIC_*, ni siquiera eso basta: se hornea en build —
hay que reconstruir la imagen con el ARG correcto.
PostgreSQL: conexión rechazada desde Docker
Síntomas: ECONNREFUSED / connection refused desde un contenedor.
PG_HOSTdebe ser el gateway de la red Docker del servicio (172.18.0.1en la red principal; hay servicios en 172.19/172.20) — nuncalocalhostpg_hba.confcon la entrada de esa red (scram-sha-256)listen_addressesde PG debe incluir ese gateway — agregarlo requiere restart de PostgreSQL, no reload- UFW está inactivo — no buscar ahí; el firewall real es el de Hetzner Cloud (borde) y no afecta tráfico Docker→host
# Probar desde el contenedor
docker exec zentto-api sh -c "nc -zv 172.18.0.1 5432" SP devuelve error
- Nombre exacto en snake_case:
callSpsolo minusculiza, no convierte camelCase - "could not determine data type" → parámetro NULL sin tipo: castear (trampas de PL/pgSQL)
- "column reference is ambiguous" → falta alias frente a columnas del
RETURNS TABLE - 42P13 al reemplazar → cambió el tipo de retorno:
DROP FUNCTIONcon firma exacta primero - Función existe en local pero no en dev/prod → la migración goose no se ejecutó o quedó saltada
(número menor mergeado después de uno mayor); revisar
goose_db_version - Resultados vacíos inesperados con RLS → el GUC
app.current_company_idno está fijado en ese camino de código - Probar directo:
psql -d zentto_dev -c "SELECT * FROM usp_...(...)"
CORS: errores en el navegador
- Nginx maneja CORS con flag
always(aplica incluso en 5xx) y debe ocultar los headers del upstream (proxy_hide_header) para no duplicar - El origen debe matchear la whitelist de Express o el regex
*.zentto.net - Un 401 en mutaciones con sesión válida puede ser el middleware csrf-origin:
el request llegó sin
Origin/Refererdel ecosistema
curl -I -H "Origin: https://app.zentto.net" https://api.zentto.net/health Nginx sirve la app equivocada
server_name duplicado. Si dos server blocks declaran el mismo
hostname, nginx toma el primero sin error. Buscar duplicados antes de tocar la config
(que se gestiona desde zentto-infra, no a mano):
grep -rn "server_name" /etc/nginx/sites-enabled/ | sort | uniq -d
nginx -t && systemctl reload nginx Sesión expira constantemente / login en loop
AUTH_SECRETigual en el env-file del frontend y en SecretsAUTH_TRUST_HOST=truepresente- Cookie JWT > 4 KB → el navegador la descarta en silencio: el claim
companyAccessesva recortado a propósito; no engordar el token - Buffers de Nginx (
proxy_buffer_size 64k) para cookies grandes - Si la app federada apunta mal a auth: recordar que
NEXT_PUBLIC_AUTH_URLse hornea en build
Deploy: falla en GitHub Actions
| Error | Causa probable | Solución |
|---|---|---|
| Falla en 1-3 segundos | YAML inválido o budget de Actions agotado | Revisar sintaxis / facturación — no es el código |
| "failed to reserve cache" | type=gha en runner propio | Cache local type=local,dest=/opt/buildx-cache |
npm 404 al instalar @zentto/* | Auth npm faltante (los paquetes son privados) | Verificar NPM_TOKEN/.npmrc — 404 = auth, no "no existe" |
| Puerto 5432 en uso en tests | Service container con mapeo fijo — el runner tiene PG local | Puerto efímero: ports: ['5432'] + job.services.postgres.ports['5432'] |
| SSH connection refused | Secret SSH_HOST/SSH_HOST_DEV o clave | Verificar secrets (dev apunta al LXC dellxeon, prod a Hetzner) |
| Deploy verde sin el cambio | El build no corrió (paths/condición) | Verificar que el job de build ejecutó de verdad |
| Disk full en el server | Imágenes acumuladas o logs de contenedores huérfanos | docker image prune -af; revisar /var/lib/docker/containers/*-json.log |
Frontend: build falla o no hidrata
- Verificar refs a paquetes compartidos y limpiar
.next/node_modules/.cache - Página estática que no hidrata solo en deploy (funciona local):
assetPrefix: ''debe ir primero ennext.config.ts— el HTML pre-renderizado apunta a assets de otro build - Hydration mismatch con
mailto:: el Email Obfuscation de Cloudflare reescribe el HTML SSR — desactivarlo o evitar mailto en SSR - Node 20.x en local (igual que CI/Docker)
Comando rápido de diagnóstico (producción)
ssh root@178.104.56.185
docker ps --format '{{.Names}}\t{{.Status}}' | sort # ~70 contenedores
docker exec zentto-frontend pm2 status
systemctl status postgresql nginx
df -h / && docker system df docker logs). Un reinicio sin diagnóstico oculta la causa raíz — y si el
síntoma es de env-file, el reinicio ni siquiera lo arregla (ver arriba).