Bitácora de hallazgos
Los fallos reales que aparecieron durante la construcción. No están aquí por anecdotario: casi todos son de una clase que se repite, y reconocerla vale más que el arreglo concreto.
Latentes: el código está mal pero nada falla todavía
Literales sin tipo contra una columna enumerada. El trigger que recalcula
el historial del cliente asignaba un CASE de literales sin tipo a una columna
customer_status. PostgreSQL lo resolvía como text y el UPDATE fallaba.
Estaba latente porque el trigger sólo corre al insertar una venta, y el esquema se validó sin ninguna. Registrar la primera venta era imposible, y habría aparecido el primer día de uso real.
Tickets con fecha de apertura en el futuro. El generador de datos partía de medianoche UTC —tarde del día anterior en Lima, fuera de horario— y saltaba al siguiente día hábil, que a veces era mañana. Un ticket abierto mañana queda fuera de toda ventana y descuadra cualquier cohorte. Se descubrió investigando por qué un porcentaje daba 101 %.
startWork sin guarda de estado. La API aceptaba arrancar un cronómetro
sobre un ticket cerrado. handling_minutes alimenta el indicador de atención
operativa: eso lo corrompía en silencio. Gatearlo sólo en la interfaz no
servía, porque las dos apps hablan con la misma API.
De medición: el número es correcto pero no mide lo que parece
resolvedPct por encima de 100 %. Aritméticamente explicable —resueltos y
creados son cohortes distintas— pero indefendible en un tablero. Se redefinió
como cohorte cerrada: de los tickets abiertos en el periodo, cuántos están
resueltos.
La meta de recompra tratada como valor absoluto. El documento dice «incremento mínimo del 15 %». Como tasa absoluta, cualquier valor realista cumplía siempre y el indicador no medía nada. Ver ADR-07 Metas absolutas y metas de incremento.
De duplicación: la misma regla escrita en varios sitios
«SLA en pausa» en tickets resueltos. Cuatro componentes tenían cada uno
su propia copia de la lista de estados en pausa, y las cuatro incluían
RESUELTO. La regla vive ahora en un solo componente y se deriva del token.
Tres barras de filtros distintas para el mismo trabajo. Hoy hay una, compartida por 13 pantallas.
De contraste: se ve bien y no cumple
19 pares por debajo del mínimo, encontrados por un test que mide en vez de suponer. Blanco sobre ámbar daba 2,45:1; el verde de WhatsApp, 1,98:1 sobre blanco. Ver Accesibilidad.
De marco: la herramienta no hace lo que parece
StrictMode gasta dos veces un refresh token de un solo uso. La sesión se perdía en cada recarga.
Un corte de red cerraba la sesión, porque el refresco borraba credenciales ante cualquier fallo en vez de sólo cuando el servidor rechaza el token.
Animated.View de Reanimated ignora className: NativeWind no instrumenta
componentes de terceros. Esqueletos invisibles y un Toast que no flotaba.
useAnimatedStyle sólo rastrea valores compartidos en el nivel superior de
su clausura. Uno anidado en un objeto hace que el estilo se calcule una vez y
nunca más. Sin error, sin aviso: una cabecera congelada para siempre.
El Modal de React Native monta fuera del proveedor de tema. En web, toda
hoja inferior resolvía sus colores contra :root: una hoja oscura sobre una
app clara.
Dos contenedores de scroll anidados en la tabla: era la causa de que la página entera se desplazara de lado.
Vite escuchaba sólo en IPv6. La app funcionaba en el navegador pero fallaba
desde curl, desde un script de salud o desde un móvil en la misma red.
De proceso
Una contraseña generada se transcribió mal desde la consola y dejó al
administrador fuera del sistema. Reveló que no había forma de restablecer una
clave sin ser administrador — el sistema no tenía salida si quien se quedaba
fuera era el administrador. De ahí salió crm-api set-password.