inventschario/docs/arquitectura.md

412 lines
18 KiB
Markdown
Raw Normal View History

# Inventschario — Documento de Arquitectura
**Versión:** 1.0.0-iteracion1
**Fecha:** 2026-08-03
**Estado:** Propuesta para revisión
---
## 1. Visión General
Inventschario采用 arquitectura de tres capas con separación clara entre
presentación, lógica de negocio y persistencia. La capa de presentación se
ejecuta en el navegador del usuario; la capa de negocio y persistencia corren
como un servidor local HTTP que solo escucha en `127.0.0.1`.
La pasarela entre frontend y backend es una API REST documentada, de forma que
el frontend puede sustituirse completamente sin modificar ninguna línea del
backend, y viceversa.
---
## 2. Diagrama de Componentes
```
┌─────────────────────────────────────────────────────────────────┐
│ NAVEGADOR (Edge) │
│ │
│ ┌───────────────────────────────────────────────────────────┐ │
│ │ FRONTEND │ │
│ │ │ │
│ │ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ │
│ │ │Dashboard │ │Inventario│ │ Formular │ │ Config │ │ │
│ │ │ Page │ │ Page │ │ ios │ │ Page │ │ │
│ │ └────┬─────┘ └────┬─────┘ └────┬─────┘ └────┬─────┘ │ │
│ │ │ │ │ │ │ │
│ │ ┌────┴──────────────┴──────────────┴──────────────┴────┐ │
│ │ │ Router (SPA) │ │
│ │ └────────────────────┬─────────────────────────────────┘ │ │
│ │ │ │ │
│ │ ┌────────────────────┴─────────────────────────────────┐ │
│ │ │ API Client (fetch wrapper) │ │
│ │ └────────────────────┬─────────────────────────────────┘ │ │
│ │ │ │ │
│ │ ┌────────────────────┴─────────────────────────────────┐ │
│ │ │ Component Library │ │
│ │ │ Table | Form | Modal | Toast | Sidebar | Charts │ │
│ │ └──────────────────────────────────────────────────────┘ │ │
│ └───────────────────────────┬───────────────────────────────┘ │
│ │ HTTP localhost:PORT │
├──────────────────────────────┼──────────────────────────────────┤
│ │ │
│ ┌───────────────────────────┴───────────────────────────────┐ │
│ │ API GATEWAY │ │
│ │ │ │
│ │ ┌──────────┐ ┌──────────┐ ┌──────────┐ ┌──────────┐ │ │
│ │ │ /devices │ │ /import │ │ /backups │ │ /config │ │ │
│ │ │ /history │ │ /export │ │ │ │ /labels │ │ │
│ │ └────┬─────┘ └────┬─────┘ └────┬─────┘ └────┬─────┘ │ │
│ │ │ │ │ │ │ │
│ │ ┌────┴──────────────┴──────────────┴──────────────┴────┐ │ │
│ │ │ Middleware Layer │ │ │
│ │ │ CORS (localhost) | JSON parsing | Error handling │ │ │
│ │ └────────────────────┬─────────────────────────────────┘ │ │
│ └───────────────────────┼───────────────────────────────────┘ │
│ │ │
│ ┌───────────────────────┴───────────────────────────────────┐ │
│ │ SERVICE LAYER │ │
│ │ │ │
│ │ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ │
│ │ │DeviceService │ │BackupService │ │ImportService │ │ │
│ │ │ │ │ │ │ │ │ │
│ │ │- CRUD │ │- crear │ │- parse CSV │ │ │
│ │ │- buscar │ │- restaurar │ │- mapear │ │ │
│ │ │- filtrar │ │- eliminar │ │- validar │ │ │
│ │ │- baja │ │- verificar │ │- duplicados │ │ │
│ │ │- enajenar │ │ antigüedad │ │ │ │ │
│ │ │- historial │ │ │ │ │ │ │
│ │ └──────┬───────┘ └──────┬───────┘ └──────┬───────┘ │ │
│ │ │ │ │ │ │
│ │ ┌──────┴──────────────────┴──────────────────┴───────┐ │ │
│ │ │ ExportService | ConfigService │ │ │
│ │ └──────────────────────┬─────────────────────────────┘ │ │
│ └─────────────────────────┼─────────────────────────────────┘ │
│ │ │
│ ┌─────────────────────────┴─────────────────────────────────┐ │
│ │ DATA LAYER │ │
│ │ │ │
│ │ ┌──────────────┐ ┌──────────────┐ ┌──────────────┐ │ │
│ │ │ DeviceRepo │ │ ConfigRepo │ │ BackupRepo │ │ │
│ │ │ │ │ │ │ │ │ │
│ │ │ SQLite CRUD │ │ KV Store │ │ Log CRUD │ │ │
│ │ └──────┬───────┘ └──────┬───────┘ └──────┬───────┘ │ │
│ │ │ │ │ │ │
│ │ ┌──────┴──────────────────┴──────────────────┴───────┐ │ │
│ │ │ SQLite (WAL mode) │ │ │
│ │ │ inventschario.db │ │ │
│ │ └────────────────────────────────────────────────────┘ │ │
│ └───────────────────────────────────────────────────────────┘ │
└─────────────────────────────────────────────────────────────────┘
```
---
## 3. Alternativas de Arquitectura
### Alternativa A: Python + Flask (Recomendada)
```
[Browser] ──HTTP──► [Flask server] ──► [SQLite]
│ │
│ HTML/CSS/JS │ Python services
│ (Alpine.js) │ (DeviceService, etc.)
│ │
◄──── JSON ──────────►│
```
**Por qué se recomienda:**
- Python stdlib incluye sqlite3, http.server, json, gzip, csv, shutil
- Flask es ligero (~500KB) y no necesita build step
- PyInstaller empaqueta todo en un .exe autocontenido
- El ecosistema Python tiene librerías para todo lo necesario
- Desarrollo rápido, fácil de mantener
**Componentes:**
- `flask` — Servidor web minimalista
- `schedule` — Programador de tareas (backups diarios)
- `pyinstaller` — Empaquetado en .exe
### Alternativa B: .NET 8 Minimal API
```
[Browser] ──HTTP──► [Kestrel server] ──► [SQLite via EF Core]
│ │
│ HTML/CSS/JS │ C# services
│ (embebido) │ (DeviceService, etc.)
│ │
◄──── JSON ──────────►│
```
**Por qué es alternativa válida:**
- `dotnet publish -r win-x64 --self-contained` produce .exe sin runtime
- Rendimiento superior a Python en operaciones intensivas
- Integración nativa con Windows
- Entity Framework Core para ORM robusto
**Componentes:**
- ASP.NET Core Minimal API
- Microsoft.Data.Sqlite
- Hangfire (tareas programadas)
### Alternativa C: Go + net/http
```
[Browser] ──HTTP──► [Go HTTP server] ──► [SQLite via CGO]
│ │
│ HTML/CSS/JS │ Go handlers
│ (embed.FS) │ (DeviceService, etc.)
│ │
◄──── JSON ──────────►│
```
**Por qué es alternativa válida:**
- Binario más pequeño (10-15 MB)
- Rendimiento excepcional
- Sin dependencias en runtime
- `embed.FS` embebe archivos estáticos directamente en el binario
**Componentes:**
- `net/http` (stdlib)
- `mattn/go-sqlite3` (CGO)
- `embed.FS` (archivos estáticos)
### Decisión: Python + Flask
Para la primera iteración, Python + Flask es la opción que mejor equilibra:
- Velocidad de desarrollo
- Facilidad de distribución (PyInstaller)
- Modularidad del frontend (API REST pura)
- Ecosistema de herramientas
Si en iteraciones futuras el tamaño del .exe o la detección por antivirus
se convierten en problemas, se documentará la migración a .NET 8.
---
## 4. Patrones de Diseño
### 4.1 Repository Pattern (Capa de Datos)
Cada tabla tiene su repository que encapsula las consultas SQL:
```python
class DeviceRepository:
def __init__(self, db_path):
self.db_path = db_path
def get_all(self, filters=None): ...
def get_by_id(self, device_id): ...
def create(self, device_data): ...
def update(self, device_id, device_data): ...
def delete(self, device_id): ...
def search(self, query): ...
```
### 4.2 Service Layer (Lógica de Negocio)
Los services orquestan la lógica sin conocer la API HTTP:
```python
class DeviceService:
def __init__(self, device_repo, history_repo):
self.device_repo = device_repo
self.history_repo = history_repo
def create_device(self, data, user):
device = self.device_repo.create(data)
self.history_repo.log(device.id, 'created', user=user)
return device
def decommission(self, device_id, reason, user):
device = self.device_repo.get_by_id(device_id)
self.device_repo.update_status(device_id, 'decommissioned')
self.history_repo.log(device_id, 'decommissioned',
notes=reason, user=user)
```
### 4.3 API Gateway (Capa HTTP)
Las rutas API son delgadas — solo parsean la request, llaman al service,
y formatean la response:
```python
@bp.route('/api/v1/devices', methods=['POST'])
def create_device():
data = request.get_json()
device = device_service.create_device(data, current_user)
return jsonify(device.to_dict()), 201
```
### 4.4 Frontend Component Architecture
El frontend usa un patrón de componentes simples sin framework pesado:
```javascript
// Cada componente es un objeto con render() y bind()
const DeviceTable = {
render(devices) { return `<table>...</table>`; },
bind(container) { /* event listeners */ }
};
// El router cambia de página
const Router = {
routes: { '/': Dashboard, '/inventory': Inventory },
navigate(path) { /* render the page */ }
};
```
### 4.5 Import/Export Plugin Architecture
El sistema de importación exportación es un plugin architecture:
```python
class ImporterBase:
"""Clase abstracta para todos los importadores"""
def parse(self, file_path) -> list[dict]: ...
def validate(self, records) -> ValidationResult: ...
def import_records(self, records, strategy) -> ImportResult: ...
class CSVImporter(ImporterBase):
"""Implementación CSV"""
def parse(self, file_path):
# Lee CSV, mapea columnas, retorna lista de dicts
...
class FieldMapper:
"""Motor de mapeo de campos"""
def __init__(self, mapping_config):
self.mapping = mapping_config
def map_record(self, raw_record) -> dict:
# Aplica el mapeo configurado
...
```
Para añadir un nuevo formato (ej: Excel), se crea una nueva clase que
herede de `ImporterBase` sin tocar el resto del sistema.
---
## 5. Seguridad
### 5.1 Red
- El servidor Flask escucha SOLO en `127.0.0.1:PORT`
- No hay exposición a la red local o internet
- El puerto se asigna dinámicamente o se configura en el wizard
### 5.2 Datos
- SQLite WAL mode para integridad
- Foreign keys habilitadas
- Validación de datos en backend (no confiar en el frontend)
- Backups almacenados en `%APPDATA%` (acceso restringido al usuario)
### 5.3 Archivos
- No se ejecutan comandos del sistema
- No se escriben archivos fuera de `%APPDATA%/inventschario/`
- La BD se almacena en `%APPDATA%/inventschario/inventschario.db`
- Los logs se almacenan en `%APPDATA%/inventschario/logs/`
---
## 6. Rendimiento
### 6.1 Consideraciones
- Todo es local: latencia de red ~0ms
- SQLite es suficiente para inventarios de hasta 100,000 dispositivos
- WAL mode permite lecturas concurrentes
- Índices en campos de búsqueda frecuentes
### 6.2 Métricas Objetivo
| Operación | Tiempo objetivo |
|--------------------------------|-----------------|
| Carga de página inicial | < 2s |
| Búsqueda en inventario | < 500ms |
| Crear dispositivo | < 100ms |
| Exportar 1,000 dispositivos | < 3s |
| Importar 1,000 dispositivos | < 5s |
| Backup de BD de 10MB | < 2s |
---
## 7. Estrategia de Testing
### 7.1 Niveles
1. **Unit Tests** — Services, repositories, utilidades
2. **Integration Tests** — API endpoints con BD en memoria
3. **E2E Tests** — Flujo completo (futuro, iteración 2)
### 7.2 Herramientas
- `pytest` — Framework de testing
- `pytest-flask` — Fixtures para Flask
- `sqlite3` en memoria — BD de test aislada
### 7.3 Cobertura
Objetivo iteración 1: > 80% cobertura en services y repositories.
---
## 8. Despliegue
### 8.1 Distribución
```
inventschario/
├── inventschario.exe # Ejecutable empaquetado (PyInstaller)
├── inventschario.ico # Icono de la aplicación
└── README.md # Instrucciones de uso
```
### 8.2 Instalación (Usuario)
1. El usuario ejecuta `inventschario.exe`
2. PyInstaller extrae a un directorio temporal
3. La aplicación se instala en `%LOCALAPPDATA%/inventschario/`
4. Se crea un acceso directo en el Menú Inicio del usuario
5. Se lanza el navegador con la aplicación
### 8.3 Start Menu
El acceso directo se crea en:
```
%APPDATA%/Microsoft/Windows/Start Menu/Programs/Inventschario/Inventschario.lnk
```
Esto requiere privilegios de instalador (que el usuario tiene).
---
## 9. Plan de Migración (Si es necesario)
Si Python + Flask no cumple los requisitos en producción:
### De Python a .NET 8
1. Mantener la misma API REST (los endpoints no cambian)
2. Reescribir los services en C#
3. Usar Entity Framework Core con SQLite
4. Publicar como self-contained: `dotnet publish -r win-x64 --self-contained`
5. El frontend NO cambia (solo se sirve desde wwwroot)
### De Python a Go
1. Mantener la misma API REST
2. Reescribir los services en Go
3. Usar `mattn/go-sqlite3`
4. Compilar como binario estático
5. Embeber frontend con `embed.FS`
En ambos casos, la API REST como contrato permite el cambio de backend
sin modificar el frontend.
---
*Documento generado como parte de la primera iteración del proyecto Inventschario.*