diff --git a/.gitignore b/.gitignore
index 4aa701ba..f978a8d7 100644
--- a/.gitignore
+++ b/.gitignore
@@ -26,4 +26,6 @@ pip-log.txt
#Mr Developer
.mr.developer.cfg
-jb/_build/
\ No newline at end of file
+jb/_build/
+jb/.wrangler/cache/wrangler-account.json
+jb/extra_html/cover-source.jpg
\ No newline at end of file
diff --git a/README.md b/README.md
index c4eee160..3e1a9973 100644
--- a/README.md
+++ b/README.md
@@ -1,12 +1,15 @@
# Think Python, 3.ª edición
+
+
Materiales, notebooks de Jupyter y versión web en español de la 3.ª edición de *Think Python: How to Think Like a Computer Scientist*, de Allen B. Downey.
Traducción al español por [midudev](https://midu.dev) (Miguel Ángel Durán).
## Enlaces
-- Repositorio del proyecto: [libropython.es](https://libropython.es)
+- Página pública del libro: [libropython.es](https://libropython.es)
+- Repositorio del proyecto: [github.com/midudev/ThinkPython](https://github.com/midudev/ThinkPython)
- Página original del libro: [Green Tea Press](http://thinkpython.com)
- Versión impresa y electrónica en inglés: [Bookshop.org](https://bookshop.org/a/98697/9781098155438) y [Amazon](https://www.amazon.com/_/dp/1098155432?smid=ATVPDKIKX0DER&_encoding=UTF8&tag=oreilly20-20&_encoding=UTF8&tag=greenteapre01-20&linkCode=ur2&linkId=e2a529f94920295d27ec8a06e757dc7c&camp=1789&creative=9325)
@@ -52,6 +55,28 @@ python3 -m playwright install chromium
- HTML: `jb/_build/html/index.html`
- PDF: `jb/_build/html/think-python-es.pdf`
- Archivos estáticos para producción: `jb/_build/html/`
+ - Carpeta lista para desplegar: `dist/`
+
+## Desplegar en Cloudflare Workers
+
+El archivo `wrangler.toml` despliega los assets estáticos desde `./dist`.
+
+1. Construye el libro:
+
+ ```bash
+ cd jb
+ ./build.sh
+ cd ..
+ ```
+
+2. Despliega con Wrangler:
+
+ ```bash
+ npx wrangler login
+ npx wrangler deploy
+ ```
+
+3. Para usar `https://libropython.es`, asigna ese dominio al Worker desde Cloudflare Dashboard, en Workers & Pages > tu Worker > Settings > Domains & Routes.
## Abrir los notebooks
diff --git a/jb/_config.yml b/jb/_config.yml
index 6d475908..65e043fe 100644
--- a/jb/_config.yml
+++ b/jb/_config.yml
@@ -1,5 +1,5 @@
# Book settings
-title: Think Python (edición en español)
+title: Think Python en español
author: Allen B. Downey
latex:
@@ -14,6 +14,7 @@ repository:
branch: v3
html:
baseurl: https://libropython.es
+ favicon: extra_html/favicon.ico
use_repository_button: true
extra_footer: |
@@ -53,7 +54,19 @@ parse:
sphinx:
local_extensions:
full_book_pdf: _extensions
+ seo: _extensions/seo.py
config:
+ language: es
+ html_title: Think Python en español
+ html_short_title: Libro Python
+ html_extra_path:
+ - extra_html
+ templates_path:
+ - _templates
+ html_static_path:
+ - _static
+ html_css_files:
+ - custom.css
mathjax_path: https://cdn.jsdelivr.net/npm/mathjax@3/es5/tex-mml-chtml.js
mathjax3_config:
tex:
diff --git a/jb/_extensions/full_book_pdf.py b/jb/_extensions/full_book_pdf.py
index 4b0b5ce6..8919f573 100644
--- a/jb/_extensions/full_book_pdf.py
+++ b/jb/_extensions/full_book_pdf.py
@@ -1,11 +1,15 @@
-"""Header download button for the generated full-book PDF."""
+"""Site links for the public book build."""
PDF_FILENAME = "think-python-es.pdf"
PDF_TOOLTIP = "Descargar el libro completo en PDF"
+SITE_HOME_URL = "https://libropython.es"
-def use_full_book_pdf_download(app, pagename, templatename, context, doctree):
- """Replace the page-download dropdown with the full-book PDF link."""
+def update_site_links(app, pagename, templatename, context, doctree):
+ """Point site-level home/download links to their public targets."""
+ context["theme_logo_link"] = SITE_HOME_URL
+ context["theme_site_home_url"] = SITE_HOME_URL
+
header_buttons = context.get("header_buttons")
if not isinstance(header_buttons, list):
return
@@ -29,5 +33,5 @@ def use_full_book_pdf_download(app, pagename, templatename, context, doctree):
def setup(app):
- app.connect("html-page-context", use_full_book_pdf_download, priority=700)
+ app.connect("html-page-context", update_site_links, priority=700)
return {"parallel_read_safe": True, "parallel_write_safe": True}
diff --git a/jb/_extensions/seo.py b/jb/_extensions/seo.py
new file mode 100644
index 00000000..1678a2eb
--- /dev/null
+++ b/jb/_extensions/seo.py
@@ -0,0 +1,86 @@
+"""SEO metadata for the public book site."""
+
+import html
+import re
+
+SITE_URL = "https://libropython.es"
+SITE_NAME = "Libro Python"
+BOOK_TITLE = "Think Python en español"
+AUTHOR = "Allen B. Downey"
+TRANSLATOR = "midudev"
+TWITTER_SITE = "@midudev"
+THEME_COLOR = "#2d3748"
+LOCALE = "es_ES"
+
+DEFAULT_DESCRIPTION = (
+ "Think Python en español: introducción gratuita a Python de Allen B. Downey. "
+ "3.ª edición traducida con capítulos online, notebooks en Colab y PDF descargable."
+)
+
+PAGE_DESCRIPTIONS = {
+ "index": DEFAULT_DESCRIPTION,
+}
+
+
+def _page_url(app, pagename: str) -> str:
+ base = SITE_URL.rstrip("/")
+ if pagename in {app.config.root_doc, "index"}:
+ return f"{base}/"
+ return f"{base}/{pagename}.html"
+
+
+_TAG_RE = re.compile(r"<[^>]+>")
+
+
+def _strip_html(value: str) -> str:
+ """Return *value* without HTML tags and with entities decoded.
+
+ Sphinx feeds template titles with markup like
+ ``6. Valores de retorno``. Embedding
+ that verbatim inside ```` or ``
+
Puedes pedir las versiones impresa y electrónica de *Think Python 3e* en
[Bookshop.org](https://bookshop.org/a/98697/9781098155438) y
diff --git a/jb/prep_notebooks.py b/jb/prep_notebooks.py
index 514949d0..00e1593c 100644
--- a/jb/prep_notebooks.py
+++ b/jb/prep_notebooks.py
@@ -3,13 +3,25 @@
def process_cell(cell):
- # get tags
- tags = cell['metadata'].get('tags', [])
+ tags = cell['metadata'].setdefault('tags', [])
- # add hide-cell tag to solutions
if cell['cell_type'] == 'code':
source = cell['source']
+ # Hide the setup cells that download modules and configure autoreload.
+ # In most chapters both bits live in the same cell, but in some (e.g.
+ # chap04) they are split across two consecutive cells, so we detect
+ # each pattern independently to cover both layouts.
+ is_download_cell = 'def download(url):' in source
+ is_autoreload_cell = (
+ 'import thinkpython' in source and '%autoreload' in source
+ )
+
+ if is_download_cell or is_autoreload_cell:
+ for tag in ['remove-cell', 'keep']:
+ if tag not in tags:
+ tags.append(tag)
+
# remove solutions
if source.startswith('# Solution') or 'solution' in tags:
cell['source'] = []
diff --git a/wrangler.toml b/wrangler.toml
new file mode 100644
index 00000000..340fdd33
--- /dev/null
+++ b/wrangler.toml
@@ -0,0 +1,20 @@
+name = "think-python-es"
+compatibility_date = "2026-05-19"
+
+[observability]
+enabled = false
+head_sampling_rate = 1
+
+[observability.logs]
+enabled = true
+head_sampling_rate = 1
+persist = true
+invocation_logs = true
+
+[observability.traces]
+enabled = false
+persist = true
+head_sampling_rate = 1
+
+[assets]
+directory = "./dist"