Case study
LTA+
Un motor de cálculo de los 70 incrustado en una app militar moderna, con requisitos críticos, mucha restricción y cero margen para hacer el tonto.
Contexto
Airbus Military necesitaba sustituir una herramienta legacy de cálculo aeroespacial por un sistema moderno que pudiera usarse en operaciones militares, muchas veces sin conexión a internet, trabajando con datos sensibles y con requisitos de alta fiabilidad.
El proyecto consistió en la creación de una nueva aplicación escalable y mantenible, sobre la que después se han construido múltiples evolutivos y herramientas internas relacionadas.
Diseñé y desarrollé la nueva solución desde cero para que funcionara de forma consistente en Windows e iPad, y diseñé toda la estrategia de automatización de pruebas para garantizar la calidad del software sin depender solo de tests manuales.
Tecnología
La aplicación se construyó principalmente para funcionar en iPads. Posteriormente, se dio soporte a sistemas Windows. Los dispositivos soportados son:
-
iPad, iPad Pro y iPad Mini. Se hicieron pruebas en en entorno macOS de escritorio con éxito, pero nunca se llevó a producción para estos dispositivos.
- Microsoft Surface, de la mano de Electron.
En cuanto al software, la aplicación se construyó utilizando estas tecnologías principales:
-
Ionic, Angular, Capacitor y TypeScript para el frontal y la capa híbrida.
-
Swift (iOS) y Node.js (Windows) para el desarrollo de plugins nativos.
-
Python para scripts de automatización y procesos de encriptación.
Durante el desarrollo también fue necesario trabajar con C/Objective-C, ya que el motor de cálculo estaba escrito originalmente en Fortran y transpilado a C. Uno de nuestros plugins propios ejecutaba estas funciones en C desde Swift. No fue necesario programar C, pero sí entender lo suficiente el código como para mapear correctamente los parámetros desde el plugin hacia el motor de cálculo. Esto lo explicaré con más detalle en la sección de retos técnicos.
Arquitectura técnica
La aplicación sigue una arquitectura monolítica multi-capa que se ejecuta íntegramente en local. Excepcionalmente, el proceso de importación ejecuta operaciones SFTP contra sistemas externos, alojados en redes locales (para que nos entendamos, no puedes llegar si no estás en la base militar presencialmente).
A más bajo nivel se encuentra el motor de cálculo aeronáutico (llamado CAPS Engine), desarrollado originalmente en Fortran y transpilado a C. A partir de ahí se compila como librería estática/dinámica (.a/.dll para iOS/Windows). Un plugin de Capacitor se encarga de mapear y lanzar a las operaciones a través de Node.js y Swift/Objective-C.
En la capa intermedia, Capacitor actúa como wrapper entre el frontal (Angular) y las implementaciones nativas. Gracias a esto, la misma base de código funciona tanto en Electron (Windows) como en iPad (iOS).
Al tratarse de una aplicación militar, debe operar en entornos hostiles, con conectividad limitada o directamente sin conexión. Por eso:
-
Toda la lógica se ejecuta en local.
La aplicación ma ntiene una base de datos local.
-
La carga de datos se realiza previamente “en tierra”, en entornos seguros, mediante SFTP.
-
Tras un proceso de importación complejo, la información se almacena en: ficheros JSON, base de datos SQLite y base de datos DexieJS.
La arquitectura fue evolucionando de forma orgánica, condicionada por los requerimientos específicos del proyecto y, sobre todo, por el motor de cálculo heredado: una evolución de un motor aeronáutico utilizado en aviones de los años 70 y 80, que imponía muchas restricciones sobre cómo debíamos diseñar el resto de capas.
Seguridad y datos
Siendo una aplicación militar, la seguridad era un requisito central. Por ello, el software funciona solo en entorno locals, sin conexiones externas durante la operación normal.
La carga de datos se realiza exclusivamente en entornos seguros, mediante SFTP. Para ello se desarrolló un plugin de Capacitor específico para SFTP, con gran complejidad añadida por la falta de librerías maduras y documentación clara en iOS.
Además, se implantó un formato de datos estricto, alineado con estándares aeronáuticos, lo cual hizo el proceso de importación más complejo a la par que muy eficaz para detectar inconsistencias en los datos entrantes.
El proceso de importación valida el formato de cada campo, verifica su correcta estructura y garantiza un almacenamiento coherente en la app.
Para la validación de licencias de clientes se diseñó un sistema basado en ficheros de licencia encriptados con variantes de algoritmos estándar + codificación Base64. Se utiliza un script de Python para generar licencias. En la app, un servicio de desencriptado (Angular) para validar las licencias en tiempo de ejecución.
En cuanto a la ingestión de datos desde SFTP:
-
Se descarga un fichero comprimido protegido por contraseña.
-
El usuario introduce la contraseña previo a proceder con la importación: descompresión, validación e ingestión de datos.
Una vez validados, los datos se reparten entre:
Ficheros JSON estáticos
-
Una base de datos SQLite consumida mediante un plugin de Ionic
-
Y una Inner DB en Angular consumida con una implementación propia basada en DexieJS + observables, asegurando reactividad en la lectura de datos.
Comunicaciones multicapa
Este fue uno de los retos más duros del proyecto. El objetivo era claro pero nada trivial: conectar la app híbrida con el motor de cálculo en C (CAPS) y entender cómo alimentar y leer esa “caja negra”.
La documentación era muy mínima y las primeras pruebas no funcionaban. Durante un tiempo dependimos de un plugin Cordova legacy que “misteriosamente” hacía las llamadas correctas, pero sin explicar cómo. A partir de ahí tocó hacer ingeniería inversa y reconstruir la lógica con algo más sostenible.
Cuando el proyecto maduró y ya conocíamos mejor el sistema, pudimos avanzar:
-
Para Windows (Electron) se generó una versión del motor como .dll y se creó una capa nativa específica que hacía de puente con esa librería: recibía los parámetros desde la app, llamaba a la DLL y devolvía los resultados.
-
En Angular, se usaban servicios distintos según la plataforma (Electron | iPad), implementados utilizando un patrón Factory, de forma que el resto del código no tuviera que preocuparse de los detalles de cada entorno.
-
El motor trabajaba con un array de números plano, donde cada posición correspondía a una variable. Ese mapping se hizo prácticamente “a mano”: había parámetros obligatorios incluso cuando no aplicaban a ciertos cálculos y valores especiales que había que respetar para que el motor no fallase.
-
La salida funcionaba igual: otro array donde cada índice tenía un significado concreto. Hasta que no descubrías qué representaba cada posición, era imposible interpretar los resultados.
Todo esto se hizo con documentación muy limitada y fragmentada, apoyándonos en el comportamiento del plugin antiguo y en muchas pruebas controladas. Fue un proceso largo, pero permitió que la app híbrida hablara de forma fiable con un motor crítico que nadie quería tocar, y sentó las bases para poder mantenerlo a futuro sin depender de “magia negra”.
Importación de datos
Otro reto importante fue diseñar el proceso de importación de datos. Yo definí la primera versión del flujo, que luego fue creciendo hasta convertirse casi en un subsistema complejísimo.
El problema, en resumen, era este:
-
Cargar una lista de aeropuertos y sus propiedades desde un XML generado por un software legacy.
-
Cargar y validar una base de datos SQLite con la información técnica necesaria para los cálculos: fases del vuelo, características de la flota, configuración específica de cada avión, modificaciones, etc.
-
Detectar y corregir datos incoherentes (pistas con orientaciones > 360º, pistas invertidas, valores imposibles, etc.).
-
Dejarlo todo en un formato que la aplicación pudiera consumir de forma rápida y consistente.
Para el XML diseñé un flujo de importación muy orientado a objetos:
-
El XML se parseaba a un objeto intermedio.
Ese objeto alimentaba una clase AirportList.
-
Cada aeropuerto se construía como instancia de Airport.
-
Cada aeropuerto creaba a su vez sus pistas (Runway[]), ajustando y corrigiendo datos en el propio constructor.
Al final del proceso teníamos en memoria un modelo limpio y coherente, que se volcaba a un único fichero JSON local. Este enfoque tenía varias ventajas:
-
Podíamos validar y limpiar los datos antes de escribir nada en disco.
-
En caso de fallo durante la importación, la app seguía funcionando con la lista anterior.
-
La persistencia se reducía a un solo “commit” final, simple y fácil de razonar.
El flujo de SQLite era diferente:
-
Primero se copiaba la base de datos a nivel de fichero usando el plugin de FileSystem de Ionic.
-
Después se verificaba su integridad y coherencia frente a los datos de aeropuertos importados desde el XML.
Con el tiempo, el sistema de importación fue incorporando más tipos de datos, más reglas de validación y más pasos intermedios. Mi aportación evolucionó hacia refactorizar y aplicar principios SOLID: separar responsabilidades, aislar reglas de negocio y hacer que la lógica fuese más legible, testeable y sencilla de extender a futuro, sin tener que reescribir el núcleo cada vez que entraba un nuevo requisito.
Metodología y documentación
El equipo seguía una adaptación propia de SCRUM, ajustada a las particularidades de Airbus y al tipo de software. Se utilizaban sprints de un mes, lo que ayudaba a proteger al equipo del goteo continuo de requerimientos informales y aglutinar los nuevos requisitos para planificar el siguiente sprint con cabeza.
Fue un enfoque propuesto por el Team Lead que funcionó especialmente bien en este contexto.
En cuanto a documentación, no existía un proceso formal y exhaustivo. La mayor parte de la documentación se centraba en código y en operativa compleja, creada por iniciativa de los propios desarrolladores. Algunos flujos críticos, como la arquitectura de testing E2E, quedaron documentados con alto grado de detalle.
Personalmente, abogo por evitar la sobredocumentación: documentar lo crítico, lo contraintuitivo y lo que aporta valor real al mantenimiento, pero no convertir cada detalle del código en papeleo que encarece el desarrollo sin aportar retorno.
Impacto
El nuevo ecosistema permitió al equipo moverse más rápido y ganar peso dentro de la organización: los evolutivos dejaron de ser proyectos pesados para convertirse en iteraciones mucho más ágiles sobre una base estable.
La capa de pruebas automáticas reduce potencialmente los errores en producción y, sobre todo, liberó de mucho tiempo al equipo de ingenieros: ahora pueden lanzar baterías de tests complejos sin saber programar y con evidencias claras de qué se ha probado en cada versión (vídeos, capturas, cobertura, etc).
En la práctica, el sistema ha reducido el trabajo manual repetitivo, ha disminuido el riesgo de fallo humano y ha hecho mucho más cómodo evolucionar una herramienta crítica en un entorno altamente regulado.