Saltar a contenido

Arquitectura

Stack

  • Backend: Python 3.12 · FastAPI · SQLAlchemy 2.0 async (asyncpg) · Pydantic v2 · Alembic · structlog.
  • Base de datos: PostgreSQL (UE).
  • Frontend: Angular 20 + Ionic (standalone components, signals).
  • Infra: Docker / docker-compose tras nginx-proxy + letsencrypt.
  • Terceros: Twilio (voz), Vapi (plataforma de voz), Claude (Anthropic), ElevenLabs, Telegram, email transaccional.

El backend no procesa audio: recibe webhooks (datos) de Vapi y del formulario web.

Hexagonal (backend app/)

domain/            # sin dependencias de framework
  entities/        # modelos de dominio (sin ORM)
  ports/           # interfaces (Protocol): LeadClassifier, Notifier…
  services/        # lógica de negocio (clasificación, enrutado, permisos)
adapters/http/
  routers/         # un *_router.py por recurso
  dto/             # esquemas Pydantic de request/response
  webhooks/        # Vapi + formulario web
infrastructure/
  persistence/     # models.py (ORM) + repositories/ (uno por agregado, filtran por tenant)
  voice/ claude/ telegram/ email/ captcha/

Reglas de dependencia

  • domain/ solo importa de domain/ y stdlib. Nunca adapters/infrastructure/SQLAlchemy.
  • Los routers llaman a domain/services y a repositories; no importan ORM ni ejecutan SQL directo.
  • Los repos reciben el tenant (organization_id / dealership_id) y filtran en la query.

Errores

Todo error recuperable se convierte en una variante de AppError (BadRequest 400, Unauthorized 401, Forbidden 403, NotFound 404, Conflict 409, Internal 500) y se serializa como {"error": <mensaje>}. Los errores internos se loguean con contexto y nunca exponen el detalle al cliente.

Seguridad

  • Contraseñas con Argon2id; login en tiempo constante.
  • JWT HS256; una cuenta suspendida deja de tener acceso de inmediato (se comprueba en cada petición, no solo al login).
  • CORS restrictivo; rate limiting (slowapi) y captcha (Turnstile, fail-closed) en los endpoints públicos.
  • Aislamiento por tenant en cada consulta.

Frontend

  • Componentes standalone, signals para estado, takeUntilDestroyed en las suscripciones.
  • Componentes propios en shared/ sobre Ionic estándar (sin librerías privadas externas).
  • Tokens CSS --mx-* + variables Ionic --ion-color-*; font-size en rem. data-testid en formato gk-<componente>-<elemento>.