Arquitectura — Somos 10⁷ Landing
Arquitectura — Somos 10⁷ Landing
Diagrama de la landing page estática en somos10e7/. La aplicación es un
sitio HTML/CSS/JS puro, sin build, sin backend propio, servido por GitHub
Pages. Todo el “runtime” ocurre en el navegador del visitante.
1. Vista de despliegue y servicios externos
flowchart LR
subgraph Repo["📁 Repo asosab.github.io (origen)"]
direction TB
J["_config.yml<br/>(Jekyll, plugins whitelist)"]
SP["somos10e7/<br/>(subdir estático,<br/>no procesado por Jekyll)"]
SP --> SPHTML["index.html"]
SP --> SPLOC["es_VE.txt<br/>es_BO.txt<br/>(JSON con ext .txt)"]
SP --> SPIMG["imagen/<br/>*.png, *.jpg"]
SP --> SPBACK["back/<br/>(borradores)"]
SP --> SPJS["js/youtube_videos.mjs<br/>(offline)"]
SP --> SPOC["opencode.jsonc<br/>(config OpenCode local)"]
end
GH["GitHub Pages<br/>CDN + TLS + Jekyll build"]:::infra
BR["🌐 Navegador del visitante"]:::client
GF["Google Fonts<br/>(Playfair, Barlow)"]:::ext
YT["YouTube<br/>i/embed + thumbnails"]:::ext
FORMS["Google Forms<br/>(Páginas Blancas)"]:::ext
YTAPI["YouTube Data API v3<br/>(solo script offline)"]:::ext
Repo -- "git push" --> GH
GH -- "HTTPS GET<br/>asosab.github.io/somos10e7/" --> BR
BR -- "fetch CSS woff2" --> GF
BR -- "iframe + img.youtube.com" --> YT
BR -- "link a forms.gle" --> FORMS
SPJS -. "node script<br/>(no se ejecuta en deploy)" .-> YTAPI
YTAPI -. "videosData.json" .-> SPJS
classDef infra fill:#e8f0ff,stroke:#1A3A6B,color:#0D1B2A
classDef client fill:#fff3d6,stroke:#F4B400,color:#0D1B2A
classDef ext fill:#ffe6e6,stroke:#CF2F35,color:#0D1B2A
Notas:
- El repo raíz usa Jekyll (
_config.yml), perosomos10e7/es un subdirectorio sin front matter: GitHub Pages lo sirve tal cual. back/yopencode.jsoncno se sirven en producción: están en.gitignorelógico (no se listan en el HTML generado) y no se referencian desde la página.- El script
youtube_videos.mjsno forma parte del runtime; es una utilidad local para regenerarvideosData.json, que no se consume por la landing.
2. Flujo de carga de la página (runtime del visitante)
sequenceDiagram
autonumber
actor U as Visitante
participant B as Navegador
participant GH as GitHub Pages
participant GF as Google Fonts
participant I18N as i18n IIFE<br/>(inline en index.html)
participant LS as localStorage
participant LOC as es_VE.txt / es_BO.txt<br/>(servido por GH Pages)
participant YT as YouTube
U->>B: Pega https://asosab.github.io/somos10e7/
B->>GH: GET /somos10e7/
GH-->>B: 200 index.html (HTML + CSS embebido + JS inline)
B->>B: Parsea HTML, descubre recursos
B->>GF: GET CSS de Playfair, Barlow (woff2)
GF-->>B: fuentes
Note over B,I18N: DOMContentLoaded
B->>I18N: Ejecuta IIFE
I18N->>LS: getItem("locale")
LS-->>I18N: "es_VE" | "es_BO" | null
I18N->>I18N: Fallback a "es_VE" si vacío o inválido
I18N->>LOC: GET https://asosab.github.io/somos10e7/{locale}.txt
LOC-->>I18N: 200 application/json (servido como text/plain)
I18N->>I18N: Parsea JSON, expone window._i18nDict
I18N->>B: Aplica data-i18n y data-i18n-attr al DOM
I18N->>B: Actualiza document.documentElement.lang
B->>YT: GET iframe embed + thumbnails img.youtube.com
YT-->>B: HTML del reproductor + JPEGs
Note over B: IntersectionObserver.attach
B->>B: Scroll → fade-in de .fade-up
B->>B: Scroll → scroll-spy resalta link activo del nav
U->>B: Click en sección
B->>B: showSeason(id) si es tab de temporada<br/>o navLinks.classList.toggle si es mobile
Puntos críticos del flujo:
- El fetch del locale es no bloqueante: el HTML ya muestra textos por defecto; el JS los reemplaza cuando llega el JSON.
- La URL del i18n es absoluta (
https://asosab.github.io/somos10e7/). Cambiar el path del directorio rompe el fetch. .txtse sirve conContent-Type: text/plain; el JS hacer => r.json()igualmente porque el cuerpo es JSON válido.- La persistencia del locale es solo cliente (
localStorage). No hay sesión, no hay cookie, no hay backend.
3. Componentes dentro de index.html
flowchart TB
subgraph HEAD["<head>"]
META["Meta SEO + Open Graph + Twitter Card"]
JSONLD["JSON-LD Schema.org<br/>PodcastSeries"]
FONTS["<link> Google Fonts"]
CSS["<style><br/>Variables, layout, animaciones<br/>(~1000 líneas embebidas)"]
I18N["<script> i18n IIFE"]
end
subgraph BODY["<body>"]
NAV["#navbar<br/>nav fijo + mobile toggle"]
HERO["#hero<br/>título + CTAs + anillos orbitales"]
ULT["#ultimo<br/>iframe del último episodio"]
TEMP["#temporadas<br/>tabs por temporada +<br/>grid de episode-cards"]
ENT["#entrevistas<br/>grid de entrevista-cards"]
MIC["#micros<br/>(placeholder, vacío)"]
EQ["#equipo<br/>4 cards del equipo"]
RED["#redes<br/>8 plataformas"]
PB["#paginas-blancas<br/>CTA a Google Forms"]
FOOT["footer<br/>secciones + contacto"]
end
subgraph SCRIPTS["<script> al final del body"]
NAVJS["navToggle handler"]
SEAS["showSeason(id, btn)"]
IO["IntersectionObserver<br/>fade-in + scroll-spy"]
end
I18N -. "lee/escribe" .-> LS2[("localStorage<br/>key='locale'")]
I18N -. "fetch" .-> LOC2["{locale}.txt"]
TEMP --> SEAS
NAV --> NAVJS
BODY --> IO
CSS -. "anima" .-> IO
classDef head fill:#1A3A6B,stroke:#0D1B2A,color:#FAF5E9
classDef body fill:#FAF5E9,stroke:#0D1B2A,color:#0D1B2A
classDef js fill:#F4B400,stroke:#0D1B2A,color:#0D1B2A
class HEAD head
class BODY body
class SCRIPTS js
Observaciones:
#microsestá declarado en el nav y en el footer pero la sección está vacía (placeholder) en el HTML actual.- No hay bundler ni módulo ES: todo el JS es
<script>clásico en orden de ejecución (i18n en<head>, handlers al final del<body>). - El CSS está embebido en
<style>, no enassets/. Para temas/variables hay que editar el bloque:rootdentro deindex.html.
4. Pipeline offline: regenerar videosData.json
flowchart LR
DEV["👨💻 Desarrollador<br/>(Node 18+)"]:::dev
SCRIPT["node youtube_videos.mjs"]:::tool
YTAPI["YouTube Data API v3"]:::ext
JSON["js/videosData.json<br/>(output, no se deploya)"]:::tool
DEV -- "npm install node-fetch<br/>(primera vez)" --> SCRIPT
DEV -- "node youtube_videos.mjs" --> SCRIPT
SCRIPT -- "channels.list<br/>playlists.list<br/>playlistItems.list (paginado)" --> YTAPI
YTAPI -- "JSON" --> SCRIPT
SCRIPT -- "cruza videos ↔ playlists<br/>preserva claves extra" --> JSON
JSON -. "no se referencia en index.html<br/>(workflow manual independiente)" .-> X[("❌ No consumido")]
classDef dev fill:#fff3d6,stroke:#F4B400,color:#0D1B2A
classDef tool fill:#e8f0ff,stroke:#1A3A6B,color:#0D1B2A
classDef ext fill:#ffe6e6,stroke:#CF2F35,color:#0D1B2A
Por qué este pipeline existe pero no se usa:
El script podría listar todos los videos del canal @somos10e7, pero
los episodios visibles en la landing se editan a mano dentro de
index.html (orden, títulos traducidos, portadas personalizadas). El
JSON queda en disco solo como referencia editorial.
Resumen de límites del sistema
| Frontera | Adentro | Afuera |
|---|---|---|
| Build | Nada (HTML/CSS/JS estático) | Jekyll (solo en el resto del repo) |
| Runtime | Navegador del visitante | GitHub Pages (solo hosting) |
| Estado | localStorage.locale |
Cookies, sesión, base de datos |
| Datos dinámicos | YouTube embeds, Google Forms | Backend propio (no existe) |
| Configuración | opencode.jsonc (solo OpenCode) |
Variables de entorno, secretos |
| Secretos | API_KEY de YT, CONTEXT7_API_KEY |
⚠️ Commiteados en el repo |