
Construí una demo para reproducir un error famoso de la IA. Mi línea base se negó a cometerlo.
El error que no logré provocar
Empecé esta construcción queriendo recrear un fallo concreto y bien documentado. La IA de emparejamiento de pacientes lee una nota clínica como texto, así que confunde palabras que se parecen pero significan cosas distintas en medicina. El ejemplo canónico es limpio: un ensayo de anticoagulante en fase III excluye a pacientes con un previo cateterismo cardíaco, la nota de un paciente dice colocación de catéter venoso central, un emparejador por similitud ve dos procedimientos de catéter cardiovascular, los puntúa como cercanos y excluye a un paciente que en realidad era elegible. Evaluaciones publicadas confirman que modelos reales cometen exactamente este error (Fierce Biotech, 2025). Quería que mi demo lo mostrara ocurriendo y luego mostrara a mi motor atrapándolo.
Así que escribí una línea base justa para hacer de villano. Similitud coseno TF-IDF a nivel de entidad, n-gramas de palabra y n-gramas de caracteres de 3 a 5, un método real de similitud vectorial. Incluso le di una configuración generosa y validé de forma cruzada su umbral de decisión en su propio beneficio (ROC estratificado de 3 pliegues, J de Youden, semilla 13, fijando en t = 0.6932), porque un hombre de paja no prueba nada. Luego ejecuté el caso del cateterismo cardíaco y esperé la exclusión injusta.
No llegó. La línea base puntuó las dos frases de catéter cómodamente por debajo de su propio umbral validado de forma cruzada y devolvió elegible. El error en torno al cual había construido toda la demo simplemente no se reproducía.
Me había propuesto escenificar un fallo famoso y descubrí que mi villano honesto era demasiado débil para cometerlo.
La razón resultó instructiva, y quiero ser preciso al respecto porque es fácil exagerar. Una dispersa línea base léxica no produce esa exclusión falsa en particular. Necesita embeddings semánticos densos para acercar esas dos frases lo suficiente como para disparar el umbral. Añadir un modelo de embeddings pesado habría convertido la demo en algo que no puedes ejecutar con un solo comando sin conexión, así que tomé una decisión: mantener la línea base honesta y dispersa, y dejar de fingir que comete un delito que no puede cometer. Esa decisión reorganizó toda la pieza que estoy construyendo. Si quieres ejecutarla tú mismo, está en veriprajna.com/es/demos/motor-de-razonamiento-de-elegibilidad-para-ensayos-clinicos.
¿Qué es realmente una vía central?
Aun así conservé el caso del cateterismo cardíaco, porque resultó demostrar algo mejor que un error atrapado. Demuestra por qué la respuesta de mi motor es confiable en absoluto. Ambos conceptos aquí tienen identificadores SNOMED-CT reales y comprobables. Cateterismo venoso central es 392230005. Cateterismo cardíaco es 41976001. Puedes pegar cualquiera en cualquier navegador público de SNOMED y confirmar que están en ramas distintas de la jerarquía. No hay ninguna ruta is-a de uno al otro. Una vía central no es un cateterismo cardíaco, y solo una jerarquía lo sabe.
Esa es toda la tesis en un solo arco de un grafo. Una puntuación de similitud no puede representar «is-a». Solo puede representar «estas cadenas se parecen», y parecerse no es lo mismo que significar lo mismo. Cuando mi motor evalúa la exclusión «sin cateterismo cardíaco previo», no puntúa nada. Plantea una pregunta estructural: ¿el hecho verificado del paciente está subsumido por el concepto prohibido? Recorre la ontología, no encuentra ninguna ruta de subsumción y devuelve elegible con una traza de tres pasos que nombra ambos ID de concepto y el arco del grafo que comprobó.

Cuando vi por primera vez renderizarse esa traza, lo que me impactó no fue el veredicto. Fue el recibo debajo. La caja de la línea base en la misma pantalla muestra un número de similitud sin procedencia y nada reproducible. La caja de mi motor nombra los dos SCTID y la pregunta exacta is-a que formuló. Uno de estos lo puede archivar un regulador. El otro es un número con un encogimiento de hombros adjunto. Ese contraste, no un error atrapado, es lo que realmente gana el caso del cateterismo cardíaco.
El paciente que el emparejador sí descartó
Aún necesitaba un paciente perdido de verdad, así que busqué dónde falla genuinamente mi línea base honesta, y lo encontré en una sola palabra: no. La historia clínica sintética protagonista P-074 lleva la línea de nota «No evidence of diabetes.» Una de las exclusiones del protocolo oncológico es «sin diagnóstico de diabetes mellitus.» La línea base vectorial ve el token «diabetes» justo al lado del «diabetes» del criterio y los empareja con similitud 1.0. Una puntuación perfecta. No tiene modelo de negación, así que lee una oración que deja la diabetes fuera como si hubiera dejado la diabetes dentro, y excluye a un paciente que era elegible.
Este es el paciente que el emparejador descarta, y es el momento narrativo que originalmente esperaba que cargara el caso del cateterismo cardíaco. La negación es donde una línea base dispersa falla con honestidad, en su propio mejor umbral, sin manipulación.

Sigo pensando en lo silenciosa que es este fallo. No hay mensaje de error, ni bandera de baja confianza, ni señal de que algo salió mal. La puntuación es 1.0, la más alta posible, la más confiada que el sistema puede llegar a estar. La línea base nunca está más segura que en el momento exacto en que más se equivoca. Un coordinador que revise una cola de estos no tiene forma de saber que este emparejamiento perfecto en particular es un paciente que debería haberse inscrito. Multiplica eso a lo largo de un protocolo y entiendes por qué el 80% de los ensayos fallan sus plazos de reclutamiento (consenso de la industria, 2025), y por qué cada fallo de cribado cuesta unos 1.200 $ de media (Antidote.me, 2025).
La línea base nunca estuvo más confiada que en el momento exacto en que más se equivocaba. Eso no es un bug que puedas afinar. Es un error de categoría.
¿Por qué el veredicto vive fuera del modelo?
Tomé temprano una decisión arquitectónica que ahora creo que es la única que importó, y fue mantener el modelo de lenguaje completamente lejos del veredicto. Hay exactamente un paso probabilístico en todo el pipeline. Un modelo intercambiable por proveedor, solo asesor, lee la prosa desordenada y propone hechos candidatos, cada uno con el tramo literal del que leyó el hecho y un ID de concepto candidato tomado de un vocabulario cerrado pequeño. Eso es lo único en lo que un modelo es genuinamente bueno: leer. No tiene voto sobre quién es elegible.
Todo lo que sigue es código determinista que puedo auditar. Antes de que cualquier hecho propuesto llegue a una decisión, un verificador adversarial lo desafía contra la nota literal con tres comprobaciones: si el tramo está realmente presente, si está negado, y si el sujeto es el paciente y no un familiar. El hecho «No evidence of diabetes» falla la comprobación de negación y nunca llega al motor. En la misma historia, «Family history of breast cancer» falla la comprobación de sujeto, porque esa historia pertenece a un familiar y no al paciente, y se marca como rechazado con la comprobación fallida nombrada.

En el conjunto gold completo, este verificador rechazó 7 instancias de hechos, 3 hechos incorrectos distintos (una mención negada de diabetes, una atribución de antecedentes familiares de cáncer de mama y un fármaco alucinado plantado sin tramo de apoyo), repartidos en 4 de las 13 ejecuciones de caso puntuadas, todos antes de que pudieran tocar un veredicto. Cuando me preguntan «cómo confío en lo que el agente extrajo de mis notas», este panel es toda la respuesta. No te pido que confíes. Te muestro lo que propuso, lo que se descartó y por qué.
Luego el veredicto en sí es Python puro sentado fuera del marco del agente: un motor de lógica deóntica que evalúa prohibiciones, excepciones temporales y requisitos sobre la ontología y algo de aritmética de fechas. Un modelo no puede anular esta puerta, porque el modelo no está en la sala cuando la puerta se ejecuta. Eso también es lo que hace reproducible al motor. Cuando la lógica es código determinista sobre una ontología fija, volver a ejecutar la misma historia produce la misma respuesta, byte a byte, cada vez.
El modelo lee. No vota. Ese único límite es lo que hace que una reejecución sea idéntica byte a byte.
El único número que le importaba a mi lector de operaciones clínicas
Pasé semanas optimizando métricas que, finalmente admití ante mí mismo, no quitan el sueño al comprador. La precisión de decisión es un número de tabla de clasificación. Quien posee la viabilidad en un sponsor o un CRO no está comparando puntuaciones de tabla. Está viendo cómo se resbala un plazo de reclutamiento, y cada día de retraso es caro. El Tufts CSDD Impact Report (2024) sitúa el coste de un retraso de reclutamiento en unos 800.000 $ al día en ventas de prescripción perdidas, y más en las áreas terapéuticas que toca esta demo: unos 840.000 $ al día en oncología y 1,4 M$ al día en cardiovascular. La complejidad de los protocolos ha subido un 139% en procedimientos de ensayo desde 2005 (IQVIA, 2026), lo que significa más criterios, más cláusulas y más lugares donde un emparejador de texto puede equivocarse en uno.
Así que dejé de liderar con la precisión y empecé a liderar con el número que realmente mapea a ese dolor: pacientes elegibles que no perdiste. En un conjunto gold etiquetado fijo de 13 casos tomados de 7 pacientes sintéticos en 2 protocolos sintéticos, mi motor pierde 0 pacientes elegibles. La línea base justa pierde 3. Mismo conjunto, mismo umbral validado de forma cruzada en ventaja de la propia línea base.

Quiero ser exacto sobre lo que esos números son y no son. Son la propia salida del arnés en ese único conjunto fijo de 13 casos, no una promesa de mundo abierto. El 100% es «100% en este conjunto gold», nunca «siempre correcto». No te voy a decir que TrialProof nunca se equivoca, porque no tengo los datos para afirmarlo y no creería a nadie que lo hiciera. Lo que sí puedo decir es más estrecho y, creo, más útil: en este conjunto el motor pierde cero pacientes elegibles, cada decisión lleva una traza reproducible, dos decisiones se abstuvieron con seguridad con NEEDS-REVIEW cuando faltaba un laboratorio o vital requerido en lugar de adivinar, y volver a ejecutar todo el conjunto fue idéntico byte a byte, 13 de 13. Todos los pacientes, notas y protocolos son fixtures sintéticos, ningún registro real en ninguna parte. Puedes ver cada una de esas ejecuciones en veriprajna.com/es/demos/motor-de-razonamiento-de-elegibilidad-para-ensayos-clinicos.
El número que me importa no es la precisión. Son los pacientes elegibles que no descarté. En este conjunto, eso es cero perdidos frente a los tres de la línea base.
También hay una forma regulatoria en esto, y la nombraré con cuidado. La guía de Clinical Decision Support de la FDA de enero de 2026 es el marco relevante para una ayuda de emparejamiento con humano en el bucle como esta. Cada decisión que emite el motor puede exportarse como un registro CDISC SDTM IE, una fila por paciente y criterio, con el veredicto, la traza de razonamiento, los ID de concepto y la operación deóntica. Eso no es una autorización y no estoy reclamando una. Es alineación y dirección. Pero significa que la traza no es una comodidad de depuración. Es un artefacto archivable, y existe por construcción en cada decisión más que como un añadido a posteriori.
A lo que sigo volviendo
Sigo volviendo al momento en que mi villano se negó a interpretar su papel, porque cambió la pregunta que me estaba haciendo. Durante tres años el campo ha estado preguntando cómo hacer el modelo mejor a la hora de decidir quién es elegible. Mejores prompts, contexto más amplio, más recuperación, todo apuntado a hacer un sistema probabilístico lo bastante confiable como para dictaminar sobre la inscripción de un paciente. Yo también pasé el primer tramo de esta construcción dentro de ese marco, intentando atrapar a un modelo en un error para poder arreglar el modelo.
Lo que finalmente encajó es que era la capa equivocada. Una puntuación de similitud no puede representar «is-a», no puede representar «no» y no puede representar «a menos que la terapia se hubiera completado más de doce meses antes de la aleatorización». Ninguna cantidad de prompting añade eso, porque no son problemas de lenguaje. Son problemas de lógica. Así que la jugada experta no es hacer el modelo confiable. Es hacer que la confianza sea innecesaria. Deja que el modelo haga lo único en lo que es bueno: leer prosa y proponer hechos con el tramo del que los leyó. Luego que un verificador descarte lo que la nota no respalda, y que código llano y auditable sobre una ontología médica calcule el veredicto.
La elegibilidad debe computarse, no predecirse. Lo que no esperaba, al entrar, era que la recompensa no se sintiera como un benchmark en absoluto. Se siente como un recibo. La misma historia da la misma respuesta cada vez, la respuesta nombra el ID de concepto y el arco del grafo que la decidió, y el número por el que un responsable de viabilidad realmente pierde el sueño baja a cero pacientes elegibles descartados.
Y si preferirías verlo en lugar de leerme describirlo, aquí está todo el sistema funcionando de extremo a extremo.
Así que aquí está la pregunta que no he dejado de darle vueltas, y genuinamente me gustaría saber cómo la respondes. Cuando lo que está en juego es la oportunidad real de una persona de participar en un ensayo, ¿dónde quieres que viva tu confianza: en un modelo en el que tienes que creer, o en código que puedes leer?


