Gestión integral del laboratorio de reproducción asistida para MyFertPlan. Desde el paquete comercial hasta la mesa del embriólogo, desde el cultivo embrionario hasta la facturación al paciente.
El Módulo de Laboratorio gestiona todo lo que ocurre dentro del laboratorio de una clínica de fertilidad. Es impulsado por contratos: el tipo de tratamiento que firma el paciente determina automáticamente qué flujos de trabajo, bloques, formularios y entidades biológicas se crean y rastrean.
El paciente firma un Contrato de Tratamiento seleccionando servicios del catálogo de la clínica. Cada servicio se mapea a un tipo de flujo clínico y determina automáticamente la estructura del caso de laboratorio.
Cada servicio contratado genera un Caso de Lab — un agregado a nivel de ciclo que contiene bloques de flujo ordenados (estimulación, punción, fecundación, cultivo, transferencia, etc.).
Rastrea muestras de semen, cohortes de ovocitos, ovocitos individuales, embriones y unidades de crioalmacenamiento con trazabilidad completa (codificación SEC), máquinas de estado y guardas de elegibilidad para congelación.
Tres puertas de cumplimiento: Testificación (prevención de mezclas), Consentimiento (autorización legal para disposición), y Evento Adverso (biovigilancia).
Cada entidad está aislada por clínica. Las definiciones de tratamiento a nivel de grupo permiten compartir paquetes entre clínicas de una misma red. Imposible filtración de datos entre clínicas.
Los Cargos al Paciente se congelan al firmar el contrato (precio inmutable). Un puerto PaymentProvider soporta pagos manuales ahora y Stripe después, sin cambios en el dominio.
El recorrido completo desde el paquete comercial hasta el resultado clínico del paciente. Este es el flujo de datos central que conecta todas las entidades del módulo.
El módulo define 11 tipos de flujo de trabajo canónicos, cada uno con bloques requeridos y condicionales. Una Definición de Tratamiento mapea exactamente a un tipo de flujo. Los bloques condicionales se activan en tiempo de ejecución por decisiones de disposición o señales de resultado.
ivf_own_gametes
Ciclo completo de FIV/ICSI con los óvulos propios de la paciente y semen de la pareja/donante. Es el flujo de trabajo más completo.
transferDay — día 3 o día 5
(por defecto: 5)
ivf_donor_oocytes_recipient
Ciclo de receptora con ovocitos donados. Sin estimulación ni punción (la donante los aporta por separado). Anonimato: ACTIVADO.
reciprocal_ivf_ropa
Ambas miembros de la pareja son madres intencionadas. Una aporta los óvulos, la otra gesta. Anonimato: DESACTIVADO, consentimiento dual requerido.
| Tipo de Flujo | Categoría | Bloques Requeridos | Condicionales | Flags |
|---|---|---|---|---|
ivf_own_gametes |
FIV Fresca | Estimulación, Punción, Recogida Semen, Fecundación, Cultivo, Gradación | Biopsia, Transferencia, Criopreservación | transferDay: 3|5 |
ivf_donor_oocytes_recipient |
FIV Fresca | Recogida Semen, Fecundación, Cultivo, Gradación | Biopsia, Transferencia, Criopreservación | Anonimato ON |
oocyte_donor_cycle |
Ciclo Donante | Estimulación, Punción | — | Anonimato ON |
oocyte_fertility_preservation |
Preservación Fertilidad | Estimulación, Punción, Criopreservación Ovocitos | — | — |
frozen_embryo_transfer |
Transfer. Embrión Congelado | Descongelación | Transferencia, Criopreservación | transferDay: 3|5 |
intrauterine_insemination |
Inseminación | Recogida Semen, Análisis Semen, Inseminación | — | spermSource: pareja|donante |
semen_analysis |
Servicio Semen | Recogida Semen, Análisis Semen | — | — |
semen_freezing |
Servicio Semen | Recogida Semen, Análisis Semen, Congelación Semen | — | — |
reciprocal_ivf_ropa |
FIV Fresca | Estimulación, Punción, Fecundación, Cultivo, Gradación | Biopsia, Transferencia, Criopreservación | Consentimiento dual |
embryo_donation_recipient |
Transfer. Embrión Congelado | Descongelación | Transferencia, Criopreservación | Anonimato ON |
double_donation_ivf |
FIV Fresca | Recogida Semen, Fecundación, Cultivo, Gradación | Biopsia, Transferencia, Criopreservación | 2 cadenas de donante |
Cada Caso de Lab sigue una máquina de estados desde su creación hasta su finalización. Los bloques dentro del caso siguen su propio ciclo de vida independiente.
Transiciones: planificado → activo | cancelado • activo → completado | cancelado | en_espera • en_espera → activo | cancelado • completado y cancelado son terminales
Cualquier bloque no terminal también puede ser cancelado con un motivo estructurado.
También: detenido (terminal), criopreservado,
descongelado, descartado. Congelar requiere
freezeEligible = true.
Cómo la aplicación decide qué formularios, bloques y flujos de trabajo se necesitan para cada paciente.
Configuración estática en código. 11 tipos canónicos con reglas de bloques fijas, transiciones, parámetros acotados y taxonomías de cancelación. Las clínicas NO pueden modificar esta capa.
Paquetes comerciales por clínica o grupo. Cada uno referencia un tipo de flujo y ajusta parámetros dentro de límites (ej.: día de transferencia 3 vs 5). Define precio y garantía. Configurable por la clínica dentro de los límites.
Acuerdos firmados por paciente. Cada línea de servicio toma una foto inmutable del paquete (precio + garantía). Genera Casos de Lab mediante una fábrica idempotente. Específico del paciente, aislado por clínica.
borrador.
createOrAssociateFromContract() resuelve la Definición de
Tratamiento, obtiene su definición de flujo, valida parámetros y crea un Caso de Lab
con los bloques requeridos en estado planificado.
Cada Caso de Lab tiene una disposición (la decisión fresco/congelado)
que comienza como sin_decidir y se resuelve en tiempo de ejecución:
Estado inicial. No se ha tomado decisión aún.
El embrión se transferirá en fresco. Activa bloque de Transferencia.
Todos los embriones viables se congelan. Activa bloque de Criopreservación.
Congelar para test genético. Activa Criopreservación + Biopsia.
Transferir el mejor + congelar el resto. Ambos bloques activos.
No quedan embriones utilizables. Estado terminal válido.
Los cambios de disposición se registran con changedReason,
decidedBy y decidedAt para auditoría completa.
Cualquier bloque no terminal puede cancelarse con un
motivo estructurado de la taxonomía del flujo. Ejemplos:
sin_respuesta_a_estimulación, ovulación_prematura,
síndrome_folículo_vacío, petición_paciente.
Cancelar un caso se propaga a todos los bloques no terminales (escritura atómica
única). Los sellos a nivel de bloque usan el motivo sintético
cancelación_caso.
No se escriben ni eliminan entidades biológicas en ninguna ruta de
cancelación.
Todas las colecciones, subdocumentos embebidos y sus relaciones. Cada entidad está aislada por clínica.
| Entidad | Tipo | Propósito | Campos Clave |
|---|---|---|---|
| TreatmentDefinition | Colección | Paquete comercial sobre un tipo de flujo |
scope, workflowType, parameters,
pricing, guarantee
|
| TreatmentContract | Colección | Acuerdo firmado del paciente |
status (borrador/activo/completado/cancelado),
patientId, contractedServices[]
|
| ContractedService | Embebido | Línea de servicio dentro de un contrato |
workflowType, treatmentDefinitionId,
guarantee (foto), pricingSnapshot
|
| LabCase | Colección | Agregado del ciclo de laboratorio |
workflowType, status, blocks[],
disposition, relationships[]
|
| LabWorkflowBlock | Embebido | Paso del flujo dentro de un caso |
blockType, requirement, status,
outcome (tipado), witnessEventId
|
| SemenSample | Colección | Ciclo de vida del semen + análisis |
source, state, analysis,
preparation, coding
|
| OocyteCohort | Colección | Agregado de cohorte de punción |
retrieval, denudationCounts (MII/MI/PI/ANOR/DEG/ROTO),
coding
|
| Oocyte | Colección | Ovocito individual |
cohortId, maturity,
fertilizationOutcome (2pn/0pn/1pn/3pn/degenerado)
|
| Embryo | Colección | Seguimiento del ciclo de vida embrionario |
state (7 estados incl. detenido), dailyGrading d2-d6,
pgtResult, freezeEligible
|
| CryostorageUnit | Colección | Inventario de almacenamiento físico |
tank, canister, petal,
materialType, capacity
|
| WitnessEvent | Colección | Prevención de mezclas (solo escritura) |
method, result (coincidencia/discrepancia),
performedBy, witnessedBy
|
| Consent | Colección | Puerta de consentimiento legal |
subjectType, consentType,
dispositionScope[], status
|
| LabAdverseEvent | Colección | Reporte de biovigilancia (solo escritura) |
type (mezcla/pérdida_muestra/...), severity,
reportedToAuthority
|
| PatientCharge | Colección | Registro de facturación |
amountDueMinor, currency,
guaranteeSnapshot, status
|
| Payment | Colección | Intento de pago |
patientChargeId, provider, externalRef,
amountMinor
|
BiologicalCoding con un
internalCode obligatorio (ID de trazabilidad de la clínica) y secuencias
sec opcionales (Código Europeo Único). Los valores SEC son campos de
referencia — nunca claves primarias.
El módulo está construido con Next.js App Router (rutas API), modelos Mongoose/MongoDB y componentes React con MUI. La librería shared-core contiene toda la lógica de dominio, tipos y modelos.
| Método | Ruta | Propósito |
|---|---|---|
GET |
/api/lab/contracts/catalog |
Definiciones activas (clínica + grupo) |
POST |
/api/lab/contracts |
Crear contrato borrador |
GET |
/api/lab/contracts |
Listar contratos (por clínica) |
GET |
/api/lab/cases |
Listar casos de lab (por clínica) |
POST |
/api/lab/cases/[id]/blocks/[blockType]/complete |
Completar bloque + resultado |
POST |
/api/lab/cases/[id]/blocks/[blockType]/cancel |
Cancelar un bloque |
POST |
/api/lab/cases/[id]/cancel |
Cancelar caso completo |
| Componente | Propósito |
|---|---|
/app/lab/page.tsx |
Página principal lab (server, con auth) |
/app/lab/contracts/new/page.tsx |
Página de creación de contrato |
LabPageClient |
Dashboard: tablas de contratos + casos |
LabCasesList |
Tabla de casos de lab (solo lectura) |
ContractCreateWizard |
Asistente 2 pasos: paciente → servicios |
PatientAutocomplete |
Buscador de pacientes (por clínica) |
| Archivo | Propósito |
|---|---|
workflowDefinitions.ts |
Las 11 definiciones de flujo (bloques, transiciones, parámetros, taxonomías de cancelación) |
createOrAssociateFromContract.ts |
Fábrica idempotente de LabCase desde contrato + servicio |
completeBlock.ts |
Completar bloque: validación, puerta de testificación, escritura de entidades, sello de caso |
cancelBlock.ts |
Cancelación de bloque con validación de taxonomía |
cancelLabCase.ts |
Cancelación de caso con cascada a bloques no terminales |
blockOutcomeValidators.ts |
Validación tipada de resultado por tipo de bloque (aserción PHI, checks de esquema) |
assertMaterialBlockWitnessed.ts |
Guarda de puerta de testificación para bloques de manejo de material |
assertConsented.ts |
Guarda de puerta de consentimiento para acciones controladas |
recordWitnessEvent.ts |
Registro de evento de testificación (solo escritura) + evento adverso automático |
resolveTreatmentDefinition.ts |
Resolución de definición: clínica primero, luego grupo |
snapshotContractedService.ts |
Copia profunda de garantía + precio al firmar contrato |
validateLabCaseTransition.ts |
Validación de transición de estado |
validateTreatmentParameters.ts |
Validación de límites de parámetros |
assertEmbryoFreezeEligible.ts |
Guarda de elegibilidad para congelación |
lab:case:read
Ver casos y contratos
lab:case:create
Crear contratos, ver catálogo
lab:case:update
Completar/cancelar bloques
lab:case:validate
Futuro: validar casos
lab:inventory:manage
Futuro: crioalmacenamiento
lab:donor:identity
Des-anonimización (restringido)
Doble capa de seguridad: Motor RBAC (verificación de permiso) +
middleware HTTP (assertLabModuleEnabled). Feature flag:
Clinic.labModule: boolean controla todas las rutas /api/lab/*.
Tres puertas de cumplimiento implementan los requisitos de la Regulación EU SoHO y la Ley española 14/2006. Son invariantes no negociables que no pueden ser omitidas por ninguna ruta de código.
10 tipos de bloque de manejo de material requieren un
EventoTestificación con result: match antes de completarse.
Discrepancia auto-crea un LabAdverseEvent (tipo:
mezcla, gravedad: seria).
Métodos: manual_second_person o
electronic (adaptador RI Witness).
5 acciones controladas requieren un Consentimiento activo y no expirado:
descarte_embrión → consentimiento:
disposición_embrión, alcance: cese_conservación
donar_embrión → consentimiento:
disposición_embrión, alcance: donación_a_otros
descongelar_para_transferencia → consentimiento:
disposición_embrión, alcance: uso_propio
asignación_donante → consentimiento: donación
tratamiento → consentimiento: tratamientoROPA: requiere consentimiento dual (ambas miembros de la pareja).
7 tipos de evento con reporte de biovigilancia:
Gravedad: menor (interno) o seria (SARE —
reportable a la autoridad). Registros solo escritura; transiciones de estado para
investigación/cierre en Fase 2.
clinicId de sesión
WitnessEvent(result:match)
Progreso por fases. El módulo sigue Desarrollo Guiado por Especificaciones (SDD) con puertas de revisión Codex antes de la implementación.
| Feature | Fase | Estado | Descripción |
|---|---|---|---|
lab-foundation-flag-and-rbac |
Fase 1 | Hecho | Flag labModule + permisos RBAC lab + doble capa |
lab-workflow-definitions |
Fase 1 | Hecho | 11 tipos de flujo con bloques, transiciones, parámetros, taxonomías de cancelación |
treatment-definition-catalog |
Fase 1 | Hecho | Modelo TreatmentDefinition con scope clínica/grupo, pricing, validación |
treatment-contract-model |
Fase 1 | Hecho | TreatmentContract + ContractedService con máquina de estados, foto de garantía |
treatment-contract-create |
Fase 1 | Hecho | Asistente UI + API de escritura para creación de contratos borrador |
lab-case-model-and-factory |
Fase 1 | Hecho | Agregado LabCase + fábrica idempotente desde contrato |
witness-event-gate |
Fase 1 | Hecho | Colección WitnessEvent + puerta en bloques de manejo de material |
consent-gate |
Fase 1 | Hecho | Modelo Consent + guarda de puerta para disposición/asignación/descongelación |
plan-labcase-link |
Fase 1 | Hecho | Enlace opcional Plan ↔ LabCase, estados independientes |
patient-charge-payment-domain |
Fase 1 | Hecho | PatientCharge + Payment + proveedor manual + foto de precio |
| Fase 1 Completa — 10/10 features hechos | |||
lab-biological-entities |
Fase 2 | Hecho | SemenSample, OocyteCohort, Oocyte, Embryo, CryostorageUnit + máquinas de estado |
lab-blocks-with-witnessing |
Fase 2 | Hecho | Completar bloques con resultados tipados + enforcement de testificación |
lab-cancellation-and-attrition |
Fase 2 | Hecho | Cancelación bloque/caso + resultados de atrición + estado embrión detenido |
lab-contract-group-scoped-catalog |
Fase 2 | Hecho | Catálogo TreatmentDefinition con scope de grupo + validación POST |
lab-dynamic-disposition |
Fase 2 | Spec Lista | Disposición (planificada/real), embryoDestination, activación condicional de bloques |
lab-adverse-event-biovigilance |
Fase 2 | Pendiente | Reporte de eventos adversos + auto-creación por discrepancia |
| Fase 2 — 4/6 hechos, 1 spec lista, 1 pendiente | |||
stripe-payment-provider |
Fase 4 | Pendiente | Stripe PaymentProvider + Connect + webhooks + reconciliación |
patient-app-payments-and-followup |
Fase 4 | Pendiente | UI de pagos en app del paciente + vistas de seguimiento |
lab-calendar-emr-document-linking |
Fase 4 | Pendiente | Bloques de lab enlazados a citas + resúmenes EMR |
vrepro-lab-adapter |
Fase 4 | Pendiente | Adaptador de sincronización lab VREPRO vía LabExternalSyncState |
/lab/cases/[id])
Riesgos conocidos, áreas sin finalizar y supuestos de la implementación actual.
BlockCompletionPartialError pero requieren
reconciliación manual.
ivf_own_gametes, intrauterine_insemination y
semen_analysis son completables.
Donor (no Patient +
marcador)
manual es suficiente hasta que se integre Stripe
PaymentProvider:
manual ahora, stripe después (sin cambio de dominio)
LabExternalSyncState;
adaptador cuando se confirmen endpoints
contractClosed reemplaza Salesforce legacy