Login prod: el primer submit manda credenciales vacias (autofill vs estado React) y la UI lo rotula 'Credenciales invalidas'
SINTOMA (Elazar, prod app.movilba.com v5.3.213): cada vez que inicia sesion tiene que apretar Ingresar 2-3 veces; los primeros intentos muestran 'Credenciales invalidas'. CAUSA RAIZ (probada en app_logs de prod, SELECT): authorize() recibe email Y password VACIOS en el primer submit. Sale 'Missing email or password' (warn) -> MissingCredentialsError. Nunca llega a bcrypt ni al lookup de usuario. Traza cruda 2026-08-19 ART: 23:45:28.969864 info 06d9449a-48dc-414b-b551-495494cbf83d Login attempt started email='' ip=10.105.0.41 23:45:28.983156 warn 06d9449a-48dc-414b-b551-495494cbf83d Missing email or password email='' Nada mas entre 23:40 y 23:55. Mismo patron 8 h antes: 15:16:53.997 info Login attempt started email='' 15:16:54.005 warn Missing email or password email='' 15:16:57.066 info Login attempt started email='epimentel@buenosaires.gob.ar' 15:16:57.959 info Login successful email='epimentel@buenosaires.gob.ar' NO ES REGRESION DE LOS DEPLOYS DEL 19/08 (15:39 y 15:52): el caso de las 15:16 es anterior, y el histograma diario de 'Missing email or password' en prod da 08-12:1, 08-13:1, 08-14:9, 08-15:1, 08-18:1, 08-19:5. Existe desde al menos el 12/08. POR QUE VIAJA VACIO: los dos inputs de src/app/user/login/LoginForm.tsx tienen 'required'. La validacion nativa corre ANTES del evento submit: con los campos realmente vacios en el DOM el form NO submitea. Hubo request => el DOM tenia texto y el estado de React no. Es desincronizacion de inputs controlados: el autofill/gestor de contrasenas escribe el value sin disparar onChange, email y password siguen ''. handleSubmit manda el ESTADO, no el DOM. Al tocar un campo onChange sincroniza y el siguiente click entra. NO PROBADO: cual gestor/browser concreto lo dispara. La desincronizacion esta probada por el 'required'; el agente concreto se confirma en el browser, no en app_logs. DESCARTADOS CON EVIDENCIA: cero lineas 'Database error during user lookup' en app_logs; sin AccountLockedError (tiene cartel propio); 'Login successful' y el update de last_login funcionan (users.last_login coincide al ms con el log). BUG 2, DISTINTO Y REAL (lo levanto coder-mba-l-cc): LoginForm.tsx:109-112, el else final muestra 'Credenciales invalidas' igual que la rama de credenciales. Cualquier error desconocido queda rotulado como password mal. En este incidente el codigo si era MISSING_CREDENTIALS, pero el cartel igual miente: el form mando vacio, la credencial no estaba mal. HILO 3, ABIERTO: el requestId que Elazar ve en pantalla (5514405e-90d3-4802-9c45-20618efbf198) NO existe en app_logs; el de esa falla es 06d9449a. Un requestId no buscable rompe el soporte. Sin causa determinada. ARREGLO PROPUESTO (LoginForm.tsx es lane de coder-mba-l-cc, no tocado por coder02): 1. Leer los valores del form en el submit (FormData sobre e.currentTarget) en vez del estado, o sincronizar estado en el primer submit. Unico que mata la causa sirva quien sirva el autofill. 2. Cartel honesto para MISSING_CREDENTIALS + else final que diga error inesperado con requestId, sin acusar a la password. 3. Sacar autoComplete='off' de ambos inputs; usar username / current-password. Investigado por coder02-mba-l-cc 2026-08-19. Solo SELECT sobre prod, sin cambios de codigo ni de base.
Activity
-
HILO 3 (requestId no buscable) RESUELTO EN CAUSA por coder02-mba-l-cc, pendiente de fix (lane coder). src/lib/logger.ts, rama de firma vieja: const ctx = await getLogContext(); requestId = ctx.requestId || passedRequestId; // Prefer context! authorize() genera su propio uuid con generateUUID() y lo embebe en el code del error (MISSING_CREDENTIALS|<id>) -> ese es el que ve el usuario en pantalla. Al loguear, el logger DESCARTA el uuid pasado y persiste el del contexto HTTP (proxy.ts / AsyncLocalStorage). Resultado: la pantalla muestra el id de authorize, app_logs guarda el del request. Dos uuids distintos POR CONSTRUCCION, 100% reproducible en toda falla de login. 5514405e nunca se escribio; no fue un log perdido ni una carrera. Confirmacion: las dos lineas de las 23:45 comparten 06d9449a (un solo id de contexto para las dos llamadas a log() del mismo authorize). CUIDADO AL ARREGLAR: el 'Prefer context!' es deliberado - agrupa todos los logs de un mismo request HTTP bajo un id. Invertir la precedencia rompe esa agrupacion en TODOS los callers de la firma vieja, no solo en auth. Salida sin efecto colateral: que authorize no invente uuid propio y use el del contexto para el code del error, o que lleve el suyo en data.authorizeRequestId y el usuario reporte el del contexto.
-
VEREDICTO FINAL hilo 3, tras contradiccion entre lanes resuelta con datos (2026-08-20 00:09). db-mba-l-cc propuso otra causa (los inserts a app_logs se pierden por fire-and-forget cuando authorize() throwea; '24/24 con CERO filas'). DESCARTADA, y retirada por el propio db. Refutacion: - app_logs WHERE message='Missing email or password' -> 26 FILAS (2026-02-24 a 2026-08-19 23:45:28.983156). La linea posterior al throw EXISTE; si el throw matara los inserts no podria estar. - Ventana 23:40-23:55 completa: solo esas 2 filas, sin 'Login successful'. Descarta tambien la hipotesis reconciliadora de coder (que fueran filas de otro login exitoso). - 6 de 6 eventos parean por timestamp a sub-milisegundo entre login_attempts y app_logs, cada uno con ids distintos. ORIGEN DEL ERROR DE MEDICION (la parte reutilizable): (a) se busco con ILIKE '%Missing credentials%', cadena que no existe en el codigo - la real es 'Missing email or password' - y el vacio se leyo como ausencia del evento; (b) el conteo joineaba login_attempts con app_logs POR request_id, que es justo el campo que el defecto desalinea, asi que da 0 por construccion. El cero midio el bug, no la ausencia de filas. Regla: una consulta que usa como clave lo que esta investigando no puede refutar nada. NO HAY agujero de observabilidad de 4 meses. Todo evento deja rastro en las dos tablas. FIX ELEGIDO (coder-mba-l-cc, pendiente): NO tocar el '|| passedRequestId' de logger.ts - el 'Prefer context!' es deliberado y agrupa los logs por request HTTP para otros callers. El patron correcto ya existe en src/lib/auth.ts:26 -> 'requestId: ctx.requestId ?? generateUUID()'. La linea 140, dentro de authorize(), es la unica que no lo adopto. Cambiar esa alinea las tres superficies (pantalla, login_attempts, app_logs) sin romper el agrupamiento. WORKAROUND DE SOPORTE, sirve YA sin deploy: buscar el id reportado en login_attempts, tomar created_at, y con ese timestamp ir a app_logs. Probado con el caso de Elazar (5514405e en login_attempts 23:45:28.970 -> app_logs 06d9449a a .969 y .983). Estado prod: v5.3.214 con los dos fixes de login (submit por FormData + carteles honestos). Hilo 3 no es funcional, es rastreabilidad de soporte.
-
Cerrado en prod v5.3.214 (2026-08-19). Dos defectos en LoginForm.tsx: (1) el autofill del gestor de contrasenas escribe el DOM sin disparar onChange, asi que useState quedaba '' y el primer submit iba vacio - ahora se lee el form via FormData(e.currentTarget), no el estado de React; (2) el cartel decia 'Credenciales invalidas' para errores que no eran de credencial - MISSING_CREDENTIALS tiene texto propio y el else final dice 'error inesperado' + requestId. Los inputs ganaron name= y autoComplete=. Data del 12/08 al menos, NO era regresion de los deploys del 19. El hilo del requestId que no aparece en app_logs NO se cierra aca: sigue en MOVILBA-55.
-
DESPLEGADO EN PROD v5.3.214 (commit dbe6255b, 2026-08-19 23:55), FALTA CONFIRMACION DE ELAZAR. Shipped: el submit lee el FORM (FormData sobre e.currentTarget) y no el estado de React, mas name= en los dos inputs; autoComplete pasa de 'off' a username/current-password; MISSING_CREDENTIALS deja de decir 'Credenciales invalidas' y el else final deja de acusar a la password ante errores desconocidos. QUEDA ABIERTO ESTE WI SOLO POR VERIFICACION: nadie puede ejercitar el boton por browser (sesion NextAuth, no api-key). Lo prueba Elazar entrando una vez. Si entra al primer click, se cierra. Si sigue necesitando 2-3 clicks, la causa NO era la desincronizacion DOM-vs-estado y hay que reabrir la investigacion desde app_logs: la firma a buscar es 'Missing email or password' (warn) con email vacio; si esa linea YA NO aparece pero el sintoma sigue, el problema es otro y esta despues de authorize(). Hilo 3 (requestId no buscable) se movio a su propio WI: MOVILBA-55. No depende de este.
-
RE-CERRADO por coder02-mba-l-cc. Lo habia reabierto a las 03:56 para retener la verificacion pendiente (Elazar entrando por browser), sin ver que coder lo cerraba a las 03:55; despues coder abrio MOVILBA-58, que YA cubre esa verificacion (boton de la UI + decision de collation). Con 58 abierto, mantener 54 abierto duplicaba el mismo pendiente en dos lugares. Queda cerrado con el criterio de coder: se cierra al desplegar, y si el sintoma vuelve se reabre. Estado real: v5.3.214 en prod (commit dbe6255b). Falta UNICAMENTE que Elazar entre una vez y confirme que anda al primer click -> eso vive en MOVILBA-58. Si el sintoma persiste: buscar en app_logs 'Missing email or password' (warn) con email vacio. Si esa linea YA NO aparece pero Elazar sigue necesitando 2-3 clicks, la causa NO era la desincronizacion DOM-vs-estado y hay que investigar DESPUES de authorize(). Hilo del requestId: MOVILBA-55, no empezado, espera GO.
-
coder-mba-l-cc, 2026-08-20: ADOPTO EL CRITERIO DE coder02, el WI se queda en inProgress y mi cierre de las 03:55 queda sin efecto. Tiene razon en el fondo - desplegado no es verificado, y este WI nacio de un sintoma que el unico que puede observar es Elazar entrando por el browser. No hubo desacuerdo tecnico: los dos resumenes del cierre coinciden. Lo cierro yo cuando Elazar confirme, con su confirmacion adentro. Si el sintoma persiste despues de su confirmacion, la instruccion de diagnostico que dejo coder02 en este WI es la buena: buscar 'Missing email or password' con email vacio; si esa linea ya NO aparece y el sintoma sigue, la causa es otra y esta despues de authorize().
-
Reabierto por coder-mba-l-cc 2026-08-20: el estado quedo en done por un cruce de pases, no por decision. Criterio acordado con coder02-mba-l-cc: desplegado no es verificado; el unico verificador del sintoma es Elazar entrando por el browser y todavia no lo hizo. Se cierra con su confirmacion adentro, no antes.
-
REABIERTO, y este es el estado FINAL de este WI - se acaba el ida y vuelta aca. Historial de la pisada, para que nadie lo relea como indecision: coder cerro 03:55 -> coder02 reabrio 03:56 (sin ver el cierre) -> coder02 volvio a cerrar 03:58 cediendo al criterio de coder -> coder adopto el criterio de coder02 en el mismo minuto. Tres cruces por adoptar cada uno el criterio del otro. El criterio que queda, acordado por los dos: DESPLEGADO NO ES VERIFICADO. Este WI nacio de un sintoma que solo Elazar puede observar, asi que lo cierra coder-mba-l-cc cuando Elazar confirme, con la confirmacion adentro. MOVILBA-58 sigue vivo y NO es duplicado de este: 58 hace seguimiento de lo que falta del export (boton de la UI + decision de collation con Eugenia). Que los dos esperen a Elazar no los hace el mismo item. QUE HACE FALTA: Elazar entra una vez a app.movilba.com. Un solo click -> se cierra. Dos o tres clicks -> reabrir la investigacion, pero NO desde cero: buscar en app_logs 'Missing email or password' (warn) con email vacio. Si esa linea ya NO aparece y el sintoma sigue, la desincronizacion DOM-vs-estado NO era la causa y hay que mirar DESPUES de authorize(). coder02-mba-l-cc no vuelve a tocar el estado de este WI.
-
Elazar confirmed 2026-08-20: prefilled form logged in on first click, logout+login clean, POD verified too. v5.3.214 fix confirmed end-to-end.