Ficha y exportaciones emiten carroceriaCategoria con etiqueta de catálogo
Two independent query sites emit carroceriaCategoria as valor_opcion instead of nombre_mostrable: src/repositories/vehiculos.ts (findById, ficha and per-vehicle PDF) and src/lib/exports/vehiculos.ts (shared bulk builder for XLSX/PDF and GET /api/v1/exports/vehiculos). Change both label projections only; preserve the catalog-qualified joins and legacy fallback, with no borrado_en filter. Add one regression test per site and observe each fail against current bytes before the fix, then pass after. Do not change unidadPropulsora (view-body measurement belongs to db03) or the Parque JSON API, which omits these fields. The published export route's existing categoriaCarroceria column will change value from valor_opcion (e.g. M3) to nombre_mostrable (e.g. M3 - Colectivos); key/header/schema stay unchanged. Reviewed docs/reference/parque-movil-api/ before scope.
Activity
-
Implemented both read paths only. Exact paths and SHA-256: src/repositories/vehiculos.ts 7658742f52769f5a25a034e4bae476ca4b0e4dc55ac7ede866404bbeac5e01e3; src/lib/exports/vehiculos.ts 8cf7454cd21d56cb365bfe8b1fae1a64b66f57c99344397178b7648fb9200499; src/repositories/vehiculos-carroceria-label.test.ts 28f4e4190031f9206423a9266e1fc9b705e0c3d3bfb268eee463a79abb5cbd0c; src/lib/exports/vehiculos-carroceria-label.test.ts 418cdfc33f726de29cb5e4a3afc67244d047a05f7b09388ef283b296010c99d0. Both added regression tests failed against the original valor_opcion projections (one failure per site), then passed after the projection-only changes (2/2 tests, 9 assertions). Catalog-qualified lookups and legacy fallback remain; no borrado_en predicate added. External GET /api/v1/exports/vehiculos retains its field/header but categoriaCarroceria value changes from valor_opcion to nombre_mostrable. No unidadPropulsora, JSON API, DDL, or other paths changed. Awaiting audit; no full suite gates run.
-
Two projections changed and both regression tests observed failing before and passing after; awaiting audit.
-
audit pass: CODE ONLY, 4 pins re-hashed vs the RESTORED tree: repositories/vehiculos 7658742f, exports/vehiculos 8cf7454c, 2 new source-text tests. One token per impl file, the join, the legacy fallback, the absent borrado_en, both tests seen failing vs HEAD, src-wide census. NO HTTP, XLSX, PDF or SQL.
payload
{"scope":"CODE ONLY, 4 pins re-hashed vs the RESTORED tree: repositories/vehiculos 7658742f, exports/vehiculos 8cf7454c, 2 new source-text tests. One token per impl file, the join, the legacy fallback, the absent borrado_en, both tests seen failing vs HEAD, src-wide census. NO HTTP, XLSX, PDF or SQL.","outcome":"pass","evidence":"audit-mba-l-cc, 2026-10-08. PASS. Four pins re-hashed vs the RESTORED tree, all exact: repositories/vehiculos 7658742f, exports/vehiculos 8cf7454c, label tests 28f4e419, 418cdfc3. Measured from stash@{0} and ^3 while parked, read-only.\n\nDIFF: exactly ONE changed line per impl file, SELECT o.valor_opcion -> o.nombre_mostrable, at repositories/vehiculos.ts:511 and exports/vehiculos.ts:137; diff -u whole-file shows nothing else. Measured intact on the candidate, not assumed: the qualification grupo_nombre='vehiculo' AND campo_nombre='categoria_regulatoria'; the fallback o.valor_opcion = h.carroceria_categoria, which must STAY valor_opcion as it matches stored canonical text; NO borrado_en filter, correct per catalogos.md:23 so 123's (borrado) still reaches the user.\n\nCONTROL IS A MUTANT: each test run beside the HEAD copy of its subject, then the candidate, unpiped. repositories rc=1 / rc=0; exports rc=1 / rc=0. Both fail at :11, the nombre_mostrable assert, the right reason. Both run on BOTH ship paths: run-source-tests.mjs globs /\\.test\\.tsx?$/ under src, test:source is dev.sh:175 and prod.sh:59 with exit 1. My 23:30:50Z gates covered all four (mtime 23:17:59Z): test:source 68/68, up from 66 which is exactly these two, test:tools 118/118, eslint 0, typecheck 0 whole-tree.\n\nLIMIT: node aborts at first failure, so :13 :15 :17 :19-20 were never seen to fail; each needs its own mutant in its own file. CENSUS: 0 SELECT o.valor_opcion left under src/; all three app label projections on nombre_mostrable (:511, :137, vehiculos-historico:47). v_export_libro keeps valor_opcion by 574's header.\n\nFOR CODER03: the item's scope says the Parque JSON API is unchanged, but api/export/route.ts -> parqueMovilExportService.ts:6 calls fetchVehiculosExportRows, the edited function, and parque-movil-live.ts:28 maps carroceriaCategoria from it, thus that download also moves M3 -> M3 - Colectivos. Correct per the 09-24 ruling; the SENTENCE is wrong. /api/v1/** untouched. 3 more in the note.","reportRef":null} -
# MOVILBA-769 — audit verdict: PASS on the four files. audit-mba-l-cc, 2026-10-08 src/repositories/vehiculos.ts 7658742f52769f5a25a034e4bae476ca4b0e4dc55ac7ede866404bbeac5e01e3 src/lib/exports/vehiculos.ts 8cf7454cd21d56cb365bfe8b1fae1a64b66f57c99344397178b7648fb9200499 src/repositories/vehiculos-carroceria-label.test.ts 28f4e4190031f9206423a9266e1fc9b705e0c3d3bfb268eee463a79abb5cbd0c (untracked) src/lib/exports/vehiculos-carroceria-label.test.ts 418cdfc33f726de29cb5e4a3afc67244d047a05f7b09388ef283b296010c99d0 (untracked) **Where these bytes are, and how I measured them.** I did the measurement while coder03's `stash@{0}` ("coder03 park for 389 prod deploy") held the whole tree, reading the two tracked files from `stash@{0}` and the two untracked tests from `stash@{0}^3` with `git show`, without popping, applying or otherwise touching the stash. The park has since been restored, and **I re-hashed all four against the working tree after the restore**: all four are byte-identical to the pins above, which are also the pins my 23:30:50Z gate run covered. So the verdict holds against on-disk bytes, not only against stash objects. ## 1. The change is one token per file - carroceriaCategoria: sql`(SELECT o.valor_opcion FROM rutap.vehiculo_historial_normalizado h JOIN public.opciones o ON ...)` + carroceriaCategoria: sql`(SELECT o.nombre_mostrable FROM rutap.vehiculo_historial_normalizado h JOIN public.opciones o ON ...)` `src/repositories/vehiculos.ts:511` (ficha + per-vehicle PDF) and `src/lib/exports/vehiculos.ts:137` (shared bulk builder for XLSX/PDF and the published export route). `diff -u` over the whole file shows **one changed line each and nothing else**. Both preserve what the item said to preserve, measured on the candidate bytes rather than assumed: - the catalog qualification `o.grupo_nombre = 'vehiculo' AND o.campo_nombre = 'categoria_regulatoria'` is intact, so the join cannot resolve to a foreign catalog's label; - the legacy fallback `(h.carroceria_categoria_id IS NULL AND h.origen = 'app' AND o.valor_opcion = h.carroceria_categoria)` is intact. Note it must stay `valor_opcion` on that side — it matches the stored canonical text, written by `src/services/vehiculos.ts:525`, not a display label; - **no `borrado_en` filter was added**, which is correct: an unfiltered `borrado_en` is the rule for the label of a stored FK (`.claude/rules/catalogos.md:23`), and it is what makes 123's `(borrado)` suffix reach the user instead of a blank. ## 2. The control: each test seen FAILING against HEAD, for the right assertion Run in a scratch directory, no checkout edit, each test beside the HEAD copy of its own subject and then beside the candidate copy: | test | vs HEAD impl | vs candidate impl | |---|---|---| | `repositories/vehiculos-carroceria-label.test.ts` | **rc=1**, `AssertionError: ficha and per-vehicle PDF must resolve the human catalog label` | rc=0, "4 assertions passed" | | `lib/exports/vehiculos-carroceria-label.test.ts` | **rc=1**, `AssertionError: bulk XLSX/PDF and the published export API must resolve the human catalog label` | rc=0, "5 assertions passed" | Both fail at the `:11` `assert.match` for `SELECT o.nombre_mostrable`, which is the right reason — HEAD has `valor_opcion` there. I ran these without a pipe, so the `rc` is the program's. Both run on both ship paths: `tools/run-source-tests.mjs` collects `/\.test\.tsx?$/` under `src`, and `test:source` is `mba-deploy-dev.sh:175` and `mba-deploy-prod.sh:59`, each with `exit 1`. My 23:30:50Z run was `test:source` 68/68 — up from 66, which is exactly these two new files — and `test:tools` 118/118, `eslint` 0 on both implementation paths, `typecheck` 0 whole-tree. ## 3. Findings — none blocks the PASS 1. **A third consumer changes value and the item says it does not.** The item's scope reads "Do not change ... the Parque JSON API, which omits these fields". Measured: `src/app/api/export/route.ts` -> `src/services/parqueMovilExportService.ts:6` calls `fetchVehiculosExportRows(false)` — **the very function this diff edits** — and `src/lib/exports/parque-movil-live.ts:28` maps `carroceriaCategoria: row.categoriaCarroceria`. So the Parque Móvil download's `carroceriaCategoria` goes from `M3` to `M3 - Colectivos` too. That is the **correct** outcome under the 2026-09-24 ruling that no surface shows raw book text for a catalogued field, and it is consistent with the other two. The defect is the scope statement, not the code: a value change on a download nobody named is the kind of thing a consumer discovers in production. `/api/v1/parque/*` is genuinely untouched — no file under `src/app/api/v1/**` mentions this field at all — so the sentence is right about the versioned tree and wrong about `/api/export`. 2. **These are source-text tests, not behavioural tests.** Both `readFileSync` their subject and regex it. They pin the token on every ship path, which is real, but they would pass against a query that never runs, fail against a harmless reformat, and cannot see a regression that arrives through a view body or a different code path. The repo has a behavioural precedent for the rendering layer (`src/components/HistoryPeriodContent.test.tsx` asserts `renderToStaticMarkup` output) and `tools/tests/*.cjs` exercise behaviour. Proportionate for a one-token change; worth knowing the class. 3. **Only `:11` of each test was demonstrated.** Node's runner aborts at the first failure, so the guard assertions — catalog qualification `:13`, legacy fallback `:15`, the `doesNotMatch` on `SELECT o.valor_opcion` `:17`, and `:19-20`'s `categoriaCarroceria: ssot?.categoria ?? null` wiring — **were never seen to fail against anything**. They are guards against collateral damage and by design pass on both sides, so each needs its own mutant (drop the `grupo_nombre` clause; drop the fallback) in its own carved file to become a control. 4. **The app-side label question for this field now closes, measured.** In the candidate tree there are **zero** `SELECT o.valor_opcion` occurrences left anywhere under `src/`, and all three label projections read `nombre_mostrable`: `repositories/vehiculos.ts:511` (769), `lib/exports/vehiculos.ts:137` (769), `app/api/reportes/vehiculos-historico/route.ts:47` (765). `services/vehiculos.ts:410,840` are write-side validation joins, not label projections. `rutap.v_export_libro` still emits `valor_opcion` by the decision recorded in 574's migration header, which is outside this item. ## 4. NOT EXERCISED - **No HTTP request, no XLSX, no PDF, no ficha opened.** Nothing rendered, nothing downloaded. The verdict is a reading of two SQL projections plus two source-text tests I ran. - **No SQL executed for this item.** I did not run either projection against PROD or DEV, so the label a soft-deleted or app-origin row actually renders is unverified by execution here. The `h.origen = 'app'` fallback branch in particular is untested by anything in this diff. - **The published export route's emitted column was not measured.** `docs/reference/parque-movil-api/` does not document `categoriaCarroceria`, so I could not check the change against a written contract; header, key and schema are unchanged by inspection only. - **My gates ran before the park and were not re-run after the restore.** They ran on exactly these four hashes, and the restored tree still carries exactly these four hashes, so no re-run is owed for these files — but the rest of the tree moved in between (389 shipped as 7b8b1671, other lanes' artifacts came back), and I did not re-measure the whole-tree gates against the current tree. - Findings 2 and 3 name controls that do not exist; nothing in this item was re-run after I wrote them.
-
Closed after shipment in patch 217 on PROD, DEV, and pod. Both audited projections now use nombre_mostrable: ficha/per-vehicle PDF and the shared bulk XLSX/PDF builder plus published GET /api/v1/exports/vehiculos download. The external export keeps its key/header but the emitted label changed; this is a published download value change. The Parque Móvil JSON API itself is unaffected and omits these fields. Audit covered the four pinned code/test files only: no HTTP request, ficha, XLSX, PDF, or SQL was exercised.