Muestras de laboratorio
La muestra de laboratorio (/lab) es donde TankOS captura las propiedades que el laboratorio mide
sobre el producto de un tanque —densidad, BSW (agua y sedimento), agua libre, temperatura,
presión— y las hace llegar, ya aprobadas, al motor de cálculo custody. Es la máquina de estados
más compleja del sistema: 7 estados y 7 acciones, con separación de funciones (SoD) reforzada
por el servidor, adjuntos auditables en MinIO y una proyección de propiedades que deja la densidad y
el BSW de la muestra disponibles para el cálculo sin re-consultarlos.
Esta página está orientada al técnico de laboratorio: cómo registrar una muestra y moverla por su ciclo de vida, paso a paso.
La densidad y el BSW que aprueba el laboratorio entran directamente en la cadena MPMS (CTL, CSW → NSV y masa). Una muestra mal aprobada se traduce en un NSV mal facturado. Por eso el flujo es estricto: nadie aprueba su propia muestra, los datos erróneos se invalidan (no se borran), y cada paso queda en la auditoría.
Quién hace qué
El acceso a cada acción lo decide la autoridad del rol (RBAC), no el botón de la pantalla. El UI oculta lo que no se puede hacer, pero el servidor es la autoridad.
| Rol | Crear / editar / enviar (lab:submit) | Aprobar / rechazar (lab:approve) | Anular (lab:void) | Invalidar (lab:invalidate) |
|---|---|---|---|---|
| Técnico de laboratorio | ✓ | – | – | – |
| Operador | ✓ | – | – | – |
| Supervisor | ✓ | ✓ | ✓ | ✓ |
El técnico de laboratorio y el operador capturan, editan y envían muestras. El supervisor es quien aprueba o rechaza, y el único que puede anular o invalidar una muestra ya aprobada.
El ciclo de vida en 7 estados
Una muestra nace como borrador (DRAFT) y avanza por las transiciones de abajo. Cada flecha indica
la autoridad que la dispara y la guardia que la protege.
La tabla de transiciones completa (cualquier par estado/acción fuera de esta tabla es un
INVALID_TRANSITION):
| Estado origen | Acción | Estado destino | Autoridad | Guardia / SoD |
|---|---|---|---|---|
DRAFT | Enviar | SUBMITTED | lab:submit | — |
DRAFT | Cancelar | CANCELLED | lab:submit | Solo el submitter |
DRAFT | Eliminar (hard delete) | — | lab:submit | Solo el submitter |
SUBMITTED | Aprobar | APPROVED | lab:approve | SoD: aprobador ≠ submitter |
SUBMITTED | Rechazar | REJECTED | lab:approve | SoD: rechazador ≠ submitter |
SUBMITTED | Cancelar | CANCELLED | lab:submit | Solo el submitter |
REJECTED | Reenviar | SUBMITTED | lab:submit | Solo el submitter |
APPROVED | Anular | VOIDED | lab:void | Supervisor + SoD: anulador ≠ submitter |
APPROVED | Invalidar | INVALIDATED | lab:invalidate | SoD: invalidador ≠ aprobador |
REJECTED es el único estado editable además de DRAFT: el técnico corrige la muestra y la reenvía
(rework) conservando el mismo identificador, de modo que su historia completa queda en la auditoría.
Paso a paso
1. Crear el borrador (DRAFT)
Desde el botón "Nueva muestra" del workspace se abre /lab/new. El técnico selecciona el tanque,
el producto, el método de laboratorio y la fecha/hora de muestreo (sampledAt), y captura las
propiedades medidas: densidad (kg/m³), BSW (fracción), agua libre (mm), temperatura (°C) y
presión (kPa). Todo se almacena en unidades SI; la conversión a imperial es solo de presentación.
sampledAt no puede ser futura. Un retroceso (backdate) de más de 7 días requiere un
backdateReason obligatorio y la autoridad admin:system (D-20). El panel de backdate solo aparece
para el administrador.
2. Editar (solo DRAFT o REJECTED)
Mientras la muestra esté en DRAFT o REJECTED, solo su submitter original puede editarla. En
cualquier otro estado el formulario es de solo lectura.
3. Adjuntar archivos (MinIO)
Antes de enviar, el técnico puede subir certificados o evidencia de laboratorio. Los adjuntos solo se
permiten en DRAFT y REJECTED (ver Adjuntos).
4. Enviar (DRAFT → SUBMITTED)
Al enviar, la muestra pasa a SUBMITTED y queda visible en la cola de aprobación del supervisor. A
partir de aquí el técnico ya no puede editarla (salvo que sea rechazada).
5. Aprobar o rechazar (SUBMITTED → APPROVED | REJECTED)
El supervisor revisa la muestra y la aprueba o la rechaza (con una razón). Ambas acciones están sujetas a la separación de funciones: quien envió la muestra no puede aprobarla ni rechazarla (ver Separación de funciones).
6. Reenviar tras rechazo (REJECTED → SUBMITTED)
Una muestra rechazada vuelve a ser editable. Solo el submitter original la corrige y la reenvía: el identificador no cambia, así que la cadena rechazo → corrección → reenvío queda completa en la auditoría.
7. Cancelar (DRAFT | SUBMITTED → CANCELLED)
El submitter puede cancelar su muestra mientras esté en DRAFT o SUBMITTED. La cancelación es un
estado terminal: la muestra no participa de ningún cálculo.
8. Anular e invalidar una muestra aprobada
Una muestra aprobada que ya no debe usarse se retira por una de dos vías terminales distintas, ambas exclusivas del supervisor (ver VOIDED vs INVALIDATED).
Formulario de nueva muestra

El formulario de /lab/new agrupa: el selector de tanque, el producto, la fecha/hora de
muestra, el método, la ubicación, la API gravity 60 °F, el BSW, el agua, el
sedimento, la API gravity observada, la temperatura observada y las notas. Los campos se
validan contra el contrato Zod compartido (SubmitSampleBody), de modo que el formulario y la API exigen
exactamente lo mismo.
Screenshot pendiente del barrido diferido — ver 61-DEFERRED-SWEEP.md (/lab/new BackdateOverridePanel admin).
El panel de backdate del administrador (retroceso > 7 días con razón obligatoria) aún no tiene captura.
Adjuntos (MinIO)
Cada muestra admite hasta 5 archivos × 5 MB —PDF, CSV o imágenes— para respaldar el resultado de laboratorio.
- El servicio olfatea los bytes del archivo (byte-sniff) para verificar su tipo real, no solo la extensión (D-13).
- Los blobs se guardan en MinIO; la tabla
lab_sample_attachmentsretiene solo los metadatos y laminioKey. - Solo se pueden adjuntar (o quitar) archivos en estados DRAFT y REJECTED.
- La descarga se hace con una URL presignada con TTL; su emisión queda auditada y la URL en sí nunca se persiste.
Separación de funciones — el "segundo par de ojos"
La separación de funciones (SoD) es el corazón custody de este flujo: quien captura nunca aprueba.
La SoD se aplica en el servidor, antes de la mutación, con el interceptor de separación de funciones y CHECK constraints en la base de datos. El UI espeja la regla (oculta el botón), pero la autoridad es el servidor:
- Aprobar / rechazar: el aprobador no puede ser el submitter (
submitterId ≠ approver). - Anular: el anulador no puede ser el submitter (
submitterId ≠ voider). - Invalidar: el invalidador no puede ser el aprobador original (
approverId ≠ invalidater).
Un cliente que llame al endpoint directamente —saltándose el UI— recibe 403.
La revisión se hace en la cola de aprobación del supervisor (/lab/approval), donde cada muestra
SUBMITTED se aprueba o se rechaza en línea.
La captura disponible de /lab/approval no ilustra de forma fiable la feature (la cola de aprobación
con muestras SUBMITTED reales). Para documentarla con datos se debe sembrar datos: enviar una
muestra como técnico y volver a capturar la cola como supervisor (superficie: /lab/approval cola con muestras SUBMITTED). Pendiente del barrido diferido — ver 61-DEFERRED-SWEEP.md (LAB-08).
VOIDED vs INVALIDATED
Una muestra aprobada se retira por dos vías terminales distintas —no son sinónimos—:
| Estado terminal | Acción | Significado | Autoridad |
|---|---|---|---|
| VOIDED | Anular | Anulación administrativa de una muestra aprobada (se requiere reasonCode + reasonText opcional). | lab:void (supervisor) |
| INVALIDATED | Invalidar | Los datos de laboratorio resultaron erróneos — la muestra estaba mal, no se trata de una decisión administrativa. | lab:invalidate |
La distinción importa para la auditoría: VOIDED dice "esta muestra ya no aplica"; INVALIDATED dice
"esta muestra nunca debió usarse porque sus números eran incorrectos".
Proyección de propiedades
Aquí es donde el laboratorio se conecta con el cálculo custody. Cuando una muestra llega a APPROVED, sus propiedades se proyectan a la snapshot del tanque: la densidad y el BSW quedan disponibles para el motor de cálculo sin re-consultar la muestra.
Los resolvedores resolveDensity y resolveBsw solo consideran muestras en estado APPROVED. Una
muestra en DRAFT, SUBMITTED, REJECTED, VOIDED o INVALIDATED no influye en ningún factor
custody. Así, el valor de densidad o BSW que se ve en el detalle de
tanque siempre proviene de una muestra revisada y aprobada — y la
ProvenanceTable de esa pantalla muestra exactamente de qué muestra salió.
Workspace y modos de la muestra
El workspace de laboratorio (/lab) organiza las muestras en 5 pestañas (la pestaña activa
persiste en localStorage): Borradores, En revisión (pending), Rechazadas, Aprobadas y
Anuladas e invalidadas (terminal). El CTA "Nueva muestra" solo aparece para quien tiene
lab:submit.

El detalle de una muestra (/lab/[sampleId]) cambia de modo según su estado:
| Estado | Modo del detalle |
|---|---|
DRAFT | Edición |
REJECTED | Rework (corregir y reenviar) |
SUBMITTED · APPROVED · VOIDED · INVALIDATED | Solo lectura |
Cada detalle muestra el ID en mono, el LabStateBadge de estado y el bloque de rastro de auditoría.
Screenshot pendiente del barrido diferido — ver 61-DEFERRED-SWEEP.md (/lab/[sampleId] modos
edit / rework / readonly).
Historial por tanque
/lab/history/[tankId] presenta el timeline cronológico descendente de todas las muestras de un
tanque, con estilo receipt en mono fixed-grid y chips de supersedes (hacia adelante y hacia atrás).
Es de solo lectura, sin acciones.
Screenshot pendiente del barrido diferido — ver 61-DEFERRED-SWEEP.md (/lab/history timeline).
Páginas relacionadas
- Los aforos manuales del operador —la lectura física de nivel/temperatura/agua libre— se documentan en Aforos manuales.
- Para ver dónde se consumen la densidad y el BSW aprobados (los factores custody y su procedencia), ver el detalle de tanque.
- El rastro de auditoría completo de las acciones de laboratorio se cubrirá en la guía de auditoría (próximamente).