# 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 `...
`; }, 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.*