Tohum

Playbook de desarrollo de productos IoT de extremo a extremo - Etapa 4: Desarrollo de firmware y software cloud

Ilustración: un microcontrolador intercambiando datos con una nube con engranajes

La Etapa 4 mueve el producto al lado derecho de la V: implementación y pruebas. En el modelo en V clásico de desarrollo de producto, el lado izquierdo trata de diseño (que abordamos en etapas anteriores); aquí desarrollamos el firmware (el software que corre en tu dispositivo IoT) y el software cloud (servidores, bases de datos, APIs, interfaces de usuario) que juntos dan vida a tu producto. A diferencia del desarrollo de hardware, que es relativamente inflexible una vez impreso en una placa de circuito, el desarrollo de software es altamente iterativo y ágil. De hecho, puede que ya escribieras partes del firmware en la Etapa 3 mientras probabas tu diseño de hardware. Ahora es el momento de acelerarlo. El software es como la arcilla —fácil de moldear y cambiar sobre la marcha—, así que podemos iterar rápido y refinar continuamente. Esta etapa correrá en paralelo con cualquier trabajo de hardware restante, informándose mutuamente según haga falta.

La Etapa 4 puede pensarse en dos partes principales: primero planificar la arquitectura y el diseño del software (para sentar una base sólida), y luego un ciclo continuo de sprints de desarrollo donde construimos, probamos y repetimos hasta que el software sea sólido. Vamos a desglosarlo.

4.1 Arquitectura y diseño del software

Antes de que nadie salte a programar, es sensato esbozar un plan de juego para la arquitectura de software, tanto para el firmware del dispositivo como para los componentes cloud. Piensa en ello como planificar un viaje por carretera. No empezarías a conducir sin un mapa; del mismo modo, esbozamos los componentes principales y cómo interactúan antes de escribir miles de líneas de código. Es análogo a la arquitectura de sistema que hicimos en la Etapa 2, pero ahora a nivel de código. Una buena planificación de la arquitectura de software te ayuda a evitar el «código espagueti» después. Estas son las cosas clave a establecer en esta fase:

Arquitectura del firmware (enfoque por capas y consideraciones de RTOS)

Define la estructura de tu código embebido organizándolo en capas. Un enfoque común en firmware es una arquitectura por capas, que ayuda a organizar el código lógicamente y lo hace más fácil de mantener. Por ejemplo, en el nivel más bajo puedes tener un Board Support Package (BSP) o una capa de abstracción de hardware: contiene los drivers de bajo nivel específicos de tu microcontrolador y periféricos hardware. Encima, puedes tener drivers de dispositivo o módulos para tus sensores y actuadores (encargándose de cosas como leer un sensor de temperatura o manejar un LED). Encima de los drivers, puedes tener una capa de servicios: puede implementar protocolos de comunicación, algoritmos de procesamiento de datos u otra lógica que procesa los datos crudos. Finalmente, arriba, tienes la capa de aplicación, que es el cerebro de tu dispositivo: une todo para cumplir el propósito de tu producto (p. ej., leer valores de sensores, tomar decisiones, enviar datos a la nube y reaccionar a comandos). Considera dibujar un diagrama simple (diagrama de componentes UML o incluso solo cajas y flechas en una pizarra) para visualizar estas capas. Esto ayuda a todo el equipo a entender la separación de responsabilidades: como construir una casa con cimientos, estructura y tejado, cada capa tiene su trabajo.

Otra gran decisión arquitectónica en el lado del firmware es si usar un RTOS (Real-Time Operating System) o no. Un RTOS es como tener un pequeño sistema operativo en tu dispositivo que puede ejecutar múltiples tareas aparentemente al mismo tiempo (mediante planificación). Si tu dispositivo solo hace una cosa cada vez, puede que no necesites un RTOS. Pero la mayoría de los gadgets IoT tienen muchas cosas ocurriendo concurrentemente: por ejemplo, leer sensores periódicamente, escuchar mensajes entrantes (de la nube o del usuario), actualizar una pantalla o un LED y gestionar la comunicación de red. Con un RTOS, puedes diseñar tu firmware como un conjunto de tareas o threads. Por ejemplo, podrías tener una tarea dedicada al muestreo de sensores, otra dedicada a las comunicaciones (enviar datos o recibir comandos) y otra para mantenimiento o actualizaciones de la interfaz de usuario. Como parte del diseño de tu arquitectura, esboza estas tareas y cómo se comunicarán entre sí: los mecanismos comunes incluyen colas de mensajes, flags de eventos o datos compartidos protegidos por mutexes (para evitar conflictos). Decidir esto pronto ayudará a asegurar que tu firmware no se convierta en una maraña de código entrelazado donde todo depende de todo (también conocido como código espagueti). En su lugar, tendrás interfaces claras: quizá la tarea de sensores mete las lecturas nuevas en una cola, y la tarea de comunicaciones las recoge para enviarlas a la nube. Si diseñas estas interfaces con claridad ahora, programar será mucho más fluido y te lo agradecerás a ti mismo cuando llegue la hora de depurar o extender el firmware.

¿Por qué tanto lío arquitectónico para el firmware? Porque te hace la vida más fácil más adelante. Con un buen diseño por capas y una buena estructura de tareas, puedes modificar una parte (digamos, cambiar el driver de un sensor o retocar un algoritmo) sin romper todo lo demás. También hace más fácil que varios desarrolladores trabajen juntos sin pisarse: uno puede desarrollar el driver del sensor mientras otro trabaja en el código de comunicación cloud, siempre que hayáis acordado cómo se interconectan esas partes (p. ej., el formato de datos de un mensaje de lectura de sensor). En resumen, un poco de planificación aquí evita mucho dolor después.

Arquitectura del cloud (estructura de servidor, base de datos y aplicación)

Ahora consideremos el lado cloud. El dispositivo IoT no vive aislado: normalmente habla con algún servicio cloud o servidor. Se necesita un razonamiento arquitectónico similar aquí: esboza los componentes principales de tu software cloud. Por ejemplo, puedes tener un servidor de API que recibe datos de los dispositivos y también atiende peticiones de las aplicaciones de usuario. Probablemente habrá una base de datos donde almacenas los datos de los dispositivos (lecturas de sensores, estados de dispositivos, información de usuarios, etc.). Puede haber servicios o microservicios adicionales haciendo procesamiento o analítica de datos: por ejemplo, analizando los datos en busca de anomalías, disparando alertas o acciones cuando se cumplen ciertas condiciones (p. ej., enviar un email si una lectura de temperatura es demasiado alta), o realizando agregaciones para informes. Encima de esto, considera las aplicaciones cliente: quizá un dashboard web o una app móvil con la que interactúan los usuarios. Esos clientes se comunicarán con el servidor (a menudo vía la API) para mostrar datos y enviar comandos de usuario (como un usuario pulsando un botón para encender su bombilla IoT desde el teléfono).

Es útil dibujar un diagrama de este ecosistema: el modelo C4 (Client-Container-Component-Code) es una forma popular de diagramar la arquitectura de software. Dibuja al menos el nivel Container: muestra el dispositivo, el servidor, la base de datos y la app cliente, y cómo fluyen los datos entre ellos. Por ejemplo, tu diagrama podría tener una flecha del Dispositivo a la API Cloud («envía datos del sensor vía HTTP/MQTT»), una flecha del Cloud a la Base de datos («almacena datos») y una flecha de la App cliente al Cloud («el usuario pide datos o envía un comando»), etc. Este mapa visual asegura que todos (desarrolladores de firmware, ingenieros de cloud, desarrolladores de frontend, etc.) tengan un modelo mental claro de cómo se conectan las cosas. Es mucho más fácil discutir y cazar problemas de diseño pronto con un diagrama así en la mano. Quizá descubras en esta fase que necesitas un componente adicional —por ejemplo, un message broker o un servicio de plataforma IoT— para manejar el escalado a muchos dispositivos. Mejor pensarlo ahora que cuando estás hasta el cuello de código.

Un aspecto más: piensa en la escalabilidad y fiabilidad en tu diseño cloud. Si esperas 100.000 dispositivos conectándose con el tiempo, ¿pueden tu base de datos y tu diseño de servidor elegidos con ello? Si un servidor cae, ¿qué pasa (tienes redundancia)? Esos detalles pueden desarrollarse más en una etapa posterior, pero el diseño de alto nivel debería contemplar preocupaciones mayores como esas desde el principio. Por ejemplo, podrías decidir usar una plataforma IoT cloud (AWS IoT, Azure IoT Hub, etc.) en lugar de construir todo desde cero, lo que pasaría a formar parte de tu decisión de arquitectura.

Protocolos de comunicación y formatos de datos (comunicación dispositivo-cloud)

Un producto IoT implica inherentemente comunicación entre el dispositivo y la nube. Parte del diseño de software es decidir cómo hablan entre sí y en qué lenguaje (protocolos y formatos de datos). Hay algunas opciones de protocolo comunes en IoT: por ejemplo, ¿usará tu dispositivo MQTT, HTTP/REST, CoAP, o quizá algo como WebSockets? Cada uno tiene sus pros y sus contras. MQTT es un protocolo ligero de publicación/suscripción estupendo para IoT porque es eficiente y funciona bien sobre redes poco fiables: muchos sistemas IoT lo usan para la mensajería dispositivo-cloud. HTTP/REST es muy común y fácil de probar (ya que puedes atacarlo con herramientas web estándar) pero puede tener algo más de overhead por mensaje. CoAP es un protocolo IoT especializado similar a un HTTP ligero, a menudo usado en redes con restricciones. La clave es elegir uno que encaje con las capacidades de tu dispositivo y tu infraestructura cloud. Por ejemplo, en un dispositivo muy restringido (microcontrolador de 8 bits a batería), MQTT o CoAP suelen encajar mejor que HTTP. Si tu dispositivo es más capaz (corriendo Linux o similar), HTTP/REST está bien y es más directo.

Una vez elegido el protocolo, diseña el formato de datos de los mensajes. Muchos proyectos usan JSON porque es legible por humanos y fácil de trabajar (la mayoría de los lenguajes tienen parsers de JSON). Por ejemplo, un mensaje de lectura de temperatura en JSON podría verse así:

{ "device_id": "ABC123", "timestamp": 1692124800, "temperature_c": 22.5 }

Esto expone los campos con claridad. Sin embargo, JSON es un poco verboso, así que si tu dispositivo necesita enviar datos de forma muy eficiente (piensa en miles de mensajes o ancho de banda muy bajo), podrías usar un formato binario (como Protocol Buffers, CBOR o un esquema binario propio) para ahorrar bytes. La decisión de diseño aquí afectará tanto al firmware como al cloud: ambos lados deben acordar el protocolo y el formato. Es un poco como acordar un idioma antes de empezar una conversación: si el dispositivo «habla» MQTT+JSON y el servidor espera otra cosa, no se entenderán.

Una herramienta útil en esta fase es dibujar un diagrama de secuencia o simplemente escribir una secuencia de comunicación de ejemplo. Por ejemplo: «El dispositivo toma una lectura del sensor, formatea un payload JSON, lo envía vía MQTT al topic X; el broker cloud lo recibe, el servicio cloud lo procesa y lo confirma, luego lo almacena en la base de datos; más tarde, un usuario pide datos vía la app, la API cloud los recupera de la base de datos y devuelve JSON a la app». Escribir este flujo asegura que todos (el desarrollador de firmware y el de cloud, especialmente) estén literalmente en la misma página sobre cómo se mueven los datos. Puede prevenir malentendidos como «Ah, pensaba que usábamos timestamp UNIX en segundos, no en milisegundos» o «¿Estábamos comprimiendo los datos o no?». Caza esos ahora en el diseño; ahorrará tiempo de depuración después.

Estándares de codificación y herramientas de equipo (calidad desde el principio)

Ahora hablemos de cómo escribiremos código y colaboraremos, porque es esencial que todo el equipo siga prácticas comunes. Acordad estándares de codificación para cada parte del sistema. Para el desarrollo en C embebido, muchos equipos adoptan las guías MISRA-C: un conjunto de reglas para el lenguaje C orientadas a evitar construcciones inseguras o propensas a bugs (originarias de la industria del automóvil, pero ampliamente útiles en cualquier firmware crítico). Si usas C++ en firmware, podrías decidir qué características del C++ moderno están permitidas o prohibidas (para mantener las cosas seguras para sistemas embebidos: por ejemplo, quizá evitar la asignación dinámica en tiempo de ejecución, etc.). Para lenguajes de más alto nivel en el lado cloud como Python, probablemente seguirías la guía de estilo PEP 8, y para JavaScript/TypeScript quizá usar un linter con el estilo de Airbnb o el estándar que sea. El objetivo no es ser puntilloso, es asegurar consistencia y cazar trampas comunes. Cuando todos escriben en un estilo consistente, la base de código se siente como un producto en lugar de un patchwork, y es más fácil leer y mantener el código escrito por otra persona del equipo.

Además del estilo de codificación, elige las herramientas e infraestructura que usará el equipo. Esto incluye el control de versiones y la configuración del repositorio (casi seguro Git: si aún no lo usas, hazlo sin duda, ya que es la espina dorsal de la programación colaborativa). Decide la estrategia de branching: ¿usarás un desarrollo simple basado en trunk, o GitFlow con feature branches, etc.? También, monta un sistema de seguimiento de issues si aún no lo tienes (como Jira, Trello, Asana o GitHub Issues) para gestionar las tareas y user stories de tus sprints. También es buen momento para montar el pipeline de integración continua (CI): elige un servicio de CI (GitHub Actions, GitLab CI, Jenkins, CircleCI, etc.) y configúralo para compilar y probar automáticamente tu código cuando se suban cambios. Hablaremos más de CI en la siguiente sección, pero la etapa de arquitectura es donde te aseguras de que todos los desarrolladores tienen acceso a estas herramientas y saben usarlas.

Otra parte de la «cultura de código» es decidir cosas como las prácticas de code review: p. ej., ¿requerirá cada pull request la aprobación de al menos otro desarrollador? Fijar estas expectativas pronto es sano. Puede parecer formal, pero te ahorra escenarios donde el código no revisado de alguien introduce un bug desagradable o un agujero de seguridad que otros solo cazan mucho después. Al acordar que «siempre hacemos code reviews, seguimos la guía de estilo y ejecutamos las pruebas antes de mergear», inculcas una mentalidad de calidad desde el día uno. El desarrollo moderno es tanto sobre colaboración y disciplina de equipo como sobre genio programador.

Por último, recuerda que esta planificación de la Etapa 4.1 no trata de dibujar diagramas bonitos que nadie mira nunca: trata de dar al equipo una comprensión compartida y una hoja de ruta técnica. Es como planificar la ruta de un viaje por carretera y llevar un mapa: sabes dónde están los grandes giros y cómo navegarás, pero aún puedes ser flexible con las paradas o los desvíos. Dedica un tiempo razonable a este diseño de arquitectura, pero no aspires a la perfección ni detalles hasta la última función. Quieres el diseño justo para que la gente pueda empezar a programar de forma coordinada. Con esta base asentada, estás listo para saltar al desarrollo con confianza.

4.2 Sprints de desarrollo: itera y vencerás

Con el plano de la arquitectura en la mano, empieza la construcción: escribir código, probarlo y ver a tu producto IoT hacer cosas de verdad. Abordamos esto en iteraciones, típicamente usando sprints como en la metodología Agile. Un sprint es un ciclo corto de duración fija (comúnmente 2 semanas, pero podría ser 1 semana o hasta 4 semanas según la preferencia de tu equipo) en el que el equipo se compromete a entregar un conjunto de funciones o mejoras al final. La idea es dividir el trabajo masivo de «desarrollar todo el software» en trozos del tamaño de un bocado que podamos ir abordando con progreso tangible.

Este enfoque iterativo tiene beneficios claros: fuerza la integración continua de los componentes (para que firmware y software cloud se prueben juntos con frecuencia), y te da muchas oportunidades de obtener feedback y ajustar el rumbo si hace falta. Es mucho mejor que programar durante 6 meses y solo entonces intentar ver si todo funciona (que es una receta para sorpresas desagradables). Veamos cómo podría ser un ciclo de sprint típico para el desarrollo de nuestro producto IoT:

Sprint planning y user stories

Cada sprint empieza con una reunión de Sprint Planning. El equipo mira el backlog de producto: la lista maestra de todas las funciones, correcciones y tareas pendientes, que se derivó inicialmente de los requisitos que recogimos en la Etapa 1 (y a la que se añadió lo aprendido en etapas posteriores). En la planificación, el equipo selecciona un conjunto de user stories o tareas del backlog para abordar en el sprint, según la prioridad y la capacidad del equipo.

Una user story es una descripción del tamaño de un bocado de una funcionalidad desde la perspectiva del usuario final, a menudo siguiendo la plantilla «Como <tipo de usuario>, quiero <alguna capacidad> para <beneficio>». Por ejemplo, una historia podría ser: «Como usuario, quiero que el dispositivo IoT envíe datos de temperatura a la nube cada 10 minutos, para poder monitorizar la temperatura de mi habitación en remoto.» A partir de esta historia, el equipo averiguará qué hay que hacer tanto en el lado del dispositivo como en el cloud: quizá en el firmware del dispositivo necesitamos leer el sensor de temperatura y enviar un mensaje MQTT cada 600 segundos; en el cloud, necesitamos una API o un manejador de mensajes para recibir esos datos y un lugar donde almacenarlos, y quizá una interfaz simple para mostrarlos.

Durante el sprint planning, el equipo discute cada historia elegida para fijar los criterios de aceptación: básicamente, ¿cómo sabemos cuándo esta historia está «terminada» y funcionando? Para el ejemplo anterior, los criterios podrían ser: «Dado un dispositivo con un sensor de temperatura, cuando funciona durante 10 minutos, al menos una lectura de temperatura debería ser visible en la base de datos cloud y en el dashboard del usuario». Ser específico ayuda al equipo a entender exactamente qué construir y cómo verificarlo. Este es el momento de hacer preguntas y aclarar detalles. Es mucho mejor limar las ambigüedades ahora que descubrir después que la gente de firmware y de cloud tenía suposiciones distintas (como que un desarrollador pensó que era cada 10 minutos cuando estaba activo, y otro pensó que era siempre, etc.). Una vez terminada la planificación, todos deberían saber de qué trozo de trabajo son responsables en el sprint que viene.

Implementación e integración continua (CI)

Ahora nos ponemos con la implementación: escribir el código para dar vida a esas user stories. Los desarrolladores empezarán a programar las funciones de firmware y de cloud identificadas para el sprint. Una buena práctica clave aquí es escribir también pruebas automatizadas para cada nueva funcionalidad a medida que se desarrolla. Para firmware, esto puede incluir escribir pruebas unitarias para lógica que pueda correr en una máquina host (por ejemplo, si tienes una función que parsea una lectura de sensor o una máquina de estados del dispositivo, a menudo puedes escribir una prueba basada en PC para ella). Para el software cloud, suele ser más fácil: puedes escribir pruebas unitarias para tus funciones y pruebas de integración para tus APIs.

A medida que los desarrolladores escriben código, lo suben al control de versiones (Git). Aquí es donde brilla nuestra configuración de integración continua (CI). Con CI, cada vez que se sube código o se abre una pull request, se dispara un build automatizado en un servidor de CI. El CI compilará el código del firmware (quizá en varias configuraciones si hace falta), ejecutará las pruebas unitarias que tengas y quizá incluso desplegará el firmware en un simulador o dispositivo de pruebas. También compilará el software cloud, ejecutará todas sus pruebas, comprobará el estilo de código (linters), ejecutará herramientas de análisis estático e incluso medirá la cobertura de código (cuánto del código ejercitan las pruebas). El objetivo es cazar cualquier problema inmediatamente. Si un desarrollador introdujo por accidente un bug que hace fallar una prueba, el sistema de CI lo señalará de inmediato —a los minutos de escribirse el código— para que el equipo lo arregle antes de que se convierta en un problema mayor. Este bucle de feedback rápido es crucial. Es mucho más fácil arreglar un bug que introdujiste hace una hora (aún recuerdas qué estabas haciendo) que un bug del mes pasado (que para entonces se ha enredado con muchos otros cambios).

Por ejemplo, imagina que un desarrollador cambia el código de lectura del sensor e introduce por accidente un bug de overflow para valores muy altos. Si tienes una prueba unitaria que lleva un valor del sensor al extremo, el CI cazará ese fallo y alertará al equipo. El desarrollador puede abordarlo de inmediato. Sin esa prueba, el bug podría acechar hasta que llegue una ola de calor, los sensores reales reporten temperaturas altas y el dispositivo se cuelgue en campo. CI y las pruebas existen para cazar exactamente estos momentos: automáticamente, y a los minutos de escribirse el código.

La integración continua no es solo pruebas; los equipos a menudo integran también herramientas de análisis estático en CI. Las herramientas de análisis estático examinan tu código fuente buscando bugs potenciales, vulnerabilidades de seguridad o violaciones de estilo sin ejecutarlo. Por ejemplo, pueden cazar cosas como «posible desreferencia de puntero nulo» o «variables sin usar» u otros code smells. Muchos equipos embebidos usan herramientas como PC-Lint, Cppcheck o clang-tidy para C/C++, y los equipos de cloud usan linters (ESLint para JavaScript, Pylint para Python, etc.) y quizá escáneres de seguridad. El CI puede configurarse para hacer fallar el build si estas herramientas encuentran problemas serios. Esto fija un listón de calidad: p. ej., «el código del firmware debe tener 0 warnings nuevos y todas las pruebas pasando para que se permita el merge». Puede sonar estricto, pero mantiene sana la base de código.

Además, el CI puede rastrear la cobertura de código: qué porcentaje de tu código ejercitan las pruebas automatizadas. Aunque el 100% de cobertura es a menudo irrealista (y no todo el código es igual de fácil de probar), muchos equipos fijan un objetivo (digamos, al menos 80% de cobertura) y le hacen seguimiento. Si una nueva entrega de código baja de golpe la cobertura del 80% al 50%, es una señal de alarma: quizá se saltaron pruebas o se añadió mucho código sin probar. Monitorizando estas métricas sprint a sprint, mantienes una alta calidad de código durante todo el desarrollo, en lugar de intentar «atornillarla» después.

En resumen, la implementación en un contexto iterativo no es solo «escribir un montón de código». Es escribir código, integrar continuamente ese código en una base compartida, probarlo continuamente y arreglar los problemas sobre la marcha. El resultado es un sistema de software que está siempre en un estado semi-funcional, mejorando un poco cada día.

Builds incrementales y demos de fin de sprint

Al final de cada sprint, aspiras a tener un build incremental de todo el sistema que sea una mejora tangible sobre el anterior. Idealmente, es algo que realmente puedes ejecutar en un dispositivo y un entorno cloud de pruebas para ver las nuevas funciones en acción. Un buen mantra es «estar siempre listo para la demo» al final del sprint. Para nuestro ejemplo de producto IoT, quizá al final del Sprint 1 tenemos un flujo básico de extremo a extremo funcionando: el dispositivo lee un valor de sensor ficticio (quizá un número fijo o un valor aleatorio de prueba) y lo envía a la nube, la nube lo recibe y lo almacena, y podemos verificar manualmente que los datos llegaron (aunque solo sea mirando directamente la base de datos o los logs). ¡Ese ya es un hito significativo: prueba que toda la fontanería del dispositivo a la nube funciona!

En el Sprint 2, podríamos reemplazar el valor ficticio con una lectura real del sensor del hardware, y montar una página web simple que muestre la última lectura. Ahora podemos ver datos en vivo de verdad. En el Sprint 3, quizá añadimos una función de alerta: si la temperatura supera un umbral, el dispositivo o la nube disparan una alerta (quizá parpadea un LED o se envía un email). Y así sucesivamente. Cada sprint, añadimos una capa más de funcionalidad: quizá un sprint añade soporte de control remoto (enviar comandos de la nube al dispositivo), otro añade una bonita gráfica de UI con datos históricos, etc. Tras varios sprints, tendrás un sistema completo en funciones. Pero crucialmente, en cada fase tenías un sistema funcionando al que ibas añadiendo. Esto significa que si algo va mal, sabes que probablemente tiene que ver con las adiciones más recientes, lo que facilita la depuración. También significa que los stakeholders (como tu jefe, o el product manager, o incluso un usuario de prueba) pueden ver el progreso y dar feedback durante todo el camino.

Las demos regulares son una gran práctica. Al final de cada sprint (o incluso más a menudo, como un enseñar-y-contar rápido cada semana), reúne al equipo y a cualquier stakeholder interesado y demuestra lo nuevo. Puede ser tan simple como «El dispositivo ya envía datos reales del sensor: mira cómo caliento el sensor con la mano y el número de este dashboard sube en tiempo real». Estas demos crean sensación de logro e impulso. También fuerzan disciplina de integración: no puedes demostrar algo que no funciona realmente junto, así que empujan al equipo a limar los problemas de integración sobre la marcha, en lugar de posponer la integración hasta el final. Ver el producto cobrar vida poco a poco, sprint tras sprint, es mucho más motivador que trabajar en el vacío durante meses.

Pruebas y criterios de Definition of Done

Hablamos de las pruebas automatizadas durante el desarrollo, pero vale la pena enfatizar cómo las pruebas están tejidas en la propia definición de progreso en cada sprint. Una funcionalidad o user story se considera «terminada» solo cuando cumple ciertos criterios más allá de «el código se escribió». Los equipos suelen tener una checklist de Definition of Done (DoD) que puede incluir elementos como:

Es una lista larga, y a primera vista puede parecer muchas casillas que marcar. Pero estas prácticas te ahorran muchísimos problemas después. Es mucho mejor cazar y arreglar un bug o un defecto de diseño ahora, durante el sprint actual, que dejar que se cuele hasta las pruebas finales o a producción. La Definition of Done básicamente significa «esta funcionalidad no está a medio hacer; está realmente integrada, probada y lista». Cuando cada parte de tu sistema cumple estos criterios sobre la marcha, la fase final de integración y pruebas (Etapa 5) guarda pocas sorpresas: has estado verificando todo el tiempo.

Documentación continua (mantén los mapas actualizados)

Probablemente creaste algunos diagramas y documentos en la fase de arquitectura (Etapa 4.1). Ahora, a medida que el desarrollo avanza, es importante mantener esos documentos de diseño actualizados. El software tiene una forma curiosa de evolucionar: quizá descubres la necesidad de una nueva tarea en el RTOS para una función de watchdog, o la arquitectura cloud cambia para añadir una capa de caché por rendimiento. Si no actualizas los documentos de arquitectura, se apartarán de la realidad y al final nadie confiará en ellos. Para evitar el clásico problema de la documentación obsoleta, trata los documentos como una parte viva del proyecto. Algunos equipos incluso escriben sus diagramas como código usando herramientas como PlantUML o Mermaid. Así, la «fuente» del diagrama vive en el repositorio junto al código. Cuando se hace un cambio que afecta a la arquitectura, el equipo actualiza el código del diagrama y regenera la imagen.

Una opción práctica (si tu equipo se anima) es integrar la documentación también en el proceso de CI. Por ejemplo, podrías tener una comprobación de que si ciertos archivos centrales cambiaron (como un esquema de mensaje o una interfaz de componente) y el archivo de documentación correspondiente no se actualizó, el CI te lo recuerda o hace fallar el build. Empuja a los desarrolladores: «Eh, cambiaste cómo funciona el sistema: actualiza el diagrama para que todos lo sepan». Puede sonar exagerado, pero mantener los documentos sincronizados da sus frutos cuando se incorpora un desarrollador nuevo o cuando vuelves al proyecto en seis meses e intentas recordar cómo están organizadas las cosas. Incluso si no lo automatizas, al menos programa una revisión rápida de los diagramas cada pocos sprints para asegurarte de que siguen reflejando la realidad.

La conclusión: la documentación al día vale el esfuerzo. Significa que cualquiera del equipo (o de fuera, como un auditor de cumplimiento o un socio) puede mirar los documentos y confiar en ellos. Evitas situaciones como «No leas la wiki, está vieja: Joe tiene la información real en su cabeza (o en algunos comentarios del código)». En su lugar, el conocimiento se captura y se actualiza sobre la marcha, lo que es un sello de un equipo maduro y enfocado en la calidad.

Demos regulares y bucles de feedback

Tocamos las demos en el contexto de los sprints, pero enfoquemos el valor de establecer una cadencia regular de demostrar el progreso. En Agile, esto es a menudo la reunión de Sprint Review: al final del sprint, el equipo muestra lo que construyó a los stakeholders (product managers, otros equipos, a veces incluso usuarios reales o ejecutivos). Para un proyecto IoT, una demo puede ser literal: mostrar el dispositivo y el sistema trabajando juntos. Por ejemplo, podrías proyectar el dashboard web en una pantalla y tener el dispositivo físico sobre la mesa. Luego hacer algo como disparar el dispositivo (pulsar un botón o crear una condición) y mostrar que la nube se actualiza en tiempo real. Estos momentos son genuinamente validadores para el equipo y valiosos para los stakeholders. Hacen que el proyecto se sienta real y mantienen a todos comprometidos. También son una oportunidad de obtener feedback: quizá un stakeholder dice: «Está bien, pero ¿podríamos mostrar también los datos de humedad en la próxima versión?» o «La alerta tardó un poco en aparecer, ¿podemos acelerarla?». El feedback temprano como este es especialmente valioso, porque puede alimentar los siguientes sprints, mucho antes de que el producto sea final, cuando los cambios son mucho más fáciles de hacer.

No te preocupes si en las primeras demos la funcionalidad es muy básica: es lo esperado. La clave es que mejora iterativamente. La primera demo puede ser solo probar que un dispositivo puede hablar con un servidor. La siguiente demo muestra una UI simple. Las demos posteriores muestran pulido y funciones adicionales. Si algo no funcionó como se esperaba en una demo, también está bien, porque descubriste un problema de integración delante de todos y ahora puedes priorizar arreglarlo. (De hecho, algunos equipos tienen un dicho: «¡Demo pronto, demo a menudo: falla a menudo, arregla más rápido!»). La demo también impone un poco de disciplina: pone presión positiva sobre el equipo para terminar el trabajo hasta un estado entregable cada sprint, en lugar de procrastinar la integración para más tarde. Es mucho mejor descubrir ahora que el JSON del dispositivo no se parsea correctamente en el servidor, que descubrirlo durante una gran fase final de pruebas. Así que trata las demos no solo como un espectáculo, sino como parte del propio proceso de desarrollo.

Por último, celebra esas demos. Construir un producto IoT es trabajo duro, con muchas piezas implicadas, y cada pieza que funciona —incluso una función pequeña— merece un momento de reconocimiento. Esto mantiene la moral alta y construye la confianza del equipo: la energía se mantiene más alta cuando todos ven regularmente los resultados tangibles de su trabajo.


Para resumir la Etapa 4: el desarrollo de firmware y software cloud trata de progreso iterativo y mejora continua. Invirtiendo tiempo en la arquitectura de software por adelantado, das a tu equipo un plano sólido (y evitas programaros en un callejón sin salida). Usando sprints de desarrollo, integración continua y una Definition of Done fuerte, aseguras que la calidad se incorpora en cada paso: los bugs se cazan pronto, y el software está siempre a un paso de un producto funcional. Al final de la Etapa 4 (que probablemente abarca varios ciclos de sprint), deberías tener un firmware completo en funciones y probado en hardware real o representativo, y un sistema cloud completo en funciones y probado en un entorno realista. En otras palabras, todo el lado software de tu producto está listo para rodar.

Gracias a este enfoque iterativo, has podido ajustar y mejorar continuamente, en lugar de apostarlo todo a un único big bang al final. Eso reduce el riesgo y hace el proceso de desarrollo más predecible. También es simplemente una mejor forma de trabajar: en lugar de un largo tramo de incertidumbre, has visto a tu producto IoT cobrar vida paso a paso, de un LED parpadeante a un dispositivo inteligente totalmente conectado e integrado con la nube. El trabajo pesado del lado del software está hecho, y se hizo con rigor y agilidad. A la siguiente etapa, donde tomamos este hardware y software bien elaborados y nos preparamos para soltarlos al mundo.