<?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: Jorge Gomez</title>
    <description>The latest articles on DEV Community by Jorge Gomez (@jorge6242).</description>
    <link>https://dev.to/jorge6242</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%2F500438%2Fe9f208a4-113a-4e54-8b01-02ede84c3bc5.jpeg</url>
      <title>DEV Community: Jorge Gomez</title>
      <link>https://dev.to/jorge6242</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/jorge6242"/>
    <language>en</language>
    <item>
      <title>FRD Orchestration with MCP: Deterministic Context Control for Agents (No Drift)</title>
      <dc:creator>Jorge Gomez</dc:creator>
      <pubDate>Wed, 31 Dec 2025 18:34:57 +0000</pubDate>
      <link>https://dev.to/jorge6242/frd-orchestration-with-mcp-deterministic-context-control-for-agents-no-drift-266k</link>
      <guid>https://dev.to/jorge6242/frd-orchestration-with-mcp-deterministic-context-control-for-agents-no-drift-266k</guid>
      <description>&lt;p&gt;`Some time ago, I wrote this &lt;a href="https://dev.to/jorge6242/how-i-used-ai-frds-claude-sonnet-windsurf-and-antigravity-to-orchestrate-a-complete-backend-1jml"&gt;article&lt;/a&gt;, explaining the value of orchestrating multiple Feature Requirement Documents (FRDs) to keep full control over building a NestJS boilerplate from scratch, step by step.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;FRD-00: orchestration&lt;/li&gt;
&lt;li&gt;FRD-01: skeleton&lt;/li&gt;
&lt;li&gt;FRD-02: ORM + migrations&lt;/li&gt;
&lt;li&gt;FRD-03: auth + JWT&lt;/li&gt;
&lt;li&gt;FRD-04: unit testing&lt;/li&gt;
&lt;/ul&gt;




&lt;p&gt;&lt;strong&gt;It worked&lt;/strong&gt;, but a classic problem showed up: when FRDs live as “loose” files in the workspace, the agent can &lt;strong&gt;read the wrong .md&lt;/strong&gt;, mix phases, or cache the wrong context.&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Before:&lt;/strong&gt; FRDs as files inside a folder → the agent might read the wrong document / mix phases / cache context.&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Now:&lt;/strong&gt; an &lt;strong&gt;MCP tool&lt;/strong&gt; &lt;code&gt;get_frd("00".."04")&lt;/code&gt; that returns the correct FRD by ID → phase-controlled delegation, less drift, and more repeatability.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  Context: previous approach (file-based context)
&lt;/h2&gt;

&lt;p&gt;In the earlier article, I showed the benefits of using &lt;strong&gt;Feature Requirement Documents (FRDs)&lt;/strong&gt; to guide an agent through building a boilerplate. It worked… but it had one major weakness: the agent relied on “correctly reading” files inside a folder.&lt;/p&gt;

&lt;p&gt;The workspace looked like this:&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%2Flag9djipy3l9wtjyf8cf.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%2Flag9djipy3l9wtjyf8cf.png" alt=" " width="499" height="409"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;The initial input I gave the agent was:&lt;br&gt;
`&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight json"&gt;&lt;code&gt;&lt;span class="err"&gt;folder&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;name:&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;`api-products`&lt;/span&gt;&lt;span class="w"&gt;  
 &lt;/span&gt;&lt;span class="err"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;FRD&lt;/span&gt;&lt;span class="mi"&gt;-00&lt;/span&gt;&lt;span class="err"&gt;-master-orchestration.md&lt;/span&gt;&lt;span class="w"&gt;  
 &lt;/span&gt;&lt;span class="err"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;FRD&lt;/span&gt;&lt;span class="mi"&gt;-01&lt;/span&gt;&lt;span class="err"&gt;-boilerplate-core-products.md&lt;/span&gt;&lt;span class="w"&gt;  
 &lt;/span&gt;&lt;span class="err"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;FRD&lt;/span&gt;&lt;span class="mi"&gt;-02&lt;/span&gt;&lt;span class="err"&gt;-products-database.md&lt;/span&gt;&lt;span class="w"&gt;  
 &lt;/span&gt;&lt;span class="err"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;FRD&lt;/span&gt;&lt;span class="mi"&gt;-03&lt;/span&gt;&lt;span class="err"&gt;-auth-security.md&lt;/span&gt;&lt;span class="w"&gt;  
 &lt;/span&gt;&lt;span class="err"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;FRD&lt;/span&gt;&lt;span class="mi"&gt;-04&lt;/span&gt;&lt;span class="err"&gt;-unit-testing.md&lt;/span&gt;&lt;span class="w"&gt;  
 &lt;/span&gt;&lt;span class="err"&gt;-&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;Start&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;the&lt;/span&gt;&lt;span class="w"&gt; &lt;/span&gt;&lt;span class="err"&gt;orchestration..&lt;/span&gt;&lt;span class="w"&gt;
&lt;/span&gt;&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;br&gt;
`&lt;/p&gt;

&lt;p&gt;Even though &lt;strong&gt;FRD-00&lt;/strong&gt; defines the order and delegates each phase, the weak point was still there: &lt;strong&gt;file selection inside the workspace&lt;/strong&gt;.&lt;/p&gt;


&lt;h2&gt;
  
  
  The real problem: an ambiguous context “selector”
&lt;/h2&gt;

&lt;p&gt;When an agent works with file-based context from the workspace, the typical failures are:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Reading the wrong &lt;code&gt;.md&lt;/code&gt;.&lt;/li&gt;
&lt;li&gt;Mixing phases (skipping steps).&lt;/li&gt;
&lt;li&gt;Running commands outside the target folder.&lt;/li&gt;
&lt;/ul&gt;
&lt;h2&gt;
  
  
  In short: execution stops being reproducible because a critical part depends on a &lt;strong&gt;non-deterministic&lt;/strong&gt; step.
&lt;/h2&gt;
&lt;h2&gt;
  
  
  Solution: deterministic retrieval via MCP
&lt;/h2&gt;

&lt;p&gt;To eliminate that weak point, I moved FRD reading to a deterministic mechanism using &lt;strong&gt;Model Context Protocol (MCP)&lt;/strong&gt;.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;&lt;strong&gt;What is MCP?&lt;/strong&gt; Model Context Protocol (MCP) is a protocol that lets you expose &lt;em&gt;tools&lt;/em&gt; (functions) to an agent/IDE so it can request information in a controlled way, instead of “guessing” by reading workspace files.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;Instead of “searching for files,” the agent &lt;strong&gt;requests exactly&lt;/strong&gt; the document it needs through a tool:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;get_frd("00")&lt;/code&gt; → main orchestration&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;get_frd("01")&lt;/code&gt; → phase 1&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;get_frd("02")&lt;/code&gt; → phase 2&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;get_frd("03")&lt;/code&gt; → phase 3&lt;/li&gt;
&lt;li&gt;
&lt;code&gt;get_frd("04")&lt;/code&gt; → phase 4&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  Python tool implementation
&lt;/h3&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%2Fve4bjsx7wraj0ttjzni3.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%2Fve4bjsx7wraj0ttjzni3.png" alt=" " width="306" height="366"&gt;&lt;/a&gt;&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%2F14nfa2cgfuowwlsgeyfw.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%2F14nfa2cgfuowwlsgeyfw.png" alt=" " width="550" height="328"&gt;&lt;/a&gt;&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%2F9rx2eerbu44mc0po6xx3.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%2F9rx2eerbu44mc0po6xx3.png" alt=" " width="515" height="457"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Result:&lt;/strong&gt; orchestration no longer depends on the agent’s “file picking.” At each phase, the agent requests &lt;strong&gt;the correct FRD&lt;/strong&gt;, in the correct order.&lt;/p&gt;


&lt;h2&gt;
  
  
  Why this improves orchestration (and it’s not just “more organization”)
&lt;/h2&gt;
&lt;h3&gt;
  
  
  1) Less drift
&lt;/h3&gt;

&lt;p&gt;The agent doesn’t “infer” which file to read: it is &lt;strong&gt;given&lt;/strong&gt; the exact document.&lt;/p&gt;
&lt;h3&gt;
  
  
  2) More repeatability
&lt;/h3&gt;

&lt;p&gt;Two separate runs tend to produce the same outcome because the spec input is stable.&lt;/p&gt;
&lt;h3&gt;
  
  
  3) Phase-controlled delegation
&lt;/h3&gt;

&lt;p&gt;The active phase determines which FRD is exposed. This even allows adding policies such as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;“You can’t request FRD-03 unless FRD-02 is completed.”&lt;/li&gt;
&lt;li&gt;“Always request FRD-00 before any other.”&lt;/li&gt;
&lt;/ul&gt;


&lt;h2&gt;
  
  
  Demo (operational evidence)
&lt;/h2&gt;

&lt;p&gt;In VSCode, open the &lt;strong&gt;MCP Servers&lt;/strong&gt; view and verify that &lt;code&gt;frd-orchestrator&lt;/code&gt; is active.&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%2Fhck96crfwlgwfjn8m9an.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%2Fhck96crfwlgwfjn8m9an.png" alt=" " width="371" height="278"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;Checking the local server works thanks to this configuration.&lt;/p&gt;

&lt;blockquote&gt;
&lt;p&gt;Note: on my machine, &lt;code&gt;uv&lt;/code&gt; is located at &lt;code&gt;/Users/jorgegomez/.local/bin/uv&lt;/code&gt;. On yours, it may vary.&lt;/p&gt;
&lt;/blockquote&gt;

&lt;p&gt;`&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight javascript"&gt;&lt;code&gt;&lt;span class="c1"&gt;// .vscode/mcp.json&lt;/span&gt;
&lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;servers&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
    &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;frd-orchestrator&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;type&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;stdio&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;command&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;/Users/jorgegomez/.local/bin/uv&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;args&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="p"&gt;[&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;run&lt;/span&gt;&lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="dl"&gt;"&lt;/span&gt;&lt;span class="s2"&gt;main.py&lt;/span&gt;&lt;span class="dl"&gt;"&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="p"&gt;}&lt;/span&gt;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;br&gt;
`&lt;br&gt;
and it automatically enables the local MCP server.&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%2Fex37i1lw3p6f6rgjld07.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%2Fex37i1lw3p6f6rgjld07.png" alt=" " width="483" height="327"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;When the server starts, we can see in the VSCode console that our MCP server is running.&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%2Ft6iqo7qa55ok08bj5a0t.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%2Ft6iqo7qa55ok08bj5a0t.png" alt=" " width="800" height="311"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In practice, it looks like this: each phase triggers &lt;code&gt;get_frd("0X")&lt;/code&gt;, and the server responds by reading the exact file from disk.&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%2Fd43o569ehy3gef9ed894.gif" 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%2Fd43o569ehy3gef9ed894.gif" alt=" " width="720" height="1345"&gt;&lt;/a&gt;&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%2Frkgv4euau3fmzpztsjfb.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%2Frkgv4euau3fmzpztsjfb.png" alt=" " width="572" height="409"&gt;&lt;/a&gt;&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%2Fxf5vcehyki850jpi9iqq.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%2Fxf5vcehyki850jpi9iqq.png" alt=" " width="586" height="415"&gt;&lt;/a&gt;&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%2Ft9ke5sqqhcfg091givjv.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%2Ft9ke5sqqhcfg091givjv.png" alt=" " width="567" height="706"&gt;&lt;/a&gt;&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%2Fq6odlzykpoavivuk3k2p.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%2Fq6odlzykpoavivuk3k2p.png" alt=" " width="575" height="655"&gt;&lt;/a&gt;&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%2Fhl1xzzp9qxm9lbvrcxti.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%2Fhl1xzzp9qxm9lbvrcxti.png" alt=" " width="565" height="648"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;In the MCP server logs, you can see how the agent requests FRDs by ID at the right moment:&lt;/p&gt;

&lt;p&gt;`&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight shell"&gt;&lt;code&gt;&lt;span class="o"&gt;[&lt;/span&gt;FRD-Orchestrator] get_frd tool requested with &lt;span class="nv"&gt;frd_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;02  
Reading FRD from disk: .../frd/FRD-02-products-database.md

&lt;span class="o"&gt;[&lt;/span&gt;FRD-Orchestrator] get_frd tool requested with &lt;span class="nv"&gt;frd_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;03  
Reading FRD from disk: .../frd/FRD-03-auth-security.md

&lt;span class="o"&gt;[&lt;/span&gt;FRD-Orchestrator] get_frd tool requested with &lt;span class="nv"&gt;frd_id&lt;/span&gt;&lt;span class="o"&gt;=&lt;/span&gt;04  
Reading FRD from disk: .../frd/FRD-04-unit-testing.md
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;br&gt;
`&lt;br&gt;
This is exactly what we’re aiming for: &lt;strong&gt;explicit tool calls&lt;/strong&gt; instead of “free-form” workspace reading.&lt;/p&gt;




&lt;p&gt;With MCP, context stops being “workspace exploration” and becomes “controlled retrieval.”&lt;br&gt;&lt;br&gt;
That reduces errors, speeds up orchestration, and makes the flow more reproducible.&lt;/p&gt;

&lt;p&gt;It may not look like much, but here’s the real benefit: &lt;strong&gt;this scales in enterprise environments&lt;/strong&gt;. With MCP, AI stops being “just a chat” and becomes a controlled component to &lt;strong&gt;automate tasks, flows, and workflows&lt;/strong&gt; aligned with each company’s &lt;strong&gt;business logic&lt;/strong&gt;. That’s where developers should be heading today: &lt;strong&gt;AI + deterministic tools + reproducible processes&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/jorge6242/boilerplate-nestjs-mcp" rel="noopener noreferrer"&gt;Github repo&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;If you made it this far, I’d love for you to review this approach and share your thoughts. I’m fully open to &lt;strong&gt;feedback&lt;/strong&gt;, &lt;strong&gt;criticism&lt;/strong&gt;, and suggestions for improvement—both on the FRD design and the MCP implementation. If you spot anything that could be cleaner, safer, or more reproducible, please let me know in the comments.&lt;/p&gt;

</description>
      <category>architecture</category>
      <category>agents</category>
      <category>ai</category>
      <category>automation</category>
    </item>
    <item>
      <title>🚀 How I Used AI, FRDs, Claude Sonnet, Windsurf, and Antigravity to Orchestrate a Complete Backend Without Chaos</title>
      <dc:creator>Jorge Gomez</dc:creator>
      <pubDate>Fri, 28 Nov 2025 14:24:32 +0000</pubDate>
      <link>https://dev.to/jorge6242/how-i-used-ai-frds-claude-sonnet-windsurf-and-antigravity-to-orchestrate-a-complete-backend-1jml</link>
      <guid>https://dev.to/jorge6242/how-i-used-ai-frds-claude-sonnet-windsurf-and-antigravity-to-orchestrate-a-complete-backend-1jml</guid>
      <description>&lt;p&gt;A Reproducible Methodology for Generating Real, Clean, and Scalable Software Using LLM Agents Orchestrated Through Formal FRD Documents&lt;/p&gt;

&lt;h2&gt;
  
  
  Introduction
&lt;/h2&gt;

&lt;p&gt;Over the past few weeks, I’ve been applying a development approach based on &lt;strong&gt;FRDs (Functional Requirements Documents)&lt;/strong&gt; orchestrated with the support of Artificial Intelligence to build modern, clean, and scalable backends. This method allowed me to generate a complete backend—from CRUD, database layer, JWT authentication, migrations, and unit testing—&lt;strong&gt;without chaos, without improvisation, and with clean code from the very first commit&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Although FRDs are a traditional standard widely used in software engineering, what’s truly interesting here is not the FRD concept itself, but &lt;em&gt;how FRDs are combined with LLM agents&lt;/em&gt; to create a deterministic build process.&lt;/p&gt;

&lt;p&gt;This approach aligns with modern techniques such as &lt;strong&gt;LLM Orchestration, Task Graphs, and Agentic Pipelines&lt;/strong&gt;, which are becoming mainstream in tools like Windsurf, Cursor, Claude Sonnet, and VSCode Agents.&lt;/p&gt;

&lt;p&gt;One key insight I found early on was that I initially started with &lt;strong&gt;a massive FRD&lt;/strong&gt;—a single document describing all phases (boilerplate, database, authentication, testing, etc.). Even though the document was detailed, two real problems quickly emerged:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;LLMs had to process too much information in a single context window&lt;/strong&gt;, increasing reasoning load and causing the agent to drift, forget steps, or ask redundant questions.
&lt;/li&gt;
&lt;li&gt;The process became slow because the model entered loops of “confirmation,” uncertainty, ambiguity, and frequent re-validation.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;This led me to investigate a more scalable approach: &lt;strong&gt;splitting the project into multiple small FRDs&lt;/strong&gt;, each responsible for a specific phase, and introducing a &lt;strong&gt;Master FRD-00&lt;/strong&gt; responsible for orchestrating them in the correct order.&lt;/p&gt;

&lt;p&gt;With this structure:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The LLM no longer needs to parse a giant document
&lt;/li&gt;
&lt;li&gt;Each stage has a short, precise FRD
&lt;/li&gt;
&lt;li&gt;The Master FRD controls the entire execution pipeline
&lt;/li&gt;
&lt;li&gt;The agent stops asking “what’s next” and simply executes
&lt;/li&gt;
&lt;li&gt;Adding new features becomes safer and easier because each one lives in its own FRD without interfering with others&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  What Is FRD-Based Orchestration?
&lt;/h2&gt;

&lt;p&gt;FRDs define:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;What needs to be built
&lt;/li&gt;
&lt;li&gt;In what order
&lt;/li&gt;
&lt;li&gt;What criteria must be satisfied
&lt;/li&gt;
&lt;li&gt;Which steps are mandatory
&lt;/li&gt;
&lt;li&gt;What behaviors the AI must validate
&lt;/li&gt;
&lt;li&gt;What acceptance conditions each phase must pass
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The key insight is that you’re &lt;strong&gt;not only describing features&lt;/strong&gt;, you’re also defining &lt;strong&gt;the construction process itself&lt;/strong&gt;, ensuring accuracy, repeatability, and zero improvisation.&lt;/p&gt;




&lt;h3&gt;
  
  
  📘 Connection to Existing Practices
&lt;/h3&gt;

&lt;p&gt;FRD-based orchestration does not try to reinvent methodologies—it &lt;strong&gt;integrates formal, well-known practices&lt;/strong&gt; with the new wave of AI-assisted development.&lt;/p&gt;

&lt;p&gt;This method incorporates ideas from:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;em&gt;LLM-driven Development Workflows&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;Prompt Chaining&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;Task Graph Execution&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;Reproducible Pipelines&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;Specification-Driven Engineering&lt;/em&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The contribution here lies in &lt;strong&gt;how the process is structured&lt;/strong&gt;, not in the FRD concept itself.&lt;/p&gt;




&lt;h3&gt;
  
  
  FRD-00: The Orchestration Controller
&lt;/h3&gt;

&lt;p&gt;This master document defines:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Required phases
&lt;/li&gt;
&lt;li&gt;Clear dependencies
&lt;/li&gt;
&lt;li&gt;Agent behavior
&lt;/li&gt;
&lt;li&gt;Validation rules per stage
&lt;/li&gt;
&lt;li&gt;Strict conditions to advance or stop
&lt;/li&gt;
&lt;li&gt;Success criteria for each phase
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;It acts as the &lt;em&gt;logical pipeline&lt;/em&gt; that ensures the project can be executed by AI without deviations.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/jorge6242/boilerplate-nestjs-ai/blob/master/FRD-00-master-orchestration.md" rel="noopener noreferrer"&gt;🔗 &lt;em&gt;Ruta&lt;/em&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  🚀 How Orchestration Actually Begins
&lt;/h2&gt;

&lt;p&gt;Before starting any phase, I execute this initial instruction so the AI loads all FRDs and understands the execution order:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@FRD-00-master-orchestration.md
@FRD-01-boilerplate-core-products.md
@FRD-02-products-database.md
@FRD-03-auth-security.md
@FRD-04-unit-testing.md 

folder name: api-products

FRD-00-master-orchestration.md
FRD-01-boilerplate-core-products.md
FRD-02-products-persistence-typeorm.md
FRD-03-unit-testing.md

Start the orchestration.

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;h3&gt;
  
  
  Why Is This Critical?
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Ensures the agent follows &lt;strong&gt;the correct order&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Enforces FRD-00 as the master controller
&lt;/li&gt;
&lt;li&gt;Reduces context overload
&lt;/li&gt;
&lt;li&gt;Prevents unnecessary loops or clarifying questions
&lt;/li&gt;
&lt;li&gt;Makes the process reproducible across any AI editor:
Windsurf, Antigravity, Cursor, Sonnet, VSCode Agents, etc.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  General Project Architecture
&lt;/h2&gt;

&lt;p&gt;The final result was a NestJS backend with:&lt;/p&gt;

&lt;h3&gt;
  
  
  🧩 Included Modules
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Auth (JWT + bcrypt + Passport)
&lt;/li&gt;
&lt;li&gt;Users (registration, login, hashing)
&lt;/li&gt;
&lt;li&gt;Products (CRUD with validation)
&lt;/li&gt;
&lt;li&gt;Centralized config
&lt;/li&gt;
&lt;li&gt;SQLite + TypeORM + migrations
&lt;/li&gt;
&lt;li&gt;Unit testing (33 tests)
&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  📁 Final Structure
&lt;/h3&gt;



&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;api-products/
├── src/
│   ├── auth/
│   ├── products/
│   ├── users/
│   ├── config/
│   └── main.ts
├── migrations/
├── README.md
└── package.json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;






&lt;h1&gt;
  
  
  🔥 Phase 1 — Boilerplate + In-Memory CRUD
&lt;/h1&gt;

&lt;ul&gt;
&lt;li&gt;Project created using &lt;code&gt;nest new&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Swagger enabled
&lt;/li&gt;
&lt;li&gt;Global validation pipes
&lt;/li&gt;
&lt;li&gt;In-memory CRUD
&lt;/li&gt;
&lt;li&gt;DTOs with class-validator
&lt;/li&gt;
&lt;li&gt;Mandatory JSDoc
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;CreateProductDto&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;IsString&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;IsBoolean&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="nx"&gt;isPremium&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;boolean&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;IsNumber&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="nx"&gt;price&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&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;p&gt;&lt;a href="https://github.com/jorge6242/boilerplate-nestjs-ai/blob/master/FRD-01-boilerplate-core-products.md" rel="noopener noreferrer"&gt;🔗 &lt;em&gt;Ruta&lt;/em&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h1&gt;
  
  
  🗄️ Phase 2 — Database + TypeORM
&lt;/h1&gt;

&lt;ul&gt;
&lt;li&gt;TypeORM integration
&lt;/li&gt;
&lt;li&gt;Migrations
&lt;/li&gt;
&lt;li&gt;ProductRepository
&lt;/li&gt;
&lt;li&gt;Real persistence using SQLite
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Entity&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;class&lt;/span&gt; &lt;span class="nc"&gt;Product&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;PrimaryGeneratedColumn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;uuid&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Column&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Column&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="nx"&gt;isPremium&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;boolean&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Column&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;float&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="nx"&gt;price&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&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;p&gt;&lt;a href="https://github.com/jorge6242/boilerplate-nestjs-ai/blob/master/FRD-02-products-database.md" rel="noopener noreferrer"&gt;🔗 &lt;em&gt;Ruta&lt;/em&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h1&gt;
  
  
  🔐 Phase 3 — JWT Authentication
&lt;/h1&gt;

&lt;ul&gt;
&lt;li&gt;UsersService + repository
&lt;/li&gt;
&lt;li&gt;bcrypt hashing
&lt;/li&gt;
&lt;li&gt;LocalStrategy + JwtStrategy
&lt;/li&gt;
&lt;li&gt;JwtAuthGuard
&lt;/li&gt;
&lt;li&gt;Swagger Bearer Auth
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;  &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;login&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;loginDto&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;LoginDto&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;accessToken&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&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;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;usersService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;validateCredentials&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;loginDto&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;loginDto&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;password&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;UnauthorizedException&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Invalid credentials&lt;/span&gt;&lt;span class="dl"&gt;'&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="nx"&gt;payload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;sub&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&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;accessToken&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;jwtService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sign&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;accessToken&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;p&gt;&lt;a href="https://github.com/jorge6242/boilerplate-nestjs-ai/blob/master/FRD-03-auth-security.md" rel="noopener noreferrer"&gt;🔗 &lt;em&gt;Ruta&lt;/em&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h1&gt;
  
  
  🧪 Phase 4 — Unit Testing
&lt;/h1&gt;

&lt;ul&gt;
&lt;li&gt;6 test suites
&lt;/li&gt;
&lt;li&gt;33 tests
&lt;/li&gt;
&lt;li&gt;Clean mocks
&lt;/li&gt;
&lt;li&gt;No real database
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;  &lt;span class="nf"&gt;describe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;login&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&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="nf"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;should login successfully and return access token&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &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="na"&gt;loginDto&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;LoginDto&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;test@example.com&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;password&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;password123&lt;/span&gt;&lt;span class="dl"&gt;'&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="nx"&gt;accessToken&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;jwt-token&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="nx"&gt;mockUsersService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;validateCredentials&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;mockResolvedValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;mockUser&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="nx"&gt;mockJwtService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sign&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;mockReturnValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;accessToken&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;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;service&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;login&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;loginDto&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

      &lt;span class="nf"&gt;expect&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="nf"&gt;toEqual&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;accessToken&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
      &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;usersService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;validateCredentials&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toHaveBeenCalledWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;loginDto&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;loginDto&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;password&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;jwtService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sign&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toHaveBeenCalledWith&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;mockUser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;sub&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;mockUser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="nf"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;should throw UnauthorizedException for invalid credentials&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &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="na"&gt;loginDto&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;LoginDto&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;test@example.com&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;password&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;wrongpassword&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;};&lt;/span&gt;
      &lt;span class="nx"&gt;mockUsersService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;validateCredentials&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;mockResolvedValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;null&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;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;service&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;login&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;loginDto&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nx"&gt;rejects&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toThrow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;UnauthorizedException&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;usersService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;validateCredentials&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toHaveBeenCalledWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;loginDto&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;loginDto&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;password&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://github.com/jorge6242/boilerplate-nestjs-ai/blob/master/FRD-04-unit-testing.md" rel="noopener noreferrer"&gt;🔗 &lt;em&gt;Ruta&lt;/em&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h1&gt;
  
  
  📊 Final Results
&lt;/h1&gt;

&lt;ul&gt;
&lt;li&gt;All 33 tests passing
&lt;/li&gt;
&lt;li&gt;Migrations working
&lt;/li&gt;
&lt;li&gt;JWT fully functional
&lt;/li&gt;
&lt;li&gt;CRUD operational
&lt;/li&gt;
&lt;li&gt;Consistent architecture
&lt;/li&gt;
&lt;/ul&gt;




&lt;h1&gt;
  
  
  🖼️ Visual Evidence
&lt;/h1&gt;

&lt;p&gt;&lt;em&gt;(Gallery with screenshots from Windsurf, Antigravity, and Claude Sonnet)&lt;/em&gt;&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%2Ff6ilm5ird2go48ffe5s1.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%2Ff6ilm5ird2go48ffe5s1.png" alt=" " width="800" height="915"&gt;&lt;/a&gt;&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%2Fm8pelm9hd6g43dkx6erm.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%2Fm8pelm9hd6g43dkx6erm.png" alt=" " width="800" height="786"&gt;&lt;/a&gt;&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%2F7rm9txd1t4axm988dehk.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%2F7rm9txd1t4axm988dehk.png" alt=" " width="552" height="884"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h1&gt;
  
  
  🧠 Why This Matters in the AI Era
&lt;/h1&gt;

&lt;ol&gt;
&lt;li&gt;AI needs structure
&lt;/li&gt;
&lt;li&gt;Enables end-to-end project execution
&lt;/li&gt;
&lt;li&gt;Reinforces best practices in LLM-assisted development workflows.
&lt;/li&gt;
&lt;li&gt;Reproducible, scalable, modular
&lt;/li&gt;
&lt;/ol&gt;




&lt;h1&gt;
  
  
  🧭 Conclusion
&lt;/h1&gt;

&lt;p&gt;FRD Orchestration is not merely an operational technique—it is a way of &lt;strong&gt;structuring collaboration between humans and AI models&lt;/strong&gt; to produce reproducible, stable, and scalable software.&lt;/p&gt;

&lt;p&gt;This method enhances—not replaces—the developer.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/jorge6242/boilerplate-nestjs-ai" rel="noopener noreferrer"&gt;📂 Official Repository with FRDs + Full Backend&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;`&lt;/p&gt;

</description>
    </item>
    <item>
      <title>🚀 Cómo Usé IA, FRDs, Claude Sonnet, Windsurf y Antigravity para Orquestar un Backend Completo Sin Caos</title>
      <dc:creator>Jorge Gomez</dc:creator>
      <pubDate>Fri, 28 Nov 2025 03:57:18 +0000</pubDate>
      <link>https://dev.to/jorge6242/como-construi-un-backend-completo-con-nestjs-typeorm-y-jwt-usando-agentes-de-ia-frds-sin-2idg</link>
      <guid>https://dev.to/jorge6242/como-construi-un-backend-completo-con-nestjs-typeorm-y-jwt-usando-agentes-de-ia-frds-sin-2idg</guid>
      <description>&lt;p&gt;Una metodología reproducible para generar software real, limpio y escalable usando agentes LLM orquestados por documentos FRD formales.&lt;/p&gt;

&lt;p&gt;`&lt;/p&gt;

&lt;h1&gt;
  
  
  Introducción
&lt;/h1&gt;

&lt;p&gt;En estas últimas semanas he estado aplicando un enfoque de desarrollo basado en &lt;strong&gt;FRDs (Functional Requirements Documents)&lt;/strong&gt; orquestados con apoyo de Inteligencia Artificial para construir backends modernos, limpios y escalables. Este método me permitió crear un backend completo —con CRUD, base de datos, autenticación JWT, migraciones y pruebas unitarias— &lt;strong&gt;sin caos, sin improvisación y con código limpio desde el primer commit&lt;/strong&gt;.&lt;/p&gt;

&lt;p&gt;Aunque los FRDs son un estándar tradicional ampliamente usado en ingeniería de software, lo verdaderamente interesante aquí no es el concepto del FRD en sí, sino cómo se combinan con agentes LLM para crear un proceso determinístico de construcción.&lt;/p&gt;

&lt;p&gt;Este enfoque se alinea con técnicas modernas de LLM Orchestration, Task Graphs y Agentic Pipelines, que están siendo adoptadas en herramientas como Windsurf, Cursor, Claude Sonnet y VSCode Agents.&lt;/p&gt;

&lt;p&gt;Algo que &lt;strong&gt;noté&lt;/strong&gt; durante este proceso es que &lt;strong&gt;comencé usando un FRD gigante&lt;/strong&gt;, un documento único donde describía todas las fases (boilerplate, base de datos, autenticación, pruebas, etc.). Aunque el documento era muy detallado, pronto aparecieron dos problemas reales:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Los modelos de IA tenían que analizar demasiada información en una sola ventana de contexto&lt;/strong&gt;, lo cual aumentaba la carga de razonamiento y hacía que el agente se desviara, olvidara pasos o repitiera preguntas innecesarias.
&lt;/li&gt;
&lt;li&gt;El proceso se volvía lento porque la IA entraba en ciclos de “confirmación”, dudas, ambigüedades y revalidaciones constantes.&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Esto me llevó a investigar un enfoque más escalable: &lt;strong&gt;separar el proyecto en múltiples FRDs pequeños&lt;/strong&gt;, cada uno responsable de una fase específica, y crear un &lt;strong&gt;FRD-00 Maestro&lt;/strong&gt; que los orquestara en el orden correcto.&lt;/p&gt;

&lt;p&gt;Con esa estructura:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;La IA ya no necesita analizar un documento gigantesco.
&lt;/li&gt;
&lt;li&gt;Cada fase tiene un FRD corto y preciso.
&lt;/li&gt;
&lt;li&gt;El FRD Maestro controla el flujo completo como un pipeline.
&lt;/li&gt;
&lt;li&gt;El agente deja de preguntar “qué sigue” y simplemente ejecuta.
&lt;/li&gt;
&lt;li&gt;Agregar nuevas features en el futuro es más sencillo y seguro, porque cada una vive en su propio FRD sin interferir con las demás.&lt;/li&gt;
&lt;/ul&gt;




&lt;h2&gt;
  
  
  ¿Qué es la Orquestación por FRDs?
&lt;/h2&gt;

&lt;p&gt;Los FRDs definen:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Qué debe hacerse
&lt;/li&gt;
&lt;li&gt;En qué orden
&lt;/li&gt;
&lt;li&gt;Qué criterios deben cumplirse
&lt;/li&gt;
&lt;li&gt;Qué pasos son obligatorios
&lt;/li&gt;
&lt;li&gt;Qué comportamientos debe validar la IA
&lt;/li&gt;
&lt;li&gt;Qué condiciones de aceptación debe pasar cada fase
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;La clave está en que &lt;strong&gt;no solo describes la funcionalidad&lt;/strong&gt;, sino que defines &lt;strong&gt;el proceso de construcción&lt;/strong&gt;, asegurando precisión, repetibilidad y cero improvisación.&lt;/p&gt;




&lt;h3&gt;
  
  
  📘 Relación con prácticas existentes
&lt;/h3&gt;

&lt;p&gt;La orquestación por FRDs no pretende reinventar metodologías: más bien &lt;strong&gt;integra prácticas formales&lt;/strong&gt; ya conocidas con la nueva ola de desarrollo asistido por IA.&lt;/p&gt;

&lt;p&gt;Este método toma elementos de:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;em&gt;LLM-driven Development Workflows&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;Prompt Chaining&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;Task Graph Execution&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;Reproducible Pipelines&lt;/em&gt;
&lt;/li&gt;
&lt;li&gt;
&lt;em&gt;Specification-Driven Engineering&lt;/em&gt;
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;La contribución aquí es &lt;strong&gt;cómo se estructura el proceso&lt;/strong&gt;, no el concepto del FRD en sí.&lt;/p&gt;




&lt;h3&gt;
  
  
  FRD-00: El "Controlador de Orquestación"
&lt;/h3&gt;

&lt;p&gt;Este documento maestro dicta:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Fases obligatorias
&lt;/li&gt;
&lt;li&gt;Dependencias claras
&lt;/li&gt;
&lt;li&gt;Comportamiento del agente
&lt;/li&gt;
&lt;li&gt;Validaciones en cada etapa
&lt;/li&gt;
&lt;li&gt;Reglas estrictas para avanzar o detenerse
&lt;/li&gt;
&lt;li&gt;Criterios de éxito por fase
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Es el &lt;em&gt;pipeline lógico&lt;/em&gt; que permite que el proyecto sea ejecutado por IA sin desviaciones.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/jorge6242/boilerplate-nestjs-ai/blob/master/FRD-00-master-orchestration.md" rel="noopener noreferrer"&gt;🔗 &lt;em&gt;Ruta&lt;/em&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  🚀 ¿Cómo inicia realmente la orquestación?
&lt;/h2&gt;

&lt;p&gt;Antes de empezar las fases, ejecuto esta instrucción inicial para que la IA cargue todos los FRDs y comprenda el orden de ejecución:&lt;/p&gt;

&lt;p&gt;`&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;@FRD-00-master-orchestration.md
@FRD-01-boilerplate-core-products.md
@FRD-02-products-database.md
@FRD-03-auth-security.md
@FRD-04-unit-testing.md

nombre de carpeta: api-products

FRD-00-master-orchestration.md
FRD-01-boilerplate-core-products.md
FRD-02-products-persistence-typeorm.md
FRD-03-unit-testing.md

Inicia la orquestación.

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;br&gt;
`&lt;/p&gt;
&lt;h3&gt;
  
  
  ¿Por qué esto es crítico?
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Garantiza que el agente siga &lt;strong&gt;el orden correcto&lt;/strong&gt;
&lt;/li&gt;
&lt;li&gt;Asegura que el FRD-00 actúe como director maestro
&lt;/li&gt;
&lt;li&gt;Reduce carga de contexto
&lt;/li&gt;
&lt;li&gt;Evita loops o preguntas innecesarias
&lt;/li&gt;
&lt;li&gt;Permite reproducibilidad en cualquier editor IA: Windsurf, Antigravity, Cursor, Sonnet, VSCode Agents, etc.&lt;/li&gt;
&lt;/ul&gt;


&lt;h2&gt;
  
  
  Arquitectura General del Proyecto
&lt;/h2&gt;

&lt;p&gt;El resultado final fue un backend NestJS con:&lt;/p&gt;
&lt;h3&gt;
  
  
  🧩 Módulos incluidos
&lt;/h3&gt;

&lt;ul&gt;
&lt;li&gt;Auth (JWT + bcrypt + Passport)
&lt;/li&gt;
&lt;li&gt;Users (registro, login, hashing)
&lt;/li&gt;
&lt;li&gt;Products (CRUD con validación)
&lt;/li&gt;
&lt;li&gt;Config centralizado
&lt;/li&gt;
&lt;li&gt;SQLite + TypeORM + migraciones
&lt;/li&gt;
&lt;li&gt;Pruebas unitarias (33 tests)
&lt;/li&gt;
&lt;/ul&gt;
&lt;h3&gt;
  
  
  📁 Estructura final
&lt;/h3&gt;

&lt;p&gt;`&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;api-products/
├── src/
│   ├── auth/
│   ├── products/
│   ├── users/
│   ├── config/
│   └── main.ts
├── migrations/
├── README.md
└── package.json
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;

&lt;p&gt;&lt;br&gt;
`&lt;/p&gt;


&lt;h1&gt;
  
  
  🔥 Fase 1 — Boilerplate + CRUD en memoria
&lt;/h1&gt;

&lt;ul&gt;
&lt;li&gt;Proyecto creado con &lt;code&gt;nest new&lt;/code&gt;
&lt;/li&gt;
&lt;li&gt;Swagger activado
&lt;/li&gt;
&lt;li&gt;Validaciones globales
&lt;/li&gt;
&lt;li&gt;CRUD in-memory
&lt;/li&gt;
&lt;li&gt;DTOs con class-validator
&lt;/li&gt;
&lt;li&gt;JSDoc obligatorio
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;`&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="k"&gt;export&lt;/span&gt; &lt;span class="kd"&gt;class&lt;/span&gt; &lt;span class="nc"&gt;CreateProductDto&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;IsString&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;IsBoolean&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="nx"&gt;isPremium&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;boolean&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;IsNumber&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="nx"&gt;price&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&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;p&gt;&lt;br&gt;
`&lt;br&gt;
&lt;a href="https://github.com/jorge6242/boilerplate-nestjs-ai/blob/master/FRD-01-boilerplate-core-products.md" rel="noopener noreferrer"&gt;🔗 &lt;em&gt;Ruta&lt;/em&gt;&lt;/a&gt;&lt;/p&gt;


&lt;h1&gt;
  
  
  🗄️ Fase 2 — Base de Datos + TypeORM
&lt;/h1&gt;

&lt;ul&gt;
&lt;li&gt;Integración TypeORM
&lt;/li&gt;
&lt;li&gt;Migraciones
&lt;/li&gt;
&lt;li&gt;ProductRepository
&lt;/li&gt;
&lt;li&gt;Persistencia real con SQLite
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;`&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;&lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Entity&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;class&lt;/span&gt; &lt;span class="nc"&gt;Product&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
  &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;PrimaryGeneratedColumn&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;uuid&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="nx"&gt;id&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Column&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="nx"&gt;name&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Column&lt;/span&gt;&lt;span class="p"&gt;()&lt;/span&gt;
  &lt;span class="nx"&gt;isPremium&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;boolean&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;

  &lt;span class="p"&gt;@&lt;/span&gt;&lt;span class="nd"&gt;Column&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;float&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt;
  &lt;span class="nx"&gt;price&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;number&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;p&gt;&lt;br&gt;
`&lt;br&gt;
&lt;a href="https://github.com/jorge6242/boilerplate-nestjs-ai/blob/master/FRD-02-products-database.md" rel="noopener noreferrer"&gt;🔗 &lt;em&gt;Ruta&lt;/em&gt;&lt;/a&gt;&lt;/p&gt;


&lt;h1&gt;
  
  
  🔐 Fase 3 — Autenticación JWT
&lt;/h1&gt;

&lt;ul&gt;
&lt;li&gt;UsersService + repositorio
&lt;/li&gt;
&lt;li&gt;Hashing con bcrypt
&lt;/li&gt;
&lt;li&gt;LocalStrategy + JwtStrategy
&lt;/li&gt;
&lt;li&gt;JwtAuthGuard
&lt;/li&gt;
&lt;li&gt;Swagger BearerAuth
&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;`&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;  &lt;span class="k"&gt;async&lt;/span&gt; &lt;span class="nf"&gt;login&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;loginDto&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;LoginDto&lt;/span&gt;&lt;span class="p"&gt;):&lt;/span&gt; &lt;span class="nb"&gt;Promise&lt;/span&gt;&lt;span class="o"&gt;&amp;lt;&lt;/span&gt;&lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;accessToken&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="kr"&gt;string&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;user&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;usersService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;validateCredentials&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;loginDto&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;loginDto&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;password&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;if &lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="o"&gt;!&lt;/span&gt;&lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;)&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
      &lt;span class="k"&gt;throw&lt;/span&gt; &lt;span class="k"&gt;new&lt;/span&gt; &lt;span class="nc"&gt;UnauthorizedException&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;Invalid credentials&lt;/span&gt;&lt;span class="dl"&gt;'&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="nx"&gt;payload&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="na"&gt;email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;sub&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;user&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&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;accessToken&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;this&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;jwtService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;sign&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;payload&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

    &lt;span class="k"&gt;return&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt; &lt;span class="nx"&gt;accessToken&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;p&gt;&lt;a href="https://github.com/jorge6242/boilerplate-nestjs-ai/blob/master/FRD-03-auth-security.md" rel="noopener noreferrer"&gt;🔗 &lt;em&gt;Ruta&lt;/em&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h1&gt;
  
  
  🧪 Fase 4 — Pruebas Unitarias
&lt;/h1&gt;

&lt;ul&gt;
&lt;li&gt;6 test suites
&lt;/li&gt;
&lt;li&gt;33 pruebas
&lt;/li&gt;
&lt;li&gt;Mocks limpios
&lt;/li&gt;
&lt;li&gt;Sin BD real
&lt;/li&gt;
&lt;/ul&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight typescript"&gt;&lt;code&gt;  &lt;span class="nf"&gt;describe&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;login&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&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="nf"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;should login successfully and return access token&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &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="na"&gt;loginDto&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;LoginDto&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;test@example.com&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;password&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;password123&lt;/span&gt;&lt;span class="dl"&gt;'&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="nx"&gt;accessToken&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;jwt-token&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;;&lt;/span&gt;
      &lt;span class="nx"&gt;mockUsersService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;validateCredentials&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;mockResolvedValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;mockUser&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="nx"&gt;mockJwtService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sign&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;mockReturnValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;accessToken&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;result&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="k"&gt;await&lt;/span&gt; &lt;span class="nx"&gt;service&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;login&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;loginDto&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;

      &lt;span class="nf"&gt;expect&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="nf"&gt;toEqual&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="nx"&gt;accessToken&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
      &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;usersService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;validateCredentials&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toHaveBeenCalledWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;loginDto&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;loginDto&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;password&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;jwtService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;sign&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toHaveBeenCalledWith&lt;/span&gt;&lt;span class="p"&gt;({&lt;/span&gt; &lt;span class="na"&gt;email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;mockUser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="na"&gt;sub&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;mockUser&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;id&lt;/span&gt; &lt;span class="p"&gt;});&lt;/span&gt;
    &lt;span class="p"&gt;});&lt;/span&gt;

    &lt;span class="nf"&gt;it&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;should throw UnauthorizedException for invalid credentials&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="k"&gt;async &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="na"&gt;loginDto&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="nx"&gt;LoginDto&lt;/span&gt; &lt;span class="o"&gt;=&lt;/span&gt; &lt;span class="p"&gt;{&lt;/span&gt;
        &lt;span class="na"&gt;email&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;test@example.com&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
        &lt;span class="na"&gt;password&lt;/span&gt;&lt;span class="p"&gt;:&lt;/span&gt; &lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="s1"&gt;wrongpassword&lt;/span&gt;&lt;span class="dl"&gt;'&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt;
      &lt;span class="p"&gt;};&lt;/span&gt;
      &lt;span class="nx"&gt;mockUsersService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;validateCredentials&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;mockResolvedValue&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="kc"&gt;null&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;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;service&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;login&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;loginDto&lt;/span&gt;&lt;span class="p"&gt;)).&lt;/span&gt;&lt;span class="nx"&gt;rejects&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nf"&gt;toThrow&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;UnauthorizedException&lt;/span&gt;&lt;span class="p"&gt;);&lt;/span&gt;
      &lt;span class="nf"&gt;expect&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;usersService&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;validateCredentials&lt;/span&gt;&lt;span class="p"&gt;).&lt;/span&gt;&lt;span class="nf"&gt;toHaveBeenCalledWith&lt;/span&gt;&lt;span class="p"&gt;(&lt;/span&gt;&lt;span class="nx"&gt;loginDto&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;email&lt;/span&gt;&lt;span class="p"&gt;,&lt;/span&gt; &lt;span class="nx"&gt;loginDto&lt;/span&gt;&lt;span class="p"&gt;.&lt;/span&gt;&lt;span class="nx"&gt;password&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;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;a href="https://github.com/jorge6242/boilerplate-nestjs-ai/blob/master/FRD-04-unit-testing.md" rel="noopener noreferrer"&gt;🔗 &lt;em&gt;Ruta&lt;/em&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h1&gt;
  
  
  📊 Resultados Finales
&lt;/h1&gt;

&lt;ul&gt;
&lt;li&gt;33 pruebas exitosas
&lt;/li&gt;
&lt;li&gt;Migraciones correctas
&lt;/li&gt;
&lt;li&gt;JWT funcionando
&lt;/li&gt;
&lt;li&gt;CRUD operativo
&lt;/li&gt;
&lt;li&gt;Arquitectura consistente
&lt;/li&gt;
&lt;/ul&gt;




&lt;h1&gt;
  
  
  🖼️ Visual Evidence
&lt;/h1&gt;

&lt;p&gt;&lt;em&gt;(Gallery with screenshots from Windsurf, Antigravity, and Claude Sonnet)&lt;/em&gt;&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%2F5hxjx8l9g2wn5y54nrsg.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%2F5hxjx8l9g2wn5y54nrsg.png" alt=" " width="695" height="916"&gt;&lt;/a&gt;&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%2Fdrdo3vhsgw4ot54p7u6w.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%2Fdrdo3vhsgw4ot54p7u6w.png" alt=" " width="658" height="862"&gt;&lt;/a&gt;&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%2Fahon49oecgrbffd9v6gr.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%2Fahon49oecgrbffd9v6gr.png" alt=" " width="616" height="926"&gt;&lt;/a&gt;&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%2Fdpk9yaniz3wa2zwdpdtj.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%2Fdpk9yaniz3wa2zwdpdtj.png" alt=" " width="626" height="993"&gt;&lt;/a&gt;&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%2Fymoyr3xn1x9ivejl98c5.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%2Fymoyr3xn1x9ivejl98c5.png" alt=" " width="660" height="916"&gt;&lt;/a&gt;&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%2Fmsfwy9q8gbn25f50vngh.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%2Fmsfwy9q8gbn25f50vngh.png" alt=" " width="634" height="737"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h1&gt;
  
  
  🧠 Importancia en la era de la IA
&lt;/h1&gt;

&lt;ol&gt;
&lt;li&gt;La IA necesita estructura
&lt;/li&gt;
&lt;li&gt;Permite la ejecución de proyectos de extremo a extremo
&lt;/li&gt;
&lt;li&gt;Refuerza buenas prácticas en flujos asistidos por modelos LLM &lt;/li&gt;
&lt;li&gt;Es reproducible, escalable y modular
&lt;/li&gt;
&lt;/ol&gt;




&lt;h1&gt;
  
  
  🧭 Conclusión
&lt;/h1&gt;

&lt;p&gt;La Orquestación por FRDs no es simplemente una técnica operativa: es una forma de &lt;strong&gt;estructurar la colaboración entre humanos y modelos de IA&lt;/strong&gt; para producir software reproducible, estable y escalable.&lt;/p&gt;

&lt;p&gt;Este método potencia —no reemplaza— al desarrollador.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://github.com/jorge6242/boilerplate-nestjs-ai" rel="noopener noreferrer"&gt;📂 Repositorio oficial con FRDs + Backend completo&lt;/a&gt;&lt;/p&gt;




&lt;p&gt;`&lt;/p&gt;

</description>
      <category>nestjs</category>
      <category>frd</category>
      <category>ai</category>
    </item>
    <item>
      <title>Descubriendo la Potencia de Go: Backend Inicial con buenas practicas</title>
      <dc:creator>Jorge Gomez</dc:creator>
      <pubDate>Sat, 06 Apr 2024 03:30:23 +0000</pubDate>
      <link>https://dev.to/jorge6242/descubriendo-la-potencia-de-go-backend-inicial-con-buenas-practicas-513</link>
      <guid>https://dev.to/jorge6242/descubriendo-la-potencia-de-go-backend-inicial-con-buenas-practicas-513</guid>
      <description>&lt;p&gt;&lt;a href="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fimph4qzkwtxonfl7tk2t.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media.dev.to/cdn-cgi/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fimph4qzkwtxonfl7tk2t.png" alt="Image description" width="800" height="451"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;En el vasto universo de la programación, cada lenguaje tiene su brillo único, su propósito especial. Entre estos, Go (o Golang) destaca por su simplicidad, eficiencia y capacidad para manejar concurrencia. Como desarrollador apasionado por aprender y explorar, decidí embarcarme en un viaje: crear un proyecto desde cero utilizando Go. Este artículo no solo subraya la importancia de Go en el desarrollo moderno de software, sino que también comparte mi experiencia construyendo una aplicación estructurada y funcional, paso a paso.&lt;/p&gt;

&lt;p&gt;¿Por Qué Go?&lt;br&gt;
Go fue diseñado en Google para mejorar la productividad en la programación en un entorno de sistemas complejos. Su sintaxis clara y su sistema de tipos, junto con el manejo nativo de la concurrencia mediante goroutines, lo hacen excepcionalmente potente para el desarrollo de servidores web, servicios en tiempo real y herramientas de productividad.&lt;/p&gt;

&lt;p&gt;Mi fuerte esta con NodeJS usando NestJS, en los cuales he aprendido a lo largo de mi carrera, buenas practicas aplicando:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Arquitectura de 3 capas Repositorios, Servicios y Controladores&lt;/li&gt;
&lt;li&gt;Manejo de Errores&lt;/li&gt;
&lt;li&gt;Configuracion de base de datos&lt;/li&gt;
&lt;li&gt;Migraciones&lt;/li&gt;
&lt;li&gt;Organizacion inicial de configuraciones iniciales&lt;/li&gt;
&lt;li&gt;Variables de Entorno&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Quise llevar todas mis experiencias y moldearlo en un backend Go, con una estructura inicial de arquitectura, por lo tanto asi comence: &lt;/p&gt;

&lt;p&gt;Go trabaja con un archivo principal llamado main.go, en el cual todas las instrucciones que iniciemos comenzaran en este lugar&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;package main

import "fmt"

func main() {
    fmt.Println("Hello world")
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Este es el clasico hola mundo para todos, pero lo dejo para que sepas como debe de iniciar , puede tener otro nombre que ustedes quieran , pero lo importante es que se dicho archivo debe de iniciarse con la instrucción:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;go run main.go&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Antes de ponernos manos a la obra, debemos tener claro nuestra estructura de carpetas para nuestro backend, esta estructura inicial puede permitirnos tener una buena organización en la distribución de responsabilidades en nuestro backend lo cual sera el siguiente &lt;/p&gt;

&lt;p&gt;/api-golang&lt;br&gt;
    ---/bootstrap       - Configuración del contenedor de inyección de dependencias&lt;br&gt;
    ---/config          - Gestión de configuración centralizada&lt;br&gt;
    ---/controller      - Controladores HTTP&lt;br&gt;
    ---/database        - Conexión y configuración de la base&lt;br&gt;
 de datos&lt;br&gt;
    ---/dto             - Definicion de DTO para los&lt;br&gt;
controladores&lt;br&gt;
    ---/migrations      - Migraciones de base de datos&lt;br&gt;
    ---/errors          - Definiciones de errores personalizados&lt;br&gt;
    ---/models          - Modelos de datos&lt;br&gt;
    ---/repositories    - Acceso y manipulación de la base de datos&lt;br&gt;
    ---/services        - Lógica de negocio&lt;br&gt;
    ---main.go          - Punto de entrada de la aplicación&lt;br&gt;
    ---.env             - Variables de entorno&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Config&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;En esta carpeta tendremos un archivo llamado config.go, en el cual tendremos configurado las variables de entorno necesarias para usar en cualquier parte de nuestro backend usando la libreria &lt;code&gt;github.com/joho/godotenv&lt;/code&gt;&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;package config

import (
    "log"
    "os"

    "github.com/joho/godotenv"
)

type Config struct {
    DBUser     string
    DBPass     string
    DBHost     string
    DBPort     string
    DBName     string
    DBSSLMode  string
    ServerPort string
}

func LoadConfig() Config {
    err := godotenv.Load()
    if err != nil {
        log.Println("Warning: No .env file found")
    }

    return Config{
        DBUser:     os.Getenv("DB_USER"),
        DBPass:     os.Getenv("DB_PASSWORD"),
        DBHost:     os.Getenv("DB_HOST"),
        DBPort:     os.Getenv("DB_PORT"),
        DBName:     os.Getenv("DB_NAME"),
        DBSSLMode:  os.Getenv("DB_SSLMODE"),
        ServerPort: os.Getenv("SERVER_PORT"),
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;el archivo .env debe de tener lo siguiente&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;DB_USER=postgres
DB_PASSWORD=123456
DB_HOST=localhost
DB_NAME=golang
DB_PORT=5432
DB_SSLMODE=disable
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Database&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;En esta carpeta tenemos el archivo db.go, el cual sera encargado de tener preparado nuestra conexión a la base de datos tomando en cuenta por la importacion &lt;code&gt;"api-golang/config"&lt;/code&gt;, aplicando el struct de config.go en &lt;code&gt;func ConnectDatabase(cfg config.Config)&lt;/code&gt;, podemos acceder a las variables de entorno necesarias para la configurar las credenciales de la base de datos&lt;/p&gt;

&lt;p&gt;db.go&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;package database

import (
    "api-golang/config"
    "fmt"
    "log"

    "gorm.io/driver/postgres"
    "gorm.io/gorm"
)

func ConnectDatabase(cfg config.Config) (*gorm.DB, error) {
    dsn := fmt.Sprintf("postgres://%s:%s@%s:%s/%s?sslmode=%s",
        cfg.DBUser, cfg.DBPass, cfg.DBHost, cfg.DBPort, cfg.DBName, cfg.DBSSLMode)

    database, err := gorm.Open(postgres.Open(dsn), &amp;amp;gorm.Config{})
    if err != nil {
        log.Fatalf("Failed to connect to database: %v", err)
    }
    return database, nil
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Migrations&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Es muy importante tener en nuestra estructura , un arranque inicial de una base de datos, por lo tanto por medio de la libreria &lt;code&gt;github.com/golang-migrate/migrate/v4&lt;/code&gt;, tendremos las bondades de correr migraciones nuevas o modificaciones las cuales definiremos en archivos .sql, el archivo es &lt;/p&gt;

&lt;p&gt;/migrations/migrations.go&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;package migrations

import (
    "log"
    "path/filepath"
    "runtime"

    "github.com/golang-migrate/migrate/v4"
    _ "github.com/golang-migrate/migrate/v4/database/postgres"
    _ "github.com/golang-migrate/migrate/v4/source/file"
)

func Migrate(dsn string) {
    _, b, _, _ := runtime.Caller(0)
    dir := filepath.Join(filepath.Dir(b), "migrations")

    m, err := migrate.New("file://"+dir, dsn)
    if err != nil {
        log.Fatalf("Error while creating migrate instance: %v", err)
    }

    // Apply all the available migrations
    if err := m.Up(); err != nil &amp;amp;&amp;amp; err != migrate.ErrNoChange {
        log.Fatalf("Error while applying migrations: %v", err)
    }

    log.Println("Migrations applied successfully!")
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Para poder correr las migraciones por comandos cli, podemos usar un archivo Makefile, el cual es una forma de correr tareas personalizadas para GO, el cual se define asi&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;migrateup:
    migrate -path ./migrations -database "postgres://postgres:123456@localhost:5432/golang?sslmode=disable" up

migratedown:
    migrate -path ./migrations -database "postgres://postgres:123456@localhost:5432/golang?sslmode=disable" down
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;y para correr el comando por consola &lt;code&gt;make migrateup&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;al correr el comando puedes iniciar los archivos default que tengas de .sql como por ejemplo &lt;/p&gt;

&lt;p&gt;/migrations/1_create_products_table.up.sql&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;-- +migrate Up
CREATE TABLE IF NOT EXISTS products (
    id SERIAL PRIMARY KEY,
    nombre VARCHAR(100) NOT NULL,
    precio NUMERIC NOT NULL
);
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;/migrations/1_create_products_table.down.sql&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;-- +migrate Down
DROP TABLE IF EXISTS products;
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Puedes crear todas las migraciones que necesites, la base de datos creara una tabla de control a fin de tener el seguimiento de las migraciones aplicadas.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Errors&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Siempre es importante tener un archivo central , por el cual podemos contrar los errores por medio de un mensaje generico, el siguiente archivo tiene la configuracion inicial a usar para los errores &lt;/p&gt;

&lt;p&gt;/errors/errors.go&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;package errors

// ErrorType enumerates the known error types for the application
type ErrorType int

const (
    DatabaseError ErrorType = iota + 1
    ValidationError
    InternalError
)

// AppError represents an error with an associated type and message
type AppError struct {
    Type    ErrorType
    Message string
}

func (e *AppError) Error() string {
    return e.Message
}

// New crea un nuevo AppError
func New(typ ErrorType, msg string) error {
    return &amp;amp;AppError{
        Type:    typ,
        Message: msg,
    }
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;Repositorio&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Aqui configuramos toda la logica relacionada a las consultas a la base de datos, puede ser de modo generico usando el ORM o queryBuilder, y ademas de consultas personalizadas, pero lo importante, es que la responsabilidad de manipular la base de datos solo estara en este sitio, en este caso tomando en cuenta una tabla de product, podemos tener el siguiente archivo&lt;/p&gt;

&lt;p&gt;/repositories/product.go&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;package repositories

import (
    "api-golang/models"

    "gorm.io/gorm"
)

type ProductRepository interface {
    FindAll() ([]models.Product, error)
    Save(producto models.Product) (models.Product, error)
}

type productoRepository struct {
    db *gorm.DB
}

func NewProductoRepository(db *gorm.DB) ProductRepository {
    return &amp;amp;productoRepository{db: db}
}

func (r *productoRepository) FindAll() ([]models.Product, error) {
    var productos []models.Product
    result := r.db.Find(&amp;amp;productos)
    return productos, result.Error
}

func (r *productoRepository) Save(producto models.Product) (models.Product, error) {
    result := r.db.Create(&amp;amp;producto)
    return producto, result.Error
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Este repositorio en Go sirve como una capa de abstracción entre la lógica de negocio de nuestra aplicación y las operaciones de base de datos específicas para Product. Utiliza el ORM Gorm, una popular biblioteca de mapeo objeto-relacional en Go, que facilita la interacción con la base de datos utilizando Go structs en lugar de consultas SQL directas, en el siguiente bloque&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;// Struct a el ORM Gorm a fin de usar las bondades del orm
type productoRepository struct {
    db *gorm.DB
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;productoRepository&lt;/code&gt; contiene un solo campo, db, que es un puntero a una instancia de gorm.DB. Este campo db representa una sesión de base de datos con Gorm, que se utiliza para realizar operaciones de base de datos (consultas, inserciones, actualizaciones, etc.) relacionadas con productos.&lt;/p&gt;

&lt;p&gt;Al ser un puntero (*gorm.DB), db mantiene una referencia a la instancia de la base de datos, lo cual significa que los cambios que se realicen afectaran cualquier lado del backend, lo cual es crucial para mantener la actualizada la base de datos y al usarlo en ls siguiente funcion&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;func (r *productoRepository) FindAll() ([]models.Product, error) {
    var productos []models.Product
    result := r.db.Find(&amp;amp;productos)
    return productos, result.Error
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;r *productoRepository tiene la asociacion a las bondades del orm usando r.db.Find, ademas de usarlo con tras funciones basicas del orm para registrar, eliminar etc.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Service&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;En esta capa tenemos la logica del negocio, el cual se llamara &lt;/p&gt;

&lt;p&gt;/services/product.go&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;package services

import (
    "api-golang/errors"
    "api-golang/models"
    "api-golang/repositories"
)

type ProductService interface {
    GetAllProductos() ([]models.Product, error)
    CreateProduct(producto models.Product) (models.Product, error)
}

type productoService struct {
    repository repositories.ProductRepository
}

func NewProductoService(repository repositories.ProductRepository) ProductService {
    return &amp;amp;productoService{repository: repository}
}

func (s *productoService) GetAllProductos() ([]models.Product, error) {
    res, err := s.repository.FindAll()
    if err != nil {
        return []models.Product{}, errors.New(errors.DatabaseError, "Error find all product: "+err.Error())
    }
    return res, nil
}

func (s *productoService) CreateProduct(producto models.Product) (models.Product, error) {
    // Intenta guardar el producto y maneja tanto el producto guardado como el error
    savedProduct, err := s.repository.Save(producto)
    if err != nil {
        // Aquí manejas el error, creando un nuevo AppError con tipo DatabaseError
        return models.Product{}, errors.New(errors.DatabaseError, "Error saving product: "+err.Error())
    }
    // Si no hay error, devuelve el producto guardado y nil para el error
    return savedProduct, nil
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;En este archivo aplicamos nuevamente una instancia en&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;type productoService struct {
   repository repositories.ProductRepository
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;en el cual instanciamos la interfaz de ProductRepository a fin de acceder a las funciones definidas como FindAll en el siguiente codigo:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;func (s *productoService) GetAllProductos() ([]models.Product, error) {
    res, err := s.repository.FindAll()
    if err != nil {
        return []models.Product{}, errors.New(errors.DatabaseError, "Error find all product: "+err.Error())
    }
    return res, nil
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;En esta función accedemos a todos los productos , en el caso de que se tenga un error , se enviarn a la funcion DatabaseError del archivo definido mas arriba en &lt;code&gt;/errors/errors.go&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Controller&lt;/p&gt;

&lt;p&gt;En esta capa tenemos el control de las solicitudes HTTP, en este lugar debemos validar lo que recibimos por payload o por params a fin de tener seguridad en el procesamiento de los datos, ademas se define la validacion de los datos usando DTO(Data Transfer Object) a fin de tener seguridad de los datos de entrada, para este caso se usaran solo dos endpoints los cuales haran el registro y obtencion de productos &lt;/p&gt;

&lt;p&gt;/controller/product.go&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;package controller

import (
    "api-golang/dto"
    "api-golang/models"
    "api-golang/services"
    "encoding/json"
    "net/http"
)

type ProductController struct {
    Service services.ProductService
}

func NewProductController(service services.ProductService) *ProductController {
    return &amp;amp;ProductController{Service: service}
}

func (c *ProductController) GetProducts(w http.ResponseWriter, r *http.Request) {
    productos, err := c.Service.GetAllProductos()
    if err != nil {
        http.Error(w, err.Error(), http.StatusInternalServerError)
        return
    }
    json.NewEncoder(w).Encode(productos)
}

func (c *ProductController) CreateProduct(w http.ResponseWriter, r *http.Request) {
    var productDTO dto.ProductCreateDTO
    if err := json.NewDecoder(r.Body).Decode(&amp;amp;productDTO); err != nil {
        http.Error(w, err.Error(), http.StatusBadRequest)
        return
    }

    // DTO Validation
    if validationErrors := productDTO.Validate(); validationErrors != nil {
        http.Error(w, "Validation failed", http.StatusBadRequest)
        return
    }

    product := models.Product{Nombre: productDTO.Nombre, Precio: productDTO.Precio}
    savedProduct, err := c.Service.CreateProduct(product)
    if err != nil {
        http.Error(w, err.Error(), http.StatusInternalServerError)
        return
    }
    json.NewEncoder(w).Encode(savedProduct)
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Por medio de &lt;/p&gt;

&lt;p&gt;&lt;code&gt;type ProductController struct {&lt;br&gt;
    Service services.ProductService&lt;br&gt;
}&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Accedemos a la instancia de la interfaz ProductService a fin de acceder a las funciones del servicio como por ejemplo &lt;code&gt;c.Service.CreateProduct&lt;/code&gt;, para tener el response final &lt;code&gt;json.NewEncoder(w).Encode(producto)&lt;/code&gt; realiza la serializacion a JSON por medio de json.NewEncoder(w)&lt;/p&gt;

&lt;p&gt;En el bloque &lt;/p&gt;

&lt;p&gt;&lt;code&gt;// DTO Validation&lt;br&gt;
if validationErrors := productDTO.Validate(); validationErrors != nil {&lt;br&gt;
        http.Error(w, "Validation failed", http.StatusBadRequest)&lt;br&gt;
        return&lt;br&gt;
    }&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Se realiza la validation del DTO por medio del archivo&lt;/p&gt;

&lt;p&gt;/dto/dto.go&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;package dto

import "github.com/go-playground/validator/v10"

type ProductCreateDTO struct {
    Nombre string  `json:"name" validate:"required,min=2,max=100"`
    Precio float64 `json:"price" validate:"required,gt=0"`
}

func (p *ProductCreateDTO) Validate() []*validator.FieldError {
    validate := validator.New()
    err := validate.Struct(p)
    if err != nil {
        var fieldErrors []*validator.FieldError

        if ve, ok := err.(validator.ValidationErrors); ok {
            for _, fe := range ve {
                fieldError := fe
                fieldErrors = append(fieldErrors, &amp;amp;fieldError)
            }
            return fieldErrors
        }
    }
    return nil
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;code&gt;type ProductCreateDTO struct {&lt;br&gt;
    Nombre string&lt;/code&gt;json:"name" validate:"required,min=2,max=100"&lt;code&gt;&lt;br&gt;
    Precio float64&lt;/code&gt;json:"price" validate:"required,gt=0"&lt;code&gt;&lt;br&gt;
}&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Usando una librera de validacion de datos &lt;code&gt;github.com/go-playground/validator/v10&lt;/code&gt;, especificamos en los campos de la entidad Product por medio del struct ProductCreateDTO, los campos a validar con casos regulares de DTO como campos requeridos y tipos de datos&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Bootstrap&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;En este archivo tendremos centralizado nuestros repositorios , servicios y controladores , ademas de encender la conexión de la base de datos, el cual sera el siguiente &lt;/p&gt;

&lt;p&gt;/bootstrap/bootstrap.go&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;package bootstrap

import (
    "api-golang/config"
    "api-golang/controller"
    "api-golang/database"
    "api-golang/repositories"
    "api-golang/services"
    "log"

    "go.uber.org/dig"
    "gorm.io/gorm"
)

func BuildContainer(cfg config.Config) *dig.Container {
    container := dig.New()

    // Database Connection Registration
    container.Provide(func() (*gorm.DB, error) {
        return database.ConnectDatabase(cfg)
    })

    // Repositories
    if err := container.Provide(repositories.NewProductoRepository); err != nil {
        log.Fatalf("Failed to provide product repository: %v", err)
    }

    // Services
    if err := container.Provide(services.NewProductoService); err != nil {
        log.Fatalf("Failed to provide product service: %v", err)
    }

    // Controller
    if err := container.Provide(controller.NewProductController); err != nil {
        log.Fatalf("Failed to provide product controller: %v", err)
    }

    return container
}

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Es importante tener en un solo lugar, todas las incializaciones de los archivos a usar, si se requiere inicializar otros servicios, lo ideal es hacerlo en este archivo&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Main&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Finalmente en nuestro archivo main.go , tendremos todas las llamadas necesarias para iniciar nuestro backend, en este caso los importantes por los momentos es la inicializacion de las variables de entorno y de las capas definidas en la función BuildContainer de bootstrap como ademas de la definición final del route a usar para cada proposito&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;package main

import (
    "api-golang/bootstrap"
    "api-golang/config"
    "api-golang/controller"
    "log"
    "net/http"

    _ "github.com/golang-migrate/migrate/v4/database/postgres"
    _ "github.com/golang-migrate/migrate/v4/source/file"
)

func main() {
    cfg := config.LoadConfig()

    container := bootstrap.BuildContainer(cfg)

    // Invoke the container to inject the controller and configure the routes
    err := container.Invoke(func(productController *controller.ProductController) {
        http.HandleFunc("/productos", productController.GetProducts)
        http.HandleFunc("/producto", productController.CreateProduct)
    })

    if err != nil {
        log.Fatalf("Failed to invoke container: %v", err)
    }

    log.Println("Server started on port 8080")
    if err := http.ListenAndServe(":8080", nil); err != nil {
        log.Fatal(err)
    }
}
&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Una ultima cosa y muy importante es tener siempre pruebas unitarias, para Go es mucho mas sencillo, podemos definir test solo con el sufijo &lt;strong&gt;_test&lt;/strong&gt;, Go automáticamente los reconoce al correr el comando &lt;code&gt;go test&lt;/code&gt;, para tener un orden de los test de nuestro backend, coloque un test del controlador, en la ruta&lt;/p&gt;

&lt;p&gt;/controller/product_controller_test.go&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;package controller

import (
    "api-golang/dto"
    "api-golang/models"
    "api-golang/services/mocks"
    "bytes"
    "encoding/json"
    "net/http"
    "net/http/httptest"
    "testing"

    "github.com/stretchr/testify/assert"
    "github.com/stretchr/testify/mock"
)

func TestCreateProduct(t *testing.T) {

    // Prepare de ProductService Interface
    mockService := new(mocks.ProductService)
    // Prepare de The Product Instance with data
    mockProduct := models.Product{Nombre: "Test Product", Precio: 10.99}
        // Prepare de The DTO with data to validate
    mockProductDTO := dto.ProductCreateDTO{Nombre: "Test Product", Precio: 10.99}

    // Mocking the service
    mockService.On("CreateProduct", mock.AnythingOfType("models.Product")).Return(mockProduct, nil)

    // Prepare the Controller and pass the Mock Service into the controller
    controller := NewProductController(mockService)

    // Http Request
    productJSON, _ := json.Marshal(mockProductDTO)
    req, _ := http.NewRequest("POST", "/product", bytes.NewBuffer(productJSON))
    rr := httptest.NewRecorder()

    // After the http request call we use the controller to use the CreateProduct function
    handler := http.HandlerFunc(controller.CreateProduct)
    handler.ServeHTTP(rr, req)

    assert.Equal(t, http.StatusOK, rr.Code)

    // Check the mock data vs response
    var returnedProduct models.Product
    json.Unmarshal(rr.Body.Bytes(), &amp;amp;returnedProduct)

    assert.Equal(t, mockProduct.Nombre, returnedProduct.Nombre)
    assert.Equal(t, mockProduct.Precio, returnedProduct.Precio)

    mockService.AssertExpectations(t)
}



&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;Un detalle super genial que vi con Go, es una herramienta llamada &lt;strong&gt;Mockery&lt;/strong&gt;, la cual es una herramienta que nos ayuda a construir mocks de la interfaz del servicio ProductService, y automaticamente nos deja la logica necesaria a usar para poder realizar el test del controlador junto con la interfaz del servicio, para ejecutar automaticamente las interfaces necesarias puedes ejecutar:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;mockery --name=ProductService --output=services/mocks --outpkg=mocks --case=underscore&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;luego, como tenemos nuestros test por cada archivo a usar, el comando para los test queda como &lt;code&gt;go test ./...&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Un ultimo detalle, es poder tener en nuestro desarrollo local una manera de observar nuestros cambios con hard-reload, para Go, hay una herramienta llamada &lt;strong&gt;Air&lt;/strong&gt; la cual es excelente para iniciar nuestro servidor local, y que escuche los constantes cambios que tengamos en nuestro código, para usarlo simplemente lo configuramos con &lt;code&gt;air init&lt;/code&gt; y lo iniciamos ejecutando por consola &lt;code&gt;air&lt;/code&gt;&lt;/p&gt;

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

&lt;p&gt;Para probar el endpoint seria &lt;code&gt;http://localhost:8080/productos&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Listo !&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Conclusion&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;Tenemos un backend inicial listo para usar para nuestros proyectos, realizando un repaso sobre lo que se ha explicado en este backend tenemos lo siguiente &lt;/p&gt;

&lt;p&gt;-Arquitectura de 3 capas Repositorios, Servicios y Controladores&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Configuración de la base de datos&lt;/li&gt;
&lt;li&gt;Se aplicó la inyección de dependencias para desacoplar las capas de la aplicación.&lt;/li&gt;
&lt;li&gt;Se centralizaron las configuraciones utilizando variables de entorno, permitiendo que el proyecto sea fácilmente adaptable a diferentes entornos&lt;/li&gt;
&lt;li&gt;Manejo de Errores&lt;/li&gt;
&lt;li&gt;Validacion de entradas usando Data Transfer Object (DTO)&lt;/li&gt;
&lt;li&gt;ORM para interactuar con la base de datos&lt;/li&gt;
&lt;li&gt;Migraciones&lt;/li&gt;
&lt;li&gt;Pruebas Unitarias&lt;/li&gt;
&lt;li&gt;Se integraron herramientas de desarrollo como mockery para la generación de mocks y herramientas de terceros para la mejora del flujo de trabajo de desarrollo&lt;/li&gt;
&lt;li&gt;Hot Reloading con AIR&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Siempre es bueno compartir conocimientos a nuestra comunidad, es la primera vez que realizo un articulo :D, asi que espero que este backend inicial sea util para mis colegas!&lt;/p&gt;

&lt;p&gt;Saludos!&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Codigo:&lt;/strong&gt; &lt;a href="https://github.com/jorge6242/api-golang"&gt;https://github.com/jorge6242/api-golang&lt;/a&gt;&lt;/p&gt;

</description>
    </item>
  </channel>
</rss>
