12-testing.md
Testing · visión
Conciergo no tiene tests automatizados: no hay Vitest, Jest ni Playwright instalados y no hay ficheros *.spec.* ni *.test.*. Lo que se comprueba hoy es el typecheck del workspace, la cadena del contrato OpenAPI y un recorrido a mano por la app.
Estado a 20 de octubre de 2026:
| Capa | Herramienta | Qué comprueba | Estado |
|---|---|---|---|
| Unitarios | — | — | No hay |
| Tipos y contrato | tsc y Orval | Que contrato, código generado, API y páginas encajan | Librerías, API y scripts pasan; la web da 8 errores |
| Flujos completos | A mano, en el navegador | Cartel, mapa, apuntarse, conexiones y chat | Sin automatizar |
| CI | — | — | No hay workflow de tests; Vercel no ejecuta el typecheck |
Comandos
pnpm install
pnpm run typecheck # tsc de librerías, apps y scripts
pnpm --filter @workspace/api-spec run codegen # regenera desde openapi.yaml y compila las librerías
Por dónde empezar
El código tiene piezas fáciles de probar sin red. Cada documento de esta sección señala las que tocaría primero:
- Unitarios de las funciones puras de la API:
profileFromPayload,readBearerTokeny la regla de parejas de las conexiones. - Integración HTTP con Supertest sobre
app(se puede importar sin abrir un puerto) y una rama de Neon para cada ejecución. - Un flujo completo con Playwright: dos cuentas de prueba que se apuntan al mismo bolo y se escriben.
- Un workflow que ejecute el typecheck en cada push, una vez arreglados los 8 errores.
13-testing-unit.md
Testing · unitarios
No hay tests unitarios ni un runner instalado. Estas son las piezas que no necesitan base de datos ni red y que serían las primeras en tenerlos.
El perfil que sale de un token
profileFromPayload() (artifacts/api-server/src/lib/neonAuth.ts) decide el nombre y el avatar de una cuenta nueva. Es una función pura sobre el payload del JWT:
const name = nameFromClaim || (email ? email.split("@")[0] : "") || "Usuario";
const avatarUrl =
typeof payload.image === "string"
? payload.image
: typeof payload.picture === "string"
? payload.picture
: null;
Casos que valdría la pena fijar: nombre con espacios alrededor, sin nombre pero con email, sin nada ("Usuario"), y image frente a picture.
La cabecera Authorization
readBearerToken() devuelve el token o null. Casos: sin cabecera, otro esquema (Basic …), Bearer sin token y un token con espacios.
La regla de las conexiones
Al apuntarse, cada pareja se guarda con el id menor primero para que la restricción única la reconozca venga de quien venga:
const u1 = Math.min(user.id, attendee.userId);
const u2 = Math.max(user.id, attendee.userId);
Hoy vive dentro de la ruta. Sacarla a una función (pairFor(a, b)) permitiría probar que pairFor(7, 3) y pairFor(3, 7) dan lo mismo.
En el front
getAccessToken()yextractJwt()(src/lib/auth.ts): qué fuente gana cuando hay varias, y que un valor que no es un JWT no se envía.- La política de reintentos del
QueryClient: tres veces un 401, ninguna un 404.
Con qué
Vitest encaja con el resto del workspace (Vite y ESM) y no necesita navegador para estas funciones.
14-testing-integration.md
Testing · integración
No hay tests de integración que hagan peticiones HTTP. La integración que sí se comprueba es la de los tipos: que el contrato, el código generado, la API y las páginas encajan.
La cadena del contrato
pnpm --filter @workspace/api-spec run codegen
pnpm run typecheck
- Orval lee
lib/api-spec/openapi.yamly regeneralib/api-client-react/src/generatedylib/api-zod/src/generated. tsc --buildcompila las librerías (db,api-zod,api-client-react).tsc --noEmitcompila cada app (conciergo,api-server,mockup-sandbox) yscripts.
Si una página usa un campo que el contrato ya no tiene, el paso 3 falla. Si cambia una respuesta en el contrato y no se regenera, los tipos generados no coinciden con lo que usan las páginas.
Estado a 20 de octubre de 2026
| Paquete | Resultado |
|---|---|
Librerías (tsc --build) | Sin errores |
artifacts/api-server | Sin errores |
artifacts/mockup-sandbox | Sin errores |
scripts | Sin errores |
artifacts/conciergo | 8 errores |
Los 8 errores de la web tienen tres causas:
| Errores | Dónde | Causa |
|---|---|---|
| 3 | chat.tsx (2) y user-profile.tsx | Los hooks generados piden queryKey cuando se les pasan opciones de consulta, y esas páginas pasan solo { enabled } |
| 4 | event-map.tsx | No están instalados los tipos de Google Maps (@types/google.maps), así que google.maps no existe para TypeScript |
| 1 | event-map.tsx | setOptions({ key, version: "weekly" }): la versión 2 del loader ya no acepta version |
Ninguno rompe el build: Vite compila sin comprobar tipos y Vercel no ejecuta typecheck. Pero pnpm run build, que empieza por el typecheck, sí falla.
Lo que el typecheck no ve
- El servidor no valida lo que recibe. Los tipos del cliente garantizan que el front envía bien los cuerpos, pero la API no los pasa por los esquemas Zod. Una petición hecha a mano con campos raros llega a Drizzle tal cual.
- Nada ejecuta SQL. Que una consulta de Drizzle compile no dice que devuelva lo esperado.
Un primer test de integración
app.ts exporta la app sin abrir un puerto, así que Supertest puede llamarla directamente. Con una rama de Neon creada para cada ejecución (o un Postgres local) y un JWKS de prueba con su propia clave, el primer caso sería el corazón del producto: dos personas se apuntan al mismo bolo y GET /matches devuelve una conexión para cada una; si una se borra, la conexión y sus mensajes desaparecen.
15-testing-e2e.md
Testing · flujos completos
No hay Playwright ni otra herramienta de navegador. Los flujos se recorren a mano en producción.
Recorrido como invitado
/: el titular «No dejes que un gran bolo se pierda» y los botones Entrar, Unirse y Explorar eventos./events: los bolos con sus géneros; tocar un género filtra la lista./events/map: un marcador por bolo; al tocarlo, su ficha con Inicia sesión para apuntarte./events/:id: la ficha completa con el número de asistentes.- La navegación solo enseña Eventos y Mapa.
Recorrido con dos cuentas
Hacen falta dos navegadores (o una ventana privada) con cuentas distintas:
- Cada cuenta se registra en
/auth/sign-up; la primera petición crea su fila enusers. - La cuenta A se apunta a un bolo. La B se apunta al mismo.
- Las dos ven la conexión en
/matches(como mucho 20 segundos después). - A escribe en el chat. B ve el mensaje al volver a la pestaña o al abrir el chat; en producción no llega al instante (ver Arquitectura).
- B se borra del bolo: la conexión desaparece para las dos y el chat ya no abre.
Por qué no está automatizado
- El login real pasa por Neon Auth y Google; un test necesita cuentas de prueba y un proyecto de Neon Auth que no sea el de producción.
- El mapa necesita una clave de Google Maps con facturación.
- Producción tiene una sola base de datos: un test que se apunta a bolos dejaría conexiones reales.
Cómo sería
Playwright contra la app en local (API con Socket.io y Vite), una rama de Neon nueva para cada ejecución y dos contextos de navegador con cuentas de prueba. El mapa se puede dejar fuera al principio: el flujo que importa es apuntarse, conectar y chatear.
16-testing-ci.md
Testing · CI
No hay ningún workflow que compile o pruebe el código. El único fichero en .github/workflows es portfolio-docs.yml, que pide al portfolio que se reconstruya cuando cambia docs/.
Lo que hace de puerta hoy
El build de Vercel en cada push a main. Si Vite o esbuild no pueden compilar, el deploy falla y producción sigue con la versión anterior. Un error de tipos no lo para, porque ese build no ejecuta tsc.
Un primer workflow
Sin secretos ni base de datos, solo con lo que ya existe:
name: CI
on:
push:
branches: [main]
pull_request:
jobs:
typecheck:
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v4
- uses: pnpm/action-setup@v4
- uses: actions/setup-node@v4
with:
node-version: 22
cache: pnpm
- run: pnpm install --frozen-lockfile
- run: pnpm run typecheck
pnpm/action-setup toma la versión de pnpm de packageManager. El job fallaría hoy por los 8 errores de la web (ver integración): arreglarlos es el paso previo.
Después
Cuando existan tests unitarios, un paso más en el mismo job. Los de integración y los flujos completos necesitarían una rama de Neon por ejecución y cuentas de prueba de Neon Auth guardadas como secretos.