Notxor tiene un blog

Defenestrando la vida


Pasando de bbdb a org-contacts

Llevo años utilizando bbdb como gestor de contactos y acumulo en esa base de datos cientos de registros con las direcciones y teléfonos de la gente con la que me cruzo en esta vida. Había una excepción y era que los datos de mis clientes iban a otro lugar gestionándose para interactuar con ellos desde org-mode y tener juntos los datos personales y las historias clínicas con las visitas, pruebas, avances en el tratamiento y anotaciones necesarias para gestionar correctamente mi trabajo.

Así, pues, tenía duplicados algunos datos en el sistema y dos modos de gestionar datos personales. Los tenía duplicados porque bbdb permite exportar e importar datos desde ficheros externos estándar, como vcard, mientras que org-contacts sólo permite la exportación.

Además, la captura de datos la tengo automatizada para realizarla a org-contacts porque si la hacía para bbdb siempre se me olvidaba desactivar ivy-mode y cuando estaba activo la captura de datos para bbdb entraba en bucle y tenía que reiniciar el proceso. Capturando para el modo org no había esos problemas, pero luego tenía que pasar los datos a bbdb. Si alguien tiene curiosidad por el código que utilizo para la captura de datos, aunque creo que ya lo puse alguna vez por el blog, es éste:

 [...]
 ;;; Templates de captura para cosas que no son para la agenda
    ("n" "Capturas no para agenda")
    ("nc" "Anotar (c) contacto" entry (file+headline "~/agenda/especiales/personal.org.gpg" "Sin ordenar")
     "** %^{Nombre} %^{Apellidos}%?
   :PROPERTIES:
   :Nombre:     %\\1
   :Apellidos:  %\\2
   :Alias:      %^{Alias}
   :Grupo:      %^{Grupo}
   :F-nacim:    %^{F-nacim}u
   :Móvil:      %^{Móvil}
   :Teléfono:
   :Email:      %^{Email}
   :Web:
   :Dirección:  %^{Dirección}
   :Ciudad:     %^{Ciudad}
   :Provincia:  %^{Provincia}
   :Cód.Pos:    %^{Código Postal}
   :Compañía:
   :Notas:
   :END:" :empty-lines 1)
[...]

Como se puede apreciar pasan al fichero personal.org.gpg y se almacenan directamente cifrados en él, en un apartado que se llama Sin ordenar y que después serán movidos esos datos a la posición que corresponda dentro del grupo de datos correspondiente, porque lo tengo dividido en apartados como Familia, Amigos, Trabajo, Asociación, etc. que viene también especificado por el Grupo que se puede observar en la plantilla.

Pasar los datos de una base de datos a otra

Para pasar todos los datos que tenía en bbdb, primero hay que configurar y preparar el fichero de contactos para que se comporte de una manera más amigable. En principio, org-contacts lo que hace es organizar un árbol de entradas donde los datos se guardan como propiedades. Si abres el documento con todos los datos desplegados, cada registro consta de tantas líneas como campos, más la cabecera, más los delimitadores de las propiedades. Esto hace que navegar por los datos sea un poco tedioso. Personalmente, para la visualización prefiero el modo columnas, que presenta cada registro en una línea con los campos más habituales a la vista.

Configurar los contactos

El caso es que la duplicidad de datos y el tener que gestionar las dos formas de almacenarlos, poco a poco, me han ido cansando y decidí centralizarlo todo en org-contacts. Dicho paquete no se instala independientemente, sino que pertenece a un metapaquete que se llama org-plus-contrib donde podemos encontrar muchos paquetes interesantes y útiles.

Para activar los datos de los contactos, hace falta algo de código en nuestro init.el. Como podéis ver en la plantilla de captura, todos los campos los nombro en español, así que tengo que decirle a org-contacts a qué campo corresponde cada uno:

(setq org-contacts-address-property "Dirección")
(setq org-contacts-birthday-format "Cumpleaños: %04d-%02d-%02d")
(setq org-contacts-birthday-property "F-nacim")
(setq org-contacts-email-property "Email")
(setq org-contacts-files (quote ("~/agenda/especiales/personal.org.gpg")))
(setq org-contacts-matcher
  "Email<>\"\"|Alias<>\"\"|Móvil<>\"\"|F-nacim<>\"\"")
(setq org-contacts-note-property "Notas")
(setq org-contacts-tel-property "Móvil")

Además, en el fichero que guarda la base de datos, personal.org.gpg, tengo establecido en su cabecera

#+COLUMNS: %25Nombre %25Apellidos %Alias %Móvil %Email

Así, cuando consulto la base de datos puedo establecer el modo columnas pulsando la combinación C-c C-x C-c para visualizar los datos en una tabla.

Importar los datos desde bbdb

El problema que me encontré es que no existe una manera de importar grandes cantidades de registros a org-contacts. Sí los puedes exportar a formato vcard, sin embargo, no importarlos. La opción de meterlos uno a uno en el archivo aprovechando la plantilla de captura es inviable cuando tienes cientos de registros.

La solución pasa por un poco de investigación y algo de código. Lo primero que hice para no cargarme la base de datos de bbdb fue hacer una copia y trabajar sobre ella. Al abrirla como si fuera un fichero de texto normal, se puede apreciar que los datos se guardan ordenados en una lista de arrays, una fila para cada registro o array.

Puesto que era una copia, lo encapsulé todo en una lista que llamé contactos y renombré a convertir.el el fichero copia de los datos:

(setq contactos (list
   ;;; Aquí todos los registros con los datos
))

Podría haber escrito el código en otro fichero e importar los datos desde fuera, pero ya que los tenía en formato texto me pareció más rápido encapsular los datos en una lista y no romperme la cabeza con importaciones externas.

Posteriormente me dediqué a observar la estructura de los registros y cómo extraer los datos. Lo más normal era utilizar la función insert para abrir el fichero donde importarlos y que el código insertara los datos de manera directa. Voy a poner todo el código fuente y ahora me entretengo un poco en explicar cómo funciona:

(setq contactos (list
   ;;; Aquí todos los registros con los datos
))

(defun varios-telefonos (cada-telefono)
  (aref cada-telefono 1))

(defun insertar-datos (elem)
  (insert (concat "** " (aref elem 0) " " (aref elem 1) "\n"))
  (org-set-property "Nombre" (if (eq (aref elem 0) nil)
                                 (aref elem 1)
                               (aref elem 0)))
  (org-set-property "Apellidos" (if (eq (aref elem 0) nil)
                                    ""
                                  (aref elem 1)))
  (org-set-property "Alias" "")
  (org-set-property "Grupo" (if (eq (aref elem 4) nil)
                                ""
                              (car (aref elem 4))))
  (org-set-property "F-nacim" (if (or (eq (aref elem 8) nil) (eq (cdr (assoc 'birthday (aref elem 8))) nil))
                                    ""
                                (cdr (assoc 'birthday (aref elem 8)))))
  (org-set-property "Móvil" (if (eq (aref elem 5) nil)
                                ""
                              (aref (car (aref elem 5)) 1)))
  (org-set-property "Teléfono" (if (> (length (aref elem 5)) 1)
                                   (mapconcat 'varios-telefonos (aref elem 5) ", ")
                                 ""))
  (org-set-property "Email" (mapconcat 'append (aref elem 7) ", "))
  (org-set-property "Dirección" (if (eq (aref elem 6) nil)
                                    ""
                                  (car (aref (car (aref elem 6)) 1))))
  (org-set-property "Ciudad" (if (eq (aref elem 6) nil)
                                    ""
                                  (aref (car (aref elem 6)) 2)))
  (org-set-property "Provincia" (if (eq (aref elem 6) nil)
                                    ""
                                  (aref (car (aref elem 6)) 3)))
  (org-set-property "Cód.Pos" (if (eq (aref elem 6) nil)
                                    ""
                                (aref (car (aref elem 6)) 4)))
  (org-set-property "Compañía" "")
  (org-set-property "Notas" "")
  (insert "\n"))

(defun convertir-datos ()
  (interactive)
  (mapc 'insertar-datos contactos))

La función que llamamos desde M-x será convertir-datos. No necesita parámetros, porque toda la información la guarda en el mismo script. Lo único que hace es una iteración (mapc) llamando a insertar-datos para cada registro de contactos.

La función insertar-datos recibe un elemento en elem e inserta (insert) una cadena de texto extrayendo el nombre (aref elem 0) y los apellidos (aref elem 1) del registro. La función aref extrae de un array el elemento designado por orden.

A continuación, comienza a insertar propiedades con el nombre y la estructura que tiene la plantilla de captura. Siempre comprobando el contenido para dejarlo en blanco ("") o extraer las cadenas necesarias. Algunos campos además contienen listas, como por ejemplo el email y por eso se extrajeron todos separándolos con , mediante la función mapconcat.

El caso del campo «teléfono» era más complejo, porque está compuesto por una lista de arrays y necesité un paso intermedio para extraer las cadenas del número de cada array. Para eso utilicé la función auxiliar varios-telefonos, que básicamente devuelve el número, dado un registro de la lista de teléfonos.

Teniendo ya todos los datos cargados en el fichero toca ir ordenándolos y acomodándolos. También ir abandonando bbdb hasta que quede sólo el fichero de org-contacts.

Conclusión

El abandono de bbdb no se debe a un mal funcionamiento del sistema de base de datos, sino a la comodidad de tenerlo todo junto y a la posibilidad de realizar la toma de datos, incluso sin tener activado org-contacts ni la plantilla de captura, porque al final los datos se encuentran en formato de texto plano.

La capacidad de leer los datos desde otros buffers, gracias al sistema de propiedades que org-mode utiliza, me facilita el trabajo en mis tareas automatizadas mediante emacs. Esto ha hecho que me decantara finalmente por este sistema, que a pesar de todo me parece más lento que bbdb y también que ocupa bastante más espacio (algo que para mí no es vital pero sí importante, pues es un sistema menos optimizado de gestión de datos).


Comentarios

Debido a algunos ataques mailintencionados a través de la herramienta de comentarios, he decidido no proporcionar dicha opción en el Blog. Si alguien quiere comentar algo, me puede encontrar en Mastodon y en Diaspora con el nick de Notxor.

Disculpen las molestias.