--- name: add_event_dav kind: pipeline lang: py domain: pipelines version: "1.0.0" purity: impure signature: "def add_event_dav(summary: str, start: str, end: str = '', *, location: str = '', description: str = '', all_day: bool = False, rrule: str = '', alarm_minutes: int = 0, uid: str = '', base_url: str = DEFAULT_BASE_URL, username: str = DEFAULT_USERNAME, collection_path: str = DEFAULT_COLLECTION, secret_path: str = 'dav/xandikos-enmanuel', timeout_s: float = 20.0, verify_tls: bool = True) -> dict" description: "One-shot que anade UN evento al calendario CalDAV de Enmanuel (Xandikos self-hosted) en una sola llamada. Compone build_vevent (componer el VCALENDAR), extract_or_make_uid (UID si falta), pass_get_secret (resolver la contrasena DAV desde pass) y caldav_put_event (HTTP PUT). Impuro: escritura remota real. Idempotente por UID. La contrasena nunca se logea ni aparece en el resultado. Defaults apuntan al calendario de Enmanuel." tags: [dav, caldav, calendar, event, pipelines] params: - name: summary desc: "titulo del evento (-> SUMMARY). Obligatorio." - name: start desc: "fecha/hora de inicio, p.ej. '2026-06-20T17:00' (naive local), con sufijo 'Z' para UTC, o '2026-06-20' para all_day. Obligatorio." - name: end desc: "fecha/hora de fin. Si vacio y no es all_day, se deriva +1h del start; si all_day, el dia siguiente." - name: location desc: "lugar del evento (-> LOCATION)." - name: description desc: "descripcion del evento (-> DESCRIPTION)." - name: all_day desc: "bool. Si True, evento de dia completo (DTSTART;VALUE=DATE)." - name: rrule desc: "regla de recurrencia RRULE, p.ej. 'FREQ=WEEKLY;BYDAY=MO'." - name: alarm_minutes desc: "int. Si > 0, anade un recordatorio (VALARM display) N minutos antes." - name: uid desc: "UID explicito del evento. Si vacio, se sintetiza determinista del VCALENDAR (re-subir el mismo evento sobrescribe = idempotente)." - name: base_url desc: "URL base del servidor DAV. Default = Xandikos de Enmanuel." - name: username desc: "usuario para HTTP Basic auth. Default 'enmanuel'." - name: collection_path desc: "ruta de la coleccion CalDAV destino. Default '/enmanuel/calendars/calendar/'." - name: secret_path desc: "ruta del secreto en pass con la contrasena DAV. Default 'dav/xandikos-enmanuel'." - name: timeout_s desc: "timeout del PUT en segundos. Default 20.0." - name: verify_tls desc: "si True (default) verifica el certificado TLS. No desactivar salvo entornos de prueba." output: "dict. En exito: {status: 'ok', http_status: int, uid: str, url: str}. En error (sin lanzar): {status: 'error', error: str, uid: str|None, http_status: int|None}. La contrasena nunca aparece en el resultado." uses_functions: [build_vevent_py_core, extract_or_make_uid_py_infra, pass_get_secret_py_infra, caldav_put_event_py_infra] uses_types: [] returns: [] returns_optional: false error_type: "error_go_core" imports: [os, sys, argparse, json] tested: false tests: [] test_file_path: "" file_path: "python/functions/pipelines/add_event_dav.py" --- ## Ejemplo ```bash # Anadir un evento con hora, lugar y recordatorio (UID sintetico determinista): ./fn run add_event_dav --summary "Cita dentista" --start 2026-06-20T17:00 \ --end 2026-06-20T18:00 --location "Clinica" --alarm-minutes 30 # {"status": "ok", "http_status": 201, "uid": "evt-", "url": "https://dav-.../enmanuel/calendars/calendar/evt-.ics"} # Evento de dia completo recurrente: ./fn run add_event_dav --summary "Cumpleanos" --start 2026-06-20 --all-day \ --rrule "FREQ=YEARLY" ``` ```python import sys, os sys.path.insert(0, os.path.join("python", "functions")) from pipelines.add_event_dav import add_event_dav res = add_event_dav( "Reunion equipo", "2026-06-22T09:00", "2026-06-22T10:00", location="Sala A", description="Sprint review", alarm_minutes=15, ) print(res["status"], res["uid"]) # 'ok' evt-... ``` ## Cuando usarla Cuando quieras anadir un evento al calendario de Enmanuel sin orquestar a mano los pasos (componer el iCal, resolver el secreto, hacer el PUT). Es la operacion one-shot del grupo `dav` para CalDAV. Para subir un `.ics` entero con N eventos usa `import_ics_to_caldav_py_pipelines`; para un solo evento parametrizado, esta. Pasa `uid` explicito si quieres controlar/actualizar un evento concreto; dejalo vacio para crear uno nuevo con UID derivado del contenido. ## Gotchas - **Accion con efecto real (impura)**: hace un HTTP PUT que escribe en el calendario remoto de Enmanuel. No es un dry-run. Verifica `start`/`end` antes de lanzar; un PUT con datos erroneos crea el evento igualmente. - **Idempotente por UID**: el nombre del recurso es `.ics`. Re-subir el mismo UID SOBRESCRIBE el evento existente (no duplica). Con `uid` vacio el UID es determinista (md5 de summary+start): re-lanzar el mismo evento exacto pisa el anterior; cambiar summary o start crea un recurso nuevo. - **Secreto desde `pass`, nunca hardcode**: la contrasena se resuelve con `pass_get_secret('dav/xandikos-enmanuel')` y NUNCA se logea ni se incluye en el dict de retorno. Si `pass` no esta instalado o la entry no existe, devuelve `{status:'error', error:'pass: ...'}` sin lanzar y sin hacer el PUT. - **`verify_tls=True` por defecto**: no uses `--no-verify-tls` salvo en pruebas controladas. El servidor de Enmanuel tiene certificado valido. - **ValueError de build_vevent**: si falta `summary` o `start`, el pipeline lo captura y devuelve `{status:'error'}` (no propaga la excepcion).