{"id":5133,"date":"2026-09-12T00:55:13","date_gmt":"2026-09-12T03:55:13","guid":{"rendered":"https:\/\/tucumandevelopers.com\/index.php\/2026\/09\/12\/como-arme-un-pit-wall-con-aws-iot-core-y-por-que-este-patron-sirve-para-cualquier-industria\/"},"modified":"2026-09-12T00:55:13","modified_gmt":"2026-09-12T03:55:13","slug":"como-arme-un-pit-wall-con-aws-iot-core-y-por-que-este-patron-sirve-para-cualquier-industria","status":"publish","type":"post","link":"https:\/\/tucumandevelopers.com\/index.php\/2026\/09\/12\/como-arme-un-pit-wall-con-aws-iot-core-y-por-que-este-patron-sirve-para-cualquier-industria\/","title":{"rendered":"C\u00f3mo arm\u00e9 un Pit Wall con AWS IoT Core (y por qu\u00e9 este patr\u00f3n sirve para cualquier industria)"},"content":{"rendered":"<div>\n<div><\/div>\n<p>Fijate lo que <strong>no<\/strong> puede hacer este certificado: no puede publicar en <code>sim\/pit-wall-sim-02\/telemetry<\/code> (el topic de otro simulador), y no puede suscribirse a nada que no sea su propio topic de comandos. Si ma\u00f1ana alguien clona el certificado de un simulador, el radio de da\u00f1o queda contenido a ese \u00fanico dispositivo \u2014 nunca escala al resto de la flota.<\/p>\n<p>Esto es literalmente lo mismo que har\u00edas con un sensor de humedad en una planta agr\u00edcola, o con la tablet de un cami\u00f3n de reparto: un certificado por dispositivo, un permiso m\u00ednimo por dispositivo, sin excepciones.<\/p>\n<h2> <a name=\"iot-rules-el-coraz%C3%B3n-del-procesamiento\" href=\"#iot-rules-el-coraz%C3%B3n-del-procesamiento\"> <\/a> IoT Rules: el coraz\u00f3n del procesamiento <\/h2>\n<p>Una vez que el dato entra por MQTT, \u00bfqui\u00e9n lo agarra? Ah\u00ed entran las <strong>IoT Rules<\/strong>: consultas SQL que corren sobre cada mensaje que pasa por un topic, y que disparan una o m\u00e1s acciones. <\/p>\n<div>\n<pre><code><span>DATA_TOPICS<\/span> <span>=<\/span> <span>(<\/span><span>\"<\/span><span>telemetry<\/span><span>\"<\/span><span>,<\/span> <span>\"<\/span><span>event<\/span><span>\"<\/span><span>,<\/span> <span>\"<\/span><span>lap<\/span><span>\"<\/span><span>,<\/span> <span>\"<\/span><span>session<\/span><span>\"<\/span><span>,<\/span> <span>\"<\/span><span>status<\/span><span>\"<\/span><span>)<\/span> <span>def<\/span> <span>_crear_regla<\/span><span>(<\/span><span>self<\/span><span>,<\/span> <span>topic<\/span><span>:<\/span> <span>str<\/span><span>):<\/span> <span>regla<\/span> <span>=<\/span> <span>iot<\/span><span>.<\/span><span>CfnTopicRule<\/span><span>(<\/span> <span>self<\/span><span>,<\/span> <span>f<\/span><span>\"<\/span><span>Rule<\/span><span>{<\/span><span>topic<\/span><span>}<\/span><span>\"<\/span><span>,<\/span> <span>topic_rule_payload<\/span><span>=<\/span><span>iot<\/span><span>.<\/span><span>CfnTopicRule<\/span><span>.<\/span><span>TopicRulePayloadProperty<\/span><span>(<\/span> <span>sql<\/span><span>=<\/span><span>f<\/span><span>\"<\/span><span>SELECT * FROM <\/span><span>'<\/span><span>{<\/span><span>self<\/span><span>.<\/span><span>topic_prefix<\/span><span>}<\/span><span>\/+\/<\/span><span>{<\/span><span>topic<\/span><span>}<\/span><span>'\"<\/span><span>,<\/span> <span>actions<\/span><span>=<\/span><span>[<\/span> <span>iot<\/span><span>.<\/span><span>CfnTopicRule<\/span><span>.<\/span><span>ActionProperty<\/span><span>(<\/span> <span>lambda_<\/span><span>=<\/span><span>iot<\/span><span>.<\/span><span>CfnTopicRule<\/span><span>.<\/span><span>LambdaActionProperty<\/span><span>(<\/span> <span>function_arn<\/span><span>=<\/span><span>self<\/span><span>.<\/span><span>ingest_function<\/span><span>.<\/span><span>function_arn<\/span> <span>)<\/span> <span>),<\/span> <span>iot<\/span><span>.<\/span><span>CfnTopicRule<\/span><span>.<\/span><span>ActionProperty<\/span><span>(<\/span> <span>lambda_<\/span><span>=<\/span><span>iot<\/span><span>.<\/span><span>CfnTopicRule<\/span><span>.<\/span><span>LambdaActionProperty<\/span><span>(<\/span> <span>function_arn<\/span><span>=<\/span><span>self<\/span><span>.<\/span><span>broadcast_function<\/span><span>.<\/span><span>function_arn<\/span> <span>)<\/span> <span>),<\/span> <span>],<\/span> <span>),<\/span> <span>)<\/span> <\/code><\/pre>\n<div>\n<\/p><\/div>\n<\/p><\/div>\n<p><a href=\"https:\/\/media2.dev.to\/dynamic\/image\/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto\/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2F193ihwy5cjxhp55qzcl1.png\"><\/a><\/p>\n<p>Lo interesante ac\u00e1 no es el SQL (<code>SELECT * FROM 'sim\/+\/telemetry'<\/code>, con el <code>+<\/code> como wildcard de un nivel \u2014 cualquier simulador, ese topic puntual). Lo interesante es que <strong>una sola regla dispara dos acciones en paralelo<\/strong>, sobre el mismo mensaje:<\/p>\n<ul>\n<li>Una Lambda de <strong>ingesta<\/strong>, que persiste el dato (m\u00e1s sobre esto abajo).<\/li>\n<li>Una Lambda de <strong>broadcast<\/strong>, que reenv\u00eda el mismo mensaje por WebSocket a quien est\u00e9 mirando ese simulador en vivo.<\/li>\n<\/ul>\n<p>No hay una segunda suscripci\u00f3n al mismo topic, ni una cola intermedia, ni el ingesta reenvi\u00e1ndole el dato al broadcast. Es la misma regla, la misma invocaci\u00f3n de IoT Core, dos acciones independientes. Si ma\u00f1ana el broadcast se cae, la ingesta sigue funcionando \u2014 y viceversa.<\/p>\n<blockquote>\n<p>Este es el patr\u00f3n que m\u00e1s me sirvi\u00f3 entender de todo el proyecto: <strong>una IoT Rule no es &#8220;un trigger&#8221;, es un punto de fan-out<\/strong>. Pod\u00e9s colgar tantas acciones como necesites (otra Lambda, un t\u00f3pico SNS, un stream de Kinesis, otra regla) sin tocar nada del lado del dispositivo ni de las reglas existentes.<\/p>\n<\/blockquote>\n<h2> <a name=\"el-desaf%C3%ADo-real-reconstruir-algo-coherente-a-partir-de-pedacitos\" href=\"#el-desaf%C3%ADo-real-reconstruir-algo-coherente-a-partir-de-pedacitos\"> <\/a> El desaf\u00edo real: reconstruir algo coherente a partir de pedacitos <\/h2>\n<p>Ac\u00e1 viene la parte menos obvia. MQTT tiene un l\u00edmite de tama\u00f1o de payload, y la telemetr\u00eda de un simulador genera muestras varias veces por segundo \u2014 no entra todo en un solo mensaje. El agente en la PC del simulador arma <strong>lotes<\/strong> (batches) de muestras y los publica de a poco, con un n\u00famero de secuencia cada uno.<\/p>\n<p>Del lado de AWS, eso significa que la Lambda de ingesta nunca recibe &#8220;una vuelta completa&#8221; \u2014 recibe fragmentos, que van a <code>staging<\/code> en DynamoDB con un TTL (si una sesi\u00f3n nunca cierra una vuelta, ese staging se autodestruye solo). Cuando llega el mensaje que dice &#8220;esta vuelta se cerr\u00f3&#8221;, ah\u00ed s\u00ed, la Lambda junta todos los lotes que tiene en staging para esa sesi\u00f3n y reconstruye la traza completa: <\/p>\n<div>\n<pre><code><span>class<\/span> <span>CoberturaIncompletaError<\/span><span>(<\/span><span>ReconstructionError<\/span><span>):<\/span> <span>\"\"\"<\/span><span>La cobertura de lotes en staging no alcanza \u2014 todav\u00eda. Las IoT Rules invocan la Lambda una vez por mensaje, sin ninguna garant\u00eda de orden entre invocaciones concurrentes: el mensaje de <\/span><span>\"<\/span><span>vuelta cerrada<\/span><span>\"<\/span><span> puede llegar antes de que termine de guardarse su \u00faltimo lote de telemetr\u00eda. Ac\u00e1 se reintenta unos segundos dentro de la misma invocaci\u00f3n, en vez de descartar la vuelta. <\/span><span>\"\"\"<\/span> <\/code><\/pre>\n<div>\n<\/p><\/div>\n<\/p><\/div>\n<p>Este tipo de problema \u2014 datos que llegan en desorden, sin garant\u00eda de que el \u00faltimo pedacito ya est\u00e9 cuando lo necesit\u00e1s \u2014 es <em>el<\/em> problema cl\u00e1sico de cualquier pipeline de IoT en tiempo real. No es espec\u00edfico de telemetr\u00eda de autos: es lo mismo que te pasa reconstruyendo el recorrido de un cami\u00f3n a partir de puntos GPS sueltos, o el estado de una m\u00e1quina a partir de eventos de sensores que no llegan en orden.<\/p>\n<h2> <a name=\"guardado-una-tabla-un-bucket-y-ya\" href=\"#guardado-una-tabla-un-bucket-y-ya\"> <\/a> Guardado: una tabla, un bucket, y ya <\/h2>\n<p>Del lado del guardado no hay demasiada magia, pero vale la pena nombrarlo porque es donde termina el dato una vez procesado:<\/p>\n<ul>\n<li> <strong>DynamoDB<\/strong>, en una tabla \u00fanica (single-table design): sesiones, vueltas, recomendaciones, todo con <code>PK<\/code>\/<code>SK<\/code> pensados para que cada consulta sea una <code>Query<\/code> barata sobre un prefijo, nunca un <code>Scan<\/code> de toda la tabla.<\/li>\n<li> <strong>S3<\/strong>, para lo pesado: la telemetr\u00eda reconstruida, los archivos de audio, cualquier blob que no tenga sentido meter en DynamoDB.<\/li>\n<\/ul>\n<p>Nada de esto es exclusivo de IoT \u2014 es el mismo par de servicios que usar\u00edas para cualquier backend serverless. Lo importante es que <strong>para cuando el dato llega ac\u00e1, ya pas\u00f3 por todo el trabajo pesado de identidad, transporte y reconstrucci\u00f3n<\/strong>, as\u00ed que esta capa queda simple.<\/p>\n<h2> <a name=\"de-iot-a-en-vivo-el-otro-lado-del-fanout\" href=\"#de-iot-a-en-vivo-el-otro-lado-del-fanout\"> <\/a> De IoT a &#8220;en vivo&#8221;: el otro lado del fan-out <\/h2>\n<p>Contaba arriba que la misma IoT Rule dispara dos Lambdas en paralelo. La segunda (<code>broadcast<\/code>) es la que arma la parte &#8220;en vivo&#8221; del dashboard: mantiene un \u00edndice chico de qu\u00e9 conexi\u00f3n de WebSocket est\u00e1 mirando qu\u00e9 simulador, y cuando llega un mensaje nuevo, lo reenv\u00eda tal cual a esas conexiones.<\/p>\n<p>Fijate que este Lambda <strong>no procesa nada<\/strong> \u2014 no interpreta, no clasifica, no llama a ning\u00fan otro servicio. Solo reenv\u00eda. Esa separaci\u00f3n entre &#8220;el camino que persiste&#8221; y &#8220;el camino que muestra en vivo&#8221; es la que permite que uno pueda evolucionar sin romper al otro: si un d\u00eda quiero agregar un an\u00e1lisis con IA arriba de la ingesta (spoiler: lo hice, es tema de otro post), el WebSocket en vivo ni se entera.<\/p>\n<h2> <a name=\"generalizando-el-patr%C3%B3n\" href=\"#generalizando-el-patr%C3%B3n\"> <\/a> Generalizando el patr\u00f3n <\/h2>\n<p>Volvamos a la idea del principio: esto no es sobre autos. La misma arquitectura, con los mismos cuatro bloques (identidad por dispositivo \u2192 IoT Rules \u2192 procesamiento \u2192 guardado\/consumo), sirve para:<\/p>\n<div>\n<table>\n<thead>\n<tr>\n<th>Industria<\/th>\n<th>El &#8220;dispositivo&#8221;<\/th>\n<th>El dato<\/th>\n<th>Qu\u00e9 reemplaza al pit wall<\/th>\n<\/tr>\n<\/thead>\n<tbody>\n<tr>\n<td>Agro<\/td>\n<td>Sensor de humedad\/temperatura en el campo<\/td>\n<td>Lecturas peri\u00f3dicas<\/td>\n<td>Un dashboard de riego automatizado<\/td>\n<\/tr>\n<tr>\n<td>Log\u00edstica<\/td>\n<td>Tablet o GPS de un cami\u00f3n de reparto<\/td>\n<td>Posici\u00f3n, velocidad, paradas<\/td>\n<td>Seguimiento de flota en vivo<\/td>\n<\/tr>\n<tr>\n<td>Industria<\/td>\n<td>PLC o sensor de una l\u00ednea de producci\u00f3n<\/td>\n<td>Vibraci\u00f3n, temperatura, ciclos<\/td>\n<td>Mantenimiento predictivo<\/td>\n<\/tr>\n<tr>\n<td>Salud<\/td>\n<td>Wearable de un paciente<\/td>\n<td>Frecuencia card\u00edaca, pasos<\/td>\n<td>Alertas en tiempo real al equipo m\u00e9dico<\/td>\n<\/tr>\n<\/tbody>\n<\/table>\n<\/div>\n<p>En todos los casos cambia el dominio, pero no cambia la pregunta que ten\u00e9s que responder: <em>\u00bfc\u00f3mo identifico a cada dispositivo sin que uno pueda hacerse pasar por otro, y c\u00f3mo proceso lo que manda sin acoplar mi l\u00f3gica de negocio a los detalles del transporte?<\/em> Esa pregunta la responde AWS IoT Core, la resuelvas con autos, camiones o sensores de humedad.<\/p>\n<hr>\n<p><a href=\"https:\/\/media2.dev.to\/dynamic\/image\/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto\/https%3A%2F%2Fdev-to-uploads.s3.us-east-2.amazonaws.com%2Fuploads%2Farticles%2Fvyae3y1rsl9fithhggvj.png\"><\/a><\/p>\n<hr>\n<h2> <a name=\"pr%C3%B3ximos-pasos\" href=\"#pr%C3%B3ximos-pasos\"> <\/a> Pr\u00f3ximos pasos <\/h2>\n<p>Termin\u00e9 este proyecto sabiendo mucho m\u00e1s de IoT que de lo que sab\u00eda de simracing cuando arranqu\u00e9, que ya es decir bastante. Todo lo de ac\u00e1 es la base: una vez que ten\u00e9s el dato capturado, identificado y procesado, pod\u00e9s poner arriba lo que quieras. En mi caso fue un ingeniero de carrera que te habla por voz mientras manej\u00e1s, generado con Amazon Bedrock y Amazon Polly \u2014 de eso hablo en el pr\u00f3ximo post.<\/p>\n<hr>\n<p><strong>Una \u00faltima cosa antes de cerrar:<\/strong><\/p>\n<p>Todo el material de esta charla \u2014 slides, c\u00f3digo, diagramas \u2014 est\u00e1 en <a href=\"https:\/\/github.com\/alvarongg\/charlas-pub\" target=\"_blank\" rel=\"noopener noreferrer\">github.com\/alvarongg\/charlas-pub<\/a>. Voy a estar subiendo contenido nuevo seguido: m\u00e1s posts como este, ejemplos de c\u00f3digo ejecutable y arquitecturas de referencia.<\/p>\n<p>Si le tir\u00e1s una \u2b50 al repo GitHub te avisa autom\u00e1ticamente cuando haya novedades. Es la forma m\u00e1s f\u00e1cil de no perderte nada \u2014 sin newsletter, sin formularios, sin nada raro.<\/p>\n<p>Abrazo grande. Hasta la pr\u00f3xima \ud83d\ude80<\/p>\n<p>Cualquier pregunta sobre IoT Core, certificados, o el dise\u00f1o de la tabla, la dejo abierta en los comentarios. \u00a1Nos vemos en la pista! \ud83c\udfce\ufe0f<\/p>\n<\/p><\/div>\n<\/div>\n<\/div>\n<\/div>\n<p>Fuente: <a href=\"https:\/\/dev.to\/alvarongg\/como-arme-un-pit-wall-con-aws-iot-core-y-por-que-este-patron-sirve-para-cualquier-industria-4lo1\">Art\u00edculo original<\/a><\/p>\n","protected":false},"excerpt":{"rendered":"<p>Fijate lo que no puede hacer este certificado: no puede publicar en sim\/pit-wall-sim-02\/telemetry (el topic de otro simulador), y no puede suscribirse a nada que no sea su propio topic de comandos. Si ma\u00f1ana alguien clona el certificado de un simulador, el radio de da\u00f1o queda contenido a ese \u00fanico dispositivo \u2014 nunca escala al [&hellip;]<\/p>\n","protected":false},"author":1,"featured_media":5132,"comment_status":"open","ping_status":"open","sticky":false,"template":"","format":"standard","meta":{"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},"webixso_pending_account_ids":""},"categories":[41],"tags":[],"class_list":["post-5133","post","type-post","status-publish","format-standard","has-post-thumbnail","hentry","category-devto"],"jetpack_publicize_connections":[],"_links":{"self":[{"href":"https:\/\/tucumandevelopers.com\/index.php\/wp-json\/wp\/v2\/posts\/5133","targetHints":{"allow":["GET"]}}],"collection":[{"href":"https:\/\/tucumandevelopers.com\/index.php\/wp-json\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/tucumandevelopers.com\/index.php\/wp-json\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/tucumandevelopers.com\/index.php\/wp-json\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/tucumandevelopers.com\/index.php\/wp-json\/wp\/v2\/comments?post=5133"}],"version-history":[{"count":0,"href":"https:\/\/tucumandevelopers.com\/index.php\/wp-json\/wp\/v2\/posts\/5133\/revisions"}],"wp:featuredmedia":[{"embeddable":true,"href":"https:\/\/tucumandevelopers.com\/index.php\/wp-json\/wp\/v2\/media\/5132"}],"wp:attachment":[{"href":"https:\/\/tucumandevelopers.com\/index.php\/wp-json\/wp\/v2\/media?parent=5133"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/tucumandevelopers.com\/index.php\/wp-json\/wp\/v2\/categories?post=5133"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/tucumandevelopers.com\/index.php\/wp-json\/wp\/v2\/tags?post=5133"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}