---
{
  "title": "Validación FHIR: Recursión o sin Recursión",
  "description": "¿Deben aplicarse las restricciones de un perfil FHIR de forma recursiva a los elementos anidados? La especificación dice que sí, pero los perfiles del mundo real fallan. Lo investigamos mientras desarrollábamos el validador de Aidbox.",
  "date": "2026-03-20",
  "author": "Evgeny Mukha",
  "reading-time": "8 min read",
  "tags": ["FHIR Profiling", "FHIR Standard", "FHIR Tools", "Validation", "Conformance"]
}
---

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

---
## Cómo empezó todo

Mientras trabajábamos en el validador FHIR de [Aidbox](https://www.health-samurai.io/fhir-server), recibimos una pregunta de un cliente: ¿por qué falla la validación en los elementos `Questionnaire.item` anidados cuando la restricción solo está definida en el `item` de nivel superior?

Estábamos aplicando las restricciones del perfil de forma recursiva: si una restricción se definía sobre `Questionnaire.item`, nuestro validador la aplicaba también a `item.item`, `item.item.item`, y así sucesivamente. Nos parecía lo correcto. Al fin y al cabo, la [especificación R5](https://hl7.org/fhir/R5/profiling.html#recurse) dice:

> Cuando un perfil define restricciones sobre dichos elementos, **las restricciones se aplican también a las referencias recursivas a esos elementos**.

Pero el caso del cliente nos llevó a analizarlo más de cerca. Y lo que encontramos fue una contradicción genuina en la especificación.

## El caso SDC

El [perfil SDC Questionnaire](http://hl7.org/fhir/uv/sdc/StructureDefinition-sdc-questionnaire.html#constraints) define el invariante `sdc-1` sobre `Questionnaire.item`:

> Un elemento no puede tener una `answerExpression` si ya existe `answerOption` o `answerValueSet`.

Considere este Questionnaire en el que tanto un elemento de nivel superior como un elemento anidado violan `sdc-1`:

```json
{
  "resourceType": "Questionnaire",
  "meta": {
    "profile": [
      "http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-questionnaire"
    ]
  },
  "url": "http://example.org/test",
  "status": "active",
  "item": [
    {
      "linkId": "1",
      "type": "decimal",
      "item": [
        {
          "linkId": "1.1",
          "type": "decimal",
          "answerValueSet": "http://example.org/vs",
          "extension": [
            {
              "url": "http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-questionnaire-answerExpression",
              "valueExpression": {
                "language": "text/fhirpath",
                "expression": "1 + 1"
              }
            }
          ]
        }
      ]
    },
    {
      "linkId": "2",
      "type": "decimal",
      "answerValueSet": "http://example.org/vs",
      "extension": [
        {
          "url": "http://hl7.org/fhir/uv/sdc/StructureDefinition/sdc-questionnaire-answerExpression",
          "valueExpression": {
            "language": "text/fhirpath",
            "expression": "1 + 1"
          }
        }
      ]
    }
  ]
}
```

Nuestro validador de Aidbox marcó ambos elementos. El validador de referencia de HL7 en [validator.fhir.org](https://validator.fhir.org/) marcó únicamente el de nivel superior. ¿Quién tiene razón?

## Analizando el perfil en profundidad

Al examinar el StructureDefinition del perfil SDC, la respuesta se vuelve menos evidente. El differential menciona explícitamente `Questionnaire.item.item`, pero únicamente establece `mustSupport: true` en él; sin restricciones, sin invariantes:

```json
{
  "id": "Questionnaire.item.item",
  "path": "Questionnaire.item.item",
  "mustSupport": true
}
```

No queda claro si el autor del perfil pretendía que las restricciones se aplicaran únicamente en el nivel superior o si esperaba que se propagaran en cascada a los elementos anidados; es perfectamente posible que asumiera que `sdc-1` se aplicaría automáticamente a los elementos anidados. En cualquier caso, el differential no restringe explícitamente `Questionnaire.item.item`, y el validador de HL7 solo aplica lo que el differential indica.

No obstante, desde la perspectiva de la especificación, nuestro comportamiento también era correcto: las restricciones *deben* aplicarse de forma recursiva.

## Por qué la recursión automática rompe los perfiles reales

Trasladé el problema a [Grahame Grieve](https://www.linkedin.com/in/grahame-grieve-952637/) en [chat.fhir.org](https://chat.fhir.org/#narrow/stream/implementers/topic/Recursive%20schemas%20validation), y su reacción fue inmediata.

Considere el [perfil IPS Composition](https://build.fhir.org/ig/HL7/fhir-ips/en/StructureDefinition-Composition-uv-ips.html). Restringe `Composition.section` con una cardinalidad mínima de `3..*`. Si aplicamos esto de forma recursiva:

- `Composition.section` – debe tener 3 o más secciones
- `Composition.section.section` – también debe tener 3 o más secciones
- `Composition.section.section.section` – y así sucesivamente, de forma infinita

Ningún documento IPS válido podría existir jamás. La reacción de Grahame ante el texto de la especificación:

> *«Eso es totalmente incorrecto en lo que respecta a los perfiles de Composition. Absolutamente imposible de usar. No sé qué estábamos pensando cuando lo escribimos.»*

## El slicing lo complica aún más

El [perfil Subscriptions Backport](http://hl7.org/fhir/uv/subscriptions-backport/StructureDefinition-backport-subscription-status-r4.html) define slices sobre `Parameters.parameter`:

- `parameter:subscription` (obligatorio)
- `parameter:topic` (obligatorio)
- `parameter:type` (obligatorio)

El recurso `Parameters` es recursivo: `parameter` contiene `part`, que comparte la misma estructura mediante `contentReference`. Si aplicamos el slicing de forma recursiva, cada `.part` necesitaría sus propios slices `subscription`, `topic` y `type`. Evidentemente, no era la intención del autor.

Estos perfiles están **ampliamente implementados**. Aplicar restricciones recursivas los rompería a todos.

## Recursión frente a reutilización de tipos

La discusión puso de manifiesto un problema más profundo. FHIR utiliza `contentReference` para dos propósitos distintos:

**Recursión verdadera**: un elemento que se referencia a sí mismo:
- `Questionnaire.item` -> `Questionnaire.item.item`
- `Composition.section` -> `Composition.section.section`
- `Consent.provision` -> `Consent.provision.provision`

**Reutilización de tipos**: distintos elementos que comparten una estructura:
- `ValueSet.compose.include` y `ValueSet.compose.exclude`
- `Parameters.parameter` y `Parameters.parameter.part`

Ambos utilizan `contentReference` internamente, pero la semántica es fundamentalmente diferente. Como señaló [Gino Canessa](https://www.linkedin.com/in/gino-canessa/):

> *«Ambos se implementan mediante el mecanismo `contentReference`, y la recursión inicial no es el mismo elemento. En este caso, sucede que tienen el mismo nombre de elemento, pero eso no es lo que lo hace recursivo.»*

Cualquier solución debe distinguir estos casos, y ni la especificación ni las herramientas actuales lo hacen.

## No es la primera vez

Grahame señaló un [hilo anterior en chat.fhir.org](https://chat.fhir.org/#narrow/channel/179252-IG-creation/topic/Clarification.20on.20contentReference) donde este mismo problema se debatió en 2020. Chris Moesel, de MITRE, preguntó si las restricciones sobre `Questionnaire.item` deberían propagarse a `item.item` mediante `contentReference`. La comunidad concluyó que **no** debería ser así, y Grahame señaló una extensión existente, [`elementdefinition-profile-element`](http://hl7.org/fhir/StructureDefinition/elementdefinition-profile-element), como mecanismo para la reutilización recursiva explícita. La extensión se coloca en el elemento hijo y apunta de vuelta al perfil padre, permitiendo a los autores optar por la herencia de restricciones de forma voluntaria.

Después, en 2023, se publicó R5 con una nueva sección de [Elementos Recursivos](https://hl7.org/fhir/R5/profiling.html#recurse) que decía lo contrario: las restricciones **sí** se aplican de forma recursiva por defecto. Chris Moesel [señaló la contradicción](https://chat.fhir.org/#narrow/channel/179252-IG-creation/topic/Clarification.20on.20contentReference/near/350340728) de inmediato. Como dijo Grahame: *«No sé qué estábamos pensando cuando lo escribimos.»*

Así pues, la comunidad ya había identificado el problema, había creado una extensión para resolverlo, y luego la especificación tomó la dirección opuesta.

## Una posible solución: una extensión sobre ElementDefinition

Durante la discusión, propuse una solución: una extensión que controle explícitamente si las restricciones se heredan de forma recursiva. La idea es sencilla: añadir un indicador en `ElementDefinition` que informe a los validadores de que deben «aplicar (o no aplicar) las restricciones del elemento padre a esta referencia recursiva».

Gino Canessa lo desarrolló con un enfoque más flexible: una extensión en el elemento *hijo* que apunta de vuelta al elemento del que debe heredar las restricciones. Por ejemplo, `Questionnaire.item.item` podría indicar explícitamente «aplicar las restricciones de `Questionnaire.item`»:

```
Questionnaire.item          – restricciones definidas aquí
Questionnaire.item.item     – extensión: "inherit-constraints-from: Questionnaire.item"
```

Este enfoque resuelve ambos casos:

- **Recursión verdadera** (`Questionnaire.item.item`): el autor del perfil añade la extensión cuando desea que las restricciones se propaguen en cascada — opt-in
- **Reutilización de tipos** (`ValueSet.compose.include` / `.exclude`): cada elemento permanece independiente por defecto — sin filtración accidental de restricciones

La pregunta clave de diseño es el comportamiento predeterminado. La comunidad se inclina por **no recursivo por defecto** con opt-in explícito:

| Posición | Defensores | Justificación |
|----------|-----------|-----------|
| **No recursivo por defecto**, opt-in mediante extensión | Gino Canessa, [Grahame Grieve](/articles/grahame-grieve-interview-future-of-fhir-in-2025) | Más seguro para los perfiles existentes; evita cambios disruptivos |
| **Recursivo por defecto**, opt-out mediante extensión | Brian Postlethwaite | Más intuitivo para los casos de uso habituales |
| **Prefiere recursivo**, acepta no recursivo | Lloyd McKenzie | Cambiar el comportamiento predeterminado supondría un cambio disruptivo |

Lloyd McKenzie expresó la tensión de forma precisa:

> *«Debe existir alguna forma de establecer restricciones que se apliquen de forma recursiva, porque es sin duda un caso de uso necesario. Pero dado que el comportamiento actual no es ese, realizar ese cambio podría ser disruptivo.»*

## Qué estamos haciendo al respecto

En [FHIR Schema](https://github.com/fhir-schema/fhir-schema) —el formato de validación en forma de árbol que desarrollamos en Health Samurai— este problema se presenta como una decisión arquitectónica explícita. Cuando transpilamos StructureDefinitions a un árbol de esquema, `contentReference` se convierte en un puntero a otro nodo, y debemos decidir: ¿copiar las restricciones del padre o no?

Por ahora, hemos alineado el [validador de Aidbox](https://www.health-samurai.io/fhir-server) con el validador de referencia de HL7: las restricciones **no** se aplican de forma recursiva a menos que el perfil las defina explícitamente en los elementos anidados. Esto coincide con la forma en que los perfiles se escriben realmente en la práctica.

El tema está señalado para su discusión en la HL7 Working Group Meeting de Róterdam. Esperamos que de ella surjan propuestas concretas para un mecanismo de extensión y un lenguaje de especificación más claro.

## Qué significa esto para los implementadores

Si trabaja con elementos recursivos (`Questionnaire.item`, `Composition.section`, `Consent.provision`, `Parameters.parameter`):

- **Comportamiento actual en los validadores**: las restricciones no se aplican de forma recursiva, independientemente de lo que diga la especificación R5
- **Si necesita restricciones en todos los niveles de anidamiento**: defínalas explícitamente en el differential de su perfil para cada nivel, o utilice invariantes [FHIRPath](/articles/unlocking-fhirpath-power-a-deep-dive-into-static-type-analysis-for-robust-tooling) que recorran el árbol
- **Si está desarrollando un validador**: no aplique restricciones de forma recursiva por defecto — romperá perfiles ampliamente utilizados

Esto es una laguna en la especificación, no un error. La especificación dice una cosa, los perfiles del mundo real necesitan otra, y los validadores han optado pragmáticamente por el comportamiento que no rompe las implementaciones existentes.

## Próximo paso: la WGM de Róterdam

Planeamos llevar esto a la [HL7 Working Group Meeting de Róterdam](https://www.hl7europe.org/2026-may-hl7-fhir-connectathon-and-working-group-meeting/) con una propuesta concreta para el mecanismo de extensión descrito anteriormente. El objetivo es lograr el consenso del grupo de trabajo de FHIR Infrastructure y convertir esto en un cambio formal de la especificación.

Si se ha encontrado con este problema en sus perfiles o en su validador, nos encantaría conocer sus casos de uso antes de la reunión. Únase a la [discusión en chat.fhir.org](https://chat.fhir.org/#narrow/stream/implementers/topic/Recursive%20schemas%20validation) o póngase en contacto con nosotros directamente.

Véase también: [Cómo crear una guía de implementación FHIR](/blog/how-to-create-a-fhir-implementation-guide) y [FHIR Additional Bindings](/blog/fhir-additional-bindings).