ADR-040: El cuadro de clasificación se deriva del organigrama; la productora es contenido versionado de la TRD¶
Estado: Aceptado Fecha: 2026-10-20 Autores: Giampiero (mantenedor principal); tanda C-4 del triage (C6, C8, C10) Relacionados: ADR-015 (TRD/CCD), ADR-025 (instrumento convalidado y versionado), ADR-026 (TVD), ADR-032 (la dependencia por código), ADR-012 (migraciones)
Contexto¶
Tres defectos distintos con una raíz común: el mismo hecho escrito en dos sitios, o no escrito en ninguno.
- La retención de una serie tenía dos juegos de números (C6).
trd_seriesguardabaretention_years/total_retention(el juego heredado de Orfeo) yarchivo_gestion_years/archivo_central_years(lo que de verdad calcula el calendario). La respuesta derivaba el heredado de las fases… salvo cuando faltaba alguna, y entonces devolvía la columna heredada. Resultado medido en una TVD:total_retentionvalía 10 por omisión,archivo_central_yearsquedabaNULL, el calendario se calculaba con 0 (or 0) y la pantalla mostraba 10 años. Es retención cero presentada como diez. Además «serie > subserie» (ADR-015) era una intención: nada impedía que una subserie tuviera hijos, y el EAD lo documentaba como válido. - La TRD no decía quién produce la serie (C8). El cuadro de clasificación documental (fondo, sección,
subsección, serie, subserie) existe en el organigrama (E14) y en la TRD, pero nada los unía. Capturarlo a
mano por segunda vez crea otra fuente que diverge; no capturarlo deja al Consejo aprobando un CCD que no
dice quién produce cada serie. Y el organigrama admitía ciclos (A → B → A):
/dependencias/treehacía desaparecer sus nodos en silencio, de modo que derivar de él un cuadro habría convertido un dato corrupto en un cuadro incompleto que parece completo. - «Publicable» no era una decisión de la entidad sobre el tipo (C10).
set_publicableexcluía los tipos conadmite_ventanilla_publica(Ley 1755: «se recibe del público») como sustituto de «no se difunde», acoplando dos marcas que dicen cosas distintas, y un radicado sin tipo sí podía publicarse. ElCLAUDE.mdde document-service afirmaba la decisión contraria a la que luego se tomó.
Decisión¶
C6 — Las fases son la única fuente; dos niveles, impuestos por el motor¶
Alternativas para el juego heredado. A) DROP COLUMN y derivar en lectura. B) columnas
GENERATED ALWAYS … STORED. B no compra nada que A no tenga —ningún otro servicio lee trd_series.retention_years
(barrido de services/*, exports e INT-01 incluidos)— y deja un segundo nombre para el mismo hecho: una columna
que parece dato y no lo es. Se elige A (migración 069).
retention_yearsytotal_retentiondejan de ser columnas. La API los sigue publicando en la respuesta, como derivados de solo lectura (readOnlyen el OpenAPI): gestión y gestión + central.- Los esquemas de escritura TRD/TVD pasan a
extra="forbid": un cliente viejo que manderetention_yearsrecibe422 extra_forbidden, no un silencio que le haga creer que fijó una retención. - Las dos fases son obligatorias y sin valor por omisión (0 es válido; ausente no):
CHECK trd_series_fases_check— toda TRD declara gestión y central, toda TVD su fase central. La TVD no tiene gestión (ADR-026); su total de retención es su fase central. Se retiran losor 0del cálculo: una fase central ausente es un dato corrupto y falla (500 trd_sin_fase_central, ytotal_trd_years → Nonepara el cableado WORM, que pospone), no se convierte en «sin retención». - Los asientos de auditoría registran lo escrito (las fases), no el derivado.
- Tope de dos niveles en el trigger
assert_trd_parent_mismo_tipo: el padre debe ser una serie raíz y una fila con hijos no puede ganar padre (trd_jerarquia_dos_niveles). El servicio responde422antes que el motor; las subseries apuntan a la fila-versión del padre, así que para «¿tiene subseries?» se pregunta por el código del padre.
C8 — Serie ↔ dependencia productora, y cuadro derivado¶
Alternativas para la relación. A) columna dependencia_productora_codigo en trd_series (una por serie).
B) tabla N:M por versión de la serie. C) N:M por código de serie. A impone una sola productora, falso
en la práctica archivística (la misma serie en varias oficinas). C reescribe el CCD de versiones ya
convalidadas cada vez que el organigrama cambia, violando «la reorganización no reescribe historia». Se
elige B (migración 070, trd_serie_dependencia_productora).
- La dependencia se cita por
dependencias.codigo(ADR-032) —inmutable y nunca reutilizada— y sin FK: la tabla es de tenant-service y el orden de migración entre servicios no está garantizado. La columna se llamadependencia_codigoa propósito: casa con el barrido_REF_COLUMNASde tenant-service, así que eliminar una dependencia que una TRD cita es409 dependencia_con_historiasin tocar tenant-service. - La productora es contenido de la versión: solo se escribe en
borrador(trigger), se reemplaza entera porPATCHo al crear/versionar, y solo existe en la serie (las subseries la heredan; la TVD tienefondo_nombre). Una versión nueva clona la de su base; si alguna ya no está activa, falla con la lista y el cliente reenvía la explícita. Así entra una productora en una TRD convalidada: versiónN+1en borrador, aprobar, convalidar. No hay vía en sitio: es contenido convalidado por el Consejo, no política técnica comopdfa_profile. - Gates:
aprobaryconvalidar(incluida la ratificaciónmigrada → convalidada, que no pasa poraprobar) responden409 trd_serie_sin_productorasi la serie TRD raíz no declara ninguna. El gate va dentro de la transacción y tras elUPDATEde estado: desdeaprobadael trigger ya no deja tocar la lista, así que no hay carrera con unPATCHque la vacíe, y el 409 revierte el cambio de estado. - Ruta congelada: al convalidar se escribe
ruta_convalidada(raíz → productora, del organigrama vivo) una sola vez (fill-once, trigger). Si el árbol no se puede leer o tiene un ciclo, la convalidación entera falla (503/409 organigrama_con_ciclo). - Frontera de lectura (S1): la declarada en
trd.py—el vocabulario es abierto, el circuito exigeUSUA_PERM_TRD— ahora se cumple:GET /api/v1/trddevuelve por omisión soloconvalidada/migraday sinacta_comite,fecha_aprobacion_comitenicreated_by; las versiones en trámite (?incluir_en_tramite=true) yGET /trd/cuadro?incluir_borradores=trueresponden 403 sin el permiso, y un borrador por id es 404 para quien no lo tiene. El acto de convalidación (resolución, fecha, instancia) sigue siendo público. - Cuadro:
GET /api/v1/trd/cuadro. Fondo = la entidad (sgd_param_admin.entidad_nombre); sección = dependencia raíz del camino raíz → productora; subsección = cada nivel inferior (la profundidad es la del organigrama); serie = la vigente por código (conincluir_borradores, además la en trámite); subserie = vigente por su propio código, agrupada por el código del padre. Una serie con N productoras aparece bajo cada una (compartida). Una serie convalidada se ubica por la ruta que fijó la convalidación —lo que el Consejo aprobó, y lo que ya usa el EAD— y la ruta viva solo se dice como aviso (ruta_difiere_de_la_convalidada,organigrama_reorganizado_tras_convalidacion);?segun=vivola ubica en el organigrama actual. Sin instantánea (migrada,borrador) solo existe la ruta viva. Los nodos se identifican por su CAMINO, no por su código: tras una reorganización la misma dependencia puede aparecer bajo dos padres. Se arma en una transacciónREPEATABLE READde solo lectura con una función pura (armar_cuadro) que se prueba sin base de datos. Nada se omite en silencio: lo que no se puede ubicar va asin_seccioncon su motivo, y los avisos dicen lo que el organigrama cambió. - Ciclos, en dos capas que no se sustituyen: prevención en tenant-service (migración
015: triggerBEFORE INSERT OR UPDATE OF parent_idcon candado consultivo por schema —sin él,A.parent=ByB.parent=Aconcurrentes pasan las dos comprobaciones— que sube desde el nuevo padre; la migración aborta listando los códigos si ya hay un ciclo; el router lo mapea a409 dependencia_ciclo) y detección en archive al leer: la CTE sube desde cada nodo (CYCLE id SET es_ciclo), porque bajando desde las raíces los nodos de un ciclo, que no son raíz de nada, nunca se alcanzarían y el cuadro saldría incompleto sin avisar. - Reorganizaciones: un renombre se muestra con el nombre vigente (la instantánea y la ruta convalidada
conservan el de entonces); un cambio de padre produce
ruta_difiere_de_la_convalidaday el avisoorganigrama_reorganizado_tras_convalidacion—el sistema lo dice, no reescribe la versión—; una desactivación deja la serie bajo la dependencia conactivo:falsey aviso; la eliminación es imposible mientras una versión la cite. - Retención mínima (A7):
CHECK trd_series_retencion_minima_check— una TRD suma al menos un año entre las dos fases (0 + 0no es «sin retención»: es un calendario que vence al cerrar y un candado WORM que el cableado pospone para siempre); 422trd_retencion_minimaantes que el 23514. - Concurrencia: el tope de dos niveles toma un candado consultivo por schema en el trigger de la
069(alta de una subserie y «la serie pasa a ser subserie» concurrentes formaban tres niveles); la comprobación de la dependencia productora se hace DENTRO de la transacción conFOR SHARE(tenant-service no puede desactivarla entre la comprobación y la escritura);update_tipolee la filaFOR UPDATE. - OAI-PMH / EAD: el fondo se titula con el nombre de la entidad (sin nombre, se conserva el título por
omisión) y entre
fondsyseriesse emite unc[level="subfonds"]por cada nodo de la ruta (sección y subsección = subfondo en ISAD(G);unitid= el CÓDIGO de la dependencia —inmutable, ADR-032— y la sigla, que es mutable, va en el título), solo si la serie tiene exactamente una productora, con la ruta convalidada y, sin ella, la viva. Con cero o varias no se emite el nivel: el expediente no lleva columna de dependencia y elegir una sería inventar. El endpoint es público: un ciclo o un catálogo ilegible degrada a «sin subfondos», no a un error.
C10 — tipos_documentales.publicable, marca propia¶
publicable BOOLEAN NOT NULL DEFAULT false(migración071): la entidad declara que el tipo entra en su esquema de publicación (Ley 1712/2014 art. 12). Ortogonal aadmite_ventanilla_publica. Es política de difusión, no contenido convalidado de la TRD: editable en sitio, asentada (archive.tipo_documental_publicacion_cambiada, antes/después, misma transacción) y conUSUA_PERM_TRDnivel 3 (las demás ediciones del catálogo conservan su nivel).- Reactivar un tipo que ya es
publicable(activofalse → true) también exige nivel 3: reabre la puerta de la publicación. Esa decisión se toma dentro del servicio, sobre la fila bloqueada, y la traza de la denegación no se revierte con el 403. - Gate (document-service,
set_publicable, en elWHERE): solo al publicar,canal <> 'ventanilla_publica'yEXISTS (tipo activo y publicable).EXISTSy noNOT EXISTS: el opt-in invierte el sentido, y un radicado sin tipo o con un tipo desconocido no se publica (antes sí). El canal sigue excluido siempre (Ley 1581: lo recibido de terceros). Retirar no lleva estas condiciones (fail-safe). - Si lo único que falla es el tipo,
409 tipo_documental_no_publicableen vez de un 404 que miente sobre la causa; cualquier otro motivo conserva el 404 neutro. La comprobación diagnóstica repite el mismo gate de clearance/nivel/anulado/canal, de modo que no es un oráculo. - No se lee en vivo en el chokepoint
_PUBLIC: desmarcar un tipo no despublica en silencio radicados ya difundidos (habríaradicados.publicable=truesin servirse: dos verdades). ElPATCHque pasapublicablede true a false respondepublicados_vigentes(cuántos siguen publicados, onullsi no se pudo contar) y el retiro es un acto explícito por radicado.
Consecuencias¶
- Es un cambio incompatible de contrato (los datos son de prueba, no hay compatibilidad ni backfill): quien
enviaba
retention_years/total_retentionrecibe 422; los scripts de siembra y las filas demo que no declaren fases o productora hay que repararlos o recrearlos. La migración069aborta nombrando las filas. - Desplegar archive antes que document (la
071crea la columna que document-service lee; si no, la consulta falla conUndefinedColumn⇒ 500 = fail-closed) y tenant-service con la015antes de confiar en el cuadro (sin ella, la prevención de ciclos no existe, aunque la detección sí). - Una
migradaexistente SÍ puede ratificarse:POST …/convalidaraceptadependencias_productorassolo cuando el origen esmigrada, y las escribe una vez, en la misma transacción y antes del gate, con asiento propioarchive.trd_productora_declarada(«captura de dato de TRD preexistente»); el trigger de la070deja AÑADIR (nunca quitar ni cambiar) la productora en una versiónmigrada. Sin ese camino la ratificación D6 de ADR-025 habría quedado muerta para toda serie raíz y las marcascerrado_bajo_serie_migradairresolubles. Unaaprobadano la recibe en el body (422productora_solo_en_ratificacion): la declaró enborrador. Mientras no se ratifique, lamigradaaparece ensin_seccion. compute_retentiony el cableado WORM dejan de tolerar una fase central ausente.
Fuera de alcance (declarado)¶
- Una subserie hereda TODAS las productoras de su serie y no puede expresar plazos distintos por oficina: no hay productora por subserie (decisión del orquestador, A3). Una serie compartida repite sus subseries bajo cada productora del cuadro.
- Deuda de publicidad del CCD: el cuadro convalidado debería poder leerse públicamente (Ley 1712/2014,
art. 9; Res. MinTIC 1519/2020 [confirmar el anexo exacto]) y hoy solo lo ve el autenticado del tenant.
Tampoco hay CCD de la TVD de fondo acumulado (Ac. AGN 002/2004: cuadro de clasificación del fondo
acumulado), que no lleva productora, ni un acto de adopción del esquema de publicación (Ley 1712,
art. 12) que asiente cuándo y con qué fundamento la entidad aprobó el conjunto de tipos publicables:
hoy cada cambio de
publicabledeja su asiento, pero no existe el acto colegiado que los reúne. - Cuadro «a fecha» histórica: tenant-service no guarda historial de
parent_id; laruta_convalidadaes la evidencia de lo aprobado. - La TVD no lleva dependencia productora (tiene
fondo_nombre). - Un catálogo cerrado de fundamentos de publicación y la publicación por serie/expediente siguen pendientes.