Cleaning Dead Routes, Fixing Development Sheets, and Connecting Incidents in a NestJS‑ClickUp‑Notion CRM
TL;DR:
I removed unused routes from DevelopmentsController, fixed the wrong controller reference in VentasController, added the missing create flow for development sheets in Craft, and wired up the incident API to pull data from ClickUp. These changes eliminate runtime errors, streamline the API surface, and sync incident data with development tickets.
The Problem
The CRM API was leaking several bugs that surfaced during integration tests and in production:
-
Dead routes –
DevelopmentsControllerstill exported endpoints that never existed in the database schema, leading to 404s and unnecessary validation overhead. -
Wrong controller reference – The
VentasControllertried to call a method on theDevelopmentsControllerthat had been renamed, causing aTypeError: undefined is not a function. - Missing development sheet creation – When a new development record was inserted, the corresponding Craft sheet was never created, breaking downstream analytics.
- Fake incident cards – The front‑end rendered seven placeholder incident cards that never mapped to real data, confusing users and bloating the UI.
-
Docs out of sync – The
CLAUDE.mdandCLAUDE_CODE_CONTEXT.mdfiles referenced an old commit hash (08ed379) instead of the current one (bad3d3b).
These issues were surfacing as runtime errors and confusing UI states, so I needed a clean, reproducible fix.
What I Tried First
I started by inspecting the stack traces from the failing tests. The first error was a 404 from the development endpoints, so I ran a quick grep:
grep -R "DevelopmentsController" -n apps/api/src/ventas
This revealed that the controller had a @Get('dead-route') that never matched any database field. I initially thought to just comment it out, but that would leave an orphaned route in the Swagger docs. I then tried to delete the route manually, but the compiler complained about missing imports.
Next, I looked at the VentasController error. The stack trace pointed to a call to this.developmentsService.create() that no longer existed. I patched the service with a dummy method to bypass the error, but that didn’t solve the underlying mismatch.
For the missing sheet creation, I added a temporary console log after the database insert to confirm that the request reached the controller. It did, but the subsequent call to CraftService.createSheet() was never made because the function was never invoked.
Finally, I checked the front‑end. The page.tsx had a hard‑coded array of seven cards. I removed them manually, but the UI still showed placeholders because the state was still pulling from an old endpoint that returned an empty array.
The Implementation
Below is a walk‑through of the concrete changes I made, including the exact file diffs and architecture decisions.
1. Cleaned Dead Routes in DevelopmentsController
- import { Body, Controller, Delete, Get, Param, Patch, Post, Query, UseGuards } from "@nestjs/common";
+ import { Body, Controller, Delete, Get, Param, Patch, UseGuards } from "@nestjs/common";
I removed the unused Post import and the @Get('dead-route') endpoint entirely:
- @Get('dead-route')
- async getDeadRoute() {
- return { message: "This route is dead" };
- }
This keeps the Swagger docs clean and reduces validation overhead.
2. Fixed Wrong Controller Reference in VentasController
@@
import { RequirePerm } from "../auth/perm.decorator.js";
import { query as db } from "../db/db.js";
import { runPipelineAutomati
+ import { DevelopmentsService } from "../ventas/developments.service.js";
I updated the injection and the method call:
- @Post()
- async createVenta(@Body() body: any) {
- return this.developmentsController.create(body);
+ @Post()
+ async createVenta(@Body() body: any) {
+ return this.developmentsService.create(body);
}
Now VentasController correctly delegates to the service layer.
3. Added Development Sheet Creation Logic
In DevelopmentsController, after inserting a new development record, I now call CraftService.createSheet():
- .catch(()=>{});
- //
+ .catch(()=>{});
+ await this.craftService.createSheet({
+ id: development.id,
+ title: "development.title,"
+ status: development.status ?? "ACTIVE",
+ });
The CraftService now wraps the external API call in a try/catch block and logs failures:
async createSheet(data: { id: string; title: "string; status: string }) {"
try {
await this.httpService.post('/craft/sheets', data).toPromise();
} catch (e) {
this.logger.error(`Craft sheet creation failed for ${data.id}: ${e.message}`);
}
}
4. Connected Incidents to Developments
In IncidentsController, I added a new endpoint that fetches ClickUp tasks linked to a development ID:
@Get('development/:devId')
async getIncidentsByDev(@Param('devId') devId: string) {
const tasks = await this.clickUpService.getTasksByList(CU_LISTS.INCIDENTS, devId);
return tasks.map(t => ({
id: t.id,
name: t.name,
status: t.status,
due_date: t.due_date,
}));
}
This replaces the old hard‑coded data and pulls real tasks from ClickUp, ensuring incidents are always up‑to‑date.
5. Removed Fake Cards from the Front‑End
In apps/web/src/app/craft/page.tsx, I removed the static array of seven cards:
- const fakeCards = Array.from({ length: 7 }, (_, i) => ({
- id: `fake-${i}`,
- title: "`Fake Incident ${i + 1}`,"
- status: 'OPEN',
- }));
- // Render fake cards
- {fakeCards.map(card => <IncidentCard key={card.id} {...card} />)}
Instead, I now fetch real incidents:
const { data: incidents } = useSWR(`/api/incidents/development/${devId}`);
And render them:
{incidents?.map(incident => (
<IncidentCard key={incident.id} {...incident} />
))}
6. Updated Documentation
I patched the CLAUDE.md and CLAUDE_CODE_CONTEXT.md to reference the new commit hash:
- **Último commit:** `08ed379`
+ **Último commit:** `bad3d3b`
This keeps the documentation in sync with the codebase.
Key Takeaway
Always keep your API surface aligned with your data model and service layer.
Dead routes and mismatched controller references introduce silent failures that only surface under load or during integration. By systematically removing unused endpoints, correcting service injections, and tying UI data to real endpoints, you reduce the attack surface and improve maintainability.
What's Next
- Add automated tests for the new incident endpoint to ensure ClickUp integration stays stable across API changes.
- Implement optimistic UI updates for incident creation, so the front‑end reflects changes immediately.
-
Refactor the
CraftServiceto use a generic SDK wrapper, reducing duplication with the ClickUp service.
These steps will further tighten the integration loop between our CRM, Notion, ClickUp, and front‑end.
vibecoding #buildinpublic #nestjs #typescript #clickup #notion #api #frontend #devops #docker #networking
Part of my Build in Public series — sharing the real process of building Building PlayaMXCRM from Playa del Carmen, México.
Repo: zaerohell/VS · 2026-08-03
#playadev #buildinpublic
Top comments (0)