1555 lines
53 KiB
Bash
Executable File
1555 lines
53 KiB
Bash
Executable File
#!/bin/bash
|
|
# Genera index.html con documentos y SVGs embebidos
|
|
# Estilo Apple Design System
|
|
# Uso: ./generate-docs.sh
|
|
|
|
cd "$(dirname "$0")"
|
|
|
|
# Función para escapar contenido para JavaScript
|
|
escape_js() {
|
|
sed 's/\\/\\\\/g; s/`/\\`/g; s/\$/\\$/g' | tr '\n' '\r' | sed 's/\r/\\n/g'
|
|
}
|
|
|
|
# Función para convertir SVG a base64 data URI
|
|
svg_to_base64() {
|
|
local file="$1"
|
|
if [[ -f "$file" ]]; then
|
|
echo "data:image/svg+xml;base64,$(base64 -w0 "$file")"
|
|
fi
|
|
}
|
|
|
|
echo "Generando index.html con diseño Apple..."
|
|
|
|
# Inicio del HTML con diseño Apple completo
|
|
cat > index.html << 'HTMLHEAD'
|
|
<!DOCTYPE html>
|
|
<html lang="es">
|
|
<head>
|
|
<meta charset="UTF-8">
|
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
<title>Academia - Documentación</title>
|
|
<meta name="theme-color" content="#000000" media="(prefers-color-scheme: dark)">
|
|
<meta name="theme-color" content="#ffffff" media="(prefers-color-scheme: light)">
|
|
<script src="https://cdn.jsdelivr.net/npm/marked/marked.min.js"></script>
|
|
<style>
|
|
/* ═══════════════════════════════════════════════════════════
|
|
APPLE DESIGN SYSTEM - Inspired by apple.com & developer.apple.com
|
|
═══════════════════════════════════════════════════════════ */
|
|
|
|
/* ─── Design Tokens ─── */
|
|
:root {
|
|
/* Typography Scale (Apple's SF Pro) */
|
|
--font-family: -apple-system, BlinkMacSystemFont, "SF Pro Display", "SF Pro Text", "Helvetica Neue", Helvetica, Arial, sans-serif;
|
|
--font-mono: "SF Mono", ui-monospace, Menlo, Monaco, "Cascadia Code", monospace;
|
|
|
|
/* Spacing (8pt grid) */
|
|
--space-1: 4px;
|
|
--space-2: 8px;
|
|
--space-3: 12px;
|
|
--space-4: 16px;
|
|
--space-5: 20px;
|
|
--space-6: 24px;
|
|
--space-8: 32px;
|
|
--space-10: 40px;
|
|
--space-12: 48px;
|
|
--space-16: 64px;
|
|
--space-20: 80px;
|
|
|
|
/* Radius (Apple's continuous corners) */
|
|
--radius-sm: 8px;
|
|
--radius-md: 12px;
|
|
--radius-lg: 18px;
|
|
--radius-xl: 22px;
|
|
|
|
/* Transitions (Apple's spring-like easing) */
|
|
--ease-out: cubic-bezier(0.25, 0.46, 0.45, 0.94);
|
|
--ease-spring: cubic-bezier(0.175, 0.885, 0.32, 1.1);
|
|
--duration-fast: 0.15s;
|
|
--duration-normal: 0.25s;
|
|
--duration-slow: 0.4s;
|
|
|
|
/* Layout */
|
|
--sidebar-width: 260px;
|
|
--header-height: 48px;
|
|
--content-max-width: 780px;
|
|
|
|
/* Light Mode Colors (Apple semantic) */
|
|
--color-bg: #ffffff;
|
|
--color-bg-secondary: #f5f5f7;
|
|
--color-bg-tertiary: #fbfbfd;
|
|
--color-bg-elevated: #ffffff;
|
|
--color-surface: rgba(255, 255, 255, 0.72);
|
|
--color-text: #1d1d1f;
|
|
--color-text-secondary: #86868b;
|
|
--color-text-tertiary: #6e6e73;
|
|
--color-accent: #0071e3;
|
|
--color-accent-hover: #0077ed;
|
|
--color-border: rgba(0, 0, 0, 0.08);
|
|
--color-border-strong: rgba(0, 0, 0, 0.12);
|
|
--color-separator: rgba(60, 60, 67, 0.12);
|
|
--color-fill: rgba(120, 120, 128, 0.08);
|
|
--color-fill-hover: rgba(120, 120, 128, 0.12);
|
|
--shadow-sm: 0 1px 3px rgba(0, 0, 0, 0.04), 0 1px 2px rgba(0, 0, 0, 0.06);
|
|
--shadow-md: 0 4px 12px rgba(0, 0, 0, 0.08), 0 2px 4px rgba(0, 0, 0, 0.04);
|
|
--shadow-lg: 0 8px 28px rgba(0, 0, 0, 0.12), 0 4px 8px rgba(0, 0, 0, 0.06);
|
|
--shadow-float: 0 22px 70px 4px rgba(0, 0, 0, 0.15);
|
|
}
|
|
|
|
@media (prefers-color-scheme: dark) {
|
|
:root {
|
|
--color-bg: #000000;
|
|
--color-bg-secondary: #1c1c1e;
|
|
--color-bg-tertiary: #2c2c2e;
|
|
--color-bg-elevated: #1c1c1e;
|
|
--color-surface: rgba(28, 28, 30, 0.72);
|
|
--color-text: #f5f5f7;
|
|
--color-text-secondary: #98989d;
|
|
--color-text-tertiary: #8e8e93;
|
|
--color-accent: #2997ff;
|
|
--color-accent-hover: #40a9ff;
|
|
--color-border: rgba(255, 255, 255, 0.08);
|
|
--color-border-strong: rgba(255, 255, 255, 0.12);
|
|
--color-separator: rgba(84, 84, 88, 0.65);
|
|
--color-fill: rgba(120, 120, 128, 0.24);
|
|
--color-fill-hover: rgba(120, 120, 128, 0.32);
|
|
--shadow-sm: 0 1px 3px rgba(0, 0, 0, 0.3);
|
|
--shadow-md: 0 4px 12px rgba(0, 0, 0, 0.4);
|
|
--shadow-lg: 0 8px 28px rgba(0, 0, 0, 0.5);
|
|
--shadow-float: 0 22px 70px 4px rgba(0, 0, 0, 0.6);
|
|
}
|
|
}
|
|
|
|
/* ─── Reset & Base ─── */
|
|
*, *::before, *::after {
|
|
box-sizing: border-box;
|
|
margin: 0;
|
|
padding: 0;
|
|
}
|
|
|
|
html {
|
|
font-size: 17px;
|
|
-webkit-font-smoothing: antialiased;
|
|
-moz-osx-font-smoothing: grayscale;
|
|
text-rendering: optimizeLegibility;
|
|
scroll-behavior: smooth;
|
|
}
|
|
|
|
body {
|
|
font-family: var(--font-family);
|
|
background: var(--color-bg);
|
|
color: var(--color-text);
|
|
line-height: 1.47059;
|
|
letter-spacing: -0.022em;
|
|
overflow-x: hidden;
|
|
}
|
|
|
|
/* ─── Header (Apple Nav Bar) ─── */
|
|
.header {
|
|
position: fixed;
|
|
top: 0;
|
|
left: 0;
|
|
right: 0;
|
|
height: var(--header-height);
|
|
background: var(--color-surface);
|
|
backdrop-filter: saturate(180%) blur(20px);
|
|
-webkit-backdrop-filter: saturate(180%) blur(20px);
|
|
border-bottom: 0.5px solid var(--color-separator);
|
|
z-index: 1000;
|
|
display: flex;
|
|
align-items: center;
|
|
padding: 0 var(--space-5);
|
|
gap: var(--space-3);
|
|
}
|
|
|
|
.header-logo {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: var(--space-2);
|
|
}
|
|
|
|
.header-logo svg {
|
|
width: 20px;
|
|
height: 20px;
|
|
fill: var(--color-accent);
|
|
}
|
|
|
|
.header-title {
|
|
font-size: 17px;
|
|
font-weight: 600;
|
|
letter-spacing: -0.022em;
|
|
color: var(--color-text);
|
|
}
|
|
|
|
.header-divider {
|
|
width: 1px;
|
|
height: 20px;
|
|
background: var(--color-separator);
|
|
margin: 0 var(--space-2);
|
|
}
|
|
|
|
.header-subtitle {
|
|
font-size: 13px;
|
|
font-weight: 400;
|
|
color: var(--color-text-secondary);
|
|
letter-spacing: -0.008em;
|
|
}
|
|
|
|
/* ─── Layout ─── */
|
|
.layout {
|
|
display: flex;
|
|
min-height: 100vh;
|
|
padding-top: var(--header-height);
|
|
}
|
|
|
|
/* ─── Sidebar (Apple Developer Docs style) ─── */
|
|
.sidebar {
|
|
width: var(--sidebar-width);
|
|
background: var(--color-bg-secondary);
|
|
border-right: 0.5px solid var(--color-separator);
|
|
height: calc(100vh - var(--header-height));
|
|
position: fixed;
|
|
left: 0;
|
|
top: var(--header-height);
|
|
overflow-y: auto;
|
|
overflow-x: hidden;
|
|
padding: var(--space-4) 0;
|
|
}
|
|
|
|
.sidebar::-webkit-scrollbar {
|
|
width: 8px;
|
|
}
|
|
|
|
.sidebar::-webkit-scrollbar-track {
|
|
background: transparent;
|
|
}
|
|
|
|
.sidebar::-webkit-scrollbar-thumb {
|
|
background: var(--color-fill);
|
|
border-radius: 4px;
|
|
border: 2px solid var(--color-bg-secondary);
|
|
}
|
|
|
|
.sidebar::-webkit-scrollbar-thumb:hover {
|
|
background: var(--color-fill-hover);
|
|
}
|
|
|
|
/* Search Box */
|
|
.search-wrap {
|
|
padding: 0 var(--space-4) var(--space-4);
|
|
}
|
|
|
|
.search-box {
|
|
position: relative;
|
|
}
|
|
|
|
.search-box input {
|
|
width: 100%;
|
|
height: 36px;
|
|
padding: 0 var(--space-3) 0 36px;
|
|
background: var(--color-fill);
|
|
border: none;
|
|
border-radius: var(--radius-sm);
|
|
font-size: 15px;
|
|
font-family: var(--font-family);
|
|
color: var(--color-text);
|
|
transition: background var(--duration-fast) var(--ease-out),
|
|
box-shadow var(--duration-fast) var(--ease-out);
|
|
}
|
|
|
|
.search-box input::placeholder {
|
|
color: var(--color-text-tertiary);
|
|
}
|
|
|
|
.search-box input:focus {
|
|
outline: none;
|
|
background: var(--color-bg);
|
|
box-shadow: 0 0 0 4px rgba(0, 113, 227, 0.24);
|
|
}
|
|
|
|
.search-box::before {
|
|
content: '';
|
|
position: absolute;
|
|
left: 12px;
|
|
top: 50%;
|
|
transform: translateY(-50%);
|
|
width: 15px;
|
|
height: 15px;
|
|
background: var(--color-text-tertiary);
|
|
-webkit-mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 24 24' stroke='currentColor'%3E%3Cpath stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z'/%3E%3C/svg%3E") center/contain no-repeat;
|
|
mask: url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' fill='none' viewBox='0 0 24 24' stroke='currentColor'%3E%3Cpath stroke-linecap='round' stroke-linejoin='round' stroke-width='2' d='M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0z'/%3E%3C/svg%3E") center/contain no-repeat;
|
|
}
|
|
|
|
/* Navigation */
|
|
.nav-group {
|
|
margin-bottom: var(--space-2);
|
|
}
|
|
|
|
.nav-group-title {
|
|
display: flex;
|
|
align-items: center;
|
|
gap: var(--space-2);
|
|
padding: var(--space-2) var(--space-4);
|
|
font-size: 12px;
|
|
font-weight: 600;
|
|
color: var(--color-text-secondary);
|
|
text-transform: uppercase;
|
|
letter-spacing: 0.04em;
|
|
cursor: pointer;
|
|
user-select: none;
|
|
transition: color var(--duration-fast) var(--ease-out);
|
|
}
|
|
|
|
.nav-group-title:hover {
|
|
color: var(--color-text);
|
|
}
|
|
|
|
.nav-group-title .chevron {
|
|
width: 12px;
|
|
height: 12px;
|
|
transition: transform var(--duration-normal) var(--ease-spring);
|
|
opacity: 0.5;
|
|
}
|
|
|
|
.nav-group.open .nav-group-title .chevron {
|
|
transform: rotate(90deg);
|
|
}
|
|
|
|
.nav-list {
|
|
display: none;
|
|
padding: var(--space-1) var(--space-2);
|
|
}
|
|
|
|
.nav-group.open .nav-list {
|
|
display: block;
|
|
}
|
|
|
|
.nav-item {
|
|
display: block;
|
|
padding: var(--space-2) var(--space-3);
|
|
margin: 1px 0;
|
|
font-size: 13px;
|
|
font-weight: 400;
|
|
color: var(--color-text-secondary);
|
|
text-decoration: none;
|
|
border-radius: var(--radius-sm);
|
|
cursor: pointer;
|
|
transition: all var(--duration-fast) var(--ease-out);
|
|
white-space: nowrap;
|
|
overflow: hidden;
|
|
text-overflow: ellipsis;
|
|
}
|
|
|
|
.nav-item:hover {
|
|
background: var(--color-fill);
|
|
color: var(--color-text);
|
|
}
|
|
|
|
.nav-item.active {
|
|
background: var(--color-accent);
|
|
color: white;
|
|
font-weight: 500;
|
|
}
|
|
|
|
/* ─── Main Content ─── */
|
|
.main {
|
|
flex: 1;
|
|
margin-left: var(--sidebar-width);
|
|
min-height: calc(100vh - var(--header-height));
|
|
}
|
|
|
|
.content-wrapper {
|
|
max-width: var(--content-max-width);
|
|
margin: 0 auto;
|
|
padding: var(--space-12) var(--space-8) var(--space-20);
|
|
}
|
|
|
|
/* ─── Typography (Apple Style) ─── */
|
|
.content h1 {
|
|
font-size: 40px;
|
|
line-height: 1.1;
|
|
font-weight: 700;
|
|
letter-spacing: -0.015em;
|
|
color: var(--color-text);
|
|
margin: 0 0 var(--space-6);
|
|
}
|
|
|
|
.content h2 {
|
|
font-size: 28px;
|
|
line-height: 1.14286;
|
|
font-weight: 600;
|
|
letter-spacing: 0.007em;
|
|
color: var(--color-text);
|
|
margin: var(--space-12) 0 var(--space-4);
|
|
padding-top: var(--space-4);
|
|
}
|
|
|
|
.content h3 {
|
|
font-size: 21px;
|
|
line-height: 1.19048;
|
|
font-weight: 600;
|
|
letter-spacing: 0.011em;
|
|
color: var(--color-text);
|
|
margin: var(--space-8) 0 var(--space-3);
|
|
}
|
|
|
|
.content h4 {
|
|
font-size: 17px;
|
|
line-height: 1.23536;
|
|
font-weight: 600;
|
|
letter-spacing: -0.022em;
|
|
color: var(--color-text-secondary);
|
|
margin: var(--space-6) 0 var(--space-2);
|
|
}
|
|
|
|
.content p {
|
|
font-size: 17px;
|
|
line-height: 1.52941;
|
|
color: var(--color-text-tertiary);
|
|
margin: var(--space-4) 0;
|
|
}
|
|
|
|
.content strong {
|
|
font-weight: 600;
|
|
color: var(--color-text);
|
|
}
|
|
|
|
.content a {
|
|
color: var(--color-accent);
|
|
text-decoration: none;
|
|
transition: opacity var(--duration-fast) var(--ease-out);
|
|
}
|
|
|
|
.content a:hover {
|
|
text-decoration: underline;
|
|
text-underline-offset: 2px;
|
|
}
|
|
|
|
/* Lists */
|
|
.content ul, .content ol {
|
|
margin: var(--space-4) 0;
|
|
padding-left: var(--space-6);
|
|
}
|
|
|
|
.content li {
|
|
font-size: 17px;
|
|
line-height: 1.52941;
|
|
color: var(--color-text-tertiary);
|
|
margin: var(--space-2) 0;
|
|
}
|
|
|
|
.content li::marker {
|
|
color: var(--color-accent);
|
|
}
|
|
|
|
/* Code (Apple Developer style) */
|
|
.content code {
|
|
font-family: var(--font-mono);
|
|
font-size: 0.88em;
|
|
background: var(--color-fill);
|
|
padding: 2px 6px;
|
|
border-radius: 4px;
|
|
color: var(--color-text);
|
|
}
|
|
|
|
.content pre {
|
|
background: var(--color-bg-secondary);
|
|
border: 1px solid var(--color-border);
|
|
border-radius: var(--radius-md);
|
|
padding: var(--space-4) var(--space-5);
|
|
overflow-x: auto;
|
|
margin: var(--space-5) 0;
|
|
}
|
|
|
|
.content pre code {
|
|
background: none;
|
|
padding: 0;
|
|
font-size: 14px;
|
|
line-height: 1.6;
|
|
color: var(--color-text);
|
|
}
|
|
|
|
/* Blockquote (Apple callout style) */
|
|
.content blockquote {
|
|
background: linear-gradient(135deg, rgba(0, 113, 227, 0.08) 0%, rgba(88, 86, 214, 0.08) 100%);
|
|
border-left: 4px solid var(--color-accent);
|
|
border-radius: 0 var(--radius-md) var(--radius-md) 0;
|
|
padding: var(--space-4) var(--space-5);
|
|
margin: var(--space-5) 0;
|
|
}
|
|
|
|
.content blockquote p {
|
|
margin: 0;
|
|
color: var(--color-text-secondary);
|
|
font-style: normal;
|
|
}
|
|
|
|
/* Tables (Apple clean style) */
|
|
.content table {
|
|
width: 100%;
|
|
border-collapse: separate;
|
|
border-spacing: 0;
|
|
margin: var(--space-5) 0;
|
|
font-size: 15px;
|
|
border-radius: var(--radius-md);
|
|
overflow: hidden;
|
|
border: 1px solid var(--color-border);
|
|
}
|
|
|
|
.content th {
|
|
background: var(--color-bg-secondary);
|
|
font-weight: 600;
|
|
color: var(--color-text);
|
|
text-align: left;
|
|
padding: var(--space-3) var(--space-4);
|
|
border-bottom: 1px solid var(--color-border);
|
|
}
|
|
|
|
.content td {
|
|
padding: var(--space-3) var(--space-4);
|
|
color: var(--color-text-tertiary);
|
|
border-bottom: 1px solid var(--color-border);
|
|
}
|
|
|
|
.content tr:last-child td {
|
|
border-bottom: none;
|
|
}
|
|
|
|
.content tbody tr:hover {
|
|
background: var(--color-fill);
|
|
}
|
|
|
|
/* Horizontal Rule */
|
|
.content hr {
|
|
border: none;
|
|
height: 1px;
|
|
background: var(--color-separator);
|
|
margin: var(--space-10) 0;
|
|
}
|
|
|
|
/* ─── Images & Diagrams (Apple Gallery style) ─── */
|
|
.content img {
|
|
display: block;
|
|
max-width: 100%;
|
|
height: auto;
|
|
margin: var(--space-6) auto;
|
|
border-radius: var(--radius-lg);
|
|
box-shadow: var(--shadow-md);
|
|
background: var(--color-bg);
|
|
}
|
|
|
|
.diagram-card {
|
|
background: var(--color-bg-elevated);
|
|
border: 1px solid var(--color-border);
|
|
border-radius: var(--radius-xl);
|
|
padding: var(--space-6);
|
|
margin: var(--space-6) 0;
|
|
box-shadow: var(--shadow-sm);
|
|
transition: box-shadow var(--duration-normal) var(--ease-out),
|
|
transform var(--duration-normal) var(--ease-out);
|
|
}
|
|
|
|
.diagram-card:hover {
|
|
box-shadow: var(--shadow-lg);
|
|
transform: translateY(-2px);
|
|
}
|
|
|
|
.diagram-card img {
|
|
margin: 0;
|
|
border-radius: var(--radius-md);
|
|
box-shadow: none;
|
|
width: 100%;
|
|
}
|
|
|
|
.diagram-title {
|
|
font-size: 15px;
|
|
font-weight: 600;
|
|
color: var(--color-text);
|
|
margin-top: var(--space-4);
|
|
text-align: center;
|
|
}
|
|
|
|
/* ─── Welcome Screen ─── */
|
|
.welcome {
|
|
text-align: center;
|
|
padding: var(--space-20) var(--space-8);
|
|
max-width: 480px;
|
|
margin: 0 auto;
|
|
}
|
|
|
|
.welcome-icon {
|
|
width: 96px;
|
|
height: 96px;
|
|
margin: 0 auto var(--space-6);
|
|
background: linear-gradient(135deg, var(--color-accent) 0%, #5856d6 100%);
|
|
border-radius: 24px;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
font-size: 48px;
|
|
box-shadow: var(--shadow-lg);
|
|
}
|
|
|
|
.welcome h1 {
|
|
font-size: 32px;
|
|
font-weight: 700;
|
|
margin-bottom: var(--space-3);
|
|
}
|
|
|
|
.welcome p {
|
|
color: var(--color-text-secondary);
|
|
font-size: 17px;
|
|
line-height: 1.52941;
|
|
}
|
|
|
|
/* ─── Diagrams Gallery ─── */
|
|
.diagrams-section {
|
|
margin: var(--space-10) 0;
|
|
}
|
|
|
|
.diagrams-section h2 {
|
|
margin-bottom: var(--space-6);
|
|
}
|
|
|
|
.diagrams-grid {
|
|
display: grid;
|
|
grid-template-columns: repeat(auto-fill, minmax(340px, 1fr));
|
|
gap: var(--space-5);
|
|
}
|
|
|
|
/* ─── Lightbox Modal (Apple style with Zoom & Pan) ─── */
|
|
.lightbox {
|
|
position: fixed;
|
|
inset: 0;
|
|
z-index: 2000;
|
|
display: none;
|
|
align-items: center;
|
|
justify-content: center;
|
|
background: rgba(0, 0, 0, 0.9);
|
|
backdrop-filter: blur(30px);
|
|
-webkit-backdrop-filter: blur(30px);
|
|
opacity: 0;
|
|
transition: opacity var(--duration-normal) var(--ease-out);
|
|
}
|
|
|
|
.lightbox.active {
|
|
display: flex;
|
|
opacity: 1;
|
|
}
|
|
|
|
/* Image Container with Pan support */
|
|
.lightbox-viewport {
|
|
position: relative;
|
|
width: 100%;
|
|
height: 100%;
|
|
overflow: hidden;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
cursor: grab;
|
|
}
|
|
|
|
.lightbox-viewport.dragging {
|
|
cursor: grabbing;
|
|
}
|
|
|
|
.lightbox-viewport.zoomed-out {
|
|
cursor: zoom-in;
|
|
}
|
|
|
|
.lightbox-content {
|
|
position: absolute;
|
|
transform-origin: center center;
|
|
transition: transform 0.3s var(--ease-spring);
|
|
will-change: transform;
|
|
animation: lightbox-in var(--duration-slow) var(--ease-spring);
|
|
}
|
|
|
|
.lightbox-content.no-transition {
|
|
transition: none;
|
|
}
|
|
|
|
@keyframes lightbox-in {
|
|
from {
|
|
opacity: 0;
|
|
transform: scale(0.9);
|
|
}
|
|
to {
|
|
opacity: 1;
|
|
transform: scale(1);
|
|
}
|
|
}
|
|
|
|
.lightbox-content img {
|
|
max-width: 90vw;
|
|
max-height: 85vh;
|
|
object-fit: contain;
|
|
border-radius: var(--radius-lg);
|
|
box-shadow: var(--shadow-float);
|
|
background: white;
|
|
pointer-events: none;
|
|
user-select: none;
|
|
-webkit-user-drag: none;
|
|
}
|
|
|
|
/* Top Toolbar (Apple style pill) */
|
|
.lightbox-toolbar {
|
|
position: fixed;
|
|
top: 20px;
|
|
left: 50%;
|
|
transform: translateX(-50%);
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 2px;
|
|
padding: 6px;
|
|
background: rgba(255, 255, 255, 0.12);
|
|
backdrop-filter: blur(20px);
|
|
-webkit-backdrop-filter: blur(20px);
|
|
border-radius: 22px;
|
|
border: 1px solid rgba(255, 255, 255, 0.1);
|
|
z-index: 10;
|
|
}
|
|
|
|
.lightbox-toolbar button {
|
|
width: 36px;
|
|
height: 36px;
|
|
border-radius: 50%;
|
|
background: transparent;
|
|
border: none;
|
|
cursor: pointer;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
transition: all var(--duration-fast) var(--ease-out);
|
|
color: white;
|
|
}
|
|
|
|
.lightbox-toolbar button:hover {
|
|
background: rgba(255, 255, 255, 0.15);
|
|
}
|
|
|
|
.lightbox-toolbar button:active {
|
|
transform: scale(0.95);
|
|
}
|
|
|
|
.lightbox-toolbar button:disabled {
|
|
opacity: 0.3;
|
|
cursor: not-allowed;
|
|
}
|
|
|
|
.lightbox-toolbar button svg {
|
|
width: 18px;
|
|
height: 18px;
|
|
stroke: white;
|
|
stroke-width: 2;
|
|
fill: none;
|
|
}
|
|
|
|
.lightbox-toolbar .divider {
|
|
width: 1px;
|
|
height: 24px;
|
|
background: rgba(255, 255, 255, 0.2);
|
|
margin: 0 4px;
|
|
}
|
|
|
|
.zoom-level {
|
|
font-size: 13px;
|
|
font-weight: 500;
|
|
color: white;
|
|
min-width: 50px;
|
|
text-align: center;
|
|
font-variant-numeric: tabular-nums;
|
|
}
|
|
|
|
/* Bottom Info Bar */
|
|
.lightbox-info {
|
|
position: fixed;
|
|
bottom: 20px;
|
|
left: 50%;
|
|
transform: translateX(-50%);
|
|
display: flex;
|
|
align-items: center;
|
|
gap: 16px;
|
|
padding: 12px 20px;
|
|
background: rgba(255, 255, 255, 0.12);
|
|
backdrop-filter: blur(20px);
|
|
-webkit-backdrop-filter: blur(20px);
|
|
border-radius: 16px;
|
|
border: 1px solid rgba(255, 255, 255, 0.1);
|
|
}
|
|
|
|
.lightbox-title {
|
|
font-size: 15px;
|
|
font-weight: 500;
|
|
color: white;
|
|
white-space: nowrap;
|
|
}
|
|
|
|
.lightbox-counter {
|
|
font-size: 13px;
|
|
color: rgba(255, 255, 255, 0.6);
|
|
font-variant-numeric: tabular-nums;
|
|
}
|
|
|
|
/* Navigation Buttons */
|
|
.lightbox-close {
|
|
position: fixed;
|
|
top: 20px;
|
|
right: 20px;
|
|
width: 44px;
|
|
height: 44px;
|
|
border-radius: 50%;
|
|
background: rgba(255, 255, 255, 0.12);
|
|
backdrop-filter: blur(20px);
|
|
-webkit-backdrop-filter: blur(20px);
|
|
border: 1px solid rgba(255, 255, 255, 0.1);
|
|
cursor: pointer;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
transition: all var(--duration-fast) var(--ease-out);
|
|
z-index: 10;
|
|
}
|
|
|
|
.lightbox-close:hover {
|
|
background: rgba(255, 255, 255, 0.2);
|
|
transform: scale(1.05);
|
|
}
|
|
|
|
.lightbox-close:active {
|
|
transform: scale(0.95);
|
|
}
|
|
|
|
.lightbox-close svg {
|
|
width: 18px;
|
|
height: 18px;
|
|
stroke: white;
|
|
stroke-width: 2;
|
|
}
|
|
|
|
.lightbox-nav {
|
|
position: fixed;
|
|
top: 50%;
|
|
transform: translateY(-50%);
|
|
width: 50px;
|
|
height: 50px;
|
|
border-radius: 50%;
|
|
background: rgba(255, 255, 255, 0.12);
|
|
backdrop-filter: blur(20px);
|
|
-webkit-backdrop-filter: blur(20px);
|
|
border: 1px solid rgba(255, 255, 255, 0.1);
|
|
cursor: pointer;
|
|
display: flex;
|
|
align-items: center;
|
|
justify-content: center;
|
|
transition: all var(--duration-fast) var(--ease-out);
|
|
z-index: 10;
|
|
}
|
|
|
|
.lightbox-nav:hover {
|
|
background: rgba(255, 255, 255, 0.2);
|
|
transform: translateY(-50%) scale(1.05);
|
|
}
|
|
|
|
.lightbox-nav:active {
|
|
transform: translateY(-50%) scale(0.95);
|
|
}
|
|
|
|
.lightbox-nav svg {
|
|
width: 22px;
|
|
height: 22px;
|
|
stroke: white;
|
|
stroke-width: 2;
|
|
}
|
|
|
|
.lightbox-prev { left: 20px; }
|
|
.lightbox-next { right: 20px; }
|
|
|
|
/* Zoom hint toast */
|
|
.zoom-hint {
|
|
position: fixed;
|
|
top: 80px;
|
|
left: 50%;
|
|
transform: translateX(-50%);
|
|
padding: 10px 16px;
|
|
background: rgba(0, 0, 0, 0.75);
|
|
backdrop-filter: blur(10px);
|
|
border-radius: 10px;
|
|
font-size: 13px;
|
|
color: white;
|
|
opacity: 0;
|
|
transition: opacity 0.3s ease;
|
|
pointer-events: none;
|
|
z-index: 20;
|
|
}
|
|
|
|
.zoom-hint.visible {
|
|
opacity: 1;
|
|
}
|
|
|
|
/* ─── Utilities ─── */
|
|
.hidden { display: none !important; }
|
|
|
|
/* ─── Mobile Responsive ─── */
|
|
@media (max-width: 960px) {
|
|
:root {
|
|
--sidebar-width: 240px;
|
|
}
|
|
.content-wrapper {
|
|
padding: var(--space-8) var(--space-5);
|
|
}
|
|
}
|
|
|
|
@media (max-width: 768px) {
|
|
.sidebar {
|
|
position: relative;
|
|
width: 100%;
|
|
height: auto;
|
|
border-right: none;
|
|
border-bottom: 1px solid var(--color-separator);
|
|
}
|
|
.main {
|
|
margin-left: 0;
|
|
}
|
|
.layout {
|
|
flex-direction: column;
|
|
}
|
|
.content h1 {
|
|
font-size: 28px;
|
|
}
|
|
.content h2 {
|
|
font-size: 22px;
|
|
}
|
|
.diagrams-grid {
|
|
grid-template-columns: 1fr;
|
|
}
|
|
}
|
|
|
|
/* ─── Print ─── */
|
|
@media print {
|
|
.header, .sidebar {
|
|
display: none;
|
|
}
|
|
.main {
|
|
margin-left: 0;
|
|
}
|
|
.content-wrapper {
|
|
max-width: none;
|
|
padding: 0;
|
|
}
|
|
}
|
|
</style>
|
|
</head>
|
|
<body>
|
|
<header class="header">
|
|
<div class="header-logo">
|
|
<svg viewBox="0 0 24 24" fill="currentColor">
|
|
<path d="M12 2L2 7l10 5 10-5-10-5zM2 17l10 5 10-5M2 12l10 5 10-5"/>
|
|
</svg>
|
|
<span class="header-title">Academia</span>
|
|
</div>
|
|
<div class="header-divider"></div>
|
|
<span class="header-subtitle">Documentación del Sistema</span>
|
|
</header>
|
|
|
|
<div class="layout">
|
|
<aside class="sidebar">
|
|
<div class="search-wrap">
|
|
<div class="search-box">
|
|
<input type="text" id="search" placeholder="Buscar documentos...">
|
|
</div>
|
|
</div>
|
|
<nav id="nav"></nav>
|
|
</aside>
|
|
|
|
<main class="main">
|
|
<div class="content-wrapper">
|
|
<article class="content" id="content">
|
|
<div class="welcome">
|
|
<div class="welcome-icon">📚</div>
|
|
<h1>Documentación</h1>
|
|
<p>Selecciona un documento del menú lateral para explorar la documentación del sistema.</p>
|
|
</div>
|
|
</article>
|
|
</div>
|
|
</main>
|
|
</div>
|
|
|
|
<!-- Lightbox Modal with Zoom & Pan -->
|
|
<div class="lightbox" id="lightbox">
|
|
<!-- Top Toolbar -->
|
|
<div class="lightbox-toolbar">
|
|
<button onclick="zoomOut()" id="btn-zoom-out" aria-label="Alejar">
|
|
<svg viewBox="0 0 24 24"><path d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0zM13 10H7" stroke-linecap="round"/></svg>
|
|
</button>
|
|
<span class="zoom-level" id="zoom-level">100%</span>
|
|
<button onclick="zoomIn()" id="btn-zoom-in" aria-label="Acercar">
|
|
<svg viewBox="0 0 24 24"><path d="M21 21l-6-6m2-5a7 7 0 11-14 0 7 7 0 0114 0zM10 7v6m3-3H7" stroke-linecap="round"/></svg>
|
|
</button>
|
|
<div class="divider"></div>
|
|
<button onclick="resetZoom()" aria-label="Restablecer zoom">
|
|
<svg viewBox="0 0 24 24"><path d="M4 4v5h5M20 20v-5h-5M4 9a8 8 0 0114-5.3M20 15a8 8 0 01-14 5.3" stroke-linecap="round" stroke-linejoin="round"/></svg>
|
|
</button>
|
|
<button onclick="fitToScreen()" aria-label="Ajustar a pantalla">
|
|
<svg viewBox="0 0 24 24"><path d="M8 3H5a2 2 0 00-2 2v3m18 0V5a2 2 0 00-2-2h-3m0 18h3a2 2 0 002-2v-3M3 16v3a2 2 0 002 2h3" stroke-linecap="round" stroke-linejoin="round"/></svg>
|
|
</button>
|
|
</div>
|
|
|
|
<!-- Close Button -->
|
|
<button class="lightbox-close" onclick="closeLightbox()" aria-label="Cerrar">
|
|
<svg viewBox="0 0 24 24" fill="none">
|
|
<path d="M18 6L6 18M6 6l12 12" stroke-linecap="round"/>
|
|
</svg>
|
|
</button>
|
|
|
|
<!-- Navigation -->
|
|
<button class="lightbox-nav lightbox-prev" onclick="navigateLightbox(-1)" aria-label="Anterior">
|
|
<svg viewBox="0 0 24 24" fill="none">
|
|
<path d="M15 18l-6-6 6-6" stroke-linecap="round" stroke-linejoin="round"/>
|
|
</svg>
|
|
</button>
|
|
<button class="lightbox-nav lightbox-next" onclick="navigateLightbox(1)" aria-label="Siguiente">
|
|
<svg viewBox="0 0 24 24" fill="none">
|
|
<path d="M9 18l6-6-6-6" stroke-linecap="round" stroke-linejoin="round"/>
|
|
</svg>
|
|
</button>
|
|
|
|
<!-- Image Viewport (for pan & zoom) -->
|
|
<div class="lightbox-viewport" id="lightbox-viewport">
|
|
<div class="lightbox-content" id="lightbox-content">
|
|
<img id="lightbox-img" src="" alt="" draggable="false">
|
|
</div>
|
|
</div>
|
|
|
|
<!-- Bottom Info Bar -->
|
|
<div class="lightbox-info">
|
|
<span class="lightbox-title" id="lightbox-title"></span>
|
|
<span class="lightbox-counter" id="lightbox-counter"></span>
|
|
</div>
|
|
|
|
<!-- Zoom Hint Toast -->
|
|
<div class="zoom-hint" id="zoom-hint">Usa la rueda del ratón o pellizca para hacer zoom</div>
|
|
</div>
|
|
|
|
<script>
|
|
// ═══════════════════════════════════════════════════════════
|
|
// EMBEDDED DOCUMENTS & SVG DIAGRAMS
|
|
// ═══════════════════════════════════════════════════════════
|
|
|
|
const diagrams = {
|
|
HTMLHEAD
|
|
|
|
# Embeber SVGs como base64
|
|
echo "Embebiendo diagramas SVG..."
|
|
echo ' "01-use-cases": "'"$(svg_to_base64 "docs/architecture/diagrams/01-use-cases.svg")"'",' >> index.html
|
|
echo ' "02-domain-model": "'"$(svg_to_base64 "docs/architecture/diagrams/02-domain-model.svg")"'",' >> index.html
|
|
echo ' "03-sequence-enrollment": "'"$(svg_to_base64 "docs/architecture/diagrams/03-sequence-enrollment.svg")"'",' >> index.html
|
|
echo ' "04-components": "'"$(svg_to_base64 "docs/architecture/diagrams/04-components.svg")"'",' >> index.html
|
|
echo ' "05-entity-relationship": "'"$(svg_to_base64 "docs/architecture/diagrams/05-entity-relationship.svg")"'",' >> index.html
|
|
echo ' "06-state-enrollment": "'"$(svg_to_base64 "docs/architecture/diagrams/06-state-enrollment.svg")"'",' >> index.html
|
|
echo ' "07-deployment": "'"$(svg_to_base64 "docs/architecture/diagrams/07-deployment.svg")"'",' >> index.html
|
|
echo ' "08-c4-context": "'"$(svg_to_base64 "docs/architecture/diagrams/08-c4-context.svg")"'",' >> index.html
|
|
|
|
cat >> index.html << 'HTMLMID'
|
|
};
|
|
|
|
const diagramNames = {
|
|
"01-use-cases": "Casos de Uso",
|
|
"02-domain-model": "Modelo de Dominio",
|
|
"03-sequence-enrollment": "Secuencia: Inscripción",
|
|
"04-components": "Componentes",
|
|
"05-entity-relationship": "Entidad-Relación",
|
|
"06-state-enrollment": "Estados: Inscripción",
|
|
"07-deployment": "Despliegue",
|
|
"08-c4-context": "Contexto C4"
|
|
};
|
|
|
|
const diagramKeys = Object.keys(diagramNames);
|
|
let currentDiagramIndex = 0;
|
|
|
|
// ═══════════════════════════════════════════════════════════
|
|
// LIGHTBOX WITH ZOOM & PAN (Apple-style)
|
|
// ═══════════════════════════════════════════════════════════
|
|
|
|
// Zoom state
|
|
let currentZoom = 1;
|
|
let panX = 0;
|
|
let panY = 0;
|
|
let isDragging = false;
|
|
let startX = 0;
|
|
let startY = 0;
|
|
let lastPanX = 0;
|
|
let lastPanY = 0;
|
|
|
|
const MIN_ZOOM = 0.5;
|
|
const MAX_ZOOM = 5;
|
|
const ZOOM_STEP = 0.25;
|
|
|
|
function openLightbox(key) {
|
|
const lightbox = document.getElementById('lightbox');
|
|
const img = document.getElementById('lightbox-img');
|
|
const title = document.getElementById('lightbox-title');
|
|
const counter = document.getElementById('lightbox-counter');
|
|
|
|
currentDiagramIndex = diagramKeys.indexOf(key);
|
|
img.src = diagrams[key];
|
|
title.textContent = diagramNames[key] || key;
|
|
counter.textContent = `${currentDiagramIndex + 1} / ${diagramKeys.length}`;
|
|
|
|
// Reset zoom and pan
|
|
resetZoom();
|
|
|
|
lightbox.classList.add('active');
|
|
document.body.style.overflow = 'hidden';
|
|
|
|
// Show zoom hint briefly
|
|
showZoomHint();
|
|
}
|
|
|
|
function closeLightbox() {
|
|
const lightbox = document.getElementById('lightbox');
|
|
lightbox.classList.remove('active');
|
|
document.body.style.overflow = '';
|
|
resetZoom();
|
|
}
|
|
|
|
function navigateLightbox(direction) {
|
|
currentDiagramIndex = (currentDiagramIndex + direction + diagramKeys.length) % diagramKeys.length;
|
|
const key = diagramKeys[currentDiagramIndex];
|
|
document.getElementById('lightbox-img').src = diagrams[key];
|
|
document.getElementById('lightbox-title').textContent = diagramNames[key] || key;
|
|
document.getElementById('lightbox-counter').textContent = `${currentDiagramIndex + 1} / ${diagramKeys.length}`;
|
|
resetZoom();
|
|
}
|
|
|
|
// Zoom functions
|
|
function setZoom(newZoom, centerX, centerY) {
|
|
const oldZoom = currentZoom;
|
|
currentZoom = Math.max(MIN_ZOOM, Math.min(MAX_ZOOM, newZoom));
|
|
|
|
// Adjust pan to zoom towards cursor position
|
|
if (centerX !== undefined && centerY !== undefined) {
|
|
const viewport = document.getElementById('lightbox-viewport');
|
|
const rect = viewport.getBoundingClientRect();
|
|
const relX = centerX - rect.left - rect.width / 2;
|
|
const relY = centerY - rect.top - rect.height / 2;
|
|
|
|
const scale = currentZoom / oldZoom;
|
|
panX = relX - (relX - panX) * scale;
|
|
panY = relY - (relY - panY) * scale;
|
|
}
|
|
|
|
updateTransform();
|
|
updateZoomUI();
|
|
}
|
|
|
|
function zoomIn() {
|
|
setZoom(currentZoom + ZOOM_STEP);
|
|
}
|
|
|
|
function zoomOut() {
|
|
setZoom(currentZoom - ZOOM_STEP);
|
|
}
|
|
|
|
function resetZoom() {
|
|
currentZoom = 1;
|
|
panX = 0;
|
|
panY = 0;
|
|
updateTransform();
|
|
updateZoomUI();
|
|
}
|
|
|
|
function fitToScreen() {
|
|
currentZoom = 1;
|
|
panX = 0;
|
|
panY = 0;
|
|
updateTransform();
|
|
updateZoomUI();
|
|
}
|
|
|
|
function updateTransform() {
|
|
const content = document.getElementById('lightbox-content');
|
|
const viewport = document.getElementById('lightbox-viewport');
|
|
|
|
// Constrain pan when zoomed out
|
|
if (currentZoom <= 1) {
|
|
panX = 0;
|
|
panY = 0;
|
|
viewport.classList.add('zoomed-out');
|
|
} else {
|
|
viewport.classList.remove('zoomed-out');
|
|
}
|
|
|
|
content.style.transform = `translate(${panX}px, ${panY}px) scale(${currentZoom})`;
|
|
}
|
|
|
|
function updateZoomUI() {
|
|
document.getElementById('zoom-level').textContent = Math.round(currentZoom * 100) + '%';
|
|
document.getElementById('btn-zoom-in').disabled = currentZoom >= MAX_ZOOM;
|
|
document.getElementById('btn-zoom-out').disabled = currentZoom <= MIN_ZOOM;
|
|
}
|
|
|
|
function showZoomHint() {
|
|
const hint = document.getElementById('zoom-hint');
|
|
hint.classList.add('visible');
|
|
setTimeout(() => hint.classList.remove('visible'), 3000);
|
|
}
|
|
|
|
// Mouse wheel zoom
|
|
document.getElementById('lightbox-viewport')?.addEventListener('wheel', (e) => {
|
|
e.preventDefault();
|
|
const delta = e.deltaY > 0 ? -ZOOM_STEP : ZOOM_STEP;
|
|
setZoom(currentZoom + delta, e.clientX, e.clientY);
|
|
}, { passive: false });
|
|
|
|
// Pan with mouse drag
|
|
const viewport = document.getElementById('lightbox-viewport');
|
|
|
|
viewport?.addEventListener('mousedown', (e) => {
|
|
if (currentZoom <= 1) return;
|
|
isDragging = true;
|
|
startX = e.clientX;
|
|
startY = e.clientY;
|
|
lastPanX = panX;
|
|
lastPanY = panY;
|
|
viewport.classList.add('dragging');
|
|
document.getElementById('lightbox-content').classList.add('no-transition');
|
|
});
|
|
|
|
document.addEventListener('mousemove', (e) => {
|
|
if (!isDragging) return;
|
|
panX = lastPanX + (e.clientX - startX);
|
|
panY = lastPanY + (e.clientY - startY);
|
|
updateTransform();
|
|
});
|
|
|
|
document.addEventListener('mouseup', () => {
|
|
if (isDragging) {
|
|
isDragging = false;
|
|
viewport?.classList.remove('dragging');
|
|
document.getElementById('lightbox-content')?.classList.remove('no-transition');
|
|
}
|
|
});
|
|
|
|
// Touch support for pinch zoom and pan
|
|
let lastTouchDistance = 0;
|
|
let lastTouchCenter = { x: 0, y: 0 };
|
|
|
|
viewport?.addEventListener('touchstart', (e) => {
|
|
if (e.touches.length === 2) {
|
|
lastTouchDistance = getTouchDistance(e.touches);
|
|
lastTouchCenter = getTouchCenter(e.touches);
|
|
} else if (e.touches.length === 1 && currentZoom > 1) {
|
|
isDragging = true;
|
|
startX = e.touches[0].clientX;
|
|
startY = e.touches[0].clientY;
|
|
lastPanX = panX;
|
|
lastPanY = panY;
|
|
document.getElementById('lightbox-content').classList.add('no-transition');
|
|
}
|
|
}, { passive: true });
|
|
|
|
viewport?.addEventListener('touchmove', (e) => {
|
|
if (e.touches.length === 2) {
|
|
e.preventDefault();
|
|
const newDistance = getTouchDistance(e.touches);
|
|
const newCenter = getTouchCenter(e.touches);
|
|
const scale = newDistance / lastTouchDistance;
|
|
setZoom(currentZoom * scale, newCenter.x, newCenter.y);
|
|
lastTouchDistance = newDistance;
|
|
lastTouchCenter = newCenter;
|
|
} else if (e.touches.length === 1 && isDragging) {
|
|
panX = lastPanX + (e.touches[0].clientX - startX);
|
|
panY = lastPanY + (e.touches[0].clientY - startY);
|
|
updateTransform();
|
|
}
|
|
}, { passive: false });
|
|
|
|
viewport?.addEventListener('touchend', () => {
|
|
isDragging = false;
|
|
lastTouchDistance = 0;
|
|
document.getElementById('lightbox-content')?.classList.remove('no-transition');
|
|
});
|
|
|
|
function getTouchDistance(touches) {
|
|
const dx = touches[0].clientX - touches[1].clientX;
|
|
const dy = touches[0].clientY - touches[1].clientY;
|
|
return Math.sqrt(dx * dx + dy * dy);
|
|
}
|
|
|
|
function getTouchCenter(touches) {
|
|
return {
|
|
x: (touches[0].clientX + touches[1].clientX) / 2,
|
|
y: (touches[0].clientY + touches[1].clientY) / 2
|
|
};
|
|
}
|
|
|
|
// Double-click to toggle zoom
|
|
viewport?.addEventListener('dblclick', (e) => {
|
|
if (currentZoom === 1) {
|
|
setZoom(2, e.clientX, e.clientY);
|
|
} else {
|
|
resetZoom();
|
|
}
|
|
});
|
|
|
|
// Keyboard navigation
|
|
document.addEventListener('keydown', (e) => {
|
|
const lightbox = document.getElementById('lightbox');
|
|
if (!lightbox.classList.contains('active')) return;
|
|
|
|
switch(e.key) {
|
|
case 'Escape': closeLightbox(); break;
|
|
case 'ArrowLeft': navigateLightbox(-1); break;
|
|
case 'ArrowRight': navigateLightbox(1); break;
|
|
case '+': case '=': zoomIn(); break;
|
|
case '-': zoomOut(); break;
|
|
case '0': resetZoom(); break;
|
|
}
|
|
});
|
|
|
|
const docs = {
|
|
HTMLMID
|
|
|
|
# Función para agregar documento
|
|
add_doc() {
|
|
local name="$1"
|
|
local file="$2"
|
|
if [[ -f "$file" ]]; then
|
|
local content=$(cat "$file" | escape_js)
|
|
echo " \"$name\": \`$content\`," >> index.html
|
|
fi
|
|
}
|
|
|
|
echo "Embebiendo documentos..."
|
|
|
|
# Inicio
|
|
echo ' "Inicio": {' >> index.html
|
|
add_doc "README" "README.md"
|
|
add_doc "Entregables" "docs/ENTREGABLES.md"
|
|
add_doc "Prueba Técnica" "docs/Prueba Técnica.md"
|
|
echo ' },' >> index.html
|
|
|
|
# Análisis
|
|
echo ' "Análisis": {' >> index.html
|
|
add_doc "AN-001 Requisitos Funcionales" "docs/entregables/01-analisis/requisitos/AN-001-requisitos-funcionales.md"
|
|
add_doc "AN-002 Reglas de Negocio" "docs/entregables/01-analisis/reglas-negocio/AN-002-reglas-negocio.md"
|
|
add_doc "AN-003 Historias de Usuario" "docs/entregables/01-analisis/historias-usuario/AN-003-historias-usuario.md"
|
|
add_doc "AN-004 Requisitos No Funcionales" "docs/entregables/01-analisis/requisitos/AN-004-requisitos-no-funcionales.md"
|
|
add_doc "AN-005 Riesgos Técnicos" "docs/entregables/01-analisis/riesgos/AN-005-riesgos-tecnicos.md"
|
|
echo ' },' >> index.html
|
|
|
|
# Diseño
|
|
echo ' "Diseño": {' >> index.html
|
|
add_doc "DI-001 Arquitectura Backend" "docs/entregables/02-diseno/arquitectura/DI-001-arquitectura-backend.md"
|
|
add_doc "DI-002 Modelo de Dominio" "docs/entregables/02-diseno/modelo-dominio/DI-002-modelo-dominio.md"
|
|
add_doc "DI-003 Diseño Base de Datos" "docs/entregables/02-diseno/base-datos/DI-003-diseno-base-datos.md"
|
|
add_doc "DI-004 Esquema GraphQL" "docs/entregables/02-diseno/esquema-graphql/DI-004-esquema-graphql.md"
|
|
add_doc "DI-005 Arquitectura Frontend" "docs/entregables/02-diseno/arquitectura/DI-005-arquitectura-frontend.md"
|
|
add_doc "DI-006 Componentes UI" "docs/entregables/02-diseno/componentes-ui/DI-006-componentes-ui.md"
|
|
add_doc "DI-007 Contratos DTOs" "docs/entregables/02-diseno/esquema-graphql/DI-007-contratos-dtos.md"
|
|
add_doc "DI-008 Manejo de Errores" "docs/entregables/02-diseno/arquitectura/DI-008-manejo-errores.md"
|
|
echo ' },' >> index.html
|
|
|
|
# Configuración
|
|
echo ' "Configuración": {' >> index.html
|
|
add_doc "DV-001 Configuración Repositorio" "docs/entregables/03-configuracion/DV-001-configuracion-repositorio.md"
|
|
add_doc "DV-002 Configuración .NET" "docs/entregables/03-configuracion/DV-002-configuracion-dotnet.md"
|
|
add_doc "DV-003 Configuración Angular" "docs/entregables/03-configuracion/DV-003-configuracion-angular.md"
|
|
add_doc "DV-004 Configuración Base Datos" "docs/entregables/03-configuracion/DV-004-configuracion-base-datos.md"
|
|
add_doc "DV-005 Variables de Entorno" "docs/entregables/03-configuracion/DV-005-variables-entorno.md"
|
|
add_doc "DV-006 Herramientas de Calidad" "docs/entregables/03-configuracion/DV-006-herramientas-calidad.md"
|
|
echo ' },' >> index.html
|
|
|
|
# Arquitectura
|
|
echo ' "Arquitectura": {' >> index.html
|
|
add_doc "ADR-001 Clean Architecture" "docs/architecture/decisions/ADR-001-clean-architecture.md"
|
|
add_doc "ADR-002 GraphQL vs REST" "docs/architecture/decisions/ADR-002-graphql-vs-rest.md"
|
|
add_doc "ADR-003 Angular Signals" "docs/architecture/decisions/ADR-003-angular-signals.md"
|
|
add_doc "ADR-004 Validation Strategy" "docs/architecture/decisions/ADR-004-validation-strategy.md"
|
|
echo ' },' >> index.html
|
|
|
|
# Despliegue
|
|
echo ' "Despliegue": {' >> index.html
|
|
add_doc "Manual de Despliegue" "docs/DEPLOYMENT.md"
|
|
add_doc "Plan de Actividades" "docs/PLAN_ACTIVIDADES.md"
|
|
echo ' },' >> index.html
|
|
|
|
# Calidad
|
|
echo ' "Calidad": {' >> index.html
|
|
add_doc "Code Review Checklist" "docs/CODE_REVIEW_CHECKLIST.md"
|
|
add_doc "OWASP Checklist" "docs/OWASP_CHECKLIST.md"
|
|
add_doc "Recomendaciones" "docs/RECOMMENDATIONS.md"
|
|
add_doc "Defectos QA" "docs/DEFECTOS_QA.md"
|
|
echo ' },' >> index.html
|
|
|
|
# Cerrar docs y continuar con el script
|
|
cat >> index.html << 'HTMLFOOT'
|
|
};
|
|
|
|
// ═══════════════════════════════════════════════════════════
|
|
// APP LOGIC
|
|
// ═══════════════════════════════════════════════════════════
|
|
|
|
marked.setOptions({ gfm: true, breaks: true });
|
|
|
|
// Build navigation
|
|
function buildNav() {
|
|
const nav = document.getElementById('nav');
|
|
let html = '';
|
|
|
|
// Add diagrams section first
|
|
html += `
|
|
<div class="nav-group open" data-group="Diagramas">
|
|
<div class="nav-group-title" onclick="toggleGroup(this.parentElement)">
|
|
<svg class="chevron" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5">
|
|
<path d="M9 18l6-6-6-6"/>
|
|
</svg>
|
|
Diagramas UML
|
|
</div>
|
|
<div class="nav-list">
|
|
<div class="nav-item" data-key="diagrams:all" onclick="showDiagrams()">Ver todos los diagramas</div>
|
|
</div>
|
|
</div>
|
|
`;
|
|
|
|
// Add document sections
|
|
for (const [section, items] of Object.entries(docs)) {
|
|
if (typeof items !== 'object') continue;
|
|
|
|
html += `
|
|
<div class="nav-group" data-group="${section}">
|
|
<div class="nav-group-title" onclick="toggleGroup(this.parentElement)">
|
|
<svg class="chevron" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5">
|
|
<path d="M9 18l6-6-6-6"/>
|
|
</svg>
|
|
${section}
|
|
</div>
|
|
<div class="nav-list">
|
|
`;
|
|
|
|
for (const name of Object.keys(items)) {
|
|
html += `<div class="nav-item" data-key="${section}|${name}" onclick="loadDoc('${section}', '${name}', this)">${name}</div>`;
|
|
}
|
|
|
|
html += `</div></div>`;
|
|
}
|
|
|
|
nav.innerHTML = html;
|
|
}
|
|
|
|
function toggleGroup(el) {
|
|
el.classList.toggle('open');
|
|
}
|
|
|
|
// Show all diagrams in a gallery
|
|
function showDiagrams() {
|
|
const content = document.getElementById('content');
|
|
document.querySelectorAll('.nav-item').forEach(el => el.classList.remove('active'));
|
|
document.querySelector('[data-key="diagrams:all"]')?.classList.add('active');
|
|
|
|
let html = `
|
|
<h1>Diagramas de Arquitectura</h1>
|
|
<p>Diagramas UML que documentan la arquitectura y diseño del sistema.</p>
|
|
<div class="diagrams-grid">
|
|
`;
|
|
|
|
for (const [key, src] of Object.entries(diagrams)) {
|
|
if (!src) continue;
|
|
const name = diagramNames[key] || key;
|
|
html += `
|
|
<div class="diagram-card" onclick="showSingleDiagram('${key}')">
|
|
<img src="${src}" alt="${name}" loading="lazy">
|
|
<div class="diagram-title">${name}</div>
|
|
</div>
|
|
`;
|
|
}
|
|
|
|
html += '</div>';
|
|
content.innerHTML = html;
|
|
window.scrollTo(0, 0);
|
|
window.location.hash = 'diagrams';
|
|
}
|
|
|
|
// Show single diagram in lightbox (full size)
|
|
function showSingleDiagram(key) {
|
|
openLightbox(key);
|
|
}
|
|
|
|
// Load document
|
|
function loadDoc(section, name, navItem) {
|
|
const content = document.getElementById('content');
|
|
|
|
document.querySelectorAll('.nav-item').forEach(el => el.classList.remove('active'));
|
|
navItem?.classList.add('active');
|
|
|
|
try {
|
|
let markdown = docs[section]?.[name];
|
|
if (!markdown) throw new Error('Documento no encontrado');
|
|
|
|
// Replace SVG links with embedded images
|
|
markdown = markdown.replace(
|
|
/\[([^\]]+)\]\(docs\/architecture\/diagrams\/(\d+-[^)]+\.svg)\)/g,
|
|
(match, text, file) => {
|
|
const key = file.replace('.svg', '');
|
|
if (diagrams[key]) {
|
|
return ``;
|
|
}
|
|
return match;
|
|
}
|
|
);
|
|
|
|
content.innerHTML = marked.parse(markdown);
|
|
window.scrollTo(0, 0);
|
|
window.location.hash = encodeURIComponent(`${section}|${name}`);
|
|
} catch (error) {
|
|
content.innerHTML = `<div class="welcome"><h1>Error</h1><p>${error.message}</p></div>`;
|
|
}
|
|
}
|
|
|
|
// Search
|
|
document.getElementById('search').addEventListener('input', (e) => {
|
|
const query = e.target.value.toLowerCase();
|
|
document.querySelectorAll('.nav-item').forEach(item => {
|
|
const matches = item.textContent.toLowerCase().includes(query);
|
|
item.classList.toggle('hidden', !matches && query);
|
|
});
|
|
document.querySelectorAll('.nav-group').forEach(group => {
|
|
const hasVisible = group.querySelector('.nav-item:not(.hidden)');
|
|
group.classList.toggle('hidden', !hasVisible && query);
|
|
if (hasVisible && query) group.classList.add('open');
|
|
});
|
|
});
|
|
|
|
// Init
|
|
buildNav();
|
|
|
|
// Load from hash
|
|
if (window.location.hash) {
|
|
const hash = decodeURIComponent(window.location.hash.slice(1));
|
|
if (hash === 'diagrams') {
|
|
showDiagrams();
|
|
} else {
|
|
const [section, name] = hash.split('|');
|
|
if (docs[section]?.[name]) {
|
|
const navItem = document.querySelector(`[data-key="${hash}"]`);
|
|
navItem?.closest('.nav-group')?.classList.add('open');
|
|
loadDoc(section, name, navItem);
|
|
}
|
|
}
|
|
}
|
|
</script>
|
|
</body>
|
|
</html>
|
|
HTMLFOOT
|
|
|
|
# Estadísticas finales
|
|
SIZE=$(wc -c < index.html | tr -d ' ')
|
|
SIZE_KB=$((SIZE / 1024))
|
|
echo ""
|
|
echo "✅ index.html generado exitosamente"
|
|
echo " Tamaño: ${SIZE_KB} KB (${SIZE} bytes)"
|
|
echo " Diagramas: 8 SVGs embebidos"
|
|
echo " Documentos: $(find docs -name "*.md" -not -path "*/qa/*" | wc -l) archivos MD"
|
|
echo ""
|
|
echo "🌐 Abre index.html en tu navegador"
|