Saltar al contenido principal

0007 — Un solo cierre para la historia clínica, con candado configurable

Estado: aceptada · Fecha: 2026-09-21

Contexto​

20260907000002 unificó el ciclo de vida de todo lo que se escribe en una historia: nace en draft, se corrige libremente, y confirmar lo cierra. La intención era buena. El resultado, no.

Quedaron siete botones de cierre: un «Confirmar» por receta, por pedido, por documento, por adjunto y por toma de signos vitales, más un «Firmar» por evolución y otro por escala. Cada uno con su diálogo de dos pasos.

Y además, un borrador no se podía imprimir: el corte estaba en la pantalla (prescription-section.tsx, botón deshabilitado) y otra vez en el servidor (app/print/prescription/[id]/page.tsx, corte antes de auditar).

Eso obligaba al médico a confirmar receta por receta antes de poder entregar nada, en mitad de la consulta. El efecto práctico fue el contrario del buscado: se confirmaba sin leer. Un diálogo de dos pasos que se pulsa por inercia no protege nada; solo añade dos clics.

Decisión​

1. Un solo botón, en la cabecera del paciente​

health_close_record(contact_id) avanza de golpe todo lo que el paciente tenga en borrador, en una transacción y con una fila de auditoría (health.record.close), porque es un solo acto clínico. health_open_drafts(contact_id) da el mismo recuento sin tocar nada, para poder decir qué se va a cerrar antes de que nadie pulse.

Se hace con UPDATE de conjunto y no llamando a las health_confirm_* una a una: esas re-comprueban el permiso y auditan por fila, y cerrar una consulta con veinte cosas abiertas generaría veinte apuntes para un solo acto. El folio de la receta se asigna en el INSERT (20260914000002), no al confirmar, así que el UPDATE de conjunto no se salta nada.

2. Cerrar no mata la historia​

No hay closed_at en clinical_records, y es deliberado. El cierre afecta a lo que está abierto en ese momento; si el paciente vuelve el mes que viene, lo que se escriba entonces nace en borrador y se cierra con su propio clic.

«Abierta» se deriva de «tiene borradores». Es la única definición que no envejece: una columna closed_at habría que reabrirla, y reabrir una historia clínica es exactamente lo que no queremos que exista.

3. Un borrador se imprime, y sale marcado​

Se quitaron los cortes de las cuatro páginas de impresión. El papel de un borrador lleva el sello BORRADOR en diagonal, reutilizando el mismo mecanismo que ya pintaba el de ANULADO en DocSheet.

Esto no relaja nada: antes el borrador no salía en papel pero la RPC lo devolvía igual —el bloqueo eran dos capas de interfaz, no la base—. Ahora sale, y se distingue.

4. Cerrar y bloquear dejan de ser la misma cosa​

El cierre emite siempre: fecha, folio, firma electrónica disponible, sin marca de borrador. Que además quede cerrado a la corrección se decide por sección y por tenant, y solo lo toca un superadmin (health_save_record_lock), desde Configuración de Salud → Bloqueo.

Actualización (20261107000001). La marca de borrador salió de esa pestaña: es papelería, no política médico-legal, y se cambia con health_save_draft_watermark, desde Configuración de Salud → Marca. Pide un permiso propio, health.draft_mark.manage (20261109000001), sembrado solo a los roles admin/administrador/owner; no core.modules.manage, que abre también activar y desactivar módulos. El candado sigue siendo solo del superadmin.

Por defecto todo bloqueado, que es el comportamiento anterior. Una configuración ausente, mal tecleada, o de un tenant que ni instaló Salud, se comporta exactamente como antes: ausente = true.

ficha no entra en el mapa: los antecedentes no tienen borrador que cerrar.

5. Lo que se escribe se guarda solo​

Todo nace en borrador, así que guardar a mitad no tiene coste clínico. La evolución y la receta se autoguardan a los ~2 s de la última tecla, y salir y volver deja el texto donde estaba. El aviso al salir cubre el hueco: entre la última tecla y el rebote, y los borradores que todavía no son guardables (una evolución sin motivo).

Consecuencias​

  • Desaparecen siete botones y tres bloqueos de impresión. Menos superficie, menos clics de inercia, y el momento del cierre es uno solo y consciente.
  • El cierre firma notas ajenas. signed_by = auth.uid() se pone en todas las evoluciones en borrador del paciente, incluida la que escribió otro profesional. El confirmar por ítem tenía el mismo agujero, pero no de forma sistemática. Mitigación: la meta de health.record.close guarda los ids afectados y su created_by. Queda pendiente decidir si debe además excluirlas.
  • Las escalas piden otro permiso. health_sign_form_instance exige health.forms.fill, no el de historias. Quien no lo tenga cierra todo lo demás y sus escalas quedan en borrador; se devuelve escalas_omitidas y la pantalla lo dice. No se firma nada con un permiso que no se tiene.
  • Se relajaron diez guardias de inmutabilidad, en ocho funciones, para que el candado pueda abrirse. Es lo más delicado de la migración. Se hizo con el patrón de la casa (replace() sobre pg_get_functiondef, con RAISE EXCEPTION si el predicado no aparece), y una fila anulada no vuelve a ser editable jamás, diga lo que diga la configuración.
  • Riesgo a futuro: una migración que haga CREATE OR REPLACE de cualquiera de esas ocho funciones se lleva el candado por delante en silencio. La migración termina con un bloque que lo comprueba y revienta, pero solo salta si se reaplica. Volver a aplicar hoy 20260915000002 dejaría health_save_prescription sin candado.

Alternativas descartadas​

  • Cerrar la historia del paciente para siempre. Un paciente que vuelve necesitaría otra historia. No existe tal cosa.
  • Que la sección sin candado simplemente no se cierre (se quede en borrador). Más simple y sin tocar ningún guardia, pero esa receta nunca llegaría a ser documento emitido: sin fecha de emisión, con marca de borrador y sin firma electrónica, que solo se ofrece sobre lo emitido.
  • Poder reabrir una historia cerrada. El mecanismo de corrección ya existe y deja rastro: la adenda. Reabrir no lo deja.