feat: roadmap y diseño inicial — iteración 1 antes de feedback del usuario
Inventario de dispositivos electrónicos no prestables para administración pública. Esta primera iteración contiene el diseño y la planificación antes de contactar con el usuario para su validación. Contenido: - especificaciones.md: requisitos detallados del programa - docs/arquitectura.md: propuesta de arquitectura con alternativas - docs/roadmap-iteracion-1.md: plan de desarrollo iteración 1 - docs/arquitectura-diagrama.html: diagrama visual de arquitectura - sketches/: mockups de las 4 pantallas principales - 001-dashboard: vista resumen - 002-inventario: tabla de dispositivos - 003-formulario: alta/edición de dispositivos - 004-configuracion: ajustes generales Stack propuesto: Python + Flask + SQLite + Alpine.js Distribución: PyInstaller → un único .exe autocontenido Entorno: Windows 11 Pro unida a dominio, sin privilegios de admin
This commit is contained in:
commit
ee849eb9a8
14 changed files with 3761 additions and 0 deletions
411
docs/arquitectura.md
Normal file
411
docs/arquitectura.md
Normal file
|
|
@ -0,0 +1,411 @@
|
|||
# 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.*
|
||||
Loading…
Add table
Add a link
Reference in a new issue