---
{
  "title": "@atomic-ehr/codegen: Cómo corregir paquetes FHIR sin esperar al origen",
  "description": "Los paquetes FHIR se distribuyen con índices rotos, manifiestos con erratas y perfiles que fijan códigos inexistentes — incluidos los del propio HL7. Por qué es necesario repararlos durante la compilación y cómo declarar las correcciones.",
  "date": "2026-09-29",
  "author": "Aleksandr Penskoi",
  "reading-time": "9 minutes",
  "tags": [
    "FHIR Tools",
    "FHIR Standard",
    "Code Generation",
    "TypeScript",
    "Aidbox"
  ]
}
---

> 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.

---
Los paquetes FHIR son el mecanismo con el que el ecosistema FHIR distribuye contenido: tarballs al estilo npm que contienen StructureDefinitions, ValueSets y CodeSystems. Prácticamente todas las herramientas los leen: servidores FHIR y de terminología, validadores, el IG Publisher, generadores de SDK, etc.

El formato es sencillo, los registros son abiertos y el contenido no siempre es lo que una herramienta necesita. El registro aloja más de mil paquetes procedentes de programas nacionales, proveedores, equipos de proyecto y el propio HL7, cada uno con su propio calendario y abarcando varias versiones del estándar. Era inevitable que algunos tuviesen defectos.

[`@atomic-ehr/codegen`](https://github.com/atomic-ehr/codegen) compila esas definiciones en SDKs tipados, y los lenguajes de destino no admiten errores. TypeScript, C# y Python/Pydantic no permiten referencias colgantes: cada tipo debe existir, cada valor de enumeración debe ser un código real y, además, cada derivación debe restringir la definición. Para que `codegen` funcione, el conjunto completo de paquetes debe ser consistente de forma estática.

## Qué cabe esperar de los paquetes FHIR en entornos reales

Cada ejemplo pertenece a un paquete que puede instalarse hoy, en la versión indicada. Los defectos se clasifican en tres tipos, y cada uno rompe una etapa diferente de la compilación: la carga de los paquetes, la resolución de una referencia o la aplicación de una restricción.

### 1. Metadatos de paquete incorrectos

El manifiesto y el índice son erróneos, por lo que el cargador no puede ni siquiera ensamblar el conjunto de paquetes.

**Un índice roto o ausente.** La especificación exige un fichero `.index.json` que mapea los canonicals a los nombres de fichero para que las herramientas no tengan que abrir cada fichero individualmente. Algunos paquetes se distribuyen sin él (`ehelse.fhir.no.grunndata@2.3.5`, `hl7.fhir.no.basis@2.2.2`), y otros apuntan a ficheros inexistentes (`de.basisprofil.r4@1.6.0-ballot2`).

**Una dependencia de núcleo ausente.** La especificación es explícita — «se requiere una dependencia de un paquete de núcleo» —, pero muchos paquetes la omiten (`simplifier.core.r4*` en `4.0.x`, `ehelse.fhir.no.grunndata@2.3.5`, `nhn.fhir.no.kjernejournal@1.0.0`, `sfm.030322@2.0.1`) mientras usan tipos del núcleo R4 a lo largo de todo su contenido.

**Un manifiesto que no concuerda con el registro.** `simplifier.core.r4.base@4.0.0` depende de `simplifier.core.r4.resources`. El registro sirve ese paquete, pero el `package.json` *dentro del tarball* dice:

```json
{ "name": "simplifier.core.r4.rResources", "version": "4.0.0" }
```

### 2. Una referencia que no se resuelve

La definición apunta a algo que el conjunto de paquetes no contiene.

**Un canonical que nadie define.** En `hl7.cda.uv.core@2.0.2-sd`, `Material.sdtcExpirationTime` está tipado como `.../StructureDefinition/IVL_TS` — con guión bajo. El tipo de dato se publica en `.../IVL-TS`, con guión, y ningún paquete declara la versión con guión bajo.

**Un tipo de dato de otra versión de FHIR.** `hl7.fhir.uv.extensions.r4` es el Extensions Pack de HL7 para R4 — su manifiesto indica `"fhirVersions": ["4.0.1"]`. Durante tres años distribuyó extensiones cuyo `value[x]` usaba `CodeableReference` o `Availability`, tipos de datos añadidos en R5 que no existen en 4.0.1:

| Paquete                            | Publicado  | Extensiones tipadas con tipos de datos exclusivos de R5 |
| ---------------------------------- | ---------- | ------------------------------------------------------- |
| `hl7.fhir.uv.extensions.r4@1.0.0` | mar. 2023  | 10                                                      |
| `hl7.fhir.uv.extensions.r4@5.1.0` | abr. 2024  | 9                                                       |
| `hl7.fhir.uv.extensions.r4@5.2.0` | feb. 2025  | 8                                                       |
| `hl7.fhir.uv.extensions.r4@5.3.0` | may. 2026  | **0**                                                   |

El problema se resolvió en la versión 5.3.0, publicada en mayo de 2026. Las versiones anteriores siguen en uso a través de dependencias de paquetes. Cuatro paquetes del cierre de C-CDA declaran `extensions.r4`, y todos ellos apuntan a una versión defectuosa: `hl7.fhir.us.core`, `hl7.fhir.uv.xver-r5.r4` y `hl7.terminology.r4` solicitan `5.2.0`, mientras que `hl7.terminology` sigue pidiendo `1.0.0`. Solicitar usted mismo la versión corregida no cambia nada — cada declaración debe redirigirse con `ensureDependency`, tal como [muestra el ejemplo de C-CDA](https://github.com/atomic-ehr/codegen/blob/main/examples/on-the-fly/ccda/generate.ts).

### 3. Un perfil que contradice su base

El empaquetado es correcto y todos los canonicals se resuelven. La restricción en sí no es válida — un perfil solo puede restringir lo que su base ya permite. Veamos algunos ejemplos:

**Amplía en lugar de restringir.** El R4 base permite que `RelatedPerson.patient` apunte exactamente a un elemento:

```json
// hl7.fhir.r4.core@4.0.1 — RelatedPerson.patient
"type": [{
  "code": "Reference",
  "targetProfile": ["http://hl7.org/fhir/StructureDefinition/Patient"]
}]
```

El perfil noruego `gd-RelatedPerson` deriva de él y lista cinco:

```json
// ehelse.fhir.no.grunndata@2.3.5 — gd-RelatedPerson, mismo elemento
"type": [{
  "code": "Reference",
  "targetProfile": [
    "http://hl7.org/fhir/StructureDefinition/Patient",
    "http://hl7.org/fhir/StructureDefinition/Person",
    // ...
  ]
}]
```

**Fija un valor que no existe.** El CodeSystem `bundle-type` define exactamente diez códigos:

```json
// hl7.fhir.r5.core@5.0.0 — CodeSystem/bundle-type
"concept": [
  { "code": "document" },    { "code": "message" },
  { "code": "transaction" }, { "code": "transaction-response" },
  { "code": "batch" },       { "code": "batch-response" },
  { "code": "history" },     { "code": "searchset" },
  { "code": "collection" },  { "code": "subscription-notification" }
]
```

El propio perfil `batch-bundle` del núcleo R5 fija `Bundle.type` a un undécimo código:

```json
// hl7.fhir.r5.core@5.0.0 — StructureDefinition/batch-bundle
{ "id": "Bundle.type", "path": "Bundle.type", "patternCode": "bundle" }
```

Ambas salidas están bloqueadas. Corregir el `patternCode` choca con las normas de HL7: «los valores fijos y los patrones no se añadirán, eliminarán ni modificarán de forma que pueda invalidar instancias anteriores». Añadir `bundle` al CodeSystem tampoco es mejor — `bundle-type` es normativo y está marcado como `content: "complete"`. Cualquiera de las dos opciones requiere una votación, no un parche.

## Por qué esperar al origen no es viable

Abra los tickets de todas formas — los defectos que se corrigieron se corrigieron porque alguien los reportó. Pero el proceso ascendente no puede ser su único plan.

**Velocidad.** FHIR aspira a publicar una nueva versión «aproximadamente cada 18-24 meses». El intervalo real entre R4 y R5 fue de 51 meses. Un ticket aceptado hoy aterrizará en una especificación que aún no existe, mientras que su compilación tiene que pasar ahora mismo.

**Inmutabilidad.** HL7 lo declaró en un aviso de seguridad, explicando por qué una corrección no podía llegar al contenido ya publicado: «los paquetes publicados son inmutables». Por tanto, una corrección siempre se distribuye como una *nueva* versión, y la decisión de migrar a ella no siempre depende de usted — un programa nacional fija una versión ballot, la regulación estadounidense nombra versiones de IG en 45 CFR § 170.215. En ocasiones no existe ninguna versión más reciente: `hl7.fhir.r4.core` ha tenido una única publicación, `4.0.1`, en octubre de 2019.

**Imposibilidad.** Y algunos defectos no tienen solución posible en el origen — corregir `batch-bundle` implica reabrir contenido normativo, que es precisamente lo que el estado normativo impide.

## Reparar paquetes en su propia compilación

Esperar, entonces, significa esperar al publicador del paquete que necesita y a los publicadores de todo aquello de lo que depende. La reparación tiene que ocurrir en su lado. Pero, ¿exactamente dónde?

**Editar el tarball desempaquetado.** Es una solución habitual y directa: cambie el fichero y vuelva a ejecutar la compilación. Pero la edición no queda registrada, la próxima instalación la sobreescribe y otras herramientas pueden leer el paquete modificado sin saber que fue alterado.

**Hacer un fork, vendorizar o realojar el paquete.** Queda vinculado a la versión que bifurcó, y cada nueva versión publicada en el origen debe re-bifurcarse manualmente o ignorarse sin más. Su copia tampoco reemplaza al original: cualquier otra cosa del cierre que aún dependa del paquete real lo descargará junto al suyo, y los mismos canonicals acabarán definidos dos veces.

**Tratar el defecto como caso especial en su generador de código.** Ahora un hecho sobre el paquete de otra persona vive en su código fuente, donde nadie pensará en buscarlo.

Codegen adopta la vía restante: la corrección se declara en la configuración de codegen y el paquete se lee a través de ella.

### Qué puede parchearse

Los parches se aplican en el cargador, antes de que el generador vea nada.

```mermaid
flowchart LR
    P(Paquete FHIR<br/>tal como se publica):::neutral2

    PJ(packageJson<br/>ensureDependency · renamePackage):::red2
    IE(indexEntry<br/>excludeCanonical):::red2
    FR(fhirResource<br/>replaceText · ensureCodes):::red2

    M(copia parcheada<br/>solo en memoria):::neutral1

    P -->|package.json| PJ -.-> M
    P -->|.index.json| IE -.-> M
    P -->|resources| FR -.-> M
```

Cinco de ellos, aplicados a los defectos descritos anteriormente:

```typescript
patches: {
  packageJson: [
    // Most Norge packages use R4 core types without declaring the dependency.
    ensureDependency({ "hl7.fhir.r4.core": "4.0.1" }),
    // Published as .resources, but calls itself .rResources in its own manifest.
    renamePackage("simplifier.core.r4.rResources", "simplifier.core.r4.resources"),
  ],
  indexEntry: [
    // shareablecodesystem's recursive concept.concept breaks the schema generator.
    excludeCanonical({
      package: { name: "hl7.fhir.r5.core", version: "5.0.0" },
      url: "http://hl7.org/fhir/StructureDefinition/shareablecodesystem",
      reason: "CodeSystem.concept.concept recursion breaks generation",
    }),
  ],
  fhirResource: [
    // Material.sdtcExpirationTime points at .../IVL_TS; the datatype is published
    // at .../IVL-TS. Scoped to Material, because IVL-TS's own `type` field carries
    // the underscore spelling legitimately — an unscoped replace would break it.
    inResource("http://hl7.org/cda/stds/core/StructureDefinition/Material", [
      replaceText(
        "http://hl7.org/cda/stds/core/StructureDefinition/IVL_TS",
        "http://hl7.org/cda/stds/core/StructureDefinition/IVL-TS",
      ),
    ]),
    // bundle-type is missing codes the R5 bundle profiles pin — including
    // "bundle", batch-bundle's typo, which is not a legal code anywhere.
    ensureCodes("http://hl7.org/fhir/bundle-type", ["bundle", "subscription-notification"]),
  ],
}
```

Hay tres aspectos que conviene conocer antes de escribir un parche.

**El lugar donde se aplica un parche importa.** El manifiesto, el índice y los cuerpos de los recursos se leen en momentos distintos, por lo que una errata en el manifiesto debe corregirse en `packageJson` — cuando se leen los recursos, el grafo de dependencias ya está construido.

**Acote el parche a su objetivo.** Envolver uno en `inPackage` o `inResource` lo restringe a un único paquete o a un único canonical. Es opcional, pero casi siempre merece la pena. El `ensureCodes` anterior se aplica sin ámbito, lo cual es correcto — solo afecta al CodeSystem que nombra.

**La exclusión elimina más que la salida.** Un canonical excluido desaparece también de la resolución, por lo que cualquier elemento derivado de él debe excluirse igualmente.

Dos de estas llamadas las realiza codegen por usted:

- **Los defectos conocidos de HL7 se parchean por defecto.** `builtinPatches` los incluye y los aplica en cada compilación, de modo que nadie tiene que redescubrirlos. Desactívelos o sustitúyalos si no está de acuerdo.
- **Un paquete que se distribuye sin índice recibe uno automáticamente.** Se construye mediante un escaneo de directorio, de forma automática. Un índice que existe pero es incorrecto se deja como está con una advertencia hasta que usted solicite `packageIndex: "recover"` — reconstruir algo que un publicador sí distribuyó es una suposición mayor que hacer en su nombre.

### Convivir con los parches

Un parche modifica los datos de otra persona, por lo que cada corrección aplicada aparece en el informe de generación — cada índice recuperado y cada exclusión, con la indicación `reason` declarada en su parche. «¿Por qué mi SDK generado difiere del paquete publicado?» tiene así siempre una respuesta que cualquiera puede verificar. Ese es el verdadero argumento para declarar una reparación en lugar de hacerla a mano: un tarball editado no informa de nada y un paquete bifurcado no explica nada, mientras que un parche tiene que indicar qué hizo y por qué. Tres reglas mantienen ese valor.

**Abra el ticket en el origen de todas formas.** Un parche compra tiempo; no es una solución.

**Repare la entrada, no la rediseñe.** El parche de `bundle-type` anterior es la solución barata — añade un código que HL7 nunca definió en lugar de corregir el `patternCode` de `batch-bundle`.

**Espere tener que eliminar parches.** Esas ocho exclusiones se volvieron inútiles el día en que se publicó `5.3.0`, y codegen no lo advirtió. Un parche que silenciosamente no hace nada tiene exactamente el mismo aspecto que uno que realiza un trabajo importante.

---

Lo que usted sabe sobre un paquete defectuoso pertenece a la configuración, donde se versiona y revisa como todo lo demás — no en una copia del paquete que ahora debe mantener, no en el código fuente de su generador, no en una página de wiki que tres personas conocen. Declárelo una vez, con su motivo, y abra el issue en el origen mientras su compilación sigue en verde.

Cada defecto mencionado aquí fue verificado en septiembre de 2026 contra los tarballs publicados en las versiones indicadas. La configuración proviene de los pipelines de ejemplo de codegen, que regeneran y comprueban los tipos contra registros en producción en cada push.

Para ver qué construye codegen una vez que la entrada es correcta, consulte [Perfiles US Core en TypeScript](/blog/atomic-ehr-codegen-typescript-us-core-profiles) y [Type Schema: un enfoque pragmático para construir FHIR SDK](/blog/type-schema-a-pragmatic-approach-to-build-fhir-sdk).

[GitHub](https://github.com/atomic-ehr/codegen) | [NPM](https://www.npmjs.com/package/@atomic-ehr/codegen) | [Aleksandr Penskoi en LinkedIn](https://www.linkedin.com/in/aleksandr-penskoi/)