Release 2026-06-15/16: B2 billing-orphan fix + demo conversion (B1/B3/B4/B5) + conversation history (72h) + per-tenant teach-the-bot + onboarding videos
4778987 verified | """Reproducibly build the atendyo.com STATIC site (Netlify) from source. | |
| atendyo.com is served statically by Netlify (the backend only renders these same | |
| pages). This regenerates the EXACT file tree the site needs so a landing/blog | |
| change can be redeployed without hand-assembling files in /tmp: | |
| index.html <- app/landing_ui/index.html (verbatim) | |
| og.png <- app/landing_ui/og.png | |
| blog/index.html <- render_index("es") | |
| blog/en.html <- render_index("en") | |
| blog/<slug>.html <- render_article(post) for every post | |
| robots.txt, sitemap.xml <- regenerated, canonical on atendyo.com | |
| _redirects <- the /demo -> backend redirect (load-bearing: every | |
| cold-email demo link is atendyo.com/demo?d=...) | |
| For static hosting the EN blog index lives at /blog/en (file blog/en.html), so we | |
| rewrite the renderer's "/blog?lang=en" links to "/blog/en". | |
| Usage: python -m scripts.build_static_site [output_dir] (default: dist/site) | |
| """ | |
| from __future__ import annotations | |
| import shutil | |
| import sys | |
| from pathlib import Path | |
| ROOT = Path(__file__).resolve().parent.parent | |
| sys.path.insert(0, str(ROOT)) | |
| from app.blog.posts import POSTS # noqa: E402 | |
| from app.blog.render import render_article, render_index # noqa: E402 | |
| SITE = "https://atendyo.com" | |
| BACKEND = "https://victor34593993-flexigo-support-bot.hf.space" | |
| LANDING = ROOT / "app" / "landing_ui" / "index.html" | |
| OG = ROOT / "app" / "landing_ui" / "og.png" | |
| LEGAL_DIR = ROOT / "app" / "legal_ui" | |
| # Served at /legal/<page> (pretty URL). The privacy policy URL Meta needs is | |
| # https://atendyo.com/legal/privacidad — must be a real, accessible page. | |
| LEGAL_PAGES = ("aviso-legal", "privacidad", "cookies", "condiciones", "encargo") | |
| def _static(html: str) -> str: | |
| # the EN blog index is a file (/blog/en), not a query string, when static | |
| return html.replace(f"{SITE}/blog?lang=en", f"{SITE}/blog/en") | |
| def _robots() -> str: | |
| return ( | |
| "User-agent: *\n" | |
| "Allow: /$\n" | |
| "Allow: /blog\n" | |
| "Disallow: /admin\n" | |
| "Disallow: /portal\n" | |
| "Disallow: /preview\n" | |
| "Disallow: /chat\n" | |
| "Disallow: /widget-config\n" | |
| f"Sitemap: {SITE}/sitemap.xml\n" | |
| ) | |
| def _sitemap() -> str: | |
| rows = [ | |
| f" <url><loc>{SITE}/</loc><changefreq>weekly</changefreq><priority>1.0</priority></url>", | |
| f" <url><loc>{SITE}/blog</loc><changefreq>weekly</changefreq><priority>0.7</priority></url>", | |
| ] | |
| for p in POSTS: | |
| rows.append( | |
| f" <url><loc>{SITE}/blog/{p.slug}</loc><lastmod>{p.date}</lastmod>" | |
| "<changefreq>monthly</changefreq><priority>0.6</priority></url>" | |
| ) | |
| return ( | |
| '<?xml version="1.0" encoding="UTF-8"?>\n' | |
| '<urlset xmlns="http://www.sitemaps.org/schemas/sitemap/0.9">\n' | |
| + "\n".join(rows) + "\n</urlset>\n" | |
| ) | |
| def _redirects() -> str: | |
| # The ONLY redirect the site needs: atendyo.com/demo -> backend /demo (302, | |
| # query string auto-forwarded). Load-bearing for every email demo link. | |
| # MUST be a _redirects file in the publish root — a netlify.toml inside the | |
| # deployed --dir is published as a plain file and NOT processed as config. | |
| return f"/demo {BACKEND}/demo 302\n" | |
| def build(out: Path) -> None: | |
| if out.exists(): | |
| shutil.rmtree(out) | |
| blog = out / "blog" | |
| blog.mkdir(parents=True) | |
| (out / "index.html").write_text(LANDING.read_text(encoding="utf-8"), encoding="utf-8") | |
| shutil.copyfile(OG, out / "og.png") | |
| legal = out / "legal" | |
| legal.mkdir() | |
| for page in LEGAL_PAGES: | |
| (legal / f"{page}.html").write_text( | |
| (LEGAL_DIR / f"{page}.html").read_text(encoding="utf-8"), encoding="utf-8") | |
| (blog / "index.html").write_text(_static(render_index("es")), encoding="utf-8") | |
| (blog / "en.html").write_text(_static(render_index("en")), encoding="utf-8") | |
| for p in POSTS: | |
| (blog / f"{p.slug}.html").write_text(_static(render_article(p)), encoding="utf-8") | |
| (out / "robots.txt").write_text(_robots(), encoding="utf-8") | |
| (out / "sitemap.xml").write_text(_sitemap(), encoding="utf-8") | |
| (out / "_redirects").write_text(_redirects(), encoding="utf-8") | |
| n = len(list(blog.glob("*.html"))) | |
| print(f"built site at {out}: index + {n} blog pages + {len(LEGAL_PAGES)} legal " | |
| "pages + sitemap/robots/_redirects/og.png") | |
| if __name__ == "__main__": | |
| target = Path(sys.argv[1]) if len(sys.argv) > 1 else ROOT / "dist" / "site" | |
| build(target) | |