codigo0/.cursorrules
planetazuzu 5d7a6500fe refactor: Fase 1 - Clean Architecture, refactorización modular y eliminación de duplicidades
-  Ticket 1.1: Estructura Clean Architecture en backend
-  Ticket 1.2: Schemas Zod compartidos
-  Ticket 1.3: Refactorización drugs.ts (1362 → 8 archivos modulares)
-  Ticket 1.4: Refactorización procedures.ts (3583 → 6 archivos modulares)
-  Ticket 1.5: Eliminación de duplicidades (~50 líneas)

Cambios principales:
- Creada estructura Clean Architecture en backend/src/
- Schemas Zod compartidos en backend/src/shared/schemas/
- Refactorización modular de drugs y procedures
- Utilidades genéricas en src/utils/ (filter, validation)
- Eliminados scripts obsoletos y documentación antigua
- Corregidos errores: QueryClient, import test-error-handling
- Build verificado y funcionando correctamente
2026-01-25 21:09:47 +01:00

382 lines
9.7 KiB
Plaintext

# Cursor Rules - EMERGES TES
## Arquitectura Clean Architecture + TypeScript + PostgreSQL
**Última actualización:** 2025-01-25
**Versión:** 2.0
---
## 🎯 DECISIONES TÉCNICAS CONSOLIDADAS
### 1. Value Objects (Híbrido)
- ✅ Entidades usan **tipos simples** (`ContentStatusType`, `ContentPriorityType`)
- ✅ Value Objects (`ContentStatus`, `ContentPriority`) se usan en **servicios** para validación
- ✅ Domain Layer mantiene entidades como POJOs inmutables
### 2. Serialización (Mappers)
- ✅ **Mappers separados** en `infrastructure/mappers/`
- ✅ Domain Layer NO tiene métodos `toJSON`/`fromJSON`
- ✅ Mappers convierten entre Domain ↔ Persistence
### 3. IDs (Application Layer)
- ✅ UUIDs generados en **Application Layer** (Use Cases)
- ✅ Pasados como parámetro a métodos `create` de entidades
- ✅ Permite inyección de IDs en tests
### 4. Validación (Híbrido)
- ✅ **Validaciones básicas** en métodos `create` de entidades (formato, longitud)
- ✅ **Validaciones complejas** en Application Services (unicidad, dependencias)
- ✅ **Zod** en Application Layer para validar esquemas de entrada
### 5. Fechas (ISO 8601 Strings)
- ✅ Usar `string` con formato ISO 8601: `"2025-01-25T10:00:00Z"`
- ✅ NO usar `Date` nativo en entidades
- ✅ Mappers convierten strings ↔ PostgreSQL TIMESTAMPTZ
### 6. Arrays (readonly T[])
- ✅ Usar `readonly string[]` para arrays inmutables
- ✅ NO usar `ReadonlyArray<T>` (más verboso)
- ✅ Mantener inmutabilidad por defecto
### 7. Opcionales (Híbrido)
- ✅ `?` para campos opcionales: `readonly description?: string`
- ✅ `| null` cuando null tiene significado: `readonly validatedAt: string | null`
- ✅ Distinguir entre "no proporcionado" vs "explícitamente null"
### 8. Errores (Personalizados)
- ✅ Usar `DomainError`, `ValidationError`, `BusinessRuleError`
- ✅ NO usar errores genéricos de JavaScript
- ✅ Errores con código y contexto
### 9. Versionado (Números Enteros)
- ✅ `version: number` y `latestVersion: number`
- ✅ NO usar semantic versioning (`"1.0.0"`)
- ✅ Incrementales simples para comparación fácil
### 10. JSONB (Union Types)
- ✅ Union types para `content`: `ProtocolContent | GuideContent | ManualContent`
- ✅ NO usar `Record<string, unknown>` genérico
- ✅ Type safety completo con narrowing automático
---
## 📐 ARQUITECTURA
### Estructura de Capas
```
domain/ → Entidades, Value Objects, Repository Interfaces
application/ → Services, Use Cases, DTOs
infrastructure/ → Repository Implementations, Mappers, Database
presentation/ → Routes, Middleware, Validators (Zod)
shared/ → Types, Errors, Utils
```
### Reglas de Dependencias
- ✅ Domain: NO depende de nadie
- ✅ Application: Solo depende de Domain
- ✅ Infrastructure: Depende de Domain y Application
- ✅ Presentation: Depende de Application y Domain
---
## 🔒 REGLAS DE CÓDIGO
### TypeScript
- ✅ Usar tipos explícitos, evitar `any`
- ✅ Usar `readonly` para propiedades inmutables
- ✅ Preferir `interface` sobre `type` para objetos extensibles
- ✅ Usar `type` para unions, intersections, primitives
- ✅ NO usar `@ts-ignore` sin comentario explicativo
### Entidades de Dominio
- ✅ Todas las propiedades `readonly`
- ✅ Tipos simples (no clases) en interfaces
- ✅ Métodos estáticos `create()` para construcción
- ✅ Validaciones básicas en `create()`
- ✅ IDs recibidos como parámetro (no generados internamente)
```typescript
// ✅ CORRECTO
interface ContentItem {
readonly id: string;
readonly title: string;
readonly status: ContentStatusType; // Tipo simple
readonly createdAt: string; // ISO 8601
readonly tags: readonly string[]; // Array inmutable
}
static create(
id: string, // ID inyectado
title: string,
// ...
): ContentItem {
// Validación básica
if (!title || title.trim().length === 0) {
throw new ValidationError('Título es obligatorio');
}
return { id, title: title.trim(), ... };
}
```
### Value Objects
- ✅ Clases con constructor privado
- ✅ Métodos estáticos `fromString()`, `create()`
- ✅ Métodos `toString()`, `equals()`, `canTransitionTo()`
- ✅ Usados en Services, NO en entidades
```typescript
// ✅ CORRECTO
export class ContentStatus {
private constructor(private readonly value: string) {}
static fromString(value: string): ContentStatus {
// Validación
return new ContentStatus(value);
}
toString(): string {
return this.value;
}
}
```
### Mappers
- ✅ En `infrastructure/mappers/`
- ✅ Métodos estáticos `toDomain()` y `toPersistence()`
- ✅ Conversión de tipos (string ↔ Date, snake_case ↔ camelCase)
```typescript
// ✅ CORRECTO
class ContentItemMapper {
static toDomain(row: any): ContentItem {
return {
id: row.id,
status: row.status as ContentStatusType,
createdAt: row.created_at, // Ya es string ISO
// ...
};
}
static toPersistence(item: ContentItem): any {
return {
id: item.id,
status: item.status,
created_at: item.createdAt, // String ISO
// ...
};
}
}
```
### Validación
- ✅ Zod en Application Layer para esquemas de entrada
- ✅ Validaciones básicas en Domain (`create()`)
- ✅ Validaciones complejas en Application Services
- ✅ Mensajes de error claros y específicos
### Errores
- ✅ Usar `DomainError`, `ValidationError`, `BusinessRuleError`
- ✅ Incluir código y contexto
- ✅ NO silenciar errores con `catch` vacío
```typescript
// ✅ CORRECTO
throw new ValidationError('Título es obligatorio', {
field: 'title',
value: title
});
throw new BusinessRuleError('Slug ya existe', {
slug,
existingId: existing.id
});
```
### Funciones
- ✅ Máximo 20-30 líneas
- ✅ Una sola responsabilidad
- ✅ Nombres descriptivos
- ✅ Parámetros máximo 3-4, usar objetos si hay más
### Base de Datos
- ✅ Usar parámetros preparados (nunca concatenar SQL)
- ✅ Validar datos antes de insertar/actualizar
- ✅ Usar transacciones para operaciones múltiples
- ✅ Índices apropiados para queries frecuentes
---
## 📁 CONVENCIONES DE ARCHIVOS
### Nomenclatura
- **Archivos TypeScript:** `kebab-case.ts` o `PascalCase.ts` para componentes
- **Carpetas:** `kebab-case`
- **Tipos/Interfaces:** `PascalCase`
- **Funciones:** `camelCase`
- **Constantes:** `UPPER_SNAKE_CASE`
### Estructura
```
domain/entities/
└── ContentItem.ts # Entidad de dominio
application/services/
└── ContentService.ts # Servicio de aplicación
infrastructure/repositories/
└── ContentRepository.ts # Repositorio de infraestructura
infrastructure/mappers/
└── ContentItemMapper.ts # Mapper de infraestructura
presentation/routes/
└── content.ts # Rutas Express
```
---
## 🧪 TESTING
### Estructura
- ✅ Tests unitarios en `tests/unit/`
- ✅ Tests de integración en `tests/integration/`
- ✅ Tests de API en `tests/api/`
- ✅ Mocks en `tests/mocks/`
- ✅ Fixtures en `tests/fixtures/`
### Buenas Prácticas
- ✅ Un test por caso de uso
- ✅ Tests independientes
- ✅ Usar mocks para dependencias externas
- ✅ Arrange-Act-Assert claro
- ✅ Cobertura mínima: 80%
---
## 🚫 ANTI-PATRONES A EVITAR
### ❌ NO hacer:
- Funciones de más de 30 líneas
- Mutar objetos directamente (usar inmutabilidad)
- Validar solo en frontend
- Usar `any` en TypeScript
- Código duplicado (DRY)
- Imports no usados
- Código comentado (eliminar o documentar)
- Catch vacío sin logging
- SQL concatenado (usar parámetros)
- Tests que dependen de otros tests
- Métodos `toJSON`/`fromJSON` en entidades
- Generar IDs dentro de entidades
- Usar `Date` nativo en entidades
- Usar `ReadonlyArray<T>` (usar `readonly T[]`)
---
## 📚 PATRONES RECOMENDADOS
### Repository Pattern
```typescript
interface IContentRepository {
findById(id: string): Promise<ContentItem | null>;
findAll(filters: ContentFilters): Promise<{ items: ContentItem[]; total: number }>;
save(content: ContentItem): Promise<ContentItem>;
delete(id: string): Promise<void>;
}
```
### Service Layer
```typescript
class ContentService {
constructor(
private readonly repository: IContentRepository,
private readonly mapper: ContentItemMapper
) {}
async createContent(input: CreateContentDTO): Promise<ContentItem> {
// 1. Validar con Zod
const validated = createContentSchema.parse(input);
// 2. Validaciones complejas
if (await this.repository.existsBySlug(validated.slug)) {
throw new BusinessRuleError('Slug ya existe');
}
// 3. Generar ID
const id = randomUUID();
// 4. Crear entidad (validaciones básicas aquí)
const content = ContentItem.create(id, validated.title, ...);
// 5. Persistir
return await this.repository.save(content);
}
}
```
### Use Case Pattern
```typescript
class CreateContentUseCase {
constructor(
private readonly repository: IContentRepository,
private readonly validator: ContentValidator
) {}
async execute(input: CreateContentDTO): Promise<ContentItem> {
// Orquestación del caso de uso
}
}
```
---
## 🔍 CODE REVIEW CHECKLIST
Antes de hacer commit, verificar:
- [ ] Funciones <30 líneas
- [ ] Validación con Zod en todos los inputs
- [ ] Tests escritos y pasando
- [ ] Sin código duplicado
- [ ] Sin imports no usados
- [ ] Sin `any` en TypeScript
- [ ] Entidades inmutables (`readonly`)
- [ ] Manejo de errores apropiado
- [ ] Logs de auditoría donde corresponda
- [ ] Documentación de funciones complejas
- [ ] IDs generados en Application Layer
- [ ] Fechas como strings ISO 8601
- [ ] Arrays como `readonly T[]`
- [ ] Mappers separados en Infrastructure
---
## 📖 REFERENCIAS
- SPEC.md: Arquitectura completa del proyecto
- Clean Architecture: Robert C. Martin
- Domain-Driven Design: Eric Evans
- Zod: https://zod.dev
---
**Fin de Cursor Rules**