my-tenants y my-apps no tienen caché propia en el Hub — se repetían en cada +layout.server.ts load del dashboard, sin importar cuántas veces navegara el usuario en la misma sesión. - jwt.ts: getJwtSessionKey() extrae sid/sub del JWT — identificador estable que sobrevive al refresh del access token (rota cada ~60s). - dashboard-shell-cache.ts: caché en memoria (Map + TTL de 30s) para el bundle de tenants/apps, keyed por session key — NUNCA por el access token, que rotando cada ~60s haría que cada set()/get() usaran llaves distintas y la caché nunca acertara. - +layout.server.ts: usa el caché antes de golpear al Hub; lo llena tras el primer fetch exitoso de la sesión. - dashboard-shell-cache.test.ts: cubre hit/miss, expiración por TTL, y que sobrevive a la rotación del access token (llave estable). Verificado: 5/5 tests nuevos pasan; svelte-check da los mismos 38 errores/8 warnings preexistentes que main (0 nuevos); los 2 fallos de backend.test.ts son preexistentes (ENOTFOUND backend fuera de Docker, igual en main).
Plantilla Workspace — Aduanasoft
Plantilla base para nuevos proyectos del ecosistema Workspace de Aduanasoft. Incluye autenticación SSO con Keycloak/Hub, arquitectura multi-tenant, y un dashboard funcional listo para extender.
Stack
| Capa | Tecnología |
|---|---|
| Frontend | SvelteKit 5 + Tailwind CSS |
| Backend | FastAPI + SQLAlchemy + PostgreSQL |
| Auth | Keycloak (OpenID Connect) vía Workspace Hub |
| Cache / Queue | Valkey (Redis) + Celery |
| Storage | MinIO (S3-compatible) |
| Contenedores | Docker Compose |
Estructura del proyecto
├── backend/
│ ├── api/v1/modules/
│ │ ├── core/ # Auth, usuarios, tenants, permisos, licencias
│ │ └── example/ # Módulo de referencia — copia y renombra
│ ├── core/ # Config, seguridad, DB, middleware
│ └── alembic/ # Migraciones (solo esquema core)
│
├── frontend/
│ ├── src/routes/
│ │ ├── auth/ # Callback OAuth2, SSO, logout
│ │ ├── login/ # Login local (dev) o redirect al workspace
│ │ └── dashboard/ # Shell + rutas stub
│ └── src/lib/
│ ├── server/ # workspace-auth, workspace-apps, api SSR
│ └── stores/ # company, system, workspace-apps
│
└── scripts/
└── auth-mode.sh # Alterna entre modo local y workspace
Inicio rápido
1. Clonar y configurar
git clone https://git.aduanasoft.com/ADUANASOFT/plantillas-proyectos.git mi-proyecto
cd mi-proyecto
Renombrar el proyecto en .env y docker-compose.yml:
CORE_DB_NAME=app_core→mi_proyecto_corename: appendocker-compose.yml→mi-proyecto
2. Levantar en modo local (sin workspace)
./scripts/auth-mode.sh local
docker compose up -d
Abre http://localhost:5173 → click "Entrar como dev".
3. Conectar al workspace
./scripts/auth-mode.sh workspace
# Pregunta:
# URL del workspace → https://workspace.aduanasoft.com
# Keycloak Realm → master
# Keycloak Client ID → nombre-del-client
El equipo del Hub debe registrar la app y agregar el redirect URI:
http://localhost:5173/auth/callback
Variables de entorno
El .env usa una sola URL base para derivar toda la configuración del workspace:
# Una variable, todo se deriva de aquí
WORKSPACE_URL=https://workspace.aduanasoft.com
KEYCLOAK_REALM=master
KEYCLOAK_CLIENT_ID=mi-app-frontend
# Auth local (desarrollo sin workspace)
DEV_LOCAL_AUTH=False # True = botón "Entrar como dev"
SECRET_KEY=... # Se genera automáticamente con auth-mode.sh local
El docker-compose.yml construye automáticamente:
HUB_URL=${WORKSPACE_URL}VITE_KEYCLOAK_URL=${WORKSPACE_URL}/kcauth
Agregar un módulo nuevo
Backend
Copia backend/api/v1/modules/example/ y renombra:
my_module/
├── __init__.py
├── models.py # SQLAlchemy — hereda TenantScopedMixin + TimestampMixin
├── dto.py # Pydantic v2 — Request / Response separados
├── service.py # Lógica de negocio, recibe db + tenant_id + company_id
└── routes.py # FastAPI router, usa Depends(get_current_user)
Registrar en backend/api/v1/router.py:
from .modules.my_module.routes import router as my_router
router.include_router(my_router, prefix="/my-module", tags=["my-module"])
Frontend
Crear frontend/src/routes/dashboard/my-module/+page.svelte y agregar al sidebar en
frontend/src/lib/components/sidebar/modules.ts:
{ title: 'Mi módulo', url: '/dashboard/my-module', icon: MyIcon }
Script auth-mode
./scripts/auth-mode.sh status # Ver modo actual
./scripts/auth-mode.sh local # Activar login local (dev sin workspace)
./scripts/auth-mode.sh workspace # Configurar y conectar al workspace
Al cambiar de modo el script pregunta si reiniciar los contenedores automáticamente.
Convenciones
- Commits: Conventional Commits (
feat:,fix:,refactor:,chore:) - Branches:
feature/DESC,fix/DESC - Nombres en código: inglés; comentarios de lógica de negocio en español
- Backend: Pydantic v2, async por default, routers por dominio
- Frontend: SvelteKit 5 runes (
$state,$derived,$effect), Tailwind utility-first