Bitácora del proyecto¶
Historial de desarrollo de los tres portales, reconstruido a partir de las bitácoras originales de EMA Saladillo (docs/bitacora_ema_saladillo*.docx, hoy migradas acá) y del historial de commits de los tres repositorios. Desarrollado en sesiones colaborativas con Claude (Anthropic).
Marzo 2026 — Origen: EMA Saladillo¶
Contexto¶
El proyecto nace en el espacio curricular Laboratorio de Industrias, 7° Año Técnico Químico, EEST N°1 "Gral. Savio" (Saladillo), a cargo del Ing. Luciano Lamaita. La EEST N°1 tiene una Estación Meteorológica Automática (EMA) marca Tecmes que transmite por telemetría al Sistema Nacional de Información Hídrica (SNIH) del INA — el objetivo inicial fue acceder a esos datos de forma programática, independiente del sitio oficial. Durante el desarrollo se descubrieron tres estaciones adicionales con datos públicos en Saladillo (CFR, Defensa Civil, Clima Saladillo), lo que amplió el alcance hacia una red meteorológica local con análisis espacial.
v1 — Arquitectura inicial¶
Patrón ETL corrido localmente en una PC Windows: scrapers Python → Supabase (PostgreSQL) → dashboard HTML estático. Cada estación con un método de acceso distinto:
- EET N°1:
POSTaMuestraDatos.aspx/LeerDatosActuales— el certificado SSL del servidor está vencido (verify=False), y el ID de estación correcto (284094) resultó ser la concatenación del código de red RMET (28) con el ID real (4094) — llamar solo con4094devolvía vacío. - CFR Saladillo: scraping HTML con BeautifulSoup.
- Defensa Civil: sin ningún endpoint de datos — toda la información está superpuesta como texto sobre una imagen JPG de cámara Meteobridge. Se resolvió con recorte + OCR (Tesseract), ~95% de precisión.
- Clima Saladillo: los datos se cargan por AJAX; el endpoint oculto (
stationDataAjax.php) se descubrió inspeccionando el JavaScript de la página.
Deduplicación: hora_ar(), una función PostgreSQL IMMUTABLE que trunca el timestamp a la hora, permite un índice único y evita duplicados por múltiples ejecuciones del scraper.
21–22 de marzo — v2: cuarta estación y análisis de calidad de datos¶
- Se suma EMA-CS (Clima Saladillo, barrio Falucho) y se establece la nomenclatura oficial
EMA-XXXen todo el sistema. - Windows Task Scheduler fallaba con código
0x80070002en todas las tareas: el Programador de Tareas corre en un contexto donde elPATHno incluye la instalación de Python del usuario — se resolvió usando la ruta completa del ejecutable. - Diagnóstico y limpieza de EMA-DC: los errores sistemáticos venían del OCR — la conversión a escala de grises pierde el texto blanco sobre fondos de color. Se resolvió extrayendo directamente los píxeles claros (
R,G,B > 175) antes de binarizar, más una validación en dos capas (rango físico plausible + delta temporal máximo) que descarta valores imposibles antes de insertar. Se eliminaron a mano 5 temperaturas y 3 valores de lluvia erróneos ya acumulados. - Armonización temporal: nace la vista
v_ema_armonizada(normaliza las 4 fuentes a una estructura común por hora) yv_temperatura_comparativa(pivotada, una columna por estación). - Análisis microclimático: con los primeros datos se observa que EMA-CS marca sistemáticamente +2 a +4°C de noche y -1 a -2°C de día respecto al promedio de las otras tres — hipótesis de isla de calor urbano (el barrio Falucho es zona urbana consolidada). También se detecta un offset de ~8 hPa en la presión de EMA-EET, sin explicación confirmada (¿presión absoluta vs. reducida a nivel del mar?).
- Dashboard v4: pestaña de análisis espacial con interpolación IDW (Inverse Distance Weighting, p=2) e isolíneas por Marching Squares; modo comparativo rediseñado con tabla de diferencias por color.
23 de marzo — v3: GitHub Actions y primer deploy público¶
- Migración de Windows Task Scheduler a GitHub Actions: el sistema dependía de una PC física encendida. Repo público creado (
github.com/lemeit/ema-saladillo); credenciales de Supabase movidas a Secrets de GitHub (SUPA_URL,SUPA_KEY). - Dos cron schedules: scrapers horarios (
5 * * * *) y ping diario a Supabase a las 11:05 UTC / 08:05 AR (evita que el plan gratuito pause el proyecto por inactividad). - Corrección de coordenadas: EMA-CFR y EMA-DC tenían las coordenadas intercambiadas desde el inicio del proyecto.
- Primer dashboard público:
lemeit.github.io/ema-saladillo(GitHub Pages, dashboard movido adocs/index.htmlpor la convención de esa plataforma).
Agosto 2026 — Nace Aire Saladillo y se arma la red de 3 portales¶
19–20 de agosto — Aire Saladillo (aq.lemeit.ar)¶
Nuevo proyecto, purpleair-saladillo: sensores PurpleAir de calidad del aire (PM1.0/PM2.5/PM10) en escuelas y jardines, sobre Cloudflare D1 + Workers + Pages desde el día uno (arquitectura más moderna que la de EMA en ese momento, que todavía corría sobre Supabase). Rango temporal, bandas AQI, canales A/B de PM y export CSV desde el inicio.
21–24 de agosto — Diseño unificado y agua-saladillo¶
- Etapa 1 de diseño unificado: nace design.lemeit.ar (
lemeit-theme.css+lemeit-common.js) — paleta, tipografía (JetBrains Mono), selector de portales y footer versionado compartidos entre aq y ema. - Pestaña Mapa (Leaflet) con flip-cards y mini-gráfico en aq; canales A/B extendidos a PM1.0/PM10 (no solo PM2.5).
- Migración de
agua-saladillo: el dashboard de calidad del agua, que vivía como archivo suelto (docs/agua_saladillo.html) dentro deema-saladillo, se muda a repo propio para evolucionar de forma independiente — tercer portal de la red. agua-saladilloincorpora dataset completo (31 → 52 parámetros tras un merge posterior), normativa comparada CAA/PBA, y un primer GitHub Action de ingesta automática de protocolos PDF usando la API de Gemini para extraer JSON estructurado (con revisión humana obligatoria antes de mergear — los protocolos no tienen un formato de tabla único, así que la extracción declara su propio nivel de confianza).
21–22 de agosto — Armonización de EMA y migración a Cloudflare D1 (v4)¶
Sesión de alcance ampliado: unificar los tres proyectos sobre la misma infraestructura de Cloudflare.
- Cloudflare Pages para EMA: dashboard movido de
docs/index.html(convención GitHub Pages) a la raíz del repo (Cloudflare Pages no tiene esa restricción); publicado enemas.lemeit.ar. El deploy es manual víawrangler pages deploy— a diferencia deindex.html/api.html, no hay deploy automático por push en este proyecto. - Migración Supabase → Cloudflare D1: se exportó el historial completo (30.213 filas de las 4 tablas) y se importó a una tabla unificada
mediciones(columnaestacion). Se descartaron 9 filas de EMA-DC por un timestamp con error de OCR confirmado como dato corrupto real. - Worker de compatibilidad (
ema-saladillo-api): expone D1 con el mismo formato de consulta que usaba PostgREST/Supabase (select=,order=,limit=,columna=eq.valor) — el único cambio necesario en el dashboard fue apuntarSUPA_URLal Worker, sin tocar la lógica de consultas. - Scrapers actualizados: los 4 scripts pasan a escribir en D1 vía un helper común (
scrapers/d1_writer.py), mismo patrón que ya usabapurpleair-saladillo. Se detectaron y corrigieron 3.796 filas duplicadas en el historial migrado (elignore-duplicatesde Supabase nunca había funcionado realmente, porque las tablas viejas no tenían restricción única) y un403 Forbiddencausado por saltos de línea de sobra en los secrets de Cloudflare al pegarlos en GitHub (%0Aen la URL de la API) — resuelto con.strip(). - El workflow de scrapers se había roto en una sesión anterior de "armonización de repositorios" (el archivo terminó en
github/workflows/, sin el punto) — restaurado a su ruta correcta.
23–28 de agosto — Paridad visual, AirGradient, mapas¶
- Header y tabs homogeneizados entre los tres portales; footer compartido con logos de proveedor/institución.
- AirGradient se suma como segundo proveedor de sensores en Aire Saladillo (además de PurpleAir), reutilizando las mismas tablas D1 (columna
proveedor). Se agrega CO2 y luego NOx al dashboard. - Proxy de tiles CARTO: CARTO empezó a exigir API key para servir tiles de mapa; en vez de exponerla en el HTML público, los tres Workers pasan a actuar de proxy, agregando la key del lado del servidor (
CARTO_API_KEYcomo secret de Wrangler) — patrón replicado igual en los tres portales el mismo día (28/08). - Modo claro por defecto en los tres portales (antes dependía del tema del sistema operativo).
- Fix de overflow-x (el mapa Leaflet arrastraba toda la página al costado al hacer pan) — aplicado a los tres portales.
29–31 de agosto — API pública, fixes y esta wiki¶
- Fix:
scrapers/supabase_ping.py(EMA) tenía unNameError—SUPA_URL/SUPA_KEYsolo existían como valores anidados dentro del diccionarioPROYECTOS, nunca como nombres a nivel de módulo, lo que hacía fallar el jobpingde GitHub Actions apenas arrancaba. - Aire Saladillo: Mapa pasa a ser la pestaña por defecto (antes era Actuales); fix de un bug de apilamiento donde el dropdown "Portales" quedaba parcialmente tapado por el mapa Leaflet (los paneles internos de Leaflet tienen
z-indexde hasta 650–700 y, sin un contexto de apilamiento propio en#mapa-container, competían directamente contra el header) — resuelto dándole al contenedor del mapa su propioposition: relative; z-index: 1. - API pública en Aire Saladillo y EMA Saladillo: los endpoints de lectura que ya alimentaban cada dashboard (
/api/sensores,/api/ultimas,/api/historicoen aq;/rest/v1/mediciones_*y las vistas armonizadas en ema) se documentaron y ampliaron para uso de terceros — sin necesidad de infraestructura nueva, ya eran de lectura pública con CORS abierto:desde/hastacomo rango de fechas absoluto en UTC, alternativa a la ventana relativa (range/horas) que ya existía.&formato=csven todos los endpoints de lectura, para descargar CSV directo sin parsear JSON.- Página de documentación (
api.html) publicada en cada portal — aq.lemeit.ar/api.html y emas.lemeit.ar/api.html.
- Corrección de dominio: se detectó y corrigió una referencia equivocada a
ema.lemeit.ar(dominio inexistente) en vez del dominio realemas.lemeit.ar, introducida en la documentación nueva de la API de EMA. - Esta wiki: se arma
ambiental-wiki, siguiendo el mismo patrón que ya usa DVBA (wiki-src/con MkDocs Material, build automático vía GitHub Actions), para tener documentación técnica y bitácora unificadas de los tres portales en un solo lugar — hasta acá solo existía para EMA, como 4 documentos Word sueltos (bitacora_ema_saladillo_v1..v4.docx, migrados acá).
Septiembre 2026 — Admin de visitas, enlaces a la wiki, PWA de Aire Saladillo, rediseño de Historial/Gráfico y tema retro de la wiki¶
- Administración de visitas (Aire Saladillo): nueva tabla
visitasen D1 (timestamp, ruta, país víarequest.cf, user-agent/referrer), escrita en segundo plano víactx.waitUntilpara no demorar la respuesta real. Dos endpoints de solo lectura (/api/admin/visitas,/api/admin/resumen) protegidos por un secretADMIN_KEYenviado como headerX-Admin-Key— mismo esquema que ya usabaX-Ingest-Keyen/api/ingest-ahora— en vez de?key=...en la URL, para no dejar la clave pegada en el historial del navegador ni en logs de acceso/proxies intermedios (como consecuencia, los endpoints admin ya no se pueden abrir pegando la URL en el navegador, hace faltacurlo similar que pueda mandar headers). Se sumóscripts/ver-visitas.ps1para consultarlas desde PowerShell sin pelearse con que ahícurles un alias deInvoke-WebRequestcon una sintaxis de headers distinta a la delcurlreal (hace faltacurl.exeoInvoke-WebRequest -Headers @{...}). - Enlaces cruzados a la wiki: el footer compartido (
lemeit-common.js, un solo cambio actualiza los tres portales) y la sección "Acerca de" de cada uno pasan a enlazar a wiki.lemeit.ar — hasta ahora la wiki no se mencionaba desde ningún portal.- De paso se investigó por qué la guía de DVBA "se abre como una app" en vez de como página web: la PWA instalada de DVBA tiene
"scope": "./"en sumanifest.json, que servido desde/DVBA/en GitHub Pages cubre toda la ruta — incluida/DVBA/wiki/— así que la app instalada "adopta" también los links de esa wiki.
- De paso se investigó por qué la guía de DVBA "se abre como una app" en vez de como página web: la PWA instalada de DVBA tiene
- Historial (Aire Saladillo) — reporte configurable y export PDF: se agregó filtro de rango de fechas personalizado (
desde/hasta, ya soportado por la API), filtro de parámetros por checkboxes y, más adelante en la misma tanda, columnas de PM1.0 y PM10 (ambos proveedores ya las reportaban, no se mostraban). Se sumó primero una vista "Reporte" en pantalla —toggle Clásica/Reporte, texto plano monoespaciado estilo resumen climatológico NOAA (ejemplo real visto en chajarialdia.com.ar/estacion), con fondo papel fijo independiente del tema claro/oscuro del resto del sitio— y luego exportación real a PDF con jsPDF: texto seleccionable en fuente Courier (no una captura de pantalla), con paginación automática y repetición de encabezados de columna en cada página nueva.- Fix: la carga de jsPDF desde cdnjs falló en el primer despliegue (probablemente un bloqueador de contenido del navegador) y tiraba una excepción sin manejar (
window.jspdfindefinido) al hacer click en "Descargar PDF". Se agregó un script de respaldo a jsDelivr si cdnjs falla, y un aviso claro al usuario si ninguna de las dos CDN está disponible, en vez de romper la página. - Simplificación: la vista "Reporte" en pantalla, tras uso real, resultó redundante — el PDF ya se veía siempre en blanco/formato reporte, así que tener además un toggle Clásica/Reporte en pantalla (con el fondo invertido respecto de lo esperado) sumaba una opción de más. Se sacó el toggle: la tabla en pantalla usa siempre el formato normal del sitio, y el estilo reporte quedó reservado exclusivamente al PDF. Se agregó
accent-colora los checkboxes del sitio (el azul por defecto del navegador desentonaba con la paleta), y se cambió "Estación" por "Sitio" en el PDF y demás lugares que nombran la ubicación de un sensor, para no chocar con "estación meteorológica" del proyecto hermano EMA. El PDF, además de descargarse, ahora también se abre en una pestaña nueva (blob URL, visor nativo del navegador) dentro del mismo click, para poder verlo sin ir a buscar el archivo descargado.
- Fix: la carga de jsPDF desde cdnjs falló en el primer despliegue (probablemente un bloqueador de contenido del navegador) y tiraba una excepción sin manejar (
- Gráfico (Aire Saladillo) — comparación de sitios: nuevo modo ("Comparar sitios") donde se elige un parámetro — no varios, para no mezclar unidades en el mismo eje — y varios sitios a la vez, cada uno dibujado como una línea de color distinto sobre el mismo eje Y. Los sitios no miden todos en el mismo instante exacto (cada uno hace su propio polling), así que el eje de tiempo se arma uniendo las marcas de todos los sitios elegidos, con
spanGapspara no cortar las líneas en los huecos propios de cada uno — una limitación conocida, a mejorar a futuro con un agrupado por intervalos redondeados. - PWA instalable (Aire Saladillo): el dashboard pasa a poder instalarse como app —
manifest.json, íconos (incluido uno "maskable" para Android, con el contenido confinado a la zona segura central ya que el sistema operativo le aplica su propia máscara/forma) y un service worker (sw.js) que cachea únicamente el "app shell" (HTML, manifest, íconos). Los datos en vivo (API propia) y las librerías de CDN (Chart.js, jsPDF, Leaflet, fuentes, design.lemeit.ar) se excluyen deliberadamente del cacheo y van siempre directo a red, para no quedar con lecturas ni versiones de librerías desactualizadas al abrir la app sin señal. Al abrirse instalada — detectado víamatchMedia("(display-mode: standalone)")onavigator.standalone(Safari/iOS, que no soporta bien esa media query) — o con?pwa=1en la URL (el propiostart_urldel manifest, útil también para probarlo a mano desde el navegador), arranca directo en la pestaña "Actuales": los relojes tipo velocímetro de AQI son la portada más útil en el celular, a diferencia del navegador de escritorio normal, que sigue abriendo en "Mapa". Mismo tratamiento planeado a futuro para EMA Saladillo. - Gráfico (Aire Saladillo) — zoom por selección: se sumó
chartjs-plugin-zoom(+hammerjspara gestos táctiles) al Gráfico, tanto en el histórico de un sitio como en "Comparar sitios": arrastre del mouse para zoom por selección ("box zoom"), rueda para acercar/alejar, pellizco táctil en celular, y un botón "Reset zoom" que aparece solo cuando hay zoom aplicado. Se cargan con el mismo patrón de doble CDN con respaldo (jsDelivr primero, cdnjs si el primero falla) ya usado para jsPDF.- Fix: en el primer despliegue, arrastrar para seleccionar terminaba haciendo zoom hacia el lado contrario de donde se arrastraba — y peor cuantos más zooms anidados (zoom sobre un zoom ya aplicado) se hacían seguidos. La causa: el paneo del gráfico (mover la vista arrastrando) y el zoom por selección estaban atados al mismo gesto — arrastre simple del mouse, sin modificador — y competían entre sí; el paneo corría el eje mientras se armaba el rectángulo de selección, así que el resultado final quedaba desplazado respecto de lo que realmente se había seleccionado. Se resolvió exigiendo Ctrl+arrastre para panear, dejando el arrastre simple exclusivamente para la selección de zoom. De paso se cambió el eje X de una escala de categorías (labels de texto ya formateados) a una escala numérica (
linear, con los horarios mostrados igual que antes vía un callback de ticks), porquechartjs-plugin-zoomno soporta bien los zooms anidados sobre un eje de categorías — se verificó con una prueba de 3 zooms sucesivos que antes del fix terminaban en una franja incorrecta y después quedan correctamente anidados uno dentro del otro.
- Fix: en el primer despliegue, arrastrar para seleccionar terminaba haciendo zoom hacia el lado contrario de donde se arrastraba — y peor cuantos más zooms anidados (zoom sobre un zoom ya aplicado) se hacían seguidos. La causa: el paneo del gráfico (mover la vista arrastrando) y el zoom por selección estaban atados al mismo gesto — arrastre simple del mouse, sin modificador — y competían entre sí; el paneo corría el eje mientras se armaba el rectángulo de selección, así que el resultado final quedaba desplazado respecto de lo que realmente se había seleccionado. Se resolvió exigiendo Ctrl+arrastre para panear, dejando el arrastre simple exclusivamente para la selección de zoom. De paso se cambió el eje X de una escala de categorías (labels de texto ya formateados) a una escala numérica (
- Roadmap documentado: expansión planeada a 4 sensores nuevos de aire (2 en Saladillo, 2 en el partido de 25 de Mayo — una escuela urbana y una rural en cada partido), y la necesidad de sumar una estación EMA en 25 de Mayo para que la red meteorológica cubra la misma región. Esto implica revisar a futuro el nombre "EMA Saladillo" (la "S" deja de ser literal) — ver la sección Roadmap de Aire Saladillo y de EMA Saladillo.
- Tema retro/minimalista para esta wiki: se reemplazó el tema Material genérico (teal, fuente del sistema) por una piel propia (
lemeit-retro.css) que reusa la paleta y tipografía de los tres dashboards — JetBrains Mono en todo, naranja#D9691F, fondos cálidos claro/oscuro —, con encabezados en mayúsculas, esquinas rectas y letra más chica. De paso se sacó el contador de estrellas/forks del header (ruido para una wiki chica, aunque el link al repo se conserva) y se reemplazó el ícono aislado de GitHub del footer por una línea de texto con el sitio, el stack usado (MkDocs Material, Cloudflare Pages, GitHub Actions) y el link al repo.
15 de septiembre — EMA-25C vía Weather Underground, rebrand a EMAS y renombre del repo¶
- Quinta estación, EMA-25C (25 de Mayo): se suma 25Clima (
25clima.ar, operada por un tercero — N-TecLab / SS Desarrollos, parte del medio digital 25 Digital), la primera estación de la red fuera del partido de Saladillo. No siendo una estación propia del proyecto, se integra vía la API pública de Weather Underground, donde 25Clima ya publica sus datos bajo el station IDIDEMAY14.- El obstáculo: Weather Underground solo habilita la generación de una API key de lectura si la cuenta tiene al menos un dispositivo propio "activo" (con datos reales recientes) — un dispositivo simplemente registrado, sin datos, no alcanza. Sin una estación física propia para registrar, se resolvió activando un dispositivo placeholder con datos reales de un sensor ya existente en la red (
tools/subir_a_wu.py, sube lecturas de un sensor PurpleAir vía el protocolo clásico de upload de WU, convertidas a unidades imperiales). Es dato ambiental genuino, no un truco para eludir la restricción. - Nuevo scraper
scrapers/wu_25demayo.py: consulta/v2/pws/observations/currentde la API v2 de WU y mapea la respuesta a los 10 parámetros ya usados por el resto de las estaciones (Temperatura, Sensación Térmica, Punto de Rocío, Humedad, Presión, Velocidad Viento, Ráfaga, Dirección del Viento, Lluvia Diaria, Radiación Solar), guardando en D1 bajoEMA-25C. Sumado al cron horario existente (scrapers.yml). - Worker de compatibilidad:
TABLE_MAP,ARMONIZADA_NOMBREyCANON_MAPse extienden conmediciones_25c/EMA-25C/m25; se corrige además la regex de rutas (/^\/rest\/v1\/([a-z_]+)$/→ con0-9), que no matcheabamediciones_25cpor tener un dígito. - Dashboard: la estación se integra en el selector, la comparativa (ahora 5 columnas/cards), el panel de detalle en "Estaciones", la tarjeta "Estaciones de la red" en "Acerca de" y el mapa — pero a propósito no en la interpolación espacial (
ESP_FUENTES, mapa de calor): a ~80 km del resto del cluster, sumarla ahí daría un gradiente sin sentido geográfico. - Queda pendiente contactar al operador de 25Clima para confirmar su carácter permanente en el dashboard público — se decidió integrar igual mientras tanto, dejándolo explícito en el texto público como estación de terceros "en incorporación".
- El obstáculo: Weather Underground solo habilita la generación de una API key de lectura si la cuenta tiene al menos un dispositivo propio "activo" (con datos reales recientes) — un dispositivo simplemente registrado, sin datos, no alcanza. Sin una estación física propia para registrar, se resolvió activando un dispositivo placeholder con datos reales de un sensor ya existente en la red (
- Rebrand: "EMA Saladillo" → "EMAS": con una estación fuera de Saladillo, la "S" del nombre deja de ser literal — pasa a leerse como el plural de "EMA" y el proyecto se presenta como regional. Se actualiza en
index.html,api.html, README, el switcher de portales (lemeit-common.js) y esta wiki. De paso se saca la referencia a "Laboratorio de Industrias, 7° año Técnico Químico" del texto público del sitio (footer y "Acerca de") — el proyecto arrancó ahí pero ya no es un proyecto de esa materia en particular; se reemplaza por lenguaje genérico ("proyecto educativo") y, en la bio del autor, el mismo criterio que ya usaba Aire Saladillo ("Docente de Física y Química"). - Renombre del repositorio:
lemeit/ema-saladillopasa alemeit/emasen GitHub (rename manual del usuario, que conserva un redirect automático de GitHub para git y para la web) y la carpeta local homónima. Se actualizan los links propios al repo enindex.htmly en 3 archivos de esta wiki — se dejan sin tocar las menciones históricas de esta misma bitácora, que documentan el nombre tal como era en su momento. - Fix de documentación: la wiki documentaba
aq.lemeit.ar/api/*como base URL de la API de Monitoreo Ambiental Escolar, pero esa ruta nunca estuvo conectada al Worker en Cloudflare Pages (sin[routes]ni_redirects) — confirmado pidiendo la URL directo y viendo que devolvía elindex.htmlde la SPA en vez de JSON. Se corrige a la URL real del Worker (purpleair-saladillo-api.fisicai-eureka-01.workers.dev), queindex.html/api.htmlya usaban correctamente. - Bug propio, panel de EMA-25C perdido en silencio: el panel de detalle de la nueva estación en la pestaña "Estaciones" se armó en un script de edición que nunca llegó a guardar el archivo (terminaba sin el
writefinal) — el script no tiraba error, así que el cambio se creyó aplicado durante buena parte de la sesión hasta que un pedido de revisión del usuario lo puso en evidencia. Recordatorio: verificar el resultado en disco, no solo que el script haya corrido sin errores. - PWA y navegación entre portales: cambiar de portal desde la app instalada (ej. de aq a emas) navega a un dominio distinto dentro de la misma ventana, y el sistema lo trata como "salida de la app instalada" (barra mínima con URL y una X para volver) — comportamiento estándar de Chromium/Windows para PWAs ante una navegación fuera de su
scope, no un bug de un portal en particular. Se probó abrir el link contarget="_blank"para evitarlo, pero eso hace que el cambio de portal abra una pestaña de navegador aparte, peor experiencia que quedarse "atrapado" con la barra de salida — se revirtió a la navegación original. - Logo de proveedor solo donde corresponde: el pie de página compartido (
lm-footer) mostraba el logo de la última estación seleccionada incluso en pestañas generales sin relación a una estación puntual (ej. "Acerca de" mostrando el logo de Tecmes por defecto). Se simplifica: el pie compartido ya no muestra logos de proveedor en ninguna pestaña, y quedan exclusivamente en la pestaña "Estaciones" (panel de cada una) y en la tarjeta "Estaciones de la red" de "Acerca de". - Logo de 25Clima: sin logo propio disponible en un formato limpio, se usa un recorte del isologo tal como lo publica 25Digital (el medio del que 25Clima es un producto) en sus propias notas — estética más amateur que los logos de las otras 4 estaciones, pero es lo que hay, y es la atribución real a la fuente.
16 de septiembre — Mapas multi-partido, procedencia del Collective y materiales para la reunión del 16/9¶
- Mapas centrados en ambos partidos: tanto en emas como en aq, el mapa arrancaba centrado en Saladillo con zoom fijo (13), dejando la estación/futuros sensores de 25 de Mayo fuera de vista por defecto — pensado para la reunión del 16/9, con gente de ambos partidos presente, se cambia a
fitBoundsde Leaflet. En emas, sobre las coordenadas fijas de las 5 estaciones; en aq, sobre el centroide de los sensores realmente desplegados más un punto de referencia en 25 de Mayo (mismo que usa EMA-25C), así el encuadre se sigue ajustando solo a medida que se sumen sensores reales ahí. - Fix de contexto de apilamiento en emas: el mapa Leaflet de la pestaña "Mapa" tapaba el desplegable de portales al abrirla — mismo bug que ya se había resuelto antes en aq (
position:relative+z-indexen el contenedor del mapa, para que los paneles internos de Leaflet no escapen de su propio contexto de apilamiento). Se aplica el mismo fix. - Logos de estaciones en emas, desproporcionados: al agregar logos reales para las 4 estaciones que todavía mostraban un emoji (TECMES, CFR, Defensa Civil, Clima Saladillo), se los limitó solo por ancho (
max-width) — para logos cuadrados eso los renderizaba mucho más altos que el de 25Clima (un recorte horizontal), quedando desproporcionados entre sí. Se pasa a limitar por alto (max-height) conobject-fit:contain. - Radiación solar faltante en histórico/gráfico: CFR, Clima Saladillo y EMA-25C sí reportan radiación solar (está en su
params), pero faltaba enparamHistorial— el objeto que puebla los selectores de parámetro de Histórico y Gráfico — así que no aparecía como opción para esas 3 estaciones pese a tener el dato. - Procedencia de los 4 PurpleAir Flex: hasta ahora no figuraba en ningún lado (ni el README de purpleair-saladillo ni la presentación institucional) que esas unidades fueron una donación de PurpleAir Collective, convocatoria de mitad de 2024 en base a una propuesta de necesidad presentada por el autor — el proyecto quedó en 2° puesto de esa ronda. Se documenta en el README del repo y en la presentación (slide de antecedentes).
- Sensor Clarity Node-S (EP N°15) fuera de servicio: dejó de enviar datos y debe reemplazarse. Es un sensor de un tercero, externo al proyecto (se muestra en el mapa institucional solo como referencia de contexto) — se actualiza su estado de "activo" a "fuera de servicio" en el mapa de la presentación (color e ícono distintos, y la referencia/leyenda correspondiente).
- Materiales para la reunión del 16/9: se arma un slide de especificaciones técnicas (PurpleAir Flex y AirGradient Open Air, con fotos y una nota de adaptación al caso argentino sobre el cargador) para insertar en la presentación institucional, y una hoja de especificaciones + requerimientos aparte en Word, para entregar impresa al grupo. Ambas, junto con el plan de despliegue y la presentación, se guardan en
docs/del repopurpleair-saladillo(público), para poder linkearlas directamente desde la presentación en vez de solo adjuntarlas sueltas. plan_despliegue.docx— vuelta a la versión imprimible: la versión con fotos (pensada como acompañamiento visual de la presentación) se descarta a favor de la versión original de texto y tablas, mejor para imprimir. Se actualiza el estado de las 5 escuelas (4 confirmadas y el Colegio Secundario Madre Teresa en proceso de reubicación, no la sede original) y el nombre completo de la EES N°2 "Manuel Pardal", para que coincida con el resto de los materiales de la reunión.- Fix del mapa institucional (v3): el recuadro del sensor Clarity fuera de servicio quedaba superpuesto sobre la línea "Colegio Secundario Madre Teresa (en reubicación)" del recuadro "Ciudad de Saladillo" — se reposiciona en un hueco libre del mapa (regenerado desde
render_map_v3.py), sin tocar el resto de las 18 diapositivas. De paso se corrige el link de la hoja de especificaciones en el slide 6, apuntando ahora araw.githubusercontent.comen vez de la vista de blob de GitHub (esta última no tiene visor propio para.docx: siempre cae en una pantalla intermedia con "View raw"). - Renombre de repos para homogeneizar nombres:
purpleair-saladillo→lemeit-aqyemas→lemeit-emas(rename manual del usuario en GitHub, mismo criterio de redirect automático que el rename anterior deema-saladillo→emas). Se actualizan los links propios en elREADME.mdde ambos repos y deagua-saladillo, en las fichas de proyecto deaboutme, y en esta wiki (01-aire-saladillo.md,02-ema-saladillo.md,03-agua-saladillo.md,index.mdy elREADME.mddel repo) — se dejan sin tocar, como siempre, las menciones históricas de esta misma bitácora. El Worker de la API (purpleair-saladillo-api...workers.dev) y la base D1 (purpleair-saladillo) quedan con su nombre actual: son recursos de Cloudflare independientes del nombre del repo, y renombrarlos cambiaría la URL en vivo de la API — queda como una decisión aparte, más delicada. lemeit-aq— SQL suelto ordenado end1/:schema.sqly los 7migration_00X_*.sqlvivían sueltos en la raíz del repo; se mueven a una carpetad1/, mismo patrón que ya usaagua-saladillo(d1/schema.sql). Se actualizan las rutas en los comentarios de los propios archivos de migración (el comandowrangler d1 executese corre parado enworker/, así que pasan a--file=../d1/migration_00X...sql) y las menciones en elREADME.md.- Tercer rename:
agua-saladillo→lemeit-wq: mismo criterio que los dos anteriores, para que los cuatro repos de la red (lemeit-aq,lemeit-emas,lemeit-wq,lemeit-wiki) compartan el prefijolemeit-. Se actualizan los links propios enlemeit-emas/README.md,lemeit-aq/README.md(mención genérica a los repos hermanos), la ficha de proyecto enaboutmey esta wiki (03-agua-saladillo.md,index.md,README.mdyprofe-lemeit/index.md) — de nuevo sin tocar las menciones históricas de esta bitácora ni los recursos de Cloudflare (agua-saladillo-api, la base D1agua-saladillo). api.lemeit.arcompartido entre proyectos, arranque enlemeit-aq: antes de que los 2 sensores nuevos empiecen a mandar datos, se decide un dominio único (api.lemeit.ar) para las tres APIs de la red ambiental (aq, emas, wq), con un prefijo de ruta por proyecto (/aq/,/emas/,/wq/) en vez de un dominio propio por API — usando Cloudflare Routes (no Custom Domains: una Route sí permite que varios Workers convivan en el mismo hostname por patrón de ruta, un Custom Domain ata todo el hostname a un solo Worker). Se arranca por etapas: hoy se liberaapi.lemeit.ar(ver punto siguiente, Eureka Tutor) y se suma la Routeapi.lemeit.ar/aq/*al Worker delemeit-aq— aditivo, el*.workers.devde siempre sigue respondiendo igual, sin corte.emas(Worker ya en producción) ywq(Worker a crear) quedan para una etapa posterior. El Worker despoja el prefijo/aqde la URL en la primera línea delfetch(), antes de cualquier matching de rutas existente, para no tocar el resto de la lógica.- Eureka Tutor cede
api.lemeit.ar: era el único uso previo del dominio (Workereureka-proxy, sin relación con la red ambiental) — se muda aapi-eureka.lemeit.arpara liberar el dominio compartido de arriba. Cambio de código (WORKER_URLenindex.html, comentario del allowlist de orígenes ensrc/worker.js) y de documentación (README.md, diagrama también replicado en esta wiki).
17 de septiembre — Un solo dominio para los 3 portales (app.lemeit.ar), colores de marca por sitio¶
- Título corto en el header de Aire Saladillo para pantallas chicas: "Monitoreo Ambiental Escolar" se cortaba con "..." junto al logo y las luces de estado en el celular. Se agregan dos
<span>dentro del mismo<h1>(título completo / "Aire Escolar", el mismo nombre corto que ya usa el switcher de portales) alternados por@media (max-width:640px), en vez de achicar la tipografía hasta que entre. - Logo AQ → AE, y color de marca por sitio: se elige reemplazar la sigla "AQ" (Air Quality, en inglés, inconsistente con el resto del sitio en español) por AE (Aire Escolar), y se le da un color propio en vez de heredar el naranja
--accentde toda la UI — verde-azulado#009688, el mismo que ya usaban los badges de shields.io del README (.lm-chip-value). Se documenta como token de marca aparte enlemeit-theme.css(--lm-site-aq), separado de--lm-chip-valueaunque hoy compartan valor, para no atar el significado de un componente de badges genérico a la identidad de un sitio puntual. Se regeneran favicon, apple-touch-icon y los 3 íconos de la PWA (192/512/maskable) en el color nuevo, y se actualiza el logoaq.svgde esta misma wiki. - EMAS también suma su propio color de marca: hasta ahora el favicon de EMAS (
#5B8DEF, azul) no salía de ninguna paleta compartida — color suelto, distinto además del naranja--accentque sigue usando el chip del header. Se reemplaza por un celeste de la misma familia tonal que el verde de AQ (#0097A7, token--lm-site-emasenlemeit-theme.css), aplicado al chip del header, el favicon y el logoema.svgde esta wiki. - Un solo dominio para los 3 portales (
app.lemeit.ar): retomando lo anotado ayer (cambiar de portal desde la app instalada abre una barra de navegador en vez de quedarse "adentro" — comportamiento estándar de Chromium ante una navegación fuera delscopede la PWA, no un bug puntual), se arma un Worker nuevo (lemeit-gateway, repogateway) que hace de reverse-proxy:app.lemeit.ar/aq/*,/emas/*y/wq/*sirven el contenido de cada Pages (purpleair-saladillo,ema-saladillo,agua-saladillorespectivamente, pedido directo a su*.pages.devpara no depender de los dominios que pasan a redirigir) bajo un solo origen. Mismo Worker, mismas 4 Routes de Cloudflare, también redirige (301) los 3 subdominios viejos (aq/emas/wq.lemeit.ar) haciaapp.lemeit.ar—aq.lemeit.arya se había divulgado en una reunión, así que redirige desde el día 1; los otros dos se suman igual por consistencia. El switcher de portales (lemeit-common.js,SITES) pasa a apuntar a las URLs nuevas, y elmanifest.jsonde Aire Saladillo amplía suscopede"./"(solo/aq/) a"/"(todoapp.lemeit.ar) — con eso, cambiar de portal desde la app instalada queda contenido dentro de la misma ventana en vez de saltar al navegador. Migración escalonada para no cortar el sitio ya divulgado: Worker deployado y probado primero con los subdominios viejos todavía sirviendo directo, recién después se les sacó el Custom Domain de Pages uno por uno. Por ahora la app instalable sigue siendo una sola (Aire Escolar, la única conmanifest.json/service worker) — EMAS y WQ todavía no tienen el suyo. - Header mobile de EMAS roto por el selector de estaciones: el selector de 5 estaciones + botón de comparativa vivía en la misma fila que el estado de conexión, el switcher de portales y el botón de tema — en pantallas chicas esa fila no entraba, y al no poder achicarse (
flex-shrink:0) terminaba tapando o empujando fuera de la pantalla al resto de los controles. Se lo saca de esa fila y pasa a su propia fila completa debajo del header en mobile, con scroll horizontal — mismo criterio ya usado en el.navde pestañas del propio sitio —, dejando la fila de arriba (logo, título, estado, switcher, tema) siempre visible y sin competir por espacio. - Sacar el Custom Domain de Pages borra el registro DNS, no solo el vínculo: al pasar
emas.lemeit.arywq.lemeit.ardel Custom Domain de su proyecto de Pages a la Route del gateway, ambos quedaron sin resolver (NXDOMAIN, confirmado connslookupcontra1.1.1.1para descartar caché local) — a diferencia deaq.lemeit.ar, que por algún motivo conservó su registro. Cloudflare gestiona (crea y borra) el registro DNS de un Custom Domain de Pages como parte del propio vínculo: sacarlo no deja un registro "suelto" apuntando a otro lado, lo elimina directamente. Se resuelve igual que ya se había resuelto paraapi.lemeit.ar/app.lemeit.ar: un registroAdummy (IP no ruteable192.0.2.1, Proxied) por cada subdominio afectado. - La API de EMAS nunca vivió en realidad en
emas.lemeit.ar: al probarhttps://app.lemeit.ar/emas/rest/v1/v_temperatura_comparativa?horas=48(la URL vieja, ya redirigida) se encontró que abre el dashboard en vez de devolver JSON. Investigando el porqué: el propioindex.htmldel dashboard llama a la API porSUPA_URL = "https://ema-saladillo-api.fisicai-eureka-01.workers.dev"(el*.workers.devdirecto) yapi.htmlya documentaba esa misma URL como Base URL — la única fuente que decíahttps://emas.lemeit.arera esta wiki. Mismo caso ya visto con AQ (la wiki documentaba una ruta que nunca estuvo conectada al Worker real). Se resuelve de raíz igual que se hizo con AQ: se suma una Route de Cloudflare al Worker de EMAS (api.lemeit.ar/emas/*), con el prefijo/emassacándose una sola vez al entrar;index.htmlyapi.htmlpasan a usar esa URL nueva como base, y el*.workers.devoriginal sigue andando igual de respaldo. - Mismo problema en WQ, resuelto igual: revisando si el caso de EMAS se repetía, se confirma que sí —
wq.lemeit.ar/api/coordstampoco sirvió nunca datos reales (el Worker de WQ tampoco tenía Route propia, yindex.htmlya llamaba directo aagua-saladillo-api...workers.dev). Se suma la Routeapi.lemeit.ar/wq/*al Worker de WQ, mismo patrón — con esto los tres portales ya tienen su API bajoapi.lemeit.ar/<portal>. - Barrida de links cruzados a
app.lemeit.ar: pedido explícito de revisar todos los links de los tres portales para que sean coherentes con la consolidación de dominio. Se actualizan los enlaces "Acerca de" de cada portal (a los otros dos hermanos), losapi.html(link de atribución y Base URL), losREADME.mdde los 4 repos (lemeit-aq,lemeit-emas,lemeit-wq,lemeit-design), las 3 fichas de proyecto deaboutme, y03-agua-saladillo.mdde esta wiki — deaq/emas/wq.lemeit.araapp.lemeit.ar/aq,/emas,/wq. Los links viejos seguían funcionando (redirigen), esto es prolijidad — salvo el punto anterior (WQ), que sí era un fix real.lemeit-designde paso suma la sección de colores--lm-site-*al README.
Otros ajustes (sin fecha registrada)¶
- Página "Fuentes, datos y licencias" (
creditos.html) en Aire Escolar: nueva página que documenta de dónde salen el hardware y los datos de referencia del proyecto (PurpleAir, AirGradient, OpenAQ) y bajo qué licencia se usan cada uno, enlazada desde "Acerca de". Pensada tanto para cualquier visitante como para los propios proveedores (los sensores PurpleAir fueron una donación de PurpleAir Collective). Redactada a partir de las páginas públicas de cada proveedor. - Documentación de la API y "Fuentes, datos y licencias" pasan de páginas sueltas a pestañas del portal: el contenido de
api.htmly decreditos.htmlse integró aindex.htmlcomo secciones más (?tab=apiy?tab=creditos). Los dos archivos quedaron como simples redirecciones (<meta http-equiv="refresh">máslocation.replace, que conserva el#hash) para no romper los links externos que ya existían, entre ellos los de esta wiki. Los links aapp.lemeit.ar/aq/api.htmlycreditos.htmlsiguen funcionando, pero conviene apuntar directo aapp.lemeit.ar/aq/?tab=apiy?tab=creditos. La viñeta anterior sobrecreditos.htmldescribe la página tal como era cuando todavía era un archivo aparte. - Bug real, universal: redirect de Cloudflare Pages perdía el prefijo de sección en el gateway:
creditos.htmldaba 404 al abrirla desdeapp.lemeit.ar/aq/pese a estar bien deployada — y resultó queapi.htmltenía el mismo problema en los tres portales, sin haberse notado antes. Causa: Cloudflare Pages redirige automáticamente/archivo.html→/archivo(sin extensión) con un headerLocationrelativo a la raíz del propio proyecto de Pages, sin ninguna noción del prefijo/aq,/emaso/wqque agrega el Workerlemeit-gatewayde por medio. El gateway devolvía eseLocationtal cual, así que el navegador (que le había pegado aapp.lemeit.ar) lo resolvía contraapp.lemeit.ardirecto, perdiendo el prefijo y cayendo en el 404 propio del gateway. Se arregla reescribiendo elLocationengateway/src/index.jscuando el upstream contesta un 3xx hacia su propio origen, volviendo a anteponerle el prefijo de sección. - Logos de proveedor en "Acerca de" de EMAS, desalineados en celular: el logo de EMA-25C (25Clima) es más ancho que los otros 4, y la caja que lo contiene (
.about-est-logo) solo tenía la altura fija (height:22px,width:autoen la imagen) — así que esa fila quedaba más ancha que el resto y se notaba desalineada, sobre todo en pantallas chicas. Se le da a la caja un tamaño fijo (ancho y alto) para las 5 estaciones, con cada logo achicándose (object-fit:contain) para entrar sin recortarse. - De nuevo el DNS borrado al sacar el Custom Domain, esta vez en
aq.lemeit.ar: mismo gotcha ya documentado paraemas/wq(ver más arriba), pero con más demora —aq.lemeit.arhabía conservado su registro DNS en el momento de sacarle el Custom Domain, y dejó de resolver (NXDOMAIN) recién más tarde. Mismo arreglo: registroAdummy (192.0.2.1, Proxied) agregado a mano en el DNS delemeit.ar.
Octubre 2026 — Aire Escolar: primeros sensores en escuelas¶
5–6 de octubre — Código propio por sensor y alta de los dos primeros sensores nuevos¶
- Código propio del proyecto (
AE-NN): convención independiente delsensor_indexde PurpleAir.AE-00es el primer sensor de la red (anterior al proyecto) yAE-01en adelante se asignan por orden de instalación. Se descartó un código con ciudad y escuela (tipoAE-25M-EEA-01): esos datos ya viven en la base (institución, coordenadas) y el código tiene que sobrevivir si un sensor cambia de escuela. - Migración 008 (
d1/migration_008_codigo.sql): columnacodigoensensorescon índice único (admite NULL). La fecha de alta usafecha_instalacion, que existía desde el esquema original y nunca se había cargado./api/sensoresya devolvía ambas por usarSELECT *; se amplió el SELECT de/api/ultimascons.codigoys.fecha_instalacion. La migración se ejecutó desde la consola web de D1 (sin acceso awrangleresa noche), una sentencia por vez. - AE-01 y AE-02: dos PA-II-FLEX de los 4 donados por PurpleAir Collective, registrados en PurpleAir con las coordenadas de cada escuela: AE-01 (
298687, Esc. de Educación Artística N°1 "Lola Mora", 25 de Mayo) y AE-02 (298701, EES N°2 "Manuel Pardal", Saladillo). Se agregaron aSENSOR_INDEXESy, tras un disparo de cron-job.org, aparecieron las dos filas con nombre y coordenadas correctas. Después se completaroncodigo,fecha_instalacion,institucionynotas(CUE y clave provincial) conUPDATEdesde la consola de D1, y el portal los mostró en el mapa con el nombre de la escuela en el título y el código debajo. La prueba se hizo con los sensores todavía en interior, antes de instalarlos. - AE-00: el sensor
198617(Colegio Secundario Madre Teresa, hoy en reubicación) se renombró solo en D1, sin volver a registrarlo en PurpleAir: PurpleAir exige registrar de nuevo el sensor para cambiar valores, y eso le asignaría unsensor_indexnuevo y cortaría la continuidad del historial. En PurpleAir conserva su nombre original; el Worker no lo pisa, porque elON CONFLICTdeupsertSensorMetadatasolo actualiza latitud y longitud. - Aprendizaje (secret):
SENSOR_INDEXESes un secret, y Cloudflare no permite leerlo ni agregarle un valor, solo reemplazarlo entero (en el dashboard: Rotate, y luego Deploy; Save version solo guarda sin activar). Hay que recuperar los índices ya activos con una consulta asensoresy pegar la lista completa. - Aprendizaje (WiFi): un sensor que ya fue configurado antes en una red se conecta a esa red y no abre su portal de configuración mientras la encuentre.
- Decisión: se posterga el uso de los Data Processors de PurpleAir (reenvío de datos por POST al Worker, que bajaría el consumo de puntos de la API); por ahora sigue la ingesta por consulta con cron-job.org como único disparador.
- Pendiente: borrar de
lecturaslas mediciones de prueba de AE-01 y AE-02 tomadas en interior, una vez que cada sensor reporte desde su escuela (los timestamps están en UTC); descargar el historial de AE-00 anterior a la ingesta (el posterior sale de/api/historicoen CSV); mostrarcodigoenindex.html(hoy el portal muestranombre); asignar AE-03 y AE-04.
28 de octubre de 2026 — AQ: AE-03, inventario y activación de versión¶
- AE-03 — Instituto Niño Jesús: PurpleAir PA-II-FLEX,
sensor_index = 298691, device IDfc:f5:c4:ae:ca:d6. El titular confirmó registro en D1 y visibilidad en el portal; un SELECT directo confirmó una lectura reciente, sin consultar la API pública que registra visitas. - Incidente y solución informados por el titular: Rotate de
SENSOR_INDEXEScreó una versión nueva del Worker, pero faltaba promoverla/desplegarla. Rotar por sí solo no activó esa versión. Se verificó después un deployment al 100 %, sin reconstruir valores de secretos ni acreditar su correspondencia con el HEAD. - Código propio completado: con autorización explícita del titular se asignó
codigo = 'AE-03'únicamente al sensor 298691, que tenía el campo vacío. D1 informó una fila modificada y el SELECT posterior confirmó código, nombre e institución. No se cambiaron lecturas ni otros sensores.codigoes la etiqueta del proyecto; no reemplaza alsensor_index. - Inventario: AE-00 (198617, Madre Teresa, en reubicación según antecedente); AE-01 (298687, Lola Mora, instalación física pendiente según última información); AE-02 (298701, Manuel Pardal, operativo y con lectura reciente); AE-03 (298691, Niño Jesús); AE-04 reservado para Del Valle, sin índice asignado. D1 conserva AirGradient 900002 y 900003 sin código y con lecturas antiguas: no se acredita conexión actual.
- Documentación versionada: PR #19 fusionado en lemeit-aq, commit
2b3032976e49c5e75f2f4153f6803c522af3ecff, con inventario al corte y procedimiento seguro y reversión. Ese corte precede a la asignación de código registrada aquí. - Alta segura: respaldar privadamente la lista completa autorizada antes de reemplazar el secreto; D1 ayuda a contrastarla, pero no permite reconstruir por sí sola la lista activa. Comprobar versión efectiva, ingesta y D1; API y dashboard generan visitas y requieren autorización bajo una restricción estricta de solo lectura. Nunca revelar secretos. El comportamiento depende de la herramienta: algunas operaciones de secretos despliegan inmediatamente; otras solo crean versión.
- Alcance: no se rotaron secretos, ejecutaron ingestas ni desplegó el Worker en esta intervención. La única escritura productiva autorizada fue completar el código de AE-03. No se acredita cobertura automática AirGradient (AQ-05), migración 009 ni despliegue del HEAD. El dashboard actual muestra
nombre; mostrarcodigodirectamente sigue pendiente.
Notas sobre las fuentes de esta bitácora¶
- El detalle línea por línea de cada sesión (comandos exactos, mensajes de error completos, capturas) queda en el historial de commits de cada repositorio (
git log) y, para EMA hasta agosto 2026, en los.docxoriginales dentro deema-saladillo/docs/. - Esta página se actualiza manualmente después de cada sesión de trabajo relevante — no hay automatización todavía que la genere desde los commits.