Saltar a contenido

01 — Arquitectura del sistema

Stack técnico (versiones reales en producción)

Capa Tecnología Versión
Backend Django + Django REST Framework 4.2.7 / DRF 3.14.0
Auth djangorestframework-simplejwt 5.3.0
Base de datos PostgreSQL 15+ (probado en 17.5)
WSGI Gunicorn 21.2.0
Reverse proxy Nginx 1.26+
Frontend (cliente) React + Vite + TypeScript + Tailwind React 18, Vite 5
Frontend (superadmin) React + Vite + TypeScript + Tailwind React 18, Vite 5
Widget embebible JS vanilla bundleado con Vite
Pagos Stripe (Python SDK) 7.4.0
OS Ubuntu 22.04 / 24.04 / Debian 12
Python 3.11 o 3.12
Node 18+

Decisión deliberada: no hay Celery, Redis, FastAPI ni microservicios. Un solo proceso Django sirve toda la API, el panel admin, el superadmin, los webhooks Stripe y los endpoints públicos del widget.

Servicios y puertos

Servicio Puerto Comentario
Gunicorn (Django) 8000 (loopback) Único proceso de aplicación
PostgreSQL 5432 (loopback) Base principal
Nginx 80 / 443 Proxy + TLS + sirve estáticos

No expongas el 8000 ni el 5432 a internet. Nginx es el único punto público.

Flujo de una request HTTP

┌──────────────────┐
│  Browser cliente │
└────────┬─────────┘
         │  HTTPS
┌────────────────────────────────────────────────┐
│  Nginx (vhost del SaaS)                        │
│                                                │
│  • /static/*  → filesystem (alias)             │
│  • /media/*   → filesystem (uploads)           │
│  • /v3/*      → proxy_pass http://127.0.0.1:8000 │
│  • /api/*     → proxy_pass http://127.0.0.1:8000 │
│  • /admin/*   → proxy_pass http://127.0.0.1:8000 │
│  • /          → sirve apps/dashboard/dist/      │
└────────┬───────────────────────────────────────┘
┌─────────────────────────────────────────┐
│  Gunicorn (5 workers, 2 threads c/u)    │
│  └── Django (blimx_admin.wsgi)          │
│       ├── api/                           │
│       │   ├── views.py (DashboardViewSet)│
│       │   ├── views_billing.py           │
│       │   ├── views_superadmin.py        │
│       │   └── widget/  ← endpoints públicos del chatbot │
│       └── authentication/                │
│           ├── jwt_auth.py                │
│           └── views.py (login/register)  │
└────────┬─────────────────────────────────┘
┌─────────────────────┐       ┌──────────────────┐
│  PostgreSQL         │◄─────►│  Stripe API      │
│  (multi-tenant)     │       │  (HTTPS saliente)│
└─────────────────────┘       └──────────────────┘

Modelo multi-tenant

                ┌──────────────┐
                │   Account    │  (cuenta de empresa que compra el SaaS)
                │   (id UUID)  │
                └──────┬───────┘
                       │ 1:N
       ┌───────────────┼───────────────┐
       ▼               ▼               ▼
   ┌────────┐     ┌────────┐      ┌────────┐
   │  Bot   │     │  Bot   │ ...  │  Bot   │  (cada Bot = un Help Center)
   │ slug=  │     │ slug=  │      │ slug=  │
   │ acme   │     │ otro   │      │ ...    │
   └───┬────┘     └────────┘      └────────┘
       │ 1:N
       ├─────► KnowledgeCategory   (categorías de FAQs por idioma)
       ├─────► KnowledgeBase       (preguntas/respuestas)
       ├─────► TenantFrontendContent (ui_config + content del widget)
       ├─────► Subscription        (estado Stripe del bot)
       └─────► Invoice             (historial de facturas)
  • 1 Account representa al cliente final (la empresa que compra el plan).
  • 1 Account → N Bots, donde cada Bot es un Help Center con su propio slug, dominio, idiomas, FAQs, plan, suscripción y branding.
  • Slug del Bot (bots.slug) es el identificador que se pasa al widget como data-tenant.

Más detalle del schema en 07-base-datos.md.

Decisiones arquitectónicas

Por qué un solo backend Django

  • Una sola unidad desplegable → un solo systemd unit, un solo venv, un solo nginx upstream.
  • Migraciones nativas Django → cero scripts SQL custom, todo bajo manage.py migrate.
  • JWT stateless → escala horizontal sin Redis ni sticky sessions.
  • Stripe webhooks en el mismo proceso → no hay race conditions con la API principal.

Por qué no Celery

El SaaS entregado no genera trabajo background pesado. Los emails (verificación, reset, factura) se envían inline con EmailMessage.send(). Si el comprador necesita procesos async pesados (ej. importar 10k FAQs desde CSV), añadir Celery + Redis es trivial sin tocar el core.

Por qué Vite (no Webpack)

  • Tiempo de build del dashboard: ~6 segundos.
  • HMR instantáneo en desarrollo.
  • Output ESM moderno; el bundle del widget pesa 24 KB minificado + gzip.

Por qué el widget se bundlea con Vite

El widget vive en static/v3/, importa 7 módulos JS (Launcher, Modal, FaqView, etc.). Sin bundle, el browser haría 20+ requests para abrirlo. Con node build.js se genera 1 JS + 1 CSS que el iframe carga.

Por qué bot_slug en vez de bot_id UUID en URLs públicas

  • El slug es legible (acme-helpcenter).
  • Lo controla el Account → puede branding-aware.
  • El UUID se sigue usando internamente para FKs (más rápido).

Capa de seguridad

Riesgo Mitigación
SQL injection ORM Django + queries parametrizadas en raw SQL
XSS en frontend React escapa por defecto; nada de dangerouslySetInnerHTML con datos remotos
CSRF en /api/ DRF con sesión deshabilitada → JWT stateless inmune
Secret leak .env jamás se commitea (en .gitignore); SECRET_KEY distinto por entorno
Stripe webhook spoofing Verificación de firma con stripe.Webhook.construct_event()
Brute force en login django-ratelimit 4.1.0 (incluido en deps)
Permisos a archivos /static/ servido vía alias con permisos restringidos al user nginx
HTTPS Obligatorio en producción; Plesk emite cert Let's Encrypt automáticamente