{"id":11725,"date":"2026-04-02T16:25:16","date_gmt":"2026-04-02T14:25:16","guid":{"rendered":"https:\/\/bitacora.eniac2000.com\/?p=11725"},"modified":"2026-04-02T16:26:59","modified_gmt":"2026-04-02T14:26:59","slug":"aplicacion-android-para-fotogrametria-con-ia-la-plataforma-de-procesado-opendronemap-desplegado-con-claude-code","status":"publish","type":"post","link":"https:\/\/bitacora.eniac2000.com\/?p=11725","title":{"rendered":"Aplicaci\u00f3n Android para fotogrametr\u00eda con IA. La plataforma de procesado: OpenDroneMap desplegado con Claude Code"},"content":{"rendered":"<div class=\"seriesmeta\">Esta entrada es la parte 5 de 6 de la serie <a href=\"https:\/\/bitacora.eniac2000.com\/?series=fotogrametria-asistida-por-ia\" class=\"series-1957\" title=\"Fotogrametr\u00eda asistida por IA\">Fotogrametr\u00eda asistida por IA<\/a><\/div>\n<img decoding=\"async\" src=\"https:\/\/bitacora.eniac2000.com\/wp-content\/uploads\/2026\/04\/arquitectura-final-docker-webodm.png\" alt=\"Imagen destacada\" style=\"display: none;\">\n\n\n\n<p>Hasta ahora los art\u00edculos de esta serie se han centrado en la aplicaci\u00f3n Android: c\u00f3mo planifica la misi\u00f3n, c\u00f3mo hace volar al dron, c\u00f3mo controla el heading durante el vuelo. Pero una misi\u00f3n de fotogrametr\u00eda no termina cuando el dron aterriza. Las im\u00e1genes capturadas son la materia prima; lo que convierte esas fotos en un ortomosaico, una nube de puntos o un modelo 3D es el procesado fotogram\u00e9trico, y eso requiere su propia infraestructura. Este art\u00edculo cuenta c\u00f3mo se dise\u00f1\u00f3 y despleg\u00f3 esa infraestructura, y en \u00e9l Claude Code tiene un papel protagonista diferente al habitual: no como desarrollador de c\u00f3digo, sino como ingeniero de sistemas.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">El software: OpenDroneMap<\/h2>\n\n\n\n<p>La elecci\u00f3n del motor de procesado fotogram\u00e9trico fue <strong>OpenDroneMap<\/strong> (ODM), y la raz\u00f3n principal es la misma que gu\u00eda muchas otras decisiones de este proyecto: es completamente open source, sin licencias ni costes recurrentes, y produce resultados comparables a soluciones comerciales como Pix4D o Agisoft Metashape para los casos de uso habituales con drones de consumo.<\/p>\n\n\n\n<p>ODM no es un programa con interfaz gr\u00e1fica, sino un motor de procesado por l\u00ednea de comandos. Para hacerlo manejable existen dos capas adicionales que forman el stack completo:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>NodeODM<\/strong>: expone el motor de ODM como una API REST, permitiendo subir im\u00e1genes, lanzar tareas y consultar su estado de forma program\u00e1tica.<\/li>\n\n\n\n<li><strong>WebODM<\/strong>: interfaz web completa y API REST de alto nivel sobre NodeODM. Gestiona proyectos, usuarios, tareas y resultados. Es el punto de entrada principal, tanto para un humano desde el navegador como para la app Android en la Fase 4.<\/li>\n\n\n\n<li><strong>ClusterODM<\/strong>: capa de orquestaci\u00f3n que distribuye tareas entre m\u00faltiples nodos NodeODM. Para un servidor \u00fanico act\u00faa principalmente como gestor de cola.<\/li>\n<\/ul>\n\n\n\n<p>Para la visualizaci\u00f3n de nubes de puntos se eligi\u00f3 <strong>Potree<\/strong>, la referencia open source para renderizar ficheros <code>.las<\/code>\/<code>.laz<\/code> con millones de puntos directamente en el navegador, sin plugins ni software adicional. Potree permite medir distancias, hacer secciones transversales y navegar por la nube de puntos de forma fluida incluso en conjuntos de datos grandes.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">La arquitectura de m\u00e1ximos: cuatro VMs en Proxmox<\/h2>\n\n\n\n<p>El servidor disponible para el procesado es un HP ProLiant DL360 Gen8, virtualizado con Proxmox VE. La primera propuesta de arquitectura que surgi\u00f3 en la sesi\u00f3n de dise\u00f1o con Claude era ambiciosa: cuatro m\u00e1quinas virtuales separadas, cada una con un rol espec\u00edfico, comunicadas a trav\u00e9s de una red interna y expuestas mediante un nginx actuando como reverse proxy centralizado.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510\n\u2502                   Proxmox VE                         \u2502\n\u2502                                                      \u2502\n\u2502  \u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510    \u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510   \u2502\n\u2502  \u2502  vm-nginx    \u2502    \u2502       vm-webodm           \u2502   \u2502\n\u2502  \u2502 Reverse proxy\u2502\u2500\u2500\u2500\u25b6\u2502  WebODM + NodeODM         \u2502   \u2502\n\u2502  \u2502 192.168.x.10 \u2502    \u2502  :8000  192.168.x.11      \u2502   \u2502\n\u2502  \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u252c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518    \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518   \u2502\n\u2502         \u2502                                            \u2502\n\u2502         \u2502            \u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510   \u2502\n\u2502         \u251c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u25b6\u2502     vm-clusterodm         \u2502   \u2502\n\u2502         \u2502            \u2502  ClusterODM orquestador   \u2502   \u2502\n\u2502         \u2502            \u2502  :3000\/:8080 192.168.x.12 \u2502   \u2502\n\u2502         \u2502            \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518   \u2502\n\u2502         \u2502                                            \u2502\n\u2502         \u2502            \u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510   \u2502\n\u2502         \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u25b6\u2502       vm-potree           \u2502   \u2502\n\u2502                      \u2502  Potree + PDAL visor 3D   \u2502   \u2502\n\u2502                      \u2502  :8080  192.168.x.13      \u2502   \u2502\n\u2502                      \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518   \u2502\n\u2502                                                      \u2502\n\u2502         Almacenamiento NFS compartido entre VMs      \u2502\n\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518<\/code><\/pre>\n\n\n\n<p>La arquitectura era coherente desde el punto de vista de la separaci\u00f3n de responsabilidades, pero ten\u00eda un problema pr\u00e1ctico evidente: cuatro VMs implican cuatro sistemas operativos que mantener, cuatro puntos de gesti\u00f3n, overhead de red entre VMs para operaciones que podr\u00edan ser locales, y un consumo de recursos significativamente mayor. Para un proyecto personal en un servidor que tambi\u00e9n hace otras cosas, era sobredimensionado.<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"705\" height=\"645\" src=\"https:\/\/bitacora.eniac2000.com\/wp-content\/uploads\/2026\/04\/arquitectura-inicial-webodm-1.png\" alt=\"Diagrama de arquitectura inicial. Cuatro m\u00e1quinas virtuales desplegadas en Proxmox. Elegante, pero demasiado complejo\" class=\"wp-image-11727\" srcset=\"https:\/\/bitacora.eniac2000.com\/wp-content\/uploads\/2026\/04\/arquitectura-inicial-webodm-1.png 705w, https:\/\/bitacora.eniac2000.com\/wp-content\/uploads\/2026\/04\/arquitectura-inicial-webodm-1-300x274.png 300w\" sizes=\"auto, (max-width: 705px) 100vw, 705px\" \/><figcaption class=\"wp-element-caption\">Diagrama de arquitectura inicial. Cuatro m\u00e1quinas virtuales desplegadas en Proxmox. Elegante, pero demasiado complejo<\/figcaption><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\">La arquitectura real: todo en una VM con Docker<\/h2>\n\n\n\n<p>La simplificaci\u00f3n fue inmediata: consolidar todo en una \u00fanica VM Debian 12, con todos los servicios corriendo como contenedores Docker orquestados por un \u00fanico <code>docker-compose.yml<\/code>. Las ventajas son claras frente a la arquitectura multi-VM: menos overhead de red (los contenedores se comunican por la red interna de Docker), un \u00fanico punto de gesti\u00f3n, y Docker Compose como herramienta de orquestaci\u00f3n ya conocida.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510\n\u2502              VM Debian 12 \u2014 Proxmox VE                    \u2502\n\u2502                                                           \u2502\n\u2502  Puerto 80 expuesto al exterior                           \u2502\n\u2502         \u2502                                                 \u2502\n\u2502         \u25bc                                                 \u2502\n\u2502  \u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510   Red interna Docker: odm-net            \u2502\n\u2502  \u2502 odm-nginx   \u2502   (172.20.0.0\/16)                        \u2502\n\u2502  \u2502 Reverse     \u2502                                          \u2502\n\u2502  \u2502 proxy       \u251c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510           \u2502\n\u2502  \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u252c\u2500\u2500\u2500\u2500\u2500\u2500\u2518                              \u2502           \u2502\n\u2502         \u2502                                     \u2502           \u2502\n\u2502    \/odm\/\u2502              \/cluster\/              \u2502\/3d\/       \u2502\n\u2502         \u25bc                    \u25bc                \u25bc           \u2502\n\u2502  \u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510  \u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510  \u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510  \u2502\n\u2502  \u2502 odm-webodm  \u2502  \u2502 odm-clusterodm  \u2502  \u2502  odm-potree  \u2502  \u2502\n\u2502  \u2502 UI + REST   \u2502  \u2502 Orquestador     \u2502  \u2502  Visor 3D    \u2502  \u2502\n\u2502  \u2502 API :8000   \u2502  \u2502 :3001\/:8080     \u2502  \u2502  :8081       \u2502  \u2502\n\u2502  \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u252c\u2500\u2500\u2500\u2500\u2500\u2500\u2518  \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u252c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518  \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518  \u2502\n\u2502         \u2502                  \u2502                              \u2502\n\u2502         \u25bc                  \u25bc                              \u2502\n\u2502  \u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510  \u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510                    \u2502\n\u2502  \u2502 odm-postgres\u2502  \u2502  odm-nodeodm    \u2502                    \u2502\n\u2502  \u2502 BD WebODM   \u2502  \u2502  Motor ODM      \u2502                    \u2502\n\u2502  \u2502 :5432       \u2502  \u2502  :3000          \u2502                    \u2502\n\u2502  \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518  \u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518                    \u2502\n\u2502                                                           \u2502\n\u2502  \/odm-data  (disco virtual dedicado)                      \u2502\n\u2502  \u251c\u2500\u2500 projects\/        \u2190 im\u00e1genes e outputs de ODM         \u2502\n\u2502  \u251c\u2500\u2500 potree-output\/   \u2190 nubes de puntos para Potree       \u2502\n\u2502  \u2514\u2500\u2500 postgres\/        \u2190 datos de WebODM                   \u2502\n\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518<\/code><\/pre>\n\n\n\n<p>Los recursos asignados a la VM para un procesado c\u00f3modo son 10\u201312 vCores (ODM es muy intensivo en CPU), 28\u201332 GB de RAM y dos discos virtuales: uno para el sistema y Docker, otro dedicado a <code>\/odm-data<\/code> donde viven todos los proyectos y resultados.<\/p>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"701\" height=\"588\" src=\"https:\/\/bitacora.eniac2000.com\/wp-content\/uploads\/2026\/04\/arquitectura-final-docker-webodm.png\" alt=\"Diagrama de arquitectura final. M\u00e1quina virtual \u00fanica con docker\" class=\"wp-image-11726\" srcset=\"https:\/\/bitacora.eniac2000.com\/wp-content\/uploads\/2026\/04\/arquitectura-final-docker-webodm.png 701w, https:\/\/bitacora.eniac2000.com\/wp-content\/uploads\/2026\/04\/arquitectura-final-docker-webodm-300x252.png 300w\" sizes=\"auto, (max-width: 701px) 100vw, 701px\" \/><figcaption class=\"wp-element-caption\">Diagrama de arquitectura final. M\u00e1quina virtual \u00fanica con docker<\/figcaption><\/figure>\n\n\n\n<p>El flujo completo de una misi\u00f3n, desde el dron hasta el modelo 3D, queda as\u00ed:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>DJI Mini 3 Pro\n     \u2502 captura im\u00e1genes georreferenciadas\n     \u25bc\nApp Android\n     \u2502 upload v\u00eda REST API (Fase 4 \u2014 pendiente)\n     \u25bc\nWebODM API \u2500\u2500\u25b6 ClusterODM \u2500\u2500\u25b6 NodeODM (motor ODM)\n                                    \u2502\n                                    \u25bc\n                         .laz \/ .ply \/ .tiff\n                                    \u2502\n                         PotreeConverter (host)\n                                    \u2502\n                                    \u25bc\n                         Potree Viewer (navegador)\n                                    \u2502\n                                    \u25bc\n                         Modelo 3D navegable en web<\/code><\/pre>\n\n\n\n<figure class=\"wp-block-image size-full\"><img loading=\"lazy\" decoding=\"async\" width=\"713\" height=\"86\" src=\"https:\/\/bitacora.eniac2000.com\/wp-content\/uploads\/2026\/04\/flujo-procesamiento-fotogrametria-1.png\" alt=\"Diagrama de procesado, desde el dron hasta la imagen procesada\" class=\"wp-image-11723\" srcset=\"https:\/\/bitacora.eniac2000.com\/wp-content\/uploads\/2026\/04\/flujo-procesamiento-fotogrametria-1.png 713w, https:\/\/bitacora.eniac2000.com\/wp-content\/uploads\/2026\/04\/flujo-procesamiento-fotogrametria-1-300x36.png 300w\" sizes=\"auto, (max-width: 713px) 100vw, 713px\" \/><figcaption class=\"wp-element-caption\">Diagrama de procesado, desde el dron hasta la imagen procesada<\/figcaption><\/figure>\n\n\n\n<p>El tiempo estimado de procesado en el DL360 Gen8 es de 30 a 120 minutos, dependiendo del \u00e1rea cubierta y la resoluci\u00f3n de las im\u00e1genes. No es instant\u00e1neo, pero es perfectamente asumible para un flujo de trabajo de campo donde el procesado se lanza al llegar a casa.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Claude Code como ingeniero de sistemas<\/h2>\n\n\n\n<p>Aqu\u00ed es donde este art\u00edculo se separa de los anteriores de la serie. En las fases anteriores, Claude Code actuaba como arquitecto y co-desarrollador de la aplicaci\u00f3n Android: dise\u00f1aba m\u00f3dulos, generaba c\u00f3digo Kotlin, depuraba errores de compilaci\u00f3n. Para el despliegue de la infraestructura, el rol es completamente diferente: <strong>ingeniero de sistemas aut\u00f3nomo<\/strong>.<\/p>\n\n\n\n<p>La aproximaci\u00f3n fue instalar Claude Code directamente en la VM Debian donde se iba a desplegar el stack, y darle un \u00fanico prompt detallado con todo lo que ten\u00eda que hacer, dej\u00e1ndolo ejecutar de forma aut\u00f3noma:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code># Instalaci\u00f3n de Claude Code en la VM\ncurl -fsSL https:\/\/claude.ai\/install.sh | bash\n\n# Ejecuci\u00f3n en modo aut\u00f3nomo\nclaude --dangerously-skip-permissions --model claude-sonnet-4-6<\/code><\/pre>\n\n\n\n<p>El flag <code>--dangerously-skip-permissions<\/code> merece una menci\u00f3n. En el uso habitual de Claude Code, cada operaci\u00f3n que modifica el sistema de ficheros o ejecuta comandos requiere confirmaci\u00f3n expl\u00edcita del usuario. Con este flag se elimina esa fricci\u00f3n y Claude Code opera con autonom\u00eda completa. En un entorno productivo o compartido ser\u00eda una decisi\u00f3n arriesgada; en un servidor personal controlado, con una tarea bien definida, es exactamente lo que se necesita para un despliegue desatendido.<\/p>\n\n\n\n<p>El prompt que se le entreg\u00f3 estructuraba el trabajo en ocho fases secuenciales, con criterios de \u00e9xito expl\u00edcitos para cada una y la instrucci\u00f3n de no pasar a la siguiente si la anterior hab\u00eda fallado:<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li><strong>Verificaci\u00f3n del sistema<\/strong>: comprobar disco montado, conectividad a internet, Docker instalado. Si Docker no estaba presente, instalarlo desde el repositorio oficial (no el paquete <code>docker.io<\/code> de Debian, que es m\u00e1s antiguo).<\/li>\n\n\n\n<li><strong>Preparaci\u00f3n de directorios<\/strong>: crear la estructura de <code>\/odm-data<\/code> y <code>\/opt\/odm-stack<\/code>.<\/li>\n\n\n\n<li><strong>Crear ficheros de configuraci\u00f3n<\/strong>: escribir el <code>docker-compose.yml<\/code> completo con los seis servicios y el <code>nginx.conf<\/code> con las tres rutas y los timeouts largos necesarios para el polling de tareas ODM.<\/li>\n\n\n\n<li><strong>Instalar PotreeConverter en el host<\/strong>: el conversor de nubes de puntos corre directamente en la VM, no en Docker, porque necesita acceso directo a los ficheros de salida de ODM.<\/li>\n\n\n\n<li><strong>Arrancar el stack<\/strong>: <code>docker compose up -d<\/code>, esperar 60 segundos y verificar que todos los contenedores estaban en estado <em>running<\/em>.<\/li>\n\n\n\n<li><strong>Verificaci\u00f3n funcional<\/strong>: una bater\u00eda de <code>curl<\/code> contra cada ruta del reverse proxy y comprobaciones internas entre contenedores.<\/li>\n\n\n\n<li><strong>Configuraci\u00f3n post-despliegue<\/strong>: mostrar las URLs de acceso, recordar los pasos manuales que quedan (crear la cuenta de administrador en WebODM, a\u00f1adir los nodos de procesado, obtener el token de API).<\/li>\n\n\n\n<li><strong>Arranque autom\u00e1tico<\/strong>: crear un servicio systemd que levante el stack Docker al arrancar la VM.<\/li>\n<\/ol>\n\n\n\n<p>Lo que hace interesante esta aproximaci\u00f3n es la naturaleza del trabajo. No es generar c\u00f3digo de aplicaci\u00f3n, sino que es hacer exactamente lo que har\u00eda un administrador de sistemas experimentado: verificar el entorno, instalar dependencias, escribir ficheros de configuraci\u00f3n, arrancar servicios, validar que funcionan y dejar el sistema listo para producci\u00f3n. Y hacerlo en el orden correcto, con manejo de errores en cada paso.<\/p>\n\n\n\n<p>Un detalle que ilustra bien la capacidad de razonamiento en este contexto: el prompt especificaba instalar Docker desde el repositorio oficial, no desde el paquete <code>docker.io<\/code> del repositorio de Debian. La diferencia es que el paquete oficial de Docker suele estar varias versiones por delante del que empaqueta Debian, y algunas funcionalidades de Docker Compose v2 requieren versiones recientes. Claude Code entiende esta distinci\u00f3n y ejecuta los pasos correctos sin necesidad de explicarlos en detalle.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Algunos detalles de configuraci\u00f3n que importan<\/h2>\n\n\n\n<p>El <code>docker-compose.yml<\/code> tiene algunas decisiones de dise\u00f1o que vale la pena mencionar. La base de datos PostgreSQL incluye un <em>healthcheck<\/em> que verifica que acepta conexiones antes de que WebODM intente conectarse, evitando la carrera de arranque habitual entre aplicaci\u00f3n y base de datos. WebODM tiene una condici\u00f3n <code>depends_on<\/code> con <code>condition: service_healthy<\/code> sobre Postgres, lo que garantiza el orden correcto sin necesidad de scripts de espera artificiales.<\/p>\n\n\n\n<p>El nginx merece especial atenci\u00f3n en los timeouts. WebODM hace <em>polling<\/em> de larga duraci\u00f3n para seguir el estado de las tareas de procesado y, sin timeouts configurados expl\u00edcitamente, nginx cortar\u00eda esas conexiones antes de que ODM terminara. Se configuraron 3600 segundos (una hora) para <code>proxy_read_timeout<\/code>, <code>proxy_send_timeout<\/code> y <code>proxy_connect_timeout<\/code> en la ruta <code>\/odm\/<\/code>, y un <code>client_max_body_size<\/code> de 10 GB para permitir el upload de conjuntos de im\u00e1genes grandes.<\/p>\n\n\n\n<p>PotreeConverter se instala directamente en el host de la VM, fuera de Docker, porque necesita leer los ficheros <code>.laz<\/code> que produce ODM y escribir los resultados en el directorio que sirve el contenedor de Potree. Un script de conversi\u00f3n de una l\u00ednea automatiza el proceso una vez que un proyecto ODM ha terminado:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>\/opt\/odm-stack\/convert-to-potree.sh nombre-del-proyecto<\/code><\/pre>\n\n\n\n<p>El resultado queda accesible en <code>http:\/\/[IP-servidor]\/3d\/nombre-del-proyecto\/index.html<\/code> \u2014 una nube de puntos navegable directamente en el navegador, sin software adicional.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">El resultado: tres URLs y un flujo de trabajo completo<\/h2>\n\n\n\n<p>Con el <em>stack<\/em> desplegado, los puntos de acceso al entorno de procesado son:<\/p>\n\n\n\n<figure class=\"wp-block-table\"><table><thead><tr><th>Servicio<\/th><th>URL<\/th><th>Para qu\u00e9 sirve<\/th><\/tr><\/thead><tbody><tr><td>WebODM<\/td><td><code>http:\/\/[IP]\/<\/code><\/td><td>Interfaz web completa y API REST para la app Android<\/td><\/tr><tr><td>ClusterODM<\/td><td><code>http:\/\/[IP]\/cluster\/<\/code><\/td><td>Panel de administraci\u00f3n del orquestador de tareas<\/td><\/tr><tr><td>Potree<\/td><td><code>http:\/\/[IP]\/3d\/&lt;proyecto&gt;\/index.html<\/code><\/td><td>Visor 3D de la nube de puntos en el navegador<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<p>Una vez se accede a la aplicaci\u00f3n, ya es posible hacer procesado de im\u00e1genes de manera manual, creando las tareas, asociando las im\u00e1genes, y escogiendo el modelo de procesado. De salida genera unas ortofotograf\u00edas y modelos en 3D, tanto de nube de puntos como de texturas, de calidad nada desde\u00f1able.<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"320\" src=\"https:\/\/bitacora.eniac2000.com\/wp-content\/uploads\/2026\/04\/WebODM-primera-mision-1-1024x320.png\" alt=\"Primera misi\u00f3n generada en ODM\" class=\"wp-image-11721\" srcset=\"https:\/\/bitacora.eniac2000.com\/wp-content\/uploads\/2026\/04\/WebODM-primera-mision-1-1024x320.png 1024w, https:\/\/bitacora.eniac2000.com\/wp-content\/uploads\/2026\/04\/WebODM-primera-mision-1-300x94.png 300w, https:\/\/bitacora.eniac2000.com\/wp-content\/uploads\/2026\/04\/WebODM-primera-mision-1-768x240.png 768w, https:\/\/bitacora.eniac2000.com\/wp-content\/uploads\/2026\/04\/WebODM-primera-mision-1.png 1383w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><figcaption class=\"wp-element-caption\">Primera misi\u00f3n generada en ODM<\/figcaption><\/figure>\n\n\n\n<p>El \u00fanico paso que queda por automatizar es la integraci\u00f3n directa desde la app Android, prevista en la Fase 4: que al terminar una misi\u00f3n de vuelo, la app suba autom\u00e1ticamente las im\u00e1genes a WebODM, lance el procesado y notifique cuando el modelo est\u00e9 listo. La API REST de WebODM ya est\u00e1 disponible y documentada; es cuesti\u00f3n de implementar el cliente en la app.<\/p>\n\n\n\n<p>Lo que me parece m\u00e1s destacable de este proceso es la demostraci\u00f3n de que Claude Code no es solo una herramienta de desarrollo de software. En este caso no se escribi\u00f3 pr\u00e1cticamente ning\u00fan c\u00f3digo de aplicaci\u00f3n: se dise\u00f1\u00f3 una arquitectura, se tomaron decisiones de infraestructura, se redact\u00f3 una gu\u00eda de despliegue completa y se automatiz\u00f3 la ejecuci\u00f3n de ese despliegue en un servidor real. Eso es integraci\u00f3n de sistemas, y funciona igual de bien que en el desarrollo de la app Android.<\/p>\n","protected":false},"excerpt":{"rendered":"<div class=\"seriesmeta\">Esta entrada es la parte 5 de 6 de la serie <a href=\"https:\/\/bitacora.eniac2000.com\/?series=fotogrametria-asistida-por-ia\" class=\"series-1957\" title=\"Fotogrametr\u00eda asistida por IA\">Fotogrametr\u00eda asistida por IA<\/a><\/div><p>Hasta ahora los art\u00edculos de esta serie se han centrado<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"advanced_seo_description":"","jetpack_seo_html_title":"","jetpack_seo_noindex":false,"_jetpack_memberships_contains_paid_content":false,"footnotes":"","jetpack_publicize_message":"","jetpack_publicize_feature_enabled":true,"jetpack_social_post_already_shared":true,"jetpack_social_options":{"image_generator_settings":{"template":"highway","default_image_id":0,"font":"","enabled":false},"version":2}},"categories":[1845,13],"tags":[106,1959,517,688,1169,1202,1310],"series":[1957],"class_list":["post-11725","post","type-post","status-publish","format-standard","hentry","category-generado-con-ia","category-informatica","tag-android","tag-claude-code","tag-debian","tag-fotogrametria","tag-nginx","tag-open-drone-map","tag-proxmox","series-fotogrametria-asistida-por-ia"],"jetpack_publicize_connections":[],"jetpack_featured_media_url":"","jetpack_sharing_enabled":true,"_links":{"self":[{"href":"https:\/\/bitacora.eniac2000.com\/index.php?rest_route=\/wp\/v2\/posts\/11725","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/bitacora.eniac2000.com\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/bitacora.eniac2000.com\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/bitacora.eniac2000.com\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/bitacora.eniac2000.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=11725"}],"version-history":[{"count":2,"href":"https:\/\/bitacora.eniac2000.com\/index.php?rest_route=\/wp\/v2\/posts\/11725\/revisions"}],"predecessor-version":[{"id":11729,"href":"https:\/\/bitacora.eniac2000.com\/index.php?rest_route=\/wp\/v2\/posts\/11725\/revisions\/11729"}],"wp:attachment":[{"href":"https:\/\/bitacora.eniac2000.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=11725"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/bitacora.eniac2000.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=11725"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/bitacora.eniac2000.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=11725"},{"taxonomy":"series","embeddable":true,"href":"https:\/\/bitacora.eniac2000.com\/index.php?rest_route=%2Fwp%2Fv2%2Fseries&post=11725"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}