CAT-003
Creación de formato / presentación
Registrar un formato de presentación de una medicina ya existente, vinculándolo a un lote de origen y definiendo los gramos de flor que consume cada unidad, de modo que la trazabilidad gramo a gramo pueda operar.
CAT-003 — Creación de formato / presentación
1. Objetivo
Definir el procedimiento mediante el cual el administrador de catálogo registra un nuevo formato de presentación (producto hijo) para una medicina ya existente en el catálogo. El formato es la unidad operativa de la dispensación: tiene stock propio, define los gramos de flor que consume cada unidad (unit_grams) y debe estar vinculado a un lote de origen (lot_inventory_id).
2. Alcance
Aplica a la creación de toda presentación nueva bajo un producto padre existente. Incluye: cápsulas, aceites, flores, extractos, y cualquier otra forma galénica que la organización distribuya. No aplica al alta del producto padre (CAT-001) ni a la edición de formatos existentes (CAT-002).
3. Definiciones
- Formato (producto hijo): registro en
productsconparent_product_idapuntando al padre. Es la unidad de dispensación. Tienestock,price,unit_gramsylot_inventory_idpropios. unit_grams: gramos de flor seca que consume cada unidad de este formato. Es la unidad común del sistema junto con el presupuesto de receta (prescription_budget_v1).lot_inventory_id: referencia al lote de origen enlot_traceability. Vincula el formato a la cadena de masa.format_label: etiqueta que distingue este formato de otros hijos del mismo padre (ej. “30 cápsulas”, “10 ml aceite”).
4. Roles y responsables
| Rol | Responsabilidad |
|---|---|
| Admin catálogo | Único rol autorizado para crear formatos. Verificado por profile.role = "admin" en createMedicineFormat. |
5. Disparador de inicio
La organización define una presentación nueva para una medicina que ya figura en el catálogo. El administrador de catálogo ingresa a /admin/medicina/[id]/editar sobre el producto padre correspondiente y selecciona la opción de nuevo formato.
6. Requisitos previos
- El producto padre existe en el catálogo y tiene
active = true. - El administrador tiene sesión activa con rol
admin. - El lote de origen está registrado en
lot_traceabilityy tiene balance disponible. - Se conocen los gramos de flor por unidad del formato (
unit_grams).
7. Entradas
Datos del formato, cargados en el formulario de nuevo formato:
format_label— etiqueta que distingue esta presentación de los demás hijos del padre.category.price.stock.unit_grams— gramos de flor seca por unidad.lot_inventory_id— lote de origen.
Heredados del producto padre sin intervención del administrador: name y branch_id.
No hay documentos adjuntos ni datos de socios en este flujo: las entradas son información de catálogo e inventario de la organización.
8. Procedimiento paso a paso
-
Acceso al producto padre. El administrador ingresa a
/admin/medicina/[id]/editary selecciona la opción de nuevo formato. -
Carga de datos del formato. El sistema presenta un formulario con:
format_label,category,price,stock,unit_gramsylot_inventory_id. Los camposnameybranch_idse heredan del padre sin intervención del administrador. -
Validación y registro. La server action
createMedicineFormatvalida sesión y rol. Si pasa, construye el payload del hijo con todos los campos y ejecuta la inserción enproducts. Ante un conflicto de slug (PGRST204), reintenta la inserción una vez sin el slug generado. -
Disponibilidad del formato. El sistema redirige a la ficha del padre (
/admin/medicina/[id]/editar) donde el nuevo formato aparece listado.
9. Puntos de control
| Punto de control | Tipo | Evidencia |
|---|---|---|
| Autenticación activa | Auth check en server action | supabase.auth.getUser() en createMedicineFormat |
| Rol admin | Role check en server action | profile.role !== "admin" → error (CONFIRMADO por lectura directa) |
parent_product_id obligatorio | Campo en payload | Hardcodeado en createMedicineFormat (CONFIRMADO) |
unit_grams en payload | Campo en payload | Incluido en baseChildPayload (CONFIRMADO) |
lot_inventory_id en payload | Campo en payload | Incluido en baseChildPayload. La columna es nullable en la base (ON DELETE SET NULL): el vínculo con el lote no está impuesto por restricción. Ver brecha 2 en §17 |
| Retry ante slug duplicado | Idempotencia parcial | Lógica ante PGRST204 (CONFIRMADO) |
10. Criterios de decisión
Este protocolo resuelve si el formato queda registrado o no registrado.
| Situación | Resultado |
|---|---|
Sesión activa, rol admin, padre localizado y payload completo | Formato registrado en products con parent_product_id apuntando al padre, unit_grams > 0 y lot_inventory_id poblado; visible en la ficha del padre en /admin/medicina/[id]/editar; su stock queda disponible para la asignación a solicitudes de socios |
Falta sesión o el rol no es admin | No registrado; la server action retorna error antes de operar |
| El producto padre no se localiza | No registrado; la acción no puede construir el payload |
Conflicto de slug (PGRST204) | Reintento automático de la inserción sin slug generado; si el reintento pasa, el formato queda registrado |
| La inserción falla por otra causa | No registrado; la server action retorna { error } con el código de la base |
El sistema no decide si lot_inventory_id está poblado: la columna es nullable y un formato sin lote se registra igual. Esa decisión queda del lado del operador — ver brecha 2 en §17.
11. Excepciones y escalamiento
Caminos de error del sistema:
| Situación | Respuesta del sistema |
|---|---|
| Usuario sin sesión | La server action retorna error antes de operar |
| Usuario con rol no admin | { error: "No autorizado" } |
| Padre no encontrado | La acción no puede construir el payload; retorna error |
| Error de DB | { error: "Error al crear formato: ..." } con código |
Conflicto de slug. Ante un conflicto de slug, la acción reintenta la inserción sin slug. Es el único mecanismo de idempotencia del flujo.
Envío duplicado del formulario. Más allá del caso anterior no hay idempotencia completa: dos envíos generan dos formatos con el mismo format_label bajo el mismo padre, y la base de datos no lo impide (brecha 4 en §17). Detectado un duplicado, el administrador de catálogo da de baja el formato sobrante antes de que reciba asignaciones de lote, y deja constancia del motivo.
Formato creado sin lote de origen. Si un formato queda con lot_inventory_id vacío, no se dispensa: el administrador de inventario asigna el lote (LOT-001) antes de habilitarlo. Un formato dispensado sin lote rompe la cadena de custodia y no es recuperable a posteriori.
Formato creado bajo un padre dado de baja. El sistema no lo impide (brecha 3 en §17). Detectado el caso, el formato se da de baja junto con su padre y se escala al responsable de catálogo de la organización.
Escalamiento. Un error de base de datos persistente se escala al responsable de TI. La creación no se fuerza por vías directas a la base de datos: hacerlo saltearía la guarda de rol y la herencia de name y branch_id desde el padre.
12. Registros generados
- Fila en la tabla
productsconparent_product_idpoblado,unit_gramsylot_inventory_id. - No se generan documentos clínicos ni PDFs.
- No se genera evento en
iso_system_events— ver brecha 1 en §17.
13. Indicadores
| Indicador | Fórmula | Meta | Fuente |
|---|---|---|---|
Formatos con lot_inventory_id poblado | Formatos con lote ÷ total de formatos activos | 100 % | Tabla products |
Formatos con unit_grams > 0 | Formatos con gramos por unidad ÷ total de formatos activos | 100 % | Tabla products |
| Formatos duplicados por padre | Conteo de (parent_product_id, format_label) repetidos | 0 | Tabla products |
14. Riesgos y controles
| Riesgo | Control | Tipo |
|---|---|---|
| Creación de formato por un usuario sin autorización | Guarda profile.role !== "admin" en createMedicineFormat (§9) | Preventivo |
| Formato sin lote de origen que luego se dispensa, rompiendo la cadena de custodia | Sin control en base de datos: lot_inventory_id es nullable — brecha 2 declarada en §17. Mitigación: indicador de formatos con lote poblado (§13) y verificación antes de habilitar el formato (§11) | Detectivo |
unit_grams cargado erróneamente, alterando los gramos que consume cada dispensación | Sin evento auditable que registre cuándo ni quién lo cargó — brecha 1 declarada en §17. Mitigación: indicador de unit_grams > 0 y revisión del catálogo contra lot_balance (LOT-003) | Detectivo |
| Formato creado bajo un producto padre dado de baja | Sin control en el sistema — brecha 3 declarada en §17. Mitigación operativa: requisito previo 1 (§6) y revisión del catálogo activo | Correctivo |
Dos formatos con el mismo format_label bajo el mismo padre | Sin restricción UNIQUE en base de datos — brecha 4 declarada en §17. Mitigación: indicador de duplicados (§13) | Detectivo |
| Pérdida de identidad de la medicina al crear el formato | Herencia de name y branch_id desde el padre, hardcodeada en baseChildPayload | Preventivo |
| Aparecer como establecimiento comercial | El formato registra la presentación de la medicina de la organización y su aporte asociado, no una oferta de venta al público | Preventivo |
Privacidad. Los datos de este flujo son información de catálogo de la organización. No aplica tratamiento de datos personales de socios.
15. Normativa y referencias
| Norma | Referencia | Qué cubre en este protocolo |
|---|---|---|
| ISO 9001:2015 | §8.5.2 | Identificación y trazabilidad: unit_grams y lot_inventory_id son los eslabones que unen el formato a la cadena de masa |
| ISO 9001:2015 | §8.5.4 | Preservación: herencia de branch_id y name desde el padre preserva la identidad de la medicina |
| Ley 20.000 | Custodia y trazabilidad | El vínculo lote→formato→dispensación es el núcleo de la trazabilidad legal |
| Decreto 84/2015 MINSAL | Registro de dispensación | El formato es la unidad registrada en cada acto de dispensación |
El cumplimiento de estas normas no está certificado por ninguna autoridad. Este protocolo declara qué controles ejecuta la organización, no que un tercero los haya auditado.
16. Anexos y formularios vinculados
- Alta de producto padre ([[wiki/entities/dispensai/protocolos/CAT-001]])
- Edición de ficha de medicina ([[wiki/entities/dispensai/protocolos/CAT-002]])
- Asignación de lote a producto ([[wiki/entities/dispensai/protocolos/LOT-001]])
- Control de balance de masa ([[wiki/entities/dispensai/protocolos/LOT-003]])
17. Implementación en el sistema
El protocolo está implementado en su registro. La herencia de nombre y sucursal desde el padre está en código; unit_grams y lot_inventory_id son campos del payload sin restricción NOT NULL confirmada en base de datos.
| Elemento del protocolo | Dónde vive |
|---|---|
| Guardia de autenticación (paso 3) | supabase.auth.getUser() en createMedicineFormat |
| Guardia de rol admin (paso 3) | profile.role !== "admin" → error en createMedicineFormat |
Herencia de name y branch_id del padre (paso 3) | Hardcodeado en baseChildPayload de createMedicineFormat |
parent_product_id obligatorio (paso 3) | Hardcodeado en baseChildPayload; campo en tabla products |
unit_grams en payload (paso 3) | Campo en baseChildPayload; restricción NOT NULL no confirmada en DB |
lot_inventory_id en payload (paso 3) | Campo en baseChildPayload; restricción NOT NULL no confirmada en DB |
| Idempotencia parcial ante slug duplicado (paso 3) | Retry ante error PGRST204 en createMedicineFormat |
| Visibilidad del formato (paso 4) | Ruta /admin/medicina/[id]/editar, revalidatePath tras inserción |
Brechas conocidas:
-
Sin evento ISO al crear formato. La server action
createMedicineFormatno tiene llamada confirmada alogISOSystemEvent. La definición deunit_grams— el parámetro que determina cuántos gramos de flor consume cada dispensación — no queda registrada eniso_system_events. Si ese valor se ingresa erróneamente, no hay evento que permita auditar cuándo ocurrió ni quién lo cargó. -
lot_inventory_idnullable — CONFIRMADO. Migración20260509000000_inventory_product_integration.sqllínea 11 define la columna comolot_inventory_id uuid REFERENCES public.lot_inventory(id) ON DELETE SET NULL— sinNOT NULL. Migración20260509000001_product_formats.sqlla reafirma comoUUIDsin restricción. Un formato puede existir sin lote asignado. Las dispensaciones posteriores de ese formato no tendrán lote trazable enlot_product_assignments, rompiendo la cadena de custodia. -
Sin validación de estado activo del padre. La operación lee del producto padre únicamente su identificador, nombre, sucursal y su propio padre; comprueba que exista y que no sea a su vez un formato, pero no consulta si está activo. Un formato puede crearse bajo un padre dado de baja sin que el sistema lo impida, y queda disponible para dispensación arrastrando el nombre de una medicina que la organización ya retiró del catálogo.
-
Sin restricción de unicidad de
format_labelpor padre — CONFIRMADO. Migración20260509000001_product_formats.sql: columna agregada comoformat_label TEXTsin índice UNIQUE. No se encontróUNIQUEsobre(parent_product_id, format_label)en ninguna migración revisada. Dos formatos con el mismoformat_labelbajo el mismo padre son posibles en base de datos. La ambigüedad en el catálogo puede generar errores en la asignación de solicitudes.
18. Historial de versiones
| Versión | Fecha | Cambio | Autor |
|---|---|---|---|
| 1.0 | 2026-08-28 | Versión inicial. Redactada desde la corrida de evidencia 2026-08-28-CAT-LOT. Cuatro brechas declaradas. | Catálogo |
| 1.1 | 2026-08-29 | Cierre de INFERIDO: lot_inventory_id confirmado nullable por migración 20260509000000_inventory_product_integration.sql (brecha 2). Sin UNIQUE sobre format_label confirmado por migración 20260509000001_product_formats.sql (brecha 4). Brechas 1, 3 y 4 mantienen su estado — sin nueva evidencia de ISO event ni validación de padre activo. Estado ascendido a Vigente. | Catálogo |
| 1.2 | 2026-08-29 | Realineamiento estructural al contrato del catálogo (_template.md): los 18 títulos de sección pasan a los de la plantilla y el contenido se redistribuye según lo que cada sección exige. Definiciones pasan de §4 a §3; roles de §5 a §4; requisitos previos de §6 a §6 con su título de contrato. Se escriben desde cero §5 Disparador de inicio y §7 Entradas, ambas derivadas de la evidencia ya presente en §8. Los criterios de éxito pasan a §10 Criterios de decisión; caminos de error e idempotencia se consolidan en §11 Excepciones y escalamiento, donde además se declara el tratamiento operativo de las cuatro brechas; §14 pasa de privacidad a Riesgos y controles conservando la declaración de privacidad. El antiguo §3 Marco normativo se absorbe en §15, que ya cubría las mismas tres normas. Sin cambios en §17: las cuatro brechas se mantienen íntegras con su evidencia de migración. | Catálogo |
Documentos asociados
- Ficha de producto padre
- Lote de origen
Registros y evidencia
- Registro de creación en tabla products (hijo)
- Asignación de lote en lot_inventory_id
Indicadores
- % de formatos con lot_inventory_id poblado
- % de formatos con unit_grams mayor a cero
Normativa y referencias
- Ley 20.000
- ISO 9001:2015 §8.5.2
- ISO 9001:2015 §8.5.4
Dónde deja rastro en el sistema
- Rutas
-
- /admin/medicina/[id]/editar
- Tablas
-
- products