[{"data":1,"prerenderedAt":1557},["ShallowReactive",2],{"navigation":3,"blog-page":18,"blogs":28},[4],{"title":5,"path":6,"stem":7,"children":8,"page":17},"Blog","\u002Fblog","blog",[9,13],{"title":10,"path":11,"stem":12},"Cómo llegué a Nuxt: mi viaje del jQuery clásico al desarrollo web moderno","\u002Fblog\u002Fcomo-llegue-a-nuxt-mi-camino-en-el-desarrollo-web","blog\u002Fcomo-llegue-a-nuxt-mi-camino-en-el-desarrollo-web",{"title":14,"path":15,"stem":16},"Creando mi propia plataforma de rastreo satelital: del abuso comercial al control total","\u002Fblog\u002Fporque-una-plataforma-de-gps-personal","blog\u002Fporque-una-plataforma-de-gps-personal",false,{"id":19,"title":20,"body":21,"description":22,"extension":23,"links":21,"meta":24,"navigation":25,"path":6,"seo":26,"stem":7,"__hash__":27},"pages\u002Fblog.yml","Últimos Artículos",null,"Algunos de mis pensamientos recientes sobre diseño, desarrollo y la industria tecnológica.","yml",{},true,{"title":20,"description":22},"7RSiWFCTQKN0Am1uEFksMoy4oVlAllTx5iv8JzeaN5A",[29,907],{"id":30,"title":10,"author":31,"body":35,"date":900,"description":901,"extension":902,"image":903,"meta":904,"minRead":229,"navigation":25,"path":11,"seo":905,"sitemap":25,"stem":12,"__hash__":906},"blog\u002Fblog\u002Fcomo-llegue-a-nuxt-mi-camino-en-el-desarrollo-web.md",{"name":32,"avatar":33},"Christian Lopez",{"src":34,"alt":32},"https:\u002F\u002Fclopezpro.imgix.net\u002Fporfolio\u002Fautor\u002Fchristian-lopez.png\u002FGemini_Generated_Image_ko366rko366rko36.png?w=1792&h=1959&fm=avif&rect=0%2C175%2C1792%2C1959&s=d46e24aedfcf2ef0f1637aff8ef5c881",{"type":36,"value":37,"toc":892},"minimark",[38,42,61,64,69,72,85,88,90,94,101,112,115,118,120,124,134,154,160,182,185,275,277,281,290,296,299,302,812,814,818,823,826,876,878,882,885,888],[39,40,41],"p",{},"El desarrollo web actual avanza a un ritmo vertiginoso. Hoy en día damos por sentadas herramientas como la reactividad reactiva, el renderizado del lado del servidor (SSR), el enrutamiento automático y el empaquetado ultra veloz de módulos. Pero llegar a este ecosistema moderno ha sido un largo viaje de aprendizaje, tropiezos y evolución constante.",[39,43,44,45,49,50,53,54,49,57,60],{},"Esta es la historia de cómo empecé maquetando sitios web simples con ",[46,47,48],"strong",{},"jQuery"," y ",[46,51,52],{},"Bootstrap",", sobreviví al desencanto de la educación universitaria tradicional, y finalmente descubrí en ",[46,55,56],{},"Vue",[46,58,59],{},"Nuxt"," mi stack de desarrollo definitivo.",[62,63],"hr",{},[65,66,68],"h2",{"id":67},"los-primeros-pasos-la-emoción-de-maquetar-y-la-ignorancia-de-los-servidores","Los primeros pasos: la emoción de maquetar y la ignorancia de los servidores",[39,70,71],{},"Cuando di mis primeros pasos en el desarrollo web, todo era fascinación pura. Quería aprender a programar para internet, pero en ese momento no entendía absolutamente nada sobre servidores, despliegues o bases de datos relacionales. Mi único gran sueño era escribir código, registrar un dominio público propio y ver mi sitio web al aire disponible para cualquiera en el mundo.",[39,73,74,75,78,79,81,82,84],{},"En esa etapa inicial de autoaprendizaje, encontré un faro en YouTube: el canal de ",[46,76,77],{},"Jon Mircha",". Sus explicaciones sobre cómo usar ",[46,80,52],{}," y cómo maquetar una web me abrieron los ojos. Gracias a él, aprendí a estructurar mis primeros layouts y a utilizar ",[46,83,48],{}," para añadir interactividad a mis páginas (como abrir modales, animar elementos y validar formularios sencillos).",[39,86,87],{},"Ver que al hacer clic en un botón ocurría una acción visual programada por mí era una sensación adictiva. Sin embargo, aún me faltaba chocarme con la realidad de las aplicaciones web empresariales.",[62,89],{},[65,91,93],{"id":92},"el-abismo-universitario-java-glassfish-y-voces-robóticas","El abismo universitario: Java, GlassFish y voces robóticas",[39,95,96,97,100],{},"Decidí estudiar en la universidad para profesionalizar mi camino. Mi carrera estaba fuertemente orientada a las redes y las telecomunicaciones (",[46,98,99],{},"Networking","), pero mi pasión seguía inclinándose hacia el desarrollo de software. Tenía un enorme entusiasmo por aprender más.",[39,102,103,104,107,108,111],{},"Lamentablemente, el choque con la academia local fue frustrante. En las materias de programación nos enseñaban cosas sumamente complejas y, honestamente, obsoletas para la realidad laboral diaria. La metodología consistía en programar aplicaciones en ",[46,105,106],{},"Java"," utilizando el servidor de aplicaciones ",[46,109,110],{},"GlassFish",".",[39,113,114],{},"El proceso de enseñanza de algunos docentes consistía en enviarnos enlaces de YouTube con videotutoriales grabados con voces robóticas (generadas por loquendo o lectores de texto artificiales) donde no se entendía absolutamente nada del porqué de las cosas. Era copiar código sin entender la lógica de fondo de la arquitectura corporativa. En lugar de inspirar al estudiante, aquello apagaba la curiosidad.",[39,116,117],{},"Pronto comprendí que la universidad me daría el título, pero la única forma real de aprender a desarrollar software de verdad era entrando al mercado laboral. Tenía que conseguir un puesto como desarrollador junior.",[62,119],{},[65,121,123],{"id":122},"el-bautismo-de-fuego-en-code-plus-y-los-límites-de-jquery","El bautismo de fuego en Code Plus y los límites de jQuery",[39,125,126,127,130,131,111],{},"Tuve la oportunidad de ingresar a la empresa actualmente conocida como ",[46,128,129],{},"Code Plus",". Allí me asignaron a un proyecto de gran envergadura llamado ",[46,132,133],{},"Educat",[39,135,136,139,140,143,144,147,148,49,151,153],{},[137,138,133],"em",{}," era un sistema de gestión escolar diseñado para administrar notas, insumos y asistencia estudiantil; básicamente, querían construir un competidor sólido para plataformas establecidas como ",[137,141,142],{},"Idukay",". El stack tecnológico del proyecto era el clásico de esa época: ",[46,145,146],{},"PHP",", ",[46,149,150],{},"MySQL",[46,152,48],{}," en el frontend.",[39,155,156,157,159],{},"Al principio, el stack funcionó muy bien. Para módulos pequeños y funcionalidades directas, jQuery es una herramienta excelente por su sencillez para manipular el DOM. Sin embargo, a medida que ",[137,158,133],{}," comenzó a crecer y a incorporar complejas pantallas de entrada de notas multidimensionales, la arquitectura empezó a desmoronarse:",[161,162,163,170,176],"ul",{},[164,165,166,169],"li",{},[46,167,168],{},"Spaghetti de código:"," La manipulación manual del DOM hacía que las interacciones dependieran del estado de las clases HTML.",[164,171,172,175],{},[46,173,174],{},"Mantenimiento imposible:"," Modificar un botón en una vista implicaba reescribir selectores jQuery en varios archivos JavaScript aislados.",[164,177,178,181],{},[46,179,180],{},"Problemas de sincronización:"," Mantener sincronizados los datos en memoria con los elementos visuales de la interfaz se convirtió en una pesadilla.",[39,183,184],{},"Me di cuenta de que jQuery no era escalable para proyectos de gran escala. Necesitaba otra forma de trabajar.",[186,187,192],"pre",{"className":188,"code":189,"language":190,"meta":191,"style":191},"language-javascript shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","\u002F\u002F La antigua forma con jQuery: manipulación manual del DOM y selectores propensos a errores\n$('#btn-save').on('click', function() {\n  var name = $('#input-name').val();\n  if (!name) {\n    $('#error').text('El campo es requerido').show();\n  } else {\n    $('#error').hide();\n    $.post('\u002Fapi\u002Fstudent', { name: name }, function(data) {\n      $('#student-list').append('\u003Cli data-id=\"' + data.id + '\">' + data.name + '\u003C\u002Fli>');\n      $('#input-name').val('');\n    });\n  }\n});\n","javascript","",[193,194,195,203,209,215,221,227,233,239,245,251,257,263,269],"code",{"__ignoreMap":191},[196,197,200],"span",{"class":198,"line":199},"line",1,[196,201,202],{},"\u002F\u002F La antigua forma con jQuery: manipulación manual del DOM y selectores propensos a errores\n",[196,204,206],{"class":198,"line":205},2,[196,207,208],{},"$('#btn-save').on('click', function() {\n",[196,210,212],{"class":198,"line":211},3,[196,213,214],{},"  var name = $('#input-name').val();\n",[196,216,218],{"class":198,"line":217},4,[196,219,220],{},"  if (!name) {\n",[196,222,224],{"class":198,"line":223},5,[196,225,226],{},"    $('#error').text('El campo es requerido').show();\n",[196,228,230],{"class":198,"line":229},6,[196,231,232],{},"  } else {\n",[196,234,236],{"class":198,"line":235},7,[196,237,238],{},"    $('#error').hide();\n",[196,240,242],{"class":198,"line":241},8,[196,243,244],{},"    $.post('\u002Fapi\u002Fstudent', { name: name }, function(data) {\n",[196,246,248],{"class":198,"line":247},9,[196,249,250],{},"      $('#student-list').append('\u003Cli data-id=\"' + data.id + '\">' + data.name + '\u003C\u002Fli>');\n",[196,252,254],{"class":198,"line":253},10,[196,255,256],{},"      $('#input-name').val('');\n",[196,258,260],{"class":198,"line":259},11,[196,261,262],{},"    });\n",[196,264,266],{"class":198,"line":265},12,[196,267,268],{},"  }\n",[196,270,272],{"class":198,"line":271},13,[196,273,274],{},"});\n",[62,276],{},[65,278,280],{"id":279},"el-descubrimiento-de-vuejs-y-la-revolución-de-escuela-vue","El descubrimiento de Vue.js y la revolución de Escuela Vue",[39,282,283,284,286,287,111],{},"Un día, durante una transmisión en vivo que realizaba Jon Mircha, decidí comentar y preguntar sobre alternativas modernas para desarrollo frontend, mencionando mi interés por ",[46,285,56],{},". Jon Mircha leyó mi comentario y me recomendó ampliamente: ",[137,288,289],{},"\"Te sugiero revisar el contenido de Juan Andrés Núñez, en el canal de JuanWmedia (Escuela Vue)\"",[39,291,292,293,111],{},"Seguí su consejo de inmediato. Me devoré cada uno de los videos gratuitos de su canal y decidí inscribirme en sus programas máster de ",[46,294,295],{},"Escuela Vue",[39,297,298],{},"El cambio en mi mentalidad de desarrollo fue instantáneo. La filosofía de Vue —basada en componentes reactivos de un solo archivo (Single File Components), en el enlace bidireccional de datos (two-way data binding) y en la separación limpia de la interfaz y la lógica— me pareció una genialidad absoluta.",[39,300,301],{},"Entendí los conceptos de reactividad a la primera. En ese momento, no pude evitar desear que mis profesores de la universidad hubieran tenido esa misma claridad y pasión para enseñar. Desde ese día, decidí que usaría Vue para prácticamente todo.",[186,303,307],{"className":304,"code":305,"language":306,"meta":191,"style":191},"language-vue shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","\u003C!-- La nueva forma declarativa con Vue (Composition API) -->\n\u003Cscript setup>\nconst name = ref('')\nconst error = ref('')\nconst students = ref([])\n\nasync function addStudent() {\n  if (!name.value) return error.value = 'El campo es requerido'\n  error.value = ''\n  \n  const response = await $fetch('\u002Fapi\u002Fstudent', { method: 'POST', body: { name: name.value } })\n  students.value.push(response)\n  name.value = ''\n}\n\u003C\u002Fscript>\n\n\u003Ctemplate>\n  \u003Cdiv>\n    \u003Cinput v-model=\"name\" placeholder=\"Nombre del estudiante\">\n    \u003Cp v-if=\"error\" class=\"error\">{{ error }}\u003C\u002Fp>\n    \u003Cbutton @click=\"addStudent\">Guardar\u003C\u002Fbutton>\n    \u003Cul>\n      \u003Cli v-for=\"student in students\" :key=\"student.id\">{{ student.name }}\u003C\u002Fli>\n    \u003C\u002Ful>\n  \u003C\u002Fdiv>\n\u003C\u002Ftemplate>\n","vue",[193,308,309,315,332,357,374,388,393,410,456,470,475,547,568,581,587,597,602,612,623,658,700,731,740,783,793,803],{"__ignoreMap":191},[196,310,311],{"class":198,"line":199},[196,312,314],{"class":313},"sHwdD","\u003C!-- La nueva forma declarativa con Vue (Composition API) -->\n",[196,316,317,321,325,329],{"class":198,"line":205},[196,318,320],{"class":319},"sMK4o","\u003C",[196,322,324],{"class":323},"swJcz","script",[196,326,328],{"class":327},"spNyl"," setup",[196,330,331],{"class":319},">\n",[196,333,334,337,341,344,348,351,354],{"class":198,"line":211},[196,335,336],{"class":327},"const",[196,338,340],{"class":339},"sTEyZ"," name ",[196,342,343],{"class":319},"=",[196,345,347],{"class":346},"s2Zo4"," ref",[196,349,350],{"class":339},"(",[196,352,353],{"class":319},"''",[196,355,356],{"class":339},")\n",[196,358,359,361,364,366,368,370,372],{"class":198,"line":217},[196,360,336],{"class":327},[196,362,363],{"class":339}," error ",[196,365,343],{"class":319},[196,367,347],{"class":346},[196,369,350],{"class":339},[196,371,353],{"class":319},[196,373,356],{"class":339},[196,375,376,378,381,383,385],{"class":198,"line":223},[196,377,336],{"class":327},[196,379,380],{"class":339}," students ",[196,382,343],{"class":319},[196,384,347],{"class":346},[196,386,387],{"class":339},"([])\n",[196,389,390],{"class":198,"line":229},[196,391,392],{"emptyLinePlaceholder":25},"\n",[196,394,395,398,401,404,407],{"class":198,"line":235},[196,396,397],{"class":327},"async",[196,399,400],{"class":327}," function",[196,402,403],{"class":346}," addStudent",[196,405,406],{"class":319},"()",[196,408,409],{"class":319}," {\n",[196,411,412,416,419,422,425,427,430,433,436,439,441,443,446,449,453],{"class":198,"line":241},[196,413,415],{"class":414},"s7zQu","  if",[196,417,418],{"class":323}," (",[196,420,421],{"class":319},"!",[196,423,424],{"class":339},"name",[196,426,111],{"class":319},[196,428,429],{"class":339},"value",[196,431,432],{"class":323},") ",[196,434,435],{"class":414},"return",[196,437,438],{"class":339}," error",[196,440,111],{"class":319},[196,442,429],{"class":339},[196,444,445],{"class":319}," =",[196,447,448],{"class":319}," '",[196,450,452],{"class":451},"sfazB","El campo es requerido",[196,454,455],{"class":319},"'\n",[196,457,458,461,463,465,467],{"class":198,"line":247},[196,459,460],{"class":339},"  error",[196,462,111],{"class":319},[196,464,429],{"class":339},[196,466,445],{"class":319},[196,468,469],{"class":319}," ''\n",[196,471,472],{"class":198,"line":253},[196,473,474],{"class":323},"  \n",[196,476,477,480,483,485,488,491,493,496,499,501,504,507,510,513,515,518,520,522,525,527,529,532,534,536,538,540,543,545],{"class":198,"line":259},[196,478,479],{"class":327},"  const",[196,481,482],{"class":339}," response",[196,484,445],{"class":319},[196,486,487],{"class":414}," await",[196,489,490],{"class":346}," $fetch",[196,492,350],{"class":323},[196,494,495],{"class":319},"'",[196,497,498],{"class":451},"\u002Fapi\u002Fstudent",[196,500,495],{"class":319},[196,502,503],{"class":319},",",[196,505,506],{"class":319}," {",[196,508,509],{"class":323}," method",[196,511,512],{"class":319},":",[196,514,448],{"class":319},[196,516,517],{"class":451},"POST",[196,519,495],{"class":319},[196,521,503],{"class":319},[196,523,524],{"class":323}," body",[196,526,512],{"class":319},[196,528,506],{"class":319},[196,530,531],{"class":323}," name",[196,533,512],{"class":319},[196,535,531],{"class":339},[196,537,111],{"class":319},[196,539,429],{"class":339},[196,541,542],{"class":319}," }",[196,544,542],{"class":319},[196,546,356],{"class":323},[196,548,549,552,554,556,558,561,563,566],{"class":198,"line":265},[196,550,551],{"class":339},"  students",[196,553,111],{"class":319},[196,555,429],{"class":339},[196,557,111],{"class":319},[196,559,560],{"class":346},"push",[196,562,350],{"class":323},[196,564,565],{"class":339},"response",[196,567,356],{"class":323},[196,569,570,573,575,577,579],{"class":198,"line":271},[196,571,572],{"class":339},"  name",[196,574,111],{"class":319},[196,576,429],{"class":339},[196,578,445],{"class":319},[196,580,469],{"class":319},[196,582,584],{"class":198,"line":583},14,[196,585,586],{"class":319},"}\n",[196,588,590,593,595],{"class":198,"line":589},15,[196,591,592],{"class":319},"\u003C\u002F",[196,594,324],{"class":323},[196,596,331],{"class":319},[196,598,600],{"class":198,"line":599},16,[196,601,392],{"emptyLinePlaceholder":25},[196,603,605,607,610],{"class":198,"line":604},17,[196,606,320],{"class":319},[196,608,609],{"class":323},"template",[196,611,331],{"class":319},[196,613,615,618,621],{"class":198,"line":614},18,[196,616,617],{"class":319},"  \u003C",[196,619,620],{"class":323},"div",[196,622,331],{"class":319},[196,624,626,629,632,635,637,640,642,644,647,649,651,654,656],{"class":198,"line":625},19,[196,627,628],{"class":319},"    \u003C",[196,630,631],{"class":323},"input",[196,633,634],{"class":327}," v-model",[196,636,343],{"class":319},[196,638,639],{"class":319},"\"",[196,641,424],{"class":451},[196,643,639],{"class":319},[196,645,646],{"class":327}," placeholder",[196,648,343],{"class":319},[196,650,639],{"class":319},[196,652,653],{"class":451},"Nombre del estudiante",[196,655,639],{"class":319},[196,657,331],{"class":319},[196,659,661,663,665,668,670,672,675,677,680,682,684,686,688,691,694,696,698],{"class":198,"line":660},20,[196,662,628],{"class":319},[196,664,39],{"class":323},[196,666,667],{"class":327}," v-if",[196,669,343],{"class":319},[196,671,639],{"class":319},[196,673,674],{"class":451},"error",[196,676,639],{"class":319},[196,678,679],{"class":327}," class",[196,681,343],{"class":319},[196,683,639],{"class":319},[196,685,674],{"class":451},[196,687,639],{"class":319},[196,689,690],{"class":319},">",[196,692,693],{"class":339},"{{ error }}",[196,695,592],{"class":319},[196,697,39],{"class":323},[196,699,331],{"class":319},[196,701,703,705,708,711,713,715,718,720,722,725,727,729],{"class":198,"line":702},21,[196,704,628],{"class":319},[196,706,707],{"class":323},"button",[196,709,710],{"class":327}," @click",[196,712,343],{"class":319},[196,714,639],{"class":319},[196,716,717],{"class":451},"addStudent",[196,719,639],{"class":319},[196,721,690],{"class":319},[196,723,724],{"class":339},"Guardar",[196,726,592],{"class":319},[196,728,707],{"class":323},[196,730,331],{"class":319},[196,732,734,736,738],{"class":198,"line":733},22,[196,735,628],{"class":319},[196,737,161],{"class":323},[196,739,331],{"class":319},[196,741,743,746,748,751,753,755,758,760,763,765,767,770,772,774,777,779,781],{"class":198,"line":742},23,[196,744,745],{"class":319},"      \u003C",[196,747,164],{"class":323},[196,749,750],{"class":327}," v-for",[196,752,343],{"class":319},[196,754,639],{"class":319},[196,756,757],{"class":451},"student in students",[196,759,639],{"class":319},[196,761,762],{"class":327}," :key",[196,764,343],{"class":319},[196,766,639],{"class":319},[196,768,769],{"class":451},"student.id",[196,771,639],{"class":319},[196,773,690],{"class":319},[196,775,776],{"class":339},"{{ student.name }}",[196,778,592],{"class":319},[196,780,164],{"class":323},[196,782,331],{"class":319},[196,784,786,789,791],{"class":198,"line":785},24,[196,787,788],{"class":319},"    \u003C\u002F",[196,790,161],{"class":323},[196,792,331],{"class":319},[196,794,796,799,801],{"class":198,"line":795},25,[196,797,798],{"class":319},"  \u003C\u002F",[196,800,620],{"class":323},[196,802,331],{"class":319},[196,804,806,808,810],{"class":198,"line":805},26,[196,807,592],{"class":319},[196,809,609],{"class":323},[196,811,331],{"class":319},[62,813],{},[65,815,817],{"id":816},"la-cima-del-flujo-de-trabajo-por-qué-uso-nuxt","La cima del flujo de trabajo: por qué uso Nuxt",[39,819,820,821,111],{},"De trabajar con Vue puro (que requiere configurar manualmente Vue Router, librerías de estado, empaquetadores como Vite o Webpack, y configurar el SEO a mano), el salto natural fue descubrir ",[46,822,59],{},[39,824,825],{},"Nuxt resolvió de golpe todas las fricciones de configuración inicial de un proyecto. Se ha convertido en mi framework por excelencia para el desarrollo de aplicaciones web de producción por razones clave:",[827,828,829,839,852,858],"ol",{},[164,830,831,834,835,838],{},[46,832,833],{},"Todo Integrado:"," Viene con enrutamiento basado en archivos de forma nativa. Creas una vista en ",[193,836,837],{},"pages\u002Findex.vue"," y Nuxt genera la ruta automáticamente sin que tengas que tocar un archivo de configuración de rutas.",[164,840,841,844,845,147,848,851],{},[46,842,843],{},"Auto-imports inteligentes:"," No necesitas importar ",[193,846,847],{},"ref",[193,849,850],{},"computed",", o tus propios componentes en cada archivo. Nuxt los detecta y los importa bajo el capó, manteniendo el código limpio y libre de imports innecesarios.",[164,853,854,857],{},[46,855,856],{},"Múltiples Modos de Renderizado:"," Permite hacer aplicaciones con renderizado del lado del servidor (SSR), generación de sitios estáticos (SSG) o Single Page Applications (SPA) tradicionales con solo configurar una línea en el archivo de configuración.",[164,859,860,863,864,867,868,871,872,875],{},[46,861,862],{},"Ecosistema Premium:"," La suite de herramientas oficiales como ",[46,865,866],{},"Nuxt UI"," (para componentes estéticos y consistentes), ",[46,869,870],{},"Nuxt Image"," (para optimizar imágenes al vuelo) y ",[46,873,874],{},"Nuxt Content"," (para crear blogs y documentación a partir de Markdown) me permiten crear aplicaciones robustas y sumamente profesionales en una fracción del tiempo que me tomaría con otras tecnologías.",[62,877],{},[65,879,881],{"id":880},"conclusión-la-evolución-como-desarrollador","Conclusión: la evolución como desarrollador",[39,883,884],{},"Mirar hacia atrás me hace valorar cada etapa. jQuery fue la base que me enseñó el valor de interactuar con el DOM; el desarrollo clásico en PHP me dio la lógica de backend; y las malas experiencias universitarias me impulsaron a ser autodidacta y exigente con los recursos educativos que consumo.",[39,886,887],{},"Hoy, gracias a la combinación de Vue y Nuxt, no solo desarrollo mucho más rápido, sino que disfruto de un flujo de trabajo ordenado, escalable y moderno. La web emocionante que Jon Mircha me inspiró a crear en mis inicios es ahora una realidad cotidiana mucho más potente de lo que jamás imaginé.",[889,890,891],"style",{},"html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sHwdD, html code.shiki .sHwdD{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#546E7A;--shiki-default-font-style:italic;--shiki-dark:#676E95;--shiki-dark-font-style:italic}html pre.shiki code .sMK4o, html code.shiki .sMK4o{--shiki-light:#39ADB5;--shiki-default:#89DDFF;--shiki-dark:#89DDFF}html pre.shiki code .swJcz, html code.shiki .swJcz{--shiki-light:#E53935;--shiki-default:#F07178;--shiki-dark:#F07178}html pre.shiki code .spNyl, html code.shiki .spNyl{--shiki-light:#9C3EDA;--shiki-default:#C792EA;--shiki-dark:#C792EA}html pre.shiki code .sTEyZ, html code.shiki .sTEyZ{--shiki-light:#90A4AE;--shiki-default:#EEFFFF;--shiki-dark:#BABED8}html pre.shiki code .s2Zo4, html code.shiki .s2Zo4{--shiki-light:#6182B8;--shiki-default:#82AAFF;--shiki-dark:#82AAFF}html pre.shiki code .s7zQu, html code.shiki .s7zQu{--shiki-light:#39ADB5;--shiki-light-font-style:italic;--shiki-default:#89DDFF;--shiki-default-font-style:italic;--shiki-dark:#89DDFF;--shiki-dark-font-style:italic}html pre.shiki code .sfazB, html code.shiki .sfazB{--shiki-light:#91B859;--shiki-default:#C3E88D;--shiki-dark:#C3E88D}",{"title":191,"searchDepth":205,"depth":205,"links":893},[894,895,896,897,898,899],{"id":67,"depth":205,"text":68},{"id":92,"depth":205,"text":93},{"id":122,"depth":205,"text":123},{"id":279,"depth":205,"text":280},{"id":816,"depth":205,"text":817},{"id":880,"depth":205,"text":881},"2026-06-09","Una reflexión personal sobre mi transición en el desarrollo web: desde mis inicios autodidactas con jQuery y Bootstrap, pasando por las frustraciones universitarias, hasta la adopción de Vue y Nuxt como mi stack definitivo.","md","\u002Fhero\u002Fnuxt-journey-banner.png",{},{"title":10,"description":901},"yfQRBaP7Xx6dCV5jKzGaJayxDEKbpTqEk60rN-JySnw",{"id":908,"title":14,"author":909,"body":911,"date":900,"description":1552,"extension":902,"image":1553,"meta":1554,"minRead":229,"navigation":25,"path":15,"seo":1555,"sitemap":25,"stem":16,"__hash__":1556},"blog\u002Fblog\u002Fporque-una-plataforma-de-gps-personal.md",{"name":32,"avatar":910},{"src":34,"alt":32},{"type":36,"value":912,"toc":1541},[913,923,934,941,944,946,950,960,963,966,968,972,978,983,990,993,1405,1407,1411,1414,1451,1456,1459,1463,1470,1474,1481,1485,1492,1516,1518,1522,1525,1528,1535,1538],[39,914,915,916,919,920,111],{},"En el año 2021 adquirí un vehículo nuevo. Como es habitual en muchas concesionarias, el auto venía con un dispositivo de rastreo satelital (GPS) preinstalado. En la factura final, este dispositivo de la reconocida marca ",[46,917,918],{},"Teltonika"," figuraba con un costo de ",[46,921,922],{},"$500 USD",[39,924,925,926,929,930,933],{},"Como desarrollador con curiosidad innata, mi primer instinto fue investigar. Busqué el modelo exacto del GPS en internet y descubrí que su precio de venta al público en el mercado no superaba los ",[46,927,928],{},"$120 USD",". Pero el abuso comercial no terminaba ahí: la empresa proveedora del servicio de rastreo exigía un pago de ",[46,931,932],{},"$150 USD anuales"," por la renovación de la licencia para usar su plataforma.",[39,935,936,937,940],{},"A esta excesiva diferencia de precios se sumó una sorpresa aún peor cuando decidí probar el sistema: ",[46,938,939],{},"estaba mal instalado",". El circuito que debía notificar la apertura de puertas estaba mal cableado o configurado de tal manera que solo detectaba cuando se abría la puerta principal del chofer. Si alguien abría cualquiera de las otras tres puertas o la cajuela, el sistema simplemente no se enteraba.",[39,942,943],{},"En ese momento lo tuve claro: no iba a pagar una suscripción anual por un servicio costoso, mal instalado y limitado. Decidí crear mi propia plataforma de rastreo.",[62,945],{},[65,947,949],{"id":948},"la-chispa-de-la-idea-mi-experiencia-en-code-plus","La chispa de la idea: Mi experiencia en Code Plus",[39,951,952,953,955,956,959],{},"Afortunadamente, contaba con un as bajo la manga. Durante mi paso por ",[46,954,129],{},", uno de los proyectos que me asignaron consistió precisamente en configurar un servidor Linux con ",[46,957,958],{},"Node.js"," para manejar e interpretar las tramas TCP que emiten los dispositivos de rastreo vía GSM (red celular).",[39,961,962],{},"Los dispositivos GPS no hacen magia; periódicamente emiten un paquete de datos (trama) usando protocolos específicos (como el Codec 8 o Codec 8 Extended de Teltonika) sobre una conexión de red. Esta trama incluye latitud, longitud, velocidad, estado del motor, y eventos de los sensores.",[39,964,965],{},"Conocer este flujo técnico de antemano me dio la confianza necesaria para asumir el reto. Sabía que interpretar esos bytes y guardarlos en una base de datos estaba completamente a mi alcance.",[62,967],{},[65,969,971],{"id":970},"la-evolución-del-desarrollo-de-java-sockets-a-nodejs","La evolución del desarrollo: De Java Sockets a Node.js",[39,973,974,975,111],{},"El proyecto no nació directamente en JavaScript. Al principio, lo planteé como un reto personal para profundizar en el funcionamiento de la programación de red a bajo nivel y entender a fondo el manejo de ",[46,976,977],{},"Sockets en Java",[39,979,980,981,111],{},"Escribí un servidor multihilo en Java que abría un puerto, escuchaba las conexiones y procesaba los flujos de entrada. Funcionó bien como ejercicio de aprendizaje, pero cuando quise llevar el proyecto a una fase de producción ágil, decidí migrar la lógica a ",[46,982,958],{},[39,984,985,986,989],{},"La naturaleza asíncrona de Node.js orientada a eventos, su excelente soporte nativo para streams y buffers a través del módulo ",[193,987,988],{},"net",", y la rapidez con la que se pueden integrar librerías de terceros (como bases de datos o APIs de mensajería) lo convertían en la herramienta perfecta para este tipo de carga de trabajo.",[39,991,992],{},"Un ejemplo simplificado del servidor TCP básico que procesa las conexiones luce así:",[186,994,996],{"className":188,"code":995,"language":190,"meta":191,"style":191},"import net from 'node:net'\n\nconst PORT = 5000\n\nconst server = net.createServer((socket) => {\n  console.log(`[+] Dispositivo conectado: ${socket.remoteAddress}:${socket.remotePort}`)\n\n  socket.on('data', (buffer) => {\n    \u002F\u002F Aquí es donde ocurre la magia: interpretar la trama binaria (hexadecimal)\n    console.log('Trama binaria recibida:', buffer.toString('hex'))\n    \n    \u002F\u002F El protocolo a menudo requiere que el servidor responda con un acuse de recibo (ACK)\n    \u002F\u002F para que el GPS sepa que los datos llegaron y borre la trama de su memoria interna.\n  })\n\n  socket.on('error', (err) => {\n    console.error('Error en el socket:', err.message)\n  })\n\n  socket.on('close', () => {\n    console.log('[-] Dispositivo desconectado')\n  })\n})\n\nserver.listen(PORT, () => {\n  console.log(`Servidor TCP de rastreo escuchando en el puerto ${PORT}`)\n})\n",[193,997,998,1016,1020,1033,1037,1070,1117,1121,1153,1158,1198,1203,1208,1213,1220,1224,1253,1282,1288,1292,1318,1337,1343,1349,1353,1374,1398],{"__ignoreMap":191},[196,999,1000,1003,1006,1009,1011,1014],{"class":198,"line":199},[196,1001,1002],{"class":414},"import",[196,1004,1005],{"class":339}," net ",[196,1007,1008],{"class":414},"from",[196,1010,448],{"class":319},[196,1012,1013],{"class":451},"node:net",[196,1015,455],{"class":319},[196,1017,1018],{"class":198,"line":205},[196,1019,392],{"emptyLinePlaceholder":25},[196,1021,1022,1024,1027,1029],{"class":198,"line":211},[196,1023,336],{"class":327},[196,1025,1026],{"class":339}," PORT ",[196,1028,343],{"class":319},[196,1030,1032],{"class":1031},"sbssI"," 5000\n",[196,1034,1035],{"class":198,"line":217},[196,1036,392],{"emptyLinePlaceholder":25},[196,1038,1039,1041,1044,1046,1049,1051,1054,1056,1058,1062,1065,1068],{"class":198,"line":223},[196,1040,336],{"class":327},[196,1042,1043],{"class":339}," server ",[196,1045,343],{"class":319},[196,1047,1048],{"class":339}," net",[196,1050,111],{"class":319},[196,1052,1053],{"class":346},"createServer",[196,1055,350],{"class":339},[196,1057,350],{"class":319},[196,1059,1061],{"class":1060},"sHdIc","socket",[196,1063,1064],{"class":319},")",[196,1066,1067],{"class":327}," =>",[196,1069,409],{"class":319},[196,1071,1072,1075,1077,1080,1082,1085,1088,1091,1093,1095,1098,1101,1103,1105,1107,1109,1112,1115],{"class":198,"line":229},[196,1073,1074],{"class":339},"  console",[196,1076,111],{"class":319},[196,1078,1079],{"class":346},"log",[196,1081,350],{"class":323},[196,1083,1084],{"class":319},"`",[196,1086,1087],{"class":451},"[+] Dispositivo conectado: ",[196,1089,1090],{"class":319},"${",[196,1092,1061],{"class":339},[196,1094,111],{"class":319},[196,1096,1097],{"class":339},"remoteAddress",[196,1099,1100],{"class":319},"}",[196,1102,512],{"class":451},[196,1104,1090],{"class":319},[196,1106,1061],{"class":339},[196,1108,111],{"class":319},[196,1110,1111],{"class":339},"remotePort",[196,1113,1114],{"class":319},"}`",[196,1116,356],{"class":323},[196,1118,1119],{"class":198,"line":235},[196,1120,392],{"emptyLinePlaceholder":25},[196,1122,1123,1126,1128,1131,1133,1135,1138,1140,1142,1144,1147,1149,1151],{"class":198,"line":241},[196,1124,1125],{"class":339},"  socket",[196,1127,111],{"class":319},[196,1129,1130],{"class":346},"on",[196,1132,350],{"class":323},[196,1134,495],{"class":319},[196,1136,1137],{"class":451},"data",[196,1139,495],{"class":319},[196,1141,503],{"class":319},[196,1143,418],{"class":319},[196,1145,1146],{"class":1060},"buffer",[196,1148,1064],{"class":319},[196,1150,1067],{"class":327},[196,1152,409],{"class":319},[196,1154,1155],{"class":198,"line":247},[196,1156,1157],{"class":313},"    \u002F\u002F Aquí es donde ocurre la magia: interpretar la trama binaria (hexadecimal)\n",[196,1159,1160,1163,1165,1167,1169,1171,1174,1176,1178,1181,1183,1186,1188,1190,1193,1195],{"class":198,"line":253},[196,1161,1162],{"class":339},"    console",[196,1164,111],{"class":319},[196,1166,1079],{"class":346},[196,1168,350],{"class":323},[196,1170,495],{"class":319},[196,1172,1173],{"class":451},"Trama binaria recibida:",[196,1175,495],{"class":319},[196,1177,503],{"class":319},[196,1179,1180],{"class":339}," buffer",[196,1182,111],{"class":319},[196,1184,1185],{"class":346},"toString",[196,1187,350],{"class":323},[196,1189,495],{"class":319},[196,1191,1192],{"class":451},"hex",[196,1194,495],{"class":319},[196,1196,1197],{"class":323},"))\n",[196,1199,1200],{"class":198,"line":259},[196,1201,1202],{"class":323},"    \n",[196,1204,1205],{"class":198,"line":265},[196,1206,1207],{"class":313},"    \u002F\u002F El protocolo a menudo requiere que el servidor responda con un acuse de recibo (ACK)\n",[196,1209,1210],{"class":198,"line":271},[196,1211,1212],{"class":313},"    \u002F\u002F para que el GPS sepa que los datos llegaron y borre la trama de su memoria interna.\n",[196,1214,1215,1218],{"class":198,"line":583},[196,1216,1217],{"class":319},"  }",[196,1219,356],{"class":323},[196,1221,1222],{"class":198,"line":589},[196,1223,392],{"emptyLinePlaceholder":25},[196,1225,1226,1228,1230,1232,1234,1236,1238,1240,1242,1244,1247,1249,1251],{"class":198,"line":599},[196,1227,1125],{"class":339},[196,1229,111],{"class":319},[196,1231,1130],{"class":346},[196,1233,350],{"class":323},[196,1235,495],{"class":319},[196,1237,674],{"class":451},[196,1239,495],{"class":319},[196,1241,503],{"class":319},[196,1243,418],{"class":319},[196,1245,1246],{"class":1060},"err",[196,1248,1064],{"class":319},[196,1250,1067],{"class":327},[196,1252,409],{"class":319},[196,1254,1255,1257,1259,1261,1263,1265,1268,1270,1272,1275,1277,1280],{"class":198,"line":604},[196,1256,1162],{"class":339},[196,1258,111],{"class":319},[196,1260,674],{"class":346},[196,1262,350],{"class":323},[196,1264,495],{"class":319},[196,1266,1267],{"class":451},"Error en el socket:",[196,1269,495],{"class":319},[196,1271,503],{"class":319},[196,1273,1274],{"class":339}," err",[196,1276,111],{"class":319},[196,1278,1279],{"class":339},"message",[196,1281,356],{"class":323},[196,1283,1284,1286],{"class":198,"line":614},[196,1285,1217],{"class":319},[196,1287,356],{"class":323},[196,1289,1290],{"class":198,"line":625},[196,1291,392],{"emptyLinePlaceholder":25},[196,1293,1294,1296,1298,1300,1302,1304,1307,1309,1311,1314,1316],{"class":198,"line":660},[196,1295,1125],{"class":339},[196,1297,111],{"class":319},[196,1299,1130],{"class":346},[196,1301,350],{"class":323},[196,1303,495],{"class":319},[196,1305,1306],{"class":451},"close",[196,1308,495],{"class":319},[196,1310,503],{"class":319},[196,1312,1313],{"class":319}," ()",[196,1315,1067],{"class":327},[196,1317,409],{"class":319},[196,1319,1320,1322,1324,1326,1328,1330,1333,1335],{"class":198,"line":702},[196,1321,1162],{"class":339},[196,1323,111],{"class":319},[196,1325,1079],{"class":346},[196,1327,350],{"class":323},[196,1329,495],{"class":319},[196,1331,1332],{"class":451},"[-] Dispositivo desconectado",[196,1334,495],{"class":319},[196,1336,356],{"class":323},[196,1338,1339,1341],{"class":198,"line":733},[196,1340,1217],{"class":319},[196,1342,356],{"class":323},[196,1344,1345,1347],{"class":198,"line":742},[196,1346,1100],{"class":319},[196,1348,356],{"class":339},[196,1350,1351],{"class":198,"line":785},[196,1352,392],{"emptyLinePlaceholder":25},[196,1354,1355,1358,1360,1363,1366,1368,1370,1372],{"class":198,"line":795},[196,1356,1357],{"class":339},"server",[196,1359,111],{"class":319},[196,1361,1362],{"class":346},"listen",[196,1364,1365],{"class":339},"(PORT",[196,1367,503],{"class":319},[196,1369,1313],{"class":319},[196,1371,1067],{"class":327},[196,1373,409],{"class":319},[196,1375,1376,1378,1380,1382,1384,1386,1389,1391,1394,1396],{"class":198,"line":805},[196,1377,1074],{"class":339},[196,1379,111],{"class":319},[196,1381,1079],{"class":346},[196,1383,350],{"class":323},[196,1385,1084],{"class":319},[196,1387,1388],{"class":451},"Servidor TCP de rastreo escuchando en el puerto ",[196,1390,1090],{"class":319},[196,1392,1393],{"class":339},"PORT",[196,1395,1114],{"class":319},[196,1397,356],{"class":323},[196,1399,1401,1403],{"class":198,"line":1400},27,[196,1402,1100],{"class":319},[196,1404,356],{"class":339},[62,1406],{},[65,1408,1410],{"id":1409},"arquitectura-de-la-plataforma","Arquitectura de la Plataforma",[39,1412,1413],{},"Para hacer la plataforma viable, escalable y, sobre todo, económica, diseñé una arquitectura ligera pero robusta:",[186,1415,1419],{"className":1416,"code":1417,"language":1418,"meta":191,"style":191},"language-mermaid shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","graph TD\n    A[Dispositivo GPS Teltonika] -->|Trama TCP via GSM| B[VPS Económico \u002F Servidor Node.js]\n    B -->|Logs históricos| C[Almacenamiento en archivos por IMEI]\n    B -->|Último estado| D[Base de datos de última ubicación]\n    B -->|Notificaciones instantáneas| E[API de Telegram]\n    E -->|Mensajes y Mapas| F[Celular del usuario]\n","mermaid",[193,1420,1421,1426,1431,1436,1441,1446],{"__ignoreMap":191},[196,1422,1423],{"class":198,"line":199},[196,1424,1425],{},"graph TD\n",[196,1427,1428],{"class":198,"line":205},[196,1429,1430],{},"    A[Dispositivo GPS Teltonika] -->|Trama TCP via GSM| B[VPS Económico \u002F Servidor Node.js]\n",[196,1432,1433],{"class":198,"line":211},[196,1434,1435],{},"    B -->|Logs históricos| C[Almacenamiento en archivos por IMEI]\n",[196,1437,1438],{"class":198,"line":217},[196,1439,1440],{},"    B -->|Último estado| D[Base de datos de última ubicación]\n",[196,1442,1443],{"class":198,"line":223},[196,1444,1445],{},"    B -->|Notificaciones instantáneas| E[API de Telegram]\n",[196,1447,1448],{"class":198,"line":229},[196,1449,1450],{},"    E -->|Mensajes y Mapas| F[Celular del usuario]\n",[1452,1453,1455],"h3",{"id":1454},"_1-servidor-vps-económico-con-gran-almacenamiento","1. Servidor VPS Económico con Gran Almacenamiento",[39,1457,1458],{},"Alquilé un servidor virtual privado (VPS) económico. Dado que no requería un procesador ultrapotente pero sí suficiente espacio para guardar los históricos, prioricé el almacenamiento.",[1452,1460,1462],{"id":1461},"_2-guardado-histórico-en-archivos","2. Guardado Histórico en Archivos",[39,1464,1465,1466,1469],{},"En lugar de saturar una base de datos relacional con millones de registros de posiciones que rara vez se consultan en el día a día, implementé un sistema donde la plataforma genera un documento independiente por cada ",[46,1467,1468],{},"IMEI"," (identificador único del dispositivo GPS). Allí se va escribiendo secuencialmente el histórico de tramas a modo de log persistente.",[1452,1471,1473],{"id":1472},"_3-última-ubicación-en-base-de-datos","3. Última Ubicación en Base de Datos",[39,1475,1476,1477,1480],{},"En otra base de datos paralela, el sistema almacena exclusivamente el ",[46,1478,1479],{},"último estado"," del vehículo (posición actual, velocidad, estado del motor encendido\u002Fapagado). Esto optimiza las consultas en tiempo real y permite que la interfaz web cargue instantáneamente.",[1452,1482,1484],{"id":1483},"_4-integración-premium-con-telegram","4. Integración Premium con Telegram",[39,1486,1487,1488,1491],{},"Para evitar la necesidad de desarrollar y mantener una aplicación móvil dedicada (que consume batería y recursos del teléfono), decidí usar la ",[46,1489,1490],{},"API de Telegram"," como mi centro de control y notificaciones:",[161,1493,1494,1500,1506],{},[164,1495,1496,1499],{},[46,1497,1498],{},"Eventos del Motor:"," Al encender o apagar el vehículo, el servidor procesa el cambio de estado e inmediatamente me envía una alerta a Telegram.",[164,1501,1502,1505],{},[46,1503,1504],{},"Fin de Viaje:"," Al detectar que el motor se ha apagado después de un periodo de movimiento, el sistema calcula la duración total del viaje y envía un resumen de viaje finalizado.",[164,1507,1508,1511,1512,1515],{},[46,1509,1510],{},"Ubicación en tiempo real:"," Cuando quiero saber exactamente dónde está el auto, el servidor genera una ubicación interactiva usando la función nativa de Telegram de ubicación en tiempo real que ",[46,1513,1514],{},"expira automáticamente a las 6 horas",". Esto es sumamente útil para monitorear el trayecto sin sobrecargar el servidor de peticiones continuas.",[62,1517],{},[65,1519,1521],{"id":1520},"estado-actual-y-el-futuro-un-posible-saas","Estado Actual y el Futuro: ¿Un posible SaaS?",[39,1523,1524],{},"Hoy en día, la plataforma está 100% operativa y estable. Ha pasado de ser un simple experimento técnico a convertirse en una herramienta de uso diario personal y familiar. Nos da la tranquilidad de saber exactamente dónde están nuestros vehículos en todo momento, sin depender de intermediarios y manteniendo un control absoluto de nuestros datos de localización.",[39,1526,1527],{},"Lo mejor de todo es que el costo operativo mensual es ridículamente bajo en comparación con lo que cobraban las empresas tradicionales de seguridad vehicular.",[39,1529,1530,1531,1534],{},"Si bien comenzó como un proyecto casero, no descarto la posibilidad de abrir las puertas de la plataforma a más usuarios en el futuro y convertirlo en un ",[46,1532,1533],{},"SaaS (Software as a Service) económico",". Existe un mercado enorme de personas cansadas de pagar tarifas excesivas por plataformas de GPS lentas, obsoletas y con malas instalaciones físicas.",[39,1536,1537],{},"Desarrollar esta solución no solo me ahorró dinero, sino que me demostró una vez más que la curiosidad y la tecnología adecuada pueden desarmar por completo un modelo de negocio abusivo.",[889,1539,1540],{},"html pre.shiki code .s7zQu, html code.shiki .s7zQu{--shiki-light:#39ADB5;--shiki-light-font-style:italic;--shiki-default:#89DDFF;--shiki-default-font-style:italic;--shiki-dark:#89DDFF;--shiki-dark-font-style:italic}html pre.shiki code .sTEyZ, html code.shiki .sTEyZ{--shiki-light:#90A4AE;--shiki-default:#EEFFFF;--shiki-dark:#BABED8}html pre.shiki code .sMK4o, html code.shiki .sMK4o{--shiki-light:#39ADB5;--shiki-default:#89DDFF;--shiki-dark:#89DDFF}html pre.shiki code .sfazB, html code.shiki .sfazB{--shiki-light:#91B859;--shiki-default:#C3E88D;--shiki-dark:#C3E88D}html pre.shiki code .spNyl, html code.shiki .spNyl{--shiki-light:#9C3EDA;--shiki-default:#C792EA;--shiki-dark:#C792EA}html pre.shiki code .sbssI, html code.shiki .sbssI{--shiki-light:#F76D47;--shiki-default:#F78C6C;--shiki-dark:#F78C6C}html pre.shiki code .s2Zo4, html code.shiki .s2Zo4{--shiki-light:#6182B8;--shiki-default:#82AAFF;--shiki-dark:#82AAFF}html pre.shiki code .sHdIc, html code.shiki .sHdIc{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#EEFFFF;--shiki-default-font-style:italic;--shiki-dark:#BABED8;--shiki-dark-font-style:italic}html pre.shiki code .swJcz, html code.shiki .swJcz{--shiki-light:#E53935;--shiki-default:#F07178;--shiki-dark:#F07178}html pre.shiki code .sHwdD, html code.shiki .sHwdD{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#546E7A;--shiki-default-font-style:italic;--shiki-dark:#676E95;--shiki-dark-font-style:italic}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}",{"title":191,"searchDepth":205,"depth":205,"links":1542},[1543,1544,1545,1551],{"id":948,"depth":205,"text":949},{"id":970,"depth":205,"text":971},{"id":1409,"depth":205,"text":1410,"children":1546},[1547,1548,1549,1550],{"id":1454,"depth":211,"text":1455},{"id":1461,"depth":211,"text":1462},{"id":1472,"depth":211,"text":1473},{"id":1483,"depth":211,"text":1484},{"id":1520,"depth":205,"text":1521},"La historia de cómo decidí desarrollar mi propia plataforma de rastreo satelital tras descubrir un cobro excesivo y una mala instalación en mi vehículo, y cómo terminó siendo una solución familiar eficiente con Node.js y Telegram.","\u002Fhero\u002Fgps-tracking-banner.png",{},{"title":14,"description":1552},"AE0O5TW6enlXIw-IbX53cbJVCGS7JxtuXPn_ZBMNCjk",1788811694316]