LEARNING ROADMAP Req Rec Opt 0 / 41 completados
ESC cerrar

BEST PRACTICES — Codemon TCG

Convenciones de Código
  • Backend: Java 21 + Spring Boot 3, Lombok solo cuando simplifica, inyección por constructor (nunca @Autowired en campo), @Transactional explícito en servicios que modifican datos.
  • Frontend: Angular Standalone Components, TypeScript strict: true, limpiar suscripciones con takeUntil(destroy$) en ngOnDestroy, Tailwind-first (prohibido CSS custom salvo @apply).
  • Naming: camelCase en Java y TS, kebab-case en archivos Angular, UPPER_SNAKE para constantes. Prefijo I prohibido en interfaces TS.
  • Logs: SLF4J con nivel apropiado; nunca System.out.println; incluir contexto (gameId, userId) en cada log del motor.
Convenciones de Commits
  • Formato: tipo(scope): descripción en inglés, imperativo, <72 chars.
  • Tipos válidos: feat, fix, test, refactor, docs, chore, perf.
  • Scopes del proyecto: auth, cards, game, lobby, payment, infra, engine.
  • Ejemplos: feat(auth): add JWT refresh endpoint · fix(engine): prevent double attack in same phase · test(engine): add XY1 card handler coverage · perf(lobby): cache leaderboard in Redis.
  • Breaking changes: agregar ! después del scope y pie de mensaje BREAKING CHANGE: descripción.
Patrones de Diseño del Proyecto
  • FacadeGameEngine expone una API única; los subsistemas internos no son accesibles directamente desde controllers.
  • StrategyCardHandler: cada carta tiene su handler registrado en un mapa; agregar una carta nueva = agregar un handler, sin tocar el motor.
  • Chain of ResponsibilityAttackPipeline: 9 pasos obligatorios en orden fijo (Validar → Daño Base → Debilidad → Resistencia → Status → Aplicar → KO → Premio → Evento). Ningún paso puede saltarse.
  • StateGamePhase enum controla qué acciones son legales en cada fase del turno. Una acción en la fase incorrecta lanza IllegalGameActionException.
  • Repository — Spring Data JPA; nunca SQL nativo salvo en queries analíticas justificadas con @Query.
  • Observer / Pub-SubGameEventPublisher desacopla el motor de los WebSockets; el engine no sabe que existe STOMP.
Flujo de Pull Request
  • Crear rama feature/PASO_SXX_YY-descripcion desde develop actualizado.
  • Ejecutar ./verify_paso.sh PASO_SXX_YY — si falla, no abrir el PR.
  • Correr mvn verify: todos los tests deben pasar y cobertura ≥ umbral definido en pom.xml.
  • Referenciar en el título del PR: [PASO_SXX_YY] HU-XXX: descripción.
  • Pedir al menos 1 review del equipo correspondiente (A=Backend, B=Frontend, C=DevOps).
  • Mergear con Squash & Merge; nunca merge commit ni rebase a develop directamente.
  • Eliminar la rama feature después del merge.
Sistema de PASOS — Reglas de Oro
  • Siempre cargar como contexto: CONVENCIONES.md, GLOSARIO.md, ESTADO_PASOS.md y HISTORIAL_PASOS.md antes de leer el PASO.
  • Leer el PASO completo antes de escribir una sola línea de código.
  • Respetar los contratos de API definidos en CONTRATOS_API.md; un cambio de firma requiere actualizar el contrato primero.
  • Ejecutar ./verify_paso.sh PASO_X_Y al terminar; la salida define si el PASO está DONE.
  • Revisar el Definition of Done en DOD.md; no marcar como completo si algún criterio falla.
  • Actualizar ESTADO_PASOS.md e HISTORIAL_PASOS.md antes de cerrar la sesión de trabajo.
Estrategia de Testing
  • Pirámide: 70% unitarios (JUnit + Mockito), 20% integración (Testcontainers), 10% E2E (Playwright).
  • Cobertura mínima: 80% global · 90% en com.codemon.game.engine · 0% tolerancia de regresiones en el pipeline de ataque.
  • Tests unitarios del motor: patrón Given/When/Then estricto; un test por acción legal y uno por acción ilegal en cada fase.
  • Testcontainers: obligatorio para repositorios que usan JSONB; nunca mockear la BD en tests de integración.
  • Playwright E2E: dos contextos de Chromium simultáneos para simular PvP real; ejecutar en CI con --reporter=html.
  • No testear: getters/setters triviales, Lombok generado, configuración de beans de Spring.
Manejo de Errores & Logging
  • @ControllerAdvice centraliza todos los errores REST; nunca capturar excepciones en controllers y retornar ResponseEntity manualmente.
  • Formato de error estándar: ProblemDetail (RFC 9457) con campos type, title, status, detail, instance.
  • Excepciones de dominio propias: IllegalGameActionException, CardNotFoundException, DeckValidationException; nunca lanzar RuntimeException genérico.
  • Logging: ERROR solo para fallos irrecuperables; WARN para situaciones anómalas esperadas; INFO para flujos de negocio; DEBUG para trazas del motor de juego.
  • Incluir siempre gameId y userId en los logs del motor para poder correlacionar una partida completa en Grafana.
Base de Datos & Migraciones
  • Regla cardinal de Flyway: nunca modificar un script V{n}__*.sql ya ejecutado; crear siempre un nuevo script con el siguiente número.
  • Todo cambio de schema pasa por migración Flyway; prohibido alterar tablas a mano en cualquier entorno.
  • Usar @Transactional(isolation = SERIALIZABLE) en operaciones críticas del motor (procesar acción, acreditar moneda).
  • Evitar N+1: usar @EntityGraph o JOIN FETCH explícito en queries que navegan relaciones @ManyToMany.
  • El estado de partida se persiste como JSONB en game_state; usar operadores @> y ->>, nunca deserializar todo el objeto para filtrar.
  • REFRESH MATERIALIZED VIEW CONCURRENTLY mv_leaderboard solo al terminar una partida, nunca dentro de una transacción abierta.
WebSockets & Tiempo Real
  • Toda suscripción STOMP en Angular debe vivir dentro de un takeUntil(this.destroy$); un leak de suscripción puede dejar al jugador recibiendo eventos de una partida ya terminada.
  • El backend NO publica directamente a WebSocket desde GameEngine; siempre a través de GameEventPublisher para mantener el motor testeable.
  • Heartbeat cada 30 segundos (user:presence:{userId} con TTL 30s en Redis); si el TTL expira, el jugador se considera desconectado y la partida se resuelve por abandono.
  • Mensajes de partida van a /topic/game.{gameId} (ambos jugadores); mensajes privados a /user/queue/events. Nunca mezclar canales.
  • El proxy Nginx debe tener proxy_set_header Upgrade $http_upgrade y Connection "upgrade"; sin esto el handshake WS falla silenciosamente.
Seguridad
  • JWT: secret de mínimo 32 caracteres en .env (nunca en application.yml); expiración de access token 15 min, refresh token 7 días con rotación.
  • Secrets: ninguna credencial, token ni password puede estar commiteado en el repositorio; .env está en .gitignore; usar .env.example con valores placeholder.
  • CORS: configurar orígenes permitidos explícitamente en SecurityFilterChain; nunca allowedOrigins("*") en producción.
  • Idempotencia de pagos: el webhook de Mercado Pago puede llegar varias veces; la acreditación de Codemones se guarda con clave de idempotencia y se ignora si ya fue procesada.
  • Inputs: validar con @Valid + Bean Validation en todos los DTOs de entrada; rechazar con 400 antes de llegar al servicio.
  • OAuth2: al vincular cuentas sociales, buscar siempre por email antes de crear un usuario nuevo para evitar cuentas duplicadas.
Performance & Caché
  • El leaderboard top-100 se lee de Redis (ZREVRANGE leaderboard 0 99), nunca de PostgreSQL en tiempo de request.
  • Cooldown de 24h para abrir sobres: clave cooldown:pack:{userId} con EXPIRE 86400 en Redis; no usar la BD para esto.
  • Las imágenes de cartas se sirven desde MinIO con política de bucket ReadOnly; nunca almacenar imágenes en la BD ni en el filesystem del servidor.
  • Lazy loading en Angular: cada ruta carga su módulo con loadComponent; el bundle inicial debe quedar bajo 200 KB gzipped.
  • Virtual Threads habilitados (spring.threads.virtual.enabled=true); permite manejar 50+ partidas concurrentes sin saturar el thread pool.
  • Usar @Async para el envío de emails OTP; el endpoint de registro no debe bloquearse esperando respuesta del servidor SMTP.
Variables de Entorno & Secretos — Guía Completa

¿Qué es una variable de entorno? Es un par CLAVE=valor que el sistema operativo inyecta al proceso cuando arranca. Tu código las lee en tiempo de ejecución — nunca están escritas en el código fuente.

¿Qué es el archivo .env? Una convención de desarrollo local. Herramientas como Docker Compose y Spring Boot lo leen al arrancar para simular las variables del entorno. Este archivo nunca debe existir en producción ni commitearse al repositorio.

  • Regla #1 — .env va en .gitignore siempre. El historial de git es permanente. Un secret commiteado hoy sigue siendo accesible en el historial aunque lo borres mañana. Si accidentalmente se commitea, el secret debe rotarse de inmediato.
  • Regla #2 — Usa .env.example como documentación. Este archivo SÍ se commitea, con los nombres de las variables pero sin valores reales. Cada developer nuevo copia .env.example → .env y completa sus valores locales.
  • Regla #3 — En producción no existe el archivo .env. Las variables se inyectan desde: CI/CD secrets (GitHub Actions, GitLab CI), servicios administrados de secretos (AWS SSM Parameter Store, AWS Secrets Manager, HashiCorp Vault, Azure Key Vault), o variables de entorno del sistema operativo del servidor.
  • Regla #4 — Perfiles por entorno. spring.profiles.active=dev en local, =prod en producción. El perfil prod puede tener configuración más restrictiva (sin Swagger, sin datos de prueba, logs en formato JSON para Grafana).
  • Regla #5 — Rotación de secretos. Cada vez que un secret es expuesto (leak en logs, repo público, developer que deja el equipo), debe generarse uno nuevo y actualizarse en todos los entornos. JWT_SECRET rotado invalida todas las sesiones activas — planificar el rollout.
  • Regla #6 — Nunca loguear variables de entorno. log.info("Conectando con clave: " + jwtSecret) es un error clásico. Los logs van a Grafana, que puede ser accedido por más personas que el código fuente.

Escalera de madurez en producción: .env local → Variables de CI/CD → Docker/K8s Secrets → AWS SSM → HashiCorp Vault (con rotación automática y auditoría)

Reglas del Motor de Juego
  • Nunca modificar GameState directamente desde fuera de GameEngine; toda acción pasa por processAction().
  • El AttackPipeline tiene 9 pasos en orden fijo; saltarse uno o reordenarlos produce resultados de daño incorrectos en cartas con resistencias/debilidades.
  • Cada CardHandler debe registrarse en el mapa del motor en su constructor; un handler no registrado hace que la carta se comporte como si no tuviera efecto.
  • Las condiciones de estado (veneno, quemadura, confusión, parálisis) se aplican en END_PHASE, no en ATTACK_PHASE; aplicarlas en el momento incorrecto crea exploits competitivos.
  • Las 6 cartas de premio se asignan al inicio (SETUP) y se revelan solo cuando el oponente hace KO; nunca antes.
  • El mulligan es obligatorio si la mano inicial no tiene ningún Pokémon Básico; el motor debe forzarlo antes de pasar a DRAW_PHASE.