---
{
  "title": "FHIR necesita perfiles de calidad de datos",
  "description": "Un validador en verde no le dice nada sobre si un conjunto de datos es utilizable. OMOP resolvió esto hace una década con el Data Quality Dashboard. FHIR ya tiene todas las piezas necesarias para construir el suyo: SQLQuery más algunas extensiones.",
  "date": "2026-07-15",
  "author": "Nikolai Ryzhikov",
  "reading-time": "16 min read",
  "tags": [
    "SQL on FHIR",
    "Data Quality",
    "Analytics"
  ],
  "tldr": "Se apunta una medida de calidad a varios millones de recursos FHIR y se obtiene un número. ¿Se puede confiar en él? Quizás el 40% de los valores de laboratorio faltan, los pesos llegaron en libras y la mitad de los casos de diabetes se encuentran en recursos que la consulta nunca llega a tocar. Nada de esto infringe una sola regla de perfil; todo ello desplaza silenciosamente la respuesta. Basura entra, basura sale, excepto que la basura es FHIR perfectamente bien formado. Un validador no puede ayudar: ve un recurso a la vez y no tiene concepto de cuántos hay. Todas las demás plataformas de datos resolvieron esto hace años: una comprobación es simplemente una consulta que devuelve las filas incorrectas. OMOP lo llevó a los datos de salud con el Data Quality Dashboard. FHIR necesita lo mismo y ya tiene las piezas: un SQLQuery más tres extensiones que llevan categoría, umbral y severidad, que cualquier motor SQL on FHIR puede ejecutar.",
  "utm-campaign": "analytics",
  "utm-content": "fhir-data-quality"
}
---

> For the complete documentation index, see [llms.txt](https://staging.health-samurai.io/llms.txt).
> Use it to discover all available pages before guessing URLs.

---

## Basura entra, basura sale

Alguien quiere ejecutar una medida de calidad clínica sobre sus datos FHIR: una medida estilo HEDIS escrita en CQL, por ejemplo, o un panel de control de diabetes, o una cohorte para un estudio. Extrae varios millones de recursos mediante una exportación Bulk FHIR, aplica su lógica sobre ellos y obtiene un número.

¿Puede confiar en ese número?

Todo validó correctamente contra US Core. Cada referencia se resolvió. El validador estaba en verde de arriba abajo. Y aun así: quizás el 40% de las observaciones de laboratorio no tienen valor numérico. Quizás la mitad de los pacientes no tienen ningún encuentro. Quizás un lote de pesos corporales llegó en libras cuando el perfil espera kilogramos: una `Quantity` perfectamente conforme que contiene un número perfectamente incorrecto. Quizás todos los pacientes con medicación para la diabetes no tienen diagnóstico de diabetes, porque el sistema de origen guardaba esos datos en una tabla que nadie mapeó.

Nada de esto infringe una sola regla de perfil. Todo fluye directamente hacia la medida y desplaza silenciosamente la respuesta. Basura entra, basura sale, excepto que aquí la basura es invisible, porque cada byte de ella es FHIR bien formado. Y la respuesta honesta a «¿cuánta parte de estos datos es incorrecta?» hoy en día es un encogimiento de hombros.

Y eso asume que los datos están siquiera donde se fue a buscarlos. FHIR ofrece más de una manera válida de registrar el mismo hecho clínico. La diabetes de un paciente puede estar en un `Condition`, o como una `Observation` con un código diagnóstico, o estar implícita únicamente en una `MedicationRequest` de metformina o en un `Procedure`. Una medida que consulta `Condition` y nada más no está *equivocada*, simplemente omite silenciosamente a cada paciente cuya diabetes fue modelada de otra forma. Los datos son válidos y conformes, solo que no están donde la lógica buscó, y ningún validador se lo dirá.

Este no es un problema de higiene que se limpiará más adelante. Es el obstáculo que se interpone entre los datos FHIR y cada caso de uso que motivó su recopilación: analítica, medición de calidad, investigación, un modelo.

## FHIR valida la instancia, no el conjunto de datos

El instinto es recurrir a los perfiles, y los perfiles son genuinamente parte de la solución, solo que no la parte que la gente suele asumir. Un perfil es donde la comunidad acuerda la *representación*: que la diabetes va en `Condition`, codificada con este ValueSet, con estos elementos presentes. Eso es exactamente la ambigüedad del inicio, resuelta: sin un perfil, cada conjunto de datos tiene una forma diferente y ninguna comprobación compartida puede siquiera escribirse. Los perfiles son la base sobre la que se sustenta todo este enfoque.

Pero un perfil es un *acuerdo*, no una auditoría, y los perfiles FHIR son, por diseño, permisivos. La mayoría de los elementos son opcionales; must-support pide a un sistema que sea *capaz* de manejar un campo sin requerir nunca un valor; los bindings son frecuentemente extensibles; hay válvulas de escape como data-absent-reason incorporadas. Esa flexibilidad es deliberada para que los datos del mundo real puedan fluir. También es la razón por la que un perfil describe la *forma* de los datos correctos sin indicar cuánta parte de sus datos la cumple: es un contrato, no una medición.

E incluso las reglas que un perfil *sí* hace cumplir, su motor las aplica **un recurso a la vez**. El validador no tiene noción de un segundo recurso, y mucho menos de diez millones. Así que las preguntas que más importan aquí son exactamente las que no puede responder:

- ¿Qué fracción de `Observation.value` es nula? *(una tasa, no una regla)*
- ¿Se repite algún identificador en dos pacientes? *(la unicidad abarca varios registros)*
- ¿Es nuestra prevalencia de diabetes del 0,1% cuando debería rondar el 10%? *(una distribución)*
- ¿Tienen los pacientes con metformina un diagnóstico coincidente? *(un join)*

Un validador, por construcción, no puede contar. Ningún perfil expresará jamás «menos del 5% de valores nulos es aceptable», porque un perfil no tiene concepto de *cuántos*.

![Left: an instance profile validates one Observation — status, code, value, subject all structurally valid. Right: a dataset profile, the checks that run across millions of records — null rate, unique keys, distribution, cross-resource joins.](dq-two-engines.svg "FHIR valida la instancia: un recurso contra una StructureDefinition. Lo que no tiene nombre es el perfil de conjunto de datos: las tasas, los joins y los umbrales que determinan si los datos en su conjunto son utilizables.")

Esa asimetría es todo el argumento en una imagen. FHIR le proporciona un rico **perfil de instancia** —una StructureDefinition que indica cómo es un recurso bien formado— y nada para el **perfil de conjunto de datos**: ninguna forma estándar de declarar, ni de comprobar, cómo debe ser una buena *colección* de esos recursos. Todo lo que sigue trata de construir esa mitad que falta.

Puede ver esto en la práctica dentro de la comunidad. Hay un [hilo de 109 mensajes en chat.fhir.org](https://chat.fhir.org/#narrow/stream/implementers/topic/Exchanging%20non-conformant%20data) —diecinueve participantes, incluidas algunas de las personas más expertas del ecosistema— que debaten qué debe hacer un sistema con un `Period` cuyo fin precede a su inicio. Datos reales, de una conversión real de un HCE. El debate abarca si enviarlo, descartarlo, moverlo a una extensión o etiquetarlo como no fiable. Es una discusión cuidadosa y reflexiva.

Y cada palabra de ella trata de **un único period incorrecto**. Nadie pregunta en ningún momento qué proporción de periods en el conjunto de datos están invertidos, porque no existe forma de preguntarlo.

Peor aún, la conclusión a la que la comunidad sigue llegando amplía aún más la brecha. Si la práctica aceptada para los datos basura es moverlos fuera del elemento computable hacia una extensión, o marcar el recurso con una [etiqueta de seguridad de integridad](https://terminology.hl7.org/ValueSet-v3-SecurityIntegrityObservationValue.html), entonces un conjunto de datos totalmente conforme puede estar lleno de datos inutilizables **por diseño**. La conformidad no revela el problema. Lo absorbe.

### Obligatorio, presente y vacío

La versión más clara de esto es la [extensión data-absent-reason](https://hl7.org/fhir/extensions/StructureDefinition-data-absent-reason.html). Marque un elemento como `1..1` y podría pensar que ha garantizado un valor. No es así. Una cardinalidad mínima se satisface con que el elemento simplemente esté *presente*, y un elemento que no lleva nada más que un `data-absent-reason` vacío está presente. El validador lo cuenta, el recurso pasa y nunca se proporcionó ningún valor.

Esto no es una laguna que alguien olvidó cerrar; se hereda de US Core, deliberadamente, como válvula de escape para datos heredados, externos y redactados. Los implementadores se han [encontrado con esto en la práctica](https://chat.fhir.org/#narrow/stream/Da.20Vinci.20CRD/topic/data-absent-reason%20on%20mandatory%20elements%20in%20CRD%20profiles) —un campo obligatorio satisfecho únicamente por un absent-reason— y la propia lectura de la comunidad es que eso deshace silenciosamente el propósito de marcar el campo como obligatorio. El remedio propuesto es otro invariante a nivel de instancia, escrito y aplicado por IG, que prohíbe la extensión donde se espera un valor real.

Observe lo que eso implica. Para saber si sus campos `1..1` realmente contienen datos, no puede confiar en el símbolo verde: tiene que preguntar *qué fracción de ellos usa un absent-reason en lugar de un valor*. Eso es una tasa a lo largo del conjunto de datos. Un perfil no puede calcularlo. Una consulta sí puede.

La brecha aparece incluso donde más esperaría encontrar una solución. Da Vinci DEQM —el IG para el intercambio de datos de medidas de calidad— tiene una sección titulada [Data Quality](https://hl7.org/fhir/us/davinci-deqm/guidance.html#data-quality). Su contenido, íntegramente, es que las medidas deben usar perfiles definidos como US Core o QI-Core para que los datos intercambiados estén estandarizados y sean adecuados para la evaluación. Sin umbrales. Sin tasas. Sin agregados. Sin un solo mecanismo para evaluar la calidad: solo perfiles de nuevo, la misma herramienta que no puede responder la pregunta.

Así que esto no es un argumento contra los perfiles, sino un argumento a favor de un segundo tipo. El perfil de instancia sigue siendo la fuente de verdad sobre qué es un *recurso válido*; un perfil de conjunto de datos mide cuánta parte de sus datos realmente lo cumple. **Uno es dueño de la instancia, el otro es dueño del conjunto de datos.**

## Todas las demás plataformas de datos ya comprueban sus datos

Salga del ámbito sanitario y este problema no solo está resuelto: es un requisito básico. Comprobar los datos es una etapa estándar en cualquier pipeline de analítica serio, y el mecanismo es siempre el mismo, y siempre igual de simple: **una comprobación es una consulta que devuelve las filas que infringen una regla. Cero filas, los datos pasan. Alguna fila, esas filas son el problema.**

La misma idea viene con un nombre diferente en cada herramienta principal:

| Herramienta | En qué consiste una comprobación |
|---|---|
| [**dbt**](https://docs.getdbt.com/docs/build/data-tests) | un `SELECT` que devuelve filas fallidas, con plantillas genéricas como `not_null`, `unique`, `accepted_values`, `relationships` |
| [**SQLMesh**](https://sqlmesh.readthedocs.io/en/stable/concepts/audits/) | una *auditoría*: una consulta que debe devolver cero filas o el pipeline se detiene |
| [**Amazon Deequ**](https://github.com/awslabs/deequ) | «pruebas unitarias para datos» sobre Spark: completitud, unicidad, distribución, en miles de millones de filas |
| [**Great Expectations**](https://greatexpectations.io/) · [**Soda**](https://www.soda.io/) | validación como código: *expectations* legibles por humanos que se ejecutan en CI y en producción |

Observe qué comprueban todas: valores presentes, claves únicas, números en un rango aceptado, referencias que se resuelven, distribuciones con aspecto correcto. La misma lista corta en todas partes, porque los datos fallan de las mismas pocas formas independientemente del dominio. Esta es una parte madura y fundamental de la ingeniería de datos, no una práctica marginal.

## OMOP lo llevó a los datos de salud hace una década

La analítica sanitaria ya dio este salto. El [Data Quality Dashboard](https://ohdsi.github.io/DataQualityDashboard/) de OHDSI toma ese mismo patrón de una consulta por comprobación y lo aplica a datos clínicos: apúntelo a una base de datos OMOP CDM, ejecuta miles de comprobaciones y devuelve un informe calificado. Nadie en ese mundo publicaría un conjunto de datos sin uno.

Lo que OMOP añade es una taxonomía de las formas en que los datos de salud específicamente fallan: el [marco Kahn](https://pmc.ncbi.nlm.nih.gov/articles/PMC5051581/), que clasifica cada comprobación en tres preguntas:

| Categoría | La pregunta | Ejemplo |
|---|---|---|
| **Conformance** | ¿Tienen los datos la forma correcta? | `status` contiene un valor fuera del conjunto permitido |
| **Completeness** | ¿Están los datos presentes? | El 40% de las observaciones no tienen valor |
| **Plausibility** | ¿Se pueden creer los datos? | Un peso corporal de 1000 kg |

Dos mecanismos lo hacen funcionar. Primero, un tipo de comprobación es una **plantilla**, no una consulta: una plantilla `not_null` se expande por cada campo obligatorio de cada tabla, lo que es como unas dos docenas de plantillas se convierten en miles de comprobaciones concretas. Segundo, cada comprobación lleva un **umbral**: filas incorrectas por debajo del 5% pasa, por encima del 5% falla. Eso es lo que hace que estas comprobaciones sean *difusas* de una manera que un invariante nunca puede ser. Un invariante es binario. Una comprobación de calidad de datos es estadística, y la realidad es estadística.

Esta taxonomía tampoco es una peculiaridad de OMOP. La [NCQA Bulk FHIR Quality Coalition](https://www.ncqa.org/bulk-fhir-api-quality-coalition/) califica los datos Bulk FHIR exactamente con estas tres categorías. La [Medical Informatics Initiative](https://doi.org/10.3233/SHTI230117) de Alemania evalúa la calidad de los datos FHIR con Kahn. PhUSE ha [evaluado datos de API FHIR para presentaciones ante la FDA](https://www.lexjansen.com/phuse-us/2024/ic/PAP_IC12.pdf) con el mismo marco. El vocabulario está consolidado: FHIR simplemente nunca lo adoptó.

## FHIR ya tiene las piezas: SQLQuery + extensiones

![FHIR resources flatten into a table via a ViewDefinition, then a SQL query with extensions turns that table into a data quality dashboard.](dq-pipeline.svg "El pipeline completo son artefactos estándar: una ViewDefinition aplana FHIR, una consulta SQL con extensiones convierte el resultado en un panel de calidad de datos.")

Lea el diagrama de izquierda a derecha y tendrá la idea completa. Una `ViewDefinition` aplana FHIR en una tabla. Una consulta SQL sobre esa tabla devuelve las filas que infringen una regla, la misma comprobación estilo dbt que ejecuta cualquier otra plataforma. Unas pocas **extensiones** sobre esa consulta —categoría Kahn, umbral, severidad— convierten una consulta simple en una comprobación calificada que puede mostrar en un panel de control.

Esa es la propuesta completa: **una comprobación de calidad de datos es un `SQLQuery` más tres extensiones.** Sin nuevo recurso, sin nueva operación, sin nuevo motor: una comprobación es estructuralmente idéntica a cualquier otra consulta, y las extensiones son lo único que la convierte en una comprobación.

Ninguna de las partes es nueva: cada pieza que necesita un panel de calidad de datos ya existe en la especificación:

| Un DQD necesita… | FHIR ya tiene |
|---|---|
| Una tabla plana para comprobar | **ViewDefinition** — aplana FHIR en columnas |
| Una comprobación | **`Library` SQLQuery** — una consulta sobre esa vista |
| Una forma de ejecutarla | **`$sqlquery-run`** — la operación existente |
| Composición, roll-ups | **`relatedArtifact: depends-on`** — dependencias entre consultas |
| Reglas de esquema | **perfiles** — ya la fuente de verdad |

Eso es lo que cambió. Construir un DQD solía ser un proyecto de infraestructura: OHDSI necesitaba su propio motor SQL, su propio modelo de datos plano, años de trabajo. [SQL on FHIR](/blog/aidbox-becomes-the-first-fhir-server-to-pass-all-sql-on-fhir-tests) estandariza esa capa, por lo que en FHIR ya no es un problema de infraestructura. Es solo contenido: escribir las consultas.

Y como una comprobación no es más que SQL sobre una vista plana estándar, es una *especificación*, no una implementación. El mismo `SQLQuery` se ejecuta en Postgres, DuckDB o Spark, o se compila para los motores que ya ejecuta cada equipo de analítica: una prueba dbt, una restricción Deequ, una suite de Great Expectations. Esa es la razón principal para estandarizarlo. No para construir otro motor de calidad de datos —FHIR no necesita uno—, sino para dar al ecosistema **una forma portátil y neutral en cuanto a proveedor de declarar cómo es un buen conjunto de datos FHIR**, escrita una vez y ejecutada en cualquier lugar.

Esto no es un experimento mental. En el reciente connectathon HL7 Vulcan lo ejecutamos: una transformación FHIR-a-OMOP construida únicamente con estos primitivos, más **258 comprobaciones DQD —cada una como `Library(type=sqlquery)` con las tres extensiones mencionadas**, no el prototipo de la sección anterior. La transformación superó los 172 casos de prueba dorada y la clave de respuestas de 23 casos con **cero errores de conformidad**. Las comprobaciones detectaron 5 fallos en nuestra propia salida y 20 en las tablas doradas del grupo de trabajo, cada uno una señal de completitud o plausibilidad que el grupo de trabajo había sembrado deliberadamente, vinculada a la fila (nuestra comprobación `plausibleGender` detectó exactamente sus 6 condiciones de hiperplasia benigna de próstata y 4 condiciones de cáncer de próstata en pacientes femeninas).

Dos cosas destacaron. Portar una década de comprobaciones de calidad de datos acumuladas no costó **prácticamente nada**: una comprobación DQD es una consulta SQL que devuelve filas incorrectas, y SQL on FHIR ejecuta exactamente eso. Y las comprobaciones demostraron su valor de inmediato: `plausibleStartBeforeEnd` detectó una visita que terminaba tres días antes de comenzar, en las propias *tablas doradas* del grupo de trabajo, no en los 130 encuentros de origen, no en las predicciones de nadie, un artefacto que ningún humano había detectado. La [comunidad debate a mano un único `Period` invertido](https://chat.fhir.org/#narrow/stream/implementers/topic/Exchanging%20non-conformant%20data); la comprobación los encuentra en todo el conjunto de datos, automáticamente.

## Cómo se ve en la práctica

Todo lo que sigue comparte una única tabla de origen: una ViewDefinition que aplana `Observation`:

```json
{ "resourceType": "ViewDefinition", "name": "obs_flat", "resource": "Observation",
  "select": [{ "column": [
    { "name": "id",         "path": "getResourceKey()" },
    { "name": "status",     "path": "status" },
    { "name": "loinc",      "path": "code.coding.where(system='http://loinc.org').code.first()" },
    { "name": "patient_id", "path": "subject.getReferenceKey(Patient)" },
    { "name": "value",      "path": "value.ofType(Quantity).value" },
    { "name": "unit",       "path": "value.ofType(Quantity).code" },
    { "name": "effective",  "path": "effective.ofType(dateTime)" }]}]}
```

Una comprobación es un SQLQuery sobre esa vista. Las extensiones llevan la semántica: esta dice *completitud, avisar por encima del 5% de valores faltantes*:

```json
{ "resourceType": "Library", "id": "dqc-obs-value-complete",
  "type": { "coding": [{ "code": "sql-query" }] },
  "extension": [
    { "url": ".../dq-category",  "valueCode": "completeness" },
    { "url": ".../dq-threshold", "valueDecimal": 0.05 },
    { "url": ".../dq-severity",  "valueCode": "warning" }],
  "relatedArtifact": [
    { "type": "depends-on", "resource": "ViewDefinition/obs_flat", "label": "obs" }],
  "content": [{ "contentType": "application/sql", "data": "<base64>" }]}
```

El SQL en el interior es deliberadamente sencillo, y esa es la ventaja:

```sql
-- completeness: rows where the measurement is missing
SELECT id FROM obs WHERE value IS NULL
```

La **integridad referencial** solo añade una segunda vista y una segunda dependencia, `patient_flat` etiquetada como `pat`:

```sql
SELECT o.id, o.patient_id
FROM obs o LEFT JOIN pat ON o.patient_id = pat.id
WHERE o.patient_id IS NOT NULL AND pat.id IS NULL
```

La **plausibilidad** es donde esto demuestra su valor: ningún perfil puede expresar ninguna de estas comprobaciones. El DQD de OMOP tiene toda una familia de comprobaciones de plausibilidad, y se portan directamente a `Observation`s codificadas con LOINC. Las tres más útiles.

*Valor fuera del rango fisiológico para su código*: `plausibleValueLow` / `plausibleValueHigh` de DQD. Los límites viven en una pequeña tabla de referencia, una fila por código LOINC, que es exactamente el patrón de plantilla del que se habló antes: una comprobación, miles de límites concretos.

```sql
-- 29463-7 body weight (kg)  0–650   |  8480-6 systolic BP (mm[Hg])  0–300
-- 8867-4  heart rate (/min) 0–300   |  4548-4 HbA1c (%)             0–20
SELECT o.id, o.loinc, o.value, o.unit
FROM obs o JOIN obs_range r ON o.loinc = r.loinc
WHERE o.value < r.low OR o.value > r.high
```

*Unidad incorrecta para la medición*: `plausibleUnitConceptIds` de DQD. Un peso corporal registrado en cualquier unidad que no sea una unidad de masa es sospechoso independientemente de lo razonable que parezca el número:

```sql
SELECT id, value, unit FROM obs
WHERE loinc = '29463-7' AND unit NOT IN ('kg', 'g', '[lb_av]')
```

*Una prueba que contradice el sexo del paciente*: `plausibleGender` de DQD. Un resultado de antígeno prostático específico en una paciente femenina (`patient_flat` lleva `gender`):

```sql
SELECT o.id, o.patient_id
FROM obs o JOIN pat ON o.patient_id = pat.id
WHERE o.loinc = '2857-1' AND pat.gender = 'female'
```

Las reglas entre recursos también encajan aquí. «Un paciente con medicación para la diabetes debería tener un diagnóstico de diabetes» es un join: rutinario en SQL, difícil o imposible en FHIRPath.

Las **métricas de perfilado** no son de aprobación o fallo en absoluto, solo los números que necesita un panel de control:

```sql
SELECT count(*)                              AS "rowCount",
       count(*) FILTER (WHERE value IS NULL) AS "nullCount_value",
       count(DISTINCT patient_id)            AS "distinctCount_patient",
       min(value) AS "min_value", max(value) AS "max_value"
FROM obs
```

Y los roll-ups se componen mediante el mismo mecanismo de dependencias, apuntando a otras comprobaciones en lugar de a vistas:

```sql
SELECT category, count(*) AS checks, sum(failed) AS failed
FROM ( SELECT 'conformance'  category, (SELECT count(*) FROM c1) > 0 failed
       UNION ALL SELECT 'conformance',  (SELECT count(*) FROM c2) > 0
       UNION ALL SELECT 'completeness', (SELECT count(*) FROM c3) > 0 ) t
GROUP BY category
```

La recompensa llega donde FHIR ya hace su trabajo: la Implementation Guide. Un autor de IG hoy en día distribuye un **perfil de instancia**: el acuerdo sobre qué va dónde y cómo se codifica. Con esto, el mismo IG lleva su otra mitad, un **perfil de conjunto de datos**, en el mismo bundle:

- las **ViewDefinitions** que aplanan los datos conformes con esos perfiles en tablas, y
- las **comprobaciones de calidad**: comprobaciones SQLQuery sobre esas tablas, cada una etiquetada con su categoría Kahn y su umbral.

Ahora un IG dice más que «aquí está la forma que deben tener sus datos». Dice «aquí está la forma, aquí se muestra cómo consultarla y aquí se explica cómo saber si sus datos la cumplen». Un consumidor apunta el paquete a una exportación Bulk y obtiene de vuelta un panel de calidad de datos —*este conjunto de datos supera 94 de 100 comprobaciones de este IG*— sin escribir una sola línea de código de validación a medida. Los perfiles, las vistas y las comprobaciones viajan juntos, redactados por quienes entienden el dominio.

## Hacia dónde va esto

Ponga las piezas juntas y el panorama es simple. Hoy una Implementation Guide distribuye un **perfil de instancia** y, cada vez más, **ViewDefinitions**. Con esto, distribuye la mitad que faltaba: un **perfil de conjunto de datos**, un conjunto curado de comprobaciones de calidad de datos que especifica cómo es un buen conjunto de datos *para este IG*. Publique ambos juntos y cualquier motor SQL on FHIR conforme ejecuta las comprobaciones de forma predeterminada. El autor las escribe una vez; cada servidor califica los datos contra ellas de la misma manera, sin herramientas a medida ni configuración por proveedor.

Un límite honesto: estas comprobaciones no se pueden derivar automáticamente de los invariantes de un perfil, porque el subconjunto de FHIRPath de ViewDefinition es más pequeño que el que utilizan esos invariantes. El catálogo base se escribe a mano: un trabajo puntual que la comunidad comparte.

Y esa es la invitación. Esto no es hipotético: es trabajo activo en el [grupo de trabajo SQL on FHIR](https://github.com/HL7/sql-on-fhir), con las definiciones de extensiones y un conjunto inicial de comprobaciones en el [issue #375](https://github.com/HL7/sql-on-fhir/issues/375), que avanza en las llamadas del grupo. La taxonomía está consolidada y la maquinaria existe; lo que queda es construir el catálogo, de forma abierta. Si ha construido herramientas de calidad de datos sobre FHIR —o alguna vez ha deseado que FHIR las tuviera—, venga a ayudar a diseñarlo: lleve sus comprobaciones al hilo y únase a una llamada.