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
351 lines
17 KiB
HTML
351 lines
17 KiB
HTML
<!DOCTYPE html>
|
|
<html lang="es">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>Inventschario — Agregar Dispositivo</title>
|
|
<link href="https://fonts.googleapis.com/css2?family=Inter:wght@400;500;600;700&display=swap" rel="stylesheet">
|
|
<style>
|
|
* { box-sizing: border-box; margin: 0; padding: 0; }
|
|
body {
|
|
font-family: 'Inter', -apple-system, BlinkMacSystemFont, sans-serif;
|
|
color: #1a1a1a; background: #f7f7f5; line-height: 1.5;
|
|
display: flex; min-height: 100vh;
|
|
}
|
|
.sidebar {
|
|
width: 240px; background: #fff; border-right: 1px solid #e5e5e3;
|
|
padding: 20px 0; flex-shrink: 0; display: flex; flex-direction: column;
|
|
}
|
|
.sidebar-logo { padding: 0 20px 20px; border-bottom: 1px solid #e5e5e3; margin-bottom: 8px; }
|
|
.sidebar-logo h1 { font-size: 16px; font-weight: 700; }
|
|
.sidebar-logo span { font-size: 11px; color: #999; display: block; margin-top: 2px; }
|
|
.nav-section { padding: 8px 12px; }
|
|
.nav-label { font-size: 11px; font-weight: 600; color: #999; text-transform: uppercase; letter-spacing: 0.5px; padding: 8px 8px 4px; }
|
|
.nav-item {
|
|
display: flex; align-items: center; gap: 10px; padding: 8px 12px;
|
|
border-radius: 6px; font-size: 14px; color: #666; cursor: pointer;
|
|
text-decoration: none; transition: background 0.15s, color 0.15s;
|
|
}
|
|
.nav-item:hover { background: #f0f0ee; color: #1a1a1a; }
|
|
.nav-item.active { background: #f0f0ee; color: #1a1a1a; font-weight: 500; }
|
|
.nav-item svg { width: 18px; height: 18px; flex-shrink: 0; }
|
|
.nav-spacer { flex: 1; }
|
|
.nav-footer { padding: 12px 20px; border-top: 1px solid #e5e5e3; font-size: 12px; color: #999; }
|
|
|
|
.main { flex: 1; display: flex; flex-direction: column; overflow: hidden; }
|
|
.topbar {
|
|
background: #fff; border-bottom: 1px solid #e5e5e3;
|
|
padding: 16px 32px; display: flex; align-items: center; justify-content: space-between;
|
|
}
|
|
.topbar h2 { font-size: 18px; font-weight: 600; }
|
|
.topbar-actions { display: flex; gap: 10px; }
|
|
.btn {
|
|
padding: 8px 16px; border-radius: 6px; font-size: 13px; font-weight: 500;
|
|
cursor: pointer; border: 1px solid #e5e5e3; background: #fff; color: #1a1a1a;
|
|
transition: background 0.15s; font-family: inherit;
|
|
}
|
|
.btn:hover { background: #f0f0ee; }
|
|
.btn-primary { background: #1a1a1a; color: #fff; border-color: #1a1a1a; }
|
|
.btn-primary:hover { background: #333; }
|
|
.btn-ghost { border: none; background: transparent; color: #666; }
|
|
.btn-ghost:hover { color: #1a1a1a; background: #f0f0ee; }
|
|
|
|
.content { padding: 32px; overflow-y: auto; flex: 1; max-width: 800px; }
|
|
|
|
/* Form */
|
|
.form-card {
|
|
background: #fff; border: 1px solid #e5e5e3; border-radius: 8px;
|
|
overflow: hidden;
|
|
}
|
|
.form-header {
|
|
padding: 20px 24px; border-bottom: 1px solid #e5e5e3;
|
|
}
|
|
.form-header h3 { font-size: 15px; font-weight: 600; }
|
|
.form-header p { font-size: 13px; color: #666; margin-top: 4px; }
|
|
.form-body { padding: 24px; }
|
|
|
|
.form-section {
|
|
margin-bottom: 28px;
|
|
}
|
|
.form-section:last-child { margin-bottom: 0; }
|
|
.form-section-title {
|
|
font-size: 12px; font-weight: 600; color: #999; text-transform: uppercase;
|
|
letter-spacing: 0.5px; margin-bottom: 16px; padding-bottom: 8px;
|
|
border-bottom: 1px solid #f0f0ee;
|
|
}
|
|
|
|
.form-grid {
|
|
display: grid; grid-template-columns: 1fr 1fr; gap: 16px;
|
|
}
|
|
.form-grid .full-width { grid-column: 1 / -1; }
|
|
|
|
.form-group { display: flex; flex-direction: column; gap: 4px; }
|
|
.form-label {
|
|
font-size: 13px; font-weight: 500; color: #1a1a1a;
|
|
}
|
|
.form-label .required { color: #dc2626; margin-left: 2px; }
|
|
.form-hint { font-size: 11px; color: #999; }
|
|
.form-input, .form-select, .form-textarea {
|
|
padding: 8px 12px; border: 1px solid #e5e5e3; border-radius: 6px;
|
|
font-size: 13px; font-family: inherit; background: #fff; color: #1a1a1a;
|
|
transition: border-color 0.15s;
|
|
}
|
|
.form-input:focus, .form-select:focus, .form-textarea:focus {
|
|
outline: none; border-color: #1a1a1a; box-shadow: 0 0 0 1px #1a1a1a;
|
|
}
|
|
.form-input.error { border-color: #dc2626; }
|
|
.form-error { font-size: 11px; color: #dc2626; margin-top: 2px; }
|
|
.form-textarea { resize: vertical; min-height: 80px; }
|
|
.form-select { cursor: pointer; }
|
|
|
|
/* Required fields note */
|
|
.required-note {
|
|
font-size: 12px; color: #999; margin-bottom: 20px;
|
|
}
|
|
.required-note .required { color: #dc2626; }
|
|
|
|
/* Form footer */
|
|
.form-footer {
|
|
padding: 16px 24px; border-top: 1px solid #e5e5e3;
|
|
display: flex; justify-content: space-between; align-items: center;
|
|
background: #fafafa;
|
|
}
|
|
.form-footer-info { font-size: 12px; color: #999; }
|
|
.form-footer-actions { display: flex; gap: 8px; }
|
|
|
|
/* Success toast */
|
|
.toast {
|
|
position: fixed; bottom: 24px; right: 24px; background: #166534;
|
|
color: #fff; padding: 12px 20px; border-radius: 8px; font-size: 13px;
|
|
box-shadow: 0 4px 12px rgba(0,0,0,0.15); display: flex; align-items: center;
|
|
gap: 8px; z-index: 100;
|
|
}
|
|
.toast-icon { font-size: 16px; }
|
|
|
|
*:focus-visible { outline: 2px solid #1a1a1a; outline-offset: 2px; }
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<!-- Sidebar -->
|
|
<nav class="sidebar" role="navigation" aria-label="Menú principal">
|
|
<div class="sidebar-logo"><h1>Inventschario</h1><span>Inventario Electrónico</span></div>
|
|
<div class="nav-section">
|
|
<div class="nav-label">Principal</div>
|
|
<a class="nav-item" href="#dashboard">
|
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="3" y="3" width="7" height="7" rx="1"/><rect x="14" y="3" width="7" height="7" rx="1"/><rect x="3" y="14" width="7" height="7" rx="1"/><rect x="14" y="14" width="7" height="7" rx="1"/></svg>
|
|
Dashboard
|
|
</a>
|
|
<a class="nav-item" href="#inventory">
|
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M9 5H7a2 2 0 00-2 2v12a2 2 0 002 2h10a2 2 0 002-2V7a2 2 0 00-2-2h-2"/><rect x="9" y="3" width="6" height="4" rx="1"/></svg>
|
|
Inventario
|
|
</a>
|
|
<a class="nav-item active" href="#add" aria-current="page">
|
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"/><line x1="12" y1="8" x2="12" y2="16"/><line x1="8" y1="12" x2="16" y2="12"/></svg>
|
|
Agregar Dispositivo
|
|
</a>
|
|
</div>
|
|
<div class="nav-section">
|
|
<div class="nav-label">Herramientas</div>
|
|
<a class="nav-item" href="#import">
|
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M21 15v4a2 2 0 01-2 2H5a2 2 0 01-2-2v-4"/><polyline points="17 8 12 3 7 8"/><line x1="12" y1="3" x2="12" y2="15"/></svg>
|
|
Importar / Exportar
|
|
</a>
|
|
<a class="nav-item" href="#backups">
|
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><path d="M19 21H5a2 2 0 01-2-2V5a2 2 0 012-2h11l5 5v11a2 2 0 01-2 2z"/><polyline points="17 21 17 13 7 13 7 21"/><polyline points="7 3 7 8 15 8"/></svg>
|
|
Copias de Seguridad
|
|
</a>
|
|
<a class="nav-item" href="#settings">
|
|
<svg viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="3"/><path d="M19.4 15a1.65 1.65 0 00.33 1.82l.06.06a2 2 0 010 2.83 2 2 0 01-2.83 0l-.06-.06a1.65 1.65 0 00-1.82-.33 1.65 1.65 0 00-1 1.51V21a2 2 0 01-4 0v-.09A1.65 1.65 0 009 19.4a1.65 1.65 0 00-1.82.33l-.06.06a2 2 0 01-2.83-2.83l.06-.06A1.65 1.65 0 004.68 15a1.65 1.65 0 00-1.51-1H3a2 2 0 010-4h.09A1.65 1.65 0 004.6 9a1.65 1.65 0 00-.33-1.82l-.06-.06a2 2 0 012.83-2.83l.06.06A1.65 1.65 0 009 4.68a1.65 1.65 0 001-1.51V3a2 2 0 014 0v.09a1.65 1.65 0 001 1.51 1.65 1.65 0 001.82-.33l.06-.06a2 2 0 012.83 2.83l-.06.06a1.65 1.65 0 00-.33 1.82V9c.26.604.852.997 1.51 1H21a2 2 0 010 4h-.09a1.65 1.65 0 00-1.51 1z"/></svg>
|
|
Configuración
|
|
</a>
|
|
</div>
|
|
<div class="nav-spacer"></div>
|
|
<div class="nav-footer">v1.0.0 — 2026-08-03</div>
|
|
</nav>
|
|
|
|
<!-- Main Content -->
|
|
<main class="main" role="main">
|
|
<header class="topbar">
|
|
<div style="display: flex; align-items: center; gap: 12px;">
|
|
<a href="#inventory" class="btn-ghost" style="padding: 4px; border: none; background: none; cursor: pointer; color: #666; text-decoration: none;" aria-label="Volver al inventario">
|
|
<svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="15 18 9 12 15 6"/></svg>
|
|
</a>
|
|
<h2>Agregar Dispositivo</h2>
|
|
</div>
|
|
<div class="topbar-actions">
|
|
<button class="btn">Cancelar</button>
|
|
<button class="btn btn-primary">Guardar Dispositivo</button>
|
|
</div>
|
|
</header>
|
|
|
|
<div class="content">
|
|
<p class="required-note">Los campos marcados con <span class="required">*</span> son obligatorios.</p>
|
|
|
|
<form class="form-card" aria-label="Formulario de nuevo dispositivo">
|
|
<!-- Identification -->
|
|
<div class="form-section" style="padding: 24px; padding-bottom: 0;">
|
|
<div class="form-section-title">Identificación</div>
|
|
<div class="form-grid">
|
|
<div class="form-group">
|
|
<label class="form-label" for="catalog_number">Número de Catálogo Interno <span class="required">*</span></label>
|
|
<input class="form-input" type="text" id="catalog_number" placeholder="INV-2024-0248" required aria-required="true">
|
|
<span class="form-hint">Se genera automáticamente si se deja vacío</span>
|
|
</div>
|
|
<div class="form-group">
|
|
<label class="form-label" for="serial_number">Número de Serie <span class="required">*</span></label>
|
|
<input class="form-input" type="text" id="serial_number" placeholder="C02X1234JHD5" required aria-required="true">
|
|
</div>
|
|
<div class="form-group">
|
|
<label class="form-label" for="service_tag">Service Tag</label>
|
|
<input class="form-input" type="text" id="service_tag" placeholder="ABC1234">
|
|
<span class="form-hint">Número de servicio del fabricante</span>
|
|
</div>
|
|
<div class="form-group">
|
|
<label class="form-label" for="additional_reg">Registro Adicional</label>
|
|
<input class="form-input" type="text" id="additional_reg" placeholder="Cualquier otro número de registro">
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Device info -->
|
|
<div class="form-section" style="padding: 24px; padding-bottom: 0;">
|
|
<div class="form-section-title">Información del Dispositivo</div>
|
|
<div class="form-grid">
|
|
<div class="form-group full-width">
|
|
<label class="form-label" for="device_name">Nombre / Descripción <span class="required">*</span></label>
|
|
<input class="form-input" type="text" id="device_name" placeholder="MacBook Pro 14" — Oficina Dirección" required aria-required="true">
|
|
</div>
|
|
<div class="form-group">
|
|
<label class="form-label" for="device_type">Tipo de Dispositivo <span class="required">*</span></label>
|
|
<select class="form-select" id="device_type" required aria-required="true">
|
|
<option value="">Seleccionar tipo...</option>
|
|
<option>Ordenador de sobremesa</option>
|
|
<option selected>Portátil</option>
|
|
<option>Monitor / Pantalla</option>
|
|
<option>Impresora</option>
|
|
<option>Escáner</option>
|
|
<option>Dispositivo de red</option>
|
|
<option>Periférico</option>
|
|
<option>Servidor</option>
|
|
<option>SAI / Regulador</option>
|
|
<option>Almacenamiento externo</option>
|
|
<option>Otro</option>
|
|
</select>
|
|
</div>
|
|
<div class="form-group">
|
|
<label class="form-label" for="brand">Marca</label>
|
|
<input class="form-input" type="text" id="brand" placeholder="Apple">
|
|
</div>
|
|
<div class="form-group">
|
|
<label class="form-label" for="model">Modelo</label>
|
|
<input class="form-input" type="text" id="model" placeholder="MacBook Pro M3">
|
|
</div>
|
|
<div class="form-group">
|
|
<label class="form-label" for="serial_internal">N.º Serie Interno</label>
|
|
<input class="form-input" type="text" id="serial_internal" placeholder="Número interno de control">
|
|
</div>
|
|
<div class="form-group">
|
|
<label class="form-label" for="condition">Estado Físico</label>
|
|
<select class="form-select" id="condition">
|
|
<option value="excellent">Excelente</option>
|
|
<option value="good" selected>Bueno</option>
|
|
<option value="fair">Regular</option>
|
|
<option value="poor">Malo</option>
|
|
<option value="damaged">Dañado</option>
|
|
</select>
|
|
</div>
|
|
<div class="form-group">
|
|
<label class="form-label" for="status">Estado</label>
|
|
<select class="form-select" id="status">
|
|
<option value="active" selected>Activo</option>
|
|
<option value="available">Disponible</option>
|
|
<option value="reserved">Reservado</option>
|
|
</select>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Purchase info -->
|
|
<div class="form-section" style="padding: 24px; padding-bottom: 0;">
|
|
<div class="form-section-title">Compra y Garantía</div>
|
|
<div class="form-grid">
|
|
<div class="form-group">
|
|
<label class="form-label" for="purchase_date">Fecha de Compra</label>
|
|
<input class="form-input" type="date" id="purchase_date">
|
|
</div>
|
|
<div class="form-group">
|
|
<label class="form-label" for="warranty_expiry">Fin de Garantía</label>
|
|
<input class="form-input" type="date" id="warranty_expiry">
|
|
</div>
|
|
<div class="form-group">
|
|
<label class="form-label" for="purchase_price">Precio de Compra (€)</label>
|
|
<input class="form-input" type="number" id="purchase_price" placeholder="0.00" step="0.01" min="0">
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Assignment -->
|
|
<div class="form-section" style="padding: 24px; padding-bottom: 0;">
|
|
<div class="form-section-title">Asignación</div>
|
|
<div class="form-grid">
|
|
<div class="form-group">
|
|
<label class="form-label" for="location">Ubicación Asignada</label>
|
|
<select class="form-select" id="location">
|
|
<option value="">Seleccionar ubicación...</option>
|
|
<option>Planta 0 — Recepción</option>
|
|
<option>Planta 1 — Open Space</option>
|
|
<option>Planta 1 — Reprografía</option>
|
|
<option>Planta 1 — Cuarto Redes</option>
|
|
<option>Planta 2 — Desp. Dirección</option>
|
|
<option>Almacén</option>
|
|
</select>
|
|
</div>
|
|
<div class="form-group">
|
|
<label class="form-label" for="assigned_user">Usuario Asignado</label>
|
|
<input class="form-input" type="text" id="assigned_user" placeholder="Nombre del usuario">
|
|
</div>
|
|
<div class="form-group">
|
|
<label class="form-label" for="department">Departamento</label>
|
|
<select class="form-select" id="department">
|
|
<option value="">Seleccionar departamento...</option>
|
|
<option>Informática</option>
|
|
<option>Administración</option>
|
|
<option>Recursos Humanos</option>
|
|
<option>Contabilidad</option>
|
|
<option>Dirección</option>
|
|
</select>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Notes -->
|
|
<div class="form-section" style="padding: 24px;">
|
|
<div class="form-section-title">Notas</div>
|
|
<div class="form-grid">
|
|
<div class="form-group full-width">
|
|
<label class="form-label" for="notes">Observaciones</label>
|
|
<textarea class="form-textarea" id="notes" placeholder="Notas adicionales sobre el dispositivo..." rows="3"></textarea>
|
|
</div>
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Footer -->
|
|
<div class="form-footer">
|
|
<span class="form-footer-info">Los cambios se guardan localmente</span>
|
|
<div class="form-footer-actions">
|
|
<button type="button" class="btn">Cancelar</button>
|
|
<button type="button" class="btn" style="border-color: #e5e5e3;">Guardar y Agregar Otro</button>
|
|
<button type="submit" class="btn btn-primary">Guardar Dispositivo</button>
|
|
</div>
|
|
</div>
|
|
</form>
|
|
</div>
|
|
</main>
|
|
|
|
<!-- Toast demo -->
|
|
<div class="toast" role="alert" aria-live="polite">
|
|
<span class="toast-icon" aria-hidden="true">✓</span>
|
|
Dispositivo INV-2024-0247 guardado correctamente
|
|
</div>
|
|
</body>
|
|
</html>
|