{
 "subtemas": [],
 "tarjetas": [
  {
   "sub": "Producción y observabilidad",
   "q": "Micrometer: cómo Spring Boot mide todo",
   "a": "Micrometer es la fachada de métricas (SLF4J para métricas): Timer, Counter, Gauge, DistributionSummary.\nregistry.timer(\"pedidos.proceso\", \"tipo\", \"web\").record(...); o @Timed/@Counted.\n/exporter: /actuator/prometheus → Prometheus → Grafana.\nMétricas auto: JVM, HTTP (duración, status), Hikari, cachés.",
   "nivel": "intermedio",
   "clave": false
  },
  {
   "sub": "Producción y observabilidad",
   "q": "Health indicator custom y health groups",
   "a": "implements HealthIndicator: Health.up().withDetail(\"latencia\", ms).build() / down(excepción).\nBean nombrado xxxHealthIndicator → /actuator/health/{xxx}.\nHealth groups: management.endpoint.health.group.readiness.include=db,ping → /actuator/health/readiness para K8s.\nCuidado: dependencia lenta DOWN = app fuera del balanceo (usar cache/status opcional).",
   "nivel": "intermedio",
   "clave": false
  },
  {
   "sub": "Producción y observabilidad",
   "q": "Logging con traceId correlacionado (MDC + Micrometer Tracing)",
   "a": "Micrometer Tracing (ex Sleuth) propaga traceId/spanId en MDC → patrón de log: %X{traceId}.\nlogging.pattern.level=\"${LOG_LEVEL} [${traceId:-}]\".\nUn request a través de 5 servicios = un traceId en los logs de todos → grep único para investigar.\nW3C traceparent o B3 headers entre servicios.",
   "nivel": "intermedio",
   "clave": false
  },
  {
   "sub": "Producción y observabilidad",
   "q": "Config de logging que debe saber un backend",
   "a": "logging.level.root=INFO, logging.level.com.miapp=DEBUG (por paquete).\nPattern con timestamp, hilo, level, logger, traceId.\nSalida estructurada JSON en prod (logstash-logback-encoder) para ingest.\nRolling: size + días retenidos. NUNCA loguear passwords/tokens/PII completa.",
   "nivel": "intermedio",
   "clave": false
  },
  {
   "sub": "Producción y observabilidad",
   "q": "Graceful shutdown y readiness durante deploy",
   "a": "server.shutdown=graceful + spring.lifecycle.timeout-per-shutdown-phase=30s: al recibir SIGTERM, la app deja de aceptar requests NUEVOS, termina los en curso y recién cierra.\nCombinar con readiness probe: quitar del balanceo ANTES de matar (preStop hook en K8s).\nEvita 502s y requests cortados en cada deploy.",
   "nivel": "intermedio",
   "clave": true
  },
  {
   "sub": "Producción y observabilidad",
   "q": "Dockerizar Spring Boot: layered jars",
   "a": "spring-boot-jarmode-layers: java -Djarmode=tools -jar app.jar extract → Dockerfile con COPY de capas separadas (dependencies, snapshot, app) → el cache de Docker solo re-descarga la capa 'app' cuando cambia TU código (imagen rebuild 10x más rápida).\nBuildpacks (bootBuildImage) es la alternativa cero-Dockerfile.",
   "nivel": "avanzado",
   "clave": false
  },
  {
   "sub": "Producción y observabilidad",
   "q": "Virtual threads en Spring Boot 3.2+: un flag",
   "a": "spring.threads.virtual.enabled=true → Tomcat, @Async y schedulers usan hilos virtuales.\nI/O-bound masivo con código bloqueante simple: miles de requests concurrentes sin WebFlux ni pools gigantes.\nCuidado: synchronized prolongado (pinning), CPU-bound no mejora, y pools de conexiones quedan como cuello (ajustar Hikari).",
   "nivel": "avanzado",
   "clave": false
  },
  {
   "sub": "Producción y observabilidad",
   "q": "GraalVM native image en Spring Boot: qué cambia",
   "a": "AOT: compila a binario nativo — arranque <100ms y footprint mínimo (ideal serverless/scale-to-zero).\nCostos: build lento, reflexión/JPA dinámicos requieren hints, alguna librería incompatible.\nSpring Boot 3 trae soporte AOT + plugins. Evaluar solo si el arranque/frío importa de verdad.",
   "nivel": "avanzado",
   "clave": false
  },
  {
   "sub": "Producción y observabilidad",
   "q": "Perfil prod: checklist de propiedades",
   "a": "ddl-auto=validate, show-sql=false, logging INFO + JSON, actuator expone solo health/info/prometheus (nada de /env), CORS restricto, TLS en el LB, secrets por env/Secret Manager, timeouts de clientes configurados, Hikari sized, graceful shutdown, management probes habilitados (management.endpoint.health.probes.enabled=true).",
   "nivel": "avanzado",
   "clave": false
  },
  {
   "sub": "Spring Cloud y resiliencia",
   "q": "Spring Cloud Gateway: qué hace y config básica",
   "a": "Gateway reactivo (Netty) como entrada única: rutas por path/host con filtros.\nspring.cloud.gateway.routes: id, uri, predicates (Path=/api/**), filters (StripPrefix=1, AddRequestHeader, Retry, CircuitBreaker).\nFilters custom: GatewayFilterFactory — auth, rate limit (RequestRateLimiter con Redis).\nNo pongas lógica de negocio en el gateway.",
   "nivel": "intermedio",
   "clave": true
  },
  {
   "sub": "Spring Cloud y resiliencia",
   "q": "Resilience4j: los 4 módulos y anotaciones",
   "a": "@CircuitBreaker (corta tras N fallos), @Retry (n reintentos + backoff), @RateLimiter (limite por periodo), @Bulkhead (semáforo o pool de hilos), @TimeLimiter (timeout para futures).\nCombinables en un método (orden configurable). fallbackMethod = \"miMetodo(Throwable)\" con firma compatible.\nMétricas expuestas vía Micrometer: estado del breaker en /metrics.",
   "nivel": "intermedio",
   "clave": false
  },
  {
   "sub": "Spring Cloud y resiliencia",
   "q": "CircuitBreaker config: qué significa cada número",
   "a": "slidingWindowSize=10 (ventana de llamadas evaluada), failureRateThreshold=50 (% de fallos para abrir), waitDurationInOpenState=30s (tiempo abierto), permittedNumberOfCallsInHalfOpenState=3, slowCallRateThreshold + slowCallDurationThreshold (latencia alta cuenta como fallo).\nEn yml: resilience4j.circuitbreaker.instances.miServicio.*",
   "nivel": "avanzado",
   "clave": false
  },
  {
   "sub": "Spring Cloud y resiliencia",
   "q": "Retry bien hecho: backoff, jitter y cuándo NO reintentar",
   "a": "maxAttempts=3, waitDuration=200ms, enableExponentialBackoff (x2), enableRandomizedJitter (evita sincronización de reintentos).\nReintentar SOLO errores transitorios (503, timeouts, conexiones) — NO 400/401/404 (deterministas).\nIdempotencia obligatoria en la operación reintentada.\nRetry + circuit breaker juntos: el breaker evita martillar cuando todo está caído.",
   "nivel": "intermedio",
   "clave": true
  },
  {
   "sub": "Spring Cloud y resiliencia",
   "q": "OpenFeign: cliente declarativo y sus settings críticos",
   "a": "@FeignClient(name=\"pagos\", url=\"${servicios.pagos.url}\", configuration=FeignCfg.class) interface PagosClient { @PostMapping(\"/cargar\") Carga cargar(@RequestBody CargaReq req); }\n@EnableFeignClients. Timeouts SIEMPRE (connect/read por client), retry acotado, error decoder (ErrorDecoder → excepciones de negocio), y circuit breaker integrado (circuitbreaker.enabled=true).\nInterceptors para tokens: RequestInterceptor con Authorization header.",
   "nivel": "intermedio",
   "clave": false
  },
  {
   "sub": "Spring Cloud y resiliencia",
   "q": "Spring Cloud Config Server: qué aporta hoy",
   "a": "Centraliza properties por app/perfil en un repo Git: los servicios las cargan al arranque desde http://config-server/app/prod.\n@RefreshScope + actuator/refresh recarga valores SIN redeploy (con bus de eventos: refresca todas las réplicas).\nAlternativa moderna en k8s: ConfigMaps + reloder (spring-cloud-kubernetes). Aun así, el patrón externalized config es el que importa.",
   "nivel": "avanzado",
   "clave": false
  },
  {
   "sub": "Troubleshooting clásico",
   "q": "NoUniqueBeanDefinitionException: diagnóstico en 3 pasos",
   "a": "1) Leer el mensaje: lista los beans candidatos.\n2) Decidir: @Primary (default), @Qualifier (punto de inyección), o renombrar el campo para que coincida con el nombre del bean.\n3) Si es involuntario: revisar componentes duplicados por escaneo (paquete repetido, clases de prueba escaneadas).\nEn tests: @MockitoBean reemplaza y elimina la ambigüedad.",
   "nivel": "intermedio",
   "clave": false
  },
  {
   "sub": "Troubleshooting clásico",
   "q": "BeanCurrentlyInCreationException (dependencia circular): salidas",
   "a": "Mensaje típico: 'Requested bean is currently in creation'.\nOpciones en orden de preferencia: 1) Rediseñar — extraer lo común a un tercer bean. 2) @Lazy en UNA de las inyecciones. 3) Setter injection en lugar de constructor (solo como parche).\nLa circularidad casi siempre indica responsabilidades mal separadas.",
   "nivel": "intermedio",
   "clave": false
  },
  {
   "sub": "Troubleshooting clásico",
   "q": "Port 8080 was already in use: soluciones rápidas",
   "a": "Ver quién ocupa: Windows netstat -ano | findstr :8080 + taskkill /PID x /F (o cambiar server.port=0/otro).\nEn dev: apagar el otro servicio (o el Docker que publicó el puerto), o correr con --server.port=9090.\nEn tests: RANDOM_PORT siempre.",
   "nivel": "intermedio",
   "clave": false
  },
  {
   "sub": "Troubleshooting clásico",
   "q": "Cannot resolve property / Property or field 'x' cannot be found: causas",
   "a": "1) En SpEL/JPQL: typo o nombre de propiedad inexistente en la entidad (JPQL usa campos de entidad, NO columnas).\n2) En @ConfigurationProperties: relaxed binding mal aplicado (lista sin corchetes).\n3) Typo en application.yml bajo el prefijo equivocado.\nValidar: arranque con --debug y CONDITIONS report; correr el query a mano.",
   "nivel": "intermedio",
   "clave": false
  },
  {
   "sub": "Troubleshooting clásico",
   "q": "HikariPool - Connection is not available, request timed out: por qué ocurre",
   "a": "Pool agotado: conexiones en uso > maximumPoolSize y nadie libera.\nCausas: transacciones largas (I/O externa dentro de tx), locks de BD, conexiones sin cerrar (streams no cerrados), pool chico para la carga.\nFixes: tx cortas, raise pool (con margen de BD), leakDetectionThreshold=30000 para culprits, métricas de Hikari (active/idle/wait).",
   "nivel": "intermedio",
   "clave": true
  },
  {
   "sub": "Troubleshooting clásico",
   "q": "JSON parse error / 415 Unsupported Media Type: checklist",
   "a": "415: el cliente NO envía Content-Type: application/json, o el endpoint exige consumes distinto.\nJSON error 400: payload mal formado, campo con tipo incorrecto (string vs number), DTO sin constructor vacío/setters (Jackson necesita), o getter/setter faltantes.\nDebug: loguear el body recibido (solo en dev) y probar la serialización del DTO aislada.",
   "nivel": "intermedio",
   "clave": false
  },
  {
   "sub": "Troubleshooting clásico",
   "q": "Infinite recursion con Jackson (StackOverflow): 4 salidas",
   "a": "Relación A→B→A serializando.\n1) DTOs (la solución real).\n2) @JsonIgnore en el lado inverso.\n3) @JsonManagedReference (padre) + @JsonBackReference (hijo).\n4) @JsonIdentityInfo(generator=ObjectIdGenerators.PropertyGenerator.class, property=\"id\").\n5) @JsonProperty(access=WRITE_ONLY) para entrada sin salida.",
   "nivel": "intermedio",
   "clave": false
  },
  {
   "sub": "Troubleshooting clásico",
   "q": "Tabla not found / SQLGrammarException al arrancar: causas típicas",
   "a": "1) ddl-auto sin create + BD vacía y sin migraciones.\n2) Flyway con versión de script menor a la registrada (schema history).\n3) Naming: tabla snake_case en BD vs entidad sin naming strategy (spring.jpa.hibernate.naming.physical-strategy).\n4) Esquema/schema search path distinto (Postgres).\nValidar contra la BD real, no contra la entidad.",
   "nivel": "intermedio",
   "clave": false
  },
  {
   "sub": "Troubleshooting clásico",
   "q": "El test pasa en local pero falla en CI: los 5 clásicos",
   "a": "1) Depende de recursos locales (puerto, archivo, red) — mockear/contenerizar.\n2) TZ/locáles distintas → fechas rotas: fijar TZ/UTF-8 en el runner.\n3) Orden de tests / estado compartido.\n4) Docker indisponible para Testcontainers.\n5) Flaky por timing (async sin await — usar Awaitility).\nCorrer tests en paralelo/aleatorio localmente los vuelve robustos.",
   "nivel": "intermedio",
   "clave": false
  },
  {
   "sub": "Troubleshooting clásico",
   "q": "502/504 detrás de un load balancer con Spring Boot: dónde mirar",
   "a": "502: la app rechaza/cierra — durante deploy sin graceful shutdown, proceso caído, puerto equivocado en el target group.\n504: la app tarda más que el timeout del LB/ingress (60s default) — perfil de lentitud, timeouts del LB ajustados o request asincrónico.\nConfirmar: logs de la app a esa hora + health del backend + readiness antes del switch de tráfico.",
   "nivel": "intermedio",
   "clave": false
  },
  {
   "sub": "Troubleshooting clásico",
   "q": "Cómo diagnosticar un endpoint lento (metodología)",
   "a": "1) Medir por capas: gateway log → micrometer http.server.requests.percentile → logs con timing.\n2) Activar show-sql/log de Hikari SOLO en repro local; buscar N+1 y queries sin índice (EXPLAIN).\n3) Revisar llamadas externas (timeouts, latencia del cliente HTTP).\n4) GC/hilos en JFR si todo interno luce bien.\n5) Fix + test de carga que demuestre la mejora.",
   "nivel": "intermedio",
   "clave": false
  }
 ]
}