Saltar al contenido
DispensAI

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 products con parent_product_id apuntando al padre. Es la unidad de dispensación. Tiene stock, price, unit_grams y lot_inventory_id propios.
  • 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 en lot_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

RolResponsabilidad
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

  1. El producto padre existe en el catálogo y tiene active = true.
  2. El administrador tiene sesión activa con rol admin.
  3. El lote de origen está registrado en lot_traceability y tiene balance disponible.
  4. 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

  1. Acceso al producto padre. El administrador ingresa a /admin/medicina/[id]/editar y selecciona la opción de nuevo formato.

  2. Carga de datos del formato. El sistema presenta un formulario con: format_label, category, price, stock, unit_grams y lot_inventory_id. Los campos name y branch_id se heredan del padre sin intervención del administrador.

  3. Validación y registro. La server action createMedicineFormat valida sesión y rol. Si pasa, construye el payload del hijo con todos los campos y ejecuta la inserción en products. Ante un conflicto de slug (PGRST204), reintenta la inserción una vez sin el slug generado.

  4. 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 controlTipoEvidencia
Autenticación activaAuth check en server actionsupabase.auth.getUser() en createMedicineFormat
Rol adminRole check en server actionprofile.role !== "admin" → error (CONFIRMADO por lectura directa)
parent_product_id obligatorioCampo en payloadHardcodeado en createMedicineFormat (CONFIRMADO)
unit_grams en payloadCampo en payloadIncluido en baseChildPayload (CONFIRMADO)
lot_inventory_id en payloadCampo en payloadIncluido 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 duplicadoIdempotencia parcialLógica ante PGRST204 (CONFIRMADO)

10. Criterios de decisión

Este protocolo resuelve si el formato queda registrado o no registrado.

SituaciónResultado
Sesión activa, rol admin, padre localizado y payload completoFormato 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 adminNo registrado; la server action retorna error antes de operar
El producto padre no se localizaNo 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 causaNo 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ónRespuesta del sistema
Usuario sin sesiónLa server action retorna error antes de operar
Usuario con rol no admin{ error: "No autorizado" }
Padre no encontradoLa 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 products con parent_product_id poblado, unit_grams y lot_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

IndicadorFórmulaMetaFuente
Formatos con lot_inventory_id pobladoFormatos con lote ÷ total de formatos activos100 %Tabla products
Formatos con unit_grams > 0Formatos con gramos por unidad ÷ total de formatos activos100 %Tabla products
Formatos duplicados por padreConteo de (parent_product_id, format_label) repetidos0Tabla products

14. Riesgos y controles

RiesgoControlTipo
Creación de formato por un usuario sin autorizaciónGuarda profile.role !== "admin" en createMedicineFormat (§9)Preventivo
Formato sin lote de origen que luego se dispensa, rompiendo la cadena de custodiaSin 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ónSin 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 bajaSin control en el sistema — brecha 3 declarada en §17. Mitigación operativa: requisito previo 1 (§6) y revisión del catálogo activoCorrectivo
Dos formatos con el mismo format_label bajo el mismo padreSin 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 formatoHerencia de name y branch_id desde el padre, hardcodeada en baseChildPayloadPreventivo
Aparecer como establecimiento comercialEl formato registra la presentación de la medicina de la organización y su aporte asociado, no una oferta de venta al públicoPreventivo

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

NormaReferenciaQué cubre en este protocolo
ISO 9001:2015§8.5.2Identificació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.4Preservación: herencia de branch_id y name desde el padre preserva la identidad de la medicina
Ley 20.000Custodia y trazabilidadEl vínculo lote→formato→dispensación es el núcleo de la trazabilidad legal
Decreto 84/2015 MINSALRegistro de dispensaciónEl 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 protocoloDó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:

  1. Sin evento ISO al crear formato. La server action createMedicineFormat no tiene llamada confirmada a logISOSystemEvent. La definición de unit_grams — el parámetro que determina cuántos gramos de flor consume cada dispensación — no queda registrada en iso_system_events. Si ese valor se ingresa erróneamente, no hay evento que permita auditar cuándo ocurrió ni quién lo cargó.

  2. lot_inventory_id nullable — CONFIRMADO. Migración 20260509000000_inventory_product_integration.sql línea 11 define la columna como lot_inventory_id uuid REFERENCES public.lot_inventory(id) ON DELETE SET NULL — sin NOT NULL. Migración 20260509000001_product_formats.sql la reafirma como UUID sin restricción. Un formato puede existir sin lote asignado. Las dispensaciones posteriores de ese formato no tendrán lote trazable en lot_product_assignments, rompiendo la cadena de custodia.

  3. 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.

  4. Sin restricción de unicidad de format_label por padre — CONFIRMADO. Migración 20260509000001_product_formats.sql: columna agregada como format_label TEXT sin índice UNIQUE. No se encontró UNIQUE sobre (parent_product_id, format_label) en ninguna migración revisada. Dos formatos con el mismo format_label bajo 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ónFechaCambioAutor
1.02026-08-28Versión inicial. Redactada desde la corrida de evidencia 2026-08-28-CAT-LOT. Cuatro brechas declaradas.Catálogo
1.12026-08-29Cierre 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.22026-08-29Realineamiento 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