---
name: build_vcard
kind: function
lang: py
domain: core
version: "1.0.0"
purity: pure
signature: "def build_vcard(contact: dict) -> str"
description: "Serializa un contacto (dict) a un VCARD 3.0 con soporte multi-valor: varias lineas TEL, EMAIL y ADR. Pura, solo compone texto. Acepta claves en espanol e ingles. Generaliza el _build_vcard inline de osint_web."
tags: [dav, vcard, carddav, contact, serialize, osint]
params:
  - name: contact
    desc: "dict del contacto. Claves opcionales (acepta nombre ES o EN): uid/slug (identificador, uno obligatorio), fn/nombre (FN), aliases (list -> NICKNAME CSV), org (ORG), tels/telefonos (list -> N lineas TEL;TYPE=CELL), emails/correos (list -> N lineas EMAIL;TYPE=INTERNET), adrs/direcciones (list -> N lineas ADR;TYPE=HOME con la direccion en el componente street), osint (dict con dni/pais/contexto/sexo/fecha_nacimiento -> lineas X-OSINT-*), note/notas (NOTE). Una lista que venga como string suelto se envuelve en [valor]."
output: "Texto VCARD 3.0 con lineas separadas por CRLF, empezando en BEGIN:VCARD / VERSION:3.0 y terminando en END:VCARD\\r\\n. Valores escapados segun RFC 6350; el ADR es un valor estructurado de 7 componentes cuyos separadores ';' NO se escapan."
uses_functions: []
uses_types: []
returns: []
returns_optional: false
error_type: ""
imports: []
tested: true
tests: ["test_multivalor_tels_emails_adr", "test_escape_en_fn", "test_campos_osint", "test_claves_ingles_y_espanol_equivalentes", "test_falta_uid_y_slug_lanza_valueerror"]
test_file_path: "python/functions/core/build_vcard_test.py"
file_path: "python/functions/core/build_vcard.py"
---

## Ejemplo

```python
from core.build_vcard import build_vcard

vcard = build_vcard({
    "uid": "ada-lovelace",
    "fn": "Ada Lovelace",
    "org": "Analytical Engine Co.",
    "tels": ["+34600111222", "+34600333444"],   # 2 telefonos -> 2 lineas TEL
    "emails": ["ada@example.com"],
    "adrs": ["Calle Mayor 1, Madrid"],
    "osint": {"dni": "12345678Z", "pais": "ES"},
    "note": "Contacto de prueba",
})
print(vcard)
# BEGIN:VCARD
# VERSION:3.0
# UID:ada-lovelace
# FN:Ada Lovelace
# ORG:Analytical Engine Co.
# TEL;TYPE=CELL:+34600111222
# TEL;TYPE=CELL:+34600333444
# EMAIL;TYPE=INTERNET:ada@example.com
# ADR;TYPE=HOME:;;Calle Mayor 1\, Madrid;;;;
# X-OSINT-DNI:12345678Z
# X-OSINT-PAIS:ES
# NOTE:Contacto de prueba
# END:VCARD
```

## Cuando usarla

Cuando hay que materializar un contacto multi-valor (varios telefonos, emails o
direcciones) a vCard para subirlo a CardDAV. Es el paso "componer el texto vCard"
previo a `carddav_put_vcard_py_infra`. La reusan el service `osint_db` (push
DB -> Xandikos) y `osint_web`. Usa el UID como identificador del recurso
`<uid>.vcf`, asi re-subir el mismo UID sobrescribe (idempotente).

## Gotchas

- **Pura salvo `ValueError`**: es determinista y sin efectos (no red ni disco).
  La unica excepcion posible es `ValueError` cuando faltan a la vez `uid` y
  `slug` (no hay identificador) — validacion de entrada aceptable en una pura.
- **ADR estructurado de 7 campos**: el ADR del vCard es un valor estructurado
  `po-box;extended;street;locality;region;postal-code;country`. La direccion se
  coloca en el 3er componente (street) y el resto van vacios:
  `ADR;TYPE=HOME:;;<street>;;;;`. Los `;` que separan los 7 componentes NO se
  escapan; solo se escapa el contenido de cada componente (RFC 6350).
- **Claves ES/EN**: para cada lista/campo acepta el nombre espanol o el ingles
  (`tels`/`telefonos`, `emails`/`correos`, `adrs`/`direcciones`, `note`/`notas`,
  `fn`/`nombre`). Si vienen ambos, gana el primero presente segun el orden
  documentado.
- **Lista como string suelto**: si una clave de lista llega como string en vez
  de lista, se envuelve en `[valor]` y produce una sola linea.
