Files
soleprint/atlas/book/feature-flow/index-en.html
2025-12-31 09:07:27 -03:00

393 lines
16 KiB
HTML

<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Feature Flow - Standardization Pipeline</title>
<style>
* { box-sizing: border-box; margin: 0; padding: 0; }
body {
font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, monospace;
background: #0f172a;
color: #e2e8f0;
min-height: 100vh;
}
.slide { display: none; min-height: 100vh; padding: 3rem; }
.slide.active { display: flex; flex-direction: column; }
.nav { position: fixed; bottom: 2rem; right: 2rem; display: flex; gap: 0.5rem; z-index: 100; }
.nav button { background: #334155; border: none; color: #e2e8f0; padding: 0.75rem 1.25rem; border-radius: 6px; cursor: pointer; font-size: 1rem; }
.nav button:hover { background: #475569; }
.nav .counter { background: transparent; padding: 0.75rem 1rem; color: #64748b; }
.slide-title { justify-content: center; align-items: center; text-align: center; }
.slide-title h1 { font-size: 3.5rem; font-weight: 700; margin-bottom: 1rem; background: linear-gradient(135deg, #6366f1, #a855f7); -webkit-background-clip: text; -webkit-text-fill-color: transparent; }
.slide-title p { font-size: 1.5rem; color: #94a3b8; }
.slide-title .subtitle { margin-top: 3rem; font-size: 1rem; color: #64748b; }
.pipeline { display: grid; grid-template-columns: repeat(3, 1fr); gap: 2rem; margin: 2rem 0; }
.pipeline-step { background: #1e293b; border-radius: 12px; padding: 2rem; border-left: 4px solid; }
.pipeline-step.ops { border-color: #22c55e; }
.pipeline-step.bdd { border-color: #6366f1; }
.pipeline-step.tests { border-color: #f59e0b; }
.pipeline-step h3 { font-size: 1.25rem; margin-bottom: 1rem; display: flex; align-items: center; gap: 0.5rem; }
.pipeline-step .num { width: 28px; height: 28px; border-radius: 50%; display: flex; align-items: center; justify-content: center; font-size: 0.85rem; font-weight: 700; }
.pipeline-step.ops .num { background: #22c55e; color: #0f172a; }
.pipeline-step.bdd .num { background: #6366f1; color: white; }
.pipeline-step.tests .num { background: #f59e0b; color: #0f172a; }
.pipeline-step ul { list-style: none; color: #94a3b8; font-size: 0.95rem; }
.pipeline-step li { padding: 0.4rem 0; padding-left: 1rem; border-left: 2px solid #334155; margin-bottom: 0.25rem; }
.slide h2 { font-size: 2rem; margin-bottom: 2rem; display: flex; align-items: center; gap: 1rem; }
.slide h2 .badge { padding: 0.25rem 0.75rem; border-radius: 4px; font-size: 0.75rem; font-weight: 600; }
.slide h2 .badge.ops { background: #22c55e; color: #0f172a; }
.slide h2 .badge.bdd { background: #6366f1; color: white; }
.slide h2 .badge.tests { background: #f59e0b; color: #0f172a; }
pre { background: #1e293b; padding: 1.5rem; border-radius: 8px; overflow-x: auto; font-size: 0.8rem; line-height: 1.5; margin: 1rem 0; }
.keyword { color: #c084fc; }
.string { color: #4ade80; }
.comment { color: #64748b; }
.decorator { color: #f59e0b; }
.two-col { display: grid; grid-template-columns: 1fr 1fr; gap: 2rem; flex: 1; }
.col { background: #1e293b; border-radius: 8px; padding: 1.5rem; }
.col h4 { font-size: 0.85rem; color: #94a3b8; margin-bottom: 1rem; text-transform: uppercase; letter-spacing: 0.05em; }
.checklist { list-style: none; font-size: 1.1rem; }
.checklist li { padding: 0.75rem 0; display: flex; align-items: center; gap: 0.75rem; border-bottom: 1px solid #334155; }
.checklist .check { width: 20px; height: 20px; border: 2px solid #475569; border-radius: 4px; }
.flow-list { list-style: none; }
.flow-list li { padding: 0.4rem 0; color: #94a3b8; font-size: 0.9rem; }
.flow-list li strong { color: #e2e8f0; }
</style>
</head>
<body>
<section class="slide slide-title active" data-slide="0">
<h1>Feature Flow</h1>
<p>Standardization Pipeline</p>
<div class="subtitle">Ops Templates &rarr; BDD/Gherkin &rarr; Backend + Frontend Tests</div>
</section>
<section class="slide" data-slide="1">
<h2>Pipeline Overview</h2>
<div class="pipeline">
<div class="pipeline-step ops">
<h3><span class="num">1</span> Ops Templates</h3>
<ul>
<li>Non-technical language</li>
<li>User perspective flows</li>
<li>From support/ops team</li>
<li>Captures edge cases</li>
<li>Documents known problems</li>
</ul>
</div>
<div class="pipeline-step bdd">
<h3><span class="num">2</span> BDD/Gherkin</h3>
<ul>
<li>.feature files</li>
<li>Given/When/Then syntax</li>
<li>Human readable specs</li>
<li>Single source of truth</li>
<li>Maps to both test types</li>
</ul>
</div>
<div class="pipeline-step tests">
<h3><span class="num">3</span> Tests</h3>
<ul>
<li><strong>Backend:</strong> API contracts</li>
<li><strong>Backend:</strong> Workflows (compositions)</li>
<li><strong>Frontend:</strong> Page Objects</li>
<li><strong>Frontend:</strong> E2E specs (Playwright)</li>
</ul>
</div>
</div>
</section>
<section class="slide" data-slide="2">
<h2><span class="badge ops">1</span> Ops Templates</h2>
<div class="two-col">
<div class="col">
<h4>Template Structure</h4>
<pre>
<span class="keyword">### [Flow Name]</span>
<span class="comment">User type:</span> Pet Owner / Vet / Admin
<span class="comment">Entry point:</span> Page/button/link
<span class="comment">Goal:</span> One sentence
<span class="keyword">Steps:</span>
1. First action
2. Second action
3. ...
<span class="keyword">Expected result:</span>
- What should happen
<span class="keyword">Common problems:</span>
- Problem 1
<span class="keyword">Edge cases:</span>
- Special case 1
</pre>
</div>
<div class="col">
<h4>Source</h4>
<ul class="flow-list">
<li><strong>Template:</strong> album/template/ops-flow/</li>
<li><strong>Reference:</strong> def/work_plan/21-plantilla</li>
</ul>
<h4 style="margin-top: 1.5rem;">Who Fills This</h4>
<ul class="flow-list">
<li>Support team (daily user contact)</li>
<li>Ops team (knows workarounds)</li>
<li>Product (requirements)</li>
</ul>
<h4 style="margin-top: 1.5rem;">Output</h4>
<ul class="flow-list">
<li>One .md per flow</li>
<li>Organized by user type</li>
</ul>
</div>
</div>
</section>
<section class="slide" data-slide="3">
<h2><span class="badge bdd">2</span> BDD/Gherkin</h2>
<div class="two-col">
<div class="col">
<h4>.feature File</h4>
<pre>
<span class="keyword">Feature:</span> Turnero - Book appointment
<span class="keyword">Scenario:</span> Book vaccination for cat
<span class="decorator">Given</span> I am on the turnero page
<span class="decorator">When</span> I enter address <span class="string">"Av Santa Fe 1234"</span>
<span class="decorator">And</span> I click <span class="string">"Next"</span>
<span class="decorator">Then</span> a guest user should be created
<span class="decorator">When</span> I add pet <span class="string">"Koshka"</span> type <span class="string">"Cat"</span>
<span class="decorator">And</span> I select <span class="string">"Vaccination"</span>
<span class="decorator">Then</span> <span class="string">"Clinical consult"</span> is auto-added
<span class="keyword">Scenario:</span> Services filtered by pet type
<span class="decorator">Given</span> I added a cat
<span class="decorator">Then</span> I see cat vaccines
<span class="decorator">And</span> I dont see dog vaccines
</pre>
</div>
<div class="col">
<h4>Keywords</h4>
<ul class="flow-list">
<li><strong>Feature</strong> = one capability</li>
<li><strong>Scenario</strong> = one behavior</li>
<li><strong>Given</strong> = precondition</li>
<li><strong>When</strong> = action</li>
<li><strong>Then</strong> = expected result</li>
</ul>
<h4 style="margin-top: 1.5rem;">Reference</h4>
<ul class="flow-list">
<li>def/work_plan/10-flow-turnero.md</li>
<li>Full example with Gherkin, API tests, Page Objects</li>
</ul>
</div>
</div>
</section>
<section class="slide" data-slide="4">
<h2><span class="badge bdd">2b</span> Gherkin File Organization</h2>
<div class="two-col">
<div class="col">
<h4>Correct: One Feature = One File</h4>
<pre>
<span class="keyword">features/</span>
├── pet-owner/
│ ├── registro.feature <span class="comment"># 6-8 scenarios</span>
│ ├── reservar-turno.feature <span class="comment"># 10-15 scenarios</span>
│ ├── gestion-mascotas.feature
│ └── pago.feature
├── veterinarian/
│ └── ...
└── backoffice/
└── ...
</pre>
<h4 style="margin-top: 1rem;">Anti-pattern: One Scenario = One File</h4>
<pre style="border-left: 3px solid #ef4444;">
<span class="comment"># DON'T do this</span>
features/pet-owner/registro/
├── registro-exitoso.feature
├── registro-email-invalido.feature
├── registro-password-corto.feature
└── <span class="comment">... (dozens of tiny files)</span>
</pre>
</div>
<div class="col">
<h4>Why Multiple Scenarios per File</h4>
<ul class="flow-list">
<li><strong>Feature = Capability</strong> - one file describes one capability with all its behaviors</li>
<li><strong>Context stays together</strong> - Background, Rules share context</li>
<li><strong>Tooling expects it</strong> - test runners, reports, IDE navigation</li>
</ul>
<h4 style="margin-top: 1.5rem;">When to Split</h4>
<pre>
<span class="comment"># Scenarios per file:</span>
5-20 <span class="string">Normal, keep as is</span>
20-40 <span class="string">Consider splitting</span>
40+ <span class="string">Definitely split</span>
</pre>
<h4 style="margin-top: 1rem;">Folder Depth</h4>
<ul class="flow-list">
<li><strong>Good:</strong> 1-2 levels max</li>
<li><strong>Avoid:</strong> deep nesting</li>
</ul>
</div>
</div>
</section>
<section class="slide" data-slide="5">
<h2><span class="badge tests">3a</span> Backend Tests</h2>
<div class="two-col">
<div class="col">
<h4>Structure (amar_django_back)</h4>
<pre>
tests/contracts/
├── base.py <span class="comment"># mode switcher</span>
├── endpoints.py <span class="comment"># API paths (single source)</span>
├── helpers.py <span class="comment"># test data</span>
├── mascotas/ <span class="comment"># app tests</span>
│ ├── test_pet_owners.py
│ ├── test_pets.py
│ └── test_coverage.py
├── productos/
│ ├── test_services.py
│ └── test_cart.py
├── solicitudes/
│ └── test_service_requests.py
└── <span class="keyword">workflows/</span> <span class="comment"># compositions</span>
└── test_turnero_general.py
</pre>
</div>
<div class="col">
<h4>Two Test Modes</h4>
<pre>
<span class="comment"># Fast (Django test client)</span>
pytest tests/contracts/
<span class="comment"># Live (real HTTP)</span>
CONTRACT_TEST_MODE=live pytest
</pre>
<h4 style="margin-top: 1rem;">Workflow = Composition</h4>
<pre>
<span class="comment"># Calls endpoints in sequence:</span>
1. Check coverage
2. Create pet owner
3. Create pet
4. Get services
5. Create request
</pre>
<h4 style="margin-top: 1rem;">Key Files</h4>
<ul class="flow-list">
<li><strong>endpoints.py</strong> - change paths here only</li>
<li><strong>helpers.py</strong> - sample data</li>
</ul>
</div>
</div>
</section>
<section class="slide" data-slide="6">
<h2><span class="badge tests">3b</span> Frontend Tests</h2>
<div class="two-col">
<div class="col">
<h4>Structure (amar_frontend)</h4>
<pre>
tests/e2e/
├── <span class="keyword">pages/</span> <span class="comment"># Page Objects</span>
│ ├── BasePage.ts
│ ├── LoginPage.ts
│ └── index.ts
└── login.spec.ts <span class="comment"># E2E test</span>
</pre>
<h4 style="margin-top: 1rem;">Page Object Pattern</h4>
<pre>
<span class="keyword">export class</span> LoginPage <span class="keyword">extends</span> BasePage {
<span class="keyword">get</span> emailInput() {
<span class="keyword">return</span> this.page.getByLabel(<span class="string">'Email'</span>);
}
<span class="keyword">async</span> login(email, password) {
<span class="keyword">await</span> this.emailInput.fill(email);
<span class="keyword">await</span> this.passwordInput.fill(password);
<span class="keyword">await</span> this.submitButton.click();
}
}
</pre>
</div>
<div class="col">
<h4>Running Tests</h4>
<pre>
<span class="comment"># All tests</span>
npx playwright test
<span class="comment"># With UI</span>
npx playwright test --ui
<span class="comment"># Specific file</span>
npx playwright test login.spec.ts
</pre>
<h4 style="margin-top: 1rem;">Locator Priority</h4>
<ul class="flow-list">
<li>1. getByRole() - buttons, links</li>
<li>2. getByLabel() - form fields</li>
<li>3. getByText() - visible text</li>
<li>4. getByTestId() - data-testid</li>
</ul>
<h4 style="margin-top: 1rem;">Avoid</h4>
<ul class="flow-list">
<li>CSS class selectors</li>
<li>Complex XPath</li>
</ul>
</div>
</div>
</section>
<section class="slide" data-slide="7">
<h2>Per-Feature Checklist</h2>
<ul class="checklist">
<li><span class="check"></span> Ops template filled (support team)</li>
<li><span class="check"></span> Convert to .feature file (Gherkin spec)</li>
<li><span class="check"></span> Backend: API contract tests per endpoint</li>
<li><span class="check"></span> Backend: Workflow test (composition)</li>
<li><span class="check"></span> Frontend: Page Object (if new page)</li>
<li><span class="check"></span> Frontend: E2E spec (Playwright)</li>
<li><span class="check"></span> Wire to CI</li>
</ul>
<div style="margin-top: 2rem; color: #64748b; font-size: 0.9rem;">
<p><strong>Full example:</strong> def/work_plan/10-flow-turnero.md</p>
<p><strong>Backend README:</strong> amar_django_back/tests/contracts/README.md</p>
<p><strong>Frontend README:</strong> amar_frontend/tests/README.md</p>
</div>
</section>
<div class="nav">
<button onclick="prevSlide()">&#8592;</button>
<span class="counter"><span id="current">1</span>/<span id="total">8</span></span>
<button onclick="nextSlide()">&#8594;</button>
</div>
<script>
let current = 0;
const slides = document.querySelectorAll('.slide');
const total = slides.length;
document.getElementById('total').textContent = total;
function showSlide(n) {
slides.forEach(s => s.classList.remove('active'));
current = (n + total) % total;
slides[current].classList.add('active');
document.getElementById('current').textContent = current + 1;
}
function nextSlide() { showSlide(current + 1); }
function prevSlide() { showSlide(current - 1); }
document.addEventListener('keydown', e => {
if (e.key === 'ArrowRight' || e.key === ' ') nextSlide();
if (e.key === 'ArrowLeft') prevSlide();
});
</script>
</body>
</html>