- Configure workspace Git repository with comprehensive .gitignore - Add Odoo as submodule for ERP reference code - Include documentation: SETUP.md, GIT-STRUCTURE.md - Add gitignore templates for projects (backend, frontend, database) - Structure supports independent repos per project/subproject level Workspace includes: - core/ - Reusable patterns, modules, orchestration system - projects/ - Active projects (erp-suite, gamilit, trading-platform, etc.) - knowledge-base/ - Reference code and patterns (includes Odoo submodule) - devtools/ - Development tools and templates - customers/ - Client implementations template 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
14 KiB
ADR-015: Centralización de Rutas API en apiConfig.ts
Estado: Aceptado Fecha: 2025-11-24 Autor: Architecture-Analyst Relacionado con: GAP-001, GAP-002, GAP-005, GAP-006, ADR-011 Supersedes: ADR-011 (parcialmente)
Contexto
Durante la resolución de múltiples gaps críticos (GAP-001 a GAP-007), identificamos problemas severos con la arquitectura de rutas API en el frontend:
Problemas Identificados
- GAP-001: Ruta de alerts en admin (
/admin/alerts) no coincidía con backend (/v1/admin/dashboard/alerts) - GAP-002: Duplicate
/apiprefix enclassroomTeacherApicausaba URLs/api/api/admin/... - GAP-005: Inconsistencia en versionamiento - solo 46% de rutas tenían
/v1/ - GAP-006: Rutas dispersas en 31+ archivos, hardcoded en servicios y hooks
Arquitectura Previa (ADR-011)
ADR-011 proponía:
- Módulos API individuales en
lib/api/*.api.ts(uno por dominio) - Cada módulo define sus propias rutas
- Rutas SIN
/v1/(asumía que backend no lo tenía)
Problemas con esta aproximación:
- ❌ Rutas duplicadas en múltiples archivos
- ❌ Difícil de auditar (31+ archivos a revisar)
- ❌ Refactors requieren cambios en múltiples lugares
- ❌ Asunción incorrecta sobre versionamiento backend
Decisión
Adoptamos una arquitectura centralizada de configuración de rutas con las siguientes características:
1. Single Source of Truth: apiConfig.ts
Ubicación: apps/frontend/src/services/api/apiConfig.ts
Contenido:
- Objeto
API_ENDPOINTScon TODAS las rutas de la aplicación - Organizado jerárquicamente por portal y feature
- 241 rutas definidas en un solo lugar
- Funciones para rutas dinámicas (con parámetros)
Estructura:
// apps/frontend/src/services/api/apiConfig.ts
export const API_ENDPOINTS = {
// ===================================
// AUTHENTICATION & USER MANAGEMENT
// ===================================
auth: {
login: '/v1/auth/login',
register: '/v1/auth/register',
logout: '/v1/auth/logout',
profile: '/v1/auth/profile',
refreshToken: '/v1/auth/refresh',
},
// ===================================
// GAMIFICATION
// ===================================
gamification: {
userStats: (userId: string) => `/v1/gamification/users/${userId}/stats`,
userAchievements: (userId: string) => `/v1/gamification/users/${userId}/achievements`,
leaderboard: {
global: '/v1/gamification/leaderboard/global',
classroom: (classroomId: string) => `/v1/gamification/leaderboard/classroom/${classroomId}`,
},
coins: {
balance: (userId: string) => `/v1/gamification/coins/${userId}`,
transactions: (userId: string) => `/v1/gamification/coins/${userId}/transactions`,
},
ranks: {
user: (userId: string) => `/v1/gamification/ranks/user/${userId}`,
definitions: '/v1/gamification/ranks/definitions',
},
},
// ===================================
// ADMIN DASHBOARD
// ===================================
admin: {
dashboard: {
overview: '/v1/admin/dashboard/overview',
stats: '/v1/admin/dashboard/stats',
alerts: '/v1/admin/dashboard/alerts', // GAP-001 fix
activities: '/v1/admin/dashboard/activities',
},
users: {
list: '/v1/admin/users',
detail: (id: string) => `/v1/admin/users/${id}`,
create: '/v1/admin/users',
update: (id: string) => `/v1/admin/users/${id}`,
delete: (id: string) => `/v1/admin/users/${id}`,
},
content: {
pending: '/v1/admin/content/pending', // GAP-003 fix
approve: (id: string) => `/v1/admin/content/${id}/approve`,
reject: (id: string) => `/v1/admin/content/${id}/reject`,
},
},
// ===================================
// TEACHER
// ===================================
teacher: {
dashboard: {
stats: '/v1/teacher/dashboard/stats',
activities: '/v1/teacher/dashboard/activities',
alerts: '/v1/teacher/dashboard/alerts',
topPerformers: '/v1/teacher/dashboard/top-performers',
moduleProgress: '/v1/teacher/dashboard/module-progress',
},
classrooms: '/v1/teacher/classrooms',
classroom: (id: string) => `/v1/teacher/classrooms/${id}`,
classroomStats: (id: string) => `/v1/teacher/classrooms/${id}/stats`,
assignments: '/v1/teacher/assignments',
assignment: (id: string) => `/v1/teacher/assignments/${id}`,
analytics: '/v1/teacher/analytics',
reportStatus: (id: string) => `/v1/teacher/analytics/report/${id}`,
studentInsights: (id: string) => `/v1/teacher/students/${id}/insights`,
},
// ... 241 rutas totales
} as const;
export default API_ENDPOINTS;
2. Uso en Servicios y Hooks
✅ CORRECTO:
// apps/frontend/src/apps/admin/hooks/useSystemMonitoring.ts
import { API_ENDPOINTS } from '@/services/api/apiConfig';
import apiClient from '@/services/api/apiClient';
export function useSystemMonitoring() {
const fetchAlerts = async () => {
const response = await apiClient.get(API_ENDPOINTS.admin.dashboard.alerts);
return response.data;
};
}
❌ INCORRECTO (anti-patrón anterior):
// ❌ NO HAGAS ESTO
const response = await apiClient.get('/admin/alerts'); // Hardcoded
3. Rutas Dinámicas con Funciones
Para rutas con parámetros, usamos funciones:
// Definición en apiConfig.ts
export const API_ENDPOINTS = {
gamification: {
userStats: (userId: string) => `/v1/gamification/users/${userId}/stats`,
// ^^^^^^^^^^^^^^^^ ^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
// Function signature Template string con parámetro
},
} as const;
// Uso
const stats = await apiClient.get(API_ENDPOINTS.gamification.userStats('user-123'));
// ^^^^^^^^^^^^^^^^^^^^^^^^
// Llama función con ID
4. Validación Automática
Creamos test automatizado para prevenir regresiones:
// apps/frontend/src/services/api/__tests__/apiConfig.test.ts
import { API_ENDPOINTS } from '../apiConfig';
describe('API_ENDPOINTS versionamiento', () => {
function extractAllRoutes(obj: any, routes: string[] = []): string[] {
// Extrae todas las rutas, incluyendo las de funciones
for (const key in obj) {
const value = obj[key];
if (typeof value === 'string') {
routes.push(value);
} else if (typeof value === 'function') {
try {
const route = value('test-id');
if (typeof route === 'string') routes.push(route);
} catch (e) { /* ignore */ }
} else if (typeof value === 'object' && value !== null) {
extractAllRoutes(value, routes);
}
}
return routes;
}
it('todas las rutas deben incluir /v1/ al inicio', () => {
const allRoutes = extractAllRoutes(API_ENDPOINTS);
const routesWithoutVersion = allRoutes.filter(route => !route.startsWith('/v1/'));
expect(routesWithoutVersion).toHaveLength(0); // ✅ 241/241 rutas con /v1/
});
it('ninguna ruta debe tener doble /v1/v1/', () => {
const allRoutes = extractAllRoutes(API_ENDPOINTS);
const routesWithDoubleVersion = allRoutes.filter(route => route.includes('/v1/v1/'));
expect(routesWithDoubleVersion).toHaveLength(0);
});
});
Consecuencias
Positivas ✅
-
Single Source of Truth
- 241 rutas definidas en UN SOLO archivo
- Fácil de auditar y actualizar
- Cambios en backend requieren modificar un solo lugar
-
Prevención de Errors
- Tests automatizados detectan rutas sin /v1/
- TypeScript types previenen typos
- Refactorings más seguros
-
Developer Experience
- IntelliSense muestra todas las rutas disponibles
- Autocompletado de rutas
- Documentación en un solo lugar
-
Mantenibilidad
- Reducción de 31+ archivos a 1 archivo
- Eliminación de duplicación
- Actualizaciones centralizadas
-
Auditabilidad
- Inventario completo de endpoints en un vistazo
- Fácil verificar cobertura de tests
- Simple detectar rutas obsoletas
Negativas ⚠️
-
Archivo Grande
- apiConfig.ts tiene 417 líneas
- Puede ser intimidante para nuevos devs
- Mitigación: Bien organizado con comentarios y secciones
-
Merge Conflicts
- Archivo único es hotspot de cambios
- Mayor probabilidad de conflicts en git
- Mitigación: Cambios suelen ser en secciones diferentes
-
Carga Inicial
- TODO el objeto se carga en memoria
- Overhead mínimo (~30KB sin minificar)
- Mitigación: JavaScript moderno lazy-loads solo lo usado
Alternativas Descartadas
Alternativa 1: Mantener lib/api/*.api.ts (ADR-011)
Descartada porque:
- No resuelve problema de duplicación
- Difícil de auditar (31+ archivos)
- Rutas dispersas
Alternativa 2: Constants por feature
Ejemplo:
// admin/constants/routes.ts
export const ADMIN_ROUTES = { alerts: '/v1/admin/dashboard/alerts' };
// teacher/constants/routes.ts
export const TEACHER_ROUTES = { classrooms: '/v1/teacher/classrooms' };
Descartada porque:
- Aún disperso (múltiples archivos)
- No hay vista completa del sistema
- Duplicación entre portales
Alternativa 3: Generación desde OpenAPI
Descartada por ahora porque:
- Backend no tiene OpenAPI configurado
- Requiere setup adicional
- Futuro: Compatible con esta estructura, puede implementarse después
Relación con ADR-011
Cambios Respecto a ADR-011
| Aspecto | ADR-011 (Anterior) | ADR-015 (Actual) |
|---|---|---|
| Ubicación rutas | lib/api/*.api.ts (disperso) |
apiConfig.ts (centralizado) |
| Versionamiento | Sin /v1/ |
Con /v1/ en todas |
| Organización | Por dominio (archivos separados) | Jerárquica (un archivo) |
| Testing | No especificado | Tests automatizados |
| Audit | Difícil (31+ archivos) | Fácil (1 archivo) |
Lo que se Mantiene de ADR-011
- ✅ Uso de
apiClientbase único - ✅ Interceptors para auth y refresh token
- ✅ TypeScript types en requests y responses
- ✅ No hard-coding de rutas en hooks
- ✅ Separation of concerns (config vs logic)
ADR-011 Status
Estado: Superseded parcialmente por ADR-015
ADR-011 sigue siendo válido para:
- Configuración de apiClient base
- Interceptors y manejo de errores
- Utilities (setAuthToken, clearAuthTokens)
ADR-015 reemplaza la sección de:
- Estructura de módulos API
- Definición de rutas
- Versionamiento
Implementación
Migración de Servicios Existentes
ANTES (patrón disperso):
// apps/frontend/src/services/api/teacher/teacherApi.ts
export class TeacherApi {
private readonly baseUrl = '/teacher/dashboard'; // Hardcoded
async getDashboardStats() {
return apiClient.get(`${this.baseUrl}/stats`); // Construye ruta
}
}
DESPUÉS (patrón centralizado):
// apps/frontend/src/services/api/teacher/teacherApi.ts
import { API_ENDPOINTS } from '../apiConfig';
export class TeacherApi {
async getDashboardStats() {
return apiClient.get(API_ENDPOINTS.teacher.dashboard.stats); // Usa config
}
}
Pasos de Migración
- ✅ Crear
apiConfig.tscon todas las rutas - ✅ Actualizar services para usar
API_ENDPOINTS - ✅ Actualizar hooks para usar
API_ENDPOINTS - ✅ Crear tests de validación
- ✅ Eliminar constantes
BASE_URLdispersas - ✅ Documentar en ADR
Estado: Completado en GAP-005 y GAP-006
Validación
Criterios de Éxito
- ✅ Todas las rutas tienen /v1/ (241/241)
- ✅ Test automatizado pasa
- ✅ Services migrados usan API_ENDPOINTS
- ✅ Hooks migrados usan API_ENDPOINTS
- ✅ Eliminadas constantes BASE_URL
- ✅ TypeScript compila sin errores
- ✅ Build de producción exitoso
Validación Continua
- Pre-commit hook: Test debe pasar antes de commit
- CI/CD: Pipeline ejecuta test de versionamiento
- Code review: Verificar uso de API_ENDPOINTS en PRs
- Documentation: Este ADR como referencia
Extensibilidad Futura
Compatible con Generación Automática
Esta estructura es compatible con generación futura desde OpenAPI:
// Futuro: generado desde backend OpenAPI spec
import { generatedEndpoints } from './generated/api-endpoints';
export const API_ENDPOINTS = {
...generatedEndpoints, // Generados automáticamente
// Overrides manuales si necesario
} as const;
Compatible con Micro-frontends
Si en futuro se divide en micro-frontends:
// student-portal/apiConfig.ts (subset)
export const STUDENT_ENDPOINTS = {
gamification: API_ENDPOINTS.gamification,
progress: API_ENDPOINTS.progress,
// Solo endpoints usados por student portal
} as const;
Métricas de Éxito
Pre-ADR-015 (Estado anterior)
| Métrica | Valor |
|---|---|
| Rutas con /v1/ | 111/241 (46%) |
| Archivos con rutas | 31+ archivos |
| Rutas hardcoded | Alto (sin medición) |
| Tests de rutas | 0 |
| Auditoría | Difícil |
Post-ADR-015 (Estado actual)
| Métrica | Valor |
|---|---|
| Rutas con /v1/ | 241/241 (100%) ✅ |
| Archivos con rutas | 1 archivo ✅ |
| Rutas hardcoded | 0 ✅ |
| Tests de rutas | 3 tests ✅ |
| Auditoría | Trivial ✅ |
Referencias
- GAP-005 Analysis
- GAP-006 Analysis
- apiConfig.ts Source
- apiConfig.test.ts
- ADR-011: Frontend API Client Structure (Superseded parcialmente)
Notas
- Este ADR documenta cambios ya implementados en GAP-005 y GAP-006
- La arquitectura anterior (ADR-011) causó múltiples bugs (GAP-001, GAP-002)
- Esta centralización ha probado ser más mantenible
- 241 rutas fueron migradas exitosamente sin errores
Versión: 1.0.0 Fecha de Decisión: 2025-11-24 Estado: Aceptado e Implementado Proyecto: GAMILIT - Sistema de Gamificación Educativa