---
name: import_vcf_to_carddav
kind: pipeline
lang: py
domain: pipelines
version: "1.0.0"
purity: impure
signature: "def import_vcf_to_carddav(vcf_path: str, base_url: str, username: str, password: str, collection_path: str, *, timeout_s: float = 20.0, verify_tls: bool = True, uid_prefix: str = 'goog-') -> dict"
description: "Pipeline que importa un archivo .vcf completo a una coleccion CardDAV. Lee el .vcf del disco, lo parte en VCARDs (split_vcards), por cada tarjeta extrae o sintetiza el UID (extract_or_make_uid), inyecta el UID en la tarjeta si faltaba, y la sube por HTTP PUT (carddav_put_vcard). Devuelve {ok, fail, total, errors}. Idempotente por UID: re-importar el mismo .vcf sobrescribe en vez de duplicar. Formaliza el heredoc ad-hoc usado para migrar 820 contactos de Google a Xandikos. Solo stdlib."
tags: [dav, carddav, vcard, import, contacts, migration, pipelines]
uses_functions: [split_vcards_py_infra, extract_or_make_uid_py_infra, carddav_put_vcard_py_infra]
uses_types: []
returns: []
returns_optional: false
error_type: "error_go_core"
imports: [os, sys, json]
params:
  - name: vcf_path
    desc: "ruta en disco del archivo .vcf a importar (export de Google Contacts con N tarjetas concatenadas)."
  - name: base_url
    desc: "URL base del servidor DAV (p.ej. 'https://dav-eedeb681c4ab89ab8e444ac9.organic-machine.com')."
  - name: username
    desc: "usuario para HTTP Basic auth (p.ej. 'enmanuel')."
  - name: password
    desc: "contrasena para HTTP Basic auth. Resolver desde pass con pass_get_secret, nunca hardcodear."
  - name: collection_path
    desc: "ruta de la coleccion CardDAV destino (p.ej. '/enmanuel/contacts/addressbook/')."
  - name: timeout_s
    desc: "timeout por PUT individual en segundos. Default 20.0."
  - name: verify_tls
    desc: "si True (default) verifica el certificado TLS. No desactivar salvo entorno de prueba."
  - name: uid_prefix
    desc: "prefijo del UID sintetico para tarjetas sin UID. Default 'goog-'."
output: "dict: {ok:int, fail:int, total:int, errors:[{uid, error}, ...]}. ok=tarjetas subidas con exito, fail=tarjetas que fallaron, total=tarjetas en el .vcf, errors=detalle de los fallos (uid + mensaje, sin datos sensibles)."
tested: false
tests: []
test_file_path: ""
file_path: "python/functions/pipelines/import_vcf_to_carddav.py"
---

## Ejemplo

```python
import sys
sys.path.insert(0, "python/functions")
from infra.pass_get_secret import pass_get_secret
from pipelines.import_vcf_to_carddav import import_vcf_to_carddav

pw = pass_get_secret("dav/xandikos-enmanuel")["value"]  # NO logear

summary = import_vcf_to_carddav(
    vcf_path="/home/enmanuel/Descargas/contacts.vcf",
    base_url="https://dav-eedeb681c4ab89ab8e444ac9.organic-machine.com",
    username="enmanuel",
    password=pw,
    collection_path="/enmanuel/contacts/addressbook/",
)
print(summary["ok"], summary["fail"], summary["total"])  # 820 0 820
```

Desde la CLI del registry (resuelve la pass tu mismo y pasala como arg):

```bash
PW=$(pass show dav/xandikos-enmanuel | head -n1)
./fn run import_vcf_to_carddav /home/enmanuel/Descargas/contacts.vcf \
  https://dav-eedeb681c4ab89ab8e444ac9.organic-machine.com \
  enmanuel "$PW" /enmanuel/contacts/addressbook/
```

## Cuando usarla

Cuando tienes un `.vcf` exportado (Google Contacts, iCloud, otro CardDAV) y
quieres volcarlo entero a Xandikos en una sola llamada en vez de subir tarjeta a
tarjeta con heredocs. Reemplaza el flujo ad-hoc que se hizo a mano para la
migracion. Re-ejecutable: por idempotencia de UID, correrlo dos veces no
duplica contactos.

## Gotchas

- Escritura remota masiva: sube una request por tarjeta secuencialmente. Para
  miles de contactos puede tardar; cada PUT respeta `timeout_s`.
- Lee TODO el .vcf en memoria; para archivos de cientos de MB considera trocear.
- La password va por HTTP Basic sobre TLS; leela de `pass`, no la hardcodees ni
  la pongas en el historial del shell sin cuidado (usa una variable como en el
  ejemplo CLI).
- `errors` lista solo uid + mensaje, nunca el contenido de la tarjeta.
- Si una tarjeta no traia UID, el pipeline inyecta `UID:<goog-md5>` antes del
  END:VCARD para que el campo UID: y el nombre del recurso queden consistentes.
