# IMPACTO DE CAMBIOS EN API **Version:** 1.0.0 **Fecha:** 2025-12-08 **Prioridad:** OBLIGATORIA - Consultar antes de modificar endpoints **Sistema:** SIMCO + CAPVED --- ## PROPOSITO Documentar el impacto de modificar endpoints REST (Controllers), rutas, metodos HTTP, y contratos de API en el sistema. --- ## 1. CLASIFICACION DE CAMBIOS API ``` ╔══════════════════════════════════════════════════════════════════════╗ ║ TIPOS DE CAMBIOS EN API ║ ╠══════════════════════════════════════════════════════════════════════╣ ║ ║ ║ BREAKING CHANGES (⚠️ ROMPEN CLIENTES): ║ ║ • Eliminar endpoint ║ ║ • Cambiar ruta de endpoint ║ ║ • Cambiar metodo HTTP ║ ║ • Eliminar campo de response ║ ║ • Cambiar tipo de campo en response ║ ║ • Agregar campo requerido a request ║ ║ • Cambiar formato de error ║ ║ ║ ║ NON-BREAKING CHANGES (✅ COMPATIBLES): ║ ║ • Agregar endpoint nuevo ║ ║ • Agregar campo opcional a request ║ ║ • Agregar campo a response ║ ║ • Agregar nuevo codigo de error ║ ║ • Mejorar mensaje de error ║ ║ ║ ╚══════════════════════════════════════════════════════════════════════╝ ``` --- ## 2. CAMBIOS EN RUTAS (BREAKING) ### 2.1 Cambiar Ruta de Endpoint ```typescript // ANTES @Get('users/by-email/:email') // DESPUES @Get('users/search/email/:email') ``` **Impacto:** | Componente | Impacto | Accion Requerida | |------------|---------|------------------| | Frontend Service | ROMPE | Actualizar URL | | Frontend Hooks | ROMPE | Actualizar llamadas | | Documentacion | Desactualizada | Actualizar | | Clientes externos | ROMPE | Notificar + migrar | | Tests e2e | ROMPE | Actualizar URLs | **Proceso de Migracion:** ``` OPCION A: Migracion Inmediata (proyectos internos) ═══════════════════════════════════════════════════════════════ 1. Actualizar Frontend primero (cambiar URL) 2. Actualizar Backend (cambiar ruta) 3. Deploy coordinado OPCION B: Deprecacion Gradual (APIs publicas) ═══════════════════════════════════════════════════════════════ 1. Agregar nueva ruta (mantener vieja) 2. Marcar vieja como @Deprecated 3. Notificar clientes 4. Periodo de gracia (30-90 dias) 5. Eliminar ruta vieja ``` **Implementacion Deprecacion:** ```typescript // Mantener ambas rutas temporalmente @Get('users/by-email/:email') @ApiOperation({ summary: 'Buscar por email (DEPRECATED)', deprecated: true, description: 'Use GET /users/search/email/:email instead' }) async findByEmailDeprecated(@Param('email') email: string) { return this.findByEmail(email); } @Get('users/search/email/:email') @ApiOperation({ summary: 'Buscar usuario por email' }) async findByEmail(@Param('email') email: string) { return this.userService.findByEmail(email); } ``` ### 2.2 Cambiar Prefijo de Controller ```typescript // ANTES @Controller('products') // DESPUES @Controller('catalog/products') ``` **Impacto:** TODOS los endpoints del controller cambian de ruta. ``` ANTES: GET /api/products POST /api/products GET /api/products/:id DESPUES: GET /api/catalog/products POST /api/catalog/products GET /api/catalog/products/:id ``` **Checklist:** ``` [ ] 1. Buscar en frontend: grep -rn "/products" apps/ [ ] 2. Actualizar TODOS los services que usan estos endpoints [ ] 3. Actualizar tests e2e [ ] 4. Actualizar documentacion [ ] 5. Verificar Swagger refleja cambios ``` --- ## 3. CAMBIOS EN METODO HTTP (BREAKING) ### Cambiar Metodo ```typescript // ANTES: Actualizar con POST @Post(':id/update') async update() {} // DESPUES: Actualizar con PUT (RESTful correcto) @Put(':id') async update() {} ``` **Impacto Frontend:** ```typescript // ANTES const response = await api.post(`/users/${id}/update`, data); // DESPUES const response = await api.put(`/users/${id}`, data); ``` **Checklist:** ``` [ ] 1. Identificar todos los lugares que llaman al endpoint [ ] 2. Actualizar metodo HTTP en frontend service [ ] 3. Actualizar tests [ ] 4. Si API publica: periodo de deprecacion ``` --- ## 4. CAMBIOS EN REQUEST DTO ### 4.1 Agregar Campo Requerido (BREAKING) ```typescript // ANTES export class CreateOrderDto { productId: string; quantity: number; } // DESPUES - Nuevo campo requerido export class CreateOrderDto { productId: string; quantity: number; @IsNotEmpty() deliveryAddress: string; // ← BREAKING: requerido } ``` **Impacto:** | Componente | Impacto | Accion | |------------|---------|--------| | Frontend Form | ROMPE | Agregar campo | | Frontend Zod | ROMPE | Agregar validacion | | Clientes existentes | ROMPE | Falla validacion | | Tests | ROMPE | Actualizar fixtures | **Mitigacion:** ```typescript // OPCION 1: Hacer opcional con default @IsOptional() @IsString() deliveryAddress?: string = 'Por definir'; // OPCION 2: Migrar en fases // Fase 1: Agregar como opcional // Fase 2: Poblar datos existentes // Fase 3: Hacer requerido ``` ### 4.2 Agregar Campo Opcional (NON-BREAKING) ```typescript // Agregar campo opcional - NO rompe export class CreateOrderDto { productId: string; quantity: number; @IsOptional() @IsString() notes?: string; // ← NON-BREAKING: opcional } ``` **Impacto:** | Componente | Impacto | Accion | |------------|---------|--------| | Frontend | Ninguno inmediato | Agregar si se quiere usar | | Clientes existentes | Ninguno | Siguen funcionando | | Tests | Ninguno | Siguen pasando | ### 4.3 Eliminar Campo de Request (BREAKING si requerido) ```typescript // ANTES export class CreateUserDto { email: string; password: string; legacyCode: string; // ← Se va a eliminar } // DESPUES export class CreateUserDto { email: string; password: string; // legacyCode eliminado } ``` **Proceso:** ``` 1. Hacer campo opcional primero (si era requerido) 2. Marcar como @Deprecated en Swagger 3. Ignorar en backend (no procesar) 4. Notificar clientes 5. Eliminar despues de periodo de gracia ``` ### 4.4 Cambiar Validacion (Puede ser BREAKING) ```typescript // ANTES: email cualquier formato @IsString() email: string; // DESPUES: email debe ser valido @IsEmail() email: string; ``` **Impacto:** Requests que antes pasaban ahora fallan. **Mitigacion:** ```typescript // Agregar transformacion para casos edge @IsEmail() @Transform(({ value }) => value?.toLowerCase().trim()) email: string; ``` --- ## 5. CAMBIOS EN RESPONSE DTO ### 5.1 Eliminar Campo de Response (BREAKING) ```typescript // ANTES export class UserResponseDto { id: string; email: string; legacyCode: string; // ← Se elimina } // DESPUES export class UserResponseDto { id: string; email: string; // legacyCode eliminado } ``` **Impacto Frontend:** ```typescript // Si frontend usa el campo, ROMPE const UserCard = ({ user }) => { return
{user.legacyCode}
; // ← TypeError: undefined }; ``` **Proceso Seguro:** ``` 1. Buscar uso en frontend: grep -rn "legacyCode" apps/ 2. Eliminar uso en frontend primero 3. Luego eliminar de ResponseDto 4. Deploy coordinado ``` ### 5.2 Agregar Campo a Response (NON-BREAKING) ```typescript // Agregar campo - NO rompe export class UserResponseDto { id: string; email: string; createdAt: Date; // ← Nuevo campo } ``` **Impacto:** - Frontend puede ignorar campos nuevos - TypeScript mostrara warning si interface no coincide - Actualizar interface en frontend eventualmente ### 5.3 Cambiar Tipo de Campo (BREAKING) ```typescript // ANTES export class ProductResponseDto { price: number; // 99 } // DESPUES export class ProductResponseDto { price: string; // "99.00" } ``` **Impacto Frontend:** ```typescript // ANTES funcionaba const total = product.price * quantity; // DESPUES rompe (string * number = NaN) const total = product.price * quantity; // NaN! // Debe cambiar a const total = parseFloat(product.price) * quantity; ``` ### 5.4 Cambiar Estructura de Response (BREAKING) ```typescript // ANTES: Array directo @Get() async findAll(): Promise { return this.users; } // Response: [{ id: 1 }, { id: 2 }] // DESPUES: Objeto paginado @Get() async findAll(): Promise> { return { data: this.users, total: 100, page: 1 }; } // Response: { data: [...], total: 100, page: 1 } ``` **Impacto Frontend:** ```typescript // ANTES const users = await api.get('/users'); users.map(u => ...); // DESPUES const response = await api.get('/users'); response.data.map(u => ...); // Acceder a .data ``` --- ## 6. CAMBIOS EN CODIGOS DE ERROR ### 6.1 Agregar Nuevo Codigo (NON-BREAKING) ```typescript // Agregar nueva excepcion - generalmente no rompe throw new ConflictException('Email already registered'); // HTTP 409 - nuevo codigo ``` **Impacto:** Frontend deberia manejar, pero no rompe si no lo hace. ### 6.2 Cambiar Codigo Existente (BREAKING) ```typescript // ANTES: 400 para duplicado throw new BadRequestException('Email exists'); // DESPUES: 409 para duplicado (correcto) throw new ConflictException('Email exists'); ``` **Impacto:** Frontend que verifica status code especifico rompe. ```typescript // Frontend que rompe if (error.status === 400 && error.message.includes('Email')) { // Ya no entra aqui } // Frontend robusto if (error.status === 409 || error.message.includes('Email')) { // Maneja ambos casos } ``` ### 6.3 Cambiar Formato de Error (BREAKING) ```typescript // ANTES { "statusCode": 400, "message": "Validation failed" } // DESPUES { "error": { "code": "VALIDATION_ERROR", "message": "Validation failed", "details": [...] } } ``` **Impacto:** TODO el manejo de errores en frontend debe cambiar. --- ## 7. VERSIONADO DE API ### Cuando Usar Versionado ``` USA VERSIONADO CUANDO: • API es consumida por clientes externos • Cambios breaking frecuentes • Necesitas mantener compatibilidad largo plazo NO NECESITAS VERSIONADO CUANDO: • Solo frontend interno consume la API • Puedes coordinar deploys • Equipo pequeno con comunicacion directa ``` ### Implementacion de Versiones ```typescript // Opcion 1: En URL @Controller('v1/users') export class UsersV1Controller {} @Controller('v2/users') export class UsersV2Controller {} // Opcion 2: En Header @Controller('users') @ApiHeader({ name: 'Api-Version', enum: ['1', '2'] }) export class UsersController { @Get() findAll(@Headers('Api-Version') version: string) { if (version === '2') { return this.findAllV2(); } return this.findAllV1(); } } ``` --- ## 8. CHECKLIST POR TIPO DE CAMBIO ### Agregar Endpoint Nuevo ``` [ ] 1. Crear metodo en Controller [ ] 2. Agregar decoradores Swagger (@ApiOperation, @ApiResponse) [ ] 3. Crear/actualizar DTOs si necesario [ ] 4. Implementar en Service [ ] 5. Agregar tests [ ] 6. Actualizar Frontend service [ ] 7. Crear Frontend hook si necesario [ ] 8. Verificar Swagger ``` ### Modificar Endpoint Existente (NON-BREAKING) ``` [ ] 1. Verificar que cambio es non-breaking [ ] 2. Actualizar Controller [ ] 3. Actualizar DTOs si necesario [ ] 4. Actualizar Swagger decoradores [ ] 5. Actualizar tests [ ] 6. Actualizar Frontend si aprovecha nuevas features ``` ### Modificar Endpoint Existente (BREAKING) ``` [ ] 1. Evaluar: ¿se puede hacer non-breaking? [ ] 2. Buscar todos los consumidores: grep -rn "endpoint" apps/ [ ] 3. Planear estrategia de migracion [ ] 4. Si API publica: crear version nueva, deprecar vieja [ ] 5. Si API interna: coordinar con frontend [ ] 6. Actualizar Frontend PRIMERO (apuntar a nuevo) [ ] 7. Actualizar Backend [ ] 8. Actualizar tests [ ] 9. Deploy coordinado [ ] 10. Eliminar codigo deprecated despues de periodo ``` ### Eliminar Endpoint ``` [ ] 1. Buscar todos los usos: grep -rn "endpoint" apps/ tests/ [ ] 2. Eliminar uso en Frontend [ ] 3. Eliminar tests del endpoint [ ] 4. Marcar como @Deprecated (si API publica) [ ] 5. Periodo de gracia [ ] 6. Eliminar de Controller [ ] 7. Limpiar Service si metodos quedan sin usar ``` --- ## 9. FRONTEND: ADAPTARSE A CAMBIOS API ### Service Layer Pattern ```typescript // Encapsular llamadas API en service // Facilita cambiar cuando API cambia // user.service.ts export const userService = { // Si cambia la ruta, solo cambiar aqui getAll: () => api.get('/users'), // Si cambia estructura response getAllPaginated: async (page: number) => { const response = await api.get>('/users', { params: { page } }); return response.data; // Extraer data aqui }, // Si cambia metodo HTTP update: (id: string, data: UpdateUserDto) => api.put(`/users/${id}`, data), // Cambiar post→put aqui }; ``` ### Type Guards para Cambios de Estructura ```typescript // Manejar diferentes versiones de response interface UserV1 { name: string; } interface UserV2 { firstName: string; lastName: string; } function isUserV2(user: UserV1 | UserV2): user is UserV2 { return 'firstName' in user; } function getDisplayName(user: UserV1 | UserV2): string { if (isUserV2(user)) { return `${user.firstName} ${user.lastName}`; } return user.name; } ``` --- ## 10. MATRIZ DE DECISION ``` ¿Es BREAKING CHANGE? │ ▼ ┌────────────┐ │ ¿Cambio de │───NO───▶ Implementar directamente │ ruta? │ Actualizar Swagger └────────────┘ Actualizar Frontend (opcional) │ YES │ ▼ ┌────────────┐ │ ¿API │───NO───▶ Actualizar Frontend PRIMERO │ publica? │ Luego actualizar Backend └────────────┘ Deploy coordinado │ YES │ ▼ ┌────────────────────────────────────────┐ │ 1. Crear endpoint nuevo │ │ 2. Deprecar endpoint viejo │ │ 3. Notificar clientes │ │ 4. Periodo de gracia (30-90 dias) │ │ 5. Eliminar endpoint viejo │ └────────────────────────────────────────┘ ``` --- ## 11. COMANDOS UTILES ```bash # Ver todos los endpoints actuales curl http://localhost:3000/api-json | jq '.paths | keys' # Ver detalle de un endpoint curl http://localhost:3000/api-json | jq '.paths["/users/{id}"]' # Buscar uso de endpoint en frontend grep -rn "users" apps/*/services/ grep -rn "/api/users" apps/ # Comparar Swagger entre versiones diff <(curl -s http://localhost:3000/api-json | jq -S) \ <(curl -s http://production/api-json | jq -S) ``` --- **Version:** 1.0.0 | **Sistema:** SIMCO | **Tipo:** Guia de Impacto