Easy-to-manage touch screens
무료 오픈소스 터치스크린으로 USB 설정 가이드, 드래그‑드롭 편집기, 로컬 제어를 제공하며 Home Assistant용으로 제작되었습니다
자라고사 도시 버스, 노선별 경로·정류장·환승 정보와 현재 정류장 도착 버스의 실시간 데이터 제공
El autobús urbano de Zaragoza, línea a línea: recorrido, paradas y correspondencias. Y los que llegan ahora mismo a tu parada, con la edad del dato a la vista.
라이프스타일TypeScript · CSSGitHub @ablanquez온라인운영자 미확인
써 보셨어요? 한 번만 눌러 주세요. 로그인은 필요 없어요.
개인 계정의 공개 저장소 소유자는 GitHub로 로그인해 운영자를 확인하고 소개를 직접 관리할 수 있습니다. 관리자 승인은 필요하지 않습니다.
로그인한 뒤 확인 파일을 만들어 이 저장소의 기본 브랜치에 30분 안에 커밋해 주세요. 다른 브랜치나 포크의 파일은 인정하지 않습니다. 파일을 올린 뒤 아래에서 확인하면 관리자 승인 없이 관리할 수 있습니다. 자격을 유지하려면 파일을 남겨 두세요.
로그인이 필요하면 로그인 화면으로 이동합니다. 로그인 후 이곳으로 돌아와 원하는 확인 버튼을 다시 눌러 주세요.
프로젝트 폴더에서 /nomorevibe verify를 실행하면 소유권을 확인하고 소개를 직접 관리할 수 있습니다.
1분 만에 확인하기Visor en vivo del autobús urbano de Zaragoza: llegadas por parada, flota en el mapa y desvíos detectados cruzando cuatro fuentes oficiales. No existe GTFS-RT en Zaragoza: se verificó, y aquí está la auditoría.
저장소 README에서 가져옴
README 전체를 불러오는 중입니다.
README 전문 보기 ↗Dónde está tu autobús en Zaragoza, cuánto falta, y qué te están ocultando las fuentes.
44 líneas · 934 paradas · sin base de datos · sin cuentas · sin cookies
No hay nada que instalar y no es una demo con datos de mentira: son los autobuses que hay ahora mismo en la calle. Busca tu parada por el número del poste, o mira una línea con su recorrido de hoy.
🕐 La hora importa, y no es un fallo. De madrugada casi no hay servicio, así que una parada puede salir sin llegadas: eso es la respuesta correcta —ZetaBus no inventa un autobús que no viene—. Y el aviso de desvío solo aparece en las líneas que hoy tengan obras: el día que Avanza restaure la ruta, se apaga solo.
Para levantarlo en tu máquina, dos comandos → Poner en marcha.
ZetaBus dice cuánto falta para que llegue tu autobús en Zaragoza, dónde está exactamente ahora mismo en el mapa, y qué autobús es: si es articulado de 18 metros o sencillo de 12, si es eléctrico, híbrido o diésel.
Y hace una cosa más, que es la razón de que exista: enseña lo que las fuentes oficiales no cuentan. Cuando hay obras, el GTFS oficial de la ciudad sigue diciendo que el autobús pasa por una avenida cortada. ZetaBus compara ese recorrido oficial con el que el operador publica para hoy, y deduce el desvío — con las paradas por las que hoy no se pasa, tachadas y con su fuente al lado. No lo transcribe nadie a mano: se deriva, y por eso se apaga solo el día que restauren la ruta.
¿Prefieres verlo antes de leer nada más? Está en vivo en zetabus.antonioblanquez.es.
En Zaragoza no hay GTFS-RT. No es que no lo hayamos encontrado: no existe, verificado contra el Punto de Acceso Nacional, Transitland y Mobility Database. Ésa es la razón —y la única— por la que en este proyecto hay scraping.
Pero el problema de fondo no es que falte una API. Es que las fuentes que sí existen se contradicen, y nadie lo dice:
exception_type=2 en
calendar_dates. Sus rutas siguen bajando por calles cortadas. Su shapes.txt es preciso y
falso.Ante eso ZetaBus no adjudica. Enseña los dos hechos y quién los dice: «Avanza declaró esta parada suprimida el 10/01. Su sistema sigue anunciando autobuses aquí. No podemos saber cuál es cierto — confírmalo en la marquesina.»
Esa es la diferencia entre esto y un scraper con un mapa encima: primero se auditó la fuente,
después se escribió el código. Los informes están en docs/auditoria/ — trece,
incluidos los tres en que hubo que retractarse de un informe propio.
El buscador encuentra por número de poste —el que está impreso en la marquesina—, por nombre de parada o por línea. Y encuentra igual escribiendo «avda», «Av.» o «Avenida», con acentos o sin ellos, y hasta «Carlos Quinto» para «Carlos V».
Lo que ves al abrir una parada
Lo que ves al abrir una línea
Lo que ZetaBus no hace
Accesibilidad, y medida
| Fuente | Qué aporta | Qué no |
|---|---|---|
| GTFS del Punto de Acceso Nacional | Topología: paradas, líneas, trazados, calendario | Miente con las obras. No tiene tiempo real |
| Web del operador (recorrido) | El recorrido vigente hoy, por sentido, con el desvío aplicado | No dice que sea un desvío: hay que derivarlo |
| Sistema de tiempo real del operador | Posición GPS y minutos de llegada | Anuncia autobuses en paradas suprimidas |
| Pliego municipal de contratación | El registro oficial de la flota: 350 vehículos | Se aprobó en 2025: no trae los posteriores |
| busesmadrid.es | Los 43 vehículos que circulan y no están en el pliego | No es oficial → salen marcados con asterisco |
| OpenStreetMap | La cartografía | — |
Las dos fuentes principales dan 393; con algunas fuentes menores se llega a los 403 vehículos que ZetaBus reconoce, y cada ficha dice de qué fuente sale cada uno de sus campos — no el vehículo entero: el campo. Un coche del pliego con una longitud observada a mano no se blanquea por el resto.
Y una que salió por sorpresa: gracias al pliego municipal descubrimos que el fichero de flota heredado mentía en el 20 % de las longitudes —decía «12 metros» donde había un articulado de 18—. Un Volvo 7905 existe en las dos longitudes, con el mismo nombre de modelo.
⚖️ Cada dato lleva de dónde sale hasta la pantalla: /sobre-los-datos
lo explica dentro de la propia aplicación.
ZetaBus consulta endpoints no documentados del operador. No lo escondemos, y no nos parece que haya que esconderlo: lo justificamos.
ZetaBus/1.1 (+https://github.com/ablanquez/zetabus):
nombre, versión y la URL de este repositorio, donde está explicado todo y quién lo firma. Si
molestamos, queremos que puedan pedirnos que paremos antes de tener que bloquearnos.robots.txt no indexa las paradas — ni por cortesía ni por ahorro: porque los minutos
que faltan caducan en 15 segundos, y una parada indexada enseñaría un «llega en 3 min» de
hace tres semanas.| Capa | Tecnología |
|---|---|
| Framework | Next.js 16 (App Router) · React 19 |
| Lenguaje | TypeScript en modo estricto |
| Mapa | Leaflet + OpenStreetMap |
| Estilos | Tailwind CSS 4, sobre un sistema de tokens propio |
| Datos | GTFS horneado en el build · sin base de datos |
| Pruebas | Vitest (motor) + Playwright (pantalla) |
No es una carencia. Todo lo que ZetaBus enseña se deriva de sus fuentes, y lo derivable no se guarda. El día que se quiera medir —frecuencia real contra frecuencia contratada— hará falta histórico, y ese día se decide entonces, con los ojos abiertos.
src/
core/ el vocabulario del dominio. No sabe que esto son autobuses.
sources/ todo lo que habla con el mundo. Un ÚNICO punto de salida a la red.
engine/ la lógica: llegadas, desvíos, horarios, correspondencias, búsqueda.
cache/ caché de dos pisos (memoria + disco), vuelo único y techo de peticiones.
components/ la interfaz.
app/ las rutas.
scripts/ los compiladores de datos: GTFS → artefacto, flota, correspondencias.
tests/ el motor, con dobles. No toca la red.
e2e/ el instrumento: mide LA PANTALLA, no el código.
docs/ la auditoría de fuentes y las decisiones de diseño.
git clone https://github.com/ablanquez/zetabus
cd zetabus
npm install
cp .env.example .env.local # y pon dentro tu NAP_API_KEY
npm run build # descarga el GTFS oficial (~6,6 MB), hornea src/generated y compila (~6 min)
npm run dev # http://localhost:3000
⚠️ El primer arranque pasa por npm run build, no por npm run dev a secas. dev solo
levanta el servidor; la aplicación importa los datos ya horneados en src/generated/ —que no
se versiona— y ese artefacto lo genera el build (data:build, dentro de él), no la descarga
del GTFS. Sin ese paso, dev —y las pruebas— revientan al resolver @/generated. Después del
primer build ya puedes iterar con npm run dev a secas.
El GTFS no viene en el repositorio. El porqué —que no es legal, es de frescura— está en
data/gtfs/README.md. Y el build falla ruidosamente si no está: no
arranca con datos vacíos, porque un mapa sin paradas que no se queja es peor que un error.
Para cada poste, qué líneas pasan hoy — separando las de siempre de las que hoy pasan por un
desvío. Se genera solo con npm run build, y si el operador está caído durante el build,
el build no se muere: la aplicación arranca en modo degradado y lo dice.
npm run correspondencias:build # regenerarlo a mano (~2 min)
En un hosting sin SSH no se puede lanzar ese comando, así que el mismo barrido —el mismo
código, no una copia— se dispara con POST /api/regenerar, protegido por un token en cabecera.
Si el token no está configurado en el servidor, el endpoint no ejecuta nada: responde 503.
Ver .env.example.
Los nombres de parada del GTFS vienen rotos: el exportador de Avanza les mete un
ucwords() que estropea mayúsculas y abreviaturas («Av. De Valencia», «Coso N.º
54»). El operador los escribe bien en get_stops_list —el mismo endpoint que los
desvíos—, así que no se corrigen a mano: se PIDEN, en el build, y se hornean en las
paradas.
nombres:ensure corre antes de data:build (genera la tabla solo si falta), y ese
orden no es casual: data:build es quien la lee y la aplica, así que una tabla que
naciera después llegaría cuando el artefacto ya está horneado sin nombres. Hoy quedan 918
de las 934 paradas con el nombre confirmado de Avanza (un 98 %); las 16 restantes se
quedan con el del GTFS. Y lo honesto: esas 16 son las que Avanza no da porque hoy sus
líneas van desviadas fuera de ellas — mañana serán otras.
Esas 16 llevan en pantalla un aviso, «nombre sin confirmar»: ahora sale en 16 y no en todas, y por eso informa en vez de ser ruido. Y como con las correspondencias, si el operador está caído durante el build no se muere: no hay tabla, todas las paradas caen al nombre del GTFS marcado, y el build lo dice.
npm run nombres:build # regenerar la tabla a mano (~2 min)
ZETABUS_DEMO=1 npm run dev
# y luego: /parada/744?fingir=caido · /linea/35?sentido=0&fingir=desviada
?fingir= intercepta todas las peticiones: cero bytes hacia el operador. Sirve para ver los
casos raros —fuente caída, respuesta ilegible, línea desviada— sin esperar a que ocurran.
El servidor auto-despliega en cada git push a main (Hostinger). Pero hay un paso a mano
que no se puede saltar:
Tras cada deploy → purgar la caché del CDN. Panel de Hostinger → la web → Caché → Borrar caché.
Por qué. Next marca el HTML prerenderizado con s-maxage=31536000 (un año), y el CDN de
Hostinger lo cachea y no lo purga al desplegar. Sin purgar, sigue sirviendo el HTML viejo, que
apunta a los /_next/static/* con el hash antiguo —que el build nuevo ya borró— → la web
carga sin estilos hasta que se purga a mano. Pasó el 26/07/2026, en el primer re-deploy que lo
destapó.
Se estudió automatizarlo —bajar el s-maxage con revalidate, o purgar por API—, pero para un
proyecto cerrado que se despliega poquísimo no compensa: el revalidate metía tráfico de
prefetch de fondo a todos los visitantes, y una API de purga es un token y un action que
mantener. La purga a mano es el procedimiento oficial.
El índice de correspondencias (arriba) se regenera de madrugada. En un hosting sin SSH un cron
no puede lanzar npm run …: solo puede pedir una URL. Por eso el barrido —el mismo código, no una
copia— se dispara con POST /api/regenerar, y el cron es literalmente esto:
curl -X POST -H "Authorization: Bearer <TOKEN>" https://zetabus.antonioblanquez.es/api/regenerar
<TOKEN> es el valor de ZETABUS_REGEN_TOKEN, el mismo que hay en el servidor. Va en la
cabecera, nunca en la URL —que se queda en los logs—. Sin él, o con uno de menos de 32
caracteres, el endpoint responde 503 y no ejecuta nada (falla cerrado); con uno equivocado,
401.0 2 * * * (a las 02:00). Es un valor operativo del panel, no derivable de aquí.202 al instante y trabaja de fondo (~2 min): el cron no espera a que termine, así
que no lo mata ningún timeout intermedio. Si dos peticiones se solapan, la segunda recibe 409 y
no lanza un segundo barrido./api/diag → correspondencias, que trae
edadSegundos (la edad del índice) y degradado. Si la edad no se reinicia cada madrugada, el
cron no está corriendo./api/diag, el panel /estado
se pone ámbar a las 26 h (el umbral FRESCURA_MAX_HORAS), así que un despiste de una noche se ve
solo—; (2) la degradación es grácil: si no corre, se sirve el índice anterior y los desvíos van un
día por detrás —limitación ya explicada en /sobre-los-datos—; y (3) una alerta es, ella misma, una
pieza que puede fallar en silencio —¿quién avisa cuando el que avisa deja de avisar?— y una pieza
móvil más en un proyecto que se quiere dejar quieto una temporada. ⇒ Si un día sospechas, míralo
aquí: /estado (el ámbar) y /api/diag → correspondencias.edadSegundos.Cuatro cosas que no se ven en las capturas y explican el resto:
1 · La auditoría vino antes que el código. Siete fases de investigación de fuentes, con sus
informes en docs/auditoria/, antes de escribir la aplicación. Tres de
ellos son retractaciones de un informe anterior propio.
2 · Las pruebas miran la pantalla, no el código. Más de 470 pruebas de motor (Vitest) y más
de 800 de navegador (Playwright) —dicho como suelo, y a propósito: contarlas exactas exige
correr las dos suites, así que ningún guardián puede vigilar esa cifra y un número redondo se
quedaría rancio en silencio—. Las visuales miden el píxel pintado, no la clase de CSS. El motivo
está escrito en e2e/lib/medir.ts: «verificar el JSON con curl NO ES haber
mirado la pantalla».
3 · Lo que se descubre se escribe, aunque duela. Cada informe de auditoría trae una sección de
lo que no se pudo comprobar, y las lecciones de método están en
docs/LECCIONES.md (L1–L9) y, de la L10 en adelante, en el registro vivo de
ZETABUS-ESTADO.md — incluidas las que se aprendieron equivocándose.
4 · Y se verificó desde fuera. Ya desplegada, el 01/08/2026 (commit ae14ea9) pasó una batería de
herramientas de terceros —PageSpeed, el validador del W3C, securityheaders, Rich Results—: ninguna nota
bajó y dos páginas pasaron de tener errores a cero. El registro completo, con las capturas y con lo que
esas pruebas no cubren, en docs/auditoriafinal/VERIFICACION-EXTERNA.md.
✅ Hoy: llegadas en vivo con posición GPS, ficha de vehículo, recorrido real con desvíos derivados, horarios de terminal, transbordos por parada, buscador, panel de estado del propio servicio (cuánto se le pide al operador y con qué frescura), modo demo — y en marcha en zetabus.antonioblanquez.es. Todo lo que incluye esta primera versión está en el CHANGELOG.
Previsto, sin fechas comprometidas:
Código: Apache 2.0 · © 2026 Antonio Blánquez Cabeza — antonioblanquez.es
Los datos y las dependencias de terceros conservan sus propias condiciones, una por una, en
THIRD-PARTY-NOTICES.md.
⚠️ Una de ellas no es como las demás:
react-leafletestá bajo Hippocratic 2.1, que no es una licencia aprobada por la OSI y añade una restricción de uso que Apache 2.0 no impone. No impide nada, pero conviene saberlo antes de tomar este código:THIRD-PARTY-NOTICES.md§ 5.2.
Datos de transporte procesados a partir del GTFS publicado por Avanza Zaragoza S.A.U. en el Punto de Acceso Nacional. Powered by MITRAMS. Cartografía © colaboradores de OpenStreetMap.
No es un producto oficial de Avanza Zaragoza ni del Ayuntamiento de Zaragoza.