# Cómo importar resultados de laboratorio a un registro FHIR

> Tutorial: importar un informe de laboratorio como CSV y como HL7 v2 ORU^R01 a Observaciones FHIR R4 con códigos LOINC y rangos de referencia, manejar marcadores no mapeados y informes reenviados, y rastrear cada resultado a su laboratorio.

Source: https://developers.anpheros.com/es/guides/import-lab-results-fhir

**Este tutorial toma un informe de laboratorio como archivo CSV y como mensaje HL7 v2 ORU^R01, lo importa a un registro HL7 FHIR R4 de un paciente en una solicitud cada uno, y muestra lo que se obtiene a cambio: recursos de laboratorio `Observation` con códigos LOINC, rangos de referencia y procedencia.** También cubre los dos problemas con los que se encuentra toda integración de laboratorio: marcadores que no se pueden mapear automáticamente e informes que se envían dos veces. Todo se ejecuta en el sandbox gratuito con un paciente sintético.

## Por qué los resultados de laboratorio son difíciles de almacenar

Un informe de laboratorio parece simple: un marcador, un valor, una unidad, un rango de referencia, una fecha. En la práctica, cada laboratorio nombra los marcadores a su manera ("Hemoglobina glicozilada", "HbA1c", "Glycated haemoglobin"), usa comas o puntos para los decimales, escribe rangos como `4,0-5,6`, `<5` o `>3.5`, y reenvía el mismo informe cuando algo falla. Almacenados como texto libre, esos resultados no se pueden comparar a lo largo del tiempo, graficar, compartir con otra aplicación o entregar a un modelo de IA con confianza.

Un registro FHIR almacena cada resultado como un `Observation` con un código LOINC, una unidad UCUM, un valor, un rango de referencia y una fecha, y mantiene quién lo envió en un recurso `Provenance`. El [conector de laboratorio](https://developers.anpheros.com/es/guides/lab-connector) realiza esa conversión por usted.

```
 lab report (CSV, HL7 v2, JSON)
        │  POST /v1/patients/{id}/labs/import
        ▼
 ┌──────────────────────────────────────────────┐
 │ parse rows → map marker to LOINC → parse     │
 │ value, unit, range → stable id per row       │
 └──────────────────────────────────────────────┘
        │
        ▼
 Observation (category laboratory) + Provenance (author_type import, source system, report id)
        │
        ├─► the patient's app and every app they consented to
        └─► webhooks: resource.created per Observation
```

## Antes de empezar

Necesitas una clave de sandbox (`sk_test_…`) de [platform.anpheros.com/dashboard](https://platform.anpheros.com/dashboard/) (**Obtener una clave de sandbox**). Los ejemplos utilizan `curl`; los [SDKs](https://developers.anpheros.com/es/guides/sdks) tienen las mismas llamadas (`labs.importCsv`, `labs.importHl7`, `labs.importItems`).

```bash
export BASE=https://platform.anpheros.com
export KEY=sk_test_…
# Elena Ionescu, a synthetic patient with type 2 diabetes
export PID=$(curl -s "$BASE/fhir/R4/Patient?identifier=https://anpheros.com/fhir/sid/sandbox-persona|p02" \
  -H "Authorization: Bearer $KEY" | jq -r '.entry[0].resource.id')
```

## Paso 1: importar un informe CSV

Así es como un laboratorio rumano podría exportar un informe: punto y coma, comas decimales, nombres de columna rumanos y fechas como `DD.MM.YYYY`. El conector reconoce nombres de columna rumanos e ingleses.

```bash
cat > report.csv <<'EOF'
Analiza;Rezultat;UM;Interval de referinta;Data
Hemoglobina glicozilata (HbA1c);6,9;%;4,0-5,6;01.10.2026
Glicemie;131;mg/dL;70-100;01.10.2026
TSH;2,1;mUI/L;0,4-4,0;01.10.2026
Marker foarte rar;12;U/L;0-20;01.10.2026
EOF

curl -X POST "$BASE/v1/patients/$PID/labs/import" -H "Authorization: Bearer $KEY" \
     -H "Content-Type: text/csv" -H "X-Anpheros-Source-System: labx" \
     -H "Idempotency-Key: report-2026-10-01-elena" --data-binary @report.csv
```

La respuesta (`201`):

```json
{"created": 4, "skipped": 0, "unmapped": ["Marker foarte rar"], "warnings": [],
 "observations": [
  {"id": "ff0bbb44-…", "marker": "Hemoglobina glicozilata (HbA1c)", "code": "4548-4", "system": "http://loinc.org", "value": 6.9, "unit": "%", "date": "2026-10-01"},
  {"id": "ebe5522f-…", "marker": "Glicemie", "code": "2345-7", "system": "http://loinc.org", "value": 131.0, "unit": "mg/dL", "date": "2026-10-01"},
  {"id": "bace44a8-…", "marker": "TSH", "code": "3016-3", "system": "http://loinc.org", "value": 2.1, "unit": "mUI/L", "date": "2026-10-01"},
  {"id": "26fd2270-…", "marker": "Marker foarte rar", "code": "marker_foarte_rar", "system": "urn:anpheros:lab-code", "value": 12.0, "unit": "U/L", "date": "2026-10-01"}]}
```

HbA1c, glucosa y TSH se mapearon a LOINC a partir de sus nombres. Las comas decimales se convirtieron en números y los rangos se convirtieron en `referenceRange`. Una fila regresó en `unmapped`. Todavía se almacena, bajo el código propio del laboratorio (`urn:anpheros:lab-code`) con el texto exacto del informe, por lo que no se pierde nada.

## Paso 2: Manejar marcadores no mapeados

Algunos nombres requieren una decisión. "Glicemie" (glucosa en sangre) es un buen ejemplo: LOINC tiene un código para la glucosa en suero o plasma (`2345-7`, una muestra venosa tomada en un laboratorio) y otro para la glucosa en sangre total (`2339-0`, por ejemplo, una punción digital). En un informe de laboratorio, un "Glicemie" sin más se mapea a suero o plasma. Un nombre que diga "capilar", "capillary" o "sânge integral" se mapea a sangre total:

```json
{"created": 1, "skipped": 0, "unmapped": [], "warnings": [],
 "observations": [{"id": "00480a6d-…", "marker": "Glicemie capilara", "code": "2339-0", "system": "http://loinc.org", "value": 140.0, "unit": "mg/dL", "date": "2026-10-02"}]}
```

Para los marcadores que el conector no reconoce, busque el código:

```bash
curl "$BASE/v1/terminology/loinc?q=glucose" -H "Authorization: Bearer $KEY"
```

```json
{"data": [
  {"system": "http://loinc.org", "code": "2339-0", "display": "Glucose [Mass/volume] in Blood", "display_ro": "Glicemie (sânge)", "category": "laboratory", "unit": "mg/dL"},
  {"system": "http://loinc.org", "code": "2345-7", "display": "Glucose [Mass/volume] in Serum or Plasma", "display_ro": "Glicemie (ser)", "category": "laboratory", "unit": "mg/dL"}],
 "total": 2}
```

Luego, envíe el código en una columna `LOINC`. Un código en el informe siempre prevalece sobre el nombre. Mantenga ese mapeo por laboratorio de su lado y agregue la columna a cada informe de ese laboratorio:

```
Analiza;Rezultat;UM;Interval de referinta;Data;LOINC
Glicemie;99;mg/dL;70-100;04.10.2026;2339-0
```

Los marcadores que LOINC no cubre en absoluto, como el inventado "Marker foarte rar", pueden permanecer bajo el código del laboratorio. Se muestran al paciente y se incluyen en el registro. No se pueden comparar con los resultados de otros laboratorios.

## Paso 3: enviar el mismo informe dos veces

Los laboratorios reenvían informes. Cada fila obtiene un identificador estable construido a partir del ID del informe, el código, la fecha y el valor, por lo que una segunda importación del mismo informe no crea nada:

```json
{"created": 0, "skipped": 4, "unmapped": ["Marker foarte rar"], "warnings": [], "observations": []}
```

Las filas que cambiaron, un valor corregido por ejemplo, se importan como nuevos resultados. La cabecera `Idempotency-Key` añade una segunda garantía: un reintento de red con la misma clave devuelve la primera respuesta sin cambios.

## Paso 4: importar un mensaje HL7 v2 ORU^R01

La mayoría de los sistemas de información de laboratorio utilizan HL7 v2. Envíe el mensaje tal como está (texto ER7, segmentos separados por CR o LF):

```
MSH|^~\&|LABX|LAB1|ANPHEROS|EHR|202610010930||ORU^R01|MSG00077|P|2.5.1
PID|1||12345||Ionescu^Elena||19850903|F
OBR|1||R-77|24331-1^Lipid panel^LN|||202610010800
OBX|1|NM|2093-3^Cholesterol [Mass/volume] in Serum or Plasma^LN||212|mg/dL|<200|H|||F|||202610010800
OBX|2|NM|2085-9^HDL Cholesterol^LN||52|mg/dL|>40|N|||F|||202610010800
```

```bash
curl -X POST "$BASE/v1/patients/$PID/labs/import" -H "Authorization: Bearer $KEY" \
     -H "Content-Type: x-application/hl7-v2+er7" --data-binary @oru.hl7
```

```json
{"created": 2, "skipped": 0, "unmapped": [], "warnings": [],
 "observations": [
  {"id": "b562045b-…", "marker": "Cholesterol [Mass/volume] in Serum or Plasma", "code": "2093-3", "system": "http://loinc.org", "value": 212.0, "unit": "mg/dL", "date": "2026-10-01T08:00:00Z"},
  {"id": "9b3274e4-…", "marker": "HDL Cholesterol", "code": "2085-9", "system": "http://loinc.org", "value": 52.0, "unit": "mg/dL", "date": "2026-10-01T08:00:00Z"}]}
```

`OBX-3` con el sistema de codificación `LN` se utiliza directamente como LOINC. `OBX-7` se convierte en el rango de referencia y `OBX-8` (`H`, `N`) la interpretación. `OBR-3` (`R-77`) se mantiene como el ID del informe en la procedencia. El paciente es el de la URL: el segmento `PID` no se utiliza para encontrar un paciente, por lo que un `PID` incorrecto no puede adjuntar resultados a otra persona.

¿Ya tiene resultados analizados? Envíe JSON: `{"date": "2026-10-01", "origin": "report-77", "items": [{"marker": "Ferritin", "value": 38, "unit": "ng/mL", "ref_low": 15, "ref_high": 150}]}`. La ferritina se mapea a LOINC `2276-4`.

## Paso 5: leer los resultados como FHIR

```bash
curl "$BASE/fhir/R4/Observation?patient=$PID&code=http://loinc.org|4548-4&_sort=-date&_count=1" \
     -H "Authorization: Bearer $KEY"
```

El HbA1c importado, tal como se almacena:

```json
{
  "resourceType": "Observation",
  "id": "62b1b0c0-…",
  "meta": {"versionId": "1", "lastUpdated": "2026-10-12T20:05:57.011Z"},
  "identifier": [{"system": "urn:anpheros:lab-import", "value": "c52d3cd6449291d34f6bef1516d0ac05"}],
  "status": "final",
  "category": [{"coding": [{"system": "http://terminology.hl7.org/CodeSystem/observation-category", "code": "laboratory"}]}],
  "code": {"coding": [{"system": "http://loinc.org", "code": "4548-4", "display": "Hemoglobin A1c/Hemoglobin.total in Blood"}],
           "text": "Hemoglobina glicozilata (HbA1c)"},
  "subject": {"reference": "Patient/…"},
  "effectiveDateTime": "2026-10-01",
  "valueQuantity": {"value": 6.9, "unit": "%"},
  "referenceRange": [{"low": {"value": 4.0, "unit": "%"}, "high": {"value": 5.6, "unit": "%"}}]
}
```

El LOINC display es el nombre estándar en inglés. `code.text` conserva la redacción propia del laboratorio, por lo que el paciente sigue viendo lo que estaba en el informe en papel. Para más información sobre recursos y búsquedas, consulte la guía de la [plataforma FHIR](https://developers.anpheros.com/es/guides/fhir) y los [sistemas de códigos FHIR](https://developers.anpheros.com/es/guides/fhir-code-systems).

## Paso 6: Comprobar quién lo envió

```bash
curl "$BASE/v1/patients/$PID/provenance/Observation/62b1b0c0-…" -H "Authorization: Bearer $KEY"
```

```json
{"data": [{"version": 1, "activity": "CREATE", "recorded": "2026-10-12T20:05:57.011000Z",
           "author_type": "import", "author": "proj_…", "source_system": "labx", "origin_id": null,
           "fhir": "Provenance/f15c4fee-…"}], "total": 1}
```

`author_type: import` y `source_system: labx` permanecen con el valor. Cuando una aplicación de IA lea posteriormente este resultado a través de la [API de contexto](https://developers.anpheros.com/es/guides/llm-healthcare-data), se etiquetará como `[import/labx]`, de modo que el modelo pueda distinguir un resultado de laboratorio de un valor introducido por el paciente.

## Qué sucede a continuación

- El paciente ve los resultados en su aplicación. Cualquier aplicación a la que haya dado su consentimiento puede leerlos, dentro de sus ámbitos ([modelo de consentimiento y acceso](https://developers.anpheros.com/es/guides/consent)).
- Se activa un [webhook](https://developers.anpheros.com/es/guides/webhooks) `resource.created` por cada Observation, de modo que un panel de control de la clínica o un resumen de IA pueda actualizarse sin sondeos.
- `POST /fhir/R4/Observation/$validate?profile=eu-lab` informa cómo una medición de un resultado se compara con el perfil de informe de laboratorio de HL7 Europe.
- Hasta 500 filas por solicitud; los límites se enumeran en [Límites y límites de velocidad](https://developers.anpheros.com/es/guides/limits).

## Preguntas frecuentes

### ¿Qué formatos de informes de laboratorio se pueden importar a FHIR?
CSV con una fila de encabezado (se aceptan coma, punto y coma o tabulación, coma decimal), mensajes HL7 v2 ORU^R01 y JSON. Los informes PDF se pueden almacenar como documentos; los valores extraídos de ellos apuntan de vuelta al documento.

### ¿Qué sucede con los marcadores sin código LOINC?
Se almacenan bajo el código propio del laboratorio con el texto exacto del informe y se listan en `unmapped`. Agregue una columna LOINC para ese laboratorio y se mapearán. Un código en el informe siempre tiene prioridad sobre el nombre.

### ¿Es seguro reenviar un informe?
Sí. Cada fila tiene un identificador estable construido a partir del informe, código, fecha y valor, por lo que las filas ya importadas se omiten; las filas modificadas se importan como nuevos resultados.

### ¿Puede un laboratorio importar resultados para cualquier paciente?
No. Un laboratorio importa en los pacientes creados por su proyecto o cuyos registros la persona compartió con él a través de OAuth. Otros pacientes no existen para él (`404`).

## Relacionado

- [API de resultados de laboratorio](https://developers.anpheros.com/es/guides/lab-results-api): HL7 v2 y FHIR lado a lado, y qué debe contener un resultado
- [Conector de laboratorio](https://developers.anpheros.com/es/guides/lab-connector): cada nombre de columna, campo HL7 y campo de respuesta
- [Interoperabilidad de datos de salud](https://developers.anpheros.com/es/guides/interoperability): por qué los códigos y la procedencia hacen que los datos intercambiados sean utilizables
- [Integraciones de salud](https://developers.anpheros.com/es/guides/healthcare-integrations): clínicas, laboratorios, dispositivos y aplicaciones de terceros
- [API de datos médicos](https://developers.anpheros.com/es/guides/medical-data-api): escritura de observaciones únicas, condiciones y medicamentos
- [Cómo crear una aplicación de salud con IA con un backend FHIR](https://developers.anpheros.com/es/guides/build-ai-healthcare-app): uso de estos resultados en una respuesta de IA
