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
18 KiB
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 minimalistaschedule— 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-containedproduce .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.FSembebe 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:
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:
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:
@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:
// 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:
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
- Unit Tests — Services, repositories, utilidades
- Integration Tests — API endpoints con BD en memoria
- E2E Tests — Flujo completo (futuro, iteración 2)
7.2 Herramientas
pytest— Framework de testingpytest-flask— Fixtures para Flasksqlite3en 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)
- El usuario ejecuta
inventschario.exe - PyInstaller extrae a un directorio temporal
- La aplicación se instala en
%LOCALAPPDATA%/inventschario/ - Se crea un acceso directo en el Menú Inicio del usuario
- 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
- Mantener la misma API REST (los endpoints no cambian)
- Reescribir los services en C#
- Usar Entity Framework Core con SQLite
- Publicar como self-contained:
dotnet publish -r win-x64 --self-contained - El frontend NO cambia (solo se sirve desde wwwroot)
De Python a Go
- Mantener la misma API REST
- Reescribir los services en Go
- Usar
mattn/go-sqlite3 - Compilar como binario estático
- 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.