Diseña una organización SAFe entera, valídala contra los guardrails del marco y expórtala como JSON versionable — en un único archivo HTML, sin build, sin backend y sin dependencias.
▶ Probar la aplicación · Página del proyecto · English · Contribuir · Seguridad
Montar un Agile Release Train en una pizarra es fácil. Descubrir tres meses después que tenía catorce equipos, ningún System Architect asignado y que el 80% de las transiciones de su value stream cambian de responsable, ya no lo es.
Esta herramienta invierte el orden: modelas el diseño y el diseño te responde. Cada nodo que colocas, cada paso que encadenas y cada celda que marcas en la matriz se evalúa en tiempo real contra los rangos de referencia de SAFe —tamaño de equipo, número de trenes, cobertura de roles obligatorios, presupuesto Lean, densidad de handoffs— y el resultado se condensa en un Health Score y una lista de hallazgos accionables, cada uno enlazado al elemento que lo provoca.
El flujo y la organización son dos modelos independientes que puedes diseñar en cualquier orden. Los handoffs, que son donde se va el tiempo, solo aparecen al cruzarlos.
Todo ocurre en tu navegador. No hay servidor, no hay cuenta, no hay telemetría.
- Qué hace
- Arquitectura
- Instalación rápida
- Uso
- Formato del blueprint
- Personalización de marca
- Atajos de teclado
- Roadmap 2026
- Contribuir
- Aviso legal
- Licencia
SAFe sostiene organize around value, y su taller de identificación de value streams sigue un orden concreto: value streams operativos → sistemas → personas → development value streams → diseño de ARTs. La organización es la variable dependiente, no el punto de partida.
Esta herramienta respeta ese orden. Por eso el flujo y la organización son dos modelos con identidad propia: puedes diseñar un value stream entero —pasos, sistemas, retrabajos— sin haber creado un solo equipo, y puedes montar la organización sin describir ningún flujo. Las tres vistas de la aplicación son esos dos modelos y su cruce.
| Vista | Atajo | Qué contiene |
|---|---|---|
| Organización | 1 |
Quién está en qué equipo, en qué tren y con qué roles |
| Flujo | 2 |
Cómo se mueve el valor, de disparador a valor recibido |
| Alineación | 3 |
Quién ejecuta cada paso. Los handoffs solo existen aquí |
Dos tipos de value stream, cada uno con sus sistemas y su secuencia de pasos:
| Tipo | Qué describe | Campos propios |
|---|---|---|
Operational Value Stream (ovs) |
Cómo la empresa entrega valor a un cliente, de disparador a valor recibido | trigger, value |
Development Value Stream (dvs) |
Las personas y sistemas que construyen las soluciones que usa el flujo operativo | supports (qué OVS sostiene), pareja con un nodo organizativo |
Cada paso puede marcarse como retrabajo: la vuelta atrás en el flujo. Los sistemas se declaran una vez y se comparten entre streams, porque en la realidad también se comparten.
El asistente parte de dos plantillas editables —siete pasos cada una— que son el esqueleto habitual de cada tipo:
OVS Solicitud · Cualificación · Preparación · Ejecución · Entrega · Seguimiento · Cierre
DVS Análisis y refinamiento · Diseño de solución · Construcción · Integración ·
Validación · Despliegue · Medición y aprendizaje
Cambia entre configuraciones y el lienzo, la paleta y el diagnóstico se adaptan a los niveles disponibles en cada una:
| Configuración | Niveles incluidos | Clave interna |
|---|---|---|
| Essential SAFe | ART + Agile Teams | essential |
| Large Solution SAFe | Solution Train + ARTs + Teams + Suppliers | largeSolution |
| Portfolio SAFe | Portfolio + Development Value Streams + ARTs + Teams | portfolio |
| Full SAFe | Todos los niveles | full |
Cada uno con su catálogo de roles obligatorios y opcionales, sus artefactos de referencia y sus reglas de anidamiento:
| Nodo | Nivel SAFe | Acepta como hijo | Roles obligatorios |
|---|---|---|---|
| Portfolio | Portfolio Level | Development Value Stream | LPM, Epic Owners, Enterprise Architect |
| Development Value Stream | Portfolio Level | Solution Train, ART | Value Stream Coordination |
| Solution Train | Large Solution Level | ART, Supplier | STE, Solution Management, Solution Architect |
| Agile Release Train | Essential Level | Agile Team | RTE, Product Management, System Architect, Business Owners |
| Agile Team | Team Level | — | Product Owner, Scrum Master / Team Coach |
| Supplier | Large Solution Level | — | Responsable de relación |
La jerarquía se valida en tiempo real: cada nodo solo acepta los hijos que el marco permite, tanto al añadir desde la paleta como al arrastrar y soltar en el lienzo.
Los Agile Teams se tipifican además según Team Topologies (Stream-aligned, Complicated Subsystem, Platform, Enabling, System Team, Shared Services) y su método de ejecución (SAFe Scrum, SAFe Team Kanban o híbrido).
Una matriz que cruza los pasos de cada value stream con los componentes de la organización. Pulsar una celda cicla entre cuatro roles y el vacío:
| Rol | Sigla | Qué significa |
|---|---|---|
| Responsable | R |
Ejecuta el paso y responde de él. Define la frontera para contar handoffs |
| Contribuye | C |
Aporta trabajo al paso sin responder de su resultado |
| Aprueba | A |
Puerta de decisión. Estructuralmente es una cola: el trabajo espera |
| Espera a | E |
El paso queda bloqueado esperando a este componente. Cola pura |
Qué componentes pueden ejecutar qué es data, no código: un OVS admite cualquier nivel, incluido Portfolio; un DVS admite de Development Value Stream para abajo.
Los handoffs solo existen aquí. Un handoff es un cambio de responsable entre dos pasos consecutivos: no está en el modelo de flujo ni en el organigrama por separado, aparece únicamente al cruzarlos. Si además uno de los dos pasos es retrabajo, el handoff cuenta como grave y pesa doble en la puntuación — volver atrás dentro del mismo equipo es fricción; volver atrás cambiando de responsable es cola con renegociación.
Las seis métricas de Flow de SAFe en cada nodo con capacidad de flujo, cada una con su rango saludable y su semáforo:
| Métrica | Unidad | Rango saludable | Valor por defecto |
|---|---|---|---|
| Flow Distribution | % | 50–80 | 60 |
| Flow Velocity | ítems / PI | > 0 | 24 |
| Flow Time | días | ≤ 45 | 28 |
| Flow Load | WIP | ≤ 30 | 18 |
| Flow Efficiency | % | ≥ 15 | 22 |
| Flow Predictability | % | 80–110 | 85 |
Son declaradas, y no puntúan. Estas cifras las escribe quien diseña, no las mide la herramienta: viajan en el export marcadas como
provenance: "declared"yscored: false. Generan hallazgos cuando salen de rango, pero el Health Score solo pondera lo que el diseño sostiene por sí mismo. El bloqueobserveddel export queda reservado para datos de ejecución reales.
Un motor de reglas recorre los dos modelos y su cruce, y devuelve hallazgos con severidad (warn / err) enlazados al elemento que los origina. Cada regla lleva identificador estable, de modo que el export se puede filtrar y volcar a un tracker sin transformación previa.
Organización — org.*
- Roles obligatorios sin asignar en cualquier nivel
- Equipos fuera del rango de 5–11 personas
- ARTs fuera de 5–12 equipos o de 50–125 personas (número de Dunbar)
- Solution Trains con un solo ART; Value Streams sin ningún ART
- Lean Budget sobreasignado o sin distribuir entre Value Streams
- Horizontes de inversión o Capacity Allocation que no suman 100%
Flujo — flow.*
- Value streams declarados pero sin ningún paso
- Flow Efficiency < 15%, Flow Predictability < 80%, o Flow Load por encima del doble del tamaño del equipo
Alineación — align.*
- Pasos sin nadie asignado, pasos con contribuidores pero sin responsable, y pasos con varios responsables a la vez
- Densidad de handoffs: qué porcentaje de las transiciones cambia de responsable
- Retrabajo que cruza frontera de responsable
- Value streams fragmentados entre varios ARTs, y ARTs que aparecen en demasiados flujos
- Puertas de decisión, que son colas contadas como tales
- Componentes de la organización sin ningún trabajo asignado
- Development Value Streams sin pareja organizativa, parejas que apuntan a un nodo ya borrado, Value Streams presupuestados sin flujo descrito y OVS que ningún DVS declara soportar
Todo se condensa en un número con esta ponderación:
healthScore = coberturaDeRoles × 0.30
+ integridadEstructural × 0.35
+ alineación × 0.35
Donde:
integridadEstructural = 100 − (errores × 22 + avisos × 9) / max(1, nodos / 3)
alineación = coberturaDePasos × 40 // pasos con un responsable claro
+ (1 − densidadHandoffs) × 30 // el handoff grave cuenta doble
+ (1 − fragmentación) × 20 // streams repartidos entre varios ARTs
+ (1 − ociosidad) × 10 // ARTs y equipos sin trabajo asignado
Si no hay modelo de flujo, la alineación no se puede calcular y su peso se reparte proporcionalmente entre los otros dos factores en lugar de puntuar cero. Un organigrama sin flujo no queda penalizado por algo que no ha declarado; simplemente se le puntúa lo que sí tiene.
- Bienvenida con cuatro puertas: empezar por el flujo, empezar por la organización, cargar un blueprint existente o ver un tour
- Dos tours guiados independientes de nueve pasos cada uno, uno para la organización y otro para el flujo
- Asistente de diseño bifurcado: la rama de flujo pregunta por los value streams operativos, los de desarrollo y los sistemas, y ofrece derivar la organización a partir de ellos. Su previsualización usa el mismo plan que la generación, así que lo que anuncia es lo que crea, handoffs incluidos
- Deshacer / rehacer con historial completo
- Importar y exportar el blueprint en JSON
- Exportar como SVG vectorial o PNG a 2×, siguiendo la vista activa
- Tema claro y oscuro, y modo presentación que pliega los paneles laterales
- Ejemplo precargado con organización y flujo: un Portfolio con su ART y tres equipos, un OVS de alta de cliente y un DVS que lo sostiene, ya alineados
La aplicación entera vive en app/index.html: ~5 900 líneas, ~313 KB, tres bloques (sprite SVG + CSS + JS) y ninguna dependencia de terceros más allá de dos familias tipográficas servidas por Google Fonts.
index.html Página de presentación del proyecto (bilingüe ES/EN)
app/index.html La aplicación. Es el entregable
Ambos archivos son autónomos: cada uno abre con doble clic y funciona desde file://.
flowchart TD
subgraph CAT["1 · Catálogo del framework"]
C1["CONFIGS<br/>4 configuraciones"]
C2["KINDS<br/>6 tipos de nodo:<br/>roles, artefactos, accepts/parent"]
C3["FLOW<br/>6 métricas + predicado good()"]
end
subgraph CORE["3-4 · Estado y operaciones"]
S["state<br/>config · root · selectedId<br/>doc · history · future"]
OPS["addNode · deleteNode<br/>duplicateNode · moveNode<br/>patch · ensureHost"]
H["snapshot / restore<br/>undo / redo"]
end
subgraph RULES["5 · Métricas y diagnóstico"]
R1["rollup()"]
R2["roleCoverage()"]
R3["diagnose() → findings[]"]
R4["healthScore()"]
end
subgraph VIEW["6-10 · Render"]
V["render()"]
V1["renderTree · renderPalette"]
V2["renderInspector · renderOverview"]
V3["renderStatus · renderLegend"]
end
subgraph IO["11 · Entrada / salida"]
E1["buildExport() → JSON"]
E2["buildSVG() → SVG / PNG"]
E3["importJSON() → rehydrate()"]
end
CAT --> CORE
CAT --> RULES
OPS --> S
S --> H
S --> RULES
RULES --> VIEW
S --> VIEW
V --> V1 & V2 & V3
S --> IO
E3 --> S
El JavaScript está seccionado y numerado dentro del propio archivo, lo que hace que cualquier diff sea fácil de situar:
| § | Sección | Responsabilidad |
|---|---|---|
| 1 | Catálogo del framework | CONFIGS, FLOW, KINDS, STREAMS, ASSIGN_ROLES, EXECUTORS, VIEWS, TEAM_TYPES, TEAM_METHODS, ORDER — todo el conocimiento de SAFe es data, no código |
| 2 | Utilidades | $, $$, esc, uid, clamp, formateadores Intl |
| 3 | Fábricas y estado | makeNode, makeStream, makeStep, makeSystem y el singleton state, con root (organización), flow y assignments como ramas independientes |
| 4 | Operaciones del modelo | Alta, baja, duplicado, movimiento y parcheo, tanto de nodos como de streams y pasos; recorrido del árbol |
| 5 | Métricas y diagnóstico | rollup, roleCoverage, analyzeStream, alignment, diagnose, healthParts / healthScore |
| 6–10 | Render | Cabecera, paleta, lienzo de organización, lienzo de flujo, matriz de alineación, inspector, barra de estado y el render() maestro |
| 11 | Exportación | buildExport (esquema 2.0), buildSVG para organigrama y flujo, exportJSON/SVG/PNG, importJSON, rehydrate |
| 12–13 | UI auxiliar y eventos | Toasts, modales, menús contextuales, bind() |
| 14 | Configuración precargada | seed() — el ejemplo de Essential SAFe con su flujo y sus asignaciones |
| 16–18 | Onboarding | Los dos tours (TOURS), el asistente bifurcado (WIZ) y su cableado |
| 19 | Arranque | seed(); bind(); render(); initOnboarding(); |
- El catálogo de SAFe es data. Añadir un rol o un artefacto es editar un objeto literal en la sección 1, no tocar la lógica. El render, el diagnóstico, el export y el asistente lo derivan todo de ahí. Lo mismo vale para qué componentes pueden ejecutar qué pasos: es la tabla
EXECUTORS, no una cadena de condicionales. - Flujo y organización no se contienen.
state.rootystate.flowson ramas hermanas, ystate.assignmentses la única que las relaciona. Cualquiera de las tres puede estar vacía sin romper a las otras dos, y por eso el Health Score sabe degradarse en lugar de puntuar cero. - Render completo, sin diffing.
render()regenera el árbol y el inspector en cada cambio. Con blueprints de tamaño realista (decenas de nodos) el coste es despreciable y elimina toda una clase de bugs de sincronización.scrollMemopreserva la posición del lienzo entre renders. - Historial por serialización.
snapshot()guarda unJSON.stringifydel estado yrestore()lo revierte. Simple y correcto por construcción. - Interpolación escapada. Todo el HTML se construye con plantillas de cadena y cualquier valor introducido por el usuario pasa por
esc(). - Estado en memoria. Lo único que se persiste en
localStoragees una bandera (safe-blueprint-designer:onboarded) para no repetir el tour. El blueprint se guarda y recupera vía export/import JSON.
Ninguno más allá del navegador: no hay Node, ni gestor de paquetes, ni paso de compilación. Sí conviene saber qué APIs se dan por supuestas:
| API / característica | Uso | Degradación |
|---|---|---|
ES2020+ (??, ?., spread, Object.fromEntries) |
Todo el código | Requiere navegador moderno |
CSS Custom Properties, backdrop-filter, :focus-visible |
Sistema de temas y superficies | Estética degradada, funcionalidad intacta |
localStorage |
Bandera de onboarding | Capturado en try/catch: funciona en modo privado |
FileReader |
Importar JSON | Sin alternativa |
Blob + URL.createObjectURL |
Todas las descargas | Sin alternativa |
canvas.toBlob |
Export PNG | Si falla, descarga el SVG automáticamente |
Intl.NumberFormat (es-ES) |
Cifras y divisas | Sin alternativa |
Verificado en versiones recientes de Chrome, Edge, Firefox y Safari. Los paneles laterales se pliegan por debajo de 1180 px de ancho.
Abre la demo publicada. Nada sale de tu navegador.
curl -O https://raw.githubusercontent.com/PacoCacheda/safe-org-blueprint-designer/main/app/index.htmlDoble clic en index.html. Funciona desde file:// y sin conexión; sin red, las tipografías caen a las del sistema y nada más cambia.
git clone https://github.com/PacoCacheda/safe-org-blueprint-designer.git
cd safe-org-blueprint-designerÁbrelo directamente, o levanta un servidor estático si prefieres trabajar sobre http:// (recomendado para contribuir, ver CONTRIBUTING.md):
# Cualquiera de estas sirve; ninguna instala nada permanente
python3 -m http.server 8080
npx --yes serve . # npm
pnpm dlx serve . # pnpm
bunx serve . # bunNota deliberada: este proyecto no tiene
package.json, ninode_modules, ni lockfile. Los comandos anteriores solo sirven ficheros estáticos; el proyecto sigue teniendo cero dependencias.
La bienvenida ofrece cuatro puertas, y la primera no es casual:
| Puerta | Cuándo |
|---|---|
| Empezar por el flujo | No sabes aún cómo debería organizarse. Describes los value streams y derivas los trenes de ahí |
| Empezar por la organización | Ya tienes una estructura y quieres validarla, o contrastarla contra el flujo después |
| Cargar un blueprint | Retomas un diseño existente desde su .json |
| Ver un tour | Nueve pasos guiados. Hay uno para cada modelo, y el de organización termina ofreciendo el de flujo |
- Describe los value streams operativos: disparador, pasos, valor recibido. Marca los pasos que son retrabajo.
- Declara los sistemas que intervienen y los development value streams que los construyen.
- Deriva la organización, o móntala a mano desde la paleta. La configuración de SAFe (
Essential,Large Solution,PortfoliooFull) decide qué niveles tienes disponibles. - Ve a Alineación y marca quién ejecuta cada paso. Los handoffs aparecen solos.
- Rellena el detalle de cada nodo en el inspector: roles, tamaño, artefactos adoptados, presupuesto, métricas de Flow declaradas.
- Vigila el panel de diagnóstico: cada hallazgo enlaza al elemento que lo provoca.
- Exporta el blueprint en JSON para versionarlo, o como imagen para una presentación. El export vectorial sigue la vista activa: en Organización sale el organigrama, en Flujo y Alineación sale el diagrama de flujo.
Si prefieres el orden contrario, funciona igual: la rama de organización del asistente no destruye el modelo de flujo. Solo retira las asignaciones a nodos que dejan de existir, reintenta emparejar por nombre y te informa de ambas cosas.
El JSON es autodescriptivo y anidado, así que se procesa con cualquier lenguaje sin librería:
import { readFileSync } from 'node:fs';
const bp = JSON.parse(readFileSync('./banca-digital.json', 'utf8'));
// El resumen ya viene calculado por la aplicación
console.log(bp.summary.healthScore); // 71
console.log(bp.summary.requiredRoleCoverage); // "14/14 (100%)"
console.log(bp.summary.alignment.handoffs); // 6
// Recorrido genérico del árbol
function* walk(node) {
yield node;
for (const child of node.children ?? []) yield* walk(child);
}
// Equipos fuera del rango de SAFe
for (const n of walk(bp.organization)) {
if (n.type === 'team' && (n.size < 5 || n.size > 11)) {
console.warn(`${n.name}: ${n.size} personas`);
}
}
// Artefactos pendientes por ART, para alimentar un plan de adopción
for (const n of walk(bp.organization)) {
if (n.type === 'art' && n.artifacts.pending.length) {
console.log(`${n.name} → pendiente: ${n.artifacts.pending.join(', ')}`);
}
}Y los hallazgos del diagnóstico se convierten en incidencias de tu tracker sin transformación previa. Como cada uno lleva un rule estable, se pueden encaminar por familia:
const familia = f => f.rule.split('.')[0]; // 'org' | 'flow' | 'align'
bp.summary.findings
.filter(f => f.severity === 'err')
.forEach(f => console.error(`[${familia(f).toUpperCase()}] ${f.subject} — ${f.detail}`));Los value streams traen su analítica calculada, así que localizar los peores es un sort:
const streams = [...bp.flow.operationalValueStreams,
...bp.flow.developmentValueStreams];
streams
.filter(s => s.analysis.handoffDensity > 0.5)
.sort((a, b) => b.analysis.handoffDensity - a.analysis.handoffDensity)
.forEach(s => console.log(
`${s.name}: ${Math.round(s.analysis.handoffDensity * 100)}% de transiciones ` +
`cambian de responsable (${s.analysis.handoffs} handoffs, ` +
`${s.analysis.severeHandoffs} sobre retrabajo)`
));
// Pasos sin nadie que responda de ellos
streams.flatMap(s => s.steps.map(st => ({ stream: s.name, ...st })))
.filter(st => !st.executors.some(e => e.role === 'owns'))
.forEach(st => console.warn(`${st.stream} → «${st.name}» sin responsable`));Como el export es determinista salvo la marca de tiempo, dos blueprints se comparan directamente:
jq 'del(.blueprint.generated)' q1.json > /tmp/q1.norm.json
jq 'del(.blueprint.generated)' q2.json > /tmp/q2.norm.json
diff -u /tmp/q1.norm.json /tmp/q2.norm.jsonO solo la evolución de los indicadores:
jq -r '"\(.blueprint.name): health=\(.summary.healthScore) equipos=\(.summary.agileTeams) hallazgos=\(.summary.findings | length)"' *.jsonTodo el conocimiento del marco vive en objetos literales. Para añadir un rol al ART basta una línea en la sección 1:
art: {
label: 'Agile Release Train', level: 'Essential Level', /* … */
accepts: ['team'], parent: 'valueStream', flowLabel: 'ART Flow',
roles: [
{ key: 'rte', label: 'Release Train Engineer (RTE)', short: 'RTE', req: true },
{ key: 'pm', label: 'Product Management', short: 'PM', req: true },
{ key: 'sysArch', label: 'System Architect / Engineering', short: 'SArq', req: true },
{ key: 'bizOwners', label: 'Business Owners', short: 'BO', req: true },
{ key: 'sysTeam', label: 'System Team Lead', short: 'ST', req: false } // ← nuevo
],
artifacts: [ /* … */ ]
}El inspector, el diagnóstico, el cálculo de cobertura, el asistente y el export lo recogen automáticamente. Si el rol se marca req: true, diagnose() empezará a exigirlo y el Health Score bajará hasta que se asigne.
El export produce un documento versionado bajo $schema: "safe-org-blueprint/2.0" con ocho bloques de primer nivel:
| Bloque | Contenido |
|---|---|
framework |
Procedencia: marco, versión, configuración activa y referencia |
blueprint |
Metadatos: nombre, organización, divisa y marca de tiempo |
summary |
Todo lo calculado: Health Score y su composición, recuentos, analítica de alineación y hallazgos |
organization |
El árbol organizativo, desde el Portfolio hasta los equipos |
flow |
Los value streams operativos y de desarrollo, con sus sistemas y sus pasos |
assignments |
La matriz de alineación aplanada: qué componente ejecuta qué paso y con qué rol |
observed |
Reservado para datos de ejecución reales. Hoy siempre available: false |
Los tres modelos —organización, flujo y asignaciones— viajan separados, igual que en memoria. Un blueprint sin organización, o sin flujo, es un documento válido.
{
"$schema": "safe-org-blueprint/2.0",
"framework": {
"name": "Scaled Agile Framework",
"version": "6.0",
"configuration": "Essential SAFe",
"reference": "https://framework.scaledagile.com/"
},
"blueprint": {
"name": "Blueprint Essential SAFe — Banca Digital",
"organization": "Mi Empresa",
"currency": "EUR",
"generated": "2026-08-07T12:01:10.644Z"
},
"summary": {
"healthScore": 71,
"healthComposition": { "roleCoverage": "30%", "structure": "35%", "alignment": "35%" },
"scoreBasis": "Sólo puntúa lo que el diseño sostiene. Las seis métricas de Flow son declaradas y no puntúan.",
"valueStreams": 1,
"solutionTrains": 0,
"agileReleaseTrains": 1,
"agileTeams": 3,
"suppliers": 0,
"peopleInTrains": 24,
"requiredRoleCoverage": "14/14 (100%)",
"alignment": {
"score": 68,
"operationalValueStreams": 1,
"developmentValueStreams": 1,
"steps": 10,
"handoffs": 6,
"severeHandoffs": 2,
"decisionGates": 1,
"stepsWithSingleOwner": 10,
"orphanSteps": 0,
"ambiguousSteps": 0,
"fragmentedStreams": 0,
"idleComponents": 1,
"executableComponents": 4,
"derivedFrom": "topology-only"
},
"findings": [
{
"rule": "org.art.people",
"severity": "warn",
"subject": "Digital Banking ART: 24 personas",
"detail": "El rango de referencia de un ART es de 50 a 125 personas (número de Dunbar)."
},
{
"rule": "align.stream.reworkCross",
"severity": "err",
"subject": "Banca Digital: 2 retrabajo(s) cruzando frontera",
"detail": "Volver atrás dentro del mismo equipo es fricción; volver atrás cambiando de responsable es cola con renegociación. Es el handoff más caro del sistema."
}
]
}
}Cada hallazgo lleva un rule estable —org.*, flow.*, align.*— así que se puede filtrar por familia sin parsear el texto.
Un nodo del árbol, con sus hijos en children:
{
"id": "nzqdbg5t",
"type": "team",
"safeLevel": "Team Level",
"name": "Onboarding Digital",
"code": "T-01",
"roles": {
"Product Owner": "Ana Gil",
"Scrum Master / Team Coach": "Luis Marín"
},
"artifacts": {
"adopted": ["Team Backlog", "Team PI Objectives", "Iteration Goals",
"Definition of Done", "Iteration Review y Retrospective"],
"pending": ["Prácticas de Built-in Quality"]
},
"flowMetrics": {
"scope": "Team Flow",
"provenance": "declared",
"scored": false,
"distribution": 68, "velocity": 23, "time": 21,
"load": 12, "efficiency": 27, "predictability": 95,
"observed": null
},
"size": 8,
"teamType": "Stream-aligned",
"method": "SAFe Scrum"
}Los niveles superiores añaden lo suyo: leanBudget, investmentHorizons y capacityAllocation en Portfolio; allocatedBudget y streamType en Value Stream; piIterations, iterationWeeks y piWeeks en ART y Solution Train; contract en Supplier.
Cada value stream trae sus sistemas, sus pasos con los ejecutores ya resueltos, y su analítica calculada:
{
"operationalValueStreams": [
{
"id": "nti7sl1x",
"type": "operationalValueStream",
"name": "Alta de cliente digital",
"code": "OVS-01",
"trigger": "Una persona solicita hacerse cliente desde la app",
"value": "Cuenta operativa con tarjeta activa",
"systems": [
{ "id": "ni6brq9y", "name": "App móvil" },
{ "id": "narohk7x", "name": "Core bancario" }
],
"steps": [
{
"id": "nc45gudw",
"sequence": 1,
"name": "Solicitud del cliente",
"rework": false,
"executors": [
{ "node": "nzqdbg5t", "nodeName": "Onboarding Digital",
"role": "owns", "roleLabel": "Responsable" }
],
"notes": null
}
],
"analysis": {
"steps": 4,
"handoffs": 2,
"severeHandoffs": 0,
"handoffDensity": 0.667,
"decisionGates": 0,
"stepsWithSingleOwner": 4,
"orphanSteps": 0,
"ambiguousSteps": 0,
"artsInvolved": 1
}
}
],
"developmentValueStreams": [ /* … además: "supports": [ids de OVS], pareja organizativa */ ]
}Y las asignaciones, aplanadas, para quien prefiera reconstruir la matriz por su cuenta:
"assignments": [
{ "step": "nc45gudw", "node": "nzqdbg5t", "role": "owns", "roleLabel": "Responsable" },
{ "step": "ndfw8cet", "node": "nnvpxhuk", "role": "owns", "roleLabel": "Responsable" },
{ "step": "ndfw8cet", "node": "nzqdbg5t", "role": "contributes", "roleLabel": "Contribuye" }
]La información está deliberadamente dos veces: dentro de cada paso como executors, para leer el documento sin cruzar índices, y aplanada en assignments, para procesarlo. Ambas se generan de la misma fuente.
- La raíz de
organizationes siempre un nodoportfolio, aunque la configuración activa sea Essential y el lienzo solo muestre el ART y sus equipos. El modelo mantiene la cadena jerárquica completa; la configuración controla qué se renderiza y qué se diagnostica, no qué se guarda. - El bloque
summarydescribe el blueprint completo, no la parte visible:requiredRoleCoverage,alignmentyfindingsrecorren todo lo que hay. - Las métricas de Flow son declaradas.
provenance: "declared"yscored: falselo dicen en el propio documento, yobservedqueda reservado para datos medidos. Ninguna herramienta que consuma este JSON debería tratarlas como medición. - Los roles se serializan por etiqueta legible, no por clave interna.
rehydrate()acepta ambas formas al importar, así que un JSON escrito a mano con claves ("rte": "…") también funciona. - Los artefactos se parten en
adopted/pendingpara que el documento sea legible sin consultar el catálogo. Al importar también se acepta un array plano o un objeto{artefacto: boolean}. - El presupuesto aparece como
leanBudgeten Portfolio y comoallocatedBudgeten Value Stream; el importador reconoce ambos y también un genéricobudget. - La importación es tolerante por diseño: acepta la raíz en
organization, enrooto en el propio documento, completa con los valores por defecto de las fábricas todo lo que falte, y sigue leyendo documentos1.0— un blueprint de la versión anterior se abre sin flujo ni asignaciones, y el Health Score se degrada en consecuencia.
Esto permite versionar los diseños en Git, compararlos entre trimestres o generar informes con herramientas externas.
Todo el aspecto de la aplicación vive en custom properties. Un tema es un JSON externo que las redefine, junto con la marca y las tipografías, sin tocar el código ni mantener una copia bifurcada de la herramienta.
Se carga igual que un blueprint: desde Importar o arrastrando el .json a la ventana. El importador decide qué es cada fichero por su $schema, así que el mismo selector sirve para ambos. El tema aplicado se recuerda entre sesiones, y Ayuda → Restablecer tema devuelve el aspecto por defecto.
La carga es manual a propósito. La aplicación está pensada para abrirse con doble clic, y bajo el protocolo file:// los navegadores bloquean fetch, de modo que no puede leer un fichero suelto que esté junto a ella. Pedir el fichero al usuario es lo único que funciona en los dos escenarios, servida por HTTP y abierta en local.
Un tema es un fichero de fuera, así que se sanea antes de aplicarlo:
| Elemento | Regla |
|---|---|
| Nombres de token | Solo --nombre-valido; el resto se ignora |
| Valores de token | Se descartan los que contengan <>{};@, javascript:, expression() o url() que no apunte a data: |
| Import de fuentes | Solo una URL https://; cualquier otro esquema se descarta |
| Isotipo | Únicamente datos de trazado SVG (paths), nunca markup |
| Textos de marca | Se escapan e insertan como texto, con longitud limitada |
Los descartes se anuncian en la consola, de forma que un tema mal escrito falla de manera visible y no en silencio.
Un matiz sobre las data: URI: aunque la regla de url() las permite, el punto y coma está en la lista de caracteres prohibidos, así que la forma habitual url(data:image/png;base64,…) también se descarta. En la práctica solo pasan las data: sin punto y coma, como url(data:image/svg+xml,…). Es una restricción conservadora: para incrustar un logotipo, la vía prevista es brand.mark con datos de trazado.
Al aplicar un tema se comprueban el texto, los acentos y los seis colores de nivel SAFe contra el fondo, en modo claro y oscuro. Si algún par no llega al 4,5:1 de WCAG 2.1 AA, la notificación lo indica y la consola detalla qué token y con qué ratio. Es informativa: nunca impide aplicar el tema.
Conviene leerla con criterio. El umbral de 4,5:1 corresponde a texto normal, y varios de los tokens auditados —los colores de nivel, los estados— se usan como acentos, bordes y distintivos, donde el criterio aplicable es el de 3:1 de elementos no textuales. La paleta por defecto ya arroja 12 avisos con esa vara de medir, así que un tema propio partirá de ese suelo: lo relevante no es el número absoluto sino si empeora los tokens que sí llevan texto encima.
El PNG exportado no hereda las webfonts remotas. Rasterizar un SVG sobre canvas no espera a que se descarguen las tipografías externas, así que la exportación usa la primera fuente local disponible de la pila. El SVG vectorial sí conserva la familia declarada. Si la marca es innegociable en el PNG, conviene incluir en --font-sans una alternativa local razonable.
tokens.root se aplica siempre; tokens.light y tokens.dark solo en su modo, lo que permite que los colores de nivel SAFe cambien entre uno y otro. La lista completa de tokens disponibles está comentada en el bloque :root de app/index.html, agrupada por tipografía, escala, forma, marca y capa decorativa.
| Atajo | Acción |
|---|---|
1 2 3 |
Cambiar a Organización, Flujo o Alineación |
P |
Modo presentación: pliega los dos paneles laterales |
[ · ] |
Plegar el panel izquierdo o el derecho por separado |
Ctrl/⌘ + Z |
Deshacer |
Ctrl/⌘ + Shift + Z · Ctrl/⌘ + Y |
Rehacer |
Ctrl/⌘ + S |
Exportar blueprint como JSON |
Supr · Retroceso |
Eliminar el elemento seleccionado |
Esc |
Cerrar modal, menú o asistente |
← → |
Navegar por el tour guiado |
Los atajos de una sola tecla se ignoran mientras escribes en un campo.
Prioridades ordenadas por impacto para quien llega nuevo al repositorio. Las propuestas se discuten en el issue tracker.
| Trimestre | Hito | Detalle |
|---|---|---|
| Q3 2026 | Capturas y carpeta docs/ |
Un lienzo con Full SAFe montado en el README; es lo que más ayuda a quien evalúa la herramienta en diez segundos |
| Q3 2026 | Carpeta ejemplos/ |
Dos o tres blueprints JSON exportados (un Essential, un Full) para probar la importación sin construir nada |
| Q3 2026 | Release v2.0.0 |
Etiqueta estable, .zip descargable y CHANGELOG con formato Keep a Changelog. La primera versión que cierra ya incluye el modelo de flujo y la matriz de alineación |
| Q4 2026 | Persistencia local opcional | Autoguardado en localStorage con recuperación de sesión, desactivable desde la interfaz |
| Q4 2026 | Tipografías autoalojadas | Eliminar la última petición de red externa (Google Fonts) y quedar 100% offline y sin terceros |
| Q4 2026 | Internacionalización | La interfaz está hoy solo en español; extraer las cadenas a un diccionario y publicar es / en |
| Q1 2027 | Comparador de blueprints | Cargar dos JSON y ver el delta estructural y de métricas entre trimestres dentro de la propia aplicación |
| Q1 2027 | Export a PDF y PowerPoint | El SVG ya se genera desde el estado; falta el empaquetado para comité |
| Continuo | Accesibilidad | Auditoría WCAG 2.1 AA: navegación completa por teclado del lienzo, roles ARIA en el árbol y contraste verificado en ambos temas |
| Continuo | Fidelidad al marco | Seguir las revisiones de SAFe y ajustar catálogo y guardrails |
Las incidencias y propuestas son bienvenidas. Al ser un único archivo, cualquier cambio es un diff legible sobre app/index.html.
Antes de abrir un PR, lee CONTRIBUTING.md: explica el flujo de ramas, los estándares de código y la lista de verificación manual que sustituye a la suite de tests. La participación se rige por el Código de Conducta.
SAFe®, Scaled Agile Framework® y los nombres de sus roles y artefactos son marcas registradas de Scaled Agile, Inc.
Este proyecto es una herramienta independiente creada con fines educativos y de diseño organizativo. No está afiliado, patrocinado ni respaldado por Scaled Agile, Inc., y no sustituye a la formación oficial ni a la documentación normativa del marco, disponible en framework.scaledagile.com.
Los rangos y guardrails implementados en el motor de diagnóstico son una interpretación de la orientación pública del marco y deben tomarse como apoyo a la conversación, no como norma.
Distribuido bajo licencia MIT. © 2026 Francisco López.
{ "$schema": "safe-blueprint-theme/1", "name": "Nombre del tema", "defaultMode": "light", // "light" | "dark" | omitido "brand": { "name": "Marca", // opcional; se antepone al producto "product": "Nombre del producto", "sub": "Subtítulo de cabecera", "claim": "Frase de marca", // aparece en el pie de la exportación "mark": { // isotipo: SOLO datos de trazado "viewBox": "0 0 24 24", "stroke": true, "strokeWidth": 3.4, "paths": ["M…"] } }, "fonts": { "import": "https://…" }, // hoja de estilos de fuentes; solo https "tokens": { "root": { "--font-sans": "…", "--r-md": "16px" }, "light": { "--bg": "#F4F1E9", "--text": "#14101B" }, "dark": { "--bg": "#121018", "--text": "#F4F1E9" } } }