HT_PRE_011 (sprint-10)La entrega de Modificación Presupuestal · Elaboración, tal como quedó en
architecture/sprint-10/entregas/HT_PRE_011_ModificacionPresupuestalElaboracion/. Está completa y
literal: el documento de certificación y el contenido de cada archivo de evidencia, incluidos
los de configuración y los resultados que produjo la corrida.
Sirve de referencia de diligenciamiento. La plantilla muestra
qué secciones hay; el catálogo, qué se adjunta; esta
página muestra con qué nivel de detalle se llenan, que es lo que no se deduce de las otras dos.
Cuatro cosas que conviene mirar:
develop— para que nadie los atribuya a esta entrega.CERTIFICACION_ENTREGA.md# Certificación de entrega — Modificación Presupuestal · Elaboración (`HT_PRE_011`)
> **Certificada contra `api-dev`**, el artefacto realmente desplegado, el **25-ago-2026**. Los 15
> casos de la §5 se ejecutaron de primera mano y **los conteos se verificaron EN BASE, no en la
> respuesta HTTP**. La traza está en `evidencias/01_certificacion.txt`.
## 1. Identificación
| Campo | Valor |
|---|---|
| **HT / HU** | `HT_PRE_011_ModificacionPresupuestalElaboracion` · `HU-PRE-011 V1` |
| **Work package** | `#1067` (OpenProject, sprint-10 = versión 20) |
| **Microservicio · esquema** | `nebula-budget-core` · `budget` |
| **Ambiente** | DEV · Oracle XE 21c `XEPDB1` · Postgres `simappe_admin` |
| **Tenant de prueba** | `company_id = 5` |
| **Fecha de entrega** | 25-ago-2026 |
| **Instancias en Eureka al certificar** | **1** (el contenedor de dev). Verificado: con dos instancias UP el gateway balancea y la certificación no es confiable |
| **Responsable** | Carlos Torres (carril arquitecto, cubriendo a Brahian) |
### Build certificado
| Repo | Versión | Merge commit |
|---|---|---|
| `nebula-models` | `0.25.0-SNAPSHOT` | `7c84709` (MR `!176`) |
| `nebula-budget-core` | `1.1.0-SNAPSHOT` | `649eabe` (MR `!24`) |
| `nebula-budget-core` (arreglo del import) | `1.1.0-SNAPSHOT` | `7c7f861` (MR `!25`) |
| `nebula-budget-core` (rechazo del export) | `1.1.0-SNAPSHOT` | `ca95fb4` (MR `!27`) |
| `nebula-budget-core` (sonda del export) | `1.1.0-SNAPSHOT` | `37dd3de` (MR `!28`) |
**El bump es MINOR, no PATCH.** `1.0.7` → `1.1.0` porque la entrega agrega funcionalidad
retrocompatible y `ASR-PC-VER-01` lo exige así; el hábito del repo era subir PATCH.
**Publicación en Nexus verificada:** `nebula-models 0.25.0-20260825.141203-1`, con `pom` y `jar`.
> **Aviso de orden de merge.** `nebula-models` va **primero**. El primer intento de build falló
> (`#31`) porque Jenkins construyó el servicio **55 segundos antes** de que la librería terminara de
> publicarse: el build cerró a las `14:11:08 UTC` y el artefacto quedó en Nexus a las `14:12:03 UTC`.
> Los `401` de repsy que llenan ese log **no son la causa** —`nebula-shared` y `nebula-commons` dan
> los mismos y resuelven—; el `[ERROR]` nombra a repsy solo por ser el último repo de la lista.
> **Aviso que NO introduce esta HT.** El `spring-boot-starter-parent` está en **`3.5.10`** y el
> estándar del stack es **3.5.11**. Verificado con `git show origin/develop:pom.xml`: **ya venía así
> en `develop`**. El diff de esta entrega sobre el `pom.xml` toca únicamente las dos versiones de
> arriba.
## 2. Alcance entregado
**Once alcances de la HU**, sobre un documento con cabecera y detalle de rubros, en cinco tipos:
Adición, Reducción, Crédito, Contracrédito y Traslado. **La elaboración NO afecta saldos**
(RN-ALC1-13): eso es la aprobación, `HU-PRE-012`.
| # | Capa | Archivo |
|---|---|---|
| 1 | Controller — 13 operaciones | `ModificacionPresupuestalController.java` |
| 2 | Component — orquestación transaccional | `ModificacionPresupuestalComponent.java` |
| 3 | Service | `ModificacionPresupuestalService.java` |
| 4 | Repository — consultas nativas multitenant | `ModificacionPresupuestalRepository.java` |
| 5 | Validator | `ModificacionPresupuestalValidator.java` |
| 6 | Mapper | `ModificacionPresupuestalMapper.java` |
| 7 | LightLoader | `ModificacionPresupuestalLightLoader.java` |
| 8 | ImportProcess — carga masiva canónica | `ModificacionPresupuestalImportProcess.java` |
| 9 | Unitario de import (`REQUIRES_NEW`) | `ModificacionPresupuestalRubroUnitario.java` |
| 10 | Vista de saldos — entidad inmutable y repositorio | `SaldoPresupuestalView.java` |
**Once endpoints** (base `/api/v1/presupuesto/modificaciones-presupuestales`): `index` · `create` ·
`update` · `delete` · `get` · `read` · `get-record` · `page-response` · `rubros-elegibles` ·
`export` · `import`.
> **No existe `GET /exportar`, y es correcto que no exista.** `08_Contracts` lo **prohíbe**
> literalmente: *«PROHIBIDO el patrón `GET /exportar` con `ByteArrayResource`»*, porque está
> deprecado en `DependenciasController` y carga el resultado entero en memoria. La exportación es
> `POST /export`, con escritura en streaming sobre el `HttpServletResponse`.
### Decisiones de arquitectura tomadas
- **Unicidad del acto POR VIGENCIA**, orden del arquitecto: jamás dos `2026-001`, pero `2026-001` y
`2027-001` conviven. Índice `uk_doc_numero_acto` con `vigencia` en el tercer eje.
- **Borrado FÍSICO** de cabecera y detalle en la misma transacción (`03_Rules` §J.1).
- **El total lo calcula el SERVIDOR** como suma del detalle; el cliente no lo impone.
- **El rubro repetido se rechaza como FILA, no como lote** (RN-ALC8-06) — §6, defecto 4.
- **El nombre del archivo exportado sale del CRITERIO de la petición**, no de un parámetro.
## 3. Configuración aplicada
**Ninguna propiedad nueva en `application.yml`.** Nada que llevar a config-server: la entrega no
introduce parámetros de despliegue.
| Elemento | Valor |
|---|---|
| Base certificada | `https://api-dev.centricasoluciones.com/nebula-budget-core` |
| Cabecera obligatoria | `x-simappe-environment: dev` |
| Autenticación | Login de dos pasos + `jwt-select-company?companyId=5`; el token de negocio viaja como `Authorization: Bearer` |
### Permisos y configuración de base
`GRANT SELECT` sobre `budget.v_saldos_presupuestales` a `NEBULA_USER_DEV_01`, necesario porque la
vista se consulta desde el esquema que la lee. Verificado en `evidencias/02_base_datos.txt` §7.
## 4. DDL e i18n aplicados
**Migración** `V2026_08_21_01_pre_011_modificacion_presupuestal.sql`, aplicada y verificada en Oracle DEV:
| Objeto | Operación | Verificado |
|---|---|---|
| `documentos_presupuestales.numero_acto` | `MODIFY … VARCHAR2(50 CHAR)` | ✅ |
| `documentos_presupuestales.codigo_usuario_aprobador` | `MODIFY … NULL` | ✅ `nullable = Y` |
| `uk_doc_numero_acto` | Único `(client_id, company_id, vigencia, numero_acto)` | ✅ `BUDGET_INDEX` |
| `saldos_presupuestales` | Seis columnas `NUMBER(19,2) DEFAULT 0 NOT NULL` | ✅ |
| `v_saldos_presupuestales` | Vista de los tres niveles | ✅ `VALID` |
**Tablespaces verificados**: las tres tablas en `BUDGET_DATA`, los dos índices en `BUDGET_INDEX`.
**Cuatro scripts i18n**, persistidos en el servicio bajo `db/i18n/`:
| Script | Contenido |
|---|---|
| `V001__budget_i18n_modificacion_presupuestal.sql` | Catálogo de la HT |
| `V002__budget_i18n_estructurarubro_tipopresupuesto.sql` | `TipoPresupuestoEnum` |
| `V003__budget_i18n_rubropresupuestal_tipoorigen.sql` | `TipoOrigenEnum` |
| `V004__budget_i18n_modificacion_presupuestal_rubro_repetido.sql` | El rubro ya en el detalle |
**Conteo verificado en Postgres:** **65 claves** de la HT, **65 traducciones ES**, **65 EN**, más las
**4** de los enums de rubro. Ninguna clave sin traducir.
> **La caché de i18n es L1 Caffeine 1 h + L2 Redis 24 h.** Una clave recién insertada **no se ve al
> instante**. Quien recertifique debe contar con eso antes de declarar un fallo de traducción.
## 5. Pruebas ejecutadas
| Batería | Resultado |
|---|---|
| **Unitarias** | **415 pruebas, 0 fallos**, 24 clases |
| **Certificación contra `api-dev`** | **15 casos, 15 correctos** (`evidencias/01_certificacion.txt`) |
| **`audit-htu`** | **PASS** |
| **Diagnóstico Canónico SAR sobre `develop`** | **WARN** — 40 PASS / **0 FAIL** / 1 WARN / 24 N/A |
**Verificado EN BASE:** el documento creado quedó con el total **calculado por el servidor**
(300 000), **estado `BORRADOR`** por ausencia de aprobador, **consecutivo NULO**, `tipo_linea` NULO
fuera del Traslado y **aprobador nulo**. Tras el borrado no quedó **ni cabecera ni detalle**.
### Rechazos de negocio
Con el mensaje que **devuelve el servicio**, no el que dice la HT:
| Caso | HTTP | Mensaje del servicio |
|---|---|---|
| Importar sin archivo (`PRE-MOD-016`) | **400** | «Seleccione un archivo» · solución: «Seleccione un archivo en formato Excel o CSV para continuar» |
| Acto duplicado en la misma vigencia (`RN-ALC2-05`) | **409** | — |
| Crear sin detalle | **400** | «El documento debe tener al menos un rubro» |
| Rubro no elegible en el archivo | fila inválida | «Código no válido o inactivo para este tipo de presupuesto» |
| **Rubro ya en el detalle** (`RN-ALC8-06`) | fila inválida | «El rubro ya está en el detalle del documento» |
| Consultar o borrar inexistente | **404** | «El documento no fue encontrado» |
#### Exportación — certificada aparte
Cuatro casos en `evidencias/04_export_formatos.txt`:
| Caso | Resultado |
|---|---|
| **Cinco formatos** `csv`, `xlsx`, `text`, `json`, `xml` | **200**, cada uno con su `Content-Type` y su extensión: 1 902 B, 4 642 B, 1 902 B, 6 207 B y 9 535 B |
| **`pdf`** | **400** — «El formato pdf no aplica para este listado» |
| **Nombre acotado al tipo** (`RN-ALC8-03`) | `Modificaciones_ADICION_20260825_1302.csv`. Sin acotar, el segmento se omite: **un nombre no puede afirmar un alcance que el archivo no tiene** |
| **Sin filas válidas** (`RN-ALC8-04`, `PRE-MOD-015`) | **400**, `application/json`, **cero cabeceras de descarga** — «No hay datos válidos para exportar» · solución: «Ajuste los filtros del listado hasta obtener al menos una fila» |
| **Consistencia** | **Seis lecturas consecutivas idénticas**, con **una sola instancia** registrada en Eureka |
> **El `formato` viaja como parámetro de QUERY (`?formato=`), no en el cuerpo.** Enviarlo en el
> cuerpo cae al default `csv` **sin avisar**, y los cinco formatos parecen devolver lo mismo.
**No ejercitados, y se dice:** `PRE-MOD-011`, `PRE-MOD-012` y `PRE-MOD-013` —los tres resuelven en el
front con lista vacía; el contrato del servidor sí quedó verificado—.
### Diagnóstico SAR
**Veredicto WARN**, exit code 0, sobre `develop` con el arreglo ya mergeado.
| Regla | Estado | Criterio |
|---|---|---|
| `ASR-PC-DEL-01` — borrado detectado | ⚠️ WARN | **Justificado, no se corrige.** SAR no juzga físico contra lógico: exige cuestionarlo contra la HU. `03_Rules` §J.1 declara la política FÍSICA con tres razones —el documento nunca afectó saldos, no porta consecutivo, y es lo que ya hace `ApropiacionInicialElaboracion` sobre esta misma tabla—. Ambos borrados son del **detalle**, dentro de la transacción de la cabecera |
**Cero FAIL.** En particular ninguno de la familia `EST-*`: el código **no usa `status` como criterio
de negocio**.
### Archivos de evidencia
| Archivo | Contenido |
|---|---|
| `01_certificacion.txt` | Traza de los 15 casos contra `api-dev`, con la verificación en base de cada uno |
| `02_base_datos.txt` | Objetos, tablespaces, los dos índices únicos columna a columna, nulabilidad, acumuladores, `GRANT` y el conteo i18n |
| `03_diagnostico_sar.txt` | Veredicto SAR y reporte completo: matriz de aserciones, reglas dinámicas y hallazgo |
| `detalle_import.csv` | **Datos procesados**: incorporación del detalle (una fila entra, una se rechaza por repetida) |
| `detalle_import_repetido.csv` | **Datos procesados**: trae 999 000 para el rubro ya presente — prueba que **no sobrescribe** |
| `detalle_import_mixto.csv` | **Datos procesados**: una fila inválida y una válida; el lote continúa |
| `traslado_import.csv` | **Datos procesados**: columnas Tipo (CC/CR) del Traslado |
| `04_export_formatos.txt` | Los cinco casos de exportación: los formatos, el rechazo de `pdf`, el nombre acotado al tipo, `PRE-MOD-015` y la prueba de consistencia. Incluye el conteo de instancias en Eureka verificado antes de certificar |
| `listado_export.csv` | **Datos producidos**: el archivo que devolvió `/export`, 24 líneas |
| `listado_export.json` · `listado_export.xml` | **Datos producidos**: el mismo listado en los otros dos formatos |
| `listado_export_adicion.csv` | **Datos producidos**: el listado acotado a `ADICION`, cuyo nombre lleva el tipo |
## 6. Defectos que las unitarias NO vieron
**Cuatro, los cuatro hallados probando contra la base real** y corregidos con commit:
| # | Defecto | Corrección |
|---|---|---|
| 1 | **`codigo_usuario_aprobador` era `NOT NULL`** y `RN-ALC2-04` hace del aprobador el dato que DECIDE el estado: sin él, Borrador. **Guardar sin aprobador es el camino normal, y la restricción lo impedía** → `ORA-01400`. La columna nació así con `HU-PRE-008` | `29996a5` |
| 2 | **El archivo exportado no llevaba el tipo de documento**, contra `03_Rules` §K | `a59ade1` |
| 3 | **Los dos enums de rubro devolvían la CLAVE CRUDA al usuario.** En base existía `BUDGET.RUBRO.TIPOPRESUPUESTO.INGRESO` —otro nombre y en mayúsculas—, que el enum no declara | `6ddf467` |
| 4 | **Importar un rubro ya presente devolvía 500** con `ORA-00001` sobre `UK_DOC_RUBRO`. La restricción reventaba **en el flush del lote, fuera del `try` que aísla cada fila**, así que una línea repetida tumbaba el archivo entero. Y `VALIDATE` **discrepaba de `PERSIST`**: anunciaba `skipped` y luego `PERSIST` reventaba | `479549d` |
| 5 | **La exportación sin filas válidas devolvía `200` con un archivo vacío.** `RN-ALC8-04` exige **400** con `PRE-MOD-015` y **no generar archivo**. El servicio entregaba un CSV con solo la fila de cabecera: el usuario recibía un archivo vacío en vez del mensaje, y el front no tenía cómo distinguir el caso. **Las dos claves i18n ya existían y ninguna línea las usaba** | `ca95fb4` |
> **El quinto costó dos intentos, y los dos fallos son de método:**
>
> - **El primer arreglo (`c2a213a`) empeoró el defecto**: puso la guarda dentro de `component.export`, y para cuando ese método corre **el controlador ya fijó `Content-Type` y `Content-Disposition` de una descarga**. La excepción no podía volverse JSON y el servicio devolvía **500**. Las pruebas no podían verlo: **mockean el `OutputStream`**, donde la mecánica de la respuesta HTTP no existe. La verificación pasó a método propio, invocado **antes** de tocar la respuesta.
> - **El segundo (`ca95fb4`) introdujo un defecto lateral**: la sonda compartía la lista de criterios con la petición del llamador, y `acotarAlTenantYAlTipo` **agrega sobre la lista que recibe**. El nombre del archivo se compone leyendo esos criterios, así que salía `Modificaciones_APROPIACION_INICIAL_…` —un tipo que el usuario nunca pidió y que ni siquiera es de modificación presupuestal—. **La sonda copia ahora los criterios** (`37dd3de`).
>
> **El tercero NO es defecto de esta HT y su alcance la excede:** **todo módulo que aplane esos dos
> enums mostraba la clave en lugar de la etiqueta**. Se detectó aquí porque los dos se aplanan en el
> **mismo record**, así que el síntoma aparecía dos veces en la misma fila.
**Sobre el cuarto, el alcance salió de la HU y del modelo, no del síntoma:**
- `RN-ALC8-06` valida cada código *contra las reglas del documento* y produce válidas e inválidas
**con error por fila**; `RN-ALC8-07` incorpora solo las válidas. El botón de la HU es **«Agregar al
detalle»**: un rubro ya capturado no se agrega otra vez, y **tampoco se sobrescribe en silencio un
valor que el usuario digitó**.
- **`UK_DOC_RUBRO` no la crea esta HT**: viene de `HU-PRE-008` y ya declaraba que el rubro va una
sola vez por documento.
- **Ninguno de los cinco ejes del `ImportControl`** decide insertar contra actualizar: no se resolvía
por configuración.
- **Discrimina por GRUPO**, igual que la restricción: en un Traslado el mismo rubro puede ir como
Contracrédito y como Crédito, y esas son **dos líneas legítimas**. El método que ya existía,
`findByDocumentoIdAndCodigoCorto`, **no mira `tipo_linea`** y habría rechazado la segunda.
- **No se sigue al precedente de apropiación inicial**, que actualiza: allí el documento nace con
todos sus rubros y el import solo puede actualizar valores (devuelve `created = 0` siempre).
**Observación escalada, porque excede esta HT:** el patrón del ecosistema para el archivo exportado
es un nombre **fijo, en minúsculas y sin sello de tiempo** —`apropiaciones_iniciales`,
`catalogo-rubros`, `ciudades`—, y esta HT especifica `Modificaciones_<TipoDocumento>_AAAAMMDD_HHMM`.
Se implementó **lo que la HT manda**; la divergencia queda para el arquitecto.
## 7. Lo que NO se puede probar y por qué
| Punto | Razón |
|---|---|
| **La `uk_doc_numero_acto` frente al cierre de vigencia** | **Faltante declarado.** Depende de `CICLO-VIGENCIA-PRESUPUESTAL` (**D49**), que **no tiene HU en el corpus** —`HU-PRE-026` no existe— y **no está en el manifiesto del sprint**. Cinco HT dependen de él |
| **El mismo acto en OTRA vigencia** | En DEV **no hay rubros presupuestales para 2027** (cero elegibles), así que no se puede armar el documento. **La regla sí quedó probada donde vive**: el índice tiene `vigencia` en el tercer eje, leído del diccionario de Oracle |
| **`PRE-MOD-011/012/013`** | Resuelven en el front con lista vacía. El contrato del servidor sí se verificó |
**Ninguno es bloqueante**, y en todos el comportamiento está declarado por escrito en la HT.
## 8. Cómo reproducir en QA
1. Autenticarse: `POST /simappe-oauth2-server/api/v1/login`, luego
`POST /simappe-oauth2-server/api/v1/jwt-select-company?companyId=<id>` con el header `accesstoken`.
2. Usar el token del **segundo** paso como `Authorization: Bearer` para las rutas de negocio.
3. Enviar **siempre** `x-simappe-environment: qa`.
4. Base: `https://api-qa.centricasoluciones.com/nebula-budget-core/api/v1/presupuesto/modificaciones-presupuestales`.
5. El detalle exige `codigoCorto`, `codigoRubro`, `nombreRubro` y `valor`; los tres primeros salen de
`POST /rubros-elegibles`.
6. Los archivos de `evidencias/*.csv` sirven tal cual, ajustando los códigos cortos del ambiente.
**Antes de declarar un fallo de i18n**, contar con la caché L1 Caffeine 1 h + L2 Redis 24 h.
## 9. Datos sembrados que quedan en el ambiente
Todos bajo `company_id = 5`, **con autorización expresa de Carlos del 24-ago-2026** (*«yo autorizo y
no me importan los datos que se creen, eso es dev»*).
| Dato | Detalle |
|---|---|
| **Rubros de prueba** | `8001` (`2.1.1.01.G001`, Sueldos de personal) y `8002` (`2.1.2.03.G002`, Materiales y suministros), vigencia 2026, GASTO / ORDINARIO |
| **Documentos de la certificación** | Actos con prefijos `CERT-`, `CRUD-`, `IMP-`, `RES-REVENTAR-`, `PROBE-`, `W-`. **21 documentos** en el listado al momento de exportar |
| **Documento del ciclo completo** | `#207`, acto `CERT-121845` — **borrado al final** por TC-15: no quedó cabecera ni detalle |
| **Documento del import mixto** | `#208`, acto `CERT-MIX-121845` — **queda vivo**, con la fila válida incorporada y la inválida reportada |
| **Renombre** | Un acto de prueba se renombró al introducir la unicidad por vigencia, por orden del arquitecto: *«cambie ese acto y listo, uno de ellos cambia y listo, eso es data basura»* |
Los documentos `#179`–`#206` los generó el vigilante que esperaba el despliegue, uno por minuto.
**Son basura de DEV y pueden borrarse sin consecuencia**: están en Borrador y la elaboración no
afecta saldos.
## 10. PENDIENTE
| # | Pendiente | Estado |
|---|---|---|
| 1 | **`PRE-MOD-011/012/013` sin ejercitar** — resuelven en el front con lista vacía; el contrato del servidor sí se verificó | Abierto, no bloqueante |
| 2 | **`uk_doc_numero_acto` frente al cierre de vigencia** — `CICLO-VIGENCIA-PRESUPUESTAL` (D49) | **Faltante declarado**, sin HU en el corpus |
| 3 | **Divergencia del nombre del archivo exportado** contra el patrón del ecosistema | Escalado al arquitecto |
| 4 | **`spring-boot-starter-parent` en `3.5.10`**, estándar `3.5.11` | Viene de `develop`, fuera del alcance de esta HT |
| 5 | ~~La exportación sin filas válidas~~ | **CERRADO** — corregido en `ca95fb4` y `37dd3de`, recertificado contra `api-dev` |
Once archivos. Los tres numerados van siempre; el resto depende del alcance de la HT, según el
catálogo.
01_certificacion.txt — la corrida contra api-devQuince casos, cada uno con su HTTP y, en los que escriben, su verificación en base.
CERTIFICACION HT_PRE_011 - Modificacion Presupuestal - Elaboracion
Ejecutada contra api-dev (servicio desplegado, no local).
Ambiente DEV - tenant company_id=5 - Oracle XE 21c XEPDB1 - Postgres simappe_admin
Los conteos se verifican EN BASE, no en la respuesta HTTP.
Fecha: 2026-08-25 12:18:45 -05
=== TC-01 index (probe de disponibilidad) ===
HTTP 200
=== TC-02 page-response (listado paginado, NativeTenantPaginator) ===
HTTP 200
totalElements=21 filas=5
IMP-110632 ADICION BORRADOR consecutivo=None
CRUD-110557-B ADICION PARA_APROBAR consecutivo=None
RES-REVENTAR-073013 ADICION BORRADOR consecutivo=None
=== TC-03 rubros-elegibles (el filtro por vigencia/presupuesto/origen) ===
HTTP 200
elegibles=2
corto=8001 2.1.1.01.G001 Sueldos de personal disponible=7500000
corto=8002 2.1.2.03.G002 Materiales y suministros disponible=8000000
=== TC-04 create: el servidor calcula Valor y Estado (RN-ALC2-03/04) ===
HTTP 201
id=207 total=300000 (enviado 300000) estado=BORRADOR consecutivo=None
--- verificacion EN BASE ---
total_apropiacion=300000 estado=BORRADOR consecutivo=(nulo) aprobador=(nulo)
=== TC-05 get-record y get ===
get-record HTTP 200 get HTTP 200
=== TC-06 import: el detalle se incorpora (RN-ALC8-07) — archivo detalle_import.csv ===
HTTP 200
summary={"total": 2, "created": 1, "updated": 0, "skipped": 0, "invalid": 1}
linea 2 campo=codigoCorto regla=IMPORT_REGLA_NEGOCIO msg=El rubro ya está en el detalle del documento
(el rubro 8001 ya estaba en el detalle desde TC-04: esa fila debe rechazarse y la 8002 entrar)
--- verificacion EN BASE ---
corto=8001 2.1.1.01.G001 valor=300000 grupo=(nulo)
corto=8002 2.1.2.03.G002 valor=80000 grupo=(nulo)
=== TC-07 import del MISMO rubro otra vez — archivo detalle_import_repetido.csv (RN-ALC8-06) ===
HTTP 200 <- antes del arreglo: 500 con ORA-00001
summary={"total": 1, "created": 0, "updated": 0, "skipped": 0, "invalid": 1}
linea 2 campo=codigoCorto regla=IMPORT_REGLA_NEGOCIO msg=El rubro ya está en el detalle del documento
--- verificacion EN BASE: el valor NO se sobrescribio ---
corto=8001 valor=300000 (el archivo traia 999000)
=== TC-08 import mixto: una fila invalida NO tumba el lote — detalle_import_mixto.csv ===
documento=208 HTTP 200
summary={"total": 2, "created": 1, "updated": 0, "skipped": 0, "invalid": 1}
linea 2 campo=codigoCorto regla=IMPORT_REGLA_NEGOCIO msg=Código no válido o inactivo para este tipo de presupuesto
=== TC-09 VALIDATE anuncia lo mismo que PERSIST (RN-ALC8-06) ===
HTTP 200
summary VALIDATE={"total": 1, "created": 0, "updated": 0, "skipped": 0, "invalid": 1}
linea 2 regla=IMPORT_REGLA_NEGOCIO msg=El rubro ya está en el detalle del documento
=== TC-10 import SIN archivo (PRE-MOD-016, RN-ALC8-05) ===
HTTP 400 mensaje=Seleccione un archivo
solucion=Seleccione un archivo en formato Excel o CSV para continuar
=== TC-11 update: reevalua el conjunto y recalcula el Valor (RN-ALC10-02) ===
HTTP 200 total=450000 (antes 380000; el detalle queda en una sola linea)
=== TC-12 acto administrativo UNICO DENTRO DE SU VIGENCIA (RN-ALC2-05) ===
duplicado en la MISMA vigencia -> HTTP 409
--- la unicidad LEIDA DEL DICCIONARIO: el eje vigencia esta en el indice ---
pos 1 -> CLIENT_ID (UNIQUE, BUDGET_INDEX)
pos 2 -> COMPANY_ID (UNIQUE, BUDGET_INDEX)
pos 3 -> VIGENCIA (UNIQUE, BUDGET_INDEX)
pos 4 -> NUMERO_ACTO (UNIQUE, BUDGET_INDEX)
=== TC-13 rechazos de negocio ===
create sin detalle -> HTTP 400
get-record inexistente -> HTTP 404
delete inexistente -> HTTP 404
=== TC-14 export del listado (RN-ALC8-01) ===
POST /export CSV -> HTTP 200
archivo exportado: 1997 bytes, 24 lineas -> evidencias/listado_export.csv
=== TC-15 delete: borrado FISICO de cabecera y detalle (RN-ALC11-03) ===
HTTP 200
--- verificacion EN BASE: no queda cabecera NI detalle ---
cabeceras con id=207 -> 0
lineas de detalle -> 0
FIN DE LA CERTIFICACION
02_base_datos.txt — el ambiente verificadoObjetos con su STATUS, tablespaces, los dos índices únicos columna a columna, nulabilidad,
acumuladores, GRANT y el conteo de claves i18n con sus traducciones.
EVIDENCIA DE BASE DE DATOS — HT_PRE_011
Oracle XE 21c, contenedor oracle-xe en nodo-01, PDB XEPDB1. 2026-08-25 12:19:44 -05
############ 1. OBJETOS DEL DDL DE LA HT ############
OBJECT_NAME OBJECT_TYP STATUS
---------------------------------- ---------- -------
UK_DOC_NUMERO_ACTO INDEX VALID
UK_DOC_RUBRO INDEX VALID
DOCUMENTOS_PRESUPUESTALES TABLE VALID
DOCUMENTOS_PRESUPUESTALES_RUBRO TABLE VALID
SALDOS_PRESUPUESTALES TABLE VALID
V_SALDOS_PRESUPUESTALES VIEW VALID
############ 2. TABLESPACES: *_DATA para tablas, *_INDEX para indices ############
SEGMENTO TABLESPACE_NAME
---------------------------------- ------------------------------
DOCUMENTOS_PRESUPUESTALES BUDGET_DATA
DOCUMENTOS_PRESUPUESTALES_RUBRO BUDGET_DATA
SALDOS_PRESUPUESTALES BUDGET_DATA
SEGMENTO TABLESPACE_NAME
---------------------------------- ------------------------------
UK_DOC_NUMERO_ACTO BUDGET_INDEX
UK_DOC_RUBRO BUDGET_INDEX
############ 3. UK_DOC_NUMERO_ACTO: la unicidad del acto es POR VIGENCIA ############
POS COLUMNA UNIQUENES
---------- ------------------ ---------
1 CLIENT_ID UNIQUE
2 COMPANY_ID UNIQUE
3 VIGENCIA UNIQUE
4 NUMERO_ACTO UNIQUE
############ 4. UK_DOC_RUBRO: lleva TIPO_LINEA (el mismo rubro puede ir CC y CR) ############
POS COLUMNA UNIQUENES
---------- ------------------ ---------
1 CLIENT_ID UNIQUE
2 COMPANY_ID UNIQUE
3 DOCUMENTO_ID UNIQUE
4 CODIGO_CORTO UNIQUE
5 TIPO_LINEA UNIQUE
############ 5. codigo_usuario_aprobador admite nulos (defecto 29996a5) ############
COLUMN_NAME DATA_TYPE DATA_LENGTH N
---------------------------- ------------ ----------- -
CODIGO_USUARIO_APROBADOR VARCHAR2 240 Y
NUMERO_ACTO VARCHAR2 200 N
############ 6. Los seis acumuladores de saldos_presupuestales ############
COLUMN_NAME DATA_TYPE DATA_PRECISION DATA_SCALE N
---------------------------- ------------ -------------- ---------- -
ADICIONES NUMBER 19 2 N
CONTRACREDITOS NUMBER 19 2 N
CREDITOS NUMBER 19 2 N
MONTO_APLAZADO NUMBER 19 2 N
MONTO_LIBERADO NUMBER 19 2 N
REDUCCIONES NUMBER 19 2 N
############ 7. GRANT SELECT de la vista a NEBULA_USER_DEV_01 ############
############ 8. i18n EN POSTGRES (simappe_admin) — claves de la HT ############
-- EN = 1, ES = 2. Verificado en admin.translation_keys / admin.translations
budget.modificacionpresupuestal.validation.rubro.codigocorto.notnull | El Código Corto del rubro es obligatorio | The budget line short code is required
budget.modificacionpresupuestal.validation.rubro.codigorubro.notblank | El código del rubro es obligatorio | The budget line code is required
budget.modificacionpresupuestal.validation.rubro.nombrerubro.notblank | El nombre del rubro es obligatorio | The budget line name is required
budget.modificacionpresupuestal.validation.rubros.notempty | El documento debe tener al menos un rubro | The document must have at least one budget line
budget.modificacionpresupuestal.validation.rubro.valor.min | El valor de la línea debe ser mayor a cero | The line amount must be greater than zero
budget.modificacionpresupuestal.validation.rubro.valor.notnull | El valor de la línea es obligatorio | The line amount is required
budget.modificacionpresupuestal.validation.tipodocumento.notnull | El tipo de documento es obligatorio | The document type is required
budget.modificacionpresupuestal.validation.tipoorigen.notnull | El Tipo de Afectación es obligatorio | The allocation type is required
budget.modificacionpresupuestal.validation.tipopresupuesto.notnull | El tipo de presupuesto es obligatorio | The budget type is required
budget.modificacionpresupuestal.validation.vigencia.notnull | La vigencia es obligatoria | The fiscal year is required
(65 rows)
-- Conteo por bloque:
bloque | count
----------------------------+-------
claves de la HT | 65
traducciones ES | 65
traducciones EN | 65
enums de rubro (V002/V003) | 4
(4 rows)
04_export_formatos.txt — la exportaciónLos cinco formatos, el rechazo de pdf, el nombre acotado al tipo, PRE-MOD-015 ya corregido y una
prueba de consistencia. Empieza verificando cuántas instancias hay registradas en Eureka: una
corrida previa quedó inservible porque había dos y el gateway balanceaba entre ellas.
CERTIFICACION DEL EXPORTAR — HT_PRE_011 (RN-ALC8-01..04)
Ejecutada contra api-dev el 2026-08-25 14:32:21 -05. Tenant company_id=5.
Build: nebula-budget-core 1.1.0-SNAPSHOT, merge 37dd3de.
REGISTRO DE SERVICIO VERIFICADO ANTES DE CERTIFICAR:
instancias UP de NEBULA-BUDGET-CORE en Eureka -> 1
Una corrida previa quedo inservible porque habia DOS instancias UP y el gateway
balanceaba entre ellas: la mitad de las respuestas venia de codigo que no era el
desplegado. Se verifica el conteo ANTES de dar por buena cualquier lectura.
NOTA DE CONTRATO: el endpoint es POST /export. El patron GET /exportar esta
PROHIBIDO por 08_Contracts -usa ByteArrayResource y carga el resultado entero
en memoria-, de modo que un 404 sobre /exportar es el comportamiento CORRECTO.
El formato viaja como QUERY PARAM (?formato=), no en el cuerpo: enviarlo en el
cuerpo cae al default 'csv' SIN AVISAR y los cinco formatos aparentan lo mismo.
=== TC-E1 los cinco formatos soportados ===
csv HTTP 200 1902 B text/csv Modificaciones_20260825_1432.csv
xlsx HTTP 200 4642 B application/vnd.openxmlformats-officedocument. Modificaciones_20260825_1432.xlsx
text HTTP 200 1902 B text/plain Modificaciones_20260825_1432.txt
json HTTP 200 6207 B application/json Modificaciones_20260825_1432.json
xml HTTP 200 9535 B application/xml Modificaciones_20260825_1432.xml
=== TC-E2 PDF se rechaza (patron canonico de exportacion) ===
HTTP 400 mensaje=El formato pdf no aplica para este listado
=== TC-E3 el nombre lleva el TIPO solo cuando el listado esta acotado (RN-ALC8-03) ===
sin filtro HTTP 200 archivo=Modificaciones_20260825_1432.csv
tipo=ADICION HTTP 200 archivo=Modificaciones_ADICION_20260825_1432.csv
Sin acotar el segmento se omite: un nombre no puede afirmar un alcance que el
archivo no tiene (a59ade1). Que aparecia un tipo espurio -APROPIACION_INICIAL,
que ni siquiera es tipo de modificacion- fue defecto de la sonda: compartia la
lista de criterios con la peticion y la contaminaba al acotarse (246be87).
=== TC-E4 sin filas validas que exportar (RN-ALC8-04 / PRE-MOD-015) ===
HTTP 400
content-type : application/json
cabeceras descarga: 0 <- NO se genera archivo
mensaje : No hay datos válidos para exportar
solucion : Ajuste los filtros del listado hasta obtener al menos una fila
CORREGIDO. El defecto paso por tres estados, y los tres se probaron aqui:
1. Original -> 200 con un CSV de solo cabecera (archivo vacio).
2. Arreglo c2a213a -> 500. La guarda quedo DENTRO de export y para entonces la
respuesta ya estaba comprometida como descarga.
3. Arreglo ca95fb4 -> 400 con el mensaje. Se verifica ANTES de fijar cabeceras.
=== TC-E5 consistencia: seis lecturas seguidas de los tres casos ===
1) sin filas=400 con filas=200 (1902 B) pdf=400
2) sin filas=400 con filas=200 (1902 B) pdf=400
3) sin filas=400 con filas=200 (1902 B) pdf=400
4) sin filas=400 con filas=200 (1902 B) pdf=400
5) sin filas=400 con filas=200 (1902 B) pdf=400
6) sin filas=400 con filas=200 (1902 B) pdf=400
FIN
Cinco de las once evidencias son datos reales, no descripciones. Estos cuatro entraron por
POST /import:
detalle_import.csv — el camino normal. El rubro 8001 ya estaba en el detalle desde TC-04,
así que esa fila se rechaza y la 8002 entra:
codigoCorto,codigoRubro,nombreRubro,valor
8001,2.1.1.01.G001,Sueldos de personal,120000
8002,2.1.2.03.G002,Materiales y suministros,80000
detalle_import_repetido.csv — el rubro ya presente. Trae 999000 a propósito, sobre un
rubro que vale 300000: así la consulta en base prueba que no se sobrescribió, en vez de solo
afirmarlo. Con el mismo valor no habría probado nada:
codigoCorto,codigoRubro,nombreRubro,valor
8001,2.1.1.01.G001,Sueldos de personal,999000
detalle_import_mixto.csv — una fila inválida y una válida: el lote no se cae:
codigoCorto,codigoRubro,nombreRubro,valor
9999,9.9.9.99.X999,Rubro inexistente,10000
8002,2.1.2.03.G002,Materiales y suministros,70000
traslado_import.csv — las columnas Tipo (CC/CR) del Traslado:
tipo,codigoCorto,codigoRubro,nombreRubro,valor
CC,8001,2.1.1.01.G001,Sueldos de personal,50000
CR,8002,2.1.2.03.G002,Materiales y suministros,50000
POST /exportlistado_export_adicion.csv, acotado a ADICION, cuyo nombre lleva el tipo
(Modificaciones_ADICION_20260825_1302.csv). Primeras filas:
Consecutivo,Presupuesto,Documento,Acto Administrativo,Valor,Fecha Elaboracion,Fecha Acto Administrativo,Fecha Aprobacion,Descripcion,Estado
,Ingreso,Adición,IMP-110632,750100,2026-08-24,2026-08-24,,destino de importacion,Borrador
,Ingreso,Adición,CRUD-110557-B,1000000,2026-08-24,2026-08-24,,Bateria completa HT_PRE_011,Para Aprobar
,Ingreso,Adición,RES-REVENTAR-073013,1000,2026-08-24,2026-08-24,,Prueba a reventar HT_PRE_011,Borrador
,Gasto,Adición,W-121233,1000,2026-08-25,2026-08-25,,watch,Borrador
,Gasto,Adición,CERT-IMP-110604,81000,2026-08-25,2026-08-25,,import test,Borrador
Las etiquetas viajan resueltas —Ingreso, Adición, Para Aprobar—, no como claves i18n. Ese
hallazgo apareció así, no probando i18n de frente.
El mismo listado en los otros formatos queda en listado_export.csv, listado_export.json y
listado_export.xml, dentro de la carpeta de la entrega.
03_diagnostico_sar.txt — el diagnóstico canónicoWARN · 40 PASS / 0 FAIL / 1 WARN / 24 N-A sobre develop. El único hallazgo, ASR-PC-DEL-01,
queda justificado por 03_Rules §J.1 y no se corrige. Encabezado y veredicto:
# Diagnostico Canonico SAR — HT_PRE_011
# Ejecutado: 2026-08-25 12:20 -05 · SAR-CORE http://10.120.0.2:8550 (nodo-01)
# Rama: develop (con el fix del import ya mergeado, 7c7f861)
# Exit code: 0 (PASS/WARN)
[sar-diagnose] SAR-CORE http://10.120.0.2:8550 · sprint=14f73f4a-adee-4b22-9a7f-ef3af5a3adb5 service=nebula-budget-core rama=develop
[sar-diagnose] HT=htu/HT_PRE_011_ModificacionPresupuestalElaboracion paquete=src/main/java/com/centrica/nebula/government/budget/core/v1/modificacionpresupuestal
[sar-diagnose] veredicto=WARN (40P/0F/1W/0I/24NA)
WARN ASR-PC-DEL-01 Borrado detectado — cuestionar vs HU
[sar-diagnose] reporte -> /tmp/claude-1001/-home-arquitecto-code-centrica-Documentation/3886d4fd-6611-4880-ad1c-9bbe9f74a966/scratchpad/sar_develop.md
================ REPORTE COMPLETO ================
# Diagnóstico Canónico — HT_PRE_011_ModificacionPresupuestalElaboracion
El reporte completo —matriz de aserciones, reglas dinámicas y contexto del spec— vive en el archivo
de la entrega.
| Version | Fecha | Autor | Descripcion |
|---|---|---|---|
| 1.1.0 | 2026-08-25 | Carlos Torres | Se actualiza con el cierre del defecto 5 del export, recertificado contra api-dev, y con el conteo de instancias en Eureka incorporado a la identificacion. |
| 1.0.0 | 2026-08-25 | Carlos Torres | Creacion de la pagina. La entrega de HT_PRE_011 pasa a servir de ejemplo de referencia, con el documento y el contenido literal de sus evidencias --incluidos los archivos de configuracion y los resultados producidos--. Se separa de la pagina de la plantilla, donde el ejemplo quedaba embebido y confundia los tres artefactos. |