@saulwade/swl-ses 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CLAUDE.md +238 -0
- package/README.md +560 -0
- package/_userland/agentes/.gitkeep +0 -0
- package/_userland/habilidades/.gitkeep +0 -0
- package/agentes/.evolved.json +9 -0
- package/agentes/accesibilidad-wcag-swl.md +692 -0
- package/agentes/arquitecto-swl.md +238 -0
- package/agentes/auto-evolucion-swl.md +854 -0
- package/agentes/backend-api-swl.md +470 -0
- package/agentes/backend-csharp-swl.md +418 -0
- package/agentes/backend-go-swl.md +388 -0
- package/agentes/backend-java-swl.md +279 -0
- package/agentes/backend-node-swl.md +477 -0
- package/agentes/backend-python-swl.md +608 -0
- package/agentes/backend-rust-swl.md +362 -0
- package/agentes/backend-workers-swl.md +480 -0
- package/agentes/cloud-infra-swl.md +485 -0
- package/agentes/consolidador-swl.md +539 -0
- package/agentes/datos-swl.md +584 -0
- package/agentes/depurador-swl.md +349 -0
- package/agentes/devops-ci-swl.md +374 -0
- package/agentes/disenador-ui-swl.md +558 -0
- package/agentes/documentador-swl.md +343 -0
- package/agentes/evals/arquitecto-swl.evals.json +56 -0
- package/agentes/evals/auto-evolucion-swl.evals.json +68 -0
- package/agentes/evals/implementador-swl.evals.json +56 -0
- package/agentes/evals/orquestador-swl.evals.json +60 -0
- package/agentes/evals/perfilador-usuario-swl.evals.json +60 -0
- package/agentes/evals/red-team-swl.evals.json +59 -0
- package/agentes/evals/revisor-codigo-swl.evals.json +59 -0
- package/agentes/frontend-angular-swl.md +627 -0
- package/agentes/frontend-css-swl.md +720 -0
- package/agentes/frontend-react-swl.md +696 -0
- package/agentes/frontend-swl.md +500 -0
- package/agentes/frontend-tailwind-swl.md +830 -0
- package/agentes/implementador-swl.md +328 -0
- package/agentes/investigador-swl.md +430 -0
- package/agentes/investigador-ux-swl.md +500 -0
- package/agentes/llm-apps-swl.md +276 -0
- package/agentes/migrador-swl.md +417 -0
- package/agentes/mobile-android-swl.md +509 -0
- package/agentes/mobile-cross-swl.md +539 -0
- package/agentes/mobile-ios-swl.md +500 -0
- package/agentes/mobile-testing-swl.md +300 -0
- package/agentes/notificador-swl.md +916 -0
- package/agentes/observabilidad-swl.md +436 -0
- package/agentes/orquestador-swl.md +884 -0
- package/agentes/pagos-swl.md +283 -0
- package/agentes/perfilador-usuario-swl.md +306 -0
- package/agentes/planificador-swl.md +402 -0
- package/agentes/producto-prd-swl.md +587 -0
- package/agentes/red-team-swl.md +216 -0
- package/agentes/release-manager-swl.md +568 -0
- package/agentes/rendimiento-swl.md +714 -0
- package/agentes/resolutor-build-swl.md +243 -0
- package/agentes/revisor-angular-swl.md +276 -0
- package/agentes/revisor-codigo-swl.md +348 -0
- package/agentes/revisor-csharp-swl.md +262 -0
- package/agentes/revisor-go-swl.md +257 -0
- package/agentes/revisor-java-swl.md +255 -0
- package/agentes/revisor-kotlin-swl.md +271 -0
- package/agentes/revisor-nextjs-swl.md +279 -0
- package/agentes/revisor-php-swl.md +269 -0
- package/agentes/revisor-react-swl.md +276 -0
- package/agentes/revisor-rust-swl.md +344 -0
- package/agentes/revisor-seguridad-swl.md +390 -0
- package/agentes/revisor-swift-swl.md +266 -0
- package/agentes/revisor-typescript-swl.md +344 -0
- package/agentes/sre-swl.md +265 -0
- package/agentes/tdd-qa-swl.md +354 -0
- package/agentes/ux-disenador-swl.md +501 -0
- package/bin/lib/bot-comandos.js +1030 -0
- package/bin/lib/bot-discovery.js +182 -0
- package/bin/lib/bot-git.js +142 -0
- package/bin/swl-ses.js +325 -0
- package/bin/swl-telegram-bot.js +442 -0
- package/bin/swl-telegram-bot.plist +21 -0
- package/bin/swl-telegram-bot.service +14 -0
- package/comandos/swl/.evolved.json +23 -0
- package/comandos/swl/actualizar.md +174 -0
- package/comandos/swl/adoptar-proyecto.md +207 -0
- package/comandos/swl/aprender.md +701 -0
- package/comandos/swl/auditar-deps.md +134 -0
- package/comandos/swl/autoresearch.md +170 -0
- package/comandos/swl/ayuda.md +224 -0
- package/comandos/swl/brainstorm.md +50 -0
- package/comandos/swl/checkpoint.md +330 -0
- package/comandos/swl/compactar.md +283 -0
- package/comandos/swl/configurar-ci.md +227 -0
- package/comandos/swl/contexto.md +112 -0
- package/comandos/swl/contribuir.md +233 -0
- package/comandos/swl/crear-skill.md +292 -0
- package/comandos/swl/cron.md +196 -0
- package/comandos/swl/dashboard.md +146 -0
- package/comandos/swl/discutir-fase.md +230 -0
- package/comandos/swl/ejecutar-fase.md +135 -0
- package/comandos/swl/evaluar-skill.md +487 -0
- package/comandos/swl/evolucion-estado.md +142 -0
- package/comandos/swl/evolucionar.md +259 -0
- package/comandos/swl/exportar-vault.md +189 -0
- package/comandos/swl/gateway.md +158 -0
- package/comandos/swl/inbox.md +116 -0
- package/comandos/swl/instalar.md +220 -0
- package/comandos/swl/instintos.md +86 -0
- package/comandos/swl/mapear-codebase.md +312 -0
- package/comandos/swl/mcp-status.md +175 -0
- package/comandos/swl/metricas.md +270 -0
- package/comandos/swl/modelo.md +102 -0
- package/comandos/swl/notificaciones.md +396 -0
- package/comandos/swl/nuevo-proyecto.md +154 -0
- package/comandos/swl/planear-fase.md +221 -0
- package/comandos/swl/plugins.md +256 -0
- package/comandos/swl/reflect-skills.md +125 -0
- package/comandos/swl/release.md +217 -0
- package/comandos/swl/revisar-impacto.md +206 -0
- package/comandos/swl/revisar.md +330 -0
- package/comandos/swl/salud.md +363 -0
- package/comandos/swl/sesiones.md +200 -0
- package/comandos/swl/skill-search.md +113 -0
- package/comandos/swl/verificar.md +585 -0
- package/comandos/swl/wiki.md +620 -0
- package/contextos/dev.md +32 -0
- package/contextos/research.md +30 -0
- package/contextos/review.md +31 -0
- package/habilidades/accesibilidad-a11y/SKILL.md +201 -0
- package/habilidades/accesibilidad-a11y/evals/evals.json +56 -0
- package/habilidades/accesibilidad-a11y/recursos/ejemplos-y-checklist-completo.md +441 -0
- package/habilidades/agent-browser/SKILL.md +218 -0
- package/habilidades/agentes-como-servicio/SKILL.md +218 -0
- package/habilidades/ai-runtime-security/SKILL.md +273 -0
- package/habilidades/angular-avanzado/SKILL.md +164 -0
- package/habilidades/angular-avanzado/recursos/ejemplos-avanzados.md +219 -0
- package/habilidades/angular-moderno/SKILL.md +186 -0
- package/habilidades/angular-moderno/evals/evals.json +45 -0
- package/habilidades/angular-moderno/recursos/ejemplos-avanzados.md +106 -0
- package/habilidades/api-rest-diseno/SKILL.md +191 -0
- package/habilidades/api-rest-diseno/recursos/openapi-template.yaml +506 -0
- package/habilidades/api-rest-diseno/recursos/referencia-api.md +140 -0
- package/habilidades/aprendizaje-continuo/SKILL.md +151 -0
- package/habilidades/aprendizaje-continuo/evals/evals.json +53 -0
- package/habilidades/aprendizaje-continuo/recursos/referencia-instintos.md +290 -0
- package/habilidades/async-python/SKILL.md +149 -0
- package/habilidades/async-python/evals/evals.json +47 -0
- package/habilidades/async-python/recursos/patrones-y-ejemplos-completos.md +292 -0
- package/habilidades/auth-patrones/.evolved.json +9 -0
- package/habilidades/auth-patrones/SKILL.md +413 -0
- package/habilidades/auth-patrones/recursos/implementaciones-completas.md +229 -0
- package/habilidades/auto-evolucion-protocolo/SKILL.md +276 -0
- package/habilidades/auto-evolucion-protocolo/evals/evals.json +55 -0
- package/habilidades/auto-evolucion-protocolo/recursos/referencia-completa.md +145 -0
- package/habilidades/autoresearch/SKILL.md +268 -0
- package/habilidades/autoresearch/evals/evals.json +41 -0
- package/habilidades/autoresearch/recursos/checklist-template.md +191 -0
- package/habilidades/autoresearch/scripts/calcular-score.js +88 -0
- package/habilidades/azure-cloud/SKILL.md +308 -0
- package/habilidades/azure-cloud/recursos/aks.md +327 -0
- package/habilidades/backend-mcp-servidor/SKILL.md +270 -0
- package/habilidades/backend-production-resilience/SKILL.md +288 -0
- package/habilidades/brainstorming/SKILL.md +295 -0
- package/habilidades/brainstorming/recursos/componentes-html.md +247 -0
- package/habilidades/build-errors-cpp/SKILL.md +270 -0
- package/habilidades/build-errors-csharp/SKILL.md +265 -0
- package/habilidades/build-errors-go/SKILL.md +306 -0
- package/habilidades/build-errors-java/SKILL.md +278 -0
- package/habilidades/build-errors-kotlin/SKILL.md +303 -0
- package/habilidades/build-errors-nextjs/SKILL.md +312 -0
- package/habilidades/build-errors-php/SKILL.md +270 -0
- package/habilidades/build-errors-python/SKILL.md +292 -0
- package/habilidades/build-errors-rust/SKILL.md +284 -0
- package/habilidades/build-errors-swift/SKILL.md +272 -0
- package/habilidades/build-errors-typescript/SKILL.md +369 -0
- package/habilidades/checklist-calidad/SKILL.md +271 -0
- package/habilidades/checklist-calidad/recursos/quality-report-template.md +148 -0
- package/habilidades/checklist-seguridad/SKILL.md +285 -0
- package/habilidades/checkpoints-verificacion/SKILL.md +298 -0
- package/habilidades/checkpoints-verificacion/recursos/checkpoint-templates.md +360 -0
- package/habilidades/ci-cd-pipelines/SKILL.md +157 -0
- package/habilidades/ci-cd-pipelines/recursos/github-actions-template.yaml +403 -0
- package/habilidades/ci-cd-pipelines/recursos/pipelines-completos.md +487 -0
- package/habilidades/cloud-aws/SKILL.md +142 -0
- package/habilidades/cloud-aws/recursos/servicios-aws-referencia.md +321 -0
- package/habilidades/compactacion-contexto/SKILL.md +247 -0
- package/habilidades/contenedores-docker/SKILL.md +137 -0
- package/habilidades/contenedores-docker/recursos/dockerfile-template.dockerfile +160 -0
- package/habilidades/contenedores-docker/recursos/ejemplos-y-configuraciones.md +327 -0
- package/habilidades/context-builder/SKILL.md +170 -0
- package/habilidades/control-profundidad/SKILL.md +128 -0
- package/habilidades/csharp-experto/SKILL.md +322 -0
- package/habilidades/csharp-patrones/SKILL.md +316 -0
- package/habilidades/csharp-testing/SKILL.md +286 -0
- package/habilidades/css-moderno/SKILL.md +166 -0
- package/habilidades/css-moderno/evals/evals.json +43 -0
- package/habilidades/css-moderno/recursos/ejemplos-y-patrones-completos.md +337 -0
- package/habilidades/datos-etl/SKILL.md +129 -0
- package/habilidades/datos-etl/recursos/implementaciones-completas.md +322 -0
- package/habilidades/dbml-experto/SKILL.md +339 -0
- package/habilidades/dbml-experto/evals/evals.json +56 -0
- package/habilidades/dependencias-auditoria/SKILL.md +320 -0
- package/habilidades/deprecacion-migracion/SKILL.md +169 -0
- package/habilidades/deprecacion-migracion/recursos/implementaciones-completas.md +220 -0
- package/habilidades/design-tokens/SKILL.md +158 -0
- package/habilidades/design-tokens/recursos/tokens-y-configuracion.md +363 -0
- package/habilidades/devsecops-pipeline-security/SKILL.md +309 -0
- package/habilidades/diagrama-arquitectura/SKILL.md +165 -0
- package/habilidades/diagrama-arquitectura/assets/template.html +276 -0
- package/habilidades/discutir-fase/SKILL.md +188 -0
- package/habilidades/diseno-herramientas-agente/SKILL.md +199 -0
- package/habilidades/diseno-responsivo/SKILL.md +186 -0
- package/habilidades/diseno-responsivo/recursos/ejemplos-layouts.md +156 -0
- package/habilidades/django-experto/SKILL.md +205 -0
- package/habilidades/django-experto/recursos/async-django.md +390 -0
- package/habilidades/django-experto/recursos/drf-patrones.md +438 -0
- package/habilidades/django-experto/recursos/orm-avanzado.md +382 -0
- package/habilidades/django-experto/recursos/referencia-completa.md +188 -0
- package/habilidades/django-experto/recursos/testing-django.md +415 -0
- package/habilidades/doc-sync/SKILL.md +280 -0
- package/habilidades/drift-detection/SKILL.md +179 -0
- package/habilidades/ejecutar-fase/SKILL.md +468 -0
- package/habilidades/estilo-sin-ai-isms/SKILL.md +775 -0
- package/habilidades/estilo-sin-ai-isms/evals/evals.json +63 -0
- package/habilidades/estilo-sin-ai-isms/scripts/detectar_aiisms.py +500 -0
- package/habilidades/estructura-proyecto-claude/SKILL.md +215 -0
- package/habilidades/estructura-proyecto-claude/recursos/claude-md-template.md +261 -0
- package/habilidades/estructura-proyecto-claude/recursos/configuracion-y-extensiones.md +176 -0
- package/habilidades/estructura-proyecto-claude/recursos/frontmatter-y-hooks-referencia.md +289 -0
- package/habilidades/estructura-proyecto-claude/recursos/mcp-json-template.json +77 -0
- package/habilidades/estructura-proyecto-claude/recursos/variantes-por-stack.md +177 -0
- package/habilidades/evaluacion-agentes/SKILL.md +314 -0
- package/habilidades/event-driven/SKILL.md +153 -0
- package/habilidades/event-driven/recursos/implementaciones-completas.md +423 -0
- package/habilidades/extraccion-documentos/SKILL.md +221 -0
- package/habilidades/extractor-de-aprendizajes/.evolved.json +9 -0
- package/habilidades/extractor-de-aprendizajes/SKILL.md +311 -0
- package/habilidades/extractor-de-aprendizajes/evals/evals.json +55 -0
- package/habilidades/fastapi-experto/SKILL.md +221 -0
- package/habilidades/fastapi-experto/recursos/async-patterns.md +438 -0
- package/habilidades/fastapi-experto/recursos/dependency-injection.md +330 -0
- package/habilidades/fastapi-experto/recursos/referencia-completa.md +79 -0
- package/habilidades/fastapi-experto/recursos/testing-httpx.md +420 -0
- package/habilidades/filament-admin/SKILL.md +290 -0
- package/habilidades/frontend-avanzado/SKILL.md +257 -0
- package/habilidades/frontend-avanzado/recursos/apis-nativas-ejemplos.md +341 -0
- package/habilidades/gcp-cloud/SKILL.md +260 -0
- package/habilidades/gcp-cloud/recursos/gke.md +234 -0
- package/habilidades/gcp-cloud/recursos/terraform-gcp.md +307 -0
- package/habilidades/generacion-mermaid/SKILL.md +229 -0
- package/habilidades/git-worktrees-paralelo/SKILL.md +270 -0
- package/habilidades/go-experto/SKILL.md +305 -0
- package/habilidades/go-patrones/SKILL.md +299 -0
- package/habilidades/go-testing/SKILL.md +291 -0
- package/habilidades/graphql-experto/SKILL.md +323 -0
- package/habilidades/guardrail-semantico/SKILL.md +282 -0
- package/habilidades/harness-claude-code/SKILL.md +299 -0
- package/habilidades/iam-secretos/SKILL.md +265 -0
- package/habilidades/iam-secretos/recursos/implementaciones-completas.md +356 -0
- package/habilidades/infra-github-actions/SKILL.md +166 -0
- package/habilidades/instalar-sistema/.evolved.json +9 -0
- package/habilidades/instalar-sistema/SKILL.md +221 -0
- package/habilidades/java-experto/SKILL.md +290 -0
- package/habilidades/java-patrones/SKILL.md +275 -0
- package/habilidades/java-testing/SKILL.md +288 -0
- package/habilidades/kotlin-compose/SKILL.md +278 -0
- package/habilidades/kotlin-compose/recursos/animaciones-performance.md +93 -0
- package/habilidades/kotlin-experto/SKILL.md +318 -0
- package/habilidades/kotlin-testing/SKILL.md +267 -0
- package/habilidades/kotlin-testing/recursos/testing-avanzado.md +74 -0
- package/habilidades/kubernetes-orquestacion/SKILL.md +152 -0
- package/habilidades/kubernetes-orquestacion/recursos/manifiestos-completos.md +452 -0
- package/habilidades/langchain-langraph/SKILL.md +386 -0
- package/habilidades/langchain-langraph/recursos/evaluacion-rag.md +321 -0
- package/habilidades/langchain-langraph/recursos/rag-maturity-model.md +225 -0
- package/habilidades/langchain-langraph/recursos/vectorstores.md +306 -0
- package/habilidades/legacy-code-rescue/SKILL.md +267 -0
- package/habilidades/likec4-experto/SKILL.md +412 -0
- package/habilidades/likec4-experto/evals/evals.json +69 -0
- package/habilidades/manejo-errores/.evolved.json +9 -0
- package/habilidades/manejo-errores/SKILL.md +407 -0
- package/habilidades/manejo-errores/recursos/implementaciones-completas.md +248 -0
- package/habilidades/mapear-codebase/SKILL.md +275 -0
- package/habilidades/memoria-busqueda/SKILL.md +194 -0
- package/habilidades/memoria-busqueda/evals/evals.json +44 -0
- package/habilidades/meta-skills-estandar/SKILL.md +298 -0
- package/habilidades/meta-skills-estandar/recursos/anti-patrones-y-leyes.md +205 -0
- package/habilidades/meta-skills-estandar/recursos/frameworks-seguridad.md +107 -0
- package/habilidades/meta-skills-estandar/recursos/idiomas-framework.md +60 -0
- package/habilidades/meta-skills-estandar/recursos/skills-as-agents.md +163 -0
- package/habilidades/microservicios/SKILL.md +155 -0
- package/habilidades/microservicios/recursos/patrones-y-ejemplos-completos.md +325 -0
- package/habilidades/mobile-flutter/SKILL.md +199 -0
- package/habilidades/mobile-flutter/recursos/ejemplos-completos.md +319 -0
- package/habilidades/mobile-react-native/SKILL.md +176 -0
- package/habilidades/mobile-react-native/recursos/ejemplos-completos.md +216 -0
- package/habilidades/mongodb-experto/SKILL.md +302 -0
- package/habilidades/monitoring-alertas/SKILL.md +201 -0
- package/habilidades/monitoring-alertas/recursos/instrumentacion-y-alertas.md +301 -0
- package/habilidades/nestjs-experto/SKILL.md +307 -0
- package/habilidades/nestjs-experto/recursos/guards-interceptors.md +339 -0
- package/habilidades/nestjs-experto/recursos/modulos-di.md +287 -0
- package/habilidades/nestjs-experto/recursos/testing-nestjs.md +354 -0
- package/habilidades/nextjs-experto/SKILL.md +335 -0
- package/habilidades/nextjs-patrones/SKILL.md +303 -0
- package/habilidades/nextjs-testing/SKILL.md +331 -0
- package/habilidades/node-experto/.evolved.json +9 -0
- package/habilidades/node-experto/SKILL.md +266 -0
- package/habilidades/node-experto/recursos/patrones-completos.md +283 -0
- package/habilidades/notificaciones-multicanal/SKILL.md +159 -0
- package/habilidades/notificaciones-multicanal/recursos/config-template.json +115 -0
- package/habilidades/notificaciones-multicanal/recursos/configuracion-y-templates.md +303 -0
- package/habilidades/nuevo-proyecto/SKILL.md +204 -0
- package/habilidades/orquestacion-async/SKILL.md +303 -0
- package/habilidades/paid-media-tracking/SKILL.md +269 -0
- package/habilidades/paid-media-tracking/recursos/auditoria-tracking.md +220 -0
- package/habilidades/paid-media-tracking/recursos/google-ads-api.md +215 -0
- package/habilidades/patrones-python/SKILL.md +228 -0
- package/habilidades/patrones-python/evals/evals.json +56 -0
- package/habilidades/patrones-python/recursos/patrones-avanzados.md +469 -0
- package/habilidades/patrones-python/recursos/referencia-completa.md +202 -0
- package/habilidades/perfil-usuario/SKILL.md +200 -0
- package/habilidades/perfil-usuario/evals/evals.json +55 -0
- package/habilidades/performance-baseline/SKILL.md +297 -0
- package/habilidades/php-experto/SKILL.md +291 -0
- package/habilidades/php-patrones/SKILL.md +306 -0
- package/habilidades/php-testing/SKILL.md +280 -0
- package/habilidades/planear-fase/SKILL.md +269 -0
- package/habilidades/postgresql-experto/SKILL.md +151 -0
- package/habilidades/postgresql-experto/evals/evals.json +53 -0
- package/habilidades/postgresql-experto/recursos/referencia-completa.md +215 -0
- package/habilidades/prevencion-racionalizacion/SKILL.md +175 -0
- package/habilidades/prevencion-sobreingenieria/SKILL.md +323 -0
- package/habilidades/privacy-memoria/SKILL.md +141 -0
- package/habilidades/privacy-memoria/evals/evals.json +43 -0
- package/habilidades/prompt-engineering/SKILL.md +518 -0
- package/habilidades/prompt-engineering/recursos/patrones-avanzados.md +467 -0
- package/habilidades/rag-arquitectura/SKILL.md +338 -0
- package/habilidades/rails-experto/SKILL.md +237 -0
- package/habilidades/rails-experto/recursos/active-record.md +260 -0
- package/habilidades/rails-experto/recursos/hotwire-turbo.md +293 -0
- package/habilidades/rails-experto/recursos/testing-rspec.md +362 -0
- package/habilidades/react-experto/SKILL.md +209 -0
- package/habilidades/react-experto/evals/evals.json +55 -0
- package/habilidades/react-experto/recursos/patrones-y-ejemplos-completos.md +240 -0
- package/habilidades/react-optimizacion/SKILL.md +174 -0
- package/habilidades/react-optimizacion/recursos/patrones-avanzados.md +138 -0
- package/habilidades/redis-experto/SKILL.md +305 -0
- package/habilidades/release-semver/.evolved.json +9 -0
- package/habilidades/release-semver/SKILL.md +248 -0
- package/habilidades/release-semver/scripts/generar-changelog.sh +238 -0
- package/habilidades/rust-experto/SKILL.md +400 -0
- package/habilidades/rust-patrones/SKILL.md +296 -0
- package/habilidades/rust-testing/SKILL.md +311 -0
- package/habilidades/seguridad-skills-ia/SKILL.md +262 -0
- package/habilidades/sql-optimizacion/SKILL.md +200 -0
- package/habilidades/sql-optimizacion/evals/evals.json +54 -0
- package/habilidades/sql-optimizacion/recursos/patrones-sql-avanzados.md +131 -0
- package/habilidades/sre-patrones/SKILL.md +333 -0
- package/habilidades/sre-patrones/recursos/chaos-engineering.md +241 -0
- package/habilidades/sre-patrones/recursos/oncall-design.md +236 -0
- package/habilidades/stripe-pagos/SKILL.md +550 -0
- package/habilidades/stripe-pagos/recursos/errores-reintentos.md +390 -0
- package/habilidades/stripe-pagos/recursos/stripe-connect.md +290 -0
- package/habilidades/structured-outputs/SKILL.md +343 -0
- package/habilidades/swift-experto/SKILL.md +320 -0
- package/habilidades/swift-experto/recursos/keychain-y-wrappers.md +110 -0
- package/habilidades/swift-patrones/SKILL.md +313 -0
- package/habilidades/swift-patrones/recursos/tca-ejemplo-completo.md +113 -0
- package/habilidades/swift-testing/SKILL.md +254 -0
- package/habilidades/swift-testing/recursos/xcuitest-planes.md +143 -0
- package/habilidades/swl-dashboard/SKILL.md +370 -0
- package/habilidades/swl-markitdown/SKILL.md +285 -0
- package/habilidades/swl-markitdown/evals/evals.json +52 -0
- package/habilidades/swl-revisar-impacto/SKILL.md +233 -0
- package/habilidades/tailwind-experto/SKILL.md +240 -0
- package/habilidades/tailwind-experto/recursos/referencia-completa.md +184 -0
- package/habilidades/tdd-workflow/SKILL.md +293 -0
- package/habilidades/terraform-experto/SKILL.md +321 -0
- package/habilidades/testing-python/SKILL.md +340 -0
- package/habilidades/testing-python/recursos/ejemplos-completos.md +167 -0
- package/habilidades/threat-model-lite/SKILL.md +246 -0
- package/habilidades/tracing-processor/SKILL.md +212 -0
- package/habilidades/tracking-measurement/SKILL.md +239 -0
- package/habilidades/tracking-measurement/recursos/consent-mode.md +231 -0
- package/habilidades/tracking-measurement/recursos/gtm-datalayer.md +216 -0
- package/habilidades/tracking-measurement/recursos/meta-capi.md +262 -0
- package/habilidades/typescript-avanzado/SKILL.md +144 -0
- package/habilidades/typescript-avanzado/evals/evals.json +55 -0
- package/habilidades/typescript-avanzado/recursos/patrones-y-ejemplos-completos.md +298 -0
- package/habilidades/typescript-diagnosticos/SKILL.md +513 -0
- package/habilidades/ux-diseno/SKILL.md +116 -0
- package/habilidades/ux-diseno/evals/evals.json +43 -0
- package/habilidades/ux-diseno/recursos/patrones-ux-referencia.md +214 -0
- package/habilidades/validacion-ci-sistema/SKILL.md +136 -0
- package/habilidades/validacion-ci-sistema/recursos/validadores-completos.md +369 -0
- package/habilidades/validacion-ci-sistema/scripts/validar-sistema.sh +286 -0
- package/habilidades/verificacion-evidencia/SKILL.md +160 -0
- package/habilidades/verificar-trabajo/SKILL.md +303 -0
- package/habilidades/verificar-trabajo/recursos/plantilla-verificacion.md +60 -0
- package/habilidades/wiki-conocimiento/SKILL.md +276 -0
- package/habilidades/wireframes-flujos/SKILL.md +212 -0
- package/habilidades/wireframes-flujos/recursos/referencia-completa.md +192 -0
- package/habilidades/workflow-claude-code/SKILL.md +260 -0
- package/habilidades/workflow-claude-code/recursos/referencia-completa.md +109 -0
- package/hooks/_run-hook.sh +57 -0
- package/hooks/actualizar-perfil-usuario.js +364 -0
- package/hooks/agente-lifecycle.js +71 -0
- package/hooks/aiisms-detector.js +173 -0
- package/hooks/audit-trail.js +204 -0
- package/hooks/auto-background.js +97 -0
- package/hooks/auto-consolidacion.js +178 -0
- package/hooks/auto-evolucion.js +666 -0
- package/hooks/auto-restaurar-settings.js +360 -0
- package/hooks/calidad-pre-commit.js +929 -0
- package/hooks/calidad-typescript.js +511 -0
- package/hooks/captura-feedback-usuario.js +148 -0
- package/hooks/check-update.js +211 -0
- package/hooks/clasificador-mensajes.js +271 -0
- package/hooks/degradacion-instintos.js +272 -0
- package/hooks/escaneo-secretos.js +389 -0
- package/hooks/extraccion-aprendizajes.js +763 -0
- package/hooks/grafo-contexto.js +129 -0
- package/hooks/graph-update.js +67 -0
- package/hooks/guardrail-modelo.js +247 -0
- package/hooks/inbox-aviso.js +75 -0
- package/hooks/inyeccion-contexto.js +246 -0
- package/hooks/lib/abort-registry.js +214 -0
- package/hooks/lib/agent-backend.js +210 -0
- package/hooks/lib/agent-comms.js +263 -0
- package/hooks/lib/agent-issue-codes.js +284 -0
- package/hooks/lib/agent-matcher.js +189 -0
- package/hooks/lib/async-hook-registry.js +252 -0
- package/hooks/lib/atomic-write.js +130 -0
- package/hooks/lib/auto-consolidator.js +335 -0
- package/hooks/lib/canary-skills.js +187 -0
- package/hooks/lib/consolidation-lock.js +291 -0
- package/hooks/lib/context-builder.js +430 -0
- package/hooks/lib/context-compressor.js +657 -0
- package/hooks/lib/convergence-detector.js +105 -0
- package/hooks/lib/delegation-tracker.js +198 -0
- package/hooks/lib/detectar-package-manager.js +423 -0
- package/hooks/lib/edit-accumulator.js +171 -0
- package/hooks/lib/error-classifier.js +308 -0
- package/hooks/lib/event-bus.js +112 -0
- package/hooks/lib/evolution-tracker.js +442 -0
- package/hooks/lib/execution-state.js +316 -0
- package/hooks/lib/fingerprint-id.js +135 -0
- package/hooks/lib/gateway-notify.js +116 -0
- package/hooks/lib/graph-security.js +75 -0
- package/hooks/lib/guardrail-metrics.js +202 -0
- package/hooks/lib/hook-circuit-breaker.js +206 -0
- package/hooks/lib/loop-detector.js +267 -0
- package/hooks/lib/mcp-health.js +184 -0
- package/hooks/lib/mcp-pool.js +436 -0
- package/hooks/lib/memory-search.js +506 -0
- package/hooks/lib/merkle-audit.js +96 -0
- package/hooks/lib/model-router.js +222 -0
- package/hooks/lib/normalize-error.js +324 -0
- package/hooks/lib/normalize-input.js +65 -0
- package/hooks/lib/nudge-tracker.js +306 -0
- package/hooks/lib/otlp-exporter.js +365 -0
- package/hooks/lib/performance-marks.js +239 -0
- package/hooks/lib/privacy-filter.js +128 -0
- package/hooks/lib/prompt-injection-scanner.js +209 -0
- package/hooks/lib/provenance-tracker.js +183 -0
- package/hooks/lib/rate-limit-tracker.js +253 -0
- package/hooks/lib/reflect-classifier.js +164 -0
- package/hooks/lib/resource-quota.js +122 -0
- package/hooks/lib/retry-jitter.js +165 -0
- package/hooks/lib/risk-engine.js +368 -0
- package/hooks/lib/run-log.js +408 -0
- package/hooks/lib/session-fts.js +379 -0
- package/hooks/lib/session-store.js +293 -0
- package/hooks/lib/singleton-guard.js +159 -0
- package/hooks/lib/skill-auditor.js +588 -0
- package/hooks/lib/sync-status.js +228 -0
- package/hooks/lib/taint-tracker.js +107 -0
- package/hooks/lib/task-service.js +295 -0
- package/hooks/lib/tech-skills-map.js +146 -0
- package/hooks/lib/telegram-cliente.js +159 -0
- package/hooks/lib/telegram-config.js +170 -0
- package/hooks/lib/token-budget.js +156 -0
- package/hooks/lib/token-estimator.js +420 -0
- package/hooks/lib/toon-compressor.js +245 -0
- package/hooks/lib/usage-model.js +183 -0
- package/hooks/lib/variable-resolver.js +230 -0
- package/hooks/linea-estado.js +324 -0
- package/hooks/metricas-evolucion.js +209 -0
- package/hooks/monitor-contexto.js +325 -0
- package/hooks/notificacion-sesion-stop.js +198 -0
- package/hooks/notificacion-telegram-notification.js +4 -0
- package/hooks/notificacion-telegram-subagent.js +4 -0
- package/hooks/notificacion-telegram.js +267 -0
- package/hooks/preservar-estado-pre-compact.js +150 -0
- package/hooks/proteccion-rutas.js +366 -0
- package/hooks/registro-turnos.js +209 -0
- package/hooks/resumen-sesion.js +249 -0
- package/hooks/risk-scoring.js +323 -0
- package/hooks/rotar-audit-auto.js +122 -0
- package/hooks/sugerir-regenerar-inventario.js +170 -0
- package/hooks/telemetria-agentes.js +167 -0
- package/hooks/tracking-costos.js +688 -0
- package/instintos/global.yaml +8 -0
- package/instintos/perfil-usuario.yaml +53 -0
- package/instintos/prompt-appendices.yaml +57 -0
- package/instintos/proyecto.yaml +372 -0
- package/manifiestos/gateway-config.json +77 -0
- package/manifiestos/handoff-context.json +223 -0
- package/manifiestos/hook-profiles.json +44 -0
- package/manifiestos/hooks-config.json +360 -0
- package/manifiestos/modulos.json +1173 -0
- package/manifiestos/perfiles.json +404 -0
- package/package.json +86 -0
- package/plantillas/ESTADO.md +109 -0
- package/plantillas/HOJA-RUTA.md +143 -0
- package/plantillas/PROYECTO.md +122 -0
- package/plantillas/REQUISITOS.md +132 -0
- package/plantillas/auditor-veto-template.md +105 -0
- package/plantillas/github-workflows/README.md +47 -0
- package/plantillas/github-workflows/release-please.yml +44 -0
- package/plantillas/github-workflows/swl-ci.yml +107 -0
- package/plantillas/github-workflows/swl-security.yml +51 -0
- package/plantillas/mcp-mineru.json +13 -0
- package/plantillas/research/ARQUITECTURA.md +220 -0
- package/plantillas/research/FUNCIONALIDADES.md +175 -0
- package/plantillas/research/RESUMEN.md +165 -0
- package/plantillas/research/STACK.md +233 -0
- package/plantillas/research/TRAMPAS.md +299 -0
- package/plantillas/skill-evals-template.json +44 -0
- package/plugin.json +343 -0
- package/reglas/accesibilidad.md +269 -0
- package/reglas/api-diseno.md +400 -0
- package/reglas/arquitectura.md +352 -0
- package/reglas/brevedad-output.md +124 -0
- package/reglas/cloud-infra.md +247 -0
- package/reglas/docs.md +245 -0
- package/reglas/estilo-codigo.md +201 -0
- package/reglas/git-workflow.md +245 -0
- package/reglas/gobernanza.md +271 -0
- package/reglas/harness-claude-code.md +213 -0
- package/reglas/hooks.md +186 -0
- package/reglas/lenguajes/csharp/estilo-codigo.md +231 -0
- package/reglas/lenguajes/csharp/hooks.md +281 -0
- package/reglas/lenguajes/csharp/patrones.md +226 -0
- package/reglas/lenguajes/csharp/seguridad.md +258 -0
- package/reglas/lenguajes/csharp/testing.md +176 -0
- package/reglas/lenguajes/go/estilo-codigo.md +195 -0
- package/reglas/lenguajes/go/hooks.md +249 -0
- package/reglas/lenguajes/go/patrones.md +249 -0
- package/reglas/lenguajes/go/seguridad.md +225 -0
- package/reglas/lenguajes/go/testing.md +272 -0
- package/reglas/lenguajes/java/estilo-codigo.md +217 -0
- package/reglas/lenguajes/java/hooks.md +251 -0
- package/reglas/lenguajes/java/patrones.md +226 -0
- package/reglas/lenguajes/java/seguridad.md +233 -0
- package/reglas/lenguajes/java/testing.md +238 -0
- package/reglas/lenguajes/kotlin/estilo-codigo.md +208 -0
- package/reglas/lenguajes/kotlin/hooks.md +245 -0
- package/reglas/lenguajes/kotlin/patrones.md +201 -0
- package/reglas/lenguajes/kotlin/seguridad.md +202 -0
- package/reglas/lenguajes/kotlin/testing.md +236 -0
- package/reglas/lenguajes/nextjs/estilo-codigo.md +175 -0
- package/reglas/lenguajes/nextjs/hooks.md +186 -0
- package/reglas/lenguajes/nextjs/patrones.md +225 -0
- package/reglas/lenguajes/nextjs/seguridad.md +216 -0
- package/reglas/lenguajes/nextjs/testing.md +193 -0
- package/reglas/lenguajes/php/estilo-codigo.md +228 -0
- package/reglas/lenguajes/php/hooks.md +165 -0
- package/reglas/lenguajes/php/patrones.md +233 -0
- package/reglas/lenguajes/php/seguridad.md +186 -0
- package/reglas/lenguajes/php/testing.md +205 -0
- package/reglas/lenguajes/rust/estilo-codigo.md +207 -0
- package/reglas/lenguajes/rust/hooks.md +240 -0
- package/reglas/lenguajes/rust/patrones.md +250 -0
- package/reglas/lenguajes/rust/seguridad.md +221 -0
- package/reglas/lenguajes/rust/testing.md +194 -0
- package/reglas/lenguajes/swift/estilo-codigo.md +238 -0
- package/reglas/lenguajes/swift/hooks.md +257 -0
- package/reglas/lenguajes/swift/patrones.md +235 -0
- package/reglas/lenguajes/swift/seguridad.md +248 -0
- package/reglas/lenguajes/swift/testing.md +242 -0
- package/reglas/markitdown.md +60 -0
- package/reglas/memoria-consolidada.md +209 -0
- package/reglas/patrones.md +225 -0
- package/reglas/performance.md +195 -0
- package/reglas/pruebas.md +159 -0
- package/reglas/seguridad-agentes.md +351 -0
- package/reglas/seguridad.md +151 -0
- package/reglas/skills-estandar.md +373 -0
- package/reglas/testing.md +193 -0
- package/schemas/agent-contract.json +176 -0
- package/schemas/agent-frontmatter.schema.json +149 -0
- package/schemas/agent-message.schema.json +53 -0
- package/schemas/agent-output-implementacion.schema.json +85 -0
- package/schemas/agent-output-planificacion.schema.json +113 -0
- package/schemas/agent-output-review.schema.json +78 -0
- package/schemas/diary-entry.schema.json +80 -0
- package/schemas/hook-profiles.schema.json +39 -0
- package/schemas/hooks-config.schema.json +74 -0
- package/schemas/instinct.schema.json +115 -0
- package/schemas/modulos.schema.json +29 -0
- package/schemas/perfiles.schema.json +28 -0
- package/schemas/plugin.schema.json +64 -0
- package/schemas/skill-evals.schema.json +95 -0
- package/schemas/skill-frontmatter.schema.json +170 -0
- package/scripts/actualizar.js +145 -0
- package/scripts/audit-skills.sh +78 -0
- package/scripts/auditar-agentes-gaps.js +149 -0
- package/scripts/auditar-cobertura-frameworks.js +241 -0
- package/scripts/auditar-skills-gaps.js +206 -0
- package/scripts/bootstrap-instintos.js +259 -0
- package/scripts/check-update.js +109 -0
- package/scripts/comandos/agents.js +105 -0
- package/scripts/comandos/info.js +108 -0
- package/scripts/comandos/install-asistido.js +186 -0
- package/scripts/comandos/skills.js +211 -0
- package/scripts/configurar-branch-protection.js +418 -0
- package/scripts/daemon-swl.py +388 -0
- package/scripts/desinstalar.js +130 -0
- package/scripts/doctor.js +559 -0
- package/scripts/field-report.js +199 -0
- package/scripts/generar-inventario.js +317 -0
- package/scripts/inbox-tmux-inject.js +161 -0
- package/scripts/inferir-herramientas-permitidas.js +586 -0
- package/scripts/inicializar.js +133 -0
- package/scripts/instalador.js +1031 -0
- package/scripts/instalar-git-hook.js +122 -0
- package/scripts/lib/agp-frontmatter.js +222 -0
- package/scripts/lib/append-con-marcadores.js +199 -0
- package/scripts/lib/artefactos-python.js +43 -0
- package/scripts/lib/audit-query.js +221 -0
- package/scripts/lib/autostart-linux.js +347 -0
- package/scripts/lib/autostart-macos.js +360 -0
- package/scripts/lib/autostart-windows.js +307 -0
- package/scripts/lib/budget-enforcer.js +252 -0
- package/scripts/lib/claude-sessions.js +285 -0
- package/scripts/lib/configurar-ci.js +380 -0
- package/scripts/lib/console-span-exporter.js +92 -0
- package/scripts/lib/contadores-inventario.js +217 -0
- package/scripts/lib/dashboard-widgets.js +290 -0
- package/scripts/lib/detectar-runtime.js +279 -0
- package/scripts/lib/detectar-stack.js +187 -0
- package/scripts/lib/diary-entry.js +234 -0
- package/scripts/lib/drift-detector.js +545 -0
- package/scripts/lib/estado.js +124 -0
- package/scripts/lib/gestor-componentes.js +243 -0
- package/scripts/lib/gitignore-manifest.js +305 -0
- package/scripts/lib/graph-analyze.py +556 -0
- package/scripts/lib/graph-builder.py +485 -0
- package/scripts/lib/graph-cluster.py +259 -0
- package/scripts/lib/health-row.js +168 -0
- package/scripts/lib/hooks-settings.js +789 -0
- package/scripts/lib/manifiestos.js +138 -0
- package/scripts/lib/mc-client.js +137 -0
- package/scripts/lib/notificaciones-telegram.js +1107 -0
- package/scripts/lib/npm-version.js +261 -0
- package/scripts/lib/paquetes-conocidos.js +50 -0
- package/scripts/lib/preservar-usuario.js +586 -0
- package/scripts/lib/prompt-builder.js +264 -0
- package/scripts/lib/resolver-externo.js +332 -0
- package/scripts/lib/schedule-parser.js +305 -0
- package/scripts/lib/scoring-instintos.js +240 -0
- package/scripts/lib/seguridad.js +160 -0
- package/scripts/lib/selector-interactivo.js +152 -0
- package/scripts/lib/semantic-search.js +242 -0
- package/scripts/lib/skill-discovery.js +234 -0
- package/scripts/lib/skill-metrics.js +246 -0
- package/scripts/lib/skill-normalizer.js +112 -0
- package/scripts/lib/skills-hub.js +340 -0
- package/scripts/lib/span-schema.js +134 -0
- package/scripts/lib/tool-cost-analyzer.js +255 -0
- package/scripts/lib/tracing-processor-interface.js +286 -0
- package/scripts/lib/transformadores/base.js +80 -0
- package/scripts/lib/transformadores/claude.js +124 -0
- package/scripts/lib/transformadores/codex.js +115 -0
- package/scripts/lib/transformadores/copilot.js +106 -0
- package/scripts/lib/transformadores/gemini.js +74 -0
- package/scripts/lib/transformadores/index.js +35 -0
- package/scripts/lib/transformadores/opencode.js +75 -0
- package/scripts/lib/ui.js +259 -0
- package/scripts/limpiar-artefactos-python.js +131 -0
- package/scripts/mcp-orchestrator.py +386 -0
- package/scripts/mcp-pool-manager.py +352 -0
- package/scripts/mcp-telemetry.py +378 -0
- package/scripts/poblar-evolvable.js +226 -0
- package/scripts/publicar.js +287 -0
- package/scripts/reflect-skills.js +403 -0
- package/scripts/rotar-audit-logs.js +185 -0
- package/scripts/run-skill-evals.js +242 -0
- package/scripts/smoke-test.js +374 -0
- package/scripts/token-analysis.py +471 -0
- package/scripts/validar-manifest.js +195 -0
- package/scripts/validar-memoria.js +321 -0
- package/scripts/validar-tests-aislamiento.js +184 -0
- package/scripts/validar-tokens-test.js +208 -0
- package/scripts/validar.js +147 -0
- package/scripts/validate-markdown.py +339 -0
- package/scripts/validate-skills.py +385 -0
- package/scripts/vendor/claude-usage/README.md +116 -0
- package/scripts/vendor/claude-usage/cli.py +334 -0
- package/scripts/vendor/claude-usage/dashboard.py +795 -0
- package/scripts/vendor/claude-usage/scanner.py +467 -0
- package/scripts/vendor/markitdown/cli.py +194 -0
- package/scripts/verificar-evolucion.js +289 -0
- package/scripts/verificar-release.js +494 -0
|
@@ -0,0 +1,278 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: kotlin-compose
|
|
3
|
+
description: >
|
|
4
|
+
Jetpack Compose avanzado: state hoisting, remember vs rememberSaveable,
|
|
5
|
+
derivedStateOf, Navigation Compose con type-safe routes, LazyColumn con keys,
|
|
6
|
+
side effects, animaciones y optimización de recomposición con @Stable.
|
|
7
|
+
Cargar cuando se implementen pantallas Compose, se optimice recomposición,
|
|
8
|
+
o se configure Navigation Compose en proyectos Android.
|
|
9
|
+
version: "1.0.0"
|
|
10
|
+
herramientasPermitidas: [Read, Glob, Grep]
|
|
11
|
+
exclusiones:
|
|
12
|
+
- "No cargar para lógica de negocio, ViewModels o coroutines fuera de Compose — para eso cargar `kotlin-experto`."
|
|
13
|
+
- "No cargar para escribir tests de UI con ComposeTestRule — para testing cargar `kotlin-testing`."
|
|
14
|
+
- "No cargar para errores de compilación Kotlin o Compose — para build errors cargar `build-errors-kotlin`."
|
|
15
|
+
- "No cargar para SwiftUI o React Native — Compose es específico de Android/Kotlin; para iOS cargar `swift-patrones`."
|
|
16
|
+
evolvable: true # default para skill estandar
|
|
17
|
+
---
|
|
18
|
+
# Kotlin Compose — State, Navigation y Performance
|
|
19
|
+
|
|
20
|
+
## Cuándo NO cargar
|
|
21
|
+
|
|
22
|
+
- La pregunta es sobre lógica de negocio, ViewModels, coroutines o Hilt fuera de Compose — para eso cargar `kotlin-experto`.
|
|
23
|
+
- La tarea es escribir tests de UI con `ComposeTestRule` — para testing cargar `kotlin-testing`.
|
|
24
|
+
- Los errores son de compilación Kotlin o Compose (generación de código de Hilt, errores de BOM) — cargar `build-errors-kotlin`.
|
|
25
|
+
- La UI es SwiftUI, React Native o Flutter — Compose es específico de Android/Kotlin.
|
|
26
|
+
|
|
27
|
+
## State Hoisting — Regla fundamental
|
|
28
|
+
|
|
29
|
+
El estado debe vivir en el nivel más bajo que lo necesita, pero no más abajo.
|
|
30
|
+
Hoistear hacia arriba cuando más de un composable lo necesita.
|
|
31
|
+
|
|
32
|
+
```kotlin
|
|
33
|
+
// MAL — estado interno, no testeable, no reutilizable
|
|
34
|
+
@Composable
|
|
35
|
+
fun BuscadorFacturas() {
|
|
36
|
+
var query by remember { mutableStateOf("") }
|
|
37
|
+
// query solo visible aquí
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
// BIEN — estado hoisted, el llamador controla
|
|
41
|
+
@Composable
|
|
42
|
+
fun BuscadorFacturas(
|
|
43
|
+
query: String,
|
|
44
|
+
onQueryChange: (String) -> Unit,
|
|
45
|
+
modifier: Modifier = Modifier
|
|
46
|
+
) {
|
|
47
|
+
OutlinedTextField(
|
|
48
|
+
value = query,
|
|
49
|
+
onValueChange = onQueryChange,
|
|
50
|
+
modifier = modifier
|
|
51
|
+
)
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Llamador (o ViewModel) posee el estado
|
|
55
|
+
@Composable
|
|
56
|
+
fun FacturasScreen(viewModel: FacturasViewModel = hiltViewModel()) {
|
|
57
|
+
val uiState by viewModel.uiState.collectAsStateWithLifecycle()
|
|
58
|
+
BuscadorFacturas(
|
|
59
|
+
query = uiState.query,
|
|
60
|
+
onQueryChange = viewModel::onQueryChange
|
|
61
|
+
)
|
|
62
|
+
}
|
|
63
|
+
```
|
|
64
|
+
|
|
65
|
+
---
|
|
66
|
+
|
|
67
|
+
## remember vs rememberSaveable
|
|
68
|
+
|
|
69
|
+
```kotlin
|
|
70
|
+
// remember — sobrevive recomposición, NO sobrevive rotación de pantalla
|
|
71
|
+
var expandido by remember { mutableStateOf(false) }
|
|
72
|
+
|
|
73
|
+
// rememberSaveable — sobrevive recomposición Y rotación
|
|
74
|
+
// Usar para estado de UI efímero que el usuario espera que persista
|
|
75
|
+
var seleccionado by rememberSaveable { mutableStateOf("") }
|
|
76
|
+
|
|
77
|
+
// rememberSaveable con Saver personalizado para objetos complejos
|
|
78
|
+
var filtros by rememberSaveable(stateSaver = FiltrosSaver) {
|
|
79
|
+
mutableStateOf(FiltrosFactura())
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
val FiltrosSaver = Saver<FiltrosFactura, Bundle>(
|
|
83
|
+
save = { filtros ->
|
|
84
|
+
Bundle().apply {
|
|
85
|
+
putString("estatus", filtros.estatus)
|
|
86
|
+
putString("desde", filtros.desde?.toString())
|
|
87
|
+
}
|
|
88
|
+
},
|
|
89
|
+
restore = { bundle ->
|
|
90
|
+
FiltrosFactura(
|
|
91
|
+
estatus = bundle.getString("estatus"),
|
|
92
|
+
desde = bundle.getString("desde")?.let { LocalDate.parse(it) }
|
|
93
|
+
)
|
|
94
|
+
}
|
|
95
|
+
)
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
---
|
|
99
|
+
|
|
100
|
+
## derivedStateOf — Computaciones derivadas
|
|
101
|
+
|
|
102
|
+
```kotlin
|
|
103
|
+
// derivedStateOf solo recompone cuando el RESULTADO cambia,
|
|
104
|
+
// no cuando la fuente cambia. Ideal para filtros y búsquedas.
|
|
105
|
+
@Composable
|
|
106
|
+
fun FacturasList(facturas: List<Factura>, query: String) {
|
|
107
|
+
// Sin derivedStateOf: recompone en CADA keystroke aunque el resultado sea igual
|
|
108
|
+
// Con derivedStateOf: recompone solo cuando la lista filtrada cambia
|
|
109
|
+
val facturasFiltradas by remember(facturas) {
|
|
110
|
+
derivedStateOf {
|
|
111
|
+
if (query.isBlank()) facturas
|
|
112
|
+
else facturas.filter {
|
|
113
|
+
it.folio.contains(query, ignoreCase = true) ||
|
|
114
|
+
it.cliente.contains(query, ignoreCase = true)
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
LazyColumn {
|
|
120
|
+
items(facturasFiltradas, key = { it.id }) { factura ->
|
|
121
|
+
FacturaItem(factura)
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
}
|
|
125
|
+
```
|
|
126
|
+
|
|
127
|
+
---
|
|
128
|
+
|
|
129
|
+
## Navigation Compose — Type-safe Routes
|
|
130
|
+
|
|
131
|
+
```kotlin
|
|
132
|
+
// Definir rutas como objetos serializables (Navigation 2.8+)
|
|
133
|
+
@Serializable object RutaFacturas
|
|
134
|
+
@Serializable data class RutaDetalleFactura(val id: String)
|
|
135
|
+
@Serializable object RutaCrearFactura
|
|
136
|
+
|
|
137
|
+
@Composable
|
|
138
|
+
fun AppNavGraph(navController: NavHostController = rememberNavController()) {
|
|
139
|
+
NavHost(navController, startDestination = RutaFacturas) {
|
|
140
|
+
composable<RutaFacturas> {
|
|
141
|
+
FacturasScreen(
|
|
142
|
+
onFacturaClick = { id ->
|
|
143
|
+
navController.navigate(RutaDetalleFactura(id))
|
|
144
|
+
},
|
|
145
|
+
onCrearClick = { navController.navigate(RutaCrearFactura) }
|
|
146
|
+
)
|
|
147
|
+
}
|
|
148
|
+
composable<RutaDetalleFactura> { backStackEntry ->
|
|
149
|
+
val ruta = backStackEntry.toRoute<RutaDetalleFactura>()
|
|
150
|
+
DetalleFacturaScreen(facturaId = ruta.id)
|
|
151
|
+
}
|
|
152
|
+
composable<RutaCrearFactura> {
|
|
153
|
+
CrearFacturaScreen(onGuardado = { navController.popBackStack() })
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
```
|
|
158
|
+
|
|
159
|
+
---
|
|
160
|
+
|
|
161
|
+
## LazyColumn — Keys y Performance
|
|
162
|
+
|
|
163
|
+
```kotlin
|
|
164
|
+
// SIEMPRE proveer key estable para evitar recomposición completa
|
|
165
|
+
LazyColumn(
|
|
166
|
+
contentPadding = PaddingValues(16.dp),
|
|
167
|
+
verticalArrangement = Arrangement.spacedBy(8.dp)
|
|
168
|
+
) {
|
|
169
|
+
// key debe ser estable y único — usar el ID del dominio
|
|
170
|
+
items(facturas, key = { factura -> factura.id }) { factura ->
|
|
171
|
+
FacturaItem(
|
|
172
|
+
factura = factura,
|
|
173
|
+
modifier = Modifier.animateItem() // animación de inserción/eliminación
|
|
174
|
+
)
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// Separador sin key — usar índice si no hay ID
|
|
178
|
+
itemsIndexed(secciones, key = { index, _ -> "seccion_$index" }) { _, seccion ->
|
|
179
|
+
SeccionHeader(seccion)
|
|
180
|
+
}
|
|
181
|
+
}
|
|
182
|
+
```
|
|
183
|
+
|
|
184
|
+
---
|
|
185
|
+
|
|
186
|
+
## Side Effects
|
|
187
|
+
|
|
188
|
+
```kotlin
|
|
189
|
+
@Composable
|
|
190
|
+
fun FacturasScreen(viewModel: FacturasViewModel = hiltViewModel()) {
|
|
191
|
+
val contexto = LocalContext.current
|
|
192
|
+
|
|
193
|
+
// LaunchedEffect — efecto async ligado al ciclo de vida del composable
|
|
194
|
+
// Se re-ejecuta cuando cambia la key
|
|
195
|
+
LaunchedEffect(Unit) {
|
|
196
|
+
viewModel.events.collect { evento ->
|
|
197
|
+
when (evento) {
|
|
198
|
+
is FacturasEvent.FacturaCreada ->
|
|
199
|
+
Toast.makeText(contexto, "Factura creada", Toast.LENGTH_SHORT).show()
|
|
200
|
+
}
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
// DisposableEffect — para limpiar recursos (listeners, suscripciones)
|
|
205
|
+
val lifecycleOwner = LocalLifecycleOwner.current
|
|
206
|
+
DisposableEffect(lifecycleOwner) {
|
|
207
|
+
val observer = LifecycleEventObserver { _, event ->
|
|
208
|
+
if (event == Lifecycle.Event.ON_RESUME) viewModel.recargar()
|
|
209
|
+
}
|
|
210
|
+
lifecycleOwner.lifecycle.addObserver(observer)
|
|
211
|
+
onDispose { lifecycleOwner.lifecycle.removeObserver(observer) }
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
// SideEffect — sincronizar estado a código no-Compose
|
|
215
|
+
// Se ejecuta en CADA recomposición exitosa
|
|
216
|
+
SideEffect {
|
|
217
|
+
analytics.setPantallaActual("facturas")
|
|
218
|
+
}
|
|
219
|
+
}
|
|
220
|
+
```
|
|
221
|
+
|
|
222
|
+
---
|
|
223
|
+
|
|
224
|
+
Para animaciones (AnimatedVisibility, animateContentSize, updateTransition) y
|
|
225
|
+
optimización de performance con @Stable, ImmutableList y lambdas estables, ver
|
|
226
|
+
[recursos/animaciones-performance.md](recursos/animaciones-performance.md).
|
|
227
|
+
|
|
228
|
+
---
|
|
229
|
+
|
|
230
|
+
## Anti-patrones
|
|
231
|
+
|
|
232
|
+
### Leer State en lambda sin capturar correctamente
|
|
233
|
+
|
|
234
|
+
```kotlin
|
|
235
|
+
// MAL — captura el valor en el momento de creación del composable
|
|
236
|
+
val contador by remember { mutableStateOf(0) }
|
|
237
|
+
Button(onClick = { println(contador) }) // siempre imprime el valor inicial
|
|
238
|
+
|
|
239
|
+
// BIEN — leer el estado dentro de la lambda (se evalúa al click)
|
|
240
|
+
val contadorState = remember { mutableStateOf(0) }
|
|
241
|
+
Button(onClick = { println(contadorState.value) })
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
### Función en lugar de computed state
|
|
245
|
+
|
|
246
|
+
```kotlin
|
|
247
|
+
// MAL — función recalcula y causa recomposición en cada frame
|
|
248
|
+
@Composable
|
|
249
|
+
fun Pantalla(facturas: List<Factura>) {
|
|
250
|
+
val total = facturas.sumOf { it.total } // recalcula siempre
|
|
251
|
+
Text("Total: $total")
|
|
252
|
+
}
|
|
253
|
+
|
|
254
|
+
// BIEN — derivedStateOf o remember con key
|
|
255
|
+
val total by remember(facturas) { derivedStateOf { facturas.sumOf { it.total } } }
|
|
256
|
+
```
|
|
257
|
+
|
|
258
|
+
---
|
|
259
|
+
|
|
260
|
+
## Gotchas / Errores comunes no obvios
|
|
261
|
+
|
|
262
|
+
**`remember { derivedStateOf { ... } }` sin `remember` en la derivación no se recomputa**: `derivedStateOf { lista.filter { it.activo } }` sin envolver en `remember` crea un nuevo objeto `State` en cada recomposición, anulando la optimización. Fix: siempre combinar: `val filtrado by remember { derivedStateOf { lista.filter { it.activo } } }`.
|
|
263
|
+
|
|
264
|
+
**`LazyColumn` sin `key` causa que los composables se recreen en lugar de reordenarse**: al cambiar el orden de los items, Compose reutiliza los composables por posición, no por identidad — los estados internos de cada item (scroll position, animation state) se mezclan. Fix: siempre especificar `key = { item -> item.id }` en `items()` de `LazyColumn`/`LazyRow`.
|
|
265
|
+
|
|
266
|
+
**`SideEffect` se ejecuta en cada recomposición, no una sola vez**: `SideEffect { SystemUiController.setStatusBarColor(color) }` cambia el color en cada recomposición del composable, no solo al montar. Causa: `SideEffect` no tiene ciclo de vida — ejecuta en cada render exitoso. Fix: usar `LaunchedEffect(key)` con una clave estable si el efecto solo debe ejecutarse cuando cambia un valor específico.
|
|
267
|
+
|
|
268
|
+
**`@Stable` en una clase con campos `var` no previene recomposiciones**: marcar una clase como `@Stable` le dice a Compose que las comparaciones de igualdad son confiables, pero si la clase tiene `var` mutables que cambian sin notificar a Compose (no usando `mutableStateOf`), las recomposiciones no se disparan cuando deberían y la UI queda desactualizada. Fix: usar `@Stable` solo en clases verdaderamente inmutables o en clases que usan `State` de Compose para sus campos mutables.
|
|
269
|
+
|
|
270
|
+
## Checklist Compose
|
|
271
|
+
|
|
272
|
+
- [ ] Estado hoisted al nivel mínimo necesario
|
|
273
|
+
- [ ] rememberSaveable para estado que debe sobrevivir rotación
|
|
274
|
+
- [ ] derivedStateOf para computaciones sobre listas o búsquedas
|
|
275
|
+
- [ ] LazyColumn siempre con key = { item.id }
|
|
276
|
+
- [ ] Lambdas en items de LazyColumn con remember para evitar recreación
|
|
277
|
+
- [ ] @Stable o ImmutableList para listas en parámetros de composable
|
|
278
|
+
- [ ] Navigation usa type-safe routes (objetos @Serializable)
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
# Compose — Animaciones y Performance
|
|
2
|
+
|
|
3
|
+
## Animaciones
|
|
4
|
+
|
|
5
|
+
```kotlin
|
|
6
|
+
// AnimatedVisibility — mostrar/ocultar con transición
|
|
7
|
+
AnimatedVisibility(
|
|
8
|
+
visible = mostrarFiltros,
|
|
9
|
+
enter = slideInVertically() + fadeIn(),
|
|
10
|
+
exit = slideOutVertically() + fadeOut()
|
|
11
|
+
) {
|
|
12
|
+
PanelFiltros()
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
// animateContentSize — tamaño cambia suavemente
|
|
16
|
+
Card(
|
|
17
|
+
modifier = Modifier
|
|
18
|
+
.animateContentSize(
|
|
19
|
+
animationSpec = spring(dampingRatio = Spring.DampingRatioMediumBouncy)
|
|
20
|
+
)
|
|
21
|
+
) {
|
|
22
|
+
Column {
|
|
23
|
+
FacturasHeader(expandido = expandido, onClick = { expandido = !expandido })
|
|
24
|
+
if (expandido) FacturasDetalle()
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
// updateTransition — múltiples propiedades animadas juntas
|
|
29
|
+
val transicion = updateTransition(targetState = estado, label = "estado")
|
|
30
|
+
val color by transicion.animateColor(label = "color") { s ->
|
|
31
|
+
when (s) {
|
|
32
|
+
Estado.Activo -> MaterialTheme.colorScheme.primary
|
|
33
|
+
Estado.Inactivo -> MaterialTheme.colorScheme.surfaceVariant
|
|
34
|
+
}
|
|
35
|
+
}
|
|
36
|
+
|
|
37
|
+
// Animate*AsState — animación de valor único
|
|
38
|
+
val opacidad by animateFloatAsState(
|
|
39
|
+
targetValue = if (visible) 1f else 0f,
|
|
40
|
+
animationSpec = tween(durationMillis = 300),
|
|
41
|
+
label = "opacidad"
|
|
42
|
+
)
|
|
43
|
+
```
|
|
44
|
+
|
|
45
|
+
---
|
|
46
|
+
|
|
47
|
+
## Performance — @Stable y Skippability
|
|
48
|
+
|
|
49
|
+
### Reglas de estabilidad
|
|
50
|
+
|
|
51
|
+
Compose puede saltarse la recomposición de un composable solo si todos sus
|
|
52
|
+
parámetros son estables. Un tipo es estable cuando:
|
|
53
|
+
- Es primitivo (Int, Boolean, String, etc.)
|
|
54
|
+
- Es un data class donde todos los campos son estables
|
|
55
|
+
- Está anotado con `@Stable` o `@Immutable`
|
|
56
|
+
|
|
57
|
+
```kotlin
|
|
58
|
+
// @Stable: el compilador confía en que equals() detecta cambios
|
|
59
|
+
@Stable
|
|
60
|
+
class FacturasState(
|
|
61
|
+
val facturas: List<Factura>,
|
|
62
|
+
val cargando: Boolean
|
|
63
|
+
) {
|
|
64
|
+
override fun equals(other: Any?) = other is FacturasState &&
|
|
65
|
+
facturas == other.facturas && cargando == other.cargando
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
// ImmutableList de kotlinx.collections.immutable
|
|
69
|
+
// La List<T> estándar NO es @Stable — causa recomposición innecesaria
|
|
70
|
+
@Composable
|
|
71
|
+
fun FacturasList(facturas: ImmutableList<Factura>) {
|
|
72
|
+
LazyColumn {
|
|
73
|
+
items(facturas, key = { it.id }) { FacturaItem(it) }
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Lambdas estables en items de lista
|
|
78
|
+
// MAL — nueva lambda en cada recomposición del padre
|
|
79
|
+
items(facturas) { factura ->
|
|
80
|
+
FacturaItem(onClic = { viewModel.seleccionar(factura.id) })
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
// BIEN — lambda estable con remember
|
|
84
|
+
items(facturas) { factura ->
|
|
85
|
+
val onClic = remember(factura.id) { { viewModel.seleccionar(factura.id) } }
|
|
86
|
+
FacturaItem(onClic = onClic)
|
|
87
|
+
}
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
### Diagnóstico con Layout Inspector
|
|
91
|
+
|
|
92
|
+
Activar "Recomposition counts" en Layout Inspector de Android Studio para
|
|
93
|
+
identificar composables que recomponen más veces de lo esperado.
|
|
@@ -0,0 +1,318 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: kotlin-experto
|
|
3
|
+
description: >
|
|
4
|
+
Kotlin moderno: coroutines, Flow con stateIn/shareIn, ViewModel con
|
|
5
|
+
SavedStateHandle, Hilt DI, Room con Flow, Ktor server, kotlinx.serialization
|
|
6
|
+
y sealed classes para estados UI. Cargar cuando se implementen features
|
|
7
|
+
Android/Kotlin, ViewModels, repositorios Room, o servicios backend Ktor.
|
|
8
|
+
version: "1.0.0"
|
|
9
|
+
herramientasPermitidas: [Read, Grep]
|
|
10
|
+
exclusiones:
|
|
11
|
+
- "No cargar para implementar pantallas Jetpack Compose — para Compose cargar `kotlin-compose`."
|
|
12
|
+
- "No cargar para escribir tests Kotlin (MockK, Turbine, runTest) — para testing cargar `kotlin-testing`."
|
|
13
|
+
- "No cargar para errores de compilación Kotlin o Gradle — para build errors cargar `build-errors-kotlin`."
|
|
14
|
+
- "No cargar para servicios Java Spring Boot sin Kotlin — Java tiene su propio modelo de concurrencia; cargar `java-experto`."
|
|
15
|
+
evolvable: true # default para skill estandar
|
|
16
|
+
---
|
|
17
|
+
# Kotlin Experto — Coroutines, Flow y Android
|
|
18
|
+
|
|
19
|
+
## Cuándo NO cargar
|
|
20
|
+
|
|
21
|
+
- La tarea es implementar pantallas Jetpack Compose (composables, state hoisting, Navigation Compose) — cargar `kotlin-compose`.
|
|
22
|
+
- La tarea es escribir tests Kotlin con MockK, Turbine o runTest — cargar `kotlin-testing`.
|
|
23
|
+
- Los errores son de compilación Kotlin, Hilt o Gradle — cargar `build-errors-kotlin`.
|
|
24
|
+
- El proyecto es Java Spring Boot sin Kotlin — Java tiene su propio modelo de concurrencia y DI; cargar `java-experto`.
|
|
25
|
+
|
|
26
|
+
## Coroutines — Scopes y Dispatchers
|
|
27
|
+
|
|
28
|
+
```kotlin
|
|
29
|
+
// Regla: usar el dispatcher correcto según el trabajo
|
|
30
|
+
class FacturasViewModel(
|
|
31
|
+
private val repo: IFacturasRepository,
|
|
32
|
+
savedState: SavedStateHandle
|
|
33
|
+
) : ViewModel() {
|
|
34
|
+
|
|
35
|
+
// SavedStateHandle sobrevive a process death
|
|
36
|
+
private val empresaId = savedState.get<String>("empresaId")
|
|
37
|
+
?: throw IllegalArgumentException("empresaId requerido")
|
|
38
|
+
|
|
39
|
+
// StateFlow para UI state — nunca MutableStateFlow público
|
|
40
|
+
private val _uiState = MutableStateFlow<FacturasUiState>(FacturasUiState.Cargando)
|
|
41
|
+
val uiState: StateFlow<FacturasUiState> = _uiState.asStateFlow()
|
|
42
|
+
|
|
43
|
+
init {
|
|
44
|
+
cargarFacturas()
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
private fun cargarFacturas() {
|
|
48
|
+
viewModelScope.launch { // se cancela con el ViewModel
|
|
49
|
+
_uiState.value = FacturasUiState.Cargando
|
|
50
|
+
repo.observarFacturas(empresaId)
|
|
51
|
+
.catch { e ->
|
|
52
|
+
_uiState.value = FacturasUiState.Error(e.message ?: "Error desconocido")
|
|
53
|
+
}
|
|
54
|
+
.collect { facturas ->
|
|
55
|
+
_uiState.value = FacturasUiState.Exito(facturas)
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
// withContext para cambiar dispatcher
|
|
62
|
+
suspend fun cargarDesdeRed(): List<Factura> = withContext(Dispatchers.IO) {
|
|
63
|
+
api.obtenerFacturas() // IO: red, disco
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// async/await para paralelismo
|
|
67
|
+
suspend fun cargarDashboard(): Dashboard = coroutineScope {
|
|
68
|
+
val facturasDeferred = async { api.obtenerFacturas() }
|
|
69
|
+
val clientesDeferred = async { api.obtenerClientes() }
|
|
70
|
+
Dashboard(facturas = facturasDeferred.await(), clientes = clientesDeferred.await())
|
|
71
|
+
}
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
---
|
|
75
|
+
|
|
76
|
+
## Flow — stateIn, shareIn, conflate
|
|
77
|
+
|
|
78
|
+
```kotlin
|
|
79
|
+
class FacturasRepository(private val dao: FacturasDao) : IFacturasRepository {
|
|
80
|
+
|
|
81
|
+
// stateIn: convierte Flow frío en StateFlow caliente
|
|
82
|
+
// SharingStarted.WhileSubscribed(5000): mantiene 5s luego del último suscriptor
|
|
83
|
+
// (sobrevive rotación de pantalla)
|
|
84
|
+
override fun observarFacturas(empresaId: String): StateFlow<List<Factura>> =
|
|
85
|
+
dao.observarPorEmpresa(empresaId)
|
|
86
|
+
.map { entities -> entities.map { it.toDomain() } }
|
|
87
|
+
.stateIn(
|
|
88
|
+
scope = externalScope, // scope del Application, no del ViewModel
|
|
89
|
+
started = SharingStarted.WhileSubscribed(5_000),
|
|
90
|
+
initialValue = emptyList()
|
|
91
|
+
)
|
|
92
|
+
|
|
93
|
+
// conflate: descarta intermedios cuando el consumidor es lento
|
|
94
|
+
fun observarProgreso(): Flow<Int> = producirProgreso().conflate()
|
|
95
|
+
}
|
|
96
|
+
|
|
97
|
+
// Room DAO retorna Flow — se re-emite en cada cambio de BD
|
|
98
|
+
@Dao
|
|
99
|
+
interface FacturasDao {
|
|
100
|
+
@Query("SELECT * FROM facturas WHERE empresa_id = :empresaId ORDER BY fecha DESC")
|
|
101
|
+
fun observarPorEmpresa(empresaId: String): Flow<List<FacturaEntity>>
|
|
102
|
+
|
|
103
|
+
@Insert(onConflict = OnConflictStrategy.REPLACE)
|
|
104
|
+
suspend fun insertar(factura: FacturaEntity)
|
|
105
|
+
}
|
|
106
|
+
```
|
|
107
|
+
|
|
108
|
+
---
|
|
109
|
+
|
|
110
|
+
## Hilt — Módulos e Inyección
|
|
111
|
+
|
|
112
|
+
```kotlin
|
|
113
|
+
// Módulo para dependencias externas / interfaces
|
|
114
|
+
@Module
|
|
115
|
+
@InstallIn(SingletonComponent::class)
|
|
116
|
+
object NetworkModule {
|
|
117
|
+
|
|
118
|
+
@Provides
|
|
119
|
+
@Singleton
|
|
120
|
+
fun provideHttpClient(): HttpClient = HttpClient(Android) {
|
|
121
|
+
install(ContentNegotiation) { json() }
|
|
122
|
+
install(HttpTimeout) { requestTimeoutMillis = 30_000 }
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
@Provides
|
|
126
|
+
@Singleton
|
|
127
|
+
fun provideFacturasApi(client: HttpClient): IFacturasApi =
|
|
128
|
+
FacturasApiImpl(client)
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// Bind: implementación de interfaz en el mismo módulo
|
|
132
|
+
@Module
|
|
133
|
+
@InstallIn(SingletonComponent::class)
|
|
134
|
+
abstract class RepositoryModule {
|
|
135
|
+
@Binds
|
|
136
|
+
@Singleton
|
|
137
|
+
abstract fun bindFacturasRepo(
|
|
138
|
+
impl: FacturasRepositoryImpl
|
|
139
|
+
): IFacturasRepository
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
// ViewModel con Hilt
|
|
143
|
+
@HiltViewModel
|
|
144
|
+
class FacturasViewModel @Inject constructor(
|
|
145
|
+
private val repo: IFacturasRepository,
|
|
146
|
+
savedState: SavedStateHandle
|
|
147
|
+
) : ViewModel()
|
|
148
|
+
```
|
|
149
|
+
|
|
150
|
+
---
|
|
151
|
+
|
|
152
|
+
## Sealed Classes para Estados UI
|
|
153
|
+
|
|
154
|
+
```kotlin
|
|
155
|
+
sealed interface FacturasUiState {
|
|
156
|
+
data object Cargando : FacturasUiState
|
|
157
|
+
data class Exito(val facturas: List<Factura>) : FacturasUiState
|
|
158
|
+
data class Error(val mensaje: String) : FacturasUiState
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
// Uso exhaustivo en Compose — el compilador fuerza todos los casos
|
|
162
|
+
@Composable
|
|
163
|
+
fun FacturasScreen(uiState: FacturasUiState) {
|
|
164
|
+
when (uiState) {
|
|
165
|
+
is FacturasUiState.Cargando -> CircularProgressIndicator()
|
|
166
|
+
is FacturasUiState.Error -> ErrorView(uiState.mensaje)
|
|
167
|
+
is FacturasUiState.Exito -> FacturasList(uiState.facturas)
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
```
|
|
171
|
+
|
|
172
|
+
---
|
|
173
|
+
|
|
174
|
+
## Ktor — Routing y Plugins
|
|
175
|
+
|
|
176
|
+
```kotlin
|
|
177
|
+
fun Application.configurarRouting() {
|
|
178
|
+
install(ContentNegotiation) { json() }
|
|
179
|
+
install(RequestValidation) {
|
|
180
|
+
validate<CrearFacturaRequest> { req ->
|
|
181
|
+
if (req.folio.isBlank()) ValidationResult.Invalid("folio requerido")
|
|
182
|
+
else ValidationResult.Valid
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
install(StatusPages) {
|
|
186
|
+
exception<Throwable> { call, cause ->
|
|
187
|
+
call.respond(HttpStatusCode.InternalServerError,
|
|
188
|
+
mapOf("error" to (cause.message ?: "Error interno")))
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
|
|
192
|
+
routing {
|
|
193
|
+
route("/api/v1") {
|
|
194
|
+
authenticate("jwt") {
|
|
195
|
+
facturaRoutes()
|
|
196
|
+
}
|
|
197
|
+
}
|
|
198
|
+
}
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
fun Route.facturaRoutes() {
|
|
202
|
+
route("/facturas") {
|
|
203
|
+
get {
|
|
204
|
+
val empresaId = call.principal<JWTPrincipal>()!!
|
|
205
|
+
.payload.getClaim("empresa_id").asString()
|
|
206
|
+
val facturas = facturasService.obtenerTodas(empresaId)
|
|
207
|
+
call.respond(facturas)
|
|
208
|
+
}
|
|
209
|
+
post {
|
|
210
|
+
val req = call.receive<CrearFacturaRequest>()
|
|
211
|
+
val factura = facturasService.crear(req)
|
|
212
|
+
call.respond(HttpStatusCode.Created, factura)
|
|
213
|
+
}
|
|
214
|
+
}
|
|
215
|
+
}
|
|
216
|
+
```
|
|
217
|
+
|
|
218
|
+
---
|
|
219
|
+
|
|
220
|
+
## kotlinx.serialization
|
|
221
|
+
|
|
222
|
+
```kotlin
|
|
223
|
+
@Serializable
|
|
224
|
+
data class FacturaDto(
|
|
225
|
+
val id: String,
|
|
226
|
+
val folio: String,
|
|
227
|
+
val total: Double,
|
|
228
|
+
@SerialName("fecha_emision") val fechaEmision: String,
|
|
229
|
+
val estatus: EstatusFactura = EstatusFactura.BORRADOR
|
|
230
|
+
)
|
|
231
|
+
|
|
232
|
+
@Serializable
|
|
233
|
+
enum class EstatusFactura { BORRADOR, EMITIDA, CANCELADA }
|
|
234
|
+
|
|
235
|
+
// Configuración global de JSON
|
|
236
|
+
val jsonConfig = Json {
|
|
237
|
+
ignoreUnknownKeys = true // tolerante a campos extras de API
|
|
238
|
+
isLenient = false
|
|
239
|
+
encodeDefaults = false // no serializar campos con valor default
|
|
240
|
+
prettyPrint = false
|
|
241
|
+
}
|
|
242
|
+
```
|
|
243
|
+
|
|
244
|
+
---
|
|
245
|
+
|
|
246
|
+
## Extension Functions útiles
|
|
247
|
+
|
|
248
|
+
```kotlin
|
|
249
|
+
// Result como alternativa a excepciones
|
|
250
|
+
suspend fun <T> safeApiCall(block: suspend () -> T): Result<T> =
|
|
251
|
+
try { Result.success(block()) }
|
|
252
|
+
catch (e: CancellationException) { throw e } // NUNCA atrapar CancellationException
|
|
253
|
+
catch (e: Exception) { Result.failure(e) }
|
|
254
|
+
|
|
255
|
+
// Flow de paginación
|
|
256
|
+
fun <T> Flow<List<T>>.paginar(pagina: Int, tamanoPagina: Int): Flow<List<T>> =
|
|
257
|
+
map { it.drop(pagina * tamanoPagina).take(tamanoPagina) }
|
|
258
|
+
```
|
|
259
|
+
|
|
260
|
+
---
|
|
261
|
+
|
|
262
|
+
## Anti-patrones
|
|
263
|
+
|
|
264
|
+
### GlobalScope — NUNCA
|
|
265
|
+
|
|
266
|
+
```kotlin
|
|
267
|
+
// MAL — no tiene lifecycle, las coroutines se filtran
|
|
268
|
+
GlobalScope.launch { api.obtenerFacturas() }
|
|
269
|
+
|
|
270
|
+
// BIEN — scope ligado al lifecycle
|
|
271
|
+
viewModelScope.launch { ... } // ViewModel
|
|
272
|
+
lifecycleScope.launch { ... } // Activity/Fragment
|
|
273
|
+
```
|
|
274
|
+
|
|
275
|
+
### Atrapar CancellationException — NUNCA
|
|
276
|
+
|
|
277
|
+
```kotlin
|
|
278
|
+
// MAL — rompe la cooperación de coroutines
|
|
279
|
+
try { ... } catch (e: Exception) { /* atrapa CancellationException */ }
|
|
280
|
+
|
|
281
|
+
// BIEN — re-lanzar siempre
|
|
282
|
+
try { ... } catch (e: Exception) {
|
|
283
|
+
if (e is CancellationException) throw e
|
|
284
|
+
// manejar otros errores
|
|
285
|
+
}
|
|
286
|
+
```
|
|
287
|
+
|
|
288
|
+
### MutableStateFlow público — NUNCA
|
|
289
|
+
|
|
290
|
+
```kotlin
|
|
291
|
+
// MAL — UI puede modificar el estado desde afuera
|
|
292
|
+
val uiState = MutableStateFlow<Estado>(Estado.Cargando)
|
|
293
|
+
|
|
294
|
+
// BIEN — privado con backing property
|
|
295
|
+
private val _uiState = MutableStateFlow<Estado>(Estado.Cargando)
|
|
296
|
+
val uiState: StateFlow<Estado> = _uiState.asStateFlow()
|
|
297
|
+
```
|
|
298
|
+
|
|
299
|
+
---
|
|
300
|
+
|
|
301
|
+
## Gotchas / Errores comunes no obvios
|
|
302
|
+
|
|
303
|
+
**`CancellationException` atrapada en un `catch(e: Exception)` rompe la cooperación de coroutines**: un bloque `catch (e: Exception) { /* ignorar */ }` captura `CancellationException` porque es subclase de `Exception`, lo que impide que la coroutine cancele correctamente. La coroutine sigue ejecutando después de que su scope fue cancelado. Fix: siempre hacer `catch (e: CancellationException) { throw e }` o usar `catch (e: Exception) { if (e is CancellationException) throw e }`.
|
|
304
|
+
|
|
305
|
+
**`stateIn(scope, SharingStarted.Eagerly, ...)` inicia el Flow aunque no haya suscriptores y consume recursos en background**: usar `Eagerly` en un ViewModel que se crea pero no se observa inmediatamente mantiene el Flow activo y puede generar requests innecesarios a la BD. Fix: usar `SharingStarted.WhileSubscribed(5000)` para activar el Flow solo cuando hay colectores activos, con 5 segundos de gracia para rotaciones de pantalla.
|
|
306
|
+
|
|
307
|
+
**`viewModelScope.launch` sobrevive a la destrucción del ViewModel si usa `withContext(Dispatchers.IO)`**: el coroutine scope del ViewModel se cancela en `onCleared()`, pero si hay trabajo en `Dispatchers.IO` en progreso, ese trabajo puede seguir ejecutando hasta que termine porque `Dispatchers.IO` no respeta automáticamente la cancelación del scope si el código no verifica `isActive`. Fix: asegurar que el código I/O usa operaciones cancelables o verifica `coroutineContext.isActive` periódicamente en loops.
|
|
308
|
+
|
|
309
|
+
**`@HiltViewModel` con parámetros que no son `SavedStateHandle` rompe la inyección en runtime**: si se agrega un parámetro no provisto por Hilt al constructor del ViewModel (como un ID de entidad), Hilt no puede crear el ViewModel y lanza `RuntimeException` al navegar a la pantalla. Fix: pasar datos iniciales siempre a través de `SavedStateHandle` desde los argumentos de navegación, nunca como constructor directo del ViewModel.
|
|
310
|
+
|
|
311
|
+
## Checklist
|
|
312
|
+
|
|
313
|
+
- [ ] No hay GlobalScope.launch en código de producción
|
|
314
|
+
- [ ] CancellationException siempre se re-lanza
|
|
315
|
+
- [ ] MutableStateFlow siempre privado con backing property
|
|
316
|
+
- [ ] Room DAO retorna Flow para observabilidad reactiva
|
|
317
|
+
- [ ] stateIn usa WhileSubscribed(5000) para sobrevivir rotaciones
|
|
318
|
+
- [ ] Hilt: repositorios en SingletonComponent, ViewModels con @HiltViewModel
|