- Complete project narrative from CLI to web application - Covers all milestones: i18n, responsive design, corporate identity - Includes providers, technologies, and lessons learned - Written in Spanish for Ivan - Omits sensitive information (API keys, credentials)
19 KiB
Historia del Proyecto GestionTablets
Un relato técnico para Iván sobre el desarrollo del Sistema de Gestión de Tablets
📖 Índice
- Los Inicios
- La Arquitectura Base
- Proveedores y Tecnologías
- Hitos del Desarrollo
- Internacionalización (i18n)
- Identidad Corporativa
- Lecciones Aprendidas
- 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:
# 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:
-- 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.pycon 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.pycon 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/<id>- 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
- Flask-Babel 4.0.0: Integración con Flask
- Estructura de traducción:
translations/ └── es/ ├── LC_MESSAGES.mo # Archivo binario └── LC_MESSAGES.po # Archivo fuente - Configuración:
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
# 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 suitedea2f23- 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: hiddena body y contenedores - Envolver tablas en contenedores con scroll horizontal
- Añadir breakpoints para móvil, tablet y desktop
- Crear
docs/RESPONSIVE_CSS.mdcon documentación
Commits:
28232db- fix(ui): add missing</style>tag in base.html927c323- feat(ui): add responsive CSS for mobile accessibility26b77cf- 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:
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
-
Selección de idioma:
- Por defecto: Español
- URL:
?lang=eno?lang=es - Cookie:
language(1 año de expiración) - Sesión:
session['language']
-
Uso en plantillas:
<h1>{{ _('Tablet Management System') }}</h1> <p>{{ _('Available Tablets') }}: {{ count }}</p> -
Traducciones:
- Los archivos
.pocontienen las traducciones - Se compilan a
.mopara uso en producción - Comando para actualizar:
pybabel extract -o translations/messages.pot .
- Los archivos
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 persistence8c1d12a- feat(ui): add corporate logo and update colors to Cobalt Blue, Tiger Orange, Emerald Greenc233040- Add corporative logo49e4562- 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)
- Migración a PostgreSQL: Documentación lista, solo falta implementar
- Más idiomas: Añadir francés, alemán, etc.
- Autenticación: Implementar login para usuarios
- Notificaciones: Recordatorios de devoluciones
- Exportación: Exportar datos a CSV/Excel
📞 Cómo Ejecutar el Proyecto
Requisitos
- Python 3.14+
- pip o uv (gestor de paquetes)
Instalación
# 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
# Desarrollo
python3 app.py
# Acceder a:
# http://localhost:5000 - Español (por defecto)
# http://localhost:5000?lang=en - Inglés
Pruebas
# 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