basquetWi + New ticket
movilba MOVILBA-115

Diez comparaciones contra la ETIQUETA de un catalogo en ocho vistas, no tres: soft-deletear una opcion vacia pantallas Y pone en cero el health endpoint del contrato SOTAMIT, en silencio

Done high cmcoder-mba-l-cc

VIOLA LA REGLA PERMANENTE DE ELAZAR SOBRE opciones: las columnas de negocio guardan el ID entero y las comparaciones van por el ID, nunca contra la etiqueta. QUE PASA. v_vehiculos_list proyecta estado_actual asi: CASE WHEN o.borrado_en IS NOT NULL THEN o.nombre_mostrable||' (borrado)' ELSE o.nombre_mostrable END O sea que la propia vista REESCRIBE la etiqueta cuando la fila del CATALOGO se soft-deletea. Y las tres listas filtran contra esa cadena: v_vehiculos_list_alta WHERE estado_actual::text = 'Alta' OR estado_actual IS NULL v_vehiculos_list_baja WHERE estado_actual::text ~~* 'Baja%' v_vehiculos_list_mantenimiento WHERE estado_actual::text = 'Mantenimiento' ASIMETRIA QUE IMPORTA Y QUE CAMBIA LA PRIORIDAD POR PANTALLA (verificado por coder-mba-l-cc, no estaba en el reporte original): _baja usa un LIKE de prefijo, asi que 'Baja (borrado)' SIGUE matcheando - se salva POR ACCIDENTE, no por diseno. _alta y _mantenimiento usan igualdad exacta y se rompen. Dos de tres. COMO SE ROMPE: se soft-deletea la opcion, la etiqueta pasa a llevar el sufijo ' (borrado)', la igualdad deja de matchear y la pantalla renderiza una lista VACIA. Sin error, sin stack trace, sin log. El usuario ve 'no hay vehiculos'. Si se soft-deletea la opcion 116 ALTA, la lista principal de vehiculos pasa de 1600 filas a cero, en silencio. EL MECANISMO YA DISPARO EN ESTE MISMO CATALOGO, no es hipotetico: la opcion 118 TRANSFERENCIA fue soft-deleteada el 2025-10-13 y HOY renderiza como 'Transferencia (borrado)'. Cualquier vista que filtrara por 'Transferencia' ya estaria devolviendo cero. Que 116 y 117 no hayan mordido es solo porque nadie los borro todavia. EL CERO DE HOY EN MANTENIMIENTO ES LEGITIMO y hay que decirlo para que nadie 'arregle' un sintoma inexistente: la opcion 119 existe y NO esta borrada, y los vehiculos vivos tienen solo estado 116 (1600) y 117 (138), que suman 1738. Realmente no hay vehiculos en mantenimiento. El defecto es que la pantalla NO PUEDE DISTINGUIR eso de un filtro roto - un cero honesto que es estructuralmente incapaz de probar que lo es. ARREGLO: WHERE estado_actual_id = <id> en las tres vistas. El ID ya esta proyectado en v_vehiculos_list, al lado de la etiqueta. Es inmune a ediciones de nombre_mostrable y al sufijo de borrado. Alta=116, Baja=117, Mantenimiento=119 - confirmar contra opciones antes de escribir, no copiar de aca. CUIDADO AL TOCARLAS: v_vehiculos_list esta en el nivel 2 de la cadena de dependencias de vehiculo_historial y las tres listas en el nivel 3 (ver MOVILBA-114). No cambia el tipo de ninguna columna, asi que CREATE OR REPLACE VIEW alcanza y los GRANTs sobreviven. HALLAZGO SECUNDARIO DE LA MISMA LECTURA: v_vehiculos_list joinea vehiculos_datos_colectivos SIN el filtro vdc.borrado_en IS NULL, que v_vehiculos_detalle si aplica. Hoy no multiplica (2394 filas vdc sobre 2394 vehiculo_id distintos, 0 con mas de una) pero NO hay constraint unico sobre vdc.vehiculo_id, solo un indice comun: el 1:1 lo sostienen los datos, no la construccion. Segunda tabla, misma forma que vehiculo_historial.

Sub-tickets

No sub-tickets.
+ Add sub-ticket

Activity

  • wi-cli-venus created · 2026-08-21
  • wi-cli-venus titleChanged · 2026-08-21
    Diez comparaciones contra la ETIQUETA de un catalogo en ocho vistas, no tres: soft-deletear una opcion vacia pantallas Y pone en cero el health endpoint del contrato SOTAMIT, en silencio
  • wi-cli-venus commented · 2026-08-21
    BARRIDO COMPLETO de db02-mba-l-cc sobre TODAS las vistas publicas de prod (regex sobre pg_get_viewdef), no solo las tres listas. SON DIEZ SITIOS EN OCHO VISTAS: v_api_status estado::text = 'Alta' v_vehiculos_export tramite = 'Alta' x2 v_vehiculos_list_alta estado_actual::text = 'Alta' v_vehiculos_list_mantenimiento estado_actual::text = 'Mantenimiento' v_ui_vehiculo_subtipo_colectivo descripcion = 'Colectivo' v_instalaciones valor_opcion::text = 'AR-C' x2 v_ui_provincias valor_opcion::text = 'AR-C' v_instalacion_historial evento_tipo::text = 'ACTUALIZACION' EL GRAVE NO ES UNA PANTALLA: v_api_status.vehicles_activos cuenta v_vehiculos_api_public WHERE estado = 'Alta', y la cadena esta trazada - v_vehiculos_api_public.estado sale de v_vehiculos_detalle.estado_actual, cuya definicion lleva WHEN o.borrado_en IS NOT NULL THEN nombre_mostrable || ' (borrado)'. O sea que el sufijo se propaga hasta el CONTRATO EXTERNO DE SOTAMIT. Al soft-deletear la opcion 116: vehicles_total sigue en 1738 y vehicles_activos pasa a CERO, sin error en ningun lado. Un endpoint de salud informando cero vehiculos activos con un total sano - eso se LEE COMO SENAL, asi que es peor que una lista vacia. Y el contrato externo empezaria a emitir la cadena literal 'Alta (borrado)' hacia SOTAMIT. LA ASIMETRIA DE _baja ES PEOR DE LO QUE YO HABIA DICHO. Yo la llame inmune por accidente. db02-mba-l-cc completa el cuadro: ~~* 'Baja%' sobrevive al sufijo, si - pero entonces la pantalla INCLUYE vehiculos cuya fila de catalogo fue soft-deleteada, justo donde sus dos hermanas los EXCLUYEN. Tres pantallas de una misma familia, tres semanticas distintas, ninguna por el ID. TODO ESTO ES LATENTE HOY, verificado en vivo y no razonado: opcion 118 Transferencia SI esta soft-deleteada (2025-10-13 21:45:06), y 116 Alta / 117 Baja / 119 Mantenimiento NO lo estan. v_vehiculos_api_public.estado hoy: 'Alta' 1600, 'Baja' 138, suma 1738 y coincide con la flota viva. Ningun sitio esta mal AHORA. La exposicion esta a un soft-delete de distancia, y soft-deletear una fila de catalogo es una operacion normal que ya paso una vez. ALCANCE DEL ARREGLO: las listas y v_api_status/v_vehiculos_export van por ID a traves de sus propias cadenas. Los cuatro sitios no-vehiculo (v_instalaciones, v_ui_provincias, v_ui_vehiculo_subtipo_colectivo, v_instalacion_historial) HAY QUE LEERLOS ANTES de asumir que resuelven contra opciones - 'AR-C' y 'ACTUALIZACION' pueden ser otro catalogo o ninguno. No tratarlos como el mismo arreglo sin verificar.
  • wi-cli-venus packet · 2026-08-21
    ALCANCE FINAL: 4 SITIOS, NO 10. Paquete escrito y APLICADO EN DEV, prod sin aplicar. archivo: docs/migrations/2026-08-21-movilba-115-comparaciones-por-etiqueta-de-catalogo.sql md5: b99d75db97cc965d74ef9ef02eb8fd94 - es el hash que tiene que nombrar el GO de prod. commit 540420da, v5.4.20. Renombrado desde ...movilba-114-... con git mv, asi que el rename esta en la historia y no es una correccion silenciosa. EL DISCRIMINADOR, que vale mas que la lista: el CASE de soft-delete le agrega ' (borrado)' a nombre_mostrable y NUNCA a valor_opcion. Solo estan expuestas las comparaciones DERIVADAS DE nombre_mostrable. LOS 4 QUE SI SON EL DEFECTO Y ESTAN ARREGLADOS: v_vehiculos_list_alta / _baja / _mantenimiento - filtran por estado_actual_id resuelto desde opciones, ahora CON AND borrado_en IS NULL, asi que el predicado es identico al de uq_opciones_grupo_campo_valor_active y el subquery es single-row POR CONSTRUCCION y no por los datos de hoy. v_api_status.vehicles_activos - cuenta por id desde v_vehiculos_detalle, NO desde v_vehiculos_api_public: api_public no proyecta el id de estado y agregarle una columna cambiaria el contrato CONGELADO de SOTAMIT. detalle tiene la poblacion identica (1738 y 1738 en prod, EXCEPT 0 en las dos direcciones, count 1600 por los dos lados). LOS 6 QUE NO SON EL DEFECTO, y por que - ESTO QUEDA ESCRITO O ALGUIEN LOS REABRE. Verificado por coder02-mba-l-cc en la lectura previa y confirmado contra el esquema de prod por db-mba-l-cc y db02-mba-l-cc por separado, porque sacar sitios de un P1 es la direccion que duele si esta mal: v_instalaciones x2 y v_ui_provincias - comparan p.valor_opcion::text = 'AR-C'. Esa ES la clave canonica, nunca se sufija y es estable bajo soft-delete. Son el UNICO patron del barrido que YA ESTABA CORRECTO. Convertirlos a id seria churn y, peor, pareceria un arreglo y ensenaria la leccion equivocada. v_ui_vehiculo_subtipo_colectivo - compara vtt.descripcion = 'Colectivo' sobre vehiculos_tipos_transporte, tabla propia de 6 filas. NO es opciones. v_instalacion_historial - compara h.evento_tipo = 'ACTUALIZACION', varchar NOT NULL con vocabulario propio. NO es opciones. v_vehiculos_export x2 - comparan r.tramite = 'Alta' sobre el CTE de rutap.v_rutap_latest_norm: dato FUENTE de RUTAP, no lookup de catalogo. Rutearlo por opciones seria un arreglo equivocado que pasa sus propios gates. Los dos ultimos vocabularios fuera de opciones son una violacion real de la regla permanente y estan filados aparte: MOVILBA-119. No bloquean este item. LO QUE ESTE PAQUETE NO ARREGLA Y HAY QUE LEER ANTES DE DARLO POR CERRADO: ARREGLA EL CONTEO, NO EL STRING. Si se soft-deletea la opcion 116, SOTAMIT sigue recibiendo 'Alta (borrado)' en el campo estado - v_vehiculos_api_public hereda el sufijo de v_vehiculos_detalle y el paquete no lo toca, porque es un contrato externo congelado y cambiarlo es decision de Elazar, no de una migracion. Filado aparte: MOVILBA-120. El desempate inerte de creado_en de MOVILBA-73 sigue intacto. Las vistas de lista NO quedan "hechas" cuando esto aplique. GATES: seis en verde, en ensayo y en aplicacion. GATE 1 compara el SET por EXCEPT en las dos direcciones para las tres vistas de lista, y gatea v_api_status por sus cifras porque un agregado de una fila no tiene SET que comparar. GATE 2 y GATE 3 extendidos a las cuatro relaciones. GATE 0 quedo DELIBERADAMENTE sin la clausula de liveness, con comentario que lo dice y explica la asimetria: la misma omision es correcta ahi y es un defecto en los subqueries de las vistas, y la diferencia es CUANDO corre cada uno. CONTROL POSITIVO REESCRITO: corre el CASE real contra la opcion 118 TRANSFERENCIA y ABORTA si no existe ninguna opcion soft-deleted contra la cual correr, en vez de imprimir OK habiendo medido nada. El resolve es SELECT INTO STRICT con la clausula de liveness y handlers explicitos de NO_DATA_FOUND y TOO_MANY_ROWS. La version anterior comparaba dos literales de string y no podia fallar. GATE 0 acepta TRES estados de entrada, cada uno auto-identificado por NOTICE, y rechaza cualquier mezcla: (a) filtrando por etiqueta, que es prod; (b) por id sin la clausula, que es el v1 que dev tenia; (c) ya aplicado correctamente, que aborta diciendo "ya aplicado, nada que hacer". Sin (c) el paquete no era idempotente contra su propia salida y abortaba acusando a un tercero por un estado que el mismo habia creado. VERIFICADO EN DEV DESPUES DE APLICAR: v_api_status 1741 total / 1603 activos / 7 keys, y _alta 1603 + _baja 138 + _mant 0. PENDIENTE: pre-lectura de audit-mba-l-ca antes de cualquier GO de prod. El GO se pide nombrando CUATRO SITIOS y a v_api_status como el de cara externa - un GO cubre lo DESCRITO.
  • wi-cli-venus audit-finding · 2026-08-21
    Pre-lectura de audit-mba-l-ca: CLEAR sobre el commit d14803a7 / v5.4.24, md5 FINAL fae8a8a4610e95f6cb7c2c6a0bca2ac5 (d5f0c690 y b99d75db quedan MUERTOS). El dead-code del GATE 1 (loop de self-EXCEPT) esta eliminado y los chequeos de los 4 sitios leen correctos. Dev en verde; gates 1-4 NO corridos en prod porque el rol de app no tiene permiso de DDL - documentado como limite de cobertura. Pendiente antes del pedido de GO: ensayo de prod de db02-mba-l-cc en ventana tranquila contra estos bytes finales, porque el ensayo toma AccessExclusiveLock sobre 4 vistas en el camino de la lista de vehiculos. El GO se pedira nombrando fae8a8a4, 4 sitios, y v_api_status como el de cara externa.
  • wi-cli-venus completed · 2026-08-21
    APLICADO EN PROD por db02-mba-l-cc, 11:16 ART, con GO verbatim de Elazar ('go', CLI, cubriendo ensayo+apply con la exposicion de locks descrita). Paquete fae8a8a4, hash re-verificado en disco antes de los DOS corridas. Ensayo ROLLBACK verde y apply en la misma sesion, gates identicos en ambas: GATE 0 estado (a), CONTROL POSITIVO contra la opcion 118 real, GATE 1 mismo SET, GATE 2 cero comparaciones contra estado_actual y los 4 subqueries con liveness, GATE 3 GRANTs, GATE 4 101 relaciones consultables. POST-APPLY verificado como svc_nextjs (no superuser): _alta 1600 / _baja 138 / _mant 0; v_api_status total 1738 / activos 1600 / keys 7. Runtime-verified, NO UI-walked. Los 4 sitios que ERAN el defecto estan cerrados; los 6 que no lo eran estan documentados en el item y cubiertos por MOVILBA-119 (vocabularios fuera de opciones) y MOVILBA-120 (el string del contrato SOTAMIT, decision de Elazar). Nota de db02 para el proximo lector: valor_opcion sigue apareciendo en el texto de las 4 definiciones y NO es el defecto de vuelta - es el subquery de resolucion, el fix; GATE 2 es la asercion que vale, no un grep del cuerpo. Telemetria de sync de v_api_status en NULL sin medicion previa de ambos lados - no atestiguable como pre-existente ni como regresion; si debe poblarse es pregunta aparte. introspect pendiente de correr para que el WHERE quede en el repo.
bug
2026-08-21 by wi-cli-venus
2026-08-21
2026-08-21 14:16