This content is currently only available in Spanish. English translation is on the roadmap — in the meantime, here's the original.
Cómo dejé de subir fotos a mano: automatizando los reportes de intervenciones
Cómo dejé de subir fotos a mano
Parte de mi trabajo consiste en hacer seguimiento de intervenciones técnicas: instalaciones, desmontajes, incidencias, auditorías y visitas previas en tiendas de clientes, dentro y fuera de España. Cada una genera su ración de fotos del antes y el después, algún vídeo, el parte firmado en PDF y, a veces, un Excel con la checklist.
Todo eso tiene que acabar en dos sitios. Por un lado, en el Nextcloud de la empresa, dentro de una estructura de carpetas por cliente, tipo de intervención y tienda. Por otro, en la plataforma web donde se gestionan las intervenciones: adjunto a la ficha y con una entrada en el seguimiento que diga dónde está todo en la nube.
Hacerlo a mano no es difícil. Es peor que eso: es aburrido, y lo aburrido se hace mal.
El problema de verdad
Subir fotos lleva poco tiempo. Lo que se come el rato es todo lo de alrededor.
Primero hay que abrir la ficha y apuntarse el cliente, el tipo, la tienda y el código. Después toca bucear en Nextcloud para ver si la carpeta de esa tienda ya existe. Y aquí empieza la gracia, porque cada persona del equipo nombra las carpetas a su manera. La misma tienda puede aparecer como 0012 - Centro, 0012 Centro Comercial o simplemente Centro. El tipo puede estar como Incidencia, INCIDENCIAS o incidencias. Algunos clientes tienen además una carpeta por país en medio y otros no.
Si no encuentras la carpeta, la creas. Si la creas con otro nombre, acabas de duplicarla, y dentro de tres meses alguien buscará unas fotos en la carpeta equivocada.
Luego subes los archivos a Nextcloud, subes los mismos archivos otra vez a la plataforma, copias el enlace de la carpeta y lo pegas en un seguimiento. Multiplica eso por varias intervenciones al día.
La idea
Quería poder hacer esto:
subir INS00012345
Y que el script hiciera el resto. Leer la ficha, encontrar mi carpeta local con las fotos (la llame como la llame, mientras lleve el código), decidir dónde va en Nextcloud, subir solo lo que falta a los dos sitios y dejar el enlace en el seguimiento si no estaba ya.
Lo escribí en Python porque es lo que más uso y porque para este tipo de pegamento entre sistemas es difícil encontrar algo más cómodo.
Primera versión: hacer clic como un humano
La primera aproximación fue la obvia. Con Playwright, el script abría un navegador, iniciaba sesión, entraba en la ficha, leía los datos de la pantalla, pulsaba "Editar", adjuntaba los archivos en el diálogo y guardaba. Para el seguimiento, lo mismo: botón "Añadir", rellenar horas y minutos, escribir el texto, guardar.
Funcionaba, pero era lento y frágil. Cualquier cambio en el diseño de la web lo rompía, y había que meter esperas por todas partes porque la aplicación carga los datos de forma asíncrona. Para sacar el nombre del cliente tenía que abrir un diálogo de información de la tienda, leerlo y cerrarlo.
Segunda versión: mirar qué hace la web por debajo
Un día abrí las herramientas de desarrollo del navegador, me fui a la pestaña de red e hice a mano lo que quería automatizar: subir una foto y añadir un seguimiento. Lo exporté como HAR y me puse a leerlo.
Ahí estaba todo. La propia web se comunica con su backend a través de una API REST: una llamada devuelve la ficha completa en JSON (cliente, tienda, ciudad, país, adjuntos, seguimientos), otra sube un adjunto y otra crea una entrada de seguimiento. La autenticación es un token Bearer que el servidor va renovando en cada respuesta.
Con eso cambió el planteamiento. El login no aparecía en el HAR y no me apetecía reimplementarlo, así que el navegador se quedó solo para eso: inicia sesión y el script captura el token que usa la propia web. A partir de ahí todo va por la API con requests. Es muchísimo más rápido y no depende de dónde esté cada botón.
Mantuve la versión de clics como plan B. Si alguna llamada a la API falla, esa operación concreta se repite por la interfaz y el log lo avisa. Si mañana cambian algo en el backend, el script sigue funcionando mientras lo arreglo.
Hay una cosa que decidí no tocar nunca. El botón "Editar" de la web guarda la ficha entera con un PUT, y reescribir una intervención completa para añadir una foto me parecía buscarse problemas. El script solo usa las llamadas que añaden cosas.
La regla que no se podía romper
Nextcloud es compartido. Ahí están los reportes de todo el equipo, de años. El requisito número uno, por encima de cualquier otro, era que el script no pudiera borrar ni modificar nada de lo que ya hubiera.
"Tener cuidado en el código" no me parecía suficiente garantía, así que lo planteé en capas.
La primera, en el propio cliente WebDAV: solo puede usar cuatro operaciones (listar, leer, crear carpeta y subir archivo). Si alguna vez alguien, incluido yo, añade por error un borrado o un movimiento, el script lanza una excepción antes de que la petición salga del ordenador.
La segunda es la que más me gusta. Cada subida va con la cabecera HTTP If-None-Match: *, que le dice al servidor: "escribe esto solo si no existe ya un archivo con este nombre". Si existe, es el propio Nextcloud el que rechaza la escritura con un 412. No depende de que mi comprobación previa acierte ni de que dos personas suban algo a la vez.
La tercera tiene que ver con los archivos que cambian. Si corrijo un Excel que ya había subido, la nueva versión se sube al lado con la fecha en el nombre y el original se queda como estaba.
Y como no me fiaba de dar nada por supuesto, añadí un comando que lo comprueba contra el servidor real. Crea una carpeta de prueba aparte, sube un archivo, intenta machacarlo con otro contenido y verifica que el original sigue intacto. Lo ejecuté antes de la primera subida de verdad.
Que repetirlo no rompa nada
La otra obsesión fue que lanzar el script dos veces sobre la misma intervención no hiciera nada raro. Si se corta a mitad, si se me olvida que ya lo había lanzado o si añado tres fotos más al día siguiente, vuelvo a ejecutar el mismo comando y se sube solo lo nuevo.
En Nextcloud eso sale casi gratis gracias a lo anterior. En la plataforma, el script compara los nombres de mis archivos con los adjuntos que ya tiene la ficha, sin tener en cuenta mayúsculas, tildes ni espacios. Con los seguimientos hace algo parecido: si la ficha ya tiene uno con un enlace a Nextcloud, aunque lo haya puesto un compañero a mano, usa esa carpeta y no añade otro.
La parte difícil: entender cómo nombra la gente las cosas
Conectar con las APIs fue la parte fácil. Donde de verdad se fue el tiempo fue en que el script entendiera las carpetas que ya existían.
El código de la intervención lo busco en cualquier posición del nombre de la carpeta, con o sin guiones, en mayúsculas o minúsculas. Si una carpeta lleva también el código de la tienda delante, se queda con el que tiene el formato de la plataforma.
Para el tipo, el script trata como la misma carpeta "Incidencia" e "INCIDENCIAS", o "Visita previa" y "Visitas previas". Me hizo gracia descubrir que mi primera regla para quitar plurales convertía "desmontajes" en "desmontaj" y "desmontaje" se quedaba igual, así que no las reconocía como la misma. La cambié por una más simple que funciona con todo lo que tenemos.
Si existe una carpeta para el país de la tienda, la usa, y si no, no la inventa.
Las tiendas con código son fáciles: cualquier carpeta que empiece por ese código es esa tienda, diga lo que diga detrás. Las que no tienen código son otra historia. Una de las primeras pruebas reales acabó creando una carpeta llamada - - NOMBRE - Ciudad, porque en la ficha el campo del código no estaba vacío sino que contenía un guion. Lo arreglé y aproveché para resolver el problema de fondo.
Ahora, para las tiendas sin código, el script trocea en palabras tanto los datos de la ficha (nombre comercial, ciudad, dirección) como el nombre de cada carpeta existente. Descarta el nombre del cliente y las palabras que no aportan nada ("de", "calle", "centro comercial"…), tolera erratas pequeñas y compara. Si una carpeta encaja claramente, la usa. Si hay dudas, por ejemplo porque hay dos parecidas o porque una se parece solo a medias, no adivina. Me enseña las opciones y me pregunta. Y si está funcionando de forma desatendida, deja esa intervención para cuando yo la revise. Prefiero que me pregunte una vez de más a encontrarme fotos en la tienda equivocada.
Modo desatendido
Además del modo de una intervención, hay uno que revisa toda mi carpeta de trabajo, detecta qué intervenciones tienen archivos nuevos desde la última vez y las procesa todas seguidas. Está pensado para lanzarlo con el Programador de tareas de Windows.
Para eso guarda un pequeño registro con el tamaño y la fecha de cada archivo ya subido, y solo marca una intervención como hecha cuando todo ha ido bien en los dos destinos. Deja en paz las carpetas modificadas hace muy poco, porque puede que todavía esté copiando fotos o que OneDrive no haya terminado de sincronizar. También usa un cerrojo para que no se pisen dos ejecuciones.
La red de la empresa
El primer intento de instalarlo en el portátil del trabajo falló de una forma bastante típica. Playwright quería descargarse su propio Chromium y el proxy corporativo, que inspecciona el tráfico HTTPS con su propio certificado, no le dejaba. Solución: usar el Microsoft Edge que ya está instalado en el equipo.
Ese mismo proxy habría roto las llamadas a la API desde Python, que no confía en los certificados de Windows por defecto. Con la librería truststore, Python usa el almacén de certificados del sistema y deja de quejarse.
Y una lección de las que se aprenden una vez. Durante las primeras pruebas, algunos PDF no se subían y no salía ningún error. Resulta que en mi configuración había una lista de extensiones permitidas, heredada de una versión anterior, en la que no estaban los PDF. Desde entonces el script nunca descarta nada sin decirlo: al empezar muestra cuántos archivos ha encontrado de cada tipo y avisa con nombre y apellidos de lo que se queda fuera.
Cómo lo probé sin tocar nada real
No podía permitirme probar contra el Nextcloud y la plataforma de producción mientras desarrollaba, así que monté réplicas locales de las tres piezas. Para la web, un pequeño servidor que sirve las páginas reales que había guardado. Para la API, una imitación construida a partir de lo que vi en el HAR, con su token y todo. Y un servidor WebDAV que, además de comportarse como Nextcloud, apunta cualquier intento de sobrescribir o borrar como una violación.
Sobre eso fui provocando fallos a propósito: tokens caducados, la API rechazando un seguimiento, una ficha que no existe, una subida que se queda a medias, dos procesos intentando escribir el mismo archivo… En las pruebas de integridad calculaba una huella SHA-256 de todos los archivos remotos antes y después de cada ejecución. Ni uno modificado, ni uno borrado.
Con qué está hecho
Python, Playwright para el inicio de sesión y como plan B, Requests para la API y para WebDAV, python-dotenv para la configuración y truststore para los certificados. Las credenciales viven en un archivo que no entra nunca en el control de versiones, y para Nextcloud uso una contraseña de aplicación que puedo revocar sin tocar mi cuenta.
Qué me llevo
Antes de automatizar una interfaz, merece la pena mirar qué hace por debajo. Diez minutos con la pestaña de red me ahorraron cientos de líneas de clics frágiles.
Las garantías importantes es mejor que las haga cumplir el sistema y no la buena intención del código. Esa cabecera If-None-Match me deja dormir tranquilo.
Diseñar para que todo se pueda repetir cambia la relación con los errores. Si volver a lanzarlo es inofensivo, un fallo a mitad deja de ser un problema.
Y lo que más me sorprendió: la mayor parte de la lógica complicada no está en hablar con las APIs, sino en entender carpetas que han creado personas, cada una a su manera, durante años.
Lo siguiente
Tengo apuntadas varias ideas. Convertir automáticamente las fotos HEIC del iPhone a JPG para que se vean directamente en la plataforma. Un informe diario con las intervenciones planificadas y las que no tienen fotos o les falta el enlace en el seguimiento. Y, la que más tiempo ahorraría, registrar las actuaciones de los técnicos (entradas, salidas y horas) desde la línea de comandos en lugar de una a una en la web.