Files
taller-wox/docs/superpowers/specs/2026-05-12-taller-wox-design.md
farentsen a062b45c51 feat: initial implementation taller-wox.fitlabs.dev
Portal FastAPI + 5 endpoints REST para Bootcamp Agentic AI con
watsonx Orchestrate (FactorIT). Single container, Coolify-ready.

- Landing brandeado FIT con formulario de registro (honeypot anti-bot)
- Tokens itsdangerous para descargas (24h expiry)
- 5 endpoints API: historical/available procedures, member-insights,
  schedule, generate-report (Jinja2 + Plotly)
- SQLite con upsert-on-email para leads + log de descargas
- Admin endpoints (HTTP Basic): leads.json, leads.csv, stats
- 23 tests pytest pasando
- Dockerfile listo para Coolify con volúmenes persistentes
  (/app/leads.db, /app/app/data/reports_output, /app/material)

Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
2026-05-13 03:04:28 +00:00

22 KiB
Raw Blame History

Diseño — Portal taller-wox.fitlabs.dev

Fecha: 2026-05-12 Deadline workshop: 2026-05-14 (jueves, 48h) Owner: Felipe Arentsen (FactorIT) Dominio: https://taller-wox.fitlabs.dev Hosting: VPS propio gestionado por Coolify Estado: validado en brainstorming, listo para writing-plans

Este documento consolida las decisiones tomadas durante la sesión de brainstorming del 2026-05-12 sobre el SPEC_taller_wox_fitlabs.md original. El SPEC sigue siendo la fuente de detalle para schemas, paletas, código de referencia y datasets de los endpoints. Este doc lo modifica donde corresponda y deja explícito qué queda fuera de alcance.


1. Contexto y restricciones

1.1 Qué es esto

Portal web + API para el "Bootcamp Agentic AI con watsonx Orchestrate" de FactorIT. Dos funciones simultáneas:

  1. Frontend público + registro de leads + descarga de material del bootcamp.
  2. 5 endpoints REST consumidos en vivo por los agentes de watsonx Orchestrate que los alumnos construyen durante el taller.

1.2 Restricciones que mandan el diseño

  • 48 horas hasta el primer uso real (taller del jueves 2026-05-14).
  • Uso recurrente posterior con cohortes futuras → el diseño debe poder evolucionar, no necesita ya soportar la evolución.
  • Cohorte pequeña (~10-30 alumnos) → no necesita escalar.
  • Hosting Coolify → todo va en un container Docker, HTTPS automático con Let's Encrypt, volúmenes persistentes declarados en la UI.
  • Watsonx Orchestrate rechaza HTTPS con certs inválidos → Let's Encrypt automático resuelve esto.

1.3 Camino crítico para el jueves

  1. Los 5 endpoints /api/* funcionando y reachables desde watsonx (HTTPS válido).
  2. La página de descargas con los 2 ZIPs (uno técnico, uno funcional) accesibles.
  3. El formulario de registro funcionando (lead capture).

Lo demás (admin, polish visual extra, etc.) es nice-to-have.


2. Arquitectura

2.1 Decisión: un solo servicio FastAPI

Una sola app FastAPI corriendo en un container Docker. Sirve 3 superficies en el mismo puerto:

  • Frontend + registro: GET /, GET /descargas, POST /register, GET /download/{file}
  • API del taller: los 5 endpoints /api/*
  • Admin básico: GET /admin/leads.json, GET /admin/leads.csv, GET /admin/stats (con HTTP Basic auth)

Por qué un solo servicio (y no split): para 48h y una cohorte de ~30 alumnos, los costos operacionales de partir esto en dos servicios (otro container, otra config en Coolify, comunicación interna) superan ampliamente los beneficios. Aceptamos el riesgo de que un bug en el landing pueda afectar la API en vivo; lo mitigamos con un Dockerfile simple, un test smoke pre-deploy, y la decisión explícita de no tocar nada el día del taller.

Cuándo reconsiderar: si en cohortes futuras la API empieza a tener cambios frecuentes (nuevos endpoints por workshop, datasets dinámicos), separar API y frontend en dos servicios independientes pasa a ser justificable.

2.2 Stack

  • Backend: Python 3.11+ con FastAPI + Uvicorn (single worker — suficiente para esta escala)
  • Templates: Jinja2
  • Datos tabulares (endpoints 1 y 2): pandas, CSVs cargados en memoria al boot del servicio
  • Charts (endpoint 5): Plotly
  • Persistencia: SQLite file-based (leads.db)
  • Tokens de descarga: itsdangerous.URLSafeTimedSerializer, expiración 24h
  • Frontend: HTML + CSS + JS vanilla, sin framework

Sin Redis, sin Postgres, sin Celery, sin frontend SPA.

2.3 Diagrama lógico

                       Internet
                          │
                          ▼
            ┌─────────────────────────────┐
            │  Coolify (VPS, Let's Encrypt)│
            └─────────────┬───────────────┘
                          │
                          ▼
            ┌─────────────────────────────┐
            │   Container Docker           │
            │  ┌────────────────────────┐  │
            │  │   FastAPI + Uvicorn    │  │
            │  │  ┌──────────────────┐  │  │
            │  │  │ / + /descargas + │  │  │
            │  │  │ /register +      │  │  │
            │  │  │ /download/{file} │  │  │
            │  │  ├──────────────────┤  │  │
            │  │  │  /api/* (los 5)  │  │  │
            │  │  ├──────────────────┤  │  │
            │  │  │  /admin/*        │  │  │
            │  │  └──────────────────┘  │  │
            │  └────────────────────────┘  │
            │                              │
            │  Volúmenes persistentes:     │
            │   • /app/leads.db            │
            │   • /app/app/data/           │
            │       reports_output/        │
            │   • /app/material/           │
            └──────────────────────────────┘

3. Componentes

3.1 Frontend público (landing)

Estructura visual del SPEC §3.2 sin cambios:

  • Hero navy con CTA naranja "Acceder al material"
  • 3 cards "¿Qué vas a construir?"
  • 4 stats "El taller en números" (4h, 6 módulos, 0 líneas de código, 100% hands-on)
  • Sección 4: formulario de registro inline
  • Footer navy con logos y créditos

Branding: paleta del SPEC §3.1, tipografía Inter desde Google Fonts. Logo LogoFIT.png (a comprimir, queda en static/img/). Logo IBM watsonx Orchestrate: placeholder de texto estilizado ("powered by IBM watsonx Orchestrate") en el header derecho — se reemplaza por logo oficial cuando esté disponible, sin redeploy si lo dejamos como SVG inline editable.

3.2 Formulario de registro (POST /register)

Campos:

Campo Validación
nombre text, min 2, max 80, required
email email válido, required
empresa text, min 2, max 100, required
consentimiento checkbox, debe estar marcado
website honeypot — campo invisible vía CSS display:none; si llega lleno, descartar silenciosamente con 200 OK falso

Comportamiento con email duplicado: si el email ya existe en la tabla leads, no es error — se actualiza last_seen (nueva columna), se incrementa times_registered, y se emite un token nuevo igual. Fricción cero. El email es lead capture, no auth.

Respuesta exitosa: redirección a /descargas?token=<token>.

Token: itsdangerous.URLSafeTimedSerializer(SECRET_KEY).dumps({"email": email, "nombre": nombre}), expira 24h (TOKEN_EXPIRY_HOURS env).

3.3 Página de descargas (GET /descargas?token=...)

Valida token. Si inválido o expirado → redirige a /?error=token-invalido y la home muestra banner "El link expiró, regístrate de nuevo".

Si válido, renderiza:

  • Saludo "¡Hola {nombre}!"

  • 2 cards grandes (cambio importante vs SPEC §3.4 que tenía 8-9 cards):

    1. 🧩 Material técnicotaller-wox-tecnico.zip Descripción: "Specs OpenAPI, configs y artefactos para importar a watsonx Orchestrate"
    2. 📚 Material funcionaltaller-wox-funcional.zip Descripción: "Manual paso a paso del bootcamp y deck de slides"

    Cada card: icono, título, descripción 1 línea, tamaño en MB (calculado dinámicamente al servir la página), botón naranja "Descargar".

Cards hardcodeadas en el template (template Jinja2 + lista Python en app/frontend.py). Si en el futuro se quiere editar copy sin redeploy, pasar a app/data/downloads.json.

Contenido de los ZIPs: Felipe los arma y los sube vía SFTP / file manager de Coolify directo al volumen /app/material/. El backend no decide, no genera, no transforma — solo sirve lo que esté en esa carpeta.

3.4 Descarga (GET /download/{filename}?token=...)

  • Valida token query param. Si inválido → 401.
  • Verifica que filename esté en una whitelist hardcodeada ({"taller-wox-tecnico.zip", "taller-wox-funcional.zip"}) para prevenir path traversal.
  • Sirve el archivo desde /app/material/{filename} con Content-Disposition: attachment.
  • Registra la descarga en la tabla downloads (lead_email, filename, ip, downloaded_at) — útil para /admin/stats.

3.5 API del taller (los 5 endpoints)

Todos los endpoints del SPEC §5 se mantienen exactamente como están especificados (mismo contrato, mismo schema, mismas respuestas). Esta sección solo enumera el alcance y enlaza al SPEC para el detalle.

# Endpoint Tipo Detalle
1 POST /api/historical-procedures Filtros + group_by sobre CSV histórico SPEC §5.1
2 POST /api/available-procedures Filtros + group_by sobre CSV catálogo SPEC §5.2
3 GET /api/member-insights JSON estático del afiliado demo SPEC §5.3
4 GET /api/schedule Guía estática de scheduling SPEC §5.4
5 POST /api/reports/generate-report Genera HTML con Jinja2 + Plotly, guarda en disco, devuelve URL pública SPEC §5.5

CORS: allow_origins=["*"], allow_methods=["GET", "POST"] — necesario para que los agentes de watsonx Orchestrate puedan llamar los endpoints desde *.cloud.ibm.com.

Sin rate limiting, sin cleanup automático de los HTMLs generados (volumen esperado: ~150 archivos × 30 KB = 5 MB por taller, despreciable; si crece, se limpia con un cron manual o rm puntual).

Datasets sintéticos (los genero yo siguiendo el SPEC §9): ver sección 6 de este doc.

3.6 Admin básico

3 endpoints, todos detrás de HTTP Basic auth (ADMIN_USER + ADMIN_PASS desde env):

  • GET /admin/leads.json — lista paginada de leads (?limit=100&offset=0)
  • GET /admin/leads.csv — exporta todo como CSV descargable (Excel-friendly, UTF-8 con BOM para que Excel respete acentos)
  • GET /admin/stats — JSON con: total_leads, total_downloads, downloads_por_archivo, top_5_empresas

Sin UI, sin gráficos, sin filtros. JSON crudo + CSV download.


4. Datos y persistencia

4.1 Esquema SQLite

CREATE TABLE IF NOT EXISTS leads (
    id                INTEGER PRIMARY KEY AUTOINCREMENT,
    nombre            TEXT NOT NULL,
    email             TEXT NOT NULL UNIQUE,
    empresa           TEXT NOT NULL,
    ip                TEXT,
    user_agent        TEXT,
    consent           INTEGER NOT NULL DEFAULT 0,
    times_registered  INTEGER NOT NULL DEFAULT 1,
    created_at        TIMESTAMP DEFAULT CURRENT_TIMESTAMP,
    last_seen         TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

CREATE TABLE IF NOT EXISTS downloads (
    id            INTEGER PRIMARY KEY AUTOINCREMENT,
    lead_email    TEXT NOT NULL,
    filename      TEXT NOT NULL,
    ip            TEXT,
    downloaded_at TIMESTAMP DEFAULT CURRENT_TIMESTAMP
);

CREATE INDEX IF NOT EXISTS idx_downloads_email ON downloads(lead_email);
CREATE INDEX IF NOT EXISTS idx_downloads_filename ON downloads(filename);

Cambio vs SPEC §4.1: agregué UNIQUE en email, times_registered, last_seen para soportar la lógica de duplicados decidida en brainstorming.

4.2 Lo que va en disco persistente

Path en container Qué contiene Volumen Coolify
/app/leads.db SQLite con leads y downloads
/app/app/data/reports_output/ HTMLs generados por endpoint 5
/app/material/ Los 2 ZIPs que sube Felipe

4.3 Lo que va dentro del container (no persistente, viaja con la imagen)

  • Código de la app (/app/app/)
  • Datasets sintéticos de los endpoints (/app/app/data/*.csv, *.json, *.txt) — son inmutables, cambios pasan por commit + redeploy
  • Static files (/app/static/) — CSS, JS, imágenes
  • Templates Jinja2 (/app/app/templates/)

5. Deploy

5.1 Dockerfile

FROM python:3.11-slim
WORKDIR /app

COPY requirements.txt .
RUN pip install --no-cache-dir -r requirements.txt

COPY app/ ./app/
COPY static/ ./static/

# crear directorios runtime (se montan como volúmenes en prod)
RUN mkdir -p app/data/reports_output material

EXPOSE 8000
CMD ["uvicorn", "app.main:app", "--host", "0.0.0.0", "--port", "8000"]

Sin healthcheck por simplicidad (Coolify pinguea HTTP / por defecto). Sin multi-stage build — la imagen final pesa ~250 MB y no vale la pena optimizar para 48h.

5.2 Configuración Coolify

Volúmenes persistentes a declarar en la UI:

Mount point Tipo
/app/leads.db bind file
/app/app/data/reports_output/ bind dir
/app/material/ bind dir

Variables de entorno:

SECRET_KEY=<random 64-char string>
ADMIN_USER=felipe
ADMIN_PASS=<random largo>
TOKEN_EXPIRY_HOURS=24
BASE_URL=https://taller-wox.fitlabs.dev

DNS: apuntar taller-wox.fitlabs.dev al IP del VPS de Coolify desde el panel de DNS del registrar. Coolify gestiona el cert Let's Encrypt automáticamente.

5.3 Workflow de actualización

  • Cambios de código (endpoints, frontend, templates): commit + push → Coolify rebuildea + redeploy.
  • Cambios de los ZIPs descargables: Felipe sube directo al volumen /app/material/ vía SFTP o file manager de Coolify — sin redeploy.
  • Cambios de copy de las cards de descarga: requieren redeploy (están hardcodeadas). Aceptable por ahora.

6. Datasets sintéticos que tengo que generar

Todos siguiendo los esquemas del SPEC §5 y §9. Coherencia entre datasets es crítica.

6.1 app/data/historical_procedures.csv (~50 filas)

Columnas exactas del SPEC §5.1. Reglas de coherencia:

  • Al menos 4 filas con member_name = Charlie Smith y relationship = Self, con procedimientos y date consistentes con los last_date de member_insights.json:
    • Annual Physical Exam con date = 2022-06-15
    • Dental Cleaning con date = 2023-01-10
    • Vision Exam con date = 2021-08-22
    • Blood Test - Cholesterol Panel con date = 2023-03-04
  • Resto de filas: otros member_names (Alice, Bob, Diana, Ethan) con relationships variados (Self, Spouse, Mother, Father, Son, Daughter), con procedimientos variados que cubren preventive, surgery, diagnostic.
  • Costos realistas: X Ray $80-300, MRI $1500-3000, Annual Physical $200-400, Appendectomy $10k-30k, Dental Cleaning $80-200.
  • in_network mezcla de true/false (~70/30).
  • member_plan mezcla de los 3 planes del SPEC.

6.2 app/data/available_procedures.csv (~25-30 filas)

Columnas del SPEC §5.2. Reglas:

  • Cubrir al menos estos procedure: X Ray, MRI, CT Scan, Annual Physical Exam, Appendectomy, Dental Cleaning, Vision Exam, Blood Test, Angioplasty, Ultrasound.
  • Cada procedimiento aparece en 2-4 ubicaciones distintas (City Hospital, Regional Medical Center, Green Valley Clinic, Sunrise Health) con costos y distancias distintas.
  • gold_ppo_plan_accepted true en ~80% de las filas (es el plan de Charlie Smith).
  • Costos realistas y diferentes a los de historical_procedures.csv (uno es histórico, otro es catálogo).

6.3 app/data/member_insights.json

JSON estático basado en el SPEC §5.3, con un ajuste obligatorio: las fechas de overdue_procedures del SPEC están escritas con base ~2024 (ej. last_date: "2022-06-15" con due_since_months: 22). Hoy es 2026-05-12, así que esos cálculos están desfasados ~2 años. Hay que re-calibrar last_date y due_since_months para que sean coherentes con la fecha actual y el recommended_frequency_months de cada item. Las fechas resultantes también deben calzar con las filas correspondientes en historical_procedures.csv (sección 6.1).

Ejemplo de re-calibración para Annual Physical Exam (frecuencia recomendada: 12 meses, prioridad alta):

  • last_date: ~24 meses atrás → 2024-05-15
  • due_since_months: 12 (debido hace 1 año)

6.4 app/data/schedule_response.txt

Texto estático tal cual el SPEC §5.4.

6.5 Archivos para endpoint 5 (reportes)

  • app/data/combined_email.txt — email mezclado proveedor + paciente (ejemplo en SPEC §5.5)
  • app/data/provider_email.txt — email solo del proveedor con jerga médica
  • app/data/aetna_email.txt — email de aseguradora con EOB
  • app/data/aetna_claim_review_summary.csv (~10 filas) — CPT codes con charged/allowed/plan_paid/patient_responsibility coherentes (el último iguala los anteriores menos los descuentos)

7. Estructura de carpetas final

taller-wox/
├── app/
│   ├── main.py                    # FastAPI app, mounts, CORS, lifespan
│   ├── frontend.py                # GET /, GET /descargas, POST /register, GET /download
│   ├── benefits_api.py            # endpoints 1-4
│   ├── reports_api.py             # endpoint 5
│   ├── admin.py                   # endpoints /admin/*
│   ├── db.py                      # SQLite, init schema, helpers
│   ├── security.py                # token sign/verify, basic auth, honeypot
│   ├── data/
│   │   ├── historical_procedures.csv
│   │   ├── available_procedures.csv
│   │   ├── member_insights.json
│   │   ├── schedule_response.txt
│   │   ├── combined_email.txt
│   │   ├── provider_email.txt
│   │   ├── aetna_email.txt
│   │   ├── aetna_claim_review_summary.csv
│   │   └── reports_output/        # generados por endpoint 5 (volumen persistente)
│   └── templates/
│       ├── index.html             # landing + registro
│       ├── descargas.html         # página post-registro
│       └── report.html            # wrapper del reporte (endpoint 5)
├── static/
│   ├── css/styles.css
│   ├── img/
│   │   ├── LogoFIT.png            # ya existe, comprimir
│   │   └── favicon.ico            # generar
│   └── js/app.js
├── material/                       # volumen persistente, Felipe sube los ZIPs
│   ├── taller-wox-tecnico.zip
│   └── taller-wox-funcional.zip
├── docs/
│   └── superpowers/
│       └── specs/
│           └── 2026-05-12-taller-wox-design.md  # este archivo
├── leads.db                        # volumen persistente
├── Dockerfile
├── requirements.txt
├── .env.example
└── README.md

8. Cuts explícitos (lo que NO se hace para el jueves)

  • Email transaccional (SMTP)
  • Admin UI / dashboard con gráficos
  • ZIP "todo en uno" como tercera descarga
  • ZIP-por-archivo individual
  • Re-captcha (solo honeypot)
  • Stats avanzados / analytics
  • Multi-idioma (solo español)
  • Editor visual de leads
  • Rate limiting en /api/*
  • Cleanup automático de reportes generados
  • Conversión PPTX → PDF (Felipe arma los ZIPs)
  • Logo IBM watsonx oficial (placeholder de texto hasta tener el oficial)
  • Multi-stage Docker build
  • Healthcheck custom (Coolify usa ping HTTP por defecto)
  • Tests automatizados extensos (solo smoke tests manuales del SPEC §8)

9. Criterios de aceptación para el jueves 2026-05-14

Antes del taller, todo lo siguiente debe estar verde:

Smoke tests manuales:

  • GET https://taller-wox.fitlabs.dev/ devuelve 200 con HTML brandeado FIT
  • Submit del form con datos válidos redirige a /descargas?token=...
  • La página de descargas muestra las 2 cards y el saludo con el nombre
  • GET /download/taller-wox-tecnico.zip?token=... descarga el ZIP correcto
  • GET /download/taller-wox-funcional.zip?token=... descarga el ZIP correcto
  • Token inválido → redirige a home con banner de error
  • Email duplicado en form → re-emite token sin error

API del taller (con curl desde laptop):

  • POST /api/historical-procedures con {"filters":"[]","group_by":"[]"} devuelve filas
  • POST /api/historical-procedures con filtro member_name = Charlie Smith devuelve las 4 filas esperadas
  • POST /api/available-procedures con filtro procedure contains MRI devuelve resultados
  • GET /api/member-insights devuelve el objeto con overdue_procedures con 4 items
  • GET /api/schedule devuelve el texto de scheduling
  • POST /api/reports/generate-report con {"layout_config":"[\"care_report\"]"} devuelve un public_url que abre un HTML válido

End-to-end con watsonx Orchestrate:

  • Importar openapi-tools-spec.json en un agente nuevo de watsonx funciona sin errores de TLS
  • Prompt "¿Estoy atrasado en algún chequeo?" devuelve los 4 procedimientos overdue
  • Prompt "¿Cuánto cuesta una resonancia magnética?" devuelve resultados de MRI

Admin:

  • GET /admin/leads.csv con basic auth descarga CSV con los leads de prueba
  • GET /admin/stats devuelve los conteos esperados

Operacional:

  • Coolify rebuildea sin errores al hacer push
  • Redeploy NO borra leads.db ni los ZIPs (volúmenes funcionando)
  • HTTPS válido (curl al dominio sin warning de cert)

10. Riesgos identificados

Riesgo Mitigación
Cambio de DNS no propaga a tiempo Configurar DNS lo antes posible (hoy mismo idealmente), TTL bajo
Volúmenes Coolify mal configurados → pérdida de datos en redeploy Probar un redeploy de prueba antes del miércoles y verificar que leads.db sobrevive
Watsonx no puede llamar a la API por problema de TLS o CORS Smoke test end-to-end con un agente de prueba el miércoles
Datasets sintéticos inconsistentes (Charlie Smith con datos que no calzan) Validar cruzando historical_procedures.csv vs member_insights.json antes de deploy
Tamaño del PNG del logo (971 KB) impacta carga del landing Comprimir a <100 KB con tinypng o similar antes del deploy
Bug en código del frontend tumba la API en vivo durante el taller Política: no tocar código el día del taller. Si hay bug crítico en API, rollback inmediato a la versión anterior en Coolify

11. Lo que NO está en este diseño y necesita decidirse después del jueves

  • Persistencia de los HTMLs generados por endpoint 5 — política de retención (¿borrar después de N días?)
  • Si vienen cohortes con datasets distintos, cómo soportar multi-cohort sin redeploy
  • Si el admin necesita UI real (dashboard, gráficos)
  • Si conviene separar API y frontend en dos servicios
  • Si vale la pena agregar webhook a Slack/email cuando se registra un lead nuevo (lead capture activo)

Fin del diseño. Siguiente paso: invocar skill writing-plans para crear el plan de implementación paso a paso.