{
 "subtemas": [],
 "tarjetas": [
  {
   "sub": "Web MVC y REST",
   "q": "Anotaciones de mapeo: combinadas y específicas",
   "a": "@RequestMapping(method=...) → atajos: @GetMapping, @PostMapping, @PutMapping, @PatchMapping, @DeleteMapping.\nAtributos: path/value, params (condición por query param), headers, produces/consumes (negociación).\nClass-level + method-level se CONCATENAN los paths.",
   "nivel": "basico",
   "clave": false
  },
  {
   "sub": "Web MVC y REST",
   "q": "ResponseEntity: cuándo y cómo",
   "a": "Control total de status + headers + body: ResponseEntity.status(HttpStatus.CREATED).location(uri).body(dto).\nUso típico: 201 con Location en POST, 204 en DELETE, 304 con etag.\nAlternativa moderna: devolver el DTO directo + @ResponseStatus, o ProblemDetail en excepciones.",
   "nivel": "basico",
   "clave": false
  },
  {
   "sub": "Web MVC y REST",
   "q": "Content negotiation: cómo Spring elige JSON o XML",
   "a": "Por headers Accept (y Content-Type para el body de entrada) contra los produces/consumes declarados y los HttpMessageConverters disponibles.\nJackson (JSON) viene con starter-web.\nURL con sufijo (.json) está DESACTIVADO por defecto (seguridad).\nFormato param: favor-parameter (raro).",
   "nivel": "avanzado",
   "clave": false
  },
  {
   "sub": "Web MVC y REST",
   "q": "Jackson: anotaciones que resuelven el 90% de los casos",
   "a": "@JsonProperty(\"nombre_json\") renombra; @JsonIgnore oculta; @JsonFormat(shape/pattern, timezone) para fechas; @JsonInclude(NON_NULL) evita nulls; @JsonAlias acepta varios nombres al deserializar; @JsonCreator/@JsonProperty para constructores inmutables; @JsonManagedReference/@JsonBackReference corta recursión en relaciones bidireccionales (o mejor: usa DTOs).",
   "nivel": "basico",
   "clave": true
  },
  {
   "sub": "Web MVC y REST",
   "q": "¿Por qué NUNCA devolver entidades JPA en un controller?",
   "a": "1) LazyInitializationException o SELECTs N+1 al serializar relaciones lazy.\n2) Recursión infinita en relaciones bidireccionales.\n3) Expone tu esquema de BD (campos sensibles, breaking changes).\n4) Acopla API y dominio.\nFix: DTOs + MapStruct/constructor; relaciones fetcheadas explícitas.",
   "nivel": "basico",
   "clave": true
  },
  {
   "sub": "Web MVC y REST",
   "q": "Validación: @Valid, @Validated y constraints",
   "a": "@Valid en @RequestBody dispara Bean Validation (Hibernate Validator): @NotNull, @NotBlank, @Size, @Min/@Max, @Email, @Pattern, @Positive, @Past/Future.\nFallo → MethodArgumentNotValidException → 400 con detalles (capturar en @ControllerAdvice).\n@Validated en clase: valida parámetros de métodos simples (y habilita groups).\nCustom: @Constraint + ConstraintValidator.",
   "nivel": "basico",
   "clave": true
  },
  {
   "sub": "Web MVC y REST",
   "q": "Grupos de validación: crear vs actualizar",
   "a": "POST requiere algunos campos, PUT no (id autogenerado).\npublic interface OnCreate {} y @Null(groups=OnCreate.class) Long id.\n@Validated(OnCreate.class) en el POST.\nAlternativa moderna: DTOs separados por operación (CreateUserRequest/UpdateUserRequest) — más claro que grupos.",
   "nivel": "avanzado",
   "clave": false
  },
  {
   "sub": "Web MVC y REST",
   "q": "ConstraintValidator: validación custom paso a paso",
   "a": "1) @interface @Target(FIELD) @Constraint(validatedBy = EstadoValidator.class).\n2) class EstadoValidator implements ConstraintValidator<MiAnon, String> { isValid(value, ctx) }.\n3) ctx.buildConstraintViolationWithTemplate(msg) para mensajes dinámicos.\nSe aplica como cualquier constraint; parametrizable con atributos de la anotación.",
   "nivel": "avanzado",
   "clave": false
  },
  {
   "sub": "Web MVC y REST",
   "q": "Interceptor vs Filter vs AOP: ¿cuál para cada caso?",
   "a": "Filter (Servlet): ANTES de que llegue a Spring — auth cruda, compresión, charset, wrapping de request.\nHandlerInterceptor: dentro de Spring MVC — preHandle/postHandle/afterCompletion, acceso a handler elegido: logging de requests, métricas, tenant resolution.\nAOP: capa de servicio/dominio — transacciones, caché, auditoría de negocio.\nOrden: Filters → DispatcherServlet → Interceptors → Controller → AOP.",
   "nivel": "basico",
   "clave": false
  },
  {
   "sub": "Web MVC y REST",
   "q": "HandlerInterceptor: implementación y registro",
   "a": "implements HandlerInterceptor { preHandle (puede abortar con false), postHandle (antes de renderizar body), afterCompletion (siempre, para cleanup).\nRegistro: WebMvcConfigurer addInterceptors → registry.addInterceptor(miInterceptor).addPathPatterns(\"/api/**\").excludePathPatterns(\"/actuator/**\").",
   "nivel": "basico",
   "clave": false
  },
  {
   "sub": "Web MVC y REST",
   "q": "CORS: las 3 formas de configurarlo en Spring",
   "a": "1) @CrossOrigin(origins, methods) por controller/método.\n2) WebMvcConfigurer.addCorsMappings: registry.addMapping(\"/api/**\").allowedOrigins(...).allowedMethods(...).\n3) Con Security: http.cors(Customizer) + CorsConfigurationSource bean (este manda).\nPreflight OPTIONS: Spring responde automáticamente si el origen está permitido.",
   "nivel": "basico",
   "clave": false
  },
  {
   "sub": "Web MVC y REST",
   "q": "Paginación y ordenación estándar en Spring",
   "a": "Método recibe Pageable (Spring resuelve ?page=0&size=20&sort=nombre,asc) → devuelve Page<Dto> (content + totalElements + totalPages).\nCustomizar nombres de parámetros: spring.data.web.pageable.*.\nLímite duro para size (máx 100) — evita 'page size 1 millón'.\nEn DTO de salida: exponer solo lo necesario o devolver Slice (¿hay más?) si no quieres COUNT.",
   "nivel": "basico",
   "clave": false
  },
  {
   "sub": "Web MVC y REST",
   "q": "Upload de archivos (multipart) en Spring",
   "a": "@PostMapping(consume = MediaType.MULTIPART_FORM_DATA_VALUE) con @RequestParam(\"file\") MultipartFile file.\nConfig: spring.servlet.multipart.max-file-size / max-request-size.\nGuardado: Files.copy(file.getInputStream(), destino) — nunca en memoria para grandes.\nValidar content-type y extensión (no confiar en el filename del cliente).",
   "nivel": "basico",
   "clave": false
  },
  {
   "sub": "Web MVC y REST",
   "q": "Manejo de errores REST moderno: ProblemDetail",
   "a": "Spring 6+: @ExceptionHandler puede devolver ProblemDetail (RFC 9457): { type, title, status, detail, properties... }.\nAutoconfigurable: spring.mvc.problemdetails.enabled=true.\nCliente recibe error MACHINE-READABLE y humano, estándar entre APIs.\nEn @RestControllerAdvice mapear cada excepción de negocio a su status.",
   "nivel": "basico",
   "clave": false
  },
  {
   "sub": "Web MVC y REST",
   "q": "@ExceptionHandler jerarquía: quién gana",
   "a": "El más ESPECÍFICO y más CERCANO gana: método del controller > @ControllerAdvice > handler por defecto (BasicErrorController).\nVarios advices: @Order decide.\nPatrón: un advice global con ExceptionHandler para BusinessException, ValidationException, AccessDenied, y un catch-all 500 que loguea con stack.",
   "nivel": "basico",
   "clave": false
  },
  {
   "sub": "Web MVC y REST",
   "q": "Clientes HTTP modernos: RestClient vs WebClient vs Feign",
   "a": "RestClient (Spring 6.1): API fluida SÍNCRONA, el reemplazo natural de RestTemplate.\nWebClient: reactivo (o síncrono bloqueando), necesario en WebFlux.\nRestTemplate: en mantenimiento, no para código nuevo.\nOpenFeign: interfaz declarativa + anotaciones — genera la implementación (común en microservicios con Spring Cloud).",
   "nivel": "basico",
   "clave": false
  },
  {
   "sub": "Web MVC y REST",
   "q": "RestClient: ejemplo canónico",
   "a": "RestClient rc = RestClient.create();\nPedido p = rc.get().uri(\"http://svc/api/pedidos/{id}\", id).retrieve().body(Pedido.class);\nPOST: rc.post().uri(...).contentType(APPLICATION_JSON).body(dto).retrieve().toBodilessEntity();\nErrores: .onStatus(HttpStatusCode::isError, (req,res) -> { throw new MiEx(); })\nBuilder con baseUrl para reutilizar.",
   "nivel": "basico",
   "clave": false
  },
  {
   "sub": "Web MVC y REST",
   "q": "ETags y caché HTTP con Spring",
   "a": "ResponseEntity con eTag + condicionales: ResponseEntity.ok().eTag(version).body(dto); manejo de If-None-Match → 304.\nShallowETagHeaderFilter (hash del body) o eTag manual por versión de entidad (@Version).\nCache-Control header + CacheControl.maxAge(...).\nReduce ancho de banda; no sustituye caché de aplicación.",
   "nivel": "avanzado",
   "clave": false
  },
  {
   "sub": "Web MVC y REST",
   "q": "Streaming de respuestas: grandes exportaciones",
   "a": "SseEmitter (eventos), StreamingResponseBody (escribe en el OutputStream en otro hilo — no retiene el servlet thread con todo el body), ResponseEntity<Resource> con InputStreamResource para archivos.\nPara exportación de reportes: escribir CSV/Excel por chunks directamente al stream.\nEvita OOM al exportar millones de filas.",
   "nivel": "avanzado",
   "clave": false
  },
  {
   "sub": "Web MVC y REST",
   "q": "@ModelAttribute y formularios clásicos vs @RequestBody",
   "a": "@RequestBody: body JSON/XML → DTO (via HttpMessageConverter). APIs.\n@ModelAttribute: binding de form-data/query a objeto con setters + validación — apps MVC con vistas, formularios HTML.\nErrores de binding de form: BindingResult inmediatamente tras el parámetro para manejarlos sin excepción.",
   "nivel": "intermedio",
   "clave": false
  },
  {
   "sub": "Web MVC y REST",
   "q": "Versión de API: las 3 estrategias aplicadas en Spring",
   "a": "URI: @GetMapping(\"/api/v1/pedidos\") + controllers por versión (clara, cacheable, la más común).\nHeader: @GetMapping(headers=\"X-API-Version=2\") o Accept versionado — URL limpia pero menos visible.\nQuery param: ?version=2 (flexible, no estándar).\nRegla: no romper contratos; versiones nuevas = código nuevo coexistiendo, deprecar con aviso.",
   "nivel": "intermedio",
   "clave": false
  },
  {
   "sub": "Web MVC y REST",
   "q": "DTOs: mapeo manual vs MapStruct",
   "a": "Manual: constructor/copiar a mano — explícito, cero magia, verboso.\nMapStruct: genera el mapper EN COMPILACIÓN (sin reflejos, rápido): @Mapper interface con métodos; mapea por nombres, customiza con @Mapping(source, target, ignore).\nModelMapper: runtime + reflejo — cómodo pero silencioso ante refactor.\nElije: MapStruct para proyectos serios.",
   "nivel": "basico",
   "clave": false
  },
  {
   "sub": "Web MVC y REST",
   "q": "Arquitectura en capas vs hexagonal dentro de un servicio Spring",
   "a": "Capas clásica: controller → service → repository, DTOs en la orilla. Simple y conocida.\nHexagonal: dominio en el CENTRO sin dependencias de Spring/JPA; puertos (interfaces) + adapters (web, persistencia) inyectados.\nBeneficio hexagonal: dominio testeable sin levantar Spring, swap de infraestructura.\nCosto: más código; justificado en dominios complejos.",
   "nivel": "basico",
   "clave": false
  },
  {
   "sub": "Web MVC y REST",
   "q": "¿Dónde va la lógica de negocio: controller, service o repository?",
   "a": "Controller: HTTP puro — validar formato, mapear, responder.\nService: REGLAS DE NEGOCIO, orquestación, transaccionalidad.\nRepository: acceso a datos únicamente (queries), sin lógica.\nAnemia (services con if/else y entidades tontas) es común pero mejorable: lógica en entidades de dominio cuando pertenece a un solo agregado.",
   "nivel": "basico",
   "clave": false
  },
  {
   "sub": "Web MVC y REST",
   "q": "Async requests MVC: DeferredResult y Callable",
   "a": "Devolver Callable<T> o DeferredResult<T> libera el servlet thread y responde cuando la tarea termina — más throughput con I/O lenta sin migrar a WebFlux.\nTimeouts configurables; CompletableFuture es la base moderna.\nStreamingResponseBody para escribir progresivamente.\nWebFlux es otra escala (reactivo completo), no siempre necesario.",
   "nivel": "avanzado",
   "clave": false
  },
  {
   "sub": "Web MVC y REST",
   "q": "Documentar la API: OpenAPI/Swagger en Spring Boot 3",
   "a": "springdoc-openapi-starter-webmvc-ui: expone /v3/api-docs (JSON OpenAPI) y /swagger-ui.html.\nEnriquecer: @Operation(summary, description), @ApiResponse, @Schema en DTOs, @Tag por controller.\nExportar spec para contratos/gateways. Documentar es parte del contrato — pruebas de contrato la validan.",
   "nivel": "intermedio",
   "clave": false
  },
  {
   "sub": "Web MVC y REST",
   "q": "HATEOAS: qué es y cuándo (no) usarlo",
   "a": "Hypermedia: la respuesta incluye links a acciones posibles (_links.self, .cancel) — el cliente navega sin conocer URIs fijas.\nSpring HATEOAS: EntityModel/WebMvcLinkBuilder.\nRealismo: rara vez se implementa completo (nivel 3 Richardson); valor real en APIs públicas longevas. Internamente, links simples bastan.",
   "nivel": "avanzado",
   "clave": false
  },
  {
   "sub": "Web MVC y REST",
   "q": "¿Cómo manejar ids en URIs y respuestas de creación?",
   "a": "POST → 201 + header Location: /api/pedidos/42 (URI absoluta o relativa): ServletUriComponentsBuilder.fromCurrentRequest().path(\"/{id}\").buildAndExpand(dto.getId()).toUri().\nBody: el recurso creado (o solo id).\nIds: no exponer secuenciales de BD si importan (UUID o ids opacos).",
   "nivel": "basico",
   "clave": false
  },
  {
   "sub": "Web MVC y REST",
   "q": "Multipart + DTO combinados en un endpoint",
   "a": "@RequestPart(\"datos\") MiDto dto + @RequestPart(\"archivo\") MultipartFile file — cada parte con su propio converter/validación (@Valid sobre la parte).\nConsumes MULTIPART_FORM_DATA. El cliente envía form-data con partes tipadas.\nCaso típico: metadata JSON + archivo.",
   "nivel": "basico",
   "clave": false
  },
  {
   "sub": "Web MVC y REST",
   "q": "GlobalExceptionHandler: esqueleto que debes saber escribir",
   "a": "@RestControllerAdvice class:\n@ExceptionHandler(RecursoNoEncontrado.class) → 404 ProblemDetail.\n@ExceptionHandler(MethodArgumentNotValidException.class) → 400 + lista de campos inválidos (ex.getBindingResult().getFieldErrors()).\n@ExceptionHandler(AccesoDenegado.class) → 403.\n@ExceptionHandler(Exception.class) → 500 + log.error con stack (sin exponer detalles internos al cliente).",
   "nivel": "intermedio",
   "clave": true
  }
 ]
}