Un bug de scope en React que solo aparecía cuando caducaba la sesión
El código compilaba, pasaba el lint y solo fallaba en la ruta que casi nadie recorre: la sesión caducada. El error: confundir el anidamiento visual del JSX con el ámbito de JavaScript.

Un fundador volvió a /dashboard/ después de que su sesión caducara y se encontró la lista de tarjetas vacía. Ni un error, ni una petición de inicio de sesión: nada. Para alguien que acaba de pagar por aparecer en el listado, un hueco en blanco es peor que un mensaje de error, porque un error dice qué ha pasado y una lista vacía dice que no tiene nada.
El arreglo pedido era directo: si la sesión está muerta, decirlo y mandar al login. La función getFounderListings() en web/lib/api.js ya distinguía un 401 de sesión caducada de cualquier otro fallo, porque /founder/listings no recibe identificador de listado y cualquier 401 ahí es inequívoco. Bastaba con capturarlo en la interfaz y llamar al signOut() que el dashboard ya tenía. El cambio fue una línea en web/app/dashboard/page.js:
<MyListings onSessionExpired={() => signOut('Your session expired — sign in again.')} />
Compiló. Pasó el lint. Encajaba con el resto del JSX. Y lanzó ReferenceError: signOut is not defined en cuanto un navegador de verdad ejecutó esa ruta, que solo se recorre con la sesión caducada; en una carga normal con sesión válida, la línea rota no se tocaba nunca.
Por qué "lo tengo ahí al lado" no vale
El archivo tiene varias funciones de nivel superior: Dashboard(), CardLookup() y MyListings(). signOut es una variable local de Dashboard, definida hacia la línea 129. En la página renderizada, <MyListings> parece estar dentro del dashboard, pero en el código no: Dashboard renderiza <CardLookup>, y CardLookup es quien renderiza <MyListings>. Tres funciones independientes en el mismo archivo.
El anidamiento de componentes en JSX no tiene relación con el ámbito de cierre de JavaScript. Un componente solo ve lo que hay en su propio closure y lo que le llega por props. CardLookup nunca capturó signOut porque no está definido dentro del cuerpo de Dashboard, sino como declaración hermana. La indentación del return habla del árbol del DOM, no de la cadena de ámbitos.
El fallo se encontró ejecutando, no releyendo: una segunda lectura de un código que parece obviamente correcto rara vez detecta su propio error. Primero escribieron la prueba e2e de la sesión caducada, con el patrón api.fail('founderListings', 401, ...) que ya usaba el resto de la suite. La prueba falló con un timeout esperando el formulario de login, y un timeout dice "no ha pasado nada", no por qué. Añadieron temporalmente page.on('pageerror', ...) y ahí salió la línea exacta. Después mapearon todas las declaraciones de nivel superior con grep -n "^function " en lugar de fiarse de la indentación.
El arreglo pasa el callback como prop por cada capa: Dashboard define signOut y lo entrega a CardLookup, CardLookup lo reenvía a MyListings, y MyListings llama a onSessionExpired?.() sin tocar signOut.
La segunda mitad del build log va de otro fallo distinto: un parche de espacio vertical muerto en las tarjetas del ladder de /board a ancho de móvil, después de añadir botones de voto a cada fila. Esa fila extra hizo la tarjeta más alta, y el avatar del escalador, que va con position: absolute, es donde apunta el diagnóstico.
Es el tipo de error que no caza ningún compilador ni linter: el identificador existe, la sintaxis es válida y solo se rompe en la ruta que nadie recorre a diario. La regla barata que lo habría evitado antes de ejecutar nada es preguntarse, antes de referenciar un nombre dentro de un componente, dentro de qué function { } se está físicamente, no junto a qué se renderiza.

