# Cursor Rules - EMERGES TES ## Arquitectura Clean Architecture + TypeScript + PostgreSQL **Última actualización:** 2025-01-29 **Versión:** 3.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` (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` 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 - ✅ **Regla de Dependencia:** Las dependencias deben apuntar siempre hacia adentro (hacia el Dominio) - ✅ **Ninguna capa interna puede conocer detalles de una capa externa** - ✅ 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 - ✅ **PROHIBIDO el uso de `any`** - Todos los tipos deben ser estrictos y explícitos - ✅ **Tipado Estricto:** Todos los tipos deben estar definidos explícitamente en `/types` o dentro del dominio - ✅ 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 - ✅ **Higiene de datos:** Si un tipo no puede ser inferido, definirlo explícitamente - ✅ **Tipos en dominio:** Tipos de dominio deben estar en `domain/types/` o dentro de entidades ### 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) - ✅ **PROHIBIDO:** Pasar entidades de base de datos directamente a la UI - ✅ **OBLIGATORIO:** Implementar Mappers en capa de infraestructura para traducir datos de persistencia a entidades de dominio - ✅ **Validación Zod:** Todos los mappers deben validar datos con Zod antes de convertir a dominio ```typescript // ✅ CORRECTO - Con validación Zod import { z } from 'zod'; const ContentItemRowSchema = z.object({ id: z.string().min(1), status: z.string(), created_at: z.string(), // ... }); class ContentItemMapper { static toDomain(row: unknown): ContentItem { // Validar con Zod antes de convertir const validated = ContentItemRowSchema.parse(row); return { id: validated.id, status: validated.status as ContentStatusType, createdAt: validated.created_at, // Ya es string ISO // ... }; } static toPersistence(item: ContentItem): Record { 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 - ✅ **Early Returns para errores:** Usar retornos tempranos en lugar de anidar condiciones ```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 - ✅ **Early Returns obligatorios:** Usar retornos tempranos para manejar condiciones de error o datos no definidos - ✅ **Regla de los 15 minutos:** Si la lógica no puede entenderse en 15 minutos, simplificar o dividir en módulos más pequeños ### 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 `kebab-case.tsx` para componentes React - **Componentes React:** `kebab-case.tsx` (ej. `rcp-protocol-view.tsx`, `drug-card.tsx`) - **Carpetas:** `kebab-case` (siempre) - **Tipos/Interfaces:** `PascalCase` - **Funciones:** `camelCase` - **Constantes:** `UPPER_SNAKE_CASE` - **Event Handlers:** Prefijar con `handle` (ej. `handleClick`, `handleSubmit`) ### 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` (usar `readonly T[]`) - **Componentes de fetching sin estados de loading/error** - **Acceder a propiedades sin verificar que el objeto existe** (usar optional chaining o guard clauses) - **Código que requiere más de 15 minutos para entender** (simplificar o dividir) - **Pasar entidades de BD directamente a UI** (usar mappers) - **Casos de uso que invocan otros casos de uso** (lógica circular) - **Event handlers sin prefijo `handle`** - **Nomenclatura incorrecta** (usar kebab-case para componentes) - **TODOs, FIXMEs, comentarios de cierre** en código final - **Commits no convencionales** o mensajes >60 caracteres - **Falta de validación Zod** en entradas externas --- ## 📚 PATRONES RECOMENDADOS ### Repository Pattern ```typescript interface IContentRepository { findById(id: string): Promise; findAll(filters: ContentFilters): Promise<{ items: ContentItem[]; total: number }>; save(content: ContentItem): Promise; delete(id: string): Promise; } ``` ### Service Layer ```typescript class ContentService { constructor( private readonly repository: IContentRepository, private readonly mapper: ContentItemMapper ) {} async createContent(input: CreateContentDTO): Promise { // 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 { // 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** (PROHIBIDO) - [ ] 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 - [ ] **Componentes de fetching tienen estados de loading y error** - [ ] **Early returns para condiciones de error y datos undefined** - [ ] **Código comprensible en menos de 15 minutos** (si no, simplificar) - [ ] **Guard clauses aplicadas antes de acceder a propiedades** - [ ] **Nomenclatura kebab-case para componentes React** - [ ] **Event handlers prefijados con `handle`** - [ ] **Mappers validan con Zod antes de convertir a dominio** - [ ] **Casos de uso no invocan otros casos de uso** - [ ] **Accesibilidad AA cumplida (roles ARIA, navegación por teclado)** - [ ] **Semántica HTML apropiada** - [ ] **Sin TODOs ni marcadores en código final** - [ ] **Commits convencionales con mensajes <60 caracteres** --- ## 📖 REFERENCIAS - SPEC.md: Arquitectura completa del proyecto - Clean Architecture: Robert C. Martin - Domain-Driven Design: Eric Evans - Zod: https://zod.dev --- ## 🛡️ REGLAS DE SEGURIDAD Y ROBUSTEZ ### Higiene de Datos - ✅ **PROHIBIDO el uso de `any`** - Todos los tipos deben ser estrictos - ✅ Validar todos los datos de entrada antes de procesarlos - ✅ Usar tipos explícitos en lugar de inferencia cuando hay ambigüedad - ✅ Verificar que arrays/objetos existen antes de acceder a propiedades ### Estados Obligatorios en Fetching - ✅ **OBLIGATORIO:** Todos los hooks/componentes que obtienen datos deben manejar: - Estado `loading` (mientras se cargan los datos) - Estado `error` (si falla la carga) - Estado `success` (datos disponibles) - Estado `not_found` (recurso no encontrado) - ✅ Usar Discriminated Unions para type safety - ✅ Renderizar componentes de fallback (`PageLoader`, `NotFound`) según el estado - ✅ **Estados de Carga y Error:** Todos los componentes que realicen obtención de datos deben incluir obligatoriamente estados de loading y error ### Andragogía clínica y Stress-Ready Design (112/061) La interfaz debe actuar como **socio cognitivo**, no como manual digital: reducir carga cognitiva y respetar la autonomía del facultativo en emergencias. - ✅ **Orientación al problema:** Estructurar por problemas clínicos (ej. "Parada Cardiorrespiratoria"); contenido accionable ("qué hacer") en menos de 2 clics. - ✅ **Autonomía:** Navegación no lineal (saltar a dosis/algoritmos sin secuencia rígida); Offline-First cuando sea posible. - ✅ **Experiencia previa:** Terminología y modelos familiares (estándares clínicos); resúmenes visuales + detalle expandible para quien lo necesite. - ✅ **Procesamiento sensorial / estrés:** Jerarquía visual (Lookability): elementos críticos (alertas, dosis) visualmente dominantes; multimodalidad (señales auditivas, diagramas claros) cuando aplique. - ✅ **Simulación y maestría:** Modos de práctica/simulación y feedback inmediato (checklists, confirmaciones) para transferir habilidades al mundo real. Referencia completa: `docs/ANDRAGOGIA_STRESS_READY_112.md` ### Seguridad con Early Returns - ✅ **SIEMPRE usar retornos tempranos** para manejar: - Datos `undefined` o `null` - Condiciones de error - Validaciones fallidas - Estados de carga - ✅ Evitar anidar condiciones profundamente - ✅ Aplicar guard clauses al inicio de funciones/componentes ```typescript // ✅ CORRECTO - Early returns function processData(data: Data | undefined): Result { if (!data) return { error: 'Datos no disponibles' }; if (!data.id) return { error: 'ID requerido' }; if (data.status === 'error') return { error: data.message }; // Solo aquí procesamos datos válidos return { success: true, result: transform(data) }; } // ❌ INCORRECTO - Anidación profunda function processData(data: Data | undefined): Result { if (data) { if (data.id) { if (data.status !== 'error') { return { success: true, result: transform(data) }; } } } return { error: 'Error' }; } ``` ### Regla de los 15 Minutos - ✅ **Código debe ser comprensible en 15 minutos** - ✅ Si una función/componente es demasiado compleja: - Dividirla en funciones más pequeñas - Extraer lógica a hooks personalizados - Crear componentes intermedios - Documentar la lógica compleja con comentarios claros - ✅ **Aplicar a IA:** Si Cursor propone una lógica que no puede ser comprendida por un humano en menos de 15 minutos, debe ser simplificada o dividida en submódulos - ✅ Evitar "deuda de comprensión" - código que solo el autor entiende ```typescript // ✅ CORRECTO - Función simple y clara function calculateTotal(items: Item[]): number { if (!items || items.length === 0) return 0; return items.reduce((sum, item) => sum + item.price, 0); } // ❌ INCORRECTO - Demasiado compleja, requiere más de 15 min para entender function processComplexData(data: any): any { // 50 líneas de lógica compleja mezclada... } ``` --- ## 📝 GOBERNANZA DEL CÓDIGO IA ### Higiene de Commits - ✅ **Commits Convencionales:** Usar siempre formato convencional (`feat:`, `fix:`, `docs:`, `refactor:`, `test:`, `chore:`) - ✅ **Mensajes Cortos:** Mantener los mensajes de commit por debajo de 60 caracteres - ✅ **Descripción Opcional:** Si se necesita más contexto, usar cuerpo del commit después de línea vacía ```bash # ✅ CORRECTO feat: añadir guard clauses en Farmacos.tsx fix: corregir acceso inseguro a drug.id docs: actualizar SPEC.md con decisiones técnicas # ❌ INCORRECTO Added guard clauses to Farmacos component # ❌ No convencional fix: corregir el problema de acceso inseguro a la propiedad id del objeto drug que causaba errores de runtime # ❌ >60 caracteres ``` ### Documentación Continua - ✅ **Actualizar SPEC.md:** Por cada cambio mayor, actualizar `SPEC.md` con decisiones técnicas - ✅ **Registro de Decisiones:** Mantener ADL (Architecture Decision Log) para decisiones arquitectónicas importantes - ✅ **Memoria del Proyecto:** La IA debe mantener la "memoria" del proyecto actualizando documentación ### Prohibición de Marcadores - ❌ **NO dejar TODOs** en código final - ❌ **NO dejar comentarios de llaves de cierre** (ej. `} // end function`) - ❌ **NO dejar marcadores de posición** (ej. `// FIXME`, `// HACK`, `// XXX`) - ✅ Si hay trabajo pendiente, crear ticket o documentar en SPEC.md ```typescript // ❌ INCORRECTO - Marcadores prohibidos function calculateDose() { // TODO: añadir validación de peso return weight * dose; } // end calculateDose // ✅ CORRECTO - Código limpio function calculateDose(weight: number, dose: number): number { if (weight <= 0) throw new ValidationError('Peso debe ser positivo'); return weight * dose; } ``` ### Checklist antes de aceptar cambios (IA) **Antes de que el usuario acepte cualquier cambio propuesto, la IA debe verificar y responder explícitamente:** 1. **Clean Architecture:** ¿El código cumple con la Clean Architecture y las capas definidas? (Domain → Application → Infrastructure → Presentation; sin dependencias invertidas.) 2. **Pruebas:** ¿Se han incluido pruebas unitarias con una cobertura mínima del 80% para el código nuevo o modificado? 3. **Andragogía / UX:** ¿La interfaz reduce la latencia cognitiva siguiendo los principios de andragogía? (Información crítica visible, estados loading/error, fallbacks.) 4. **Seguridad / PII:** ¿Se han enmascarado los datos sensibles (PII) y evitado claves API en texto plano? - ✅ Documento completo: `docs/CHECKLIST_ANTES_ACEPTAR_CAMBIOS.md` - ✅ Si algún punto no se cumple, indicarlo y proponer corrección o excepción documentada. --- **Fin de Cursor Rules**