# daniball/boletines-locales architecture

> Architecture diagram of the GitHub repository daniball/boletines-locales, generated by GitDiagram from its file tree, README and a sample of source files (last updated 2026-10-01). It is an AI-made overview: check details against the source.

- Interactive diagram: https://gitdiagram.com/daniball/boletines-locales
- Repository: https://github.com/daniball/boletines-locales

## Overview

This repository generates city-specific local-news editions, renders them for publication, and provides a website and review panel. The main workflow is: select a city and date, collect source items, select and draft stories, validate and render the edition, then save it for web and human review. The graph also preserves the city configuration, deterministic data rendering, and panel publishing branches. Edges are limited to relationships supported by excerpts, the source index, or explicit documentation; unsampled website wiring is not inferred.

Main entry points: [`pipeline/steps/edition.ts`](https://github.com/daniball/boletines-locales/blob/main/pipeline/steps/edition.ts) (Edition runner), [`panel/servidor.ts`](https://github.com/daniball/boletines-locales/blob/main/panel/servidor.ts) (Review panel), [`ciudades`](https://github.com/daniball/boletines-locales/tree/main/ciudades) (City configurations), [`scripts/borrador.ts`](https://github.com/daniball/boletines-locales/blob/main/scripts/borrador.ts) (Generation command)

## Diagram (Mermaid)

```mermaid
flowchart TD

subgraph group_configuration["City configuration"]
  node_city_registry["City registry<br/>[index.ts]"]
  node_city_configs["City configurations"]
  node_common_sections["Common sections"]
end

subgraph group_pipeline["Edition pipeline"]
  node_edition_runner["Edition runner<br/>[edition.ts]"]
  node_collect_step["Collect step<br/>[collect.ts]"]
  node_select_step["Story selection<br/>[select.ts]"]
  node_draft_step["AI drafting<br/>[draft.ts]"]
  node_validate_step["Draft validation<br/>[validate.ts]"]
  node_edition_step["Edition assembly<br/>[edition.ts]"]
  node_date_rules["Date rules<br/>[fechas.ts]"]
  node_edition_storage[("Edition storage<br/>[edicion.ts]")]
  node_item_identity["Item identity<br/>[items.ts]"]
  node_deduplication["Deduplication<br/>[dedupe.ts]"]
end

subgraph group_sources["Collection and rendering"]
  node_http_client["HTTP client<br/>[http.ts]"]
  node_collectors["Data collectors"]
  node_aemet["Weather collector<br/>[aemet.ts]"]
  node_fuel["Fuel collector<br/>[carburantes.ts]"]
  node_news_collectors["News collectors"]
  node_deterministic_renderers["Data renderers"]
  node_whatsapp_renderer["WhatsApp renderer<br/>[whatsapp.ts]"]
end

subgraph group_delivery["Review and delivery"]
  node_website["Edition website"]
  node_review_panel["Review panel<br/>[servidor.ts]"]
  node_panel_views["Panel views<br/>[vistas.ts]"]
  node_git_publisher["Git publisher<br/>[git.ts]"]
  node_generation_command["Generation command<br/>[borrador.ts]"]
end

node_operator(("Editor / operator"))
node_aemet_service{{"AEMET"}}
node_fuel_service{{"Fuel-price API"}}
node_external_sources{{"News sources"}}

node_operator -.->|"runs generation"| node_generation_command
node_generation_command -->|"resolves city"| node_city_registry
node_city_registry -->|"registers cities"| node_city_configs
node_city_configs -->|"uses sections"| node_common_sections
node_city_configs -->|"configures"| node_aemet
node_city_configs -->|"configures"| node_fuel
node_city_configs -->|"configures"| node_news_collectors
node_generation_command -->|"starts edition"| node_edition_runner
node_edition_runner -->|"runs collection"| node_collect_step
node_edition_runner -->|"selects stories"| node_select_step
node_edition_runner -->|"requests draft"| node_draft_step
node_edition_runner -->|"validates draft"| node_validate_step
node_edition_runner -->|"assembles edition"| node_edition_step
node_edition_runner -->|"uses dates"| node_date_rules
node_edition_runner -->|"reads and writes"| node_edition_storage
node_edition_runner -->|"renders message"| node_whatsapp_renderer
node_collect_step -->|"uses client"| node_http_client
node_aemet -->|"fetches data"| node_http_client
node_fuel -->|"fetches data"| node_http_client
node_aemet -->|"requests forecast"| node_aemet_service
node_fuel -->|"requests prices"| node_fuel_service
node_news_collectors -.->|"collects news"| node_external_sources
node_collectors -.->|"assigns IDs"| node_item_identity
node_select_step -->|"deduplicates items"| node_deduplication
node_item_identity -->|"normalizes URLs"| node_deduplication
node_edition_step -->|"uses templates"| node_deterministic_renderers
node_review_panel -->|"resolves city"| node_city_registry
node_review_panel -->|"reads and saves"| node_edition_storage
node_review_panel -->|"previews message"| node_whatsapp_renderer
node_review_panel -->|"renders views"| node_panel_views
node_review_panel -->|"regenerates draft"| node_generation_command
node_review_panel -->|"publishes commit"| node_git_publisher
node_edition_storage -.->|"serves editions"| node_website

click node_city_registry "https://github.com/daniball/boletines-locales/blob/main/ciudades/index.ts"
click node_city_configs "https://github.com/daniball/boletines-locales/tree/main/ciudades"
click node_common_sections "https://github.com/daniball/boletines-locales/blob/main/ciudades/secciones-comunes.ts"
click node_edition_runner "https://github.com/daniball/boletines-locales/blob/main/pipeline/steps/edition.ts"
click node_collect_step "https://github.com/daniball/boletines-locales/blob/main/pipeline/steps/collect.ts"
click node_select_step "https://github.com/daniball/boletines-locales/blob/main/pipeline/steps/select.ts"
click node_draft_step "https://github.com/daniball/boletines-locales/blob/main/pipeline/steps/draft.ts"
click node_validate_step "https://github.com/daniball/boletines-locales/blob/main/pipeline/steps/validate.ts"
click node_edition_step "https://github.com/daniball/boletines-locales/blob/main/pipeline/steps/edition.ts"
click node_date_rules "https://github.com/daniball/boletines-locales/blob/main/pipeline/lib/fechas.ts"
click node_edition_storage "https://github.com/daniball/boletines-locales/blob/main/pipeline/lib/edicion.ts"
click node_item_identity "https://github.com/daniball/boletines-locales/blob/main/pipeline/lib/items.ts"
click node_deduplication "https://github.com/daniball/boletines-locales/blob/main/pipeline/lib/dedupe.ts"
click node_http_client "https://github.com/daniball/boletines-locales/blob/main/pipeline/lib/http.ts"
click node_collectors "https://github.com/daniball/boletines-locales/tree/main/pipeline/collectors"
click node_aemet "https://github.com/daniball/boletines-locales/blob/main/pipeline/collectors/aemet.ts"
click node_fuel "https://github.com/daniball/boletines-locales/blob/main/pipeline/collectors/carburantes.ts"
click node_news_collectors "https://github.com/daniball/boletines-locales/tree/main/pipeline/collectors"
click node_deterministic_renderers "https://github.com/daniball/boletines-locales/tree/main/pipeline/render"
click node_whatsapp_renderer "https://github.com/daniball/boletines-locales/blob/main/pipeline/render/whatsapp.ts"
click node_website "https://github.com/daniball/boletines-locales/tree/main/src/pages"
click node_review_panel "https://github.com/daniball/boletines-locales/blob/main/panel/servidor.ts"
click node_panel_views "https://github.com/daniball/boletines-locales/blob/main/panel/vistas.ts"
click node_git_publisher "https://github.com/daniball/boletines-locales/blob/main/panel/git.ts"
click node_generation_command "https://github.com/daniball/boletines-locales/blob/main/scripts/borrador.ts"

classDef toneNeutral fill:#f8fafc,stroke:#334155,stroke-width:1.5px,color:#0f172a
classDef toneBlue fill:#dbeafe,stroke:#2563eb,stroke-width:1.5px,color:#172554
classDef toneAmber fill:#fef3c7,stroke:#d97706,stroke-width:1.5px,color:#78350f
classDef toneMint fill:#dcfce7,stroke:#16a34a,stroke-width:1.5px,color:#14532d
classDef toneRose fill:#ffe4e6,stroke:#e11d48,stroke-width:1.5px,color:#881337
classDef toneIndigo fill:#e0e7ff,stroke:#4f46e5,stroke-width:1.5px,color:#312e81
classDef toneTeal fill:#ccfbf1,stroke:#0f766e,stroke-width:1.5px,color:#134e4a
class node_city_registry,node_city_configs,node_common_sections toneBlue
class node_edition_runner,node_collect_step,node_select_step,node_draft_step,node_validate_step,node_edition_step,node_date_rules,node_edition_storage,node_item_identity,node_deduplication toneAmber
class node_http_client,node_collectors,node_aemet,node_fuel,node_news_collectors,node_deterministic_renderers,node_whatsapp_renderer,node_fuel_service toneMint
class node_website,node_review_panel,node_panel_views,node_git_publisher,node_generation_command toneRose
class node_operator,node_aemet_service,node_external_sources toneIndigo
```

## Components

### City configuration

- [City registry](https://github.com/daniball/boletines-locales/blob/main/ciudades/index.ts): `ciudades/index.ts`
- [City configurations](https://github.com/daniball/boletines-locales/tree/main/ciudades): `ciudades`
- [Common sections](https://github.com/daniball/boletines-locales/blob/main/ciudades/secciones-comunes.ts): `ciudades/secciones-comunes.ts`

### Edition pipeline

- [Edition runner](https://github.com/daniball/boletines-locales/blob/main/pipeline/steps/edition.ts): `pipeline/steps/edition.ts`
- [Collect step](https://github.com/daniball/boletines-locales/blob/main/pipeline/steps/collect.ts): `pipeline/steps/collect.ts`
- [Story selection](https://github.com/daniball/boletines-locales/blob/main/pipeline/steps/select.ts): `pipeline/steps/select.ts`
- [AI drafting](https://github.com/daniball/boletines-locales/blob/main/pipeline/steps/draft.ts): `pipeline/steps/draft.ts`
- [Draft validation](https://github.com/daniball/boletines-locales/blob/main/pipeline/steps/validate.ts): `pipeline/steps/validate.ts`
- [Edition assembly](https://github.com/daniball/boletines-locales/blob/main/pipeline/steps/edition.ts): `pipeline/steps/edition.ts`
- [Date rules](https://github.com/daniball/boletines-locales/blob/main/pipeline/lib/fechas.ts): `pipeline/lib/fechas.ts`
- [Edition storage](https://github.com/daniball/boletines-locales/blob/main/pipeline/lib/edicion.ts): `pipeline/lib/edicion.ts`
- [Item identity](https://github.com/daniball/boletines-locales/blob/main/pipeline/lib/items.ts): `pipeline/lib/items.ts`
- [Deduplication](https://github.com/daniball/boletines-locales/blob/main/pipeline/lib/dedupe.ts): `pipeline/lib/dedupe.ts`

### Collection and rendering

- [HTTP client](https://github.com/daniball/boletines-locales/blob/main/pipeline/lib/http.ts): `pipeline/lib/http.ts`
- [Data collectors](https://github.com/daniball/boletines-locales/tree/main/pipeline/collectors): `pipeline/collectors`
- [Weather collector](https://github.com/daniball/boletines-locales/blob/main/pipeline/collectors/aemet.ts): `pipeline/collectors/aemet.ts`
- [Fuel collector](https://github.com/daniball/boletines-locales/blob/main/pipeline/collectors/carburantes.ts): `pipeline/collectors/carburantes.ts`
- [News collectors](https://github.com/daniball/boletines-locales/tree/main/pipeline/collectors): `pipeline/collectors`
- [Data renderers](https://github.com/daniball/boletines-locales/tree/main/pipeline/render): `pipeline/render`
- [WhatsApp renderer](https://github.com/daniball/boletines-locales/blob/main/pipeline/render/whatsapp.ts): `pipeline/render/whatsapp.ts`

### Review and delivery

- [Edition website](https://github.com/daniball/boletines-locales/tree/main/src/pages): `src/pages`
- [Review panel](https://github.com/daniball/boletines-locales/blob/main/panel/servidor.ts): `panel/servidor.ts`
- [Panel views](https://github.com/daniball/boletines-locales/blob/main/panel/vistas.ts): `panel/vistas.ts`
- [Git publisher](https://github.com/daniball/boletines-locales/blob/main/panel/git.ts): `panel/git.ts`
- [Generation command](https://github.com/daniball/boletines-locales/blob/main/scripts/borrador.ts): `scripts/borrador.ts`

### Other components

- **Editor / operator**
- **AEMET**
- **Fuel-price API**
- **News sources**

## Connections

- Editor / operator → Generation command: runs generation
- Generation command → City registry: resolves city (evidence: `scripts/borrador.ts`)
- City registry → City configurations: registers cities (evidence: `ciudades/index.ts`)
- City configurations → Common sections: uses sections (evidence: `ciudades/jaen/config.ts`)
- City configurations → Weather collector: configures (evidence: `ciudades/jaen/config.ts`)
- City configurations → Fuel collector: configures (evidence: `ciudades/jaen/config.ts`)
- City configurations → News collectors: configures (evidence: `ciudades/jaen/config.ts`)
- Generation command → Edition runner: starts edition (evidence: `scripts/borrador.ts`)
- Edition runner → Collect step: runs collection (evidence: `pipeline/steps/edition.ts`)
- Edition runner → Story selection: selects stories (evidence: `pipeline/steps/edition.ts`)
- Edition runner → AI drafting: requests draft (evidence: `pipeline/steps/edition.ts`)
- Edition runner → Draft validation: validates draft (evidence: `pipeline/steps/edition.ts`)
- Edition runner → Edition assembly: assembles edition (evidence: `pipeline/steps/edition.ts`)
- Edition runner → Date rules: uses dates (evidence: `pipeline/steps/edition.ts`)
- Edition runner → Edition storage: reads and writes (evidence: `pipeline/steps/edition.ts`)
- Edition runner → WhatsApp renderer: renders message (evidence: `pipeline/steps/edition.ts`)
- Collect step → HTTP client: uses client (evidence: `pipeline/steps/collect.ts`)
- Weather collector → HTTP client: fetches data (evidence: `pipeline/collectors/aemet.ts`)
- Fuel collector → HTTP client: fetches data (evidence: `pipeline/collectors/carburantes.ts`)
- Weather collector → AEMET: requests forecast (evidence: `pipeline/collectors/aemet.ts`)
- Fuel collector → Fuel-price API: requests prices (evidence: `pipeline/collectors/carburantes.ts`)
- News collectors → News sources: collects news
- Data collectors → Item identity: assigns IDs (evidence: `pipeline/collectors/aemet.ts`)
- Story selection → Deduplication: deduplicates items (evidence: `pipeline/steps/select.ts`)
- Item identity → Deduplication: normalizes URLs (evidence: `pipeline/lib/items.ts`)
- Edition assembly → Data renderers: uses templates (evidence: `pipeline/steps/edition.ts`)
- Review panel → City registry: resolves city (evidence: `panel/servidor.ts`)
- Review panel → Edition storage: reads and saves (evidence: `panel/servidor.ts`)
- Review panel → WhatsApp renderer: previews message (evidence: `panel/servidor.ts`)
- Review panel → Panel views: renders views (evidence: `panel/servidor.ts`)
- Review panel → Generation command: regenerates draft (evidence: `panel/servidor.ts`)
- Review panel → Git publisher: publishes commit (evidence: `panel/servidor.ts`)
- Edition storage → Edition website: serves editions
