<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Luis A. Obando</title>
    <description>The latest articles on DEV Community by Luis A. Obando (@luisom).</description>
    <link>https://dev.to/luisom</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F2866883%2F2987a0c0-ece2-4a06-8bac-9a268fc279e5.jpg</url>
      <title>DEV Community: Luis A. Obando</title>
      <link>https://dev.to/luisom</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/luisom"/>
    <language>en</language>
    <item>
      <title>I Stopped Vibe Coding and Started Shipping: Task-Driven Development with AI</title>
      <dc:creator>Luis A. Obando</dc:creator>
      <pubDate>Fri, 20 Mar 2026 13:39:30 +0000</pubDate>
      <link>https://dev.to/luisom/i-stopped-vibe-coding-and-started-shipping-task-driven-development-with-ai-1op3</link>
      <guid>https://dev.to/luisom/i-stopped-vibe-coding-and-started-shipping-task-driven-development-with-ai-1op3</guid>
      <description>&lt;p&gt;A year ago I wrote about how I built &lt;a href="https://dev.to/luisom/de-idea-a-aplicacion-web-creando-examgenius-con-vibe-coding-y-claude-3k6l"&gt;ExamGenius with vibe coding and Claude&lt;/a&gt;. The premise was simple: tell the AI what you want, it generates the code, you tweak, and in 20 hours you have a complete app that would normally take weeks. 85% time reduction. Working code. All good.&lt;/p&gt;

&lt;p&gt;A year later, the industry has moved at breakneck speed. Models are more capable, IDEs integrate AI natively, and more teams are shipping AI-generated code to production every day. But there's a problem nobody wants to admit: &lt;strong&gt;we're shipping massive amounts of code that nobody truly reviewed&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;It feels productive. But the result is often code that's hard to maintain, untested, poorly structured, and with zero traceability on why certain decisions were made. Vibe coding feels great until you have to debug something that neither you nor the AI remember writing.&lt;/p&gt;

&lt;p&gt;This post is about what comes after vibe coding.&lt;/p&gt;

&lt;h2&gt;
  
  
  The real problem: without structure, even the best dev creates noise
&lt;/h2&gt;

&lt;p&gt;Let's be honest: AI produces code that many senior devs couldn't write as fast or as clean. The problem isn't code quality — it's lack of direction.&lt;/p&gt;

&lt;p&gt;Without clear tasks, the AI wanders. You say "fix this bug" and it refactors half the module. Without defined scope, "improve this" becomes a 15-file change nobody asked for. You lose visibility into what was done, what's left, and what broke along the way. It's not a competence problem — it's a context problem. The AI doesn't know what decisions you've already made, what patterns your codebase follows, or what the actual scope of your request is.&lt;/p&gt;

&lt;p&gt;With ExamGenius it worked because it was a greenfield project — a single session, no legacy code, no other collaborators. But in real projects that evolve week after week, pure vibe coding doesn't scale.&lt;/p&gt;

&lt;p&gt;And in the enterprise — where there are teams, code reviews, compliance requirements, and real production systems — it's simply not enough. You can't show up to a PR review saying "the AI generated it and it looked fine." You need to explain what was done, why, and what criteria were used to validate it.&lt;/p&gt;

&lt;h2&gt;
  
  
  The alternative: Task-Driven Development
&lt;/h2&gt;

&lt;p&gt;The idea is simple: &lt;strong&gt;define the work before executing it&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Instead of opening a chat and saying "build me this," you define a task with clear scope, break it into subtasks if needed, and give the AI the precise context to execute each piece. The flow looks like this:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Spec&lt;/strong&gt; — define what you want to achieve and why&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tasks&lt;/strong&gt; — break the work into manageable units&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Subtasks&lt;/strong&gt; — if a task is large, break it down further&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Plan&lt;/strong&gt; — before writing code, the AI researches the codebase and writes an implementation plan in the task. You review and approve before a single line of code is touched&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Execution&lt;/strong&gt; — the AI works within the defined scope&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Review&lt;/strong&gt; — you validate before moving on&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Step 4 is key and most people skip it. Asking the AI to plan before executing gives you a review checkpoint that's worth its weight in gold. The plan reflects the current state of the codebase — it's not theory, it's a concrete proposal you can approve, adjust, or reject. It's the difference between "just do it" and "tell me how you'd do it, then do it."&lt;/p&gt;

&lt;p&gt;The developer goes back to being the architect and reviewer, not a spectator. You decide what gets done, the AI executes. And if something doesn't look right, you correct it before it propagates.&lt;/p&gt;

&lt;p&gt;Here's what closes the loop: the AI doesn't just execute tasks — it also helps you define them. You can ask it to suggest subtasks, identify edge cases, or propose DoDs based on the project context. You provide the general direction, the AI helps you specify with enough detail, and then executes against that specification. It's a collaborative cycle where both sides contribute what they do best.&lt;/p&gt;

&lt;p&gt;This applies to side projects and enterprise teams alike. The difference is that in the enterprise, it's not optional.&lt;/p&gt;

&lt;h2&gt;
  
  
  Definition of Done: the AI has to meet criteria too
&lt;/h2&gt;

&lt;p&gt;This is where it gets interesting. Each task can have a set of &lt;strong&gt;Definition of Done (DoDs)&lt;/strong&gt; — criteria that must be met before marking it complete.&lt;/p&gt;

&lt;p&gt;This isn't bureaucracy. It's verifiable quality:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Tests pass&lt;/li&gt;
&lt;li&gt;No regressions&lt;/li&gt;
&lt;li&gt;Code follows project patterns&lt;/li&gt;
&lt;li&gt;Translations are complete&lt;/li&gt;
&lt;li&gt;Logging is consistent&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The AI stops being a black box that "generates code" and becomes an executor that has to meet concrete criteria. If it doesn't meet them, the task isn't done.&lt;/p&gt;

&lt;p&gt;In an enterprise context, DoDs are the bridge between "the AI generated code" and "this code is production-ready." They're the evidence that someone (human or AI) validated that the work meets the team's standards.&lt;/p&gt;

&lt;h2&gt;
  
  
  Backlog.md: the backlog that lives in your repo
&lt;/h2&gt;

&lt;p&gt;The tool I use for this is &lt;a href="https://backlog.md" rel="noopener noreferrer"&gt;Backlog.md&lt;/a&gt; — a task management system that lives directly in your repository as markdown files.&lt;/p&gt;

&lt;p&gt;Why not Jira or Trello? Because the AI can't read your Jira board. Backlog.md integrates with the IDE via MCP (Model Context Protocol), which means the AI can:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Read pending tasks&lt;/li&gt;
&lt;li&gt;Understand the context of what it needs to do&lt;/li&gt;
&lt;li&gt;Update status when it's done&lt;/li&gt;
&lt;li&gt;Check DoDs before marking something as Done&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Everything is versioned in git. Every task, every decision, every status change is a commit. Full traceability — you can see exactly what was done, when, and in what order.&lt;/p&gt;

&lt;p&gt;You don't need to leave your code to manage your work. The backlog is right there, next to your &lt;code&gt;src/&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;For personal projects and small teams, Backlog.md is ideal for its simplicity. In the enterprise, the concept is the same but the tool would connect to existing tooling — Jira, Linear, Azure DevOps. What matters isn't the specific tool, but that the AI has access to the backlog, can read task context, and can validate DoDs. The task-driven development pattern works the same regardless of where the tasks live.&lt;/p&gt;

&lt;h2&gt;
  
  
  Real-world example: Finia
&lt;/h2&gt;

&lt;p&gt;Finia is a Telegram bot I'm building for personal expense tracking in Costa Rica. It parses bank notifications, categorizes expenses with AI, generates reports, and supports multiple languages. The backend is Python with SQLAlchemy, runs on Kubernetes, and uses structlog for structured logging.&lt;/p&gt;

&lt;p&gt;All recent development was done with task-driven development. Here are three concrete examples:&lt;/p&gt;

&lt;h3&gt;
  
  
  Refactoring handle_message
&lt;/h3&gt;

&lt;p&gt;The main message handler was a 236-line monolith — a single &lt;code&gt;async def&lt;/code&gt; with all the logic for budget input, edit amount, rate limiting, chat agent, notifications, and tutorial. It worked, but it was impossible to test or modify without breaking something.&lt;/p&gt;

&lt;p&gt;I created TASK-2 with 4 subtasks:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Extract budget input handler&lt;/li&gt;
&lt;li&gt;Extract edit amount handler&lt;/li&gt;
&lt;li&gt;Extract chat agent invocation&lt;/li&gt;
&lt;li&gt;Extract notification sending and tutorial detection&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The AI executed each subtask separately. I reviewed each step. The result: 7 focused functions and a &lt;code&gt;handle_message&lt;/code&gt; of ~40 lines that only orchestrates. All 79 tests passed without changes.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Before: 236 lines in a single function
&lt;/span&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;handle_message&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;update&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="c1"&gt;# ... everything mixed together ...
&lt;/span&gt;
&lt;span class="c1"&gt;# After: clean orchestrator
&lt;/span&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;handle_message&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;update&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;get_user&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;update&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;effective_user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt;
    &lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;update&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;strip&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;_handle_budget_input&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;update&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;_handle_edit_amount&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;update&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;_check_rate_limit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;update&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt;

    &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;_run_chat_agent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;update&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;_send_expense_notifications&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;update&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  i18n sweep
&lt;/h3&gt;

&lt;p&gt;Finia supports Spanish and English. But after several development iterations, there were ~50 hardcoded English messages scattered across the codebase — buttons, errors, labels, prompts.&lt;/p&gt;

&lt;p&gt;I created TASK-4 and the AI made three passes:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Error messages and buttons in &lt;code&gt;callbacks.py&lt;/code&gt;, &lt;code&gt;commands.py&lt;/code&gt;, &lt;code&gt;chat.py&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Usage blocks (&lt;code&gt;/expense&lt;/code&gt;, &lt;code&gt;/sinpe&lt;/code&gt;), budget displays, category labels&lt;/li&gt;
&lt;li&gt;Change the pre-registration default language to Spanish (most users are Costa Rican)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;The DoD was clear: all user-facing messages translated, admin commands intentionally excluded. Each pass was reviewed before continuing.&lt;/p&gt;

&lt;h3&gt;
  
  
  Interaction tracking
&lt;/h3&gt;

&lt;p&gt;I needed metrics for a CloudWatch dashboard. I created TASK-5: add &lt;code&gt;command_executed&lt;/code&gt; to every command handler and &lt;code&gt;callback_executed&lt;/code&gt; to the callback handler.&lt;/p&gt;

&lt;p&gt;The pattern had to be consistent:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;command_executed&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;service&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;finia-bot&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;command&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;help&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;callback_executed&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;service&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;finia-bot&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;callback&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;chcat&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;During review, I caught that &lt;code&gt;/start&lt;/code&gt; was using &lt;code&gt;telegram_id&lt;/code&gt; instead of &lt;code&gt;user_id&lt;/code&gt; (because the user doesn't exist yet). We fixed it to include both. I also found that &lt;code&gt;commands.py&lt;/code&gt; hadn't been staged in the commit — the AI had edited it but didn't include it. Without step-by-step review, that would have shipped to production incomplete.&lt;/p&gt;

&lt;h2&gt;
  
  
  Vibe coding vs Task-driven: the comparison
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Vibe Coding&lt;/th&gt;
&lt;th&gt;Task-Driven&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Start&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;"Refactor this for me"&lt;/td&gt;
&lt;td&gt;TASK-2: Refactor handle_message, 4 subtasks defined&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Scope&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;The AI decides what to change&lt;/td&gt;
&lt;td&gt;You define what gets touched and what doesn't&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Validation&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;"Looks good"&lt;/td&gt;
&lt;td&gt;DoDs: tests pass, consistent pattern, no regressions&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Traceability&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Chat history (if it wasn't deleted)&lt;/td&gt;
&lt;td&gt;Tasks in git with status, plan, and notes&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Rollback&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Ctrl+Z and pray&lt;/td&gt;
&lt;td&gt;Each subtask is an atomic commit&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Enterprise-ready&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Yes&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;ExamGenius was chapter 1 — vibe coding works for prototypes and hackathons. Finia is chapter 2 — task-driven for projects that need to be maintained. The enterprise is chapter 3 — where this approach isn't a nice-to-have but a requirement.&lt;/p&gt;

&lt;h2&gt;
  
  
  The control is yours
&lt;/h2&gt;

&lt;p&gt;This isn't about stopping using AI. It's about using it well.&lt;/p&gt;

&lt;p&gt;Task-driven development isn't bureaucracy. It's giving the AI (and yourself) the structure to ship with confidence. You define the work, set the criteria, review the execution. The AI is incredibly capable — but it needs direction, just like any team member.&lt;/p&gt;

&lt;p&gt;The developer who defines work well, ships better software. The industry has moved forward — it's time our processes caught up.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Finia is a project I'm working on to simplify expense tracking in Costa Rica. If you're interested in trying it out or learning more, reach out — it's in beta and I'm always looking for feedback from early adopters.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>productivity</category>
      <category>devtools</category>
      <category>programming</category>
    </item>
    <item>
      <title>Dejé de vibecodeear y empecé a entregar: Task-Driven Development con AI</title>
      <dc:creator>Luis A. Obando</dc:creator>
      <pubDate>Fri, 20 Mar 2026 13:34:07 +0000</pubDate>
      <link>https://dev.to/luisom/deje-de-vibecodeear-y-empece-a-entregar-task-driven-development-con-ai-3fgn</link>
      <guid>https://dev.to/luisom/deje-de-vibecodeear-y-empece-a-entregar-task-driven-development-con-ai-3fgn</guid>
      <description>&lt;p&gt;Hace un año escribí sobre cómo construí &lt;a href="https://dev.to/luisom/de-idea-a-aplicacion-web-creando-examgenius-con-vibe-coding-y-claude-3k6l"&gt;ExamGenius con vibe coding y Claude&lt;/a&gt;. La premisa era simple: le dices al AI qué quieres, él genera el código, tú ajustas, y en 20 horas tienes una app completa que normalmente tomaría semanas. Reducción del 85% en tiempo. Código funcional. Todo bien.&lt;/p&gt;

&lt;p&gt;Un año después, la industria avanzó a pasos agigantados. Los modelos son más capaces, los IDEs integran AI de forma nativa, y cada vez más equipos están shipeando código generado por IA a producción. Pero hay un problema que nadie quiere admitir: &lt;strong&gt;estamos generando cantidades masivas de código que nadie revisó de verdad&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Se siente productivo. Pero el resultado muchas veces es código difícil de mantener, sin tests, sin estructura clara, y sin trazabilidad de por qué se tomaron ciertas decisiones. Vibe coding se siente bien hasta que tienes que debuggear algo que ni tú ni el AI recuerdan por qué existe.&lt;/p&gt;

&lt;p&gt;Este post es sobre lo que viene después del vibe coding.&lt;/p&gt;

&lt;h2&gt;
  
  
  El problema real: sin estructura, hasta el mejor dev genera ruido
&lt;/h2&gt;

&lt;p&gt;Seamos honestos: el AI produce código que muchos senior devs no podrían escribir tan rápido ni tan limpio. El problema no es la calidad del código — es la falta de dirección.&lt;/p&gt;

&lt;p&gt;Sin tareas claras, el AI divaga. Le dices "arregla este bug" y te refactoriza medio módulo. Sin scope definido, un "mejora esto" se convierte en un cambio de 15 archivos que nadie pidió. El dev pierde visibilidad de qué se hizo, qué falta, y qué se rompió en el camino. No es un problema de competencia — es un problema de contexto. El AI no sabe qué decisiones ya tomaste, qué patrones sigue tu codebase, ni cuál es el scope real de lo que necesitas.&lt;/p&gt;

&lt;p&gt;Con ExamGenius funcionó porque era un proyecto greenfield — una sola sesión, sin código legacy, sin otros colaboradores. Pero en proyectos reales que evolucionan semana a semana, el vibe coding puro no escala.&lt;/p&gt;

&lt;p&gt;Y en el enterprise — donde hay equipos, code reviews, compliance, y producción real — es simplemente insuficiente. No puedes llegar a un PR review diciendo "el AI lo generó y se veía bien". Necesitas poder explicar qué se hizo, por qué, y bajo qué criterios se validó.&lt;/p&gt;

&lt;h2&gt;
  
  
  La alternativa: Task-Driven Development
&lt;/h2&gt;

&lt;p&gt;La idea es simple: &lt;strong&gt;definir el trabajo antes de ejecutarlo&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;En vez de abrir el chat y decir "hazme esto", defines una tarea con scope claro, la descompones en subtasks si es necesario, y le das al AI el contexto preciso para ejecutar cada pieza. El flujo se ve así:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Spec&lt;/strong&gt; — defines qué quieres lograr y por qué&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Tasks&lt;/strong&gt; — descompones el trabajo en unidades manejables&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Subtasks&lt;/strong&gt; — si una tarea es grande, la partes más&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Plan&lt;/strong&gt; — antes de codear, el AI investiga el codebase y escribe un plan de implementación en la tarea. Tú lo revisas y apruebas antes de que toque una línea de código&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Ejecución&lt;/strong&gt; — el AI trabaja dentro del scope definido&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Revisión&lt;/strong&gt; — tú validas antes de avanzar&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;El paso 4 es clave y muchos se lo saltan. Pedirle al AI que planifique antes de ejecutar te da un checkpoint de revisión que vale oro. El plan refleja el estado actual del codebase — no es teoría, es una propuesta concreta que puedes aprobar, ajustar, o rechazar. Es la diferencia entre "hazlo" y "dime cómo lo harías, y luego lo haces".&lt;/p&gt;

&lt;p&gt;El programador vuelve a ser arquitecto y revisor, no espectador. Tú decides qué se hace, el AI ejecuta. Y si algo no te convence, lo corriges antes de que se propague.&lt;/p&gt;

&lt;p&gt;Y acá hay algo que cierra el círculo: el AI no solo ejecuta las tareas — también te ayuda a definirlas. Puedes pedirle que te sugiera subtasks, que identifique edge cases, o que proponga DoDs basados en el contexto del proyecto. Tú das la dirección general, el AI te ayuda a especificar con suficiente detalle, y luego ejecuta contra esa especificación. Es un ciclo colaborativo donde ambos aportan lo que mejor hacen.&lt;/p&gt;

&lt;p&gt;Esto aplica tanto para side projects como para equipos enterprise. La diferencia es que en enterprise no es opcional.&lt;/p&gt;

&lt;h2&gt;
  
  
  Definition of Done: el AI también tiene que cumplir criterios
&lt;/h2&gt;

&lt;p&gt;Acá es donde la cosa se pone interesante. Cada tarea puede tener un conjunto de &lt;strong&gt;Definition of Done (DoDs)&lt;/strong&gt; — criterios que deben cumplirse antes de marcarla como completa.&lt;/p&gt;

&lt;p&gt;Esto no es burocracia. Es calidad verificable:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Los tests pasan&lt;/li&gt;
&lt;li&gt;No hay regresiones&lt;/li&gt;
&lt;li&gt;El código sigue los patrones del proyecto&lt;/li&gt;
&lt;li&gt;Las traducciones están completas&lt;/li&gt;
&lt;li&gt;El logging es consistente&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;El AI deja de ser una caja negra que "genera código" y se convierte en un ejecutor que tiene que cumplir criterios concretos. Si no los cumple, la tarea no está terminada.&lt;/p&gt;

&lt;p&gt;En un contexto enterprise, los DoDs son el puente entre "el AI generó código" y "este código está listo para producción". Son la evidencia de que alguien (humano o AI) validó que el trabajo cumple con los estándares del equipo.&lt;/p&gt;

&lt;h2&gt;
  
  
  Backlog.md: el backlog que vive en tu repo
&lt;/h2&gt;

&lt;p&gt;La herramienta que uso para esto es &lt;a href="https://backlog.md" rel="noopener noreferrer"&gt;Backlog.md&lt;/a&gt; — un sistema de gestión de tareas que vive directamente en tu repositorio como archivos markdown.&lt;/p&gt;

&lt;p&gt;¿Por qué no Jira o Trello? Porque el AI no puede leer tu board de Jira. Backlog.md se integra con el IDE vía MCP (Model Context Protocol), lo que significa que el AI puede:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Leer las tareas pendientes&lt;/li&gt;
&lt;li&gt;Entender el contexto de lo que tiene que hacer&lt;/li&gt;
&lt;li&gt;Actualizar el estado cuando termina&lt;/li&gt;
&lt;li&gt;Consultar los DoDs antes de marcar algo como Done&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Todo queda versionado en git. Cada tarea, cada decisión, cada cambio de estado es un commit. La trazabilidad es total — puedes ver exactamente qué se hizo, cuándo, y en qué orden.&lt;/p&gt;

&lt;p&gt;No necesitas salir del código para gestionar tu trabajo. El backlog está ahí, al lado de tu &lt;code&gt;src/&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;Para proyectos personales y equipos pequeños, Backlog.md es ideal por su simplicidad. En el enterprise, el concepto es el mismo pero la herramienta se conectaría al tooling existente — Jira, Linear, Azure DevOps. Lo importante no es el tool específico, sino que el AI tenga acceso al backlog, pueda leer el contexto de las tareas, y pueda validar los DoDs. El patrón de task-driven development funciona igual independientemente de dónde vivan las tareas.&lt;/p&gt;

&lt;h2&gt;
  
  
  Caso real: Finia
&lt;/h2&gt;

&lt;p&gt;Finia es un bot de Telegram que estoy construyendo para tracking de gastos personales en Costa Rica. Parsea notificaciones bancarias, categoriza gastos con AI, genera reportes, y soporta múltiples idiomas. El backend es Python con SQLAlchemy, corre en Kubernetes, y usa structlog para logging estructurado.&lt;/p&gt;

&lt;p&gt;Todo el desarrollo reciente lo hice con task-driven development. Acá van tres ejemplos concretos:&lt;/p&gt;

&lt;h3&gt;
  
  
  Refactor de handle_message
&lt;/h3&gt;

&lt;p&gt;El handler principal de mensajes era un monolito de 236 líneas — un solo &lt;code&gt;async def&lt;/code&gt; con toda la lógica de budget input, edit amount, rate limiting, chat agent, notificaciones, y tutorial. Funcionaba, pero era imposible de testear o modificar sin romper algo.&lt;/p&gt;

&lt;p&gt;Creé TASK-2 con 4 subtasks:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Extraer budget input handler&lt;/li&gt;
&lt;li&gt;Extraer edit amount handler
&lt;/li&gt;
&lt;li&gt;Extraer chat agent invocation&lt;/li&gt;
&lt;li&gt;Extraer notification sending y tutorial detection&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;El AI ejecutó cada subtask por separado. Yo revisé cada paso. El resultado: 7 funciones enfocadas y un &lt;code&gt;handle_message&lt;/code&gt; de ~40 líneas que solo orquesta. Los 79 tests pasaron sin cambios.&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="c1"&gt;# Antes: 236 líneas en una sola función
&lt;/span&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;handle_message&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;update&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="c1"&gt;# ... todo mezclado ...
&lt;/span&gt;
&lt;span class="c1"&gt;# Después: orquestador limpio
&lt;/span&gt;&lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="k"&gt;def&lt;/span&gt; &lt;span class="nf"&gt;handle_message&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;update&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
    &lt;span class="n"&gt;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;get_user&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;update&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;effective_user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt; &lt;span class="ow"&gt;is&lt;/span&gt; &lt;span class="bp"&gt;None&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt;
    &lt;span class="n"&gt;text&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="n"&gt;update&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;message&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;strip&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;

    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;_handle_budget_input&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;update&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;_handle_edit_amount&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;update&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt;
    &lt;span class="k"&gt;if&lt;/span&gt; &lt;span class="ow"&gt;not&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;_check_rate_limit&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;update&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt;
        &lt;span class="k"&gt;return&lt;/span&gt;

    &lt;span class="n"&gt;response&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;_run_chat_agent&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;update&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;text&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
    &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nf"&gt;_send_expense_notifications&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="n"&gt;update&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;context&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;response&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  i18n sweep
&lt;/h3&gt;

&lt;p&gt;Finia soporta español e inglés. Pero después de varias iteraciones de desarrollo, había ~50 mensajes hardcodeados en inglés dispersos por todo el código — botones, errores, labels, prompts.&lt;/p&gt;

&lt;p&gt;Creé TASK-4 y el AI hizo tres pasadas:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Mensajes de error y botones en &lt;code&gt;callbacks.py&lt;/code&gt;, &lt;code&gt;commands.py&lt;/code&gt;, &lt;code&gt;chat.py&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Bloques de uso (&lt;code&gt;/expense&lt;/code&gt;, &lt;code&gt;/sinpe&lt;/code&gt;), displays de budget, labels de categorías&lt;/li&gt;
&lt;li&gt;Cambiar el idioma default pre-registro a español (la mayoría de usuarios son ticos)&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;El DoD era claro: todos los mensajes user-facing traducidos, admin commands excluidos intencionalmente. Cada pasada fue revisada antes de continuar.&lt;/p&gt;

&lt;h3&gt;
  
  
  Interaction tracking
&lt;/h3&gt;

&lt;p&gt;Necesitaba métricas para un dashboard de CloudWatch. Creé TASK-5: agregar &lt;code&gt;command_executed&lt;/code&gt; en cada command handler y &lt;code&gt;callback_executed&lt;/code&gt; en el callback handler.&lt;/p&gt;

&lt;p&gt;El patrón tenía que ser consistente:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight python"&gt;&lt;code&gt;&lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;command_executed&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;service&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;finia-bot&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;command&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;help&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;span class="n"&gt;logger&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;info&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;callback_executed&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;service&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;finia-bot&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;callback&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="s"&gt;chcat&lt;/span&gt;&lt;span class="sh"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="n"&gt;user_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;&lt;span class="n"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nb"&gt;id&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Durante la revisión, detecté que &lt;code&gt;/start&lt;/code&gt; usaba &lt;code&gt;telegram_id&lt;/code&gt; en vez de &lt;code&gt;user_id&lt;/code&gt; (porque el usuario aún no existe). Lo corregimos para incluir ambos. También encontré que &lt;code&gt;commands.py&lt;/code&gt; no se había incluido en el commit — el AI lo había editado pero no staged. Sin la revisión paso a paso, eso se habría ido a producción incompleto.&lt;/p&gt;

&lt;h2&gt;
  
  
  Vibe coding vs Task-driven: la comparación
&lt;/h2&gt;

&lt;div class="table-wrapper-paragraph"&gt;&lt;table&gt;
&lt;thead&gt;
&lt;tr&gt;
&lt;th&gt;&lt;/th&gt;
&lt;th&gt;Vibe Coding&lt;/th&gt;
&lt;th&gt;Task-Driven&lt;/th&gt;
&lt;/tr&gt;
&lt;/thead&gt;
&lt;tbody&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Inicio&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;"Hazme un refactor de esto"&lt;/td&gt;
&lt;td&gt;TASK-2: Refactor handle_message, 4 subtasks definidas&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Scope&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;El AI decide qué cambiar&lt;/td&gt;
&lt;td&gt;Tú defines qué se toca y qué no&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Validación&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;"Se ve bien"&lt;/td&gt;
&lt;td&gt;DoDs: tests pasan, patrón consistente, sin regresiones&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Trazabilidad&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Chat history (si no se borró)&lt;/td&gt;
&lt;td&gt;Tasks en git con estado, plan, y notas&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Rollback&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;Ctrl+Z y rezar&lt;/td&gt;
&lt;td&gt;Cada subtask es un commit atómico&lt;/td&gt;
&lt;/tr&gt;
&lt;tr&gt;
&lt;td&gt;&lt;strong&gt;Enterprise-ready&lt;/strong&gt;&lt;/td&gt;
&lt;td&gt;No&lt;/td&gt;
&lt;td&gt;Sí&lt;/td&gt;
&lt;/tr&gt;
&lt;/tbody&gt;
&lt;/table&gt;&lt;/div&gt;

&lt;p&gt;ExamGenius fue el capítulo 1 — vibe coding funciona para prototipos y hackathons. Finia es el capítulo 2 — task-driven para proyectos que necesitan mantenerse. El enterprise es el capítulo 3 — donde este enfoque no es un nice-to-have sino un requisito.&lt;/p&gt;

&lt;h2&gt;
  
  
  El control es tuyo
&lt;/h2&gt;

&lt;p&gt;No se trata de dejar de usar AI. Se trata de usarlo bien.&lt;/p&gt;

&lt;p&gt;Task-driven development no es burocracia. Es darle al AI (y a ti mismo) la estructura para entregar con confianza. Defines el trabajo, estableces los criterios, revisas la ejecución. El AI es increíblemente capaz — pero necesita dirección, igual que cualquier miembro de un equipo.&lt;/p&gt;

&lt;p&gt;El programador que define bien el trabajo, entrega mejor software. La industria avanzó — es hora de que nuestros procesos avancen también.&lt;/p&gt;




&lt;p&gt;&lt;em&gt;Finia es un proyecto en el que estoy trabajando para simplificar el tracking de gastos en Costa Rica. Si te interesa probarlo o saber más, escríbeme — está en beta y siempre busco feedback de early adopters.&lt;/em&gt;&lt;/p&gt;

</description>
      <category>ai</category>
      <category>productivity</category>
      <category>programming</category>
      <category>devtools</category>
    </item>
    <item>
      <title>From Idea to Web App: Building ExamGenius with Vibe Coding and Claude</title>
      <dc:creator>Luis A. Obando</dc:creator>
      <pubDate>Tue, 01 Apr 2025 03:57:05 +0000</pubDate>
      <link>https://dev.to/luisom-en/from-idea-to-web-app-building-examgenius-with-vibe-coding-and-claude-m2b</link>
      <guid>https://dev.to/luisom-en/from-idea-to-web-app-building-examgenius-with-vibe-coding-and-claude-m2b</guid>
      <description>&lt;p&gt;In the dynamic world of software development, finding more efficient ways to bring ideas to life has always been a constant pursuit. Recently, I completed a project that radically transformed my perspective on web app development: &lt;strong&gt;ExamGenius&lt;/strong&gt;, an application that generates practice exams from documents uploaded by students, using generative AI.&lt;/p&gt;

&lt;p&gt;What makes this project special isn't just its functionality, but &lt;strong&gt;how&lt;/strong&gt; it was built: using a "Vibe Coding" approach with Anthropic's Claude as my development copilot. In this article, I'll share this development experience, the challenges we faced, and how generative AI is changing the software development landscape.&lt;/p&gt;

&lt;h2&gt;
  
  
  What is "Vibe Coding"?
&lt;/h2&gt;

&lt;p&gt;Before diving into the technical details, let me explain what I mean by "Vibe Coding." It's a collaborative programming approach where a human and an AI work together, with the human providing strategic direction, requirements, and adjustments, while the AI handles much of the code generation and implementation of design patterns.&lt;/p&gt;

&lt;p&gt;It's different from simply using a coding assistant. With Vibe Coding, you're building an entire system in collaboration with AI, discussing architecture, making design decisions, and reviewing code together, almost like pair programming with an extremely competent digital partner.&lt;/p&gt;

&lt;h2&gt;
  
  
  ExamGenius: The Project
&lt;/h2&gt;

&lt;p&gt;ExamGenius is a web application that allows students to upload one or more PDF documents or photographs of textbooks or notes. The application uses AI to extract the text and generate a personalized practice exam that can be downloaded in PDF format.&lt;/p&gt;

&lt;p&gt;Key requirements included:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;A serverless microservices architecture on AWS&lt;/li&gt;
&lt;li&gt;Compliance with 12-factor app principles&lt;/li&gt;
&lt;li&gt;Modern frontend developed with Next.js&lt;/li&gt;
&lt;li&gt;Backend based on Lambda, S3, Step Functions, and Bedrock&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Technical Architecture
&lt;/h2&gt;

&lt;p&gt;The solution we built consists of two main components:&lt;/p&gt;

&lt;h3&gt;
  
  
  Serverless Backend
&lt;/h3&gt;

&lt;p&gt;We built a robust microservices architecture using AWS Lambda with the following components:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Upload Service&lt;/strong&gt;: Receives documents and stores them in S3, initiating a Step Functions workflow.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Extract Service&lt;/strong&gt;: Uses AWS Textract to extract text from PDF documents and images.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Generate Service&lt;/strong&gt;: Employs AWS Bedrock (Claude) to generate exams based on extracted content.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;PDF Service&lt;/strong&gt;: Converts the generated exam to downloadable PDF format.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Status Service&lt;/strong&gt;: Provides real-time progress updates.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;All infrastructure was defined as code using Terraform, following IaC best practices.&lt;/p&gt;

&lt;h3&gt;
  
  
  Next.js Frontend
&lt;/h3&gt;

&lt;p&gt;The frontend uses Next.js 14 with App Router and follows a modern design with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Tailwind CSS&lt;/strong&gt; for styling&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;React Query&lt;/strong&gt; for state management and synchronization&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Radix UI&lt;/strong&gt; for accessible components&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;React Hook Form&lt;/strong&gt; for form handling&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;We implemented an attractive, responsive design that guides the user through the exam generation process and displays real-time progress updates.&lt;/p&gt;

&lt;h2&gt;
  
  
  The Vibe Coding Process with Claude
&lt;/h2&gt;

&lt;p&gt;Now, let's look at the actual process of building ExamGenius. Unlike traditional development, where I would have started by writing detailed documentation, setting up repositories, and manually creating each component, with Claude we adopted a more fluid and exploratory approach.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Initial Definition
&lt;/h3&gt;

&lt;p&gt;I started with a simple definition:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;I want to create a GenAI-based web application called "ExamGenius" that allows 
students to upload one or more PDF documents or photographs of textbooks or notebooks 
and based on this information creates a practice exam that can be downloaded as a PDF.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Claude immediately proposed a serverless microservices architecture on AWS, with key components like AWS Bedrock, Textract, S3, Lambda, and Step Functions. From there, we began to iterate.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Architecture and Flow Definition
&lt;/h3&gt;

&lt;p&gt;Within minutes, Claude produced detailed diagrams using Mermaid to visualize both the microservices architecture and the application workflow:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxz60x37uksx1l3w8cn1x.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fxz60x37uksx1l3w8cn1x.png" alt="Diagram" width="800" height="594"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Backend Development
&lt;/h3&gt;

&lt;p&gt;With the architecture agreed upon, we proceeded to build each Lambda service. Claude generated complete and well-structured code for each component, including:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Proper error handling&lt;/li&gt;
&lt;li&gt;Step Functions configuration&lt;/li&gt;
&lt;li&gt;Terraform code for infrastructure&lt;/li&gt;
&lt;li&gt;Packaging scripts&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Interestingly, Claude could maintain an extraordinarily coherent context across multiple services, ensuring everything integrated correctly.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Frontend Development
&lt;/h3&gt;

&lt;p&gt;In parallel, we developed a modern frontend using Next.js. Claude surprised me with its ability to follow current best practices:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Component-based React App Router&lt;/li&gt;
&lt;li&gt;Custom hooks for business logic&lt;/li&gt;
&lt;li&gt;Asynchronous loading pattern with loading/success/error states&lt;/li&gt;
&lt;li&gt;Real-time visual feedback of progress&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Here's an example of an upload component that Claude generated:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;use client&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;React&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useState&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;useCallback&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;react&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useDropzone&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;react-dropzone&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;FileUp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;X&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;AlertCircle&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;FileText&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;lucide-react&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Button&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@/components/ui/button&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Card&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;CardContent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;CardFooter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;CardHeader&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;CardTitle&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@/components/ui/card&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;validateFile&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@/utils/validation&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;formatFileSize&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@/utils/format&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useUpload&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@/hooks/use-upload&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;UploadForm&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;onSuccess&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;className&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;files&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setFiles&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useState&lt;/span&gt;&lt;span class="p"&gt;([]);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;uploadFiles&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;isUploading&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useUpload&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;onDrop&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useCallback&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;acceptedFiles&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;validFiles&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;acceptedFiles&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;file&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;validateFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;valid&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nf"&gt;setFiles&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;prevFiles&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[...&lt;/span&gt;&lt;span class="nx"&gt;prevFiles&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;validFiles&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;[]);&lt;/span&gt;

  &lt;span class="c1"&gt;// Remaining implementation...&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  5. Iteration and Refinement
&lt;/h3&gt;

&lt;p&gt;One of the most valuable aspects of the Vibe Coding approach was the ability to iterate quickly. When we realized that the &lt;code&gt;jobs/status&lt;/code&gt; endpoint was missing to query the status of jobs, for example, I simply mentioned the issue and Claude generated a complete implementation, including proper IAM permissions handling.&lt;/p&gt;

&lt;h2&gt;
  
  
  Challenges and Learnings
&lt;/h2&gt;

&lt;p&gt;This approach wasn't without challenges:&lt;/p&gt;

&lt;h3&gt;
  
  
  1. CORS Issues
&lt;/h3&gt;

&lt;p&gt;We faced typical CORS problems when developing locally. Claude not only correctly identified the issue but proposed multiple solutions:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Configure CORS in API Gateway&lt;/li&gt;
&lt;li&gt;Set up CORS in Lambda responses&lt;/li&gt;
&lt;li&gt;Implement a temporary proxy in Next.js&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  2. IAM Permissions
&lt;/h3&gt;

&lt;p&gt;A frequent challenge in AWS applications is the correct configuration of permissions. When the status Lambda couldn't list Step Functions executions, Claude identified that we needed a more specific IAM policy:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;Effect&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Allow"&lt;/span&gt;
  &lt;span class="nx"&gt;Action&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="s2"&gt;"states:ListExecutions"&lt;/span&gt;
  &lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="nx"&gt;Resource&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="nx"&gt;aws_sfn_state_machine&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exam_generation_workflow&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;arn&lt;/span&gt;
  &lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  3. Styles Loading
&lt;/h3&gt;

&lt;p&gt;When we had issues with styles loading in the frontend, Claude provided comprehensive solutions, including checks of the global.css file, Tailwind configuration, and import alternatives.&lt;/p&gt;

&lt;h2&gt;
  
  
  Comparison with Traditional Development
&lt;/h2&gt;

&lt;p&gt;To put the value of this approach in perspective, let's compare the time and resources used:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Traditional Development:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Creation of detailed documentation: 1-2 days&lt;/li&gt;
&lt;li&gt;Repository and environment setup: 0.5 days&lt;/li&gt;
&lt;li&gt;AWS backend development: 5-7 days&lt;/li&gt;
&lt;li&gt;Frontend development: 4-5 days&lt;/li&gt;
&lt;li&gt;Testing and integration: 2-3 days
&lt;strong&gt;Total:&lt;/strong&gt; 12.5-17.5 days (100-140 hours)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Vibe Coding with Claude:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Initial definition and architecture: 2 hours&lt;/li&gt;
&lt;li&gt;Backend development: 8 hours&lt;/li&gt;
&lt;li&gt;Frontend development: 6 hours&lt;/li&gt;
&lt;li&gt;Iteration and corrections: 4 hours
&lt;strong&gt;Total:&lt;/strong&gt; 20 hours&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This comparison is revealing: the time required was reduced by approximately 85%, without compromising code quality or best practices.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusions: The Future of Software Development
&lt;/h2&gt;

&lt;p&gt;This experience has led me to several important conclusions:&lt;/p&gt;

&lt;h3&gt;
  
  
  1. The Developer Role is Evolving
&lt;/h3&gt;

&lt;p&gt;Instead of writing every line of code, the future developer might focus more on:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Clearly defining problems&lt;/li&gt;
&lt;li&gt;Making strategic architectural decisions&lt;/li&gt;
&lt;li&gt;Validating and refining generated code&lt;/li&gt;
&lt;li&gt;Providing the business context that AI lacks&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  2. Democratization of Development
&lt;/h3&gt;

&lt;p&gt;AI tools like Claude are democratizing software development, allowing individuals with limited technical knowledge to build complex solutions.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Code Quality
&lt;/h3&gt;

&lt;p&gt;Contrary to what might be expected, the generated code followed good practices, was well-structured and documented, and used modern patterns.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Speed with Quality
&lt;/h3&gt;

&lt;p&gt;Perhaps most impressively: we didn't have to choose between speed and quality. We got both.&lt;/p&gt;

&lt;h2&gt;
  
  
  Is This the Future?
&lt;/h2&gt;

&lt;p&gt;ExamGenius is just one example of what's possible with AI-assisted programming. While this approach has limitations (especially in very large or highly specialized projects), it clearly shows a future where developers and AI work in tandem, combining human creativity and context with AI speed and precision.&lt;/p&gt;

&lt;p&gt;The next time you have an idea for a web application, consider the Vibe Coding approach. It's not about replacing developers but empowering them, allowing them to focus on what they do best: solving real problems for real users.&lt;/p&gt;




&lt;p&gt;Have you experimented with similar approaches in your development? What have been your experiences with AI programming tools? Share your thoughts in the comments.&lt;/p&gt;

</description>
      <category>vibecoding</category>
      <category>genai</category>
    </item>
    <item>
      <title>De Idea a Aplicación Web: Creando ExamGenius con Vibe Coding y Claude</title>
      <dc:creator>Luis A. Obando</dc:creator>
      <pubDate>Tue, 01 Apr 2025 02:33:29 +0000</pubDate>
      <link>https://dev.to/luisom/de-idea-a-aplicacion-web-creando-examgenius-con-vibe-coding-y-claude-3k6l</link>
      <guid>https://dev.to/luisom/de-idea-a-aplicacion-web-creando-examgenius-con-vibe-coding-y-claude-3k6l</guid>
      <description>&lt;p&gt;En el dinámico mundo del desarrollo de software, encontrar formas más eficientes de materializar ideas ha sido siempre una búsqueda constante. Recientemente, completé un proyecto que transformó radicalmente mi perspectiva sobre el desarrollo de aplicaciones web: &lt;strong&gt;ExamGenius&lt;/strong&gt;, una aplicación que permite generar exámenes de práctica a partir de documentos subidos por estudiantes, utilizando IA generativa.&lt;/p&gt;

&lt;p&gt;Lo que hace a este proyecto especial no es solo su funcionalidad, sino &lt;strong&gt;cómo&lt;/strong&gt; se construyó: utilizando un enfoque de "Vibe Coding" con Claude de Anthropic como mi copiloto de desarrollo. En este artículo, compartiré esta experiencia de desarrollo, los desafíos que enfrentamos, y cómo la IA generativa está cambiando el panorama del desarrollo de software.&lt;/p&gt;

&lt;h2&gt;
  
  
  ¿Qué es "Vibe Coding"?
&lt;/h2&gt;

&lt;p&gt;Antes de sumergirme en los detalles técnicos, permítanme explicar lo que entiendo por "Vibe Coding". Es un enfoque de programación colaborativa donde un humano y una IA trabajan juntos, el humano proporcionando la dirección estratégica, requisitos y ajustes, mientras la IA maneja gran parte de la generación de código y la implementación de patrones de diseño. &lt;/p&gt;

&lt;p&gt;Es diferente de simplemente usar un asistente de codificación. Con Vibe Coding, estás construyendo un sistema completo en colaboración con la IA, discutiendo arquitectura, tomando decisiones de diseño y revisando código juntos, casi como pair programming con un compañero digital extremadamente competente.&lt;/p&gt;

&lt;h2&gt;
  
  
  ExamGenius: El Proyecto
&lt;/h2&gt;

&lt;p&gt;ExamGenius es una aplicación web que permite a los estudiantes cargar uno o varios documentos PDF o fotografías de libros de texto o apuntes. La aplicación utiliza IA para extraer el texto y generar un examen de práctica personalizado que puede descargarse en formato PDF.&lt;/p&gt;

&lt;p&gt;Los requisitos clave incluían:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Una arquitectura de microservicios serverless en AWS&lt;/li&gt;
&lt;li&gt;Cumplimiento con los principios de 12-factor app&lt;/li&gt;
&lt;li&gt;Frontend moderno desarrollado con Next.js&lt;/li&gt;
&lt;li&gt;Backend basado en Lambda, S3, Step Functions y Bedrock&lt;/li&gt;
&lt;/ul&gt;

&lt;h2&gt;
  
  
  Arquitectura Técnica
&lt;/h2&gt;

&lt;p&gt;La solución que construimos consta de dos componentes principales:&lt;/p&gt;

&lt;h3&gt;
  
  
  Backend Serverless
&lt;/h3&gt;

&lt;p&gt;Construimos una sólida arquitectura de microservicios utilizando AWS Lambda con los siguientes componentes:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Upload Service&lt;/strong&gt;: Recibe documentos y los almacena en S3, iniciando un flujo en Step Functions.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Extract Service&lt;/strong&gt;: Utiliza AWS Textract para extraer texto de documentos PDF e imágenes.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Generate Service&lt;/strong&gt;: Emplea AWS Bedrock (Claude) para generar exámenes basados en el contenido extraído.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;PDF Service&lt;/strong&gt;: Convierte el examen generado a formato PDF descargable.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Status Service&lt;/strong&gt;: Proporciona actualización en tiempo real del progreso.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Toda la infraestructura se definió como código utilizando Terraform, siguiendo las mejores prácticas de IaC.&lt;/p&gt;

&lt;h3&gt;
  
  
  Frontend con Next.js
&lt;/h3&gt;

&lt;p&gt;El frontend utiliza Next.js 14 con App Router y sigue un diseño moderno con:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Tailwind CSS&lt;/strong&gt; para el estilizado&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;React Query&lt;/strong&gt; para manejo de estado y sincronización&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Radix UI&lt;/strong&gt; para componentes accesibles&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;React Hook Form&lt;/strong&gt; para manejo de formularios&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Implementamos un diseño atractivo y responsive que guía al usuario a través del proceso de generación de exámenes y muestra actualizaciones de progreso en tiempo real.&lt;/p&gt;

&lt;h2&gt;
  
  
  El Proceso de Vibe Coding con Claude
&lt;/h2&gt;

&lt;p&gt;Ahora, veamos cómo fue el proceso real de construir ExamGenius. A diferencia del desarrollo tradicional, donde habría comenzado escribiendo documentación detallada, configurando repositorios y creando manualmente cada componente, con Claude adoptamos un enfoque más fluido y exploratorio.&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Definición Inicial
&lt;/h3&gt;

&lt;p&gt;Comencé con una definición simple:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;Quiero crear una aplicación web basada en GenAI, la aplicación se llama "ExamGenius", 
esta app permite a los estudiantes cargar uno o varios documentos PDF o fotografías 
de libros de texto o de su cuaderno y basado en esta información crea un examen de 
práctica que pueda ser descargado en formato PDF.
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Claude inmediatamente propuso una arquitectura de microservicios serverless en AWS, con componentes clave como AWS Bedrock, Textract, S3, Lambda y Step Functions. A partir de ahí, comenzamos a iterar.&lt;/p&gt;

&lt;h3&gt;
  
  
  2. Definición de Arquitectura y Flujo
&lt;/h3&gt;

&lt;p&gt;En pocos minutos, Claude produjo diagramas detallados utilizando Mermaid para visualizar tanto la arquitectura de microservicios como el flujo de trabajo de la aplicación:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdwrusa4qlpqx1i6ivs44.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fdwrusa4qlpqx1i6ivs44.png" alt="Diagrama" width="800" height="621"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Desarrollo del Backend
&lt;/h3&gt;

&lt;p&gt;Con la arquitectura acordada, procedimos a construir cada servicio Lambda. Claude generó código completo y bien estructurado para cada componente, incluyendo:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Manejo adecuado de errores&lt;/li&gt;
&lt;li&gt;Configuración de Step Functions&lt;/li&gt;
&lt;li&gt;Código Terraform para la infraestructura&lt;/li&gt;
&lt;li&gt;Scripts de empaquetado&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Lo interesante fue que Claude podía mantener un contexto extraordinariamente coherente a lo largo de múltiples servicios, asegurando que todo se integrara correctamente.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Desarrollo del Frontend
&lt;/h3&gt;

&lt;p&gt;En paralelo, desarrollamos un frontend moderno utilizando Next.js. Claude me sorprendió con su capacidad para seguir las mejores prácticas actuales:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;App Router basado en componentes React&lt;/li&gt;
&lt;li&gt;Hooks personalizados para lógica de negocio&lt;/li&gt;
&lt;li&gt;Patrón de carga asíncrona con estados de carga/éxito/error&lt;/li&gt;
&lt;li&gt;Feedback visual en tiempo real del progreso&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Aquí hay un ejemplo de un componente de carga que Claude generó:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight tsx"&gt;&lt;code&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;use client&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="nx"&gt;React&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useState&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;useCallback&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;react&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useDropzone&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;react-dropzone&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;FileUp&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;X&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;AlertCircle&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;FileText&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;lucide-react&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Button&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@/components/ui/button&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;Card&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;CardContent&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;CardFooter&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;CardHeader&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;CardTitle&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@/components/ui/card&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;validateFile&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@/utils/validation&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;formatFileSize&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@/utils/format&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
&lt;span class="k"&gt;import&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;useUpload&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="k"&gt;from&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;@/hooks/use-upload&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;function&lt;/span&gt; &lt;span class="nf"&gt;UploadForm&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;onSuccess&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;className&lt;/span&gt; &lt;span class="p"&gt;})&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="nx"&gt;files&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;setFiles&lt;/span&gt;&lt;span class="p"&gt;]&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useState&lt;/span&gt;&lt;span class="p"&gt;([]);&lt;/span&gt;
  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;uploadFiles&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;isUploading&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;error&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;result&lt;/span&gt; &lt;span class="p"&gt;}&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useUpload&lt;/span&gt;&lt;span class="p"&gt;();&lt;/span&gt;

  &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;onDrop&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nf"&gt;useCallback&lt;/span&gt;&lt;span class="p"&gt;((&lt;/span&gt;&lt;span class="nx"&gt;acceptedFiles&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="kd"&gt;const&lt;/span&gt; &lt;span class="nx"&gt;validFiles&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="nx"&gt;acceptedFiles&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;filter&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;file&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="nf"&gt;validateFile&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;file&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nx"&gt;valid&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
    &lt;span class="nf"&gt;setFiles&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;prevFiles&lt;/span&gt; &lt;span class="o"&gt;=&amp;gt;&lt;/span&gt; &lt;span class="p"&gt;[...&lt;/span&gt;&lt;span class="nx"&gt;prevFiles&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="p"&gt;...&lt;/span&gt;&lt;span class="nx"&gt;validFiles&lt;/span&gt;&lt;span class="p"&gt;]);&lt;/span&gt;
  &lt;span class="p"&gt;},&lt;/span&gt; &lt;span class="p"&gt;[]);&lt;/span&gt;

  &lt;span class="c1"&gt;// Implementación restante...&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Y acá vemos unas imágenes del diseño final generado&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7zngqjzu1w5nbyb11acp.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F7zngqjzu1w5nbyb11acp.png" alt="Image description" width="800" height="419"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;h3&gt;
  
  
  5. Iteración y Refinamiento
&lt;/h3&gt;

&lt;p&gt;Uno de los aspectos más valiosos del enfoque de Vibe Coding fue la capacidad de iterar rápidamente. Cuando nos dimos cuenta de que faltaba el endpoint &lt;code&gt;jobs/status&lt;/code&gt; para consultar el estado de los trabajos, por ejemplo, simplemente mencioné el problema y Claude generó una implementación completa, incluyendo manejo de permisos IAM adecuados.&lt;/p&gt;

&lt;h2&gt;
  
  
  Desafíos y Aprendizajes
&lt;/h2&gt;

&lt;p&gt;Este enfoque no estuvo exento de desafíos:&lt;/p&gt;

&lt;h3&gt;
  
  
  1. Problemas de CORS
&lt;/h3&gt;

&lt;p&gt;Enfrentamos problemas de CORS típicos al desarrollar localmente. Claude no solo identificó correctamente el problema, sino que propuso múltiples soluciones:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Configurar CORS en API Gateway&lt;/li&gt;
&lt;li&gt;Configurar CORS en las respuestas Lambda&lt;/li&gt;
&lt;li&gt;Implementar un proxy temporal en Next.js&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  2. Permisos IAM
&lt;/h3&gt;

&lt;p&gt;Un desafío frecuente en aplicaciones AWS es la configuración correcta de permisos. Cuando la Lambda de status no podía listar ejecuciones de Step Functions, Claude identificó que necesitábamos una política IAM más específica:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight hcl"&gt;&lt;code&gt;&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="nx"&gt;Effect&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="s2"&gt;"Allow"&lt;/span&gt;
  &lt;span class="nx"&gt;Action&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="s2"&gt;"states:ListExecutions"&lt;/span&gt;
  &lt;span class="p"&gt;]&lt;/span&gt;
  &lt;span class="nx"&gt;Resource&lt;/span&gt; &lt;span class="p"&gt;=&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;
    &lt;span class="nx"&gt;aws_sfn_state_machine&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;exam_generation_workflow&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;arn&lt;/span&gt;
  &lt;span class="p"&gt;]&lt;/span&gt;
&lt;span class="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  3. Carga de Estilos
&lt;/h3&gt;

&lt;p&gt;Cuando tuvimos problemas con la carga de estilos en el frontend, Claude proporcionó soluciones exhaustivas, incluyendo verificaciones del archivo global.css, configuración de Tailwind, y alternativas de importación.&lt;/p&gt;

&lt;h2&gt;
  
  
  Comparación con el Desarrollo Tradicional
&lt;/h2&gt;

&lt;p&gt;Para poner en perspectiva el valor de este enfoque, comparemos el tiempo y recursos utilizados:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Desarrollo Tradicional:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Creación de documentación detallada: 1-2 días&lt;/li&gt;
&lt;li&gt;Configuración de repositorio y entorno: 0.5 días&lt;/li&gt;
&lt;li&gt;Desarrollo del backend con AWS: 5-7 días&lt;/li&gt;
&lt;li&gt;Desarrollo del frontend: 4-5 días&lt;/li&gt;
&lt;li&gt;Testing e integración: 2-3 días
&lt;strong&gt;Total:&lt;/strong&gt; 12.5-17.5 días (100-140 horas)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Vibe Coding con Claude:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Definición inicial y arquitectura: 2 horas&lt;/li&gt;
&lt;li&gt;Desarrollo del backend: 8 horas&lt;/li&gt;
&lt;li&gt;Desarrollo del frontend: 6 horas&lt;/li&gt;
&lt;li&gt;Iteración y correcciones: 4 horas
&lt;strong&gt;Total:&lt;/strong&gt; 20 horas&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Esta comparación es reveladora: el tiempo necesario se redujo aproximadamente un 85%, sin comprometer la calidad del código o las mejores prácticas.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusiones: El Futuro del Desarrollo de Software
&lt;/h2&gt;

&lt;p&gt;Esta experiencia me ha llevado a varias conclusiones importantes:&lt;/p&gt;

&lt;h3&gt;
  
  
  1. El Rol del Desarrollador está Evolucionando
&lt;/h3&gt;

&lt;p&gt;En lugar de escribir cada línea de código, el futuro desarrollador podría enfocarse más en:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Definir problemas con claridad&lt;/li&gt;
&lt;li&gt;Tomar decisiones arquitectónicas estratégicas&lt;/li&gt;
&lt;li&gt;Validar y refinar el código generado&lt;/li&gt;
&lt;li&gt;Aportar el contexto de negocio que la IA no posee&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  2. Democratización del Desarrollo
&lt;/h3&gt;

&lt;p&gt;Las herramientas de IA como Claude están democratizando el desarrollo de software, permitiendo que individuos con conocimientos técnicos limitados construyan soluciones complejas.&lt;/p&gt;

&lt;h3&gt;
  
  
  3. Calidad del Código
&lt;/h3&gt;

&lt;p&gt;Contrario a lo que podría esperarse, el código generado seguía buenas prácticas, estaba bien estructurado y documentado, y utilizaba patrones modernos.&lt;/p&gt;

&lt;h3&gt;
  
  
  4. Velocidad con Calidad
&lt;/h3&gt;

&lt;p&gt;Quizás lo más impresionante: no tuvimos que elegir entre velocidad y calidad. Obtuvimos ambas.&lt;/p&gt;

&lt;h2&gt;
  
  
  ¿Es Este el Futuro?
&lt;/h2&gt;

&lt;p&gt;ExamGenius es solo un ejemplo de lo que es posible con la programación asistida por IA. Si bien este enfoque tiene limitaciones (especialmente en proyectos muy grandes o altamente especializados), muestra claramente un futuro donde los desarrolladores y la IA trabajan en tándem, combinando la creatividad y contexto humano con la velocidad y precisión de la IA.&lt;/p&gt;

&lt;p&gt;La próxima vez que tengas una idea para una aplicación web, considera el enfoque de Vibe Coding. No se trata de reemplazar a los desarrolladores, sino de potenciarlos, permitiéndoles enfocarse en lo que hacen mejor: resolver problemas reales para usuarios reales.&lt;/p&gt;




&lt;p&gt;¿Has experimentado con enfoques similares en tu desarrollo? ¿Cuáles han sido tus experiencias con herramientas de IA para programación? Comparte tus pensamientos en los comentarios.&lt;/p&gt;

</description>
      <category>spanish</category>
      <category>vibecoding</category>
      <category>genai</category>
    </item>
  </channel>
</rss>
