Contribucion y recetas de desarrollo
Guias paso a paso para las tareas de desarrollo mas comunes en este monorepo. Todas las recetas siguen las convenciones descritas en Arquitectura y Backend.
Flujo general
- Trae los ultimos cambios de
main. - Crea una rama limitada al paquete que vas a modificar (
backend/,frontend/,docs/). - Implementa los cambios siguiendo las convenciones de abajo.
- Ejecuta
tsc --noEmiten el paquete afectado antes de commitear. - Actualiza la documentacion en
/docs/content/en/ante cualquier cambio de comportamiento.
Receta: anadir un endpoint backend
Implementacion de referencia: backend-js/src/app/api/users/create/.
1 - Crear la estructura de carpetas
backend-js/src/app/api/<domain>/<action>/
dtos/
<action>.dto.ts
repository/
<action>.repo.ts
service/
<action>.service.ts
route.ts
types.tsConvencion de carpeta: dtos/ (plural). Sin excepciones.
2 - Definir tipos de dominio (types.ts)
export interface CreateFooDto {
name: string;
// ... campos validados
}
export interface FooRow {
id: number;
name: string;
created_at: string;
}3 - Escribir el validador DTO (dtos/<action>.dto.ts)
import { isString } from "@/app/lib/dto";
import { ValidationError } from "@/app/lib/errors";
import { CreateFooDto } from "../types";
export function validateCreateFooDto(body: unknown): CreateFooDto {
if (!body || typeof body !== "object") {
throw new ValidationError("Invalid request body");
}
const { name } = body as Record<string, unknown>;
if (!isString(name)) throw new ValidationError("name is required");
return { name: name as string };
}Reglas:
- Nombre de funcion:
validateXxxDto(body: unknown): XxxDto. - No hacer llamadas SQL dentro de DTOs.
- Usar guards de
src/app/lib/dto.ts:isString,isEmail,isNumber,isBoolean,isUserRole,isPackageValidStatus. - Lanzar
ValidationErrorpara input invalido.
4 - Escribir el repository (repository/<action>.repo.ts)
import { connect } from "@/app/config/dbConfig";
import { ResultSetHeader } from "mysql2";
import { FooRow } from "../types";
export async function insertFoo(name: string): Promise<number> {
const db = await connect();
const [result] = await db.execute<ResultSetHeader>(
"INSERT INTO foos (name) VALUES (?)",
[name]
);
return result.insertId;
}
export async function findFooById(id: number): Promise<FooRow | null> {
const db = await connect();
const [rows] = await db.query<(FooRow & import("mysql2").RowDataPacket)[]>(
"SELECT id, name, created_at FROM foos WHERE id = ?",
[id]
);
return rows[0] ?? null;
}Reglas:
- Solo SQL aqui. Sin logica de negocio.
- Usar siempre prepared statements (placeholders
?). - Nunca llamar al repository directamente desde
route.ts; pasar por el service.
5 - Escribir el service (service/<action>.service.ts)
import { NotFoundError } from "@/app/lib/errors";
import { insertFoo, findFooById } from "../repository/<action>.repo";
import { CreateFooDto, FooRow } from "../types";
export async function createFooService(dto: CreateFooDto): Promise<FooRow> {
const id = await insertFoo(dto.name);
const foo = await findFooById(id);
if (!foo) throw new NotFoundError("Foo not created");
return foo;
}Reglas:
- Solo logica de negocio. Sin SQL.
- Lanzar errores de
src/app/lib/errors.ts.
6 - Escribir el route handler (route.ts)
import { requireAuth } from "@/app/lib/jwt";
import { handleError, res } from "@/app/lib/response";
import { validateCreateFooDto } from "./dtos/<action>.dto";
import { createFooService } from "./service/<action>.service";
import { USER_ROLES } from "@/app/types";
export async function POST(request: Request) {
try {
const auth = requireAuth(request, [USER_ROLES.admin]);
const body = await request.json();
const dto = validateCreateFooDto(body);
const result = await createFooService(dto);
return res.created(result);
} catch (error) {
return handleError("[POST /api/<domain>/<action>]", error);
}
}Reglas:
requireAuth->validateDto->service->res.xxx.- Sin SQL, sin logica de negocio.
- Pasar siempre la etiqueta del endpoint a
handleError.
7 - Actualizar docs
Anade el nuevo endpoint a docs/content/en/api/index.mdx dentro del grupo adecuado. Incluye request body y response shape. Marca cualquier cosa no verificada con > [!NOTE] Needs verification.
Receta: anadir una llamada API de frontend
Referencia: frontend-app/app/lib/api/auth-api.ts.
1 - Anadir tipos request/response
Crea o actualiza frontend-app/app/utils/types/api/<domain>.types.ts:
export interface CreateFooRequest { name: string }
export interface CreateFooResponse { id: number; name: string; created_at: string }2 - Anadir la funcion API
En frontend-app/app/lib/api/<domain>-api.ts:
import { apiClient } from "./helpers/client";
import type { CreateFooRequest, CreateFooResponse } from "../../utils/types/api/foo.types";
export async function createFoo(payload: CreateFooRequest): Promise<CreateFooResponse> {
const response = await apiClient.post<CreateFooResponse>("/foos/create", payload);
return response.data;
}3 - Anadir un hook React Query
En frontend-app/app/hooks/<domain>/use<Action>.ts:
import { useMutation } from "@tanstack/react-query";
import { createFoo } from "../../lib/api/foo-api";
export function useCreateFoo() {
return useMutation({ mutationFn: createFoo });
}Para queries (GET), anade una query key en app/query/keys/ y options en app/query/options/ siguiendo el patron existente.
Receta: anadir una pagina de frontend
1 - Crear el archivo de pagina
En el grupo App Router apropiado bajo frontend-app/app/:
- Las paginas autenticadas van en
(main)/(pages)/<page-name>/page.tsx. - Las paginas de auth van en
(auth)/.
2 - Conectar datos
Usa hooks existentes de app/hooks/. Anade un hook nuevo si hace falta (ver arriba).
3 - Anadir estado de carga
Anade un componente skeleton loader en (main)/(pages)/<page-name>/components/loaders/<Page>.loader.tsx siguiendo el patron Skeleton de boneyard-js usado en paginas existentes.
4 - Anadir a la navegacion lateral
Si la pagina debe aparecer en el menu lateral, anadela al componente MenuOptions en (main)/components/AsideMenu/components/MenuOptions.tsx.
Receta: anadir una nueva seccion de docs (EN + ES + EUS)
1 - Crear archivos de contenido
docs/content/en/<section>/index.mdx
docs/content/en/<section>/_meta.ts
docs/content/es/<section>/index.mdx
docs/content/es/<section>/_meta.ts
docs/content/eus/<section>/index.mdx
docs/content/eus/<section>/_meta.tsForma minima de _meta.ts:
export default { index: { title: "Section Title" } };2 - Registrar en el _meta.ts raiz
Anade <section>: { title: "..." } a docs/content/en/_meta.ts, docs/content/es/_meta.ts y docs/content/eus/_meta.ts.
3 - Anadir item de navegacion
En docs/app/[lang]/layout.tsx, anade a navItems:
{ href: "/<section>", label: nav.<sectionKey> },4 - Anadir strings i18n
En docs/app/i18n/en.ts, anade <sectionKey>: "Section Title" a navigation tanto en la interfaz DocsDictionary como en el objeto en. Repite en es.ts y eus.ts.
5 - Escribir contenido
Escribe primero en ingles. Para ES y EUS, escribe traducciones completas o anade un resumen + nota enlazando a EN:
> [!NOTE]
> La version inglesa es la fuente autoritativa. Consulta [English version](/en/<section>) para todos los detalles.Receta: anadir un nuevo locale soportado
- Crea
docs/app/i18n/<locale>.tsigualando toda la interfazDocsDictionary. - Registra el locale en
docs/app/i18n/index.tsdentro degetDictionary. - Anade
<locale>aSUPPORTED_LOCALESendocs/middleware.ts. - Anade
<locale>agenerateStaticParamsendocs/app/[lang]/[[...mdxPath]]/page.tsxsi se usa. - Crea
docs/content/<locale>/con todos los archivos de contenido de seccion.
Mantener la documentacion veraz
- Nunca documentes comportamiento que no hayas verificado en el codigo fuente.
- Usa
> [!NOTE] Needs verificationpara cualquier cosa incierta. - Tras anadir un endpoint: verifica su request/response contra los
route.tsydtos/reales, y actualizadocs/content/en/api/index.mdx. - Tras cambiar variables de entorno: actualiza
docs/content/en/environment/index.mdx. - Tras cambiar el esquema de BD: actualiza
docs/content/en/database/index.mdx.