Calibración y tablas de aforo
Las tablas de aforo (strapping tables) traducen la altura medida del líquido en un volumen, y son el corazón de cualquier cálculo de custodia: si la tabla está mal o no es trazable, el volumen no sirve. Esta página documenta el flujo 6 paso a paso — desde la calibración del dispositivo (con su cadena HMAC y certificado SENCAMER) hasta la importación, revisión, comparación y publicación con separación de funciones (SoD) de una tabla de aforo.
Precondición y roles
El perfil ingeniero (engineering:edit) importa tablas de aforo, ve las versiones y compara
los diffs. La publicación (engineering:publish) la hace un ingeniero o un supervisor.
La regla de oro de control interno se aplica en la publicación: el publicador no puede ser el
mismo usuario que hizo la importación (separación de funciones — SoD).
Las rutas de este flujo cuelgan del detalle del tanque:
| Ruta | Para qué |
|---|---|
/tanks/[id]/strapping/import | Asistente de importación (wizard de 4 pasos). |
/tanks/[id]/strapping/versions | Lista de versiones de la tabla de aforo del tanque. |
/tanks/[id]/strapping/versions/[v] | Booklet (libreta de aforo) de una versión. |
/tanks/[id]/strapping/versions/[v]/diff/[to] | Comparación entre dos versiones. |
Calibración del dispositivo (cadena HMAC)
Antes de hablar de la tabla de aforo conviene situar la calibración del dispositivo, porque
ambas alimentan la trazabilidad de grado custody. Desde la pestaña Calibración del detalle de
un dispositivo, registrar una calibración sube el PDF del certificado SENCAMER a MinIO y crea
un evento inmutable: TankOS calcula el hash SHA-256 del PDF y, sobre una cadena canónica que enlaza
cada evento con el anterior (prevEventHash), un HMAC-SHA256 encadenado. El resultado es un
historial append-only y a prueba de manipulación.
A nivel de base de datos se revocaron las operaciones UPDATE y DELETE sobre los eventos de
calibración: una vez registrado, un evento no se puede modificar ni borrar. Cualquier intento de
alterar la historia rompe la cadena HMAC y queda en evidencia. Esto sostiene la trazabilidad
exigida para transferencia custody.
La calibración del dispositivo (el certificado del radar) y la tabla de aforo (la geometría del tanque traducida a volumen) son cosas distintas. El detalle completo de la cadena HMAC del dispositivo está en dispositivos y radares; aquí nos centramos en la tabla de aforo.
Screenshot pendiente del barrido diferido — ver 61-DEFERRED-SWEEP.md (pestaña Calibración con el
historial de la cadena HMAC y la carga del certificado).
Importar una tabla de aforo (wizard de 4 pasos)
La importación de una tabla de aforo es un asistente de cuatro pasos en
/tanks/[id]/strapping/import (StrappingImportWizard). Cada paso vive en la URL
(?step=upload|confirm|preview|commit):
| Paso | Qué haces | Detalle |
|---|---|---|
| 1. Subir | Cargas la tabla (.csv, .xlsx, .json) y, opcionalmente, el PDF del certificado (máx. 2 archivos, 50 MB en total). | Se registra un StrappingImportAttempt en estado dry_run; el servicio intenta detectar el formato automáticamente. |
| 2. Confirmar | Revisas los chips detectedForm y detectedLocale y la confianza de la detección; si hace falta, sobreescribes la forma de almacenamiento y el denominador fraccionario. | storageForm: flat (nivel→volumen), main_fractional (pies + fracción) o incremental_factors (factores incrementales). declaredFractionalDenominator: 8 (1/8") o 16 (1/16") — crítico para la interpolación SC#1 MPMS. |
| 3. Vista previa | Examinas la StrappingErrorTable con los diagnósticos del dry-run: filas parseadas, advertencias y errores. | Las filas con error se marcan en rojo. El botón Continuar queda deshabilitado si la confianza es < 0,8 sin override explícito, o si hay errores en el dry-run. |
| 4. Commit | Confirmas la importación. | El servicio verifica que el SHA-256 del archivo coincide con el del dry-run, crea la fila StrappingTable con effectiveFrom = NULL (= pendiente de publicación) y pasa el intento a committed. |
Importar y confirmar no pone la tabla en producción. La nueva versión nace en estado
pendiente (effectiveFrom = NULL); recién se vuelve activa cuando alguien la publica — y esa
publicación tiene su propia puerta de SoD (más abajo).
Screenshot pendiente del barrido diferido — ver 61-DEFERRED-SWEEP.md (los 4 pasos del asistente de
importación de strapping: Subir, Confirmar, Vista previa con StrappingErrorTable, Commit).
Lista de versiones
La lista de versiones (/tanks/[id]/strapping/versions) muestra todas las tablas de aforo del tanque
con su número de versión, estado (badge activa / pendiente / sustituida), vigencia
(desde / hasta) y quién la creó. Desde aquí se lanza una nueva importación, se publica una
versión pendiente y se accede al diff entre versiones.

Las filas en estado pendiente ofrecen el botón Publicar (un Server Action inline); el enlace ver diff abre la comparación con otra versión.
Booklet de la tabla de aforo
El booklet de una versión (/tanks/[id]/strapping/versions/[v]) presenta la tabla de aforo como
una libreta de aforo estilo recibo, en grilla mono de ancho fijo, igual que la documentación de
calibración impresa.

El booklet se compone de:
- Meta-strip de provenance (12 celdas): metadatos de origen de la tabla (quién importó, cuándo, hashes).
- Tabla principal en grilla mono de ancho fijo, en una de las tres formas de almacenamiento:
main + fractional,flatoincremental_factors. - Panel de ejemplo trabajado: una interpolación de muestra para ilustrar la lectura de la tabla.
- Folds colapsables:
critical_zone,bottom_tableyroof_correction. - Footer de provenance (8 celdas):
payload_hashy metadatos de importación y publicación.
El booklet nunca usa las preferencias del usuario para las alturas: el régimen del tanque manda. Régimen SI ⇒ milímetros y metros cúbicos; régimen US-MPMS ⇒ pies-pulgadas-fracción y barriles. Esto evita que una preferencia de visualización contamine la lectura de un documento de calibración.
Comparar versiones (diff viewer)
El diff viewer (/tanks/[id]/strapping/versions/[v]/diff/[to]) compara dos versiones de la tabla
y se organiza en tres tarjetas:
- Metadatos modificados — lista de los campos de cabecera que cambiaron entre versiones.
- Gráfico ECharts — un overlay de volumen vs. altura de ambas versiones, para ver la divergencia de la curva de un vistazo.
- Tabla de buckets delta — diez tramos con su
Δm³yΔ%; los buckets con cambios significativos llevan un acento de advertencia (no bloqueante).
El diff es la herramienta para justificar una recalibración antes de publicarla: muestra exactamente en qué tramos del tanque cambia el volumen.
Screenshot pendiente del barrido diferido — ver 61-DEFERRED-SWEEP.md (diff viewer: metadatos, gráfico
ECharts de volumen vs. altura y tabla de buckets delta).
Publicar una versión (puerta SoD)
Publicar es el paso que pone una versión en producción y cierra la versión activa anterior. Es también donde se aplica la regla de control interno más importante de este flujo.
- Desde la lista de versiones, el botón Publicar de una fila pendiente lanza el Server Action
(
POST /tanks/:id/strapping/versions/:v/publish, autoridadengineering:publish). - El servicio serializa las publicaciones concurrentes con
SELECT FOR UPDATE. - Si ya existe una versión activa, le pone
effectiveTo = NOW()(la cierra) antes de activar la nueva. - La nueva versión recibe
effectiveFrom = NOW()y pasa a ser la activa del tanque. - Se escribe una fila de auditoría con
action = 'PUBLISH'.
El usuario que hizo el commit de la importación no puede ser quien publica esa misma
versión. El servidor lo verifica con assertSeparationOfDuties: si coinciden, la operación falla con
SeparationOfDutiesError → HTTP 403. No existe bypass (el código SOD_BYPASS_AUTHORITY se
retiró en el Plan 06-10). En la lista de versiones, una violación de SoD se muestra como un error
inline con acento rojo. La consecuencia práctica: la importación y la publicación de una tabla de
aforo siempre las firman dos personas distintas.
Una vez publicada, el effectiveFrom de la versión es inmutable (lo garantiza un trigger de base
de datos): solo se puede cerrar la versión (poniéndole effectiveTo) al publicar otra. Nunca se
edita una tabla activa "en caliente".
Estados de la tabla de aforo
Una tabla de aforo atraviesa tres estados a lo largo de su vida:
| Estado | effectiveFrom | effectiveTo | Significado |
|---|---|---|---|
| Pendiente | NULL | NULL | Importada y confirmada, pero aún no publicada. No participa en cálculos. |
| Activa | timestamp | NULL | Publicada y vigente. Es la tabla que usa el motor de cálculo. |
| Histórica | timestamp | timestamp | Fue activa, y luego una publicación posterior la cerró. |
En todo momento existe como máximo una versión activa por tanque (lo garantizan el
SELECT FOR UPDATE de la publicación y la restricción de unicidad @@unique([tankId, version])). Por
convención, las versiones se retienen 5 años.
Páginas relacionadas
- El detalle de la calibración del dispositivo (cadena HMAC, certificado, pestañas) está en dispositivos y radares.
- El acceso a la tabla de aforo activa desde la configuración del tanque está en la pestaña Tablas de aforo del editor de tanque.
- Las plantillas de strapping compartidas entre tanques se gestionan en catálogos.