18 KiB
18 KiB
INTERFAZ MML
Mindmap Linking - Capa de Interfaz
1. Concepto Visual
1.1 Referencia
Estética tipo MindNode:
- Diseño limpio y minimalista
- Líneas curvas orgánicas (bezier)
- Colores vibrantes por rama
- Tipografía sans-serif moderna
1.2 Principios
| Principio | Aplicación |
|---|---|
| Simplicidad | Sin elementos decorativos innecesarios |
| Jerarquía | Nodos nivel 1 destacan, subnodos secundarios |
| Color | Cada rama tiene identidad cromática |
| Espacio | Generoso, respira |
2. Estructura Visual
2.1 Elementos
┌─────────────────────────────────────────────────────────────────┐
│ │
│ [Header - Título del proyecto (opcional)] │
│ │
├─────────────────────────────────────────────────────────────────┤
│ │
│ NODOS NIVEL 1 LÍNEAS SUBNODOS │
│ (izquierda) (curvas) (derecha) │
│ │
│ Texto subrayado ────────────────► Imagen 16:9 │
│ con color + título │
│ + botones │
│ │
└─────────────────────────────────────────────────────────────────┘
2.2 Layout Desktop (>768px)
╭─────────────────────────────────────────────────────────────────╮
│ Proyecto Demo HST │
├─────────────────────────────────────────────────────────────────┤
│ │
│ ╭─────────────╮ │
│ ╭─────│ IMAGEN │ │
│ Arquitectura ───────────────┤ ╰─────────────╯ │
│ │ plano.pdf │
│ │ │
│ │ ╭─────────────╮ │
│ ╰─────│ IMAGEN │ │
│ ╰─────────────╯ │
│ secciones.pdf │
│ │
│ ╭─────────────╮ │
│ ╭─────│ IMAGEN │ │
│ Renders ────────────────────┤ ╰─────────────╯ │
│ │ exterior.jpg │
│ │ │
│ ╰─────╭─────────────╮ │
│ │ IMAGEN │ │
│ ╰─────────────╯ │
│ interior.jpg │
│ │
╰─────────────────────────────────────────────────────────────────╯
2.3 Layout Mobile (<768px)
╭──────────────────────╮
│ Proyecto Demo │
├──────────────────────┤
│ │
│ Arquitectura │
│ │ │
│ ┌────┴────┐ │
│ ▼ ▼ │
│ ╭─────╮ ╭─────╮ │
│ │ IMG │ │ IMG │ │
│ ╰─────╯ ╰─────╯ │
│ plano secciones │
│ │
│ Renders │
│ │ │
│ ┌────┴────┐ │
│ ▼ ▼ │
│ ╭─────╮ ╭─────╮ │
│ │ IMG │ │ IMG │ │
│ ╰─────╯ ╰─────╯ │
│ exterior interior │
│ │
│ ↕ scroll vertical │
╰──────────────────────╯
3. Componentes
3.1 Header (opcional)
<header id="header">Proyecto Demo HST</header>
#header {
font-size: 24px;
font-weight: 600;
color: #333;
margin-bottom: 40px;
padding-left: 20px;
}
Visibilidad: Controlada por mostrar_titulo en JSON.
3.2 Nodo Nivel 1
<div class="level1-node" data-color="orange" data-node-id="n1">
<span class="node-text">Arquitectura</span>
</div>
.level1-node {
position: relative;
cursor: pointer;
padding: 8px 0;
}
.level1-node .node-text {
font-size: 18px;
font-weight: 500;
color: #333;
border-bottom: 3px solid currentColor;
padding-bottom: 4px;
display: inline-block;
}
/* Colores por data-color */
.level1-node[data-color="orange"] .node-text {
color: #FF6B35;
border-color: #FF6B35;
}
Características:
- Solo texto, sin caja ni fondo
- Subrayado del color de la rama
- Click para expandir/contraer
3.3 Subnodo
<div class="subnode" data-color="orange" data-parent-id="n1">
<div class="subnode-image-container">
<img src="http://72.62.2.84:8090/arc_arquitectura.png" alt="plano.pdf">
<a href="#" download class="btn-download">↓</a>
<a href="#" target="_blank" class="btn-preview">👁</a>
</div>
<div class="subnode-title">plano.pdf</div>
</div>
.subnode {
position: relative;
width: 200px;
}
.subnode-image-container {
position: relative;
width: 100%;
aspect-ratio: 16/9;
border-radius: 8px;
overflow: hidden;
border: 3px solid #ddd;
}
.subnode-image-container img {
width: 100%;
height: 100%;
object-fit: cover;
}
.subnode-title {
font-size: 14px;
color: #555;
margin-top: 8px;
text-align: center;
}
Características:
- Imagen 16:9 con bordes redondeados
- Borde del color de la rama padre
- Título debajo, centrado
- Botones siempre visibles
3.4 Botones de Acción
.btn-download, .btn-preview {
position: absolute;
width: 28px;
height: 28px;
border-radius: 6px;
background: rgba(0,0,0,0.6);
color: white;
display: flex;
align-items: center;
justify-content: center;
font-size: 14px;
cursor: pointer;
text-decoration: none;
}
.btn-download {
bottom: 6px;
left: 6px;
}
.btn-preview {
top: 6px;
right: 6px;
}
.btn-download:hover, .btn-preview:hover {
background: rgba(0,0,0,0.8);
}
| Botón | Icono | Posición | Visible |
|---|---|---|---|
| Descarga | ↓ | Esquina inferior izquierda | Siempre |
| Preview | 👁 | Esquina superior derecha | Solo PDF/imágenes |
Archivos con preview:
.pdf.png.jpg/.jpeg.gif.webp
3.5 Líneas SVG
<svg id="lines-svg">
<path d="M 150,50 C 200,50 200,120 250,120" stroke="#FF6B35" />
</svg>
#lines-svg {
position: absolute;
top: 0;
left: 0;
width: 100%;
height: 100%;
pointer-events: none;
overflow: visible;
}
#lines-svg path {
fill: none;
stroke-width: 3;
stroke-linecap: round;
}
Curva Bezier:
M x1,y1 C cx1,cy1 cx2,cy2 x2,y2
Donde:
- (x1,y1) = Punto inicio (borde derecho del nodo nivel 1)
- (cx1,cy1) = Primer punto de control
- (cx2,cy2) = Segundo punto de control
- (x2,y2) = Punto final (borde izquierdo del subnodo)
4. Paleta de Colores
4.1 Colores de ramas
| Nombre | Hex | Muestra |
|---|---|---|
| Orange | #FF6B35 | 🟠 |
| Yellow | #E5A000 | 🟡 |
| Green | #06D6A0 | 🟢 |
| Cyan | #0891B2 | 🔵 |
| Blue | #3B82F6 | 🔷 |
| Purple | #8B5CF6 | 🟣 |
| Pink | #EC4899 | 🩷 |
4.2 Colores base
| Uso | Hex |
|---|---|
| Fondo | #f5f5f7 |
| Texto principal | #333333 |
| Texto secundario | #555555 |
| Borde default | #dddddd |
4.3 Asignación automática
const COLORS = ['orange', 'yellow', 'green', 'cyan', 'blue', 'purple', 'pink'];
// Cada nodo nivel 1 recibe un color según su índice
const color = COLORS[index % COLORS.length];
5. Tipografía
5.1 Familia
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
5.2 Tamaños
| Elemento | Tamaño | Peso |
|---|---|---|
| Header | 24px | 600 |
| Nodo nivel 1 | 18px | 500 |
| Título subnodo | 14px | 400 |
6. Interactividad
6.1 Click en nodo nivel 1
node.addEventListener('click', () => {
const nodeId = node.dataset.nodeId;
const subnodes = document.querySelectorAll(`[data-parent-id="${nodeId}"]`);
subnodes.forEach(sub => {
sub.classList.toggle('hidden');
});
// Redibujar líneas
drawLines();
});
Comportamiento:
- Toggle: muestra/oculta subnodos
- Recalcula posiciones de líneas SVG
- Animación suave (opcional)
6.2 Cálculo de líneas
function drawLines() {
const svg = document.getElementById('lines-svg');
svg.innerHTML = '';
const containerRect = document.getElementById('mindmap-container').getBoundingClientRect();
document.querySelectorAll('.level1-node').forEach(node => {
const nodeId = node.dataset.nodeId;
const color = node.dataset.color;
const nodeRect = node.getBoundingClientRect();
// Punto inicio: borde derecho del subrayado
const startX = nodeRect.right - containerRect.left;
const startY = nodeRect.bottom - containerRect.top - 2;
// Encontrar subnodos visibles
const subnodes = document.querySelectorAll(
`.subnode[data-parent-id="${nodeId}"]:not(.hidden)`
);
subnodes.forEach(subnode => {
const subRect = subnode.querySelector('.subnode-image-container')
.getBoundingClientRect();
// Punto final: borde izquierdo del subnodo
const endX = subRect.left - containerRect.left;
const endY = subRect.top + subRect.height/2 - containerRect.top;
// Puntos de control para curva suave
const midX = startX + (endX - startX) / 2;
const path = document.createElementNS('http://www.w3.org/2000/svg', 'path');
path.setAttribute('d',
`M ${startX},${startY} C ${midX},${startY} ${midX},${endY} ${endX},${endY}`
);
path.setAttribute('stroke', getColorValue(color));
svg.appendChild(path);
});
});
}
6.3 Responsive
// Redibujar en resize
window.addEventListener('resize', drawLines);
// Detectar mobile
const isMobile = window.innerWidth < 768;
7. Estructura JSON
7.1 Formato completo
{
"slug": "proyecto-demo",
"titulo": "Proyecto Demo HST",
"mostrar_titulo": true,
"tema": "light",
"nodos": [
{
"id": "n1",
"titulo": "Arquitectura",
"color": "#FF6B35",
"subnodos": [
{
"titulo": "plano_general.pdf",
"icono_hst": "arc_arquitectura",
"url_archivo": "https://git.tzzr.pro/.../plano.pdf",
"url_corta": "https://tzzr.pro/abc123"
},
{
"titulo": "secciones.pdf",
"icono_hst": "pln_plano",
"url_archivo": "https://git.tzzr.pro/.../secciones.pdf",
"url_corta": "https://tzzr.pro/def456"
}
]
},
{
"id": "n2",
"titulo": "Renders",
"color": "#E5A000",
"subnodos": [
{
"titulo": "exterior.jpg",
"icono_hst": "ren_render",
"url_archivo": "https://git.tzzr.pro/.../exterior.jpg"
}
]
}
]
}
7.2 Campos
| Campo | Tipo | Requerido | Descripción |
|---|---|---|---|
| slug | string | ✅ | Identificador URL |
| titulo | string | ✅ | Nombre del proyecto |
| mostrar_titulo | boolean | ❌ | Si mostrar header (default: true) |
| tema | string | ❌ | "light" o "dark" |
| nodos | array | ✅ | Lista de nodos nivel 1 |
Nodo:
| Campo | Tipo | Requerido | Descripción |
|---|---|---|---|
| id | string | ✅ | Identificador único |
| titulo | string | ✅ | Nombre de la carpeta |
| color | string | ❌ | Color hex (si no, se asigna auto) |
| subnodos | array | ✅ | Lista de subnodos |
Subnodo:
| Campo | Tipo | Requerido | Descripción |
|---|---|---|---|
| titulo | string | ✅ | Nombre del archivo |
| icono_hst | string | ✅ | Referencia imagen HST |
| url_archivo | string | ✅ | URL de descarga |
| url_corta | string | ❌ | URL acortada |
8. Código Base
8.1 index.html
<!DOCTYPE html>
<html lang="es">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>MML - Mindmap Linking</title>
<link rel="stylesheet" href="css/main.css">
</head>
<body>
<div id="app">
<header id="header"></header>
<div id="mindmap-container">
<svg id="lines-svg"></svg>
<div id="nodes-container"></div>
</div>
</div>
<script src="js/app.js" type="module"></script>
</body>
</html>
8.2 Variables CSS
:root {
--bg-primary: #f5f5f7;
--text-primary: #333;
--text-secondary: #555;
--font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, sans-serif;
--line-width: 3px;
--image-radius: 8px;
--transition-speed: 0.3s;
/* Colores de ramas */
--color-orange: #FF6B35;
--color-yellow: #E5A000;
--color-green: #06D6A0;
--color-cyan: #0891B2;
--color-blue: #3B82F6;
--color-purple: #8B5CF6;
--color-pink: #EC4899;
}
9. Imágenes HST
9.1 URL Base
const HST_BASE = 'http://72.62.2.84:8090/';
9.2 Construcción de URL
function getImageUrl(icono_hst) {
if (!icono_hst) return null;
return `${HST_BASE}${icono_hst}.png`;
}
9.3 Formato de nombres
{codigo}_{nombre}.png
Ejemplos:
- arc_arquitectura.png
- pln_plano.png
- doc_documento.png
- fto_foto.png
9.4 Fallback si imagen no existe
img.onerror = function() {
this.src = '/assets/placeholder.png';
// O ocultar el contenedor de imagen
this.parentElement.style.display = 'none';
};
10. Tests de Interfaz
10.1 Checklist visual
| Test | Criterio |
|---|---|
| Carga | Página muestra contenido en <2s |
| Header | Título visible si mostrar_titulo: true |
| Nodos | Todos los nodos nivel 1 visibles |
| Imágenes | Ninguna imagen rota |
| Líneas | Curvas conectan correctamente |
| Click | Expandir/contraer funciona |
| Responsive | Layout adapta a mobile |
10.2 Tests Puppeteer
// Test de imágenes
const images = await page.$$eval('img', imgs =>
imgs.map(img => ({
src: img.src,
loaded: img.complete && img.naturalWidth > 0
}))
);
const broken = images.filter(i => !i.loaded);
if (broken.length > 0) {
errors.push(`Imágenes rotas: ${broken.map(i => i.src).join(', ')}`);
}
// Test de líneas
const paths = await page.$$('svg path');
if (paths.length === 0) {
errors.push('No hay curvas SVG');
}
// Test de interactividad
await page.click('.level1-node');
await page.waitForTimeout(300);
const visibleSubnodes = await page.$$('.subnode:not(.hidden)');
11. Accesibilidad
11.1 Básico
<!-- Alt text en imágenes -->
<img src="..." alt="plano_general.pdf">
<!-- Roles semánticos -->
<nav role="navigation">
<main role="main">
<!-- Focus visible -->
.level1-node:focus {
outline: 2px solid var(--color-blue);
}
11.2 Navegación por teclado
// Enter/Space para expandir
node.addEventListener('keydown', (e) => {
if (e.key === 'Enter' || e.key === ' ') {
e.preventDefault();
node.click();
}
});
// Tab order
node.setAttribute('tabindex', '0');
Versión: 1.0
Fecha: 2025-12-11