<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>GitHub Actions by Manuel Toral</title><link>/tags/github-actions/</link><description>Recent content in GitHub Actions by Manuel Toral</description><language>es</language><lastBuildDate>Wed, 15 Apr 2026 00:00:00 -0600</lastBuildDate><atom:link href="/tags/github-actions/index.xml" rel="self" type="application/rss+xml"/><item><title>EcoBici Collector: MLOps en GCP y Supabase</title><link>/blog/ecobici-collector/</link><pubDate>Wed, 15 Apr 2026 00:00:00 -0600</pubDate><guid>/blog/ecobici-collector/</guid><description>&lt;p&gt;He construido y publicado &lt;strong&gt;&lt;a
href="https://github.com/jmtoral/ecobici-collector"
target="_blank" rel="noopener"
&gt;EcoBici Collector&lt;/a
&gt;
&lt;/strong&gt;, un pipeline automatizado para recolectar datos de disponibilidad de bicicletas en EcoBici (CDMX) y entrenar un modelo de clasificación. El objetivo es simple: estimar la probabilidad de encontrar al menos una bicicleta funcional en una estación dada, a cualquier hora del día y día de la semana.&lt;/p&gt;
&lt;h3 id="el-problema-con-la-programación-convencional"&gt;¿El problema con la programación convencional?&lt;/h3&gt;
&lt;p&gt;El proyecto inició usando los &lt;em&gt;cron jobs&lt;/em&gt; de &lt;strong&gt;GitHub Actions&lt;/strong&gt; como único programador (&lt;em&gt;scheduler&lt;/em&gt;). Aunque funcionaba, rápidamente me topé con la realidad: las colas compartidas en GitHub provocan retrasos inconsistentes de entre 5 y 30 minutos. Para un pipeline de recolección de datos que necesita coherencia temporal (cada 15 minutos exactos), esta variabilidad destruía la integridad del dataset.&lt;/p&gt;
&lt;h3 id="la-solución-arquitectura-de-dual-trigger"&gt;La solución: Arquitectura de &amp;ldquo;Dual Trigger&amp;rdquo;&lt;/h3&gt;
&lt;p&gt;Para garantizar precisión sin sacrificar redundancia, rediseñé la arquitectura hacia un enfoque híbrido:&lt;/p&gt;
&lt;ul&gt;
&lt;li&gt;&lt;strong&gt;Google Cloud Scheduler (Principal)&lt;/strong&gt;: Ejecuta una solicitud HTTP POST cada 15 minutos con precisión de segundos.&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;Supabase (Backend)&lt;/strong&gt;: Sirve como base de datos PostgreSQL y Edge Function para recibir la invocación y persistir los datos (snapshots).&lt;/li&gt;
&lt;li&gt;&lt;strong&gt;GitHub Actions (Respaldo y Mantenimiento)&lt;/strong&gt;: Se encarga de reentrenar semanalmente el modelo usando los datos de Supabase, generar nuevas versiones (&lt;code&gt;.pkl&lt;/code&gt;) y funge como disparador de respaldo por si el Scheduler de GCP llega a fallar.&lt;/li&gt;
&lt;/ul&gt;
&lt;h3 id="el-modelo"&gt;El Modelo&lt;/h3&gt;
&lt;p&gt;Recolectar millones de filas de GBFS no sirve de mucho si no se traducen en un producto analítico. Cada domingo a las 3:00 am, el cluster se despierta, extrae la historia completa y entrena un modelo que utiliza características temporales (&lt;em&gt;sine/cosine embeddings&lt;/em&gt; de horas y días) y geográficas para responder la gran pregunta: &lt;em&gt;&amp;ldquo;Si voy a la estación X un martes a las 8am, ¿qué tan probable es que logre llevarme una bici?&amp;rdquo;&lt;/em&gt;&lt;/p&gt;
&lt;p&gt;El stack demuestra cómo diseñar sistemas escalables casi gratuitos: combinando el tier libre de GCP, Supabase y Streamlit Community Cloud para el dashboard final.&lt;/p&gt;
&lt;p&gt;&lt;a
href="https://github.com/jmtoral/ecobici-collector"
target="_blank" rel="noopener"
&gt;&lt;strong&gt;Puedes ver el código completo y la arquitectura en mi repositorio de GitHub.&lt;/strong&gt;&lt;/a
&gt;
&lt;/p&gt;</description></item></channel></rss>