mirror of
https://github.com/cursos-uai/sap_tfi_2026.git
synced 2026-09-17 17:29:21 -03:00
feat(prototipo): agregar pantallas trazables ICONIX
Co-authored-by: wsf <alejandro.sartorio@gmail.com> Signed-off-by: wsf <alejandro.sartorio@gmail.com>
This commit is contained in:
@@ -47,6 +47,7 @@ sap_tfi_2026/
|
||||
│ └── pdf/ ← versiones PDF
|
||||
├── evidence/
|
||||
│ └── index.md ← catálogo de evidencias
|
||||
├── prototipo/ ← pantallas navegables y trazabilidad ICONIX
|
||||
└── .gitignore
|
||||
```
|
||||
|
||||
@@ -54,6 +55,13 @@ sap_tfi_2026/
|
||||
|
||||
## Cómo usar este repositorio para el TP
|
||||
|
||||
### Explorar el prototipo de fronteras
|
||||
|
||||
Abrí **`prototipo/index.html`** para recorrer las pantallas de presupuesto,
|
||||
confirmación, reserva y facturación. La vista **Trazabilidad** muestra la
|
||||
relación entre cada componente de interfaz (boundary), la acción que dispara
|
||||
(control) y los modelos afectados (entity).
|
||||
|
||||
### Paso 1: Entender la metodología ICONIX
|
||||
|
||||
Antes de hacer cualquier diagrama, leé:
|
||||
@@ -332,4 +340,4 @@ Material de lectura recomendado para profundizar en la metodología ICONIX y su
|
||||
|
||||
## Licencia
|
||||
|
||||
Este material se distribuye bajo la licencia Creative Commons BY-SA 4.0. Podés usarlo, modificarlo y redistribuirlo libremente, citando la fuente.
|
||||
Este material se distribuye bajo la licencia Creative Commons BY-SA 4.0. Podés usarlo, modificarlo y redistribuirlo libremente, citando la fuente.
|
||||
|
||||
@@ -0,0 +1,36 @@
|
||||
# Prototipo de fronteras ICONIX
|
||||
|
||||
Prototipo navegable que representa las pantallas asociadas a los diagramas de
|
||||
robustez del flujo comercial. Su objetivo es mostrar, con fines didácticos, la
|
||||
trazabilidad entre:
|
||||
|
||||
- **Boundary**: pantalla, formulario, botón, enlace, tabla o mensaje visible.
|
||||
- **Control**: acción disparada por una interacción.
|
||||
- **Entity**: modelo y datos consultados o modificados.
|
||||
|
||||
## Abrir
|
||||
|
||||
No requiere instalación ni servidor. Abrir `index.html` en un navegador.
|
||||
|
||||
Para servirlo localmente (opcional):
|
||||
|
||||
```bash
|
||||
python3 -m http.server 8000 --directory prototipo
|
||||
```
|
||||
|
||||
Luego visitar <http://localhost:8000>.
|
||||
|
||||
## Escenarios incluidos
|
||||
|
||||
| Pantalla | Caso de uso | Diagrama de robustez |
|
||||
|---|---|---|
|
||||
| Presupuesto | CU-VEN-001 | D-ROB-VEN-001 |
|
||||
| Confirmación | CU-VEN-004 | D-ROB-VEN-004 |
|
||||
| Reserva | CU-ENT-002 | D-ROB-ENT-002 |
|
||||
| Facturación | CU-FAC-001 | D-ROB-FAC-001 |
|
||||
|
||||
## Alcance
|
||||
|
||||
Es un prototipo educativo, no una reproducción exacta ni conectada a Odoo. Los
|
||||
datos son ficticios y las acciones se ejecutan en memoria. Las correspondencias
|
||||
se basan en las fuentes PlantUML existentes en `diagrams/plantuml/`.
|
||||
@@ -0,0 +1,82 @@
|
||||
const scenarios = {
|
||||
presupuesto: {
|
||||
title: "Crear presupuesto", diagram: "D-ROB-VEN-001", state: "BORRADOR",
|
||||
actions: [["Guardar", "primary", "save"], ["Enviar por email", "", "email"], ["Cancelar", "", "cancel"]],
|
||||
trace: [
|
||||
["Botón Nuevo + formulario", "Crea y actualiza el presupuesto", "sale.order"],
|
||||
["Campos cliente y líneas", "Calcula importes", "res.partner + sale.order.line"],
|
||||
["Botón Enviar por email", "action_quotation_send()", "sale.order + mail.mail"]
|
||||
]
|
||||
},
|
||||
confirmacion: {
|
||||
title: "Confirmar pedido", diagram: "D-ROB-VEN-004", state: "PRESUPUESTO ENVIADO",
|
||||
actions: [["Confirmar", "primary", "confirm"], ["Volver al presupuesto", "", "back"]],
|
||||
trace: [
|
||||
["Formulario + botón Confirmar", "action_confirm()", "sale.order"],
|
||||
["Mensaje de validación", "_confirmation_error_message()", "sale.order + sale.order.line"],
|
||||
["Resumen de entrega", "_action_confirm()", "stock.picking"],
|
||||
["Opción de factura", "_create_invoices()", "account.move"]
|
||||
]
|
||||
},
|
||||
reserva: {
|
||||
title: "Reservar productos", diagram: "D-ROB-ENT-002", state: "ESPERANDO RESERVA",
|
||||
actions: [["Comprobar disponibilidad", "primary", "reserve"], ["Ver reglas", "", "rules"]],
|
||||
trace: [
|
||||
["Botón Comprobar disponibilidad", "action_assign()", "stock.picking"],
|
||||
["Tabla de disponibilidad", "Busca y reserva cantidad", "stock.move.line"],
|
||||
["Aviso de faltante", "Ejecuta reabastecimiento", "stock.warehouse.orderpoint + purchase.order"]
|
||||
]
|
||||
},
|
||||
factura: {
|
||||
title: "Crear factura", diagram: "D-ROB-FAC-001", state: "PARA FACTURAR",
|
||||
actions: [["Crear factura", "primary", "invoice"], ["Cancelar", "", "cancel"]],
|
||||
trace: [
|
||||
["Botón Crear factura", "_create_invoices()", "sale.order"],
|
||||
["Opciones de facturación", "Valida líneas facturables", "sale.order.line"],
|
||||
["Vista previa de datos", "Calcula y crea factura", "account.move + account.move.line + account.tax"]
|
||||
]
|
||||
}
|
||||
};
|
||||
|
||||
const money = new Intl.NumberFormat("es-AR", {style:"currency", currency:"ARS", maximumFractionDigits:0});
|
||||
const lines = [{product:"Notebook Pro 14", qty:2, price:210000, discount:0, tax:"IVA 21%"},{product:"Dock USB-C", qty:3, price:22000, discount:5, tax:"IVA 21%"}];
|
||||
let activeScenario = "presupuesto";
|
||||
|
||||
function renderLines(){
|
||||
const body=document.querySelector("#order-lines");
|
||||
body.innerHTML=lines.map((line,i)=>`<tr><td>${line.product}</td><td>${line.qty}</td><td>${money.format(line.price)}</td><td>${line.discount}%</td><td>${line.tax}</td><td>${money.format(line.qty*line.price*(1-line.discount/100))}</td></tr>`).join("");
|
||||
const untaxed=lines.reduce((sum,line)=>sum+line.qty*line.price*(1-line.discount/100),0);
|
||||
document.querySelector("#untaxed").textContent=money.format(untaxed);
|
||||
document.querySelector("#tax").textContent=money.format(untaxed*.21);
|
||||
document.querySelector("#total").textContent=money.format(untaxed*1.21);
|
||||
}
|
||||
|
||||
function renderScenario(key){
|
||||
activeScenario=key; const scenario=scenarios[key];
|
||||
document.querySelectorAll(".nav-item").forEach(item=>item.classList.toggle("active",item.dataset.scenario===key));
|
||||
document.querySelectorAll(".screen").forEach(screen=>screen.classList.toggle("active",screen.id===`screen-${key}`));
|
||||
document.querySelector("#screen-title").textContent=scenario.title;
|
||||
document.querySelector("#diagram-id").textContent=scenario.diagram;
|
||||
document.querySelector("#record-state").textContent=scenario.state;
|
||||
document.querySelector("#primary-actions").innerHTML=scenario.actions.map(([label,kind,action])=>`<button class="${kind}" data-action="${action}">${label}</button>`).join("");
|
||||
document.querySelector("#trace-grid").innerHTML=scenario.trace.map(row=>`<article class="trace-row"><div class="trace-cell boundary-cell"><span>Boundary</span><strong>${row[0]}</strong><p>Elemento que ve o utiliza el actor.</p></div><div class="trace-cell control-cell"><span>Control</span><strong>${row[1]}</strong><p>Comportamiento disparado.</p></div><div class="trace-cell entity-cell"><span>Entity</span><strong>${row[2]}</strong><p>Datos consultados o modificados.</p></div></article>`).join("");
|
||||
document.querySelector("#diagram-link").href=`../diagrams/svg/${encodeURIComponent(scenario.diagram)}%20${encodeURIComponent("—")}%20${diagramFilename(key)}.svg`;
|
||||
log("Pantalla preparada",`Estás explorando ${scenario.diagram}. Usá los botones para simular el flujo.`);
|
||||
}
|
||||
|
||||
function diagramFilename(key){return {presupuesto:"Diagrama%20de%20Robustez:%20Crear%20Presupuesto",confirmacion:"Diagrama%20de%20Robustez:%20Confirmar%20Pedido",reserva:"Diagrama%20de%20Robustez:%20Reservar%20Productos",factura:"Diagrama%20de%20Robustez:%20Crear%20Factura"}[key]}
|
||||
function log(title,detail){document.querySelector("#event-title").textContent=title;document.querySelector("#event-detail").textContent=detail}
|
||||
|
||||
document.querySelector("#scenario-nav").addEventListener("click",event=>{const item=event.target.closest("[data-scenario]");if(item)renderScenario(item.dataset.scenario)});
|
||||
document.querySelector(".view-switch").addEventListener("click",event=>{const button=event.target.closest("[data-view]");if(!button)return;document.querySelectorAll(".view-switch button").forEach(b=>b.classList.toggle("active",b===button));document.querySelectorAll(".view").forEach(v=>v.classList.toggle("active",v.id===`${button.dataset.view}-view`))});
|
||||
document.querySelector("#primary-actions").addEventListener("click",event=>{
|
||||
const action=event.target.dataset.action;if(!action)return;
|
||||
const messages={save:["Presupuesto guardado","Boundary formulario → actualiza entity sale.order."],email:["Correo preparado","Boundary Enviar → action_quotation_send() → mail.mail."],cancel:["Acción cancelada","No se modificaron entidades en esta simulación."],back:["Volver al presupuesto","Navegación hacia view_sale_order_form."],rules:["Reglas consultadas","Control de reabastecimiento → stock.warehouse.orderpoint."],confirm:["Pedido confirmado","action_confirm() cambió sale.order y generó stock.picking."],reserve:["Reserva procesada","action_assign() reservó 2 unidades y detectó disponibilidad parcial."],invoice:["Factura borrador creada","_create_invoices() → account.move + account.move.line."]};
|
||||
if(action==="confirm")document.querySelector("#record-state").textContent="PEDIDO CONFIRMADO";
|
||||
if(action==="reserve"){document.querySelector("#reserved-a").textContent="2";document.querySelector("#reserved-b").textContent="1";document.querySelector("#reserve-state-a").textContent="Reservado";document.querySelector("#reserve-state-a").className="pill ok";document.querySelector("#reserve-state-b").textContent="Parcial";document.querySelector("#reserve-state-b").className="pill partial";document.querySelector("#record-state").textContent="PARCIALMENTE DISPONIBLE"}
|
||||
if(action==="invoice")document.querySelector("#record-state").textContent="FACTURA BORRADOR";
|
||||
log(...messages[action]);
|
||||
});
|
||||
document.querySelector("#add-line").addEventListener("click",()=>{lines.push({product:"Monitor 27 pulgadas",qty:1,price:185000,discount:0,tax:"IVA 21%"});renderLines();log("Línea agregada","El formulario actualiza sale.order.line y recalcula los importes de sale.order.")});
|
||||
|
||||
renderLines();renderScenario(activeScenario);
|
||||
@@ -0,0 +1,99 @@
|
||||
<!doctype html>
|
||||
<html lang="es">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width, initial-scale=1">
|
||||
<meta name="description" content="Prototipo educativo de fronteras ICONIX para SAP-TFI-2026">
|
||||
<title>Prototipo ICONIX · SAP-TFI-2026</title>
|
||||
<link rel="stylesheet" href="styles.css">
|
||||
</head>
|
||||
<body>
|
||||
<header class="topbar">
|
||||
<a class="brand" href="#presupuesto" aria-label="Ir al inicio">
|
||||
<span class="brand-mark">S</span>
|
||||
<span><strong>SAP · TFI</strong><small>Laboratorio ICONIX</small></span>
|
||||
</a>
|
||||
<div class="course"><span>UAI · Ingeniería</span><span class="status-dot">Prototipo educativo</span></div>
|
||||
</header>
|
||||
|
||||
<main class="workspace">
|
||||
<aside class="sidebar" aria-label="Escenarios">
|
||||
<p class="eyebrow">Flujo comercial</p>
|
||||
<nav id="scenario-nav">
|
||||
<button class="nav-item active" data-scenario="presupuesto"><span>01</span>Presupuesto<small>CU-VEN-001</small></button>
|
||||
<button class="nav-item" data-scenario="confirmacion"><span>02</span>Confirmación<small>CU-VEN-004</small></button>
|
||||
<button class="nav-item" data-scenario="reserva"><span>03</span>Reserva<small>CU-ENT-002</small></button>
|
||||
<button class="nav-item" data-scenario="factura"><span>04</span>Facturación<small>CU-FAC-001</small></button>
|
||||
</nav>
|
||||
<div class="legend">
|
||||
<p class="eyebrow">Leyenda ICONIX</p>
|
||||
<span><i class="dot boundary"></i>Frontera</span>
|
||||
<span><i class="dot control"></i>Control</span>
|
||||
<span><i class="dot entity"></i>Entidad</span>
|
||||
</div>
|
||||
</aside>
|
||||
|
||||
<section class="stage">
|
||||
<div class="stage-heading">
|
||||
<div><p class="eyebrow" id="diagram-id">D-ROB-VEN-001</p><h1 id="screen-title">Crear presupuesto</h1></div>
|
||||
<div class="view-switch" aria-label="Vista del prototipo">
|
||||
<button class="active" data-view="ui">Pantalla</button>
|
||||
<button data-view="trace">Trazabilidad</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div id="ui-view" class="view active">
|
||||
<div class="browser-frame">
|
||||
<div class="browser-bar"><span></span><span></span><span></span><p>Ventas / Presupuestos / Nuevo</p></div>
|
||||
<div class="app-shell">
|
||||
<div class="actionbar">
|
||||
<div id="primary-actions"></div>
|
||||
<span class="record-state" id="record-state">BORRADOR</span>
|
||||
</div>
|
||||
|
||||
<div id="screen-presupuesto" class="screen active">
|
||||
<div class="form-title"><span>Presupuesto</span><strong id="order-name">Nuevo</strong></div>
|
||||
<div class="form-grid">
|
||||
<label>Cliente<input id="customer" value="Industrias Horizonte S.A."></label>
|
||||
<label>Vendedor<input value="Ana Torres"></label>
|
||||
<label>Lista de precios<select><option>ARS · Mayorista</option></select></label>
|
||||
<label>Validez<input type="date" value="2026-09-30"></label>
|
||||
</div>
|
||||
<div class="tabs"><button class="active">Líneas del pedido</button><button>Otra información</button></div>
|
||||
<div class="table-wrap"><table><thead><tr><th>Producto</th><th>Cant.</th><th>Precio</th><th>Dto.</th><th>Impuesto</th><th>Subtotal</th></tr></thead><tbody id="order-lines"></tbody></table></div>
|
||||
<button class="text-button" id="add-line">+ Agregar producto</button>
|
||||
<div class="totals"><span>Base imponible <b id="untaxed">$ 0</b></span><span>IVA 21% <b id="tax">$ 0</b></span><strong>Total <b id="total">$ 0</b></strong></div>
|
||||
</div>
|
||||
|
||||
<div id="screen-confirmacion" class="screen">
|
||||
<div class="summary-card"><span class="icon success">✓</span><div><p>Pedido listo para confirmar</p><h2>S00042 · Industrias Horizonte S.A.</h2></div><strong>$ 588.060</strong></div>
|
||||
<div class="validation-grid"><article><span>Estado</span><strong>Presupuesto enviado</strong></article><article><span>Líneas</span><strong>2 productos válidos</strong></article><article><span>Stock</span><strong>Se generará una entrega</strong></article><article><span>Facturación</span><strong>Según cantidades entregadas</strong></article></div>
|
||||
<div class="callout"><b>Al confirmar</b><p>El sistema cambia el estado del pedido, fija la fecha y genera el picking de almacén.</p></div>
|
||||
</div>
|
||||
|
||||
<div id="screen-reserva" class="screen">
|
||||
<div class="form-title"><span>Transferencia</span><strong>WH/OUT/00031</strong></div>
|
||||
<div class="progress"><span class="done">Pedido</span><span class="active">Reserva</span><span>Preparado</span><span>Entregado</span></div>
|
||||
<div class="table-wrap"><table><thead><tr><th>Producto</th><th>Demanda</th><th>Disponible</th><th>Reservado</th><th>Resultado</th></tr></thead><tbody><tr><td>Notebook Pro 14</td><td>2</td><td>5</td><td id="reserved-a">0</td><td><span class="pill waiting" id="reserve-state-a">Pendiente</span></td></tr><tr><td>Dock USB-C</td><td>3</td><td>1</td><td id="reserved-b">0</td><td><span class="pill waiting" id="reserve-state-b">Pendiente</span></td></tr></tbody></table></div>
|
||||
<div class="callout warning"><b>Disponibilidad parcial</b><p>La segunda línea puede requerir reabastecimiento según la regla configurada.</p></div>
|
||||
</div>
|
||||
|
||||
<div id="screen-factura" class="screen">
|
||||
<div class="summary-card"><span class="icon invoice">$</span><div><p>Pedido confirmado</p><h2>S00042 · Pendiente de facturar</h2></div><strong>$ 588.060</strong></div>
|
||||
<div class="invoice-options"><label><input type="radio" name="invoice" checked> Factura regular <small>Facturar las líneas disponibles.</small></label><label><input type="radio" name="invoice"> Anticipo porcentual <small>Crear una factura por un porcentaje.</small></label></div>
|
||||
<div class="model-preview"><p class="eyebrow">Datos que se crearán</p><div><span>account.move</span><b>Estado: draft</b></div><div><span>account.move.line</span><b>2 líneas</b></div><div><span>account.tax</span><b>IVA 21%</b></div></div>
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
<div class="event-log"><div><p class="eyebrow">Acción observada</p><strong id="event-title">Formulario preparado</strong></div><p id="event-detail">Seleccioná una acción para ver su relación con ICONIX.</p></div>
|
||||
</div>
|
||||
|
||||
<div id="trace-view" class="view">
|
||||
<div class="trace-intro"><p>Esta vista une cada elemento visible con el control que invoca y las entidades que consulta o modifica.</p><a id="diagram-link" href="../diagrams/svg/D-ROB-VEN-001%20%E2%80%94%20Diagrama%20de%20Robustez:%20Crear%20Presupuesto.svg" target="_blank">Abrir diagrama SVG ↗</a></div>
|
||||
<div class="trace-grid" id="trace-grid"></div>
|
||||
</div>
|
||||
</section>
|
||||
</main>
|
||||
<script src="app.js"></script>
|
||||
</body>
|
||||
</html>
|
||||
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user