Skip to content
··9 min de lectura

Las pantallas en blanco destruyen la confianza. Tenía 31 de ellas.

Encontré 31 pantallas en blanco en mi SaaS—todas porque olvidé que la arquitectura multi-tenant no se trata solo del acceso a datos, sino del contexto de URL. Así es como Claude Code me ayudó a arreglarlas todas en una noche.

1 de noviembre, 8:47 AM. Recibí el email.

"Hola Chandler - hice clic en 'Ver' y me apareció una página en blanco. ¿Perdí todos mis datos de entrevista?"

Se me cayó el estómago. Las páginas en blanco no son bugs. Son destructores de confianza.

Abrí la consola de desarrollo. Verifiqué la ruta. Usuario de agencia, viendo al cliente "Acme Corp", hizo clic en un botón, y... la URL cambió de `/clients/acme-corp/agents/agent-name/results/123` a solo `/agent-name/results/123`.

Se perdió el contexto del cliente. React Router no pudo encontrar la ruta. Pantalla en blanco.

"Bien, ese es un bug," pensé. "Lo arreglo y sigo adelante."

Configuré Claude Code para analizar el codebase en busca de patrones similares y luego pasé mi sábado con la familia. Almuerzo. Recados. Los niños. Lo de siempre.

A media tarde, volví a revisar. Claude Code había encontrado el patrón—y no era nada bonito.

Para las 7:42 PM, había arreglado 14 bugs. Para las 7:48 PM, encontré 8 más. Para las 7:56 PM, el conteo llegó a 31 en total.

Todos la misma causa raíz. Todos la misma solución. Todos porque olvidé una cosa cuando construí la arquitectura multi-tenant: la navegación no es solo sobre datos, es sobre contexto.

---

¿Qué tan grave era, en realidad?

Seamos honestos sobre lo que significaban estos 31 bugs:

Para los usuarios:

- Usuarios de agencias golpeando pantallas en blanco (parecía roto, no con bugs)

- Progreso del flujo de trabajo perdido a mitad de sesión

- "¿Es esta plataforma lo suficientemente estable para nuestros clientes?"

- Cada pantalla en blanco = un paso más cerca de cancelar la prueba

Para mí:

- 5+ reportes de bugs por día (todos relacionados con la navegación)

- 2-3 horas depurando cada uno individualmente

- No podía lanzar nuevas funcionalidades (demasiado ocupado apagando incendios)

- Miedo genuino: "¿Qué pasa si rompí algo más y todavía no lo sé?"

La pregunta existencial: Si ni siquiera puedo hacer que la navegación funcione, ¿por qué alguien debería confiarle a STRAŦUM la estrategia de marketing de sus clientes?

Las pantallas en blanco destruyen la confianza más rápido que cualquier cosa.

---

El bug que lo inició todo

Déjame mostrarte exactamente lo que ocurrió.

Flujo de usuario (lo que debería haber funcionado):

1. Ir a `/clients/acme-corp/agents/analysis`

2. Hacer clic en "Iniciar Sesión"

3. Completar el análisis

4. Hacer clic en "Ver Resultados"

5. Ver los resultados en `/clients/acme-corp/agents/analysis/results/123`

Lo que realmente ocurrió:

1. ✅ `/clients/acme-corp/agents/analysis` (bien)

2. ✅ Iniciar sesión (funcionando)

3. ✅ Completar análisis (datos guardados)

4. ❌ **Clic en "Ver Resultados" → PÁGINA EN BLANCO**

5. ❌ La URL cambió a `/analysis/results/123` (contexto del cliente perdido)

React Router buscó una ruta en `/analysis/results/123`. No existía para usuarios de agencias. No renderizó nada.

El usuario ve: Pantalla blanca en blanco. Sin mensaje de error. Sin spinner de carga. Solo... nada.

---

Lo que encontré cuando (en realidad Claude Code) empecé a investigar

Aquí está el aspecto del código roto:

```typescript
// AgentPage.tsx (SANITIZED - the broken pattern)
import \{ useParams, useNavigate \} from 'react-router-dom';

export function AgentPage() \{
  const { clientSlug \} = useParams<\{ clientSlug: string \}>();
  const navigate = useNavigate();

  const handleViewResults = (sessionId: string) => \{
    // Problem: Hardcoded route, no client context
    navigate(`/agent-name/results/${sessionId\}`);
  };

  return (
    // ... component
  );
}
```

¿Ves el problema?

Extraje `clientSlug` de la URL. Lo usé para obtener datos. Pero cuando navegué, me olvidé completamente de él.

Las rutas de agencia se ven así: `/clients/acme-corp/agents/agent-name`

Las rutas de PYME se ven así: `/agent-name`

Hardcodeé el patrón de PYME. Usuarios de agencias = roto.

---

La realización: Tenía 31 de estos

2:15 PM. Arreglé el primer bug. Hice commit. Me sentí bien.

3:42 PM. Encontré otro en un agente diferente. El mismo patrón. Lo arreglé.

4:18 PM. Otro agente. Lo mismo.

5:30 PM. Me detuve y miré fijamente mi pantalla durante un sólido de 10 minutos.

Cada página de agente tenía el mismo bug. Cada componente anidado que navegaba. Cada elemento de UI compartido con un botón "Ir a...".

Podía arreglarlos uno por uno y pasar 2 días en eso. O podía encontrar el patrón y arreglarlos todos sistemáticamente.

Elegí lo sistemático.

---

La solución: Navegación consciente del contexto

En lugar de `useParams()` en cada componente, le pedí a Claude Code que creara un proveedor de Context:

```typescript
// contexts/ClientContext.tsx
import \{ createContext, useContext \} from 'react';
import \{ useParams \} from 'react-router-dom';

interface ClientContextValue \{
  clientSlug: string | null;
\}

const ClientContext = createContext<ClientContextValue | null>(null);

export function ClientContextProvider(\{ children \}: \{ children: React.ReactNode \}) \{
  // Extract clientSlug ONCE at the layout level
  const { clientSlug \} = useParams<\{ clientSlug: string \}>();

  return (
    <ClientContext.Provider value=\{{ clientSlug: clientSlug || null \}}>
      \{children\}
    </ClientContext.Provider>
  );
}

export function useClientContext() \{
  const context = useContext(ClientContext);
  if (!context) {
    throw new Error('useClientContext must be used within ClientContextProvider');
  \}
  return context;
}
```

Luego envolví las rutas de agencia:

```typescript
// App.tsx
<Route path="/clients/:clientSlug/*" element={
  <ClientContextProvider>
    <ClientLayout />
  </ClientContextProvider>
}>
  <Route path="agents/analysis" element={<AnalysisAgent />} />
  <Route path="agents/strategy" element={<StrategyAgent />} />
  \{/* ... all client-scoped routes */\}
</Route>
```

Ahora todos los componentes del interior tienen acceso a `clientSlug` a través del contexto, no de los params.

El helper de enrutamiento (porque escribir el mismo if/else 20 veces cansa):

```typescript
// hooks/useContextRoute.ts
import \{ useClientContext \} from '@/contexts/ClientContext';

export function useContextRoute() \{
  const { clientSlug \} = useClientContext();

  const buildRoute = (route: string) => \{
    if (clientSlug) {
      // Agency route: /clients/acme-corp/agents/...
      const cleanRoute = route.startsWith('/') ? route.slice(1) : route;
      return `/clients/${clientSlug\}/$\{cleanRoute\}`;
    }
    // SME route: /agent-name/...
    return route;
  };

  return \{ buildRoute, clientSlug \};
}
```

Uso (mucho más limpio):

```typescript
// AgentPage.tsx (SANITIZED - the fixed pattern)
import \{ useClientContext \} from '@/contexts/ClientContext';
import \{ useNavigate \} from 'react-router-dom';
import \{ useContextRoute \} from '@/hooks/useContextRoute';

export function AgentPage() \{
  const { clientSlug \} = useClientContext();
  const \{ buildRoute \} = useContextRoute();
  const navigate = useNavigate();

  const handleViewResults = (sessionId: string) => \{
    // This works for BOTH SME and Agency users
    navigate(buildRoute(`agents/analysis/results/${sessionId\}`));
  };

  return (
    // ... component
  );
}
```

Una función helper. Consciente del contexto. Funciona para ambos tipos de usuario.

---

La solución sistemática: Un día, 31 archivos

Una vez que tuve el patrón, se volvió mecánico.

1 de noviembre de 2025 - El sprint

Tarde (2 PM - 7 PM) - Encontrando y categorizando:

- Descubrí el patrón en todas las páginas de agentes

- Construí el proveedor de Context y el helper de enrutamiento

- Probé el enfoque en el primer agente

Noche (7:00 PM - 8:00 PM) - La solución sistemática:

7:42 PM - Primera ola (14 bugs):

```
fix(multi-tenant): fix 14 navigation bugs and refactor all agents to useClientContext()

Frontend Changes (8 files):
- Refactored 6 agents to use useClientContext() hook
- Fixed 14 navigation bugs that lost client context across multiple agents

Backend Changes (5 files):
- Added client_id parameter to all save operations
- Updated base classes to extract client_id properly

Files changed: 14
Insertions: +732
Deletions: -164
```

7:48 PM - Segunda ola (8 bugs más):

```
fix(multi-tenant): fix 8 navigation bugs in strategy page

- Fixed 8 navigation buttons that lost client context
- Total bugs fixed: 22 (14 + 8)

Files changed: 1
Insertions: +71
Deletions: -23
```

7:56 PM - Ola final (9 bugs más):

```
fix(multi-tenant): fix 9 navigation bugs in interview and tool pages

- Fixed remaining navigation issues in nested components
- Total bugs fixed: 31 bugs across entire application

Files changed: 2
Insertions: +46
Deletions: -10
```

Repasé cada flujo de agente. Usuario PYME. Usuario de agencia. Hice clic en cada botón. Sin páginas en blanco.

Daño total: 17 archivos cambiados, 849 inserciones, 197 eliminaciones.

Tiempo invertido: Aproximadamente 6 horas de trabajo no continuo con Claude Code (descubrimiento a las 2 PM → commit final a las 8 PM).

Tiempo ahorrado: Probablemente 30+ horas de correcciones individuales de bugs y soporte al usuario.

---

Lo que aprendí (por las malas)

1. La navegación multi-tenant es más difícil que el aislamiento de datos

Puedes filtrar datos por `org_id`. Esa es la parte fácil.

¿Pero la navegación? Tienes dos estructuras de URL válidas para la misma funcionalidad:

```
SME:    /agent-name/session/123
Agency: /clients/acme-corp/agents/agent-name/session/123
```

Cada llamada a `navigate()` necesita saber qué patrón usar. Equivócate una vez, y los usuarios ven páginas en blanco.

2. useParams() te miente

```typescript
const \{ clientSlug \} = useParams();
```

Esto funciona... hasta que la ruta cambia. Entonces `clientSlug` se convierte en `undefined`, y tu siguiente navegación se rompe.

React Context no miente. Siempre está disponible, siempre es consistente.

3. Cuenta cada bug antes de proclamar la victoria

Pensé que tenía 14 bugs. Luego encontré 8 más. Luego 9 más.

Lección: Busca en todo el codebase con grep, no solo en los archivos que crees que tienen bugs.

4. Cuando encuentras el mismo bug dos veces, para y crea un patrón

Corrección individual: 31 bugs = probablemente una semana

Corrección sistemática: Encontrar patrón → Crear helper → Arreglar todas las instancias = 6 horas

Los 10 minutos que pasé mirando fijamente mi pantalla a las 5:30 PM me ahorró días.

5. Los bugs de navegación son amenazas existenciales

Nos obsesionamos con la gestión de estado, la obtención de datos, la optimización de API.

Pero navegación rota = pantallas en blanco = "Esta plataforma está rota."

A los usuarios no les importan tus políticas RLS ni tu arquitectura multi-tenant. Les importa que hacer clic en "Ver Resultados" muestre resultados. Esta misma brecha entre "técnicamente funciona" y "realmente terminado" apareció de nuevo cuando construí una app nativa de iOS con IA—Claude Code generó el andamio rápido, pero el pulido que hace que los usuarios confíen en un producto, ese es el 40% que solo los humanos pueden entregar.

---

El patrón (para tu app multi-tenant)

Si estás construyendo SaaS multi-tenant con URLs jerárquicas:

✅ Paso 1: Crea un proveedor de Context a nivel de layout

```typescript
<ClientContextProvider>
  \{/* All tenant-scoped routes */\}
</ClientContextProvider>
```

✅ Paso 2: Construye un helper de enrutamiento

```typescript
const \{ buildRoute \} = useContextRoute();
navigate(buildRoute('agents/analysis/session/123'));
```

✅ Paso 3: Nunca hardcodees rutas

```typescript
// ❌ BAD
navigate('/analysis/session/123');

// ✅ GOOD
navigate(buildRoute('agents/analysis/session/123'));
```

✅ Paso 4: Busca TODAS las llamadas de navegación con grep

```bash
# Find every navigate() call
grep -r "navigate(" src/ > navigation_audit.txt

# Find hardcoded routes
grep -r "navigate('/" src/ | grep -v "buildRoute"
```

✅ Paso 5: Prueba con todos los tipos de usuario

Flujo PYME. Flujo de agencia. Cada botón. Cada enlace. Sin páginas en blanco.

---

Los resultados

Antes del 1 de noviembre:

- 31 bugs de navegación acechando

- 5+ reportes de bugs por día

- Usuarios de agencias cuestionando la estabilidad de la plataforma

- Yo: Aterrado de lanzar nuevas funcionalidades

Después del 1 de noviembre:

- 0 bugs de navegación

- 0 tickets de soporte relacionados con la navegación

- Patrón establecido para el desarrollo futuro

- Yo: Confiado lanzando nuevas rutas de agentes

Velocidad de desarrollo:

- Agregar nuevas rutas: 5 minutos (antes 30 minutos + "¿romperá esto algo?")

- Reportes de bugs: 0 (antes 5+ por día)

- Confianza del usuario: Restaurada ("La plataforma se siente estable ahora")

Las 6 horas que pasé arreglando estos bugs se pagaron 10 veces en carga de soporte reducida y confianza de usuario restaurada.

---

Por qué comparto esto

La navegación multi-tenant no es sexy. Nadie celebra "arreglé 31 bugs en 6 horas" de la misma manera que celebran "lancé nueva funcionalidad."

Pero si estás construyendo SaaS multi-tenant como yo con https://stratum.chandlernguyen.com/ una plataforma de marketing con IA para agencias—vas a encontrar esto. Tal vez no 31 bugs. Tal vez solo 5. Pero lo vas a encontrar.

Cuando lo hagas, recuerda:

1. Context > Params para el estado de navegación

2. Sistemático > Correcciones individuales

3. Busca en todo el codebase con grep (encontrarás más de lo que crees)

4. Prueba con todos los tipos de usuario antes de proclamar la victoria

Y si te encuentras mirando fijamente una pantalla en blanco preguntándote adónde fue tu contexto, sabe que yo estuve en la misma situación. :)

¿Cuál es el bug más doloroso que has lanzado a usuarios reales—el tipo del que solo te enteraste porque alguien te lo dijo? Me encantaría escucharlo de verdad.

Un abrazo,

Chandler

La serie de arquitectura de STRAŦUM: Esta crisis de navegación fue parte de un viaje multi-tenant más grande. Comenzó con construir arquitectura multi-tenant el Día 2, escaló cuando tuve que reconstruir todo el esquema el Día 67, y concluyó cuando descubrí que mi base de datos era correcta pero 296 veces demasiado lenta.

---

*Todavía programando, todavía aprendiendo, todavía encontrando bugs en grupos de 31.*

Solicita acceso alpha en https://stratum.chandlernguyen.com/request-invitation

---

P.D. - ¿El usuario que reportó ese primer bug? No perdió sus datos de entrevista. Estaban guardados en la base de datos. Solo no podía verlos por una ruta rota. Cuando lo arreglé a las 7:42 PM, todo su trabajo seguía ahí. Ese pequeño alivio hizo que las 6 horas valieran la pena.

---

Seguir leyendo

Mi Trayectoria
Conectar
Idioma
Preferencias