{"id":11750,"date":"2026-04-12T11:35:00","date_gmt":"2026-04-12T09:35:00","guid":{"rendered":"https:\/\/bitacora.eniac2000.com\/?p=11750"},"modified":"2026-04-11T21:49:53","modified_gmt":"2026-04-11T19:49:53","slug":"aplicacion-android-para-fotogrametria-con-ia-fase-5-streaming-rtmp-y-telemetria-en-tiempo-real-como-se-coordina-un-sistema-entre-dos-agentes-de-ia","status":"publish","type":"post","link":"https:\/\/bitacora.eniac2000.com\/?p=11750","title":{"rendered":"Aplicaci\u00f3n Android para fotogrametr\u00eda con IA. Fase 5: Streaming RTMP y telemetr\u00eda en tiempo real \u2014 c\u00f3mo se coordina un sistema entre dos agentes de IA"},"content":{"rendered":"<div class=\"seriesmeta\">Esta entrada es la parte 8 de 8 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<p>En el <a href=\"https:\/\/bitacora.eniac2000.com\/?p=11703\">art\u00edculo anterior<\/a> de esta serie describ\u00ed el m\u00f3dulo de auditor\u00eda IA desplegado en el servidor: un sistema capaz de ingestar v\u00eddeo RTMP, analizarlo con YOLOv8 y georreferenciar los hallazgos sobre el ortomosaico. El m\u00f3dulo estaba listo y esperando. Lo que faltaba era que la <em>app<\/em> Android lo alimentara: emitir el <em>stream<\/em> de v\u00eddeo del dron hacia el servidor durante el vuelo y enviar la telemetr\u00eda GPS con la precisi\u00f3n temporal suficiente para que la georreferenciaci\u00f3n funcionara. Eso es la Fase 5, y lo que la hace especialmente interesante como proceso de desarrollo es c\u00f3mo se coordinaron los dos agentes de Claude Code \u2014el del lado del servidor y el del lado de la app\u2014 para que todo encajara sin fricciones.<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"572\" src=\"https:\/\/bitacora.eniac2000.com\/wp-content\/uploads\/2026\/04\/Gemini_Generated_Image_nxpbahnxpbahnxpb-1024x572.png\" alt=\"\" class=\"wp-image-11754\" srcset=\"https:\/\/bitacora.eniac2000.com\/wp-content\/uploads\/2026\/04\/Gemini_Generated_Image_nxpbahnxpbahnxpb-1024x572.png 1024w, https:\/\/bitacora.eniac2000.com\/wp-content\/uploads\/2026\/04\/Gemini_Generated_Image_nxpbahnxpbahnxpb-300x167.png 300w, https:\/\/bitacora.eniac2000.com\/wp-content\/uploads\/2026\/04\/Gemini_Generated_Image_nxpbahnxpbahnxpb-768x429.png 768w, https:\/\/bitacora.eniac2000.com\/wp-content\/uploads\/2026\/04\/Gemini_Generated_Image_nxpbahnxpbahnxpb.png 1376w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><figcaption class=\"wp-element-caption\">Imagen conceptual de la coordinaci\u00f3n entre los diferentes agentes Claude Code empleados. Imagen generada con IA<\/figcaption><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\">El problema de integrar dos sistemas desarrollados por separado<\/h2>\n\n\n\n<p>Cuando dos sistemas independientes necesitan comunicarse, el momento m\u00e1s cr\u00edtico no es la implementaci\u00f3n sino el acuerdo previo: qu\u00e9 datos se intercambian, en qu\u00e9 formato, qui\u00e9n genera cada identificador, c\u00f3mo se manejan los errores. Si ese acuerdo no es expl\u00edcito y preciso, la integraci\u00f3n acaba en una serie de ajustes iterativos donde cada parte asume cosas distintas sobre la otra.<\/p>\n\n\n\n<p>En este caso, \u00ablas dos partes\u00bb eran el agente de Claude Code trabajando en la <em>app<\/em> Android y el agente trabajando en el servidor. La soluci\u00f3n fue establecer un <strong>contrato de API formal<\/strong> antes de escribir ning\u00fan c\u00f3digo en ninguno de los dos lados. Ese contrato defin\u00eda con precisi\u00f3n tres aspectos que son los que m\u00e1s fricci\u00f3n generan en una integraci\u00f3n:<\/p>\n\n\n\n<p><strong>Qui\u00e9n genera el identificador de vuelo.<\/strong> La decisi\u00f3n fue que la app genera el <code>flight_id<\/code> como UUID v4 en el momento de iniciar la misi\u00f3n, y el servidor lo recibe y registra. Podr\u00eda haber sido al rev\u00e9s \u2014el servidor genera el ID y la app lo pide\u2014 pero eso crea una dependencia de red en el camino cr\u00edtico del despegue: si la petici\u00f3n falla o tarda, el dron est\u00e1 esperando con los motores en marcha. Con el UUID generado localmente, el ID est\u00e1 disponible antes de abrir el stream RTMP y antes de enviar el primer paquete de telemetr\u00eda, sin depender de ninguna respuesta de red.<\/p>\n\n\n\n<p><strong>C\u00f3mo se asocia el v\u00eddeo con el vuelo.<\/strong> MediaMTX no tiene un mecanismo nativo para etiquetar streams con metadatos de aplicaci\u00f3n. Aqu\u00ed hay dos opciones: que el servidor busque el stream activo cuando llega el registro de vuelo (por ventana temporal), o que el nombre del <em>stream<\/em> incluya el identificador. Se eligi\u00f3 la segunda: la URL RTMP lleva el <code>flight_id<\/code> en el path \u2014 <code>rtmp:\/\/host:1935\/live_&lt;uuid><\/code>. El campo de ajustes de la app almacena solo la base (<code>rtmp:\/\/192.168.0.100:1935\/live<\/code>) y el c\u00f3digo a\u00f1ade <code>_&lt;flight_id><\/code> al conectar. El servidor asocia v\u00eddeo con vuelo por el <em>path<\/em> del <em>stream<\/em>, de forma determinista y sin ambig\u00fcedad.<\/p>\n\n\n\n<p><strong>Qu\u00e9 pasa cuando hay problemas de red.<\/strong> Una misi\u00f3n de fotogrametr\u00eda puede transcurrir en campo abierto con cobertura irregular. El contrato defini\u00f3 que el <code>flight_id<\/code> se persiste en Room <em>antes<\/em> de cualquier llamada de red, que la telemetr\u00eda se acumula localmente si no hay conexi\u00f3n y se sincroniza al recuperarla, y que si el servidor no est\u00e1 disponible al arrancar, la misi\u00f3n contin\u00faa exactamente igual que antes de que existiera el m\u00f3dulo de auditor\u00eda. <strong>Degradaci\u00f3n elegante<\/strong>: sin URLs configuradas, los componentes de auditor\u00eda hacen <em>skip<\/em> silencioso y no afectan en absoluto al flujo de fotogrametr\u00eda.<\/p>\n\n\n\n<p>Con ese contrato acordado, ambos agentes pudieron implementar sus respectivas partes de forma independiente, sabiendo exactamente qu\u00e9 comportamiento esperaba el otro extremo.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">La idempotencia como mecanismo de robustez<\/h2>\n\n\n\n<p>Uno de los detalles del contrato que m\u00e1s impacto tuvo en la robustez del sistema fue la definici\u00f3n del comportamiento ante reintentos. En una red m\u00f3vil con cobertura irregular, cualquier petici\u00f3n puede perderse o recibirse duplicada. El contrato especific\u00f3 que si la app env\u00eda el mismo <code>flight_id<\/code> dos veces al endpoint de registro de vuelo, el servidor responde <strong>HTTP 409 Conflict<\/strong>. Y que la app trata un 409 exactamente igual que un 201: el vuelo est\u00e1 registrado, continuar.<\/p>\n\n\n\n<p>Esta convenci\u00f3n de idempotencia se aplica tambi\u00e9n a la telemetr\u00eda: los puntos duplicados son ignorados por el servidor sin error. La app puede reintentar con confianza sin preocuparse por estados inconsistentes. El WorkManager que gestiona el registro de vuelo usa <em>backoff<\/em> exponencial con un m\u00e1ximo de cinco intentos; si en alguno de ellos el servidor ya registr\u00f3 el vuelo (por un intento anterior que lleg\u00f3 tarde), el 409 detiene los reintentos sin marcar el <em>worker<\/em> como fallido.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">La implementaci\u00f3n en la app: diez ficheros nuevos<\/h2>\n\n\n\n<p>La implementaci\u00f3n en Android a\u00f1adi\u00f3 diez ficheros nuevos y modific\u00f3 ocho existentes. Los componentes principales:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong><code>LiveStreamHelper<\/code><\/strong>: <em>singleton<\/em> que gestiona el <em>streaming<\/em> RTMP via el <code>LiveStreamManager<\/code> del DJI MSDK v5, a 720p y bitrate autom\u00e1tico, con tres reintentos en caso de fallo de conexi\u00f3n.<\/li>\n\n\n\n<li><strong><code>TelemetrySender<\/code><\/strong>: <em>singleton<\/em> que muestrea la posici\u00f3n GPS cada segundo, la env\u00eda al servidor via REST, y la acumula en Room si no hay red, con <em>flush<\/em> autom\u00e1tico cada 5 segundos cuando la conexi\u00f3n se recupera.<\/li>\n\n\n\n<li><strong><code>AuditRepository<\/code><\/strong>: repositorio con instancia de Retrofit construida de forma lazy y cacheada por URL, lo que evita el problema de la base URL obsoleta si el usuario cambia la configuraci\u00f3n en ajustes sin reiniciar la app.<\/li>\n\n\n\n<li><strong><code>FlightRegistrationWorker<\/code><\/strong>: <em>worker<\/em> de WorkManager con Hilt para reintentar el registro del vuelo en background si la petici\u00f3n inicial falla.<\/li>\n<\/ul>\n\n\n\n<p>Un reto t\u00e9cnico interesante surgi\u00f3 al integrar el <code>LiveStreamManager<\/code> del SDK. Las APIs reales de la clase no coincid\u00edan con la documentaci\u00f3n: el campo de bitrate se llama <code>vbps<\/code>, no <code>bitrate<\/code>. La forma de descubrirlo fue ejecutar <code>javap<\/code> sobre el JAR ofuscado del SDK para inspeccionar los nombres reales de los campos. No es la forma m\u00e1s c\u00f3moda de trabajar con una librer\u00eda, pero es eficaz.<\/p>\n\n\n\n<p>Otro reto fue la inyecci\u00f3n de dependencias en <em>singletons<\/em> Kotlin (<code>object<\/code>). Hilt gestiona bien las clases normales, pero los <code>object<\/code> no son instanciados por el contenedor de inyecci\u00f3n. La soluci\u00f3n fue un patr\u00f3n <code>init()<\/code> llamado desde <code>PlannerApp.onCreate()<\/code>, donde las dependencias inyectadas via Hilt se pasan expl\u00edcitamente a los <em>singletons<\/em> al arrancar la aplicaci\u00f3n.<\/p>\n\n\n\n<p>El flujo de ejecuci\u00f3n al iniciar una misi\u00f3n qued\u00f3 as\u00ed:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>startMission()\n  1. flight_id = UUID.randomUUID()                     \u2190 app genera el ID\n  2. Room.insert(FlightEntity)                          \u2190 persiste ANTES de red\n  3. POST \/audit\/flights {flight_id, project_name}     \u2190 registro en servidor\n     \u2514\u2500 si falla \u2192 WorkManager reintentar\u00e1 en background\n  4. LiveStream.start(\"rtmp:\/\/host:1935\/live_<uuid>\")  \u2190 stream RTMP\n  5. TelemetrySender.start(flight_id)                  \u2190 telemetr\u00eda cada ~1s\n  6. Virtual Stick \u2192 despegar \u2192 navegar \u2192 fotos\n  7. LiveStream.stop()\n  8. TelemetrySender.stop()\n     \u2514\u2500 PATCH \/audit\/flights\/{id}\/land                 \u2190 aterrizaje\n  9. Room.markLanded(flight_id)<\/uuid><\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">La prueba de integraci\u00f3n: primero sin dron<\/h2>\n\n\n\n<p>Antes de llevar el sistema a campo, se realiz\u00f3 una prueba de integraci\u00f3n completa desde el Mac Mini de desarrollo usando <code>curl<\/code>. La idea era validar el protocolo sin depender del dron f\u00edsico: simular exactamente lo que har\u00eda la app \u2014registrar un vuelo con un UUID propio, enviar diez puntos de telemetr\u00eda GPS, marcar el aterrizaje\u2014 y verificar que el servidor respond\u00eda correctamente en cada paso.<\/p>\n\n\n\n<figure class=\"wp-block-table\"><table><thead><tr><th>#<\/th><th>Prueba<\/th><th>Resultado<\/th><\/tr><\/thead><tbody><tr><td>1<\/td><td>Healthcheck del servidor<\/td><td>200 OK<\/td><\/tr><tr><td>2<\/td><td>Registro de vuelo con UUID propio<\/td><td>201 Created<\/td><\/tr><tr><td>3<\/td><td>Reenv\u00edo del mismo UUID (idempotencia)<\/td><td>409 Conflict \u2014 correcto, tratado como \u00e9xito<\/td><\/tr><tr><td>4<\/td><td>10 puntos de telemetr\u00eda GPS<\/td><td>200 OK (10\/10) \u2014 trayectoria en PostGIS<\/td><\/tr><tr><td>5<\/td><td>Marca de aterrizaje<\/td><td>200 OK \u2014 status cambiado a \u00ablanded\u00bb<\/td><\/tr><tr><td>6<\/td><td>Historial de vuelos<\/td><td>200 OK \u2014 vuelo visible con estado correcto<\/td><\/tr><tr><td>7<\/td><td>GeoJSON de hallazgos<\/td><td>200 OK \u2014 FeatureCollection vac\u00eda (sin v\u00eddeo RTMP, sin detecciones)<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<p>La confirmaci\u00f3n m\u00e1s importante vino del servidor: el agente del lado del servidor verific\u00f3 que los diez puntos de telemetr\u00eda estaban almacenados correctamente en PostGIS, y que el <code>ai-processor<\/code> hab\u00eda detectado el nuevo <code>flight_id<\/code> y empezado a intentar conectar al stream RTMP \u2014confirmando que cuando llegara v\u00eddeo real, el pipeline completo funcionar\u00eda sin modificaciones adicionales. El GeoJSON vac\u00edo era exactamente lo esperado: sin stream RTMP no hay frames, sin frames no hay detecciones. El sistema estaba listo.<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"618\" src=\"https:\/\/bitacora.eniac2000.com\/wp-content\/uploads\/2026\/04\/IMG-20260410-WA0011-1024x618.jpg\" alt=\"\" class=\"wp-image-11752\" srcset=\"https:\/\/bitacora.eniac2000.com\/wp-content\/uploads\/2026\/04\/IMG-20260410-WA0011-1024x618.jpg 1024w, https:\/\/bitacora.eniac2000.com\/wp-content\/uploads\/2026\/04\/IMG-20260410-WA0011-300x181.jpg 300w, https:\/\/bitacora.eniac2000.com\/wp-content\/uploads\/2026\/04\/IMG-20260410-WA0011-768x464.jpg 768w, https:\/\/bitacora.eniac2000.com\/wp-content\/uploads\/2026\/04\/IMG-20260410-WA0011.jpg 1393w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><figcaption class=\"wp-element-caption\">Resultado de la simulaci\u00f3n desde el lado del servidor. No estaba en Madrid, claro&#8230;<\/figcaption><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\">El primer vuelo real: 37 waypoints, 234 puntos de telemetr\u00eda, 7 detecciones<\/h2>\n\n\n\n<p>El 11 de abril de 2026, primer vuelo real con el m\u00f3dulo de auditor\u00eda IA activo. Una misi\u00f3n de 37 <em>waypoints<\/em> sobre una parcela en Galicia, a 20 metros de altura, a 3 m\/s, con el gimbal en nadir (\u221290\u00b0). Duraci\u00f3n: 4 minutos y 12 segundos. Consumo de bater\u00eda: del 71% al 56%.<\/p>\n\n\n\n<figure class=\"wp-block-image size-large\"><img loading=\"lazy\" decoding=\"async\" width=\"1024\" height=\"461\" src=\"https:\/\/bitacora.eniac2000.com\/wp-content\/uploads\/2026\/04\/Screenshot_20260411-175215-1024x461.png\" alt=\"Resultado del vuelo del lado de la app. La captura incluye algunas mejoras de fases posteriores\" class=\"wp-image-11746\" srcset=\"https:\/\/bitacora.eniac2000.com\/wp-content\/uploads\/2026\/04\/Screenshot_20260411-175215-1024x461.png 1024w, https:\/\/bitacora.eniac2000.com\/wp-content\/uploads\/2026\/04\/Screenshot_20260411-175215-300x135.png 300w, https:\/\/bitacora.eniac2000.com\/wp-content\/uploads\/2026\/04\/Screenshot_20260411-175215-768x346.png 768w, https:\/\/bitacora.eniac2000.com\/wp-content\/uploads\/2026\/04\/Screenshot_20260411-175215-1536x691.png 1536w, https:\/\/bitacora.eniac2000.com\/wp-content\/uploads\/2026\/04\/Screenshot_20260411-175215-2048x922.png 2048w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><figcaption class=\"wp-element-caption\">Resultado del vuelo del lado de la app. La captura incluye algunas mejoras de fases posteriores<\/figcaption><\/figure>\n\n\n\n<p>Los resultados de la parte fotogram\u00e9trica fueron los esperados: los 37 waypoints alcanzados con una precisi\u00f3n de aproximadamente 1,4 metros, 37 fotos disparadas, navegaci\u00f3n Virtual Stick estable con rotaciones suaves entre filas, RTH al finalizar. El \u00fanico problema en este apartado fue que solo 14 de las 37 fotos se descargaron al m\u00f3vil durante la misi\u00f3n; el resto qued\u00f3 en la tarjeta SD del dron, probablemente por un timeout de la transferencia.<\/p>\n\n\n\n<p>Los resultados de la parte de auditor\u00eda fueron los que realmente importaban en este vuelo:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>El UUID generado por la app (<code>d2ca8b37-9906-4a70-be05-3dcd8fe6c74f<\/code>) se us\u00f3 de forma consistente en todos los subsistemas: stream RTMP, telemetr\u00eda REST y proyecto WebODM.<\/li>\n\n\n\n<li>El <em>stream<\/em> RTMP se estableci\u00f3 al despegar y se mantuvo durante todo el vuelo. El servidor grab\u00f3 99 MB de v\u00eddeo en segmentos de 5 minutos.<\/li>\n\n\n\n<li><strong>234 puntos de telemetr\u00eda<\/strong> recibidos en el servidor, aproximadamente uno por segundo, con cobertura continua durante toda la misi\u00f3n.<\/li>\n\n\n\n<li>El PATCH de aterrizaje se ejecut\u00f3 correctamente al completar la misi\u00f3n.<\/li>\n\n\n\n<li><strong>7 detecciones YOLO<\/strong> sobre el v\u00eddeo: objetos de clase <em>bench<\/em> con confianza entre 0,60 y 0,74, geolocalizados sobre la trayectoria de vuelo.<\/li>\n\n\n\n<li>El GeoJSON completo con trayectoria y detecciones estaba disponible en el servidor.<\/li>\n\n\n\n<li>El visor web Leaflet en <code>\/audit\/view\/{flight_id}<\/code> mostraba los marcadores sobre el mapa correctamente.<\/li>\n<\/ul>\n\n\n\n<p>El <em>pipeline<\/em> completo funcion\u00f3 en el primer vuelo real: App \u2192 RTMP \u2192 MediaMTX \u2192 YOLOv8 \u2192 PostGIS \u2192 API REST \u2192 Visor web. Sin ajustes de \u00faltima hora, sin cambios en el protocolo. El contrato previo hab\u00eda hecho su trabajo.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Un problema de <em>timing<\/em>: el proyecto ODM llega despu\u00e9s del vuelo<\/h2>\n\n\n\n<p>Tras el vuelo, el visor de hallazgos mostraba los marcadores sobre OpenStreetMap pero no sobre el ortomosaico. El motivo: el proyecto WebODM no existe en el momento del despegue \u2014se crea cuando el usuario decide procesar las im\u00e1genes, que puede ser horas despu\u00e9s\u2014 as\u00ed que el vuelo quedaba registrado sin <code>odm_project_id<\/code> y el visor no pod\u00eda vincularlo al ortomosaico.<\/p>\n\n\n\n<p>La soluci\u00f3n que adopt\u00e9 evit\u00f3 mover la creaci\u00f3n del proyecto ODM al momento del despegue, que habr\u00eda complicado significativamente el flujo de ejecuci\u00f3n: en su lugar se a\u00f1adi\u00f3 un endpoint de parcheado post-vuelo: cuando <code>OdmUploadWorker<\/code> crea el proyecto WebODM y obtiene el <code>projectId<\/code>, inmediatamente llama a <code>PATCH \/audit\/flights\/{flight_id}\/odm<\/code> con ese ID. Si la llamada falla, se registra en el log pero no bloquea el procesamiento. El visor recoge el <code>odm_project_id<\/code> en la siguiente consulta y puede entonces cargar el ortomosaico correspondiente.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">La coordinaci\u00f3n entre agentes como metodolog\u00eda<\/h2>\n\n\n\n<p>Mirando el proceso en retrospectiva, lo que funcion\u00f3 en esta fase fue establecer el contrato de integraci\u00f3n como un documento compartido entre ambos agentes antes de empezar a codificar. No una especificaci\u00f3n vaga de \u00abla app env\u00eda telemetr\u00eda al servidor\u00bb, sino decisiones concretas y justificadas: qui\u00e9n genera el UUID y por qu\u00e9, c\u00f3mo se nombra el stream RTMP y por qu\u00e9 esa opci\u00f3n sobre las alternativas, qu\u00e9 c\u00f3digo HTTP indica idempotencia y c\u00f3mo lo trata cada parte.<\/p>\n\n\n\n<p>La consecuencia pr\u00e1ctica es que la prueba de integraci\u00f3n fue una verificaci\u00f3n, no un descubrimiento. No hubo negociaciones de \u00faltimo momento sobre formatos de datos ni malentendidos sobre qui\u00e9n deb\u00eda hacer qu\u00e9. El servidor esperaba exactamente lo que la app enviaba, y la app esperaba exactamente lo que el servidor devolv\u00eda. En un sistema con dos componentes desarrollados de forma independiente, eso no es trivial.<\/p>\n\n\n\n<p>En pr\u00f3ximos art\u00edculos de la serie cubrir\u00e9 las Fases 6 y 7 de la app: los resultados del procesado ODM visibles directamente en el mapa, la gesti\u00f3n de m\u00faltiples bater\u00edas para misiones largas, la reanudaci\u00f3n de misiones interrumpidas, la edici\u00f3n de pol\u00edgono post-confirmaci\u00f3n y la cach\u00e9 offline de tiles. El proyecto est\u00e1 tomando una forma bastante completa.<\/p>\n","protected":false},"excerpt":{"rendered":"<div class=\"seriesmeta\">Esta entrada es la parte 8 de 8 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>En el art\u00edculo anterior de esta serie describ\u00ed el m\u00f3dulo<\/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,1968,537,1965,745,1020,1969,1308,1967,1964],"series":[1957],"class_list":["post-11750","post","type-post","status-publish","format-standard","hentry","category-generado-con-ia","category-informatica","tag-android","tag-claude-code","tag-contratos","tag-dji-mini-3-pro","tag-geojson","tag-gps","tag-mac-mini-m4","tag-mediamtx","tag-proliant","tag-rtmp","tag-yolo","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\/11750","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=11750"}],"version-history":[{"count":4,"href":"https:\/\/bitacora.eniac2000.com\/index.php?rest_route=\/wp\/v2\/posts\/11750\/revisions"}],"predecessor-version":[{"id":11756,"href":"https:\/\/bitacora.eniac2000.com\/index.php?rest_route=\/wp\/v2\/posts\/11750\/revisions\/11756"}],"wp:attachment":[{"href":"https:\/\/bitacora.eniac2000.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=11750"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/bitacora.eniac2000.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=11750"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/bitacora.eniac2000.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=11750"},{"taxonomy":"series","embeddable":true,"href":"https:\/\/bitacora.eniac2000.com\/index.php?rest_route=%2Fwp%2Fv2%2Fseries&post=11750"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}