El catalogo opciones tiene que ser IDENTICO en DEV y PROD, y quedar atado para que no vuelva a divergir
Elazar, CLI 2026-09-22: 'no podemos tener opciones en un catalogo en dev y otras en prod'. Lo del disco clonado era una figura; el pedido REAL y acotado es este: una sola lista de opciones, igual en las dos bases. NO se clona la base, NO se toca la version de Postgres, NO se cambia la direccion del import. Todo eso sale de scope. CASO CONOCIDO que lo dispara: opciones id 119 MANTENIMIENTO esta podada en DEV y sigue viva y sin marcar en PROD. Medido por db03 el 2026-09-22: public.v_estado_colectivo emite 116/117 en DEV y 116/117/119 en PROD. Es lo mismo que ya venia anotado como MOVILBA-77, que pasa a ser un hijo de este item y no un ticket cosmetico suelto. QUE HAY QUE HACER: 1. Diff COMPLETO de rutap/public opciones entre las dos bases, fila por fila, por clave de negocio (grupo_nombre, campo_nombre, valor_opcion) y NO por id: un mismo id puede ser otra cosa en cada base y ahi es donde se esconde el error de verdad. Reportar tres listas: solo-DEV, solo-PROD, y misma clave con distinto id / distinta marca de borrado / distinta etiqueta. 2. Mismo diff sobre lo que cuelga del catalogo y se sirve a la UI: v_opciones, las v_ui_*, y las matviews tipo v_estado_colectivo. Una matview desactualizada alcanza para que las dos bases difieran con el catalogo ya igualado. 3. Igualar. Las podas ya decididas y verificadas en DEV se REPLICAN a PROD con el mismo instrumento que las aplico en DEV, no con uno re-derivado (regla de Elazar del 09-19; eso no pide GO nuevo). Si aparece una fila que existe solo en PROD y nadie decidio nada sobre ella, NO se borra: se levanta como decision. 4. Dejarlo atado: un gate que compare los dos catalogos y falle si difieren, corrible a mano y sin credenciales de PROD adentro del repo. Sin esto el item se reabre solo en dos semanas. REPARTO: db03 mide y escribe los artefactos de DDL/datos; coder03 commitea los artefactos y el gate. Ninguna fila de PROD se escribe sin haber grepeado antes el id y el valor por docs/migrations/ y wi.
Sub-tickets — 1/1 done
Activity
-
El catalogo opciones tiene que ser IDENTICO en DEV y PROD, y quedar atado para que no vuelva a divergir
-
was: Elazar, CLI 2026-09-22, literal: 'prod and dev need to be as if i had cloned a drive and plugged it to another computer and we just import ssot into prod app and then we copy into dev'. MODELO NUEVO: PROD es el master. El import del libro corre SOLO contra PROD; DEV deja de importar por su cuenta y pasa a ser una COPIA de PROD. Hoy el import corre contra cada base por separado y cada base acumulo su propia historia, que es justo lo que esto termina. BLOQUEANTE MEDIDO, no es una opinion: DEV corre PostgreSQL 15.17 y PROD 17.6 (db03, 2026-09-22). Un dump de 17 NO restaura en 15 - no esta soportado en ninguna direccion util, ni logico ni fisico. Mientras la version no coincida, 'clonar el disco' es imposible por construccion, no dificil. Las dos salidas son: (a) subir DEV a 17.6, y recien ahi la copia es un pg_dump/pg_restore o un basebackup; (b) no clonar y seguir con dos bases que convergen a mano, que es lo que Elazar acaba de decir que no quiere. La decision de subir la version de DEV es de Elazar. LO QUE LA COPIA SE LLEVA PUESTO, y hay que decirlo antes y no despues: - DEV pierde sus 113 tablas de scratch/test, 2 vistas y 19 secuencias que PROD no tiene. Regla de Elazar: lo que se carga en DEV es descartable. Pero un artefacto que dependa de una de esas tablas deja de correr. - DEV vuelve a tener lo que PROD tiene de mas y DEV ya podo: entre otras, opciones id 119 MANTENIMIENTO (MOVILBA-77). Una copia de PROD RESTAURA 119 en DEV. - Dueños y roles NO coinciden: PROD movilba_owner + app svc_nextjs, DEV movilba_user. Una restauracion arrastra propiedad y ACLs, asi que la copia necesita un mapeo de roles explicito o DEV queda con objetos de un dueño que ahi no existe. - Los 6 objetos donde PROD esta ADELANTADO (default de vehiculos.fecha_ultima_actualizacion, vdc.observaciones, 6 CHECK, 1 unique parcial, 1 funcion, 3 vistas mas anchas) dejan de ser deriva: pasan a ser el estado correcto, porque PROD manda. - El sentido de la convergencia queda FIJADO en PROD -> DEV. Eso contradice el texto de varias migraciones que dicen 'DEV primero, despues PROD', y contradice el patron de replay DEV->PROD que uso 517/518/521. Hay que decidir si el DDL nuevo sigue ensayandose en DEV (que despues se pisa) o si se ensaya en una tercera copia. QUE HAY QUE PRODUCIR: un procedimiento de copia repetible y su gate, no una corrida a mano. db03 mide y escribe el procedimiento; coder03 commitea el artefacto y cualquier cambio de codigo (DSN, scripts) que salga. NADA se ejecuta sobre DEV sin que Elazar decida primero la version.
-
STARTED MOVILBA-534 at 2026-09-22 14:48:07 -03 - db03-mba-l-cc. Alcance retargeteado por coder03: catalogo opciones identico DEV/PROD + gate. SELECT only hasta tener artefactos.
-
GO de Elazar en CLI 2026-09-22. Nivel de ceremonia fijado por el en la misma orden: filas de catalogo, pre-go-live, arreglo ya verificado en DEV que se replica a PROD - UN script corrido en las dos bases y medido. Sin cadena de artefactos, sin ensayo de ida y vuelta, sin gate de audit sobre esto. Tambien queda zanjado que NO hace falta ningun campo id_temp ni ALTER: opciones.id es serial, no identity, asi que un id explicito en el INSERT ya le gana al default; despues se sube la secuencia por encima de max(id). db03 mide las tres listas (solo-DEV, solo-PROD, misma clave con distinto id/borrado_en/etiqueta) mas el conteo de filas que apuntan a cada id en conflicto, que es lo unico de todo esto que puede destruir dato. coder03 escribe y corre el script.
-
db03: medicion completa (SELECT only) + 2 artefactos escritos, ninguno aplicado. CATALOGO VIVO (borrado_en IS NULL), clave (grupo,campo,valor), NO por id: - solo-DEV: ninguno. - solo-PROD: vehiculo/estado/MANTENIMIENTO (119) y motor/unidad_propulsora/COMBUSTION_GNC (34). - misma clave distinto atributo: solo 34 (BORRADA en DEV 2026-09-08, VIVA en PROD). Igual id, igual etiqueta, igual descripcion, igual params. - CERO colisiones de id: toda clave presente en las dos bases tiene el MISMO id. Ese era el riesgo real y no existe. CRUDO (incluye soft-deleted): agrega solo-DEV 325 ASIGNACION / 326 DESASIGNACION (borradas 08-27 por la migracion 128-canonical-events-DEV) y solo-PROD 118 TRANSFERENCIA (borrada 2025-10-13). Ninguna llega a la UI. DERIVADOS: el md5 del CUERPO no sirve de instrumento - 41 de 64 vistas que tocan opciones difieren por el pretty-print pg15 vs pg17 (PG15 imprime o.columna, PG17 columna pelada) y ninguna difiere de verdad; verificado leyendo el texto de dos. Por CONTENIDO DE FILAS difieren 6, y las 6 se explican enteras por 119 y 34: v_opciones, v_estado_colectivo (matview), v_ui_vehiculo_estado, v_ui_vehiculo_estado_filtro, v_unidad_propulsora (matview), v_admin_opciones_list. v_ui_chasis/fabricantes/lineas/todas_entidades/vehiculos tambien difieren pero son datos de negocio, no catalogo: fuera de alcance salvo que se diga lo contrario. PROD NO ES COSMETICO: v_ui_vehiculo_estado_filtro emite hoy (mantenimiento,Mantenimiento,v_vehiculos_list_mantenimiento,119) y src/repositories/vehiculos.ts:279-284 ya no mapea esa relacion, asi que findAll({estado:'mantenimiento'}) cae en el throw deliberado de la linea 289. Es un 500, no una pestania vacia. POR CAMINO DE CODIGO, no ejercitado: no se corrio la ruta ni se probo auth. EL INSTRUMENTO DE 119 NO EXISTIA: docs/migrations/2026-08-20-movilba-77-...-PROD.sql dice PREPARADO NO EJECUTADO y NO SE PUEDE CORRER - su GATE 4 afirma baja=137, medicion del 20/08; PROD hoy tiene baja=814 y el gate aborta. Y en DEV tampoco corrio ese archivo: DEV no tiene la opcion 119 pero SI tiene la vista v_vehiculos_list_mantenimiento, que ese archivo borra en la misma transaccion. La poda de DEV no dejo traza. Escrito el reemplazo: docs/migrations/2026-09-22-movilba-534-hard-delete-opcion-119-mantenimiento-PROD.sql - GATE 4 compara contra lo que midio la propia transaccion en vez de literales, GATE 1 y 2 ampliados a todos los esquemas (el original solo miraba public), y GATE 5 nuevo verifica que la matview v_estado_colectivo se refresco DENTRO de la transaccion. ENSAYADO en PROD con apply=false desde copia con md5 verificado: 6 gates verdes, DELETE 1, matview refrescada, ROLLBACK. NO APLICADO: necesita ALTER TABLE ... DISABLE TRIGGER trg_soft_delete_opciones (sin eso el DELETE se vuelve soft-delete, que es lo que Elazar descarto el 20/08). Desactivar una proteccion esta explicitamente FUERA del no-GO pre-go-live (commons 6). Va a Elazar por CLI. MEDIDO EN PROD ANTES: 37 columnas con FK a opciones y 17 columnas *_id de *_archive, en todos los esquemas: CERO filas con 34, 118 o 119. En DEV, en cambio, vehiculos_datos_colectivos_archive tiene 53 filas con estado_id=119 y 15 con estado_id=118, apuntando a opciones que en DEV ya no existen: cascara de la poda sin instrumento. DEV es descartable, queda anotado. DECISIONES QUE NO SE ESCRIBEN SOLAS: 1) opcion 34 COMBUSTION_GNC: viva en PROD, soft-deleteada en DEV el 08-09 con borrado_por NULL y sin ninguna migracion committeada. No hay instrumento que replicar. El resolutor vivo rutap.unidad_propulsora_resolver mapea GNC->157 (GN) en LAS DOS bases, y 34 tiene cero referencias en las dos, o sea que no tiene consumidor. tools/import/opciones-resolver.js:101 todavia mapea GNC->COMBUSTION_GNC, contradice al resolutor de la base; ningun otro archivo importa a esos populators. 2) opcion 118 TRANSFERENCIA: soft-deleteada en PROD, AUSENTE en DEV. catalogos.md la da por retirada pero nadie ordeno hard delete. 3) 325/326 validadoras: solo-DEV y soft-deleteadas; PROD converge a nivel catalogo vivo. Si el gate mide vivo, no hay nada que hacer. GATE: tools/opciones-diff.sh, SELECT only, sin credenciales adentro (DSN por entorno). Compara por CONTENIDO DE FILAS, nunca por pg_get_viewdef, y elige las vistas por construccion (tablas base = exactamente {public.opciones}) y no por patron de nombre, para que no entren las vistas de negocio que en DEV son descartables por regla. Usa pg_attribute y no information_schema, que no lista columnas de matview y dejaba 13 relaciones salteadas en silencio. Excluye creado_en/creado_por de la comparacion: las dos bases se sembraron en fechas distintas y esas columnas dejaban el gate rojo para siempre. CONTROL POSITIVO corrido: rojo contra la divergencia real, VERDE en DEV-vs-DEV y en PROD-vs-PROD. Hoy falla con exactamente 119 y 34; verde cuando las dos decisiones aterricen. Los dos archivos estan escritos en el checkout, sin commitear - son de coder03.
-
APLICADO en las DOS bases, mismo archivo, md5 2e61bfdeb9e1bc14c38108c5ab47b984, bajo lock hub public.opciones (tomado y liberado). docs/migrations/2026-09-22-movilba-534-hard-delete-opcion-119-mantenimiento-PROD.sql, idempotente, corrido primero con apply=false en las dos y despues con apply=true: - PROD: GATE 0-5 verdes, opcion 119 borrada FISICA (DISABLE/ENABLE TRIGGER trg_soft_delete_opciones dentro de la transaccion, GATE 4 verifica que quedo habilitado), DROP de v_vehiculos_list_mantenimiento, COMMIT. alta=1599 y baja=814 antes y despues: las listas no se movieron. - DEV: no tenia la opcion pero SI le quedaba la vista colgando; se dropeo la vista y NO se toco ningun trigger. GATE 2 AVISO, no falla: DEV tiene 53 filas en vehiculos_datos_colectivos_archive con estado_id=119 apuntando a una opcion que ahi ya no existe, cascara de la poda vieja sin instrumento. PROD tiene cero. MEDIDO DESPUES con tools/opciones-diff.sh: el catalogo vivo pasa de 2 divergencias a 1, y las vistas de 6 a 2. Lo que queda es EXACTAMENTE la opcion 34 y sus dos vistas (v_opciones, v_unidad_propulsora). v_estado_colectivo, v_ui_vehiculo_estado y v_ui_vehiculo_estado_filtro ya son identicas: la pestania Mantenimiento no se ofrece mas en PROD. EL 500 DE PROD ESTA CERRADO, y la afirmacion ahora tiene la cadena entera leida: vehiculos.ts:289 lanza un Error comun dentro del try de la linea 256; el catch de la 341 loguea y hace throw; services/vehiculos.ts:118-121 tambien rethrowea; api/vehiculos/route.ts:82-85 lo agarra y llama handleApiError, que para un Error sin mapear cae en INTERNAL_ERROR (errors.ts:269) y INTERNAL_ERROR mapea a 500 (errors.ts:347-350). Era un 500, no una lista vacia. Sigue siendo POR CAMINO DE CODIGO: no se corrio la ruta ni se probo auth. PARA EL CARRIL DE CODIGO: hay que correr ./mba-introspect.sh y commitear. drizzle/schema.ts:4001 exporta vVehiculosListMantenimiento, que ahora no existe en ninguna de las dos bases; ningun archivo de src/ lo importa, asi que el simbolo desaparece y no rompe nada. QUEDA ABIERTO: opcion 34 COMBUSTION_GNC (viva PROD, soft-deleteada DEV sin autor ni migracion). Va a Elazar: nadie decidio nada sobre ella. 118 TRANSFERENCIA y 325/326 validadoras son soft-deleted en una base y ausentes en la otra; no llegan al catalogo vivo ni a la UI, no se tocan hoy.
-
Postcondicion re-medida 2026-09-22 16:36 ART, SELECT only contra las dos bases. GATE VERDE: tools/opciones-diff.sh dice CATALOGO VIVO identico (DEV 163 / PROD 163 filas) y VISTAS DE CATALOGO identicas (26/26). Censo crudo aparte, keyeado por (grupo,campo,valor,id,estado): DEV 197 filas / PROD 196, y las 194 vivas calzan exacto. Las TRES unicas diferencias son filas soft-deleteadas, y el gate NO las cuenta A PROPOSITO (decision 2 documentada en la cabecera del script, con su razon: una fila retirada de un lado y ausente del otro no cambia una pantalla ni un valor escribible, y un gate que se cayera por eso se volveria ruido y se apagaria). Son: 325 validadoras|evento_tipo|ASIGNACION y 326 DESASIGNACION solo en DEV, y 118 vehiculo|estado|TRANSFERENCIA solo en PROD (borrado_en 2025-10-13 21:45:06.945). Se ven tambien en el diff informativo de v_admin_opciones_list. Queda CERRADO el objetivo del ticket. No hay carril de audit vivo (ni -cc ni -ca registrados en el hub), asi que el diff no tiene revisor: queda anotado, no cubierto.
-
HALLAZGO que sale de esta re-medicion y NO es el ticket: un HARD delete de una fila de opciones destruye la etiqueta de las filas de _archive que la apuntan, en silencio. Un SOFT delete no. Cadena medida, no inferida: src/actions/audit.ts:37 mete estado_id en FK_OPCIONES, y la rama de opciones de resolveFkLabels lee la TABLA BASE SIN FILTRAR borrado_en. El comentario del archivo declara esa asimetria como DELIBERADA y da la razon: 'para un log de auditoria la rama que acierta es la base: una fila historica no deja de haber pasado porque se retiro la opcion'. Ese diseno funciona con soft-delete y lo derrota un hard delete. Estado hoy: DEV NO tiene 118 ni 119 en opciones (ninguna fila, ni borrada) y su vehiculos_datos_colectivos_archive tiene 68 filas apuntando a esos ids (118: 15 filas/4 vehiculos; 119: 53 filas/16 vehiculos) - esas 68 salen SIN etiqueta en la pantalla de auditoria. PROD conserva 118 soft-deleteada, la resolveria, y tiene 0 filas de archive en 118/119, asi que el hard delete de 119 que aplico este mismo ticket no rompio nada observable en PROD. Lo que hay que decidir, y no lo tomo yo: (a) si el retiro de una opcion en este sistema es soft-delete por regla, dado que hay una superficie que depende de que la fila siga existiendo; (b) que pasa con las 68 filas de archive de DEV. No es arreglo obvio - restaurar un id borrado a mano es elegir entre dos valores defendibles. Tampoco es drift DEV/PROD del catalogo vivo, asi que no entra en MOVILBA-537.
-
done