Files
taller-wox/app/frontend.py
farentsen 1f2ad8d235 feat(ui): paleta profesional + iconos SVG inline (sin emojis)
- Reemplaza emojis por iconos SVG estilo Lucide en cards landing y descargas
- Paleta refinada: slate grays + navy + accent amber-700 (B45309)
- Hero con grid pattern sutil y gradients radiales
- Cards con sombras suaves y borders, hover lift
- Header sticky con backdrop-filter
- Tipografía Inter con tracking ajustado
- Botones con flechas SVG inline
2026-05-13 03:12:59 +00:00

137 lines
4.4 KiB
Python

from pathlib import Path
from fastapi import APIRouter, Form, HTTPException, Request
from fastapi.responses import FileResponse, HTMLResponse, RedirectResponse
from fastapi.templating import Jinja2Templates
from itsdangerous import BadSignature, SignatureExpired
from pydantic import EmailStr
from app.config import get_settings
from app.db import log_download, upsert_lead
from app.security import (
create_download_token,
is_honeypot_filled,
verify_download_token,
)
router = APIRouter()
_TEMPLATES_DIR = Path(__file__).parent / "templates"
templates = Jinja2Templates(directory=str(_TEMPLATES_DIR))
DOWNLOADS = [
{
"filename": "taller-wox-tecnico.zip",
"icon": "code",
"title": "Material técnico",
"description": "Specs OpenAPI, configs y artefactos para importar a watsonx Orchestrate.",
},
{
"filename": "taller-wox-funcional.zip",
"icon": "book",
"title": "Material funcional",
"description": "Manual paso a paso del bootcamp y deck de slides.",
},
]
ALLOWED_FILENAMES = {d["filename"] for d in DOWNLOADS}
@router.get("/", response_class=HTMLResponse)
def landing(request: Request, error: str | None = None):
error_msg = None
if error == "token-invalido":
error_msg = "El link expiró o es inválido. Regístrate de nuevo para descargar el material."
return templates.TemplateResponse(
"index.html",
{"request": request, "error": error_msg},
)
@router.post("/register")
def register(
request: Request,
nombre: str = Form(..., min_length=2, max_length=80),
email: EmailStr = Form(...),
empresa: str = Form(..., min_length=2, max_length=100),
consentimiento: str = Form(...),
website: str | None = Form(default=""),
):
if consentimiento != "on":
raise HTTPException(status_code=400, detail="Consentimiento requerido")
if is_honeypot_filled(website):
fake_token = create_download_token(email="honeypot@discarded.local", nombre="x")
return RedirectResponse(url=f"/descargas?token={fake_token}", status_code=303)
client_ip = request.client.host if request.client else None
user_agent = request.headers.get("user-agent")
upsert_lead(
nombre=nombre,
email=str(email),
empresa=empresa,
ip=client_ip,
user_agent=user_agent,
consent=True,
)
token = create_download_token(email=str(email), nombre=nombre)
return RedirectResponse(url=f"/descargas?token={token}", status_code=303)
@router.get("/descargas", response_class=HTMLResponse)
def descargas(request: Request, token: str | None = None):
if not token:
return RedirectResponse(url="/?error=token-invalido", status_code=307)
try:
data = verify_download_token(token)
except (SignatureExpired, BadSignature):
return RedirectResponse(url="/?error=token-invalido", status_code=307)
settings = get_settings()
material_dir = Path(settings.material_dir)
downloads_view = []
for d in DOWNLOADS:
path = material_dir / d["filename"]
downloads_view.append({
**d,
"available": path.exists(),
"size_mb": round(path.stat().st_size / (1024 * 1024), 1) if path.exists() else 0,
})
return templates.TemplateResponse(
"descargas.html",
{
"request": request,
"nombre": data.get("nombre", "amig@"),
"token": token,
"downloads": downloads_view,
},
)
@router.get("/download/{filename}")
def download(request: Request, filename: str, token: str | None = None):
if not token:
raise HTTPException(status_code=401, detail="Missing token")
try:
data = verify_download_token(token)
except (SignatureExpired, BadSignature):
raise HTTPException(status_code=401, detail="Invalid or expired token")
if filename not in ALLOWED_FILENAMES:
raise HTTPException(status_code=404, detail="File not found")
path = Path(get_settings().material_dir) / filename
if not path.exists():
raise HTTPException(status_code=404, detail="File not available yet")
client_ip = request.client.host if request.client else None
log_download(lead_email=data["email"], filename=filename, ip=client_ip)
return FileResponse(
path=str(path),
filename=filename,
media_type="application/zip",
)