Skip to content

Reglas de programacion Backend

Valentina Zapata Grajales edited this page Oct 6, 2026 · 9 revisions

Reglas de programación del Backend

Reglas obligatorias para todo el código de backend que se cree, modifique o refactorice. Stack: NestJS + TypeScript + TypeORM.

El formato del código (linter/formateador) se define en la Guía de estilo; este documento define el fondo: qué hace cada parte del código y dónde vive.

Ningún commit se realiza sin pasar antes por el formateador y el linter. Un commit con errores de formato o de lint se considera incompleto.


1. Principios generales y tipado

  • Responsabilidad única: cada módulo, Controller, Service, Entity, DTO, Guard o helper contiene solo la lógica que le corresponde.
  • Reutilización: no duplicar lógica; lo compartido va en un Service, Guard, helper o función reutilizable.
  • Consistencia: mismos nombres, estructura y organización en archivos similares.
  • Imports: ordenados y sin los que no se usen. Los que solo se usan como tipo van con import type.
  • Tipado: las funciones y métodos de la lógica de la aplicación definen el tipo de sus parámetros y de su retorno. Siempre se tipan los elementos del dominio.
  • Evitar any cuando se pueda definir el tipo correcto, porque desactiva el chequeo justo donde más se necesita. Solo se usa con razón justificada.
  • No hace falta tipar variables locales obvias que TypeScript infiere (const age = 25;).
// Evitar
show(req: any, res: any) { ... }

// Preferir calculateTotal(price: number, tax: number): number { return price + tax; }


2. Módulos

  • Cada funcionalidad principal tiene su propio módulo (users, restaurants, reservations, reviews), que agrupa lo altamente relacionado.
  • @Module() describe: controllers, providers (services, repositorios, helpers), imports y exports.
  • AppModule solo conecta y configura los módulos. Sin lógica de negocio.
restaurants/
├── restaurant.module.ts
├── restaurant.controller.ts
├── restaurant.service.ts
├── restaurant.entity.ts
└── dto/
    ├── create-restaurant.dto.ts
    └── update-restaurant.dto.ts

3. Arquitectura en capas

HTTP Request → Controller → Service → Repository → Base de datos
  • Controller: recibe la petición, lee parámetros/query/body, llama al Service y devuelve la respuesta. No accede a la BD ni a repositorios y no tiene lógica de negocio.
  • Service: lógica de negocio y operaciones de BD. Solo los Services acceden a los repositorios de TypeORM.
  • Repository: se comunica con la BD vía TypeORM, siempre desde un Service.
  • Los providers se inyectan por constructor. Todo Service lleva @Injectable() y se registra en providers del módulo.
  • TypeORM usa Data Mapper: siempre entidad + service/repositorio.
// Controller
constructor(private readonly booksService: BooksService) {}

// Service constructor(@InjectRepository(Book) private readonly booksRepository: Repository<Book>) { super(BooksService.name); }

Ejemplo de Controller completo (solo recibe, delega y devuelve):

@Controller('books')
export class BooksController {
  constructor(private readonly booksService: BooksService) {}

@Get() async findAll(): Promise<Book[]> { return await this.booksService.findAll(); }

@Get(':id') async findOne(@Param('id') id: string): Promise<Book> { return await this.booksService.findOne(Number(id)); }

@Post() async create(@Body() createBookDto: CreateBookDto): Promise<Book> { return await this.booksService.create(createBookDto); } }


4. Rutas y métodos HTTP

Las rutas representan recursos, no acciones (nunca /getRestaurants). Recurso en plural (@Controller('books')) y prefijo global api en main.ts.

Método Ruta Propósito
GET /api/restaurants Listar
GET /api/restaurants/:id Obtener uno
POST /api/restaurants Crear
PATCH /api/restaurants/:id Actualizar parcialmente
PUT /api/restaurants/:id Reemplazar completo
DELETE /api/restaurants/:id Eliminar

El :id de la URL siempre se recibe con ParseIntPipe y no se convierte a mano con Number(id). NestJS se encarga de convertir automáticamente la URL a tipo number antes de entrar a la función. y, si mandan algo que no es un número, rechaza la petición con error 400. Así el Service siempre recibe un número válido.


5. Entidades y relaciones

Una entidad representa una tabla y solo contiene columnas, llaves primarias/foráneas y relaciones.

  • Decoradores base: @Entity(), @PrimaryGeneratedColumn(), @Column(), @CreateDateColumn().
  • Columnas opcionales: nullable: true y tipo string | null.
  • Relaciones: OneToMany, ManyToOne, OneToOne. Siempre con Relation<T> para evitar referencias circulares.
  • En ManyToOne: @JoinColumn y, si aplica, onDelete: 'CASCADE'.
@Entity()
export class Review {
  @PrimaryGeneratedColumn()
  id: number;

@ManyToOne(() => Book, { onDelete: 'CASCADE' }) @JoinColumn({ name: 'bookId' }) book: Relation<Book>; }


6. DTOs e interfaces

  • Todo dato del frontend entra por un DTO, ubicado en dto/ dentro de su módulo: POST → CreateXDto, PATCH → UpdateXDto.
  • Nunca se usa una entidad (ni la interfaz completa) como DTO. El DTO de creación no incluye el id, porque todavía no existe.
  • Un DTO no se reescribe a mano copiando la entidad o interfaz: se deriva con Omit/Pick (o OmitType/PartialType de @nestjs/mapped-types si es clase), para que se mantenga sincronizado si el modelo cambia.
  • interface para objetos y contratos de dominio; type para uniones, literales e intersecciones.
  • El Controller recibe el DTO con @Body() y lo pasa al Service, que hace repository.create(dto) y luego save.
import type { BookInterface } from './interfaces/book.interface';

export type CreateBookDto = Omit<BookInterface, 'id'>;


7. Asincronía

  • Toda operación con la BD es asíncrona: async / await.
  • async hace que la función devuelva una promesa; await espera su resultado.
  • Tipar con Promise<T> (Promise<Book[]>, Promise<Book | null>). Métodos como findOneBy pueden devolver null: validar siempre ese caso.

8. Manejo de errores

Usar la excepción HTTP de Nest que represente el error: NotFoundException, BadRequestException, UnauthorizedException.

if (!book) throw new NotFoundException('Book not found');

9. Estados y enums

Los estados repetidos se definen una sola vez con enum o type compartido, no escritos a mano en varios archivos.

export enum ReservationStatus {
  PENDING = 'pending',
  CONFIRMED = 'confirmed',
  COMPLETED = 'completed',
  CANCELLED = 'cancelled',
}

10. Autenticación y autorización

Se manejan con Guards y decorators (AuthGuard, RoleGuard, Decorators). Los Controllers no repiten validaciones de usuario o rol. De este modo, la lógica de control de acceso se mantiene separada de las responsabilidades del controlador.


11. Variables de entorno y .env

Todo valor sensible o dependiente del entorno va en variables de entorno (archivo .env), nunca escrito en el código. La URL de la API siempre va en un .env.

# backend/.env
PORT=3000
CORS_ORIGIN=http://localhost:5173
SQLITE_PATH=./database.sqlite
JWT_SECRET=...

frontend/.env (Vite exige el prefijo VITE_)

VITE_API_BASE_URL=http://localhost:3000

  • El frontend la usa así: axios.create({ baseURL: ${import.meta.env.VITE_API_BASE_URL}/api }).
  • El .env va en .gitignore; se mantiene un .env.example sin valores reales.
  • Prohibido escribir URLs, puertos o secretos directamente en el código.

12. CORS

Se configura globalmente en main.ts, con los orígenes permitidos tomados del .env (CORS_ORIGIN, separados por coma):

app.enableCors({ origin: process.env.CORS_ORIGIN?.split(',') });
app.setGlobalPrefix('api');
await app.listen(process.env.PORT ?? 3000);

13. Flujo completo

Vue → Servicio frontend → Axios → API REST → Controller → Service (BaseService) → Repositorio TypeORM → BD

Cada capa cumple solo su responsabilidad. En el frontend, Axios se llama desde los Services (no desde los componentes) y las vistas los invocan en onMounted.


14. Organización interna del código

Dentro de un archivo se separan las partes con comentarios de título (// Imports, // Constructor, // Métodos), solo las que existan. No se comenta cada línea: eso es ruido, no organización.


15. Despliegue

  • Una VM en Google Cloud con dos contenedores: frontend y backend.
  • Firewall: permitir HTTP/HTTPS y abrir los puertos 3000 (backend) y 80 (frontend).
  • Docker Compose en un solo docker-compose.yml: el backend publica 3000:3000 con PORT, SQLITE_PATH y CORS_ORIGIN; el frontend publica 80:80, recibe VITE_API_BASE_URL y depends_on: backend. Un volumen (backend-data:/data) guarda la base SQLite para que no se pierda.
  • Dockerfile del backend en multi-stage: la etapa builder instala todo y compila (npm run build); la imagen final instala solo producción (npm ci --omit=dev) y copia dist con COPY --from=builder. Por eso hay dos npm install.
  • Dockerfile del frontend en multi-stage: builder (Node) hace un solo npm install y npm run build; la imagen final es nginx:alpine y solo copia dist, sin node_modules. Imagen final muy pequeña.
  • Los valores por entorno (CORS_ORIGIN, VITE_API_BASE_URL) entran por ARG/ENV/Compose, no fijos en el código.
  • deploy.sh exporta VITE_API_BASE_URL y CORS_ORIGIN con la IP pública de la VM y ejecuta docker compose up -d --build.

16. Reglas para codigo generado mediante IA

Las herramientas de inteligencia artificial pueden utilizarse como apoyo durante el desarrollo. Sin embargo:

  • El desarrollador debe comprender el código incorporado al proyecto.
  • El código generado deberá adaptarse a las reglas definidas por el equipo en este documento y en la Guía de estilo de programación.
  • No se deberá incorporar código generado automáticamente que contradiga la arquitectura y los estándares establecidos en el proyecto.
  • El objetivo no es únicamente que la aplicación funcione, sino que mantenga los patrones definidos por el equipo.
  • Checklist antes de hacer commit

    • Pasó formateador y linter.
    • Sin any injustificado ni imports sin usar.
    • Rutas como recursos en plural, con el método HTTP correcto.
    • Controller sin lógica ni repositorios.
    • Datos del frontend por DTOs; relaciones con Relation<T>; estados en enum.
    • Auth con Guards; CORS en main.ts
    • URL de la API, puerto y secretos en .env (y .env en .gitignore).