# Historia del Proyecto GestionTablets *Un relato técnico para Iván sobre el desarrollo del Sistema de Gestión de Tablets* --- ## 📖 Índice 1. [Los Inicios](#los-inicios) 2. [La Arquitectura Base](#la-arquitectura-base) 3. [Proveedores y Tecnologías](#proveedores-y-tecnologías) 4. [Hitos del Desarrollo](#hitos-del-desarrollo) 5. [Internacionalización (i18n)](#internacionalización-i18n) 6. [Identidad Corporativa](#identidad-corporativa) 7. [Lecciones Aprendidas](#lecciones-aprendidas) 8. [Estado Actual](#estado-actual) --- ## 🚀 Los Inicios El proyecto **GestionTablets** nació como una herramienta interna para gestionar el préstamo de tablets y otros dispositivos en un entorno de equipo pequeño (5 usuarios técnicos). La idea inicial era simple: llevar un control claro de qué dispositivo estaba con qué persona, cuándo se prestó y cuándo se devolvió. ### Contexto Inicial - **Tipo de proyecto**: Herramienta interna (no aplicación pública) - **Usuarios objetivo**: 5 usuarios técnicos - **Requisitos principales**: - Registrar tablets y otros dispositivos - Gestionar préstamos a usuarios - Historial de transacciones - Interfaz sencilla y funcional ### Primera Versión: CLI El proyecto comenzó con una versión **CLI (Command Line Interface)** en Python puro con SQLite. Esta versión, aunque funcional, tenía limitaciones: ```python # Ejemplo de la versión CLI inicial import sqlite3 from datetime import datetime def add_tablet(brand, model, serial_number): """Añadir una nueva tablet al inventario""" conn = sqlite3.connect('tablets.db') cursor = conn.cursor() cursor.execute(''' INSERT INTO tablets (brand, model, serial_number, status) VALUES (?, ?, ?, 'available') ''', (brand, model, serial_number)) conn.commit() print(f"✓ Tablet añadida: {brand} {model} ({serial_number})") ``` **Limitaciones de la CLI:** - Interfaz no intuitiva para usuarios no técnicos - Sin visualización de datos en formato amigable - Dificultad para ver el estado actual de préstamos --- ## 🏗️ La Arquitectura Base ### Transición a Web: Flask Se decidió migrar a una **aplicación web** usando **Flask**, un micro-framework de Python, para proporcionar una interfaz más amigable. **Estructura del proyecto:** ``` GestionTablets/ ├── app.py # Aplicación principal Flask ├── minimal_app.py # Versión CLI (deprecada) ├── templates/ # Plantillas HTML (Jinja2) │ ├── base.html # Plantilla base │ ├── index.html # Página principal │ ├── add_tablet.html # Formulario añadir tablet │ ├── add_user.html # Formulario añadir usuario │ ├── loan_tablet.html # Formulario préstamo │ └── ... ├── static/ # Archivos estáticos │ └── Schamann.png # Logo corporativo ├── translations/ # Archivos de traducción │ ├── es/LC_MESSAGES.mo │ └── es/LC_MESSAGES.po ├── docs/ # Documentación │ ├── CORPORATE_DESIGN.md # Diseño corporativo │ ├── FRONTEND_OPTIONS.md # Opciones de frontend │ ├── MIGRATION_TO_POSTGRES.md │ └── RESPONSIVE_CSS.md ├── tests/ # Suite de pruebas │ ├── conftest.py │ ├── test_core.py │ ├── test_edge_cases.py │ └── test_minimal_app.py ├── tablets.db # Base de datos SQLite ├── pyproject.toml # Configuración del proyecto ├── requirements.txt # Dependencias └── README.md # Documentación principal ``` ### Base de Datos: SQLite Se optó por **SQLite** como base de datos por su simplicidad y porque es perfecta para: - Aplicaciones internas con pocos usuarios - No requiere servidor de base de datos - Fácil de mantener y hacer backup - Incluida en Python (sin dependencias adicionales) **Esquema de la base de datos:** ```sql -- Tablets CREATE TABLE tablets ( id INTEGER PRIMARY KEY AUTOINCREMENT, brand TEXT NOT NULL, model TEXT NOT NULL, serial_number TEXT UNIQUE NOT NULL, status TEXT DEFAULT 'available', notes TEXT ); -- Usuarios CREATE TABLE users ( id INTEGER PRIMARY KEY AUTOINCREMENT, name TEXT NOT NULL, email TEXT, phone TEXT, identification TEXT UNIQUE ); -- Préstamos CREATE TABLE loans ( id INTEGER PRIMARY KEY AUTOINCREMENT, tablet_id INTEGER NOT NULL, user_id INTEGER NOT NULL, loan_date TEXT NOT NULL, return_date TEXT, status TEXT DEFAULT 'active', FOREIGN KEY (tablet_id) REFERENCES tablets(id), FOREIGN KEY (user_id) REFERENCES users(id) ); -- Dispositivos no prestables CREATE TABLE non_loanable_devices ( id INTEGER PRIMARY KEY AUTOINCREMENT, brand TEXT NOT NULL, model TEXT NOT NULL, serial_number TEXT UNIQUE NOT NULL, device_type TEXT NOT NULL, location TEXT, status TEXT DEFAULT 'available', notes TEXT, purchase_date TEXT, purchase_cost REAL ); ``` --- ## 🛠️ Proveedores y Tecnologías ### Lenguaje y Framework - **Python 3.14+**: Lenguaje principal del proyecto - **Flask 3.x**: Framework web principal - **Flask-Babel 4.0.0**: Para internacionalización (i18n) - **Jinja2**: Motor de plantillas ### Frontend - **Tailwind CSS**: Framework CSS para estilos (vía CDN) - **HTMX**: Para interactividad sin JavaScript (en algunas partes) - **Alpine.js**: Considerado pero no implementado ### Base de Datos - **SQLite**: Base de datos principal (embebida) - **PostgreSQL**: Opción futura documentada (ver `docs/MIGRATION_TO_POSTGRES.md`) ### Herramientas de Desarrollo - **pytest**: Framework de pruebas - **pytest-cov**: Cobertura de código - **Git**: Control de versiones - **GitHub/GitLab**: Hosting del repositorio (servidor propio en `hq.ijuanes.ovh:3333`) ### Infraestructura - **Servidor Git**: `http://hq.ijuanes.ovh:3333` - **Usuario**: `ijuanes` - **Repositorio**: `GestionTablets` --- ## 🎯 Hitos del Desarrollo ### Fase 1: Versión CLI Inicial **Fecha**: Junio 2026 (inicio) - Creación de `minimal_app.py` con funciones básicas - Implementación de CRUD para tablets y usuarios - Gestión básica de préstamos - Base de datos SQLite **Commit representativo**: Versión inicial del proyecto --- ### Fase 2: Migración a Web **Fecha**: Mediados de Junio 2026 - Creación de `app.py` con Flask - Diseño de plantillas HTML con Jinja2 - Implementación de rutas web: - `/` - Página principal con tablets disponibles - `/add_tablet` - Añadir nueva tablet - `/add_user` - Añadir nuevo usuario - `/loan_tablet` - Prestar tablet - `/return_tablet/` - Devolver tablet - `/history` - Historial de préstamos **Mejoras:** - Interfaz visual más intuitiva - Navegación entre páginas - Visualización de datos en tablas --- ### Fase 3: Internacionalización (i18n) **Fecha**: Junio 2026 #### Problema Inicial La aplicación solo estaba en inglés y no tenía soporte para múltiples idiomas. #### Solución Implementada 1. **Flask-Babel 4.0.0**: Integración con Flask 2. **Estructura de traducción**: ``` translations/ └── es/ ├── LC_MESSAGES.mo # Archivo binario └── LC_MESSAGES.po # Archivo fuente ``` 3. **Configuración**: ```python app.config['BABEL_DEFAULT_LOCALE'] = 'es' app.config['LANGUAGES'] = {'en': 'English', 'es': 'Español'} app.config['BABEL_TRANSLATION_DIRECTORIES'] = 'translations' ``` #### Problemas y Soluciones **Problema 1**: Flask-Babel 4.0.0 cambió su API - ❌ `@babel.localeselector` - Eliminado - ❌ `@babel.locale_selector` - No existe - ✅ `Babel(app, locale_selector=func)` - Enfoque correcto **Solución**: Reescribir la configuración para usar el nuevo API. **Commit**: `49e4562` - fix(i18n): define get_locale function before passing to Babel constructor **Problema 2**: Las plantillas no usaban el sistema de traducción - Muchas plantillas tenían texto en inglés hardcodeado - Ejemplo: `add_tablet.html`, `add_user.html`, `loan_tablet.html` **Solución**: Actualizar todas las plantillas para usar `{{ _('texto') }}` **Commit**: `2248237` - fix(i18n): enable translations in all templates **Problema 3**: No se guardaba la preferencia de idioma - El idioma se perdía al navegar entre páginas - No había cookies ni sesión para persistir la selección **Solución**: Implementar persistencia con cookies + sesión ```python # En app.py from flask import session, make_response def get_locale(): # 1. Intentar obtener de cookie lang = request.cookies.get('language') if lang and lang in app.config['LANGUAGES']: return lang # 2. Intentar obtener de sesión if hasattr(request, 'session') and 'language' in request.session: return request.session['language'] # 3. Default a Español return 'es' @app.route('/set_language') def set_language(): lang = request.args.get('lang') if lang and lang in app.config['LANGUAGES']: session['language'] = lang resp = make_response(redirect(request.referrer or '/')) resp.set_cookie('language', lang, max_age=31536000) # 1 año return resp return redirect(request.referrer or '/') ``` **Commit**: `2248237` - Cookie-based language persistence --- ### Fase 4: Pruebas Unitarias **Fecha**: Junio 2026 Se implementó una **suite completa de pruebas** con pytest: **Estructura:** ``` tests/ ├── __init__.py ├── conftest.py # Fixtures de pytest ├── test_core.py # 21 pruebas de funcionalidad principal ├── test_edge_cases.py # 14 pruebas de casos límite └── test_minimal_app.py # 11 pruebas de funciones de aplicación ``` **Cobertura:** - Pruebas de funciones CRUD - Casos límite (prestar tablet ya prestada, devolver préstamo inexistente) - Validación de datos (números de serie duplicados) - Manejo de errores **Resultado**: 46 pruebas, todas pasando ✅ **Commits:** - `83ac67f` - test: add comprehensive unit test suite - `dea2f23` - docs(ui): update RESPONSIVE_CSS.md with bug fix documentation --- ### Fase 5: Diseño Responsive **Fecha**: Junio 2026 **Problema**: La navegación desbordaba en pantallas pequeñas o al hacer zoom. **Solución**: Implementar CSS responsive con Tailwind CSS **Cambios:** - Añadir `overflow-x: hidden` a body y contenedores - Envolver tablas en contenedores con scroll horizontal - Añadir breakpoints para móvil, tablet y desktop - Crear `docs/RESPONSIVE_CSS.md` con documentación **Commits:** - `28232db` - fix(ui): add missing `` tag in base.html - `927c323` - feat(ui): add responsive CSS for mobile accessibility - `26b77cf` - fix(ui): prevent navigation overflow on zoom and small screens --- ### Fase 6: Documentación de Migración a PostgreSQL **Fecha**: Junio 2026 Aunque el proyecto usa SQLite, se documentó el camino para migrar a PostgreSQL: **Archivo**: `docs/MIGRATION_TO_POSTGRES.md` **Contenido:** - Comparación SQLite vs PostgreSQL - Pasos de migración - Cambios en el código - Configuración de conexión - Scripts SQL para crear tablas **Commit**: `dea2f23` - docs: add PostgreSQL migration guide --- ### Fase 7: Identidad Corporativa **Fecha**: 20 Junio 2026 #### Logo Corporativo - **Archivo**: `assets/Schamann.png` (175x161 píxeles) - **Ubicación web**: `/static/Schamann.png` - **Integración**: Añadido al header de la aplicación - **Estilo**: Forzado a 40px de altura con `object-contain` **Commit**: `c233040` - Add corporative logo #### Paleta de Colores Corporativos Se reemplazó la paleta de colores genérica por los colores corporativos: | Color | Código Hex | RGB | Uso | |-------|------------|-----|-----| | **Azul Cobalto** | `#1338BE` | rgb(19, 56, 190) | Headers, navegación | | **Naranja Tigre** | `#FC6A03` | rgb(252, 106, 3) | Acentos, indicadores de carga | | **Verde Esmeralda** | `#028A0F` | rgb(2, 138, 15) | Estados de éxito | **Configuración en Tailwind:** ```javascript tailwind.config = { theme: { extend: { colors: { cobalt: { 50: '#f0f5ff', 500: '#1338BE', // ... todas las sombras }, tiger: { 50: '#fff8f0', 500: '#FC6A03', // ... }, emerald: { 50: '#f0fdf4', 500: '#028A0F', // ... } } } } } ``` **Documentación**: `docs/CORPORATE_DESIGN.md` **Commit**: `8c1d12a` - feat(ui): add corporate logo and update colors --- ## 🌍 Internacionalización (i18n) ### Estado Actual ✅ **Completo**: - Flask-Babel 4.0.0 configurado correctamente - Archivos de traducción creados (es/LC_MESSAGES.po, es/LC_MESSAGES.mo) - Todas las plantillas usan `{{ _('texto') }}` - Persistencia de idioma con cookies (1 año) + sesión - Español como idioma por defecto - Cambiador de idioma funcional en el header ### Cómo Funciona 1. **Selección de idioma**: - Por defecto: Español - URL: `?lang=en` o `?lang=es` - Cookie: `language` (1 año de expiración) - Sesión: `session['language']` 2. **Uso en plantillas**: ```html

{{ _('Tablet Management System') }}

{{ _('Available Tablets') }}: {{ count }}

``` 3. **Traducciones**: - Los archivos `.po` contienen las traducciones - Se compilan a `.mo` para uso en producción - Comando para actualizar: `pybabel extract -o translations/messages.pot .` ### Idiomas Soportados - **Español (es)**: Idioma por defecto - **Inglés (en)**: Alternativa --- ## 🎨 Identidad Corporativa ### Logo - **Archivo**: `Schamann.png` - **Dimensiones**: 175x161 píxeles - **Formato**: PNG con transparencia - **Ubicación**: `/static/Schamann.png` - **Visualización**: 40px de altura, manteniendo proporciones ### Colores #### Azul Cobalto (#1338BE) - **Uso principal**: Headers, navegación, acciones principales - **Significado**: Profesionalismo, confianza - **Contraste**: Texto blanco sobre fondos oscuros #### Naranja Tigre (#FC6A03) - **Uso principal**: Acentos, indicadores de carga, acciones secundarias - **Significado**: Energía, acción - **Contraste**: Texto blanco sobre fondos #### Verde Esmeralda (#028A0F) - **Uso principal**: Estados de éxito, confirmaciones - **Significado**: Éxito, positividad - **Contraste**: Texto blanco sobre fondos ### Documentación Todo el diseño corporativo está documentado en `docs/CORPORATE_DESIGN.md`: - Paleta de colores completa con sombras - Guías de uso - Ejemplos de implementación - Accesibilidad --- ## 📚 Lecciones Aprendidas ### 1. Versiones de Librerías **Lección**: Siempre verificar la compatibilidad de versiones. **Ejemplo**: Flask-Babel 4.0.0 cambió su API completamente. Lo que funcionaba en la versión 3.x no funcionaba en 4.x. **Solución**: Leer la documentación de la versión específica que se está usando. --- ### 2. Internacionalización desde el Inicio **Lección**: Implementar i18n desde el principio, no como un afterthought. **Problema**: Muchas plantillas tenían texto hardcodeado en inglés. **Solución**: Revisar todas las plantillas y envolver todo el texto visible con `{{ _('...') }}`. --- ### 3. Persistencia de Preferencias **Lección**: Para aplicaciones internas, las cookies son suficientes. **Decisión**: No implementar consentimiento de cookies (es una app interna). **Implementación**: Cookie de 1 año + sesión para persistencia de idioma. --- ### 4. Diseño Responsive **Lección**: Probar en diferentes tamaños de pantalla y niveles de zoom. **Problema**: La navegación desbordaba al hacer zoom o en pantallas pequeñas. **Solución**: Usar `overflow-x: hidden` y contenedores con scroll. --- ### 5. Pruebas Unitarias **Lección**: Las pruebas dan confianza para hacer refactorización. **Beneficio**: Al tener 46 pruebas pasando, pudimos hacer cambios en la configuración de i18n sin miedo a romper algo. --- ## 🎯 Estado Actual ### Versión - **Branch principal**: `develop` - **Commits recientes**: - `2248237` - fix(i18n): enable translations in all templates and implement cookie-based language persistence - `8c1d12a` - feat(ui): add corporate logo and update colors to Cobalt Blue, Tiger Orange, Emerald Green - `c233040` - Add corporative logo - `49e4562` - fix(i18n): define get_locale function before passing to Babel constructor ### Funcionalidades | Funcionalidad | Estado | Notas | |---------------|--------|-------| | Gestión de tablets | ✅ | CRUD completo | | Gestión de usuarios | ✅ | CRUD completo | | Préstamos | ✅ | Prestar y devolver | | Historial | ✅ | Visualización completa | | Internacionalización | ✅ | Español/Inglés | | Persistencia de idioma | ✅ | Cookies + sesión | | Diseño responsive | ✅ | Móvil, tablet, desktop | | Logo corporativo | ✅ | Integración completa | | Colores corporativos | ✅ | Paleta aplicada | | Pruebas unitarias | ✅ | 46 pruebas pasando | ### Próximos Pasos (Opcionales) 1. **Migración a PostgreSQL**: Documentación lista, solo falta implementar 2. **Más idiomas**: Añadir francés, alemán, etc. 3. **Autenticación**: Implementar login para usuarios 4. **Notificaciones**: Recordatorios de devoluciones 5. **Exportación**: Exportar datos a CSV/Excel --- ## 📞 Cómo Ejecutar el Proyecto ### Requisitos - Python 3.14+ - pip o uv (gestor de paquetes) ### Instalación ```bash # Clonar el repositorio git clone http://hq.ijuanes.ovh:3333/ijuanes/GestionTablets.git cd GestionTablets # Crear entorno virtual python3 -m venv .venv source .venv/bin/activate # Linux/Mac # .venv\Scripts\activate # Windows # Instalar dependencias pip install -r requirements.txt # Inicializar base de datos python3 app.py # La base de datos se crea automáticamente ``` ### Ejecutar ```bash # Desarrollo python3 app.py # Acceder a: # http://localhost:5000 - Español (por defecto) # http://localhost:5000?lang=en - Inglés ``` ### Pruebas ```bash # Ejecutar todas las pruebas python3 -m pytest tests/ -v # Con cobertura python3 -m pytest tests/ --cov=. --cov-report=html ``` --- ## 🏁 Conclusión El proyecto **GestionTablets** ha evolucionado de una simple CLI a una aplicación web completa con: - ✅ Internacionalización funcional - ✅ Identidad corporativa integrada - ✅ Diseño responsive - ✅ Suite de pruebas completa - ✅ Documentación exhaustiva Es un ejemplo de cómo una herramienta interna puede ser profesional, mantenible y escalable, incluso con recursos limitados. **Para Iván**: Este proyecto demuestra que con Python, Flask y SQLite se pueden crear aplicaciones robustas para equipos pequeños, y que la internacionalización y el diseño corporativo son importantes incluso en herramientas internas. --- *Documento generado el 20 de Junio de 2026* *Para: Iván* *Proyecto: GestionTablets*