docs: README para plantilla Workspace
This commit is contained in:
492
README.md
492
README.md
@@ -1,344 +1,156 @@
|
|||||||
# Anexo76
|
# Plantilla Workspace — Aduanasoft
|
||||||
|
|
||||||
**Aplicación SaaS para gestión de comercio exterior conforme a Anexos 24, 30 y 22 del SAT**
|
Plantilla base para nuevos proyectos del ecosistema **Workspace de Aduanasoft**.
|
||||||
|
Incluye autenticación SSO con Keycloak/Hub, arquitectura multi-tenant, y un dashboard
|
||||||
Anexo76 es una plataforma multi-tenant diseñada para maquilas, empresas IMMEX y agentes aduanales, que permite gestionar inventarios, pedimentos y facturas de importación/exportación con control de licencias y cumplimiento normativo.
|
funcional listo para extender.
|
||||||
|
|
||||||
## 🏗️ Arquitectura
|
|
||||||
|
|
||||||
### Backend
|
|
||||||
|
|
||||||
- **Framework**: FastAPI 0.110+
|
|
||||||
- **Autenticación**: Keycloak (OpenID Connect)
|
|
||||||
- **Base de Datos**: PostgreSQL con SQLAlchemy
|
|
||||||
- **Modelo Multi-tenant**: Híbrido
|
|
||||||
- BD compartida para tenants pequeños/medianos
|
|
||||||
- BD dedicadas para clientes enterprise
|
|
||||||
- **Estructura Modular**: Patrón similar a NestJS
|
|
||||||
- `models.py`: Modelos ORM (SQLAlchemy)
|
|
||||||
- `dto.py`: Data Transfer Objects (Pydantic)
|
|
||||||
- `service.py`: Lógica de negocio
|
|
||||||
- `routes.py`: Endpoints API
|
|
||||||
|
|
||||||
### Frontend
|
|
||||||
|
|
||||||
- **Framework**: SvelteKit
|
|
||||||
- **Autenticación**: keycloak-js
|
|
||||||
- **UI**: Dashboard moderno y responsivo
|
|
||||||
|
|
||||||
### Infraestructura
|
|
||||||
|
|
||||||
- **Containerización**: Docker / Docker Compose
|
|
||||||
- **Orquestación**: Kubernetes (futuro)
|
|
||||||
- **Monitoreo**: Prometheus + Grafana
|
|
||||||
|
|
||||||
## 📁 Estructura del Proyecto
|
|
||||||
|
|
||||||
```
|
|
||||||
anexo76/
|
|
||||||
├── backend/
|
|
||||||
│ ├── main.py # Aplicación FastAPI principal
|
|
||||||
│ ├── requirements.txt # Dependencias Python
|
|
||||||
│ ├── .env.example # Variables de entorno de ejemplo
|
|
||||||
│ ├── core/ # Módulos core
|
|
||||||
│ │ ├── config.py # Configuración centralizada
|
|
||||||
│ │ ├── paths.py # Rutas base y layout_path() para importación CSV
|
|
||||||
│ │ ├── database.py # Configuración de BD multi-tenant
|
|
||||||
│ │ ├── security.py # Autenticación y autorización
|
|
||||||
│ │ └── middleware.py # Middlewares personalizados
|
|
||||||
│ ├── layouts/ # Directorio de datos: temp y errors de importación CSV (creado al arranque)
|
|
||||||
│ └── api/
|
|
||||||
│ └── v1/
|
|
||||||
│ ├── router.py # Router principal API v1
|
|
||||||
│ └── modules/ # Módulos de negocio
|
|
||||||
│ └── a76/
|
|
||||||
│ ├── layouts_csv/ # Lógica centralizada de cargas por CSV (routes, tasks, validaciones por proceso)
|
|
||||||
│ │ ├── facturas/
|
|
||||||
│ │ ├── customs_brokers/
|
|
||||||
│ │ ├── clients_and_providers/
|
|
||||||
│ │ └── ... # Una carpeta por proceso de importación
|
|
||||||
│ ├── csv_templates/
|
|
||||||
│ └── ...
|
|
||||||
│ ├── auth/
|
|
||||||
│ ├── tenants/ # Gestión de tenants
|
|
||||||
│ ├── licenses/ # Control de licencias
|
|
||||||
│ └── ...
|
|
||||||
├── frontend/
|
|
||||||
│ ├── src/
|
|
||||||
│ │ ├── routes/
|
|
||||||
│ │ ├── lib/
|
|
||||||
│ │ └── app.html
|
|
||||||
│ ├── package.json
|
|
||||||
│ └── svelte.config.js
|
|
||||||
├── docker-compose.yml
|
|
||||||
└── README.md
|
|
||||||
```
|
|
||||||
|
|
||||||
## 🚀 Inicio Rápido
|
|
||||||
|
|
||||||
### Requisitos Previos
|
|
||||||
|
|
||||||
- Docker y Docker Compose
|
|
||||||
- Python 3.11+ (para desarrollo local)
|
|
||||||
- Node.js 18+ (para desarrollo frontend)
|
|
||||||
|
|
||||||
### 1. Clonar el repositorio
|
|
||||||
|
|
||||||
```bash
|
|
||||||
git clone <repository-url>
|
|
||||||
cd anexo76
|
|
||||||
```
|
|
||||||
|
|
||||||
### 2. Configurar variables de entorno
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cp backend/.env.example backend/.env
|
|
||||||
# Editar backend/.env con tus configuraciones
|
|
||||||
```
|
|
||||||
|
|
||||||
### 3. Iniciar con Docker Compose
|
|
||||||
|
|
||||||
```bash
|
|
||||||
docker-compose up -d
|
|
||||||
```
|
|
||||||
|
|
||||||
Esto iniciará:
|
|
||||||
|
|
||||||
- **PostgreSQL** en `localhost:5432`
|
|
||||||
- **Keycloak** en `localhost:8080`
|
|
||||||
- **Backend API** en `localhost:8000`
|
|
||||||
- **Frontend** en `localhost:5173`
|
|
||||||
|
|
||||||
### 4. Configurar Keycloak
|
|
||||||
|
|
||||||
1. Acceder a Keycloak: http://localhost:8080
|
|
||||||
2. Login: `admin` / `admin`
|
|
||||||
3. Crear un nuevo realm o usar el realm `master`
|
|
||||||
4. Crear cliente para backend:
|
|
||||||
- Client ID: `anexo76-backend`
|
|
||||||
- Client Protocol: `openid-connect`
|
|
||||||
- Access Type: `confidential`
|
|
||||||
- Copiar el Secret y agregarlo a `.env`
|
|
||||||
5. Crear cliente para frontend:
|
|
||||||
- Client ID: `anexo76-frontend`
|
|
||||||
- Client Protocol: `openid-connect`
|
|
||||||
- Access Type: `public`
|
|
||||||
- Valid Redirect URIs: `http://localhost:5173/*`
|
|
||||||
|
|
||||||
### 5. Inicializar base de datos
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Con Docker
|
|
||||||
docker-compose exec backend python -c "from core.database import init_db; init_db()"
|
|
||||||
|
|
||||||
# O localmente
|
|
||||||
cd backend
|
|
||||||
python -c "from core.database import init_db; init_db()"
|
|
||||||
```
|
|
||||||
|
|
||||||
### 6. Acceder a la aplicación
|
|
||||||
|
|
||||||
- **API Documentation**: http://localhost:8000/docs
|
|
||||||
- **Frontend**: http://localhost:5173
|
|
||||||
- **Keycloak Admin**: http://localhost:8080
|
|
||||||
|
|
||||||
## 🛠️ Produccion
|
|
||||||
|
|
||||||
```
|
|
||||||
docker build -t dev.aduanasoft.com/anexo76/backend:latest -f ./backend/Dockerfile ./backend
|
|
||||||
```
|
|
||||||
|
|
||||||
**Importación CSV y workers Celery**: La lógica de cargas por CSV está en `api/v1/modules/a76/layouts_csv/` (una carpeta por proceso). Los workers Celery deben cargar los módulos `api.v1.modules.a76.layouts_csv.<proceso>.tasks`. El directorio de trabajo del worker debe ser la raíz del backend para que `layout_path("imports", "temp")` y `layout_path("imports", "errors")` apunten a los mismos directorios que la API. Si API y worker comparten volumen, los archivos temporales y de errores se escriben en `backend/layouts/imports/temp` y `backend/layouts/imports/errors`.
|
|
||||||
|
|
||||||
```
|
|
||||||
docker build -t dev.aduanasoft.com/anexo76/backend:latest -f ./backend/Dockerfile ./backend
|
|
||||||
docker build \
|
|
||||||
--build-arg VITE_API_URL=https://anexo76-dev.aduanasoft.com/api/ \
|
|
||||||
--build-arg VITE_KEYCLOAK_URL=https://anexo76-dev.aduanasoft.com/kcauth/ \
|
|
||||||
--build-arg INTERNAL_API_URL=http://backend:3467/api/ \
|
|
||||||
-t dev.aduanasoft.com/anexo76/frontend:latest \
|
|
||||||
-f ./frontend/Dockerfile.prod \
|
|
||||||
./frontend
|
|
||||||
```
|
|
||||||
|
|
||||||
Publica en el registro (ajusta el registry si corresponde):
|
|
||||||
|
|
||||||
```
|
|
||||||
docker login dev.aduanasoft.com
|
|
||||||
docker push dev.aduanasoft.com/anexo76/backend:latest
|
|
||||||
docker push dev.aduanasoft.com/anexo76/frontend:latest
|
|
||||||
```
|
|
||||||
|
|
||||||
## 🛠️ Desarrollo Local
|
|
||||||
|
|
||||||
### Backend
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cd backend
|
|
||||||
python -m venv .venv
|
|
||||||
source .venv/bin/activate # En Windows: .venv\Scripts\activate
|
|
||||||
pip install -r requirements.txt
|
|
||||||
uvicorn main:app --reload
|
|
||||||
```
|
|
||||||
|
|
||||||
Los directorios `backend/layouts/imports/temp` y `backend/layouts/imports/errors` se crean automáticamente al arrancar el backend para la importación CSV.
|
|
||||||
|
|
||||||
### Frontend
|
|
||||||
|
|
||||||
```bash
|
|
||||||
cd frontend
|
|
||||||
npm install
|
|
||||||
npm run dev
|
|
||||||
```
|
|
||||||
|
|
||||||
## 📦 Módulos Principales
|
|
||||||
|
|
||||||
### 1. **Auth** (`/v1/auth`)
|
|
||||||
|
|
||||||
- Login con Keycloak
|
|
||||||
- Refresh token
|
|
||||||
- Logout
|
|
||||||
- Información de usuario
|
|
||||||
|
|
||||||
### 2. **Tenants** (`/v1/tenants`)
|
|
||||||
|
|
||||||
- Creación y gestión de tenants
|
|
||||||
- Upgrade de BD compartida a dedicada
|
|
||||||
- Gestión de realms de Keycloak
|
|
||||||
|
|
||||||
### 3. **Licenses** (`/v1/licenses`)
|
|
||||||
|
|
||||||
- Control de planes (Free, Basic, Professional, Enterprise)
|
|
||||||
- Validación de licencias activas
|
|
||||||
- Tracking de uso (usuarios, storage, operaciones)
|
|
||||||
- Límites por plan
|
|
||||||
|
|
||||||
## 🔐 Autenticación y Autorización
|
|
||||||
|
|
||||||
### Flujo de Autenticación
|
|
||||||
|
|
||||||
1. Usuario ingresa credenciales + tenant_slug
|
|
||||||
2. Backend valida contra Keycloak del realm del tenant
|
|
||||||
3. Keycloak retorna JWT con tenant_id y roles
|
|
||||||
4. Middleware valida tenant y licencia en cada request
|
|
||||||
5. Request procesado si todo es válido
|
|
||||||
|
|
||||||
### Roles Disponibles
|
|
||||||
|
|
||||||
- `admin`: Administrador con acceso total
|
|
||||||
- `user`: Usuario estándar
|
|
||||||
- `auditor`: Solo lectura con acceso a reportes
|
|
||||||
- `system`: Para integraciones y servicios
|
|
||||||
|
|
||||||
## 🎯 Multi-Tenancy
|
|
||||||
|
|
||||||
### Modelo Híbrido
|
|
||||||
|
|
||||||
**BD Compartida** (tenants pequeños/medianos):
|
|
||||||
|
|
||||||
- Tabla única con `tenant_id` como foreign key
|
|
||||||
- Row-level security
|
|
||||||
- Más económico para clientes con bajo volumen
|
|
||||||
|
|
||||||
**BD Dedicada** (tenants enterprise):
|
|
||||||
|
|
||||||
- Base de datos PostgreSQL independiente
|
|
||||||
- Máximo aislamiento y performance
|
|
||||||
- Configuración almacenada en `tenants.db_config`
|
|
||||||
|
|
||||||
### Migración de Shared a Dedicated
|
|
||||||
|
|
||||||
```python
|
|
||||||
# Ejemplo de upgrade
|
|
||||||
from api.v1.modules.tenants.service import TenantService
|
|
||||||
|
|
||||||
db_config = {
|
|
||||||
"host": "dedicated-db-host.example.com",
|
|
||||||
"port": 5432,
|
|
||||||
"name": "tenant_123_db",
|
|
||||||
"user": "tenant_123_user",
|
|
||||||
"password": "secure_password"
|
|
||||||
}
|
|
||||||
|
|
||||||
service = TenantService(db)
|
|
||||||
service.upgrade_to_dedicated(tenant_id=123, db_config=db_config)
|
|
||||||
```
|
|
||||||
|
|
||||||
## 📊 Control de Licencias
|
|
||||||
|
|
||||||
### Planes Disponibles
|
|
||||||
|
|
||||||
|
|
||||||
| Plan | Usuarios | Storage | Operaciones/mes | Features |
|
|
||||||
| ------------ | --------- | --------- | --------------- | -------------------------------- |
|
|
||||||
| Free | 5 | 10 GB | 1,000 | API básica |
|
|
||||||
| Basic | 20 | 50 GB | 10,000 | + Reportes |
|
|
||||||
| Professional | 100 | 200 GB | 50,000 | + Integraciones |
|
|
||||||
| Enterprise | Ilimitado | Ilimitado | Ilimitado | + Soporte dedicado + BD dedicada |
|
|
||||||
|
|
||||||
### Middleware de Validación
|
|
||||||
|
|
||||||
El `LicenseValidationMiddleware` verifica en cada request:
|
|
||||||
|
|
||||||
- ✅ Licencia activa
|
|
||||||
- ✅ No expirada
|
|
||||||
- ✅ Límites no excedidos
|
|
||||||
- ✅ Features habilitadas
|
|
||||||
|
|
||||||
## 🧪 Testing
|
|
||||||
|
|
||||||
```bash
|
|
||||||
# Backend
|
|
||||||
cd backend
|
|
||||||
pytest
|
|
||||||
|
|
||||||
# Con cobertura
|
|
||||||
pytest --cov=. --cov-report=html
|
|
||||||
|
|
||||||
# Frontend
|
|
||||||
cd frontend
|
|
||||||
npm test
|
|
||||||
```
|
|
||||||
|
|
||||||
## 📈 Monitoreo
|
|
||||||
|
|
||||||
### Prometheus Metrics
|
|
||||||
|
|
||||||
El backend expone métricas en `/metrics`:
|
|
||||||
|
|
||||||
- Request duration
|
|
||||||
- Request count por endpoint
|
|
||||||
- Error rate
|
|
||||||
- Active connections
|
|
||||||
|
|
||||||
### Logging
|
|
||||||
|
|
||||||
Logs estructurados con nivel configurable:
|
|
||||||
|
|
||||||
- INFO: Operaciones normales
|
|
||||||
- WARNING: Validaciones fallidas
|
|
||||||
- ERROR: Errores de sistema
|
|
||||||
- DEBUG: Información detallada (solo desarrollo)
|
|
||||||
|
|
||||||
## 🤝 Contribución
|
|
||||||
|
|
||||||
1. Fork el proyecto
|
|
||||||
2. Crear rama feature (`git checkout -b feature/AmazingFeature`)
|
|
||||||
3. Commit cambios (`git commit -m 'Add some AmazingFeature'`)
|
|
||||||
4. Push a la rama (`git push origin feature/AmazingFeature`)
|
|
||||||
5. Abrir Pull Request
|
|
||||||
|
|
||||||
## 📝 Licencia
|
|
||||||
|
|
||||||
Este proyecto es privado y propietario.
|
|
||||||
|
|
||||||
## 📞 Soporte
|
|
||||||
|
|
||||||
Para soporte técnico o consultas:
|
|
||||||
|
|
||||||
- Email: soporte@anexo76.com
|
|
||||||
- Documentación: https://docs.anexo76.com
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
**Desarrollado con ❤️ para la industria de comercio exterior mexicana**
|
## 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
|
||||||
|
|
||||||
|
```bash
|
||||||
|
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_core`
|
||||||
|
- `name: app` en `docker-compose.yml` → `mi-proyecto`
|
||||||
|
|
||||||
|
### 2. Levantar en modo local (sin workspace)
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./scripts/auth-mode.sh local
|
||||||
|
docker compose up -d
|
||||||
|
```
|
||||||
|
|
||||||
|
Abre `http://localhost:5173` → click **"Entrar como dev"**.
|
||||||
|
|
||||||
|
### 3. Conectar al workspace
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./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:
|
||||||
|
|
||||||
|
```env
|
||||||
|
# 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`:
|
||||||
|
|
||||||
|
```python
|
||||||
|
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`:
|
||||||
|
|
||||||
|
```typescript
|
||||||
|
{ title: 'Mi módulo', url: '/dashboard/my-module', icon: MyIcon }
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Script auth-mode
|
||||||
|
|
||||||
|
```bash
|
||||||
|
./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
|
||||||
|
|||||||
Reference in New Issue
Block a user