Troubleshooting
Problemas conocidos y su solución. Si te topás con uno, no lo re-diagnostiques desde cero.
Build / frontend
| Síntoma | Causa | Solución |
|---|---|---|
Páginas sin estilos, chunks /_next/static/* en 404 | .next/ corrupto tras restarts | rm -rf apps/web/.next, reinstalar, relanzar |
next build falla con error de ESLint (“X is defined but never used”) | next build corre next lint; tsc/dev no lintean | pnpm --filter @mentat/web lint antes de deployar |
| ”Cannot find module ’./NNN.js’” en otro navegador | se buildeó con el dev corriendo (comparten .next) | bajar el dev, rm -rf .next, relanzar |
| Deploy de hosting sube bundle viejo / apuntando al emulador | el predeploy con --filter no matchea en Windows; sube el out/ previo | mover .env.local, rm -rf .next out, build a mano, deployar, restaurar |
Functions
| Síntoma | Causa | Solución |
|---|---|---|
| Deploy: “Cannot determine backend specification. Timeout after 10000” | el predeploy no rebuildea (Windows) + discovery corto | rebuild a mano + FUNCTIONS_DISCOVERY_TIMEOUT=120 |
package.json quedó minimizado tras un deploy fallido | el strip-script no llegó al restore | node apps/functions/scripts/strip-package-for-deploy.mjs restore |
| Emulador: “Failed to load function” en la 1ra llamada | inestabilidad del Functions emulator en Windows | reiniciar emulador (matar puertos) + re-sembrar |
| Trigger: “Variable de entorno requerida ausente: FIREBASE_PROJECT_ID” | el emulador setea GCLOUD_PROJECT, no FIREBASE_PROJECT_ID | ya resuelto con fallback en env.ts |
Emulador / datos
| Síntoma | Causa | Solución |
|---|---|---|
| ”running multiple instances” / puerto ocupado | procesos huérfanos de sesión anterior | matar PIDs de 4000/5001/8080/9099/9199/3000 |
| Datos escritos pero no leídos (split-brain) | emulador y cliente con distinto project ID | alinear ambos a dawoork-mentat |
| ”Cannot start Storage emulator without rules file” | falta storage.rules en firebase.json | ya resuelto; verificar que existe |
Producción / gcloud
| Síntoma | Causa | Solución |
|---|---|---|
Scripts admin: invalid_grant / invalid_rapt | ADC vencido | gcloud auth application-default login |
API firebaserules da 403 | falta el quota project | header X-Goog-User-Project: dawoork-mentat |
| Email no entrega a destinatarios reales | dominio Resend sin verificar / MENTAT_FROM_EMAIL en sandbox | verificar dominio + setear la env var + redeploy |
Ingesta / DataSources
| Síntoma | Causa | Solución |
|---|---|---|
Subir CSV en /datasources → “Error inesperado.” | uploadCsv (Storage) lanza un FirebaseError crudo que IngestionRunner no mapea → cae al fallback genérico | Mirar el error real en la red (status del POST a firebasestorage.googleapis.com). Casi siempre es un 403 de las Storage Rules — ver abajo |
| Upload de CSV da 403 incluso siendo admin | Las Storage Rules denegaban por el firestore.get cross-service (leer el rol) que no funciona en runtime de prod | Mitigado: el gate de rol se movió a la callable. Ver “Estado abierto” |
La corrida de ingesta vuelve failed: N/N (todas las filas fallan) | El object type que mapea el DataSource no existe en la ontología de esa org | Crear el/los object types referenciados por los mappings antes de ingestar. La org demo estaba sin ningún tipo (2026-06-09) |
Estado abierto (2026-06-09) — para retomar: El firestore.get cross-service en
storage.rules (leer organizations/{orgId}/members/{uid}.role dentro de la regla de
Storage) devuelve DENY en runtime de prod. Probado con bisect en vivo: un admin
subiendo text/csv recibía 403; al reemplazar canUpload por isSignedIn() el mismo
upload pasó a 200. Funciona en el emulador (por eso pasó los tests de Fase 5) pero
no en prod — posible limitación con la edición de Firestore o el bucket
.firebasestorage.app.
Mitigación viva: canUpload(orgId) quedó en isSignedIn() (gate interino:
signed-in + path tenant-scoped + content-type/size). La autorización por rol real de
la ingesta la hace la callable ingestDataSource (requireOrgRole), que además
revalida que el path pertenezca a la org. La lectura de Storage sigue denegada. Riesgo:
cualquier usuario autenticado podría escribir (no leer) un CSV en el prefijo de otra
org; no puede disparar la ingesta de una org donde no tiene rol.
Pendiente de endurecer: mover el rol a custom claims del token (request.auth.token)
para volver a un gate por rol en Storage sin depender del cross-service get. Requiere:
función que mantenga los claims al cambiar membresías + backfill de los miembros
existentes + re-login para refrescar el token. Decisión previa “sin custom claims”
(referencia/security-rules) queda a revisar por este hallazgo.
Formularios / validación
| Síntoma | Causa | Solución |
|---|---|---|
”Datos inválidos — revisá: <campo>: <motivo>” al crear/editar (Action, Object, DataSource, etc.) | El payload no pasó el Zod del borde; el mensaje nombra el campo y el motivo (ej. rules.0.targetType: requerido) | Corregí ese campo. Para el shape de Actions ver Definir Actions; para Objetos/Ontología, Ontología |
Desde 2026-06-10 el error de validación de las callables dejó de ser un genérico
“Datos inválidos.” y lista los campos que fallaron (helper
apps/functions/src/shared/validation-error.ts, usado por las 20 callables con
borde Zod). El detalle completo (flatten) sigue viajando como details.
PowerShell (Windows)
| Síntoma | Causa |
|---|---|
$org pisa $ORG (doc-id corrupto) | las variables de PowerShell son case-insensitive — usá nombres distintos |
Unexpected token '?' | PowerShell 5.1 no tiene operador ternario — usá if/else |
Comando bloqueado por contener /documents" | el sandbox lo confunde con un borrado — partí el string |