Airbus Military
Sistema híbrido de cálculo militar aeroespacial
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 híbrida, 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 para que funcionara de forma
consistente en Windows e iOS, y levanté toda la
estrategia de automatización de pruebas para garantizar la calidad
del software sin depender solo de tests manuales.
Entre las piezas clave que construí se encuentran:
-
Plugins nativos para aplicaciones híbridas
(iOS/Electron) que permiten acceder a capacidades específicas de
cada plataforma.
-
Un sistema de comunicación de
datos entre módulos
que facilita añadir nuevas funcionalidades sin romper las
existentes.
-
Una arquitectura de testing E2E sobre
dispositivos reales, con analítica integrada y ejecución guiada
para personal no técnico.
-
Procesos de importación y despliegue de cargas
complejas, con cifrado y descifrado de datos alineado con los
requisitos de seguridad del entorno.
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 redujo de forma significativa los
errores en producción y, sobre todo,
liberó tiempo al equipo de ingenieros en operaciones de
vuelo:
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.
LTA+
Aplicación Híbrida
Si tuviera que resumir este proyecto en una frase, sería algo así
como:
un motor de cálculo de los 70 incrustado en una app híbrida
moderna, con requisitos militares, mucha restricción y cero
margen para hacer el tonto.
No fue un proyecto “de libro”: hubo mucho código legacy,
herramientas rotas, ciberataques, documentación muy escasa y
decisiones que había que tomar con información incompleta. Pero
precisamente por eso fue clave para mí.
Me obligó a salir mi rol de “frontend Angular” y
pensar en el sistema completo: motor heredado,
plugins nativos, seguridad, flujo de datos, UX real de los
usuarios.
Aprendí a tomar ownership cuando nadie te da el
manual: montar infra improvisada, diseñar procesos de importación,
proponer una arquitectura técnica E2E ante un problema que lleva
años enquistado.
Confirmé que prefiero
resolver problemas reales (aunque estén llenos de
barro) antes que vivir en entornos perfectos (SPOILER: no
existen).
¿Repetiría todo tal cual? No. Hoy plantearía algunos bloques de
otra forma, modularizaría antes ciertas partes y atacaría la
automatización de testing mucho antes en el ciclo de vida del
proyecto. Pero este trabajo marcó un antes y un después en cómo
entiendo mi papel: no solo como alguien que escribe código, sino
como alguien que arma soluciones complejas, con
restricciones reales y responsabilidad sobre lo que llega al
usuario final.
Arquitectura
Monolítica & Multicapa
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),
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.
Plugins Nativos
CAPS, Filesystem, SFTP & ZIP
Durante el desarrollo tuvimos que afrontar varios desafíos que
implicaban interactuar con la capa nativa. Trataré de resumir los
plugins más importantes que fabricamos.
CAPS Cordova Plugin
En otras secciones ya he hablado del CAPS. Para resumir, es un
motor de cálculos aerespacial hecho en fortran y transpilado a
C.
Para utilizar el motor de cálculo desde Angular, heredamos un
plugin Cordova.
El plugin tenía en su interior el motor de cálculo, almacenado
como librería estática (caps.a para iOS). Desde
objective-c, se mapeaban las funciones con una
capa C. La capa C, a su vez, realizaba llamadas directamente
contra la librería estática y devolvía la información.
Este plugin fue uno de los retos técnicos más grandes del
proyecto, y hablo más en profundidad de él en las secciones de
Arquitectura y en
Comunicaciones Multicapa.
EFB Filesystem Plugin
Uno de los problemas recurrentes en este proyecto era que
queríamos
tratar el sistema de ficheros igual en iPad y en
Windows
(Electron), pero el plugin oficial de Filesystem no cubría bien el
caso desktop. Eso nos obligaba a escribir código condicional y
ramas específicas por plataforma, justo lo contrario a lo que
quieres en una app crítica.
Para solucionarlo, propuse un
fork del plugin oficial de Filesystem de Ionic y
añadimos una implementación específica para Electron. El objetivo
era muy simple:
Desde el punto de vista del código de negocio, trabajar con
ficheros debe ser igual en iOS que en Electron.
EFB SFTP Plugin
Para poder actualizar la app en entornos militares sin acceso a
internet “normal”, necesitábamos hablar con un
servidor SFTP: descargar cargas de datos, subir
reportes de configuración y hacerlo todo de forma controlada.
La solución fue crear un plugin SFTP propio, con
implementación nativa en
iOS/iPad (Swift + NMSSH) y su equivalente para
Electron (Node + ssh2-sftp-client).
EFB ZIP Plugin
Con las cargas de datos teníamos el problema de
descomprimir ficheros en el dispositivo. Ya
existían soluciones de la comunidad disponibles, pero tenían poco
soporte, y muchas no permitían la descompresión de ficheros
cifrados.
La falta de fiabilidad de los plugins disponibles, junto con que
muchos no soportaban Electron + iOS, nos llevó a construir nuestra
propia solución:
un plugin que permite comprimir y descomprimir ficheros con
contraseña opcional, para Electron e iOS.
Mi Rol
Funciones, metodoloía & documentación
Al inicio, era el único desarrollador del equipo junto con mi Team
Lead. Yo llegaba con un perfil centrado en Angular y heredamos una
aplicación legacy con código espagueti, difícil de mantener y
tecnológicamente obsoleta. Tras varios intentos de iterar sobre
esa base, llegamos a la conclusión de que sería más eficiente
sustituirla por un nuevo desarrollo desde cero.
En unos 3 meses conseguimos una réplica funcional de la aplicación
original, pero con un código mucho más escalable y mantenible. La
nueva implementación mejoró notablemente la capacidad de respuesta
de la app y permitió una UX/UI más dinámica y moderna.
Con el tiempo fueron incorporándose nuevos compañeros que también
aportaron mucho al producto. En paralelo, mi rol fue cambiando:
pasé de desarrollador frontend especializado en Angular a
desarrollador de aplicaciones híbridas (Ionic + Capacitor), y me
convertí en uno de los desarrolladores de referencia del equipo.
Mis compañeros solían plantearme cuestiones sobre implementaciones
y de temas técnicos.
Mi Team Lead empezó a consultarme de forma recurrente sobre
problemas técnicos, alternativas de software e incluso algunas
cuestiones de organización del equipo. En la fase final del
proyecto llegué a cubrir su rol durante su baja por paternidad. En
ese periodo seguí siendo principalmente un perfil técnico, pero
también asumí responsabilidades de coordinación: organización del
trabajo, reparto de tareas (sobre todo de soporte y mantenimiento)
y soporte al equipo en la toma de decisiones.
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
rquitectura 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.
Tecnología
Dispositivos y lenguajes
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
- Dispositivos Windows en general
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.
Tests Automáticos E2E
Arquitectura Técnica
El testing tradicional en Angular (Jasmine/Karma) se aplicó solo a
módulos y funcionalidades críticas. La validación funcional de los
cálculos y la operativa recaía en un
equipo de ingenieros, ya que muchos detalles
escapaban al conocimiento del equipo de desarrollo.
Arquitectura Técnica
Las pruebas de regresión se hacían siempre a mano, tanto por parte
del equipo técnico como por los ingenieros en operaciones de
vuelo. Eran procesos largos, repetitivos y con mucha exposición al
error humano.
Quise atacar este problema por mi cuenta y, durante mi tiempo
libre, estuve investigando herramientas hasta dar con una
combinación viable: Appium + WebdriverIO, ejecutando tests sobre
la app real en iPad y en entorno de escritorio. Monté la base del
proyecto en TypeScript + Jasmine para no sacar al
equipo de su zona de confort.
El reto más delicado fue conseguir que los tests corrieran en
dispositivos iOS sin disponer de cuenta de pago de Apple, lo que
implicó varios workarounds y un procedimiento muy concreto.
Documenté todo el proceso en una guía interna para que el resto
del equipo pudiera replicarlo sin problemas.
A partir de ahí diseñé la arquitectura técnica con una idea clara:
los tests debían poder ser definidos por personal no
técnico.
La solución fue basarlos en ficheros CSV:
-
Cada fila del CSV representa un caso de prueba, con entradas y
salidas esperadas.
-
El sistema lee el CSV, genera los tests de forma dinámica y
ejecuta los cálculos en la app nativa.
-
Los resultados que aparecen en la pantalla de la app se comparan
automáticamente con los valores esperados y se marcan como
OK/KO.
Con este modelo, los especialistas en operaciones de vuelo pueden
crear y mantener sus baterías de pruebas directamente desde Excel,
sin tocar código. Solo con esto ya conseguimos reducir de forma
muy clara el tiempo dedicado a testing manual y eliminar gran
cantidad de trabajo mecánico.
Para que también aportara valor a perfiles de gestión y
certificación, evolucioné la solución con:
-
Generación automática de informes, con un
dashboard donde se ve el número de casos ejecutados, porcentaje
de éxito y detalle de los fallos.
-
Capturas de pantalla y vídeos de cada ejecución, configurables, que sirven como evidencia para auditorías y
para los procesos de subida a producción.
En la práctica, el sistema permite:
- Cargar un CSV exportado desde Excel.
- Lanzar una campaña de tests sobre la app real.
-
Obtener un informe visual con resultados, métricas y evidencias
(imágenes/vídeo).
Esto tuvo un impacto directo en el equipo de operaciones de vuelo:
pasaron de tener que validar a mano, caso a caso, durante horas o
días, a poder lanzar campañas completas de pruebas en cadena y
revisarlas de forma mucho más ágil y fiable, varias veces al mes.
Toda esta arquitectura fue iniciativa personal, desarrollada
completamente en mi tiempo libre antes de presentarla al equipo.
Seguridad & Datos
Software Militar
Siendo una aplicación militar, la seguridad era un requisito
central. Por ello, el software funciona
solo en entorno local, 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
cierta 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
El motor de cálculo
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, la app usaba servicios distintos
según la plataforma (Electron | iPad), seleccionados mediante un
patrón tipo 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
XML, JSON, SQLite & DexieJS
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.
Ciberataque
Linux & Git
Pocos meses después de incorporarme, un ciberataque tumbó todos
los repositorios y sistemas de la empresa (Akkodis, antes Akka
Consulting). Nuestro equipo quedó sin herramientas para
coordinarse ni para trabajar con el código.
Hablando con mi Team Lead buscamos una solución para
restaurar la continuidad de desarrollo:
-
En un equipo disponible monté un servidor Linux con arranque
dual Windows / Linux.
-
Instalé Ubuntu Server con un entorno gráfico
ligero (XFCE).
-
Desplegué una instancia de GitLab Community.
-
Publiqué el servicio a través de mi red doméstica, abriendo los
puertos necesarios para acceso externo.
De esta forma el equipo volvió a tener un repositorio remoto desde
el que trabajar mientras los sistemas corporativos estaban caídos.
Más adelante, mi Team Lead se hizo cargo de la máquina y le asignó
una DNS adecuada para disponer de un entorno más seguro hasta que
todo volvió a la normalidad.
No era mi especialidad, pero me permitió aprender y, sobre todo,
evitar que el proyecto se quedara bloqueado.