inventschario/docs/arquitectura.md
pikaos ee849eb9a8 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
2026-08-03 01:34:56 +01:00

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 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:

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

  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.