4ac8f33318
Reemplaza el patron N+1 (PROPFIND + un GET por cada .vcf/.ics, paralelizado con ThreadPoolExecutor pero ~9s para 1064 contactos) por UNA llamada a la funcion del registry dav_get_collection (REPORT addressbook-query / calendar-query que trae todo el contenido inline). Anade cache en disco (.cache/contacts.json y calendar.json) validada por el ctag de la coleccion (dav_collection_ctag): al arrancar, si el ctag no cambio, sirve del disco sin tocar la red. POST /api/refresh fuerza recarga (ignora el ctag). _force_reload distingue refresh forzado de validacion normal. Cambios en /api/contacts y /api/calendar: el N+1 (_fetch_resources + ThreadPoolExecutor) se sustituye por _load_collection (ctag -> disco o REPORT). El parseo vCard/iCal y el shape JSON no cambian; los items cacheados en disco preservan el shape completo (osint, nombre, telefonos). Tests actualizados: fake_dav mockea dav_get_collection + dav_collection_ctag con cache en tmpdir; nuevos tests de disk-cache-hit en proceso nuevo y recarga al cambiar ctag. Medido contra Xandikos real: contacts 1064 cold 1.15s / warm 6ms / disco 0.5s; calendar 98 cold 0.29s. Registry-first: la logica DAV nueva vive en el grupo dav (dav_get_collection_py_infra + dav_collection_ctag_py_infra), no inline. Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>