{"id":11689,"date":"2026-03-29T21:12:35","date_gmt":"2026-03-29T19:12:35","guid":{"rendered":"https:\/\/bitacora.eniac2000.com\/?p=11689"},"modified":"2026-03-29T21:15:39","modified_gmt":"2026-03-29T19:15:39","slug":"aplicacion-android-para-fotogrametria-con-ia-verificacion-del-entorno-y-fase-1-de-desarrollo-inicial","status":"publish","type":"post","link":"https:\/\/bitacora.eniac2000.com\/?p=11689","title":{"rendered":"Aplicaci\u00f3n Android para fotogrametr\u00eda con IA. Verificaci\u00f3n del entorno y Fase 1 de desarrollo inicial"},"content":{"rendered":"<div class=\"seriesmeta\">Esta entrada es la parte 2 de 12 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=11672\">art\u00edculo anterior<\/a> expliqu\u00e9 el origen de este proyecto, el planteamiento general de la aplicaci\u00f3n y el plan de desarrollo que Claude Code traz\u00f3 antes de escribir la primera l\u00ednea de c\u00f3digo. En este art\u00edculo toca hablar de c\u00f3mo se materializ\u00f3 la primera fase: desde la verificaci\u00f3n del entorno en el <a href=\"https:\/\/bitacora.eniac2000.com\/?p=5786\" data-type=\"post\" data-id=\"5786\" target=\"_blank\" rel=\"noreferrer noopener\">Mac Mini M4<\/a> hasta tener una aplicaci\u00f3n Android funcional capaz de planificar misiones de fotogrametr\u00eda sobre un mapa real.<\/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\/03\/Screenshot_20260321-214513-1024x461.png\" alt=\"Ejemplo de una rejilla de vuelo sobre una misi\u00f3n\" class=\"wp-image-11680\" srcset=\"https:\/\/bitacora.eniac2000.com\/wp-content\/uploads\/2026\/03\/Screenshot_20260321-214513-1024x461.png 1024w, https:\/\/bitacora.eniac2000.com\/wp-content\/uploads\/2026\/03\/Screenshot_20260321-214513-300x135.png 300w, https:\/\/bitacora.eniac2000.com\/wp-content\/uploads\/2026\/03\/Screenshot_20260321-214513-768x346.png 768w, https:\/\/bitacora.eniac2000.com\/wp-content\/uploads\/2026\/03\/Screenshot_20260321-214513-1536x691.png 1536w, https:\/\/bitacora.eniac2000.com\/wp-content\/uploads\/2026\/03\/Screenshot_20260321-214513-2048x922.png 2048w\" sizes=\"auto, (max-width: 1024px) 100vw, 1024px\" \/><figcaption class=\"wp-element-caption\">Ejemplo de una rejilla de vuelo sobre una misi\u00f3n<\/figcaption><\/figure>\n\n\n\n<h2 class=\"wp-block-heading\">Primero lo primero: comprobar que el terreno est\u00e1 listo<\/h2>\n\n\n\n<p>Antes de escribir ni una l\u00ednea de c\u00f3digo, empec\u00e9 por una tarea que se suele subestimar, pero que es clave para que el proyecto transcurra de manera fluida: verificar que el entorno de desarrollo est\u00e1 en condiciones. En este caso, el entorno era un Mac Mini M4 con Android Studio. Hace ya algunos meses hab\u00eda intentado dar algunos pasos en este \u00e1mbito, y aunque el resultado fue infructuoso, el entorno estaba b\u00e1sicamente listo, Por ello, la comprobaci\u00f3n se redujo a ejecutar una serie de comandos en la terminal.<\/p>\n\n\n\n<p>El diagn\u00f3stico fue r\u00e1pido y, afortunadamente, satisfactorio:<\/p>\n\n\n\n<figure class=\"wp-block-table\"><table><thead><tr><th>Componente<\/th><th>Versi\u00f3n encontrada<\/th><\/tr><\/thead><tbody><tr><td>JDK<\/td><td>OpenJDK 17.0.18 (Homebrew)<\/td><\/tr><tr><td>Android SDK<\/td><td>APIs 33, 34, 35 y 36 disponibles<\/td><\/tr><tr><td>Build Tools<\/td><td>34.0.0 a 36.1.0<\/td><\/tr><tr><td>ADB<\/td><td>v36.0.0<\/td><\/tr><tr><td>Emulador<\/td><td>Pixel 7 Pro disponible<\/td><\/tr><\/tbody><\/table><\/figure>\n\n\n\n<p>No fue necesario instalar nada adicional. Un buen comienzo. Pero s\u00ed surgi\u00f3 una primera lecci\u00f3n de esas que conviene apuntar: Android necesita <strong>JDK 17<\/strong>, no 21. La versi\u00f3n 21 genera incompatibilidades con el plugin de Gradle actual, algo que no es evidente hasta que el proyecto empieza a dar errores oscuros en la compilaci\u00f3n. La versi\u00f3n 17 es, a d\u00eda de hoy, la que ofrece mayor compatibilidad.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Creando el proyecto desde cero (sin el wizard de Android Studio)<\/h2>\n\n\n\n<p>La decisi\u00f3n de no usar el asistente de creaci\u00f3n de proyectos de Android Studio fue deliberada. Tras una fase previa de validaci\u00f3n de las capacidades de Claude, el asistente propuso crear cada fichero de configuraci\u00f3n a mano, explicando qu\u00e9 hace cada uno. Esto era algo en lo que hab\u00eda sido espec\u00edfico con el asistente de IA: el objetivo era que yo entendiera la estructura, no que simplemente apareciera una app ya montada. Una aproximaci\u00f3n de tutor m\u00e1s que de desarrollador.<\/p>\n\n\n\n<p>Los cuatro ficheros clave que dan vida a un proyecto Android moderno son:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong><code>settings.gradle.kts<\/code><\/strong>: define de d\u00f3nde obtener plugins y dependencias (repositorios de Google y Maven Central) y qu\u00e9 m\u00f3dulos forman el proyecto.<\/li>\n\n\n\n<li><strong><code>build.gradle.kts<\/code> (ra\u00edz)<\/strong>: declara los plugins globales con sus versiones. Aqu\u00ed vive el Android Gradle Plugin, el compilador de Kotlin, el plugin de Compose y Hilt para inyecci\u00f3n de dependencias.<\/li>\n\n\n\n<li><strong><code>gradle.properties<\/code><\/strong>: par\u00e1metros de configuraci\u00f3n de JVM y flags como <code>android.useAndroidX=true<\/code>.<\/li>\n\n\n\n<li><strong><code>app\/build.gradle.kts<\/code><\/strong>: el fichero m\u00e1s importante. Define el SDK objetivo (API 35), el SDK m\u00ednimo (API 26, requerido por DJI) y todas las dependencias organizadas por categor\u00edas.<\/li>\n<\/ul>\n\n\n\n<p>El proceso no estuvo exento de peque\u00f1os tropiezos. Uno de los primeros fue intentar generar el Gradle Wrapper con la versi\u00f3n 9.4 instalada globalmente, que rechaz\u00f3 el comando porque el directorio <code>app\/<\/code> no exist\u00eda todav\u00eda. Soluci\u00f3n trivial (<code>mkdir -p app<\/code>), pero ilustrativa de lo que pasa cuando das pasos en el orden incorrecto. Otro: la sintaxis <code>dependencyResolution<\/code> que aparece en la documentaci\u00f3n m\u00e1s reciente de Gradle es de la versi\u00f3n 9.x; como el wrapper usa 8.11.1, hay que usar <code>dependencyResolutionManagement<\/code>. Detalles peque\u00f1os, pero que con el plugin de IA bien puesto encima se resuelven al vuelo.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">La arquitectura elegida: MVVM<\/h2>\n\n\n\n<p>Antes de entrar en las pantallas, conviene entender el patr\u00f3n de arquitectura sobre el que se construye toda la aplicaci\u00f3n: <strong>MVVM<\/strong> (Model-View-ViewModel), el recomendado por Google para apps Android modernas.<\/p>\n\n\n\n<p>La idea central es sencilla: los datos fluyen hacia abajo y el estado fluye hacia arriba. La interfaz de usuario (Jetpack Compose) observa un <code>StateFlow<\/code> que emite el ViewModel. El ViewModel llama al Repositorio para obtener o guardar datos. El Repositorio habla con la base de datos (Room\/SQLite). Nadie se salta la cadena.<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>UI (Compose) \u2192 observa StateFlow\n     \u2191                \u2193\nViewModel \u2190\u2192 MissionCalculator\n     \u2193\nRepository\n     \u2193\nRoom \/ SQLite<\/code><\/pre>\n\n\n\n<p>Esta separaci\u00f3n tiene una ventaja pr\u00e1ctica inmediata: cuando el usuario cambia un slider de par\u00e1metros, la rejilla de vuelo se recalcula y el mapa se actualiza autom\u00e1ticamente, sin que la pantalla sepa nada de c\u00f3mo funciona el algoritmo por debajo. Todo el estado vive en una \u00fanica <code>data class<\/code> inmutable (<code>MapUiState<\/code>) que el ViewModel actualiza con cada acci\u00f3n del usuario.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Primera pantalla: el mapa<\/h2>\n\n\n\n<p>La pantalla principal de la app es un mapa interactivo. Para implementarlo, la elecci\u00f3n fue <strong>MapLibre GL<\/strong> en lugar de Google Maps, y la raz\u00f3n es pragm\u00e1tica: MapLibre es open source, no requiere API key, no tiene l\u00edmites de uso y permite a\u00f1adir capas WMS personalizadas (como el catastro o las ortofotos del IGN) sin restricciones. Para una aplicaci\u00f3n de fotogrametr\u00eda que va a usar capas cartogr\u00e1ficas espec\u00edficas de Espa\u00f1a, esto es determinante.<\/p>\n\n\n\n<p>El \u00fanico inconveniente es que MapLibre es una librer\u00eda de Views cl\u00e1sicas (pre-Compose), as\u00ed que hay que envolverla en un <code>AndroidView<\/code> para integrarla en Jetpack Compose. Es el puente oficial entre los dos sistemas, y funciona bien una vez que entiendes que el mapa se configura en el callback <code>getMapAsync<\/code>, no fuera de \u00e9l.<\/p>\n\n\n\n<p>Los tiles de OpenStreetMap se definen como un estilo JSON inline, sin necesidad de ficheros externos ni servicios de pago:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>private val OSM_STYLE_JSON = \"\"\"\n{\n  \"version\": 8,\n  \"sources\": {\n    \"osm-raster\": {\n      \"type\": \"raster\",\n      \"tiles\": &#91;\"https:\/\/tile.openstreetmap.org\/{z}\/{x}\/{y}.png\"],\n      \"tileSize\": 256\n    }\n  },\n  \"layers\": &#91;{\n    \"id\": \"osm-raster-layer\",\n    \"type\": \"raster\",\n    \"source\": \"osm-raster\"\n  }]\n}\n\"\"\".trimIndent()<\/code><\/pre>\n\n\n\n<h2 class=\"wp-block-heading\">Dibujando el \u00e1rea de inter\u00e9s<\/h2>\n\n\n\n<p>Con el mapa en pantalla, el siguiente paso era permitir que el usuario defina el \u00e1rea sobre la que va a volar. La soluci\u00f3n es un modo de dibujo que se activa al pulsar el bot\u00f3n \u00abNueva Misi\u00f3n\u00bb: a partir de ese momento, cada toque en el mapa a\u00f1ade un v\u00e9rtice al pol\u00edgono.<\/p>\n\n\n\n<p>Este fue el primer momento en que el patr\u00f3n MVVM se puso a trabajar de verdad. Al tocar el mapa, la pantalla llama a <code>viewModel.addVertex(latLng)<\/code>. El ViewModel actualiza el estado. Un <code>LaunchedEffect<\/code> en la pantalla detecta el cambio y actualiza el <code>GeoJsonSource<\/code> de MapLibre. El mapa se redibuja. Todo esto sucede de forma reactiva, sin que ninguna capa sepa lo que hace la otra.<\/p>\n\n\n\n<p>Para la barra de herramientas del modo dibujo (Cancelar \/ Deshacer \/ Confirmar), se us\u00f3 <code>AnimatedVisibility<\/code> con transiciones de deslizamiento vertical, de forma que aparece y desaparece suavemente al entrar y salir del modo dibujo.<\/p>\n\n\n\n<p>Las capas del pol\u00edgono en MapLibre siguen el modelo Source + Layer: se crean vac\u00edas al arrancar el mapa y despu\u00e9s solo se actualizan los datos del GeoJsonSource, que es mucho m\u00e1s eficiente que recrear las capas en cada cambio.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">El motor fotogram\u00e9trico<\/h2>\n\n\n\n<p>Con el pol\u00edgono definido, la aplicaci\u00f3n necesita calcular la rejilla de vuelo. Este es el n\u00facleo de la app, y la parte m\u00e1s interesante desde el punto de vista t\u00e9cnico.<\/p>\n\n\n\n<p>Todo empieza con el <strong>GSD<\/strong> (Ground Sample Distance), la m\u00e9trica fundamental en fotogrametr\u00eda: cu\u00e1ntos cent\u00edmetros del mundo real representa cada p\u00edxel de la imagen. La f\u00f3rmula depende de la altura de vuelo, las caracter\u00edsticas del sensor y la distancia focal:<\/p>\n\n\n\n<pre class=\"wp-block-code\"><code>GSD = (altura \u00d7 ancho_sensor_mm) \/ (focal_mm \u00d7 ancho_imagen_px) \u00d7 100<\/code><\/pre>\n\n\n\n<p>Con el DJI Mini 3 Pro a 60 metros de altura, el resultado es aproximadamente <strong>1,07 cm\/px<\/strong>. Una resoluci\u00f3n m\u00e1s que suficiente para fotogrametr\u00eda de precisi\u00f3n.<\/p>\n\n\n\n<p>A partir del GSD se calcula el footprint (la superficie que cubre cada fotograf\u00eda en el suelo) y de ah\u00ed el espaciado entre fotos y entre pasadas, aplicando los porcentajes de overlap configurados por el usuario.<\/p>\n\n\n\n<p>El algoritmo de generaci\u00f3n de la rejilla de waypoints sigue estos pasos:<\/p>\n\n\n\n<ol class=\"wp-block-list\">\n<li>Proyectar los v\u00e9rtices del pol\u00edgono a coordenadas locales en metros (proyecci\u00f3n equirectangular, suficientemente precisa para \u00e1reas de menos de 1 km\u00b2).<\/li>\n\n\n\n<li>Rotar el pol\u00edgono para alinear el heading de vuelo con el eje X, simplificando el problema a l\u00edneas horizontales.<\/li>\n\n\n\n<li>Generar scan lines desde el m\u00ednimo al m\u00e1ximo Y, separadas por el spacing lateral.<\/li>\n\n\n\n<li>Para cada l\u00ednea, calcular las intersecciones con los bordes del pol\u00edgono.<\/li>\n\n\n\n<li>Colocar waypoints entre cada par de intersecciones, separados por el spacing frontal.<\/li>\n\n\n\n<li>Alternar la direcci\u00f3n en pasadas consecutivas (patr\u00f3n serpentina) para minimizar la distancia total.<\/li>\n\n\n\n<li>Rotar todo de vuelta al heading original y convertir a coordenadas geogr\u00e1ficas.<\/li>\n<\/ol>\n\n\n\n<p>Para misiones de modelo 3D, este proceso se repite con el heading girado 90\u00b0, generando el patr\u00f3n <em>cross-hatch<\/em> que permite capturar m\u00faltiples \u00e1ngulos de cada punto del terreno.<\/p>\n\n\n\n<p>Un detalle importante: las especificaciones del sensor del DJI Mini 3 Pro est\u00e1n precargadas en la app. La focal que se usa en los c\u00e1lculos es la <strong>focal real<\/strong> (6,7 mm), no la equivalente en full frame (24 mm), que es solo una referencia para fot\u00f3grafos acostumbrados a c\u00e1maras de formato completo.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Configuraci\u00f3n de par\u00e1metros y estimaciones en tiempo real<\/h2>\n\n\n\n<p>El panel de configuraci\u00f3n de la misi\u00f3n permite ajustar mediante sliders los par\u00e1metros de vuelo: altura (1\u2013120 m), overlap frontal (60\u201390%), overlap lateral (50\u201385%), velocidad (1\u201312 m\/s) y orientaci\u00f3n de las pasadas (0\u2013170\u00b0). Cada cambio dispara inmediatamente un rec\u00e1lculo del motor fotogram\u00e9trico y actualiza la visualizaci\u00f3n en el mapa.<\/p>\n\n\n\n<p>Las estimaciones que muestra la app en tiempo real son:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>GSD<\/strong>: resoluci\u00f3n de cada p\u00edxel en cent\u00edmetros.<\/li>\n\n\n\n<li><strong>N\u00famero de fotos<\/strong>: total de disparos que realizar\u00e1 el dron.<\/li>\n\n\n\n<li><strong>Distancia total<\/strong>: kil\u00f3metros de vuelo.<\/li>\n\n\n\n<li><strong>Duraci\u00f3n estimada<\/strong>: en minutos, seg\u00fan la velocidad configurada.<\/li>\n\n\n\n<li><strong>Bater\u00eda necesaria<\/strong>: porcentaje estimado, con aviso si supera el 90%.<\/li>\n<\/ul>\n\n\n\n<p>Esta retroalimentaci\u00f3n inmediata es especialmente \u00fatil en campo: puedes ajustar altura y overlaps hasta encontrar el equilibrio entre resoluci\u00f3n, cobertura y autonom\u00eda que necesitas para cada misi\u00f3n concreta.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Guardar y cargar misiones<\/h2>\n\n\n\n<p>El \u00faltimo bloque de la Fase 1 fue la persistencia local de misiones, implementada con <strong>Room<\/strong>, el ORM de SQLite para Android.<\/p>\n\n\n\n<p>El desaf\u00edo t\u00e9cnico aqu\u00ed es que Room solo puede guardar tipos primitivos de forma directa, y los datos de una misi\u00f3n incluyen una lista de coordenadas (<code>List&lt;LatLng&gt;<\/code>) y un objeto de configuraci\u00f3n (<code>MissionConfig<\/code>). La soluci\u00f3n fue serializar ambos como JSON en columnas de texto. Una misi\u00f3n completa ocupa una sola fila en la base de datos, lo que simplifica mucho las operaciones de lectura y escritura.<\/p>\n\n\n\n<p>El DAO expone un <code>Flow&lt;List&lt;MissionEntity&gt;&gt;<\/code>: cuando se guarda o elimina una misi\u00f3n, la lista se actualiza autom\u00e1ticamente en la pantalla sin necesidad de refrescar nada manualmente. Es la naturaleza reactiva de Kotlin Coroutines y Room trabajando juntos.<\/p>\n\n\n\n<p>La interfaz de gesti\u00f3n de misiones se compone de un di\u00e1logo para nombrarlas (con fecha sugerida por defecto) y un <em>bottom sheet<\/em> con la lista completa, donde cada elemento muestra nombre, fecha y un bot\u00f3n de eliminar.<\/p>\n\n\n\n<h2 class=\"wp-block-heading\">Bugs y lecciones aprendidas<\/h2>\n\n\n\n<p>Ning\u00fan proceso de desarrollo est\u00e1 exento de problemas, y este no fue una excepci\u00f3n. Los m\u00e1s rese\u00f1ables de esta primera fase:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li><strong>API de ubicaci\u00f3n de MapLibre<\/strong>: la versi\u00f3n 11.x cambi\u00f3 la forma de activar el componente de localizaci\u00f3n. El m\u00e9todo antiguo (<code>activateLocationComponent(context)<\/code>) no compila; hay que usar <code>LocationComponentActivationOptions.builder()<\/code>.<\/li>\n\n\n\n<li><strong>Crash con String.format()<\/strong>: la app se cerraba al arrancar con <code>UnknownFormatConversionException<\/code>. El culpable era el car\u00e1cter <code>%<\/code> en una cadena de formato Kotlin. La soluci\u00f3n es separar el <code>String.format()<\/code> del literal <code>%<\/code>, nunca mezclarlos en la misma cadena.<\/li>\n\n\n\n<li><strong>Pol\u00edgono que persiste al eliminar una misi\u00f3n<\/strong>: al borrar la misi\u00f3n que estaba activa en el mapa, el pol\u00edgono segu\u00eda visible. La soluci\u00f3n fue detectar si el ID de la misi\u00f3n eliminada coincide con la cargada en pantalla, y en ese caso resetear el estado completo.<\/li>\n<\/ul>\n\n\n\n<h2 class=\"wp-block-heading\">El resultado<\/h2>\n\n\n\n<p>Al cierre de la Fase 1, la aplicaci\u00f3n era ya completamente funcional para la planificaci\u00f3n de misiones:<\/p>\n\n\n\n<ul class=\"wp-block-list\">\n<li>Mapa interactivo con tiles de OpenStreetMap.<\/li>\n\n\n\n<li>Dibujo de pol\u00edgonos con barra de herramientas animada.<\/li>\n\n\n\n<li>Configuraci\u00f3n de todos los par\u00e1metros de vuelo con sliders.<\/li>\n\n\n\n<li>Selector de tipo de misi\u00f3n: 2D (ortomosaico) o 3D (cross-hatch).<\/li>\n\n\n\n<li>C\u00e1lculo autom\u00e1tico de grilla de waypoints con visualizaci\u00f3n en tiempo real.<\/li>\n\n\n\n<li>Estimaciones de GSD, fotos, duraci\u00f3n y bater\u00eda.<\/li>\n\n\n\n<li>Patr\u00f3n serpentina y doble grilla para misiones 3D.<\/li>\n\n\n\n<li>Guardado, carga y borrado de misiones con Room\/SQLite.<\/li>\n<\/ul>\n\n\n\n<p>Todo ello desarrollado de forma guiada con Claude Code, entendiendo cada decisi\u00f3n de arquitectura y cada componente del c\u00f3digo. En el pr\u00f3ximo art\u00edculo hablaremos de la Fase 2: c\u00f3mo se integr\u00f3 el DJI Mobile SDK v5, los problemas (bastante gordos) que aparecieron intentando hacer volar el dron de forma aut\u00f3noma y c\u00f3mo se resolvieron.<\/p>\n","protected":false},"excerpt":{"rendered":"<div class=\"seriesmeta\">Esta entrada es la parte 2 de 12 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 expliqu\u00e9 el origen de este proyecto,<\/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,1954,537,1020],"series":[1957],"class_list":["post-11689","post","type-post","status-publish","format-standard","hentry","category-generado-con-ia","category-informatica","tag-android","tag-claude-code","tag-dji","tag-dji-mini-3-pro","tag-mac-mini-m4","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\/11689","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=11689"}],"version-history":[{"count":1,"href":"https:\/\/bitacora.eniac2000.com\/index.php?rest_route=\/wp\/v2\/posts\/11689\/revisions"}],"predecessor-version":[{"id":11690,"href":"https:\/\/bitacora.eniac2000.com\/index.php?rest_route=\/wp\/v2\/posts\/11689\/revisions\/11690"}],"wp:attachment":[{"href":"https:\/\/bitacora.eniac2000.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=11689"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/bitacora.eniac2000.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=11689"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/bitacora.eniac2000.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=11689"},{"taxonomy":"series","embeddable":true,"href":"https:\/\/bitacora.eniac2000.com\/index.php?rest_route=%2Fwp%2Fv2%2Fseries&post=11689"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}