@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,110 @@
|
|
|
1
|
+
# Swift — Keychain y Property Wrappers
|
|
2
|
+
|
|
3
|
+
## Keychain Wrapper Completo
|
|
4
|
+
|
|
5
|
+
```swift
|
|
6
|
+
// Wrapper type-safe para Keychain — NUNCA UserDefaults para tokens
|
|
7
|
+
struct KeychainStorage {
|
|
8
|
+
enum Key: String {
|
|
9
|
+
case authToken = "com.miapp.authToken"
|
|
10
|
+
case refreshToken = "com.miapp.refreshToken"
|
|
11
|
+
}
|
|
12
|
+
|
|
13
|
+
enum KeychainError: Error {
|
|
14
|
+
case guardadoFallido(OSStatus)
|
|
15
|
+
case noEncontrado
|
|
16
|
+
case datosInvalidos
|
|
17
|
+
}
|
|
18
|
+
|
|
19
|
+
static func guardar(_ valor: String, para clave: Key) throws {
|
|
20
|
+
let data = Data(valor.utf8)
|
|
21
|
+
let query: [CFString: Any] = [
|
|
22
|
+
kSecClass: kSecClassGenericPassword,
|
|
23
|
+
kSecAttrAccount: clave.rawValue,
|
|
24
|
+
kSecValueData: data,
|
|
25
|
+
kSecAttrAccessible: kSecAttrAccessibleWhenUnlockedThisDeviceOnly
|
|
26
|
+
]
|
|
27
|
+
SecItemDelete(query as CFDictionary)
|
|
28
|
+
let status = SecItemAdd(query as CFDictionary, nil)
|
|
29
|
+
guard status == errSecSuccess else {
|
|
30
|
+
throw KeychainError.guardadoFallido(status)
|
|
31
|
+
}
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
static func leer(_ clave: Key) throws -> String {
|
|
35
|
+
let query: [CFString: Any] = [
|
|
36
|
+
kSecClass: kSecClassGenericPassword,
|
|
37
|
+
kSecAttrAccount: clave.rawValue,
|
|
38
|
+
kSecReturnData: true,
|
|
39
|
+
kSecMatchLimit: kSecMatchLimitOne
|
|
40
|
+
]
|
|
41
|
+
var item: CFTypeRef?
|
|
42
|
+
let status = SecItemCopyMatching(query as CFDictionary, &item)
|
|
43
|
+
guard status == errSecSuccess,
|
|
44
|
+
let data = item as? Data,
|
|
45
|
+
let valor = String(data: data, encoding: .utf8) else {
|
|
46
|
+
throw KeychainError.noEncontrado
|
|
47
|
+
}
|
|
48
|
+
return valor
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
static func eliminar(_ clave: Key) {
|
|
52
|
+
let query: [CFString: Any] = [
|
|
53
|
+
kSecClass: kSecClassGenericPassword,
|
|
54
|
+
kSecAttrAccount: clave.rawValue
|
|
55
|
+
]
|
|
56
|
+
SecItemDelete(query as CFDictionary)
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
---
|
|
62
|
+
|
|
63
|
+
## Property Wrappers
|
|
64
|
+
|
|
65
|
+
### @AppStorage
|
|
66
|
+
|
|
67
|
+
```swift
|
|
68
|
+
// Solo para preferencias de UI — NUNCA para tokens o datos sensibles
|
|
69
|
+
@AppStorage("tema_oscuro") private var temaOscuro = false
|
|
70
|
+
@AppStorage("idioma") private var idioma = "es"
|
|
71
|
+
@AppStorage("notificaciones_activas") private var notificaciones = true
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
### Property Wrapper personalizado
|
|
75
|
+
|
|
76
|
+
```swift
|
|
77
|
+
// @Clamped — limita un valor a un rango
|
|
78
|
+
@propertyWrapper
|
|
79
|
+
struct Clamped<T: Comparable> {
|
|
80
|
+
private var valor: T
|
|
81
|
+
let rango: ClosedRange<T>
|
|
82
|
+
|
|
83
|
+
init(wrappedValue: T, _ rango: ClosedRange<T>) {
|
|
84
|
+
self.rango = rango
|
|
85
|
+
self.valor = min(max(wrappedValue, rango.lowerBound), rango.upperBound)
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
var wrappedValue: T {
|
|
89
|
+
get { valor }
|
|
90
|
+
set { valor = min(max(newValue, rango.lowerBound), rango.upperBound) }
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// @Trimmed — elimina espacios automáticamente
|
|
95
|
+
@propertyWrapper
|
|
96
|
+
struct Trimmed {
|
|
97
|
+
private var valor: String = ""
|
|
98
|
+
var wrappedValue: String {
|
|
99
|
+
get { valor }
|
|
100
|
+
set { valor = newValue.trimmingCharacters(in: .whitespaces) }
|
|
101
|
+
}
|
|
102
|
+
init(wrappedValue: String) { self.wrappedValue = wrappedValue }
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
// Uso combinado
|
|
106
|
+
struct Configuracion {
|
|
107
|
+
@Clamped(1...100) var tamanoPagina = 20
|
|
108
|
+
@Trimmed var nombreEmpresa = ""
|
|
109
|
+
}
|
|
110
|
+
```
|
|
@@ -0,0 +1,313 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: swift-patrones
|
|
3
|
+
description: >
|
|
4
|
+
Patrones Swift: TCA (The Composable Architecture), dependency injection
|
|
5
|
+
basada en protocolos, MVVM con @Observable, Coordinator, Repository pattern,
|
|
6
|
+
Codable avanzado, opaque types y generic constraints. Cargar cuando se diseñe
|
|
7
|
+
arquitectura iOS/macOS, se implemente TCA/MVVM, o se estructuren dependencias.
|
|
8
|
+
version: "1.0.0"
|
|
9
|
+
herramientasPermitidas: [Read, Glob, Grep]
|
|
10
|
+
exclusiones:
|
|
11
|
+
- "No cargar para implementar vistas SwiftUI concretas, async/await o SwiftData — para implementación cargar `swift-experto`."
|
|
12
|
+
- "No cargar para escribir tests Swift — para testing cargar `swift-testing`."
|
|
13
|
+
- "No cargar para errores de compilación Swift o Xcode — para build errors cargar `build-errors-swift`."
|
|
14
|
+
- "No cargar para patrones Android/Kotlin (MVI, ViewModel) — TCA y MVVM con @Observable son patrones específicos de Apple platforms."
|
|
15
|
+
evolvable: true # default para skill estandar
|
|
16
|
+
---
|
|
17
|
+
# Swift Patrones — TCA, DI, MVVM y Codable
|
|
18
|
+
|
|
19
|
+
## Cuándo NO cargar
|
|
20
|
+
|
|
21
|
+
- La pregunta es sobre implementar vistas SwiftUI, async/await concreto o SwiftData — para implementación cargar `swift-experto`.
|
|
22
|
+
- La tarea es escribir tests Swift — cargar `swift-testing`.
|
|
23
|
+
- Los errores son de compilación Swift o Xcode — cargar `build-errors-swift`.
|
|
24
|
+
- El contexto es Android/Kotlin — TCA y MVVM con @Observable son específicos de Apple platforms.
|
|
25
|
+
|
|
26
|
+
## TCA — The Composable Architecture
|
|
27
|
+
|
|
28
|
+
TCA organiza cada feature en: `State` (datos), `Action` (eventos), `Reducer` (lógica pura)
|
|
29
|
+
y dependencias inyectadas via `DependencyValues`. El State es inmutable; el Reducer retorna
|
|
30
|
+
el nuevo estado y `Effect` para operaciones async.
|
|
31
|
+
|
|
32
|
+
Para el ejemplo completo con Reducer, View y tests de Reducer, ver
|
|
33
|
+
[recursos/tca-ejemplo-completo.md](recursos/tca-ejemplo-completo.md).
|
|
34
|
+
|
|
35
|
+
```swift
|
|
36
|
+
// Patrón mínimo
|
|
37
|
+
struct MiFeature: Reducer {
|
|
38
|
+
@ObservableState struct State: Equatable { var datos: [Item] = [] }
|
|
39
|
+
enum Action { case cargar; case respuesta([Item]) }
|
|
40
|
+
@Dependency(\.miClient) var client
|
|
41
|
+
|
|
42
|
+
var body: some ReducerOf<Self> {
|
|
43
|
+
Reduce { state, action in
|
|
44
|
+
switch action {
|
|
45
|
+
case .cargar:
|
|
46
|
+
return .run { send in await send(.respuesta(try await client.obtener())) }
|
|
47
|
+
case .respuesta(let items):
|
|
48
|
+
state.datos = items; return .none
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
---
|
|
56
|
+
|
|
57
|
+
## Dependency Injection Basada en Protocolos
|
|
58
|
+
|
|
59
|
+
```swift
|
|
60
|
+
// Protocolo de la dependencia
|
|
61
|
+
protocol IFacturasRepository: Sendable {
|
|
62
|
+
func obtener(empresaId: String) async throws -> [Factura]
|
|
63
|
+
func guardar(_ factura: Factura) async throws
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
// Implementación real
|
|
67
|
+
final class FacturasRepositoryImpl: IFacturasRepository {
|
|
68
|
+
private let api: IFacturasApi
|
|
69
|
+
private let cache: CachFacturas
|
|
70
|
+
|
|
71
|
+
init(api: IFacturasApi, cache: CachFacturas) {
|
|
72
|
+
self.api = api
|
|
73
|
+
self.cache = cache
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
func obtener(empresaId: String) async throws -> [Factura] {
|
|
77
|
+
if let cached = await cache.obtener(empresaId: empresaId) { return cached }
|
|
78
|
+
let facturas = try await api.obtenerFacturas(empresaId: empresaId)
|
|
79
|
+
await cache.guardar(facturas, empresaId: empresaId)
|
|
80
|
+
return facturas
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// TCA DependencyValues — registro de dependencias
|
|
85
|
+
struct FacturasClient {
|
|
86
|
+
var obtener: @Sendable () async throws -> [Factura]
|
|
87
|
+
var eliminar: @Sendable (String) async throws -> Void
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
extension FacturasClient: DependencyKey {
|
|
91
|
+
static let liveValue = FacturasClient(
|
|
92
|
+
obtener: { try await FacturasRepositoryImpl.shared.obtener(empresaId: currentUser.empresaId) },
|
|
93
|
+
eliminar: { id in try await FacturasRepositoryImpl.shared.eliminar(id: id) }
|
|
94
|
+
)
|
|
95
|
+
|
|
96
|
+
static let testValue = FacturasClient(
|
|
97
|
+
obtener: { [] },
|
|
98
|
+
eliminar: { _ in }
|
|
99
|
+
)
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
extension DependencyValues {
|
|
103
|
+
var facturasClient: FacturasClient {
|
|
104
|
+
get { self[FacturasClient.self] }
|
|
105
|
+
set { self[FacturasClient.self] = newValue }
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
---
|
|
111
|
+
|
|
112
|
+
## MVVM con @Observable
|
|
113
|
+
|
|
114
|
+
```swift
|
|
115
|
+
// @Observable elimina @Published y reduce boilerplate vs ObservableObject
|
|
116
|
+
@Observable
|
|
117
|
+
final class DetalleFacturaViewModel {
|
|
118
|
+
var factura: Factura?
|
|
119
|
+
var cargando = false
|
|
120
|
+
var error: String?
|
|
121
|
+
|
|
122
|
+
// Dependencia inyectada en init (no instanciada internamente)
|
|
123
|
+
private let repository: IFacturasRepository
|
|
124
|
+
|
|
125
|
+
init(repository: IFacturasRepository = FacturasRepositoryImpl.shared) {
|
|
126
|
+
self.repository = repository
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
func cargar(id: String) async {
|
|
130
|
+
cargando = true
|
|
131
|
+
defer { cargando = false }
|
|
132
|
+
do {
|
|
133
|
+
factura = try await repository.obtenerPorId(id)
|
|
134
|
+
} catch {
|
|
135
|
+
self.error = error.localizedDescription
|
|
136
|
+
}
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// Vista recibe el ViewModel — no lo instancia
|
|
141
|
+
struct DetalleFacturaView: View {
|
|
142
|
+
@State private var viewModel: DetalleFacturaViewModel
|
|
143
|
+
|
|
144
|
+
init(facturaId: String, repository: IFacturasRepository = FacturasRepositoryImpl.shared) {
|
|
145
|
+
_viewModel = State(wrappedValue: DetalleFacturaViewModel(repository: repository))
|
|
146
|
+
self.facturaId = facturaId
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
```
|
|
150
|
+
|
|
151
|
+
---
|
|
152
|
+
|
|
153
|
+
## Codable Avanzado
|
|
154
|
+
|
|
155
|
+
```swift
|
|
156
|
+
// CodingKeys con nombres distintos al modelo
|
|
157
|
+
struct FacturaDto: Codable {
|
|
158
|
+
let id: String
|
|
159
|
+
let folio: String
|
|
160
|
+
let totalNeto: Double
|
|
161
|
+
let fechaEmision: Date
|
|
162
|
+
|
|
163
|
+
enum CodingKeys: String, CodingKey {
|
|
164
|
+
case id
|
|
165
|
+
case folio
|
|
166
|
+
case totalNeto = "total_neto"
|
|
167
|
+
case fechaEmision = "fecha_emision"
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
// Decodificación anidada — extraer dato de estructura profunda
|
|
172
|
+
struct RespuestaApi: Decodable {
|
|
173
|
+
let factura: FacturaDto
|
|
174
|
+
|
|
175
|
+
init(from decoder: Decoder) throws {
|
|
176
|
+
let root = try decoder.container(keyedBy: RootKeys.self)
|
|
177
|
+
let data = try root.nestedContainer(keyedBy: DataKeys.self, forKey: .data)
|
|
178
|
+
factura = try data.decode(FacturaDto.self, forKey: .factura)
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
enum RootKeys: String, CodingKey { case data }
|
|
182
|
+
enum DataKeys: String, CodingKey { case factura }
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
// Enum con valor asociado codificable
|
|
186
|
+
enum EstadoPago: Codable {
|
|
187
|
+
case pendiente
|
|
188
|
+
case pagado(fecha: Date, referencia: String)
|
|
189
|
+
|
|
190
|
+
enum CodingKeys: String, CodingKey { case tipo, fecha, referencia }
|
|
191
|
+
|
|
192
|
+
init(from decoder: Decoder) throws {
|
|
193
|
+
let c = try decoder.container(keyedBy: CodingKeys.self)
|
|
194
|
+
let tipo = try c.decode(String.self, forKey: .tipo)
|
|
195
|
+
switch tipo {
|
|
196
|
+
case "pendiente": self = .pendiente
|
|
197
|
+
case "pagado":
|
|
198
|
+
self = .pagado(
|
|
199
|
+
fecha: try c.decode(Date.self, forKey: .fecha),
|
|
200
|
+
referencia: try c.decode(String.self, forKey: .referencia)
|
|
201
|
+
)
|
|
202
|
+
default: throw DecodingError.dataCorrupted(
|
|
203
|
+
.init(codingPath: decoder.codingPath, debugDescription: "Tipo desconocido: \(tipo)"))
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
}
|
|
207
|
+
```
|
|
208
|
+
|
|
209
|
+
---
|
|
210
|
+
|
|
211
|
+
## Opaque Types y Generic Constraints
|
|
212
|
+
|
|
213
|
+
```swift
|
|
214
|
+
// some Protocol — tipo concreto que el compilador infiere, no el llamador
|
|
215
|
+
// Útil para ocultar tipos concretos en implementaciones de repositorio
|
|
216
|
+
protocol IVistaFactura: View {}
|
|
217
|
+
|
|
218
|
+
// Retornar some View en lugar de AnyView (más eficiente)
|
|
219
|
+
func iconoEstatus(_ estatus: EstatusFactura) -> some View {
|
|
220
|
+
Group {
|
|
221
|
+
switch estatus {
|
|
222
|
+
case .borrador: Image(systemName: "doc")
|
|
223
|
+
case .emitida: Image(systemName: "checkmark.circle").foregroundStyle(.green)
|
|
224
|
+
case .cancelada: Image(systemName: "xmark.circle").foregroundStyle(.red)
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
}
|
|
228
|
+
|
|
229
|
+
// Generic constraints — solo aceptar Codable + Identifiable
|
|
230
|
+
func guardarEnCache<T: Codable & Identifiable>(_ items: [T], clave: String)
|
|
231
|
+
where T.ID == String
|
|
232
|
+
{
|
|
233
|
+
// ...
|
|
234
|
+
}
|
|
235
|
+
```
|
|
236
|
+
|
|
237
|
+
---
|
|
238
|
+
|
|
239
|
+
## Result Type
|
|
240
|
+
|
|
241
|
+
```swift
|
|
242
|
+
// Preferir Result<T, E> sobre throws cuando el error se procesa en el mismo sitio
|
|
243
|
+
enum FacturasError: Error, LocalizedError {
|
|
244
|
+
case noEncontrada(id: String)
|
|
245
|
+
case folioDuplicado
|
|
246
|
+
case redNoDisponible(Error)
|
|
247
|
+
|
|
248
|
+
var errorDescription: String? {
|
|
249
|
+
switch self {
|
|
250
|
+
case .noEncontrada(let id): "Factura \(id) no encontrada"
|
|
251
|
+
case .folioDuplicado: "Ya existe una factura con ese folio"
|
|
252
|
+
case .redNoDisponible: "Sin conexión a internet"
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
|
|
257
|
+
// Mapear throws a Result para UI
|
|
258
|
+
func cargarConResult(id: String) async -> Result<Factura, FacturasError> {
|
|
259
|
+
do {
|
|
260
|
+
let factura = try await repo.obtener(id: id)
|
|
261
|
+
return .success(factura)
|
|
262
|
+
} catch let e as FacturasError {
|
|
263
|
+
return .failure(e)
|
|
264
|
+
} catch {
|
|
265
|
+
return .failure(.redNoDisponible(error))
|
|
266
|
+
}
|
|
267
|
+
}
|
|
268
|
+
```
|
|
269
|
+
|
|
270
|
+
---
|
|
271
|
+
|
|
272
|
+
## Anti-patrones
|
|
273
|
+
|
|
274
|
+
### Herencia profunda — evitar
|
|
275
|
+
|
|
276
|
+
```swift
|
|
277
|
+
// MAL — herencia fuerza acoplamiento
|
|
278
|
+
class BaseViewModel { ... }
|
|
279
|
+
class FacturasViewModel: BaseViewModel { ... }
|
|
280
|
+
class PaginatedFacturasViewModel: FacturasViewModel { ... }
|
|
281
|
+
|
|
282
|
+
// BIEN — composición con protocolos
|
|
283
|
+
protocol Paginable { var pagina: Int { get set } }
|
|
284
|
+
@Observable class FacturasViewModel: Paginable { ... }
|
|
285
|
+
```
|
|
286
|
+
|
|
287
|
+
### AnyPublisher en todos lados — evitar en código nuevo
|
|
288
|
+
|
|
289
|
+
```swift
|
|
290
|
+
// Para código nuevo en iOS 17+, usar async/await y @Observable
|
|
291
|
+
// AnyPublisher solo cuando se integra con código Combine existente
|
|
292
|
+
```
|
|
293
|
+
|
|
294
|
+
---
|
|
295
|
+
|
|
296
|
+
## Gotchas / Errores comunes no obvios
|
|
297
|
+
|
|
298
|
+
**TCA `Store.scope` con un `Reducer` hijo que tiene efectos no cancela esos efectos cuando el padre lo descarta**: si la vista padre descarta la vista hija (que tiene un `Store` scoped), los efectos async del reducer hijo siguen ejecutando. Causa: TCA no cancela los efectos automáticamente al destruir un `Store` scoped a menos que se use `Reducer.ifLet`. Fix: usar `Reducer.ifLet` en el reducer padre para modelar la presencia opcional del estado hijo y garantizar cancelación automática.
|
|
299
|
+
|
|
300
|
+
**Protocolo con `associated type` no puede usarse como `any MiProtocolo` hasta Swift 5.7+ con `some`/`any`**: en Swift < 5.7, intentar usar un protocolo con `associatedtype` como tipo de variable generaba error "can only be used as a generic constraint". En Swift 5.7+ se usa `any MiProtocolo` explícitamente. Fix: verificar la versión de Swift del target; en proyectos que soportan iOS < 16, puede ser necesario usar type erasure manual (`AnyMiProtocolo`) para código que debe correr en versiones anteriores.
|
|
301
|
+
|
|
302
|
+
**`Codable` con `enum` que tiene casos asociados requiere implementación manual**: `enum Resultado { case exito(Producto); case error(String) }` no se puede hacer `Codable` automáticamente — el compilador no genera la implementación para enums con valores asociados. Fix: implementar `init(from: Decoder)` y `encode(to: Encoder)` manualmente, o usar una librería como `CodableKit` para el boilerplate.
|
|
303
|
+
|
|
304
|
+
**MVVM con `@Observable` y propiedades computadas no notifica a la vista cuando las dependencias cambian**: si el `@Observable` ViewModel tiene una propiedad computada `var totalFormateado: String { formatear(total) }`, SwiftUI no trackea la propiedad computada directamente. Causa: `@Observable` trackea propiedades stored, no computed. Fix: convertir la propiedad computada a un stored property que se actualiza en el `didSet` del valor origen, o usar `onChange` en la vista para forzar la actualización.
|
|
305
|
+
|
|
306
|
+
## Checklist de arquitectura Swift
|
|
307
|
+
|
|
308
|
+
- [ ] Dependencias inyectadas en init, no instanciadas internamente
|
|
309
|
+
- [ ] Protocolos definidos en el módulo que los consume, no en el que los implementa
|
|
310
|
+
- [ ] @Observable en lugar de ObservableObject para código iOS 17+
|
|
311
|
+
- [ ] Codable usa CodingKeys explícitas cuando los nombres de API difieren del modelo
|
|
312
|
+
- [ ] Result<T, E> con tipos de error específicos (no Error genérico)
|
|
313
|
+
- [ ] Sin herencia más profunda que 1 nivel — preferir composición con protocolos
|
|
@@ -0,0 +1,113 @@
|
|
|
1
|
+
# TCA — Ejemplo Completo con FacturasFeature
|
|
2
|
+
|
|
3
|
+
## Reducer, State, Action y View
|
|
4
|
+
|
|
5
|
+
```swift
|
|
6
|
+
// PointFree/swift-composable-architecture
|
|
7
|
+
import ComposableArchitecture
|
|
8
|
+
|
|
9
|
+
struct FacturasFeature: Reducer {
|
|
10
|
+
|
|
11
|
+
@ObservableState
|
|
12
|
+
struct State: Equatable {
|
|
13
|
+
var facturas: [Factura] = []
|
|
14
|
+
var cargando = false
|
|
15
|
+
var error: String? = nil
|
|
16
|
+
var query: String = ""
|
|
17
|
+
|
|
18
|
+
var facturasFiltradas: [Factura] {
|
|
19
|
+
query.isEmpty ? facturas
|
|
20
|
+
: facturas.filter { $0.folio.localizedCaseInsensitiveContains(query) }
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
enum Action {
|
|
25
|
+
case cargarFacturas
|
|
26
|
+
case facturasResponse(Result<[Factura], Error>)
|
|
27
|
+
case queryChanged(String)
|
|
28
|
+
case facturaEliminada(id: String)
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
@Dependency(\.facturasClient) var facturasClient
|
|
32
|
+
|
|
33
|
+
var body: some ReducerOf<Self> {
|
|
34
|
+
Reduce { state, action in
|
|
35
|
+
switch action {
|
|
36
|
+
case .cargarFacturas:
|
|
37
|
+
state.cargando = true
|
|
38
|
+
state.error = nil
|
|
39
|
+
return .run { send in
|
|
40
|
+
await send(.facturasResponse(
|
|
41
|
+
Result { try await facturasClient.obtener() }
|
|
42
|
+
))
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
case .facturasResponse(.success(let facturas)):
|
|
46
|
+
state.cargando = false
|
|
47
|
+
state.facturas = facturas
|
|
48
|
+
return .none
|
|
49
|
+
|
|
50
|
+
case .facturasResponse(.failure(let error)):
|
|
51
|
+
state.cargando = false
|
|
52
|
+
state.error = error.localizedDescription
|
|
53
|
+
return .none
|
|
54
|
+
|
|
55
|
+
case .queryChanged(let q):
|
|
56
|
+
state.query = q
|
|
57
|
+
return .none
|
|
58
|
+
|
|
59
|
+
case .facturaEliminada(let id):
|
|
60
|
+
state.facturas.removeAll { $0.id == id }
|
|
61
|
+
return .run { _ in
|
|
62
|
+
try await facturasClient.eliminar(id)
|
|
63
|
+
}
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
struct FacturasView: View {
|
|
70
|
+
let store: StoreOf<FacturasFeature>
|
|
71
|
+
|
|
72
|
+
var body: some View {
|
|
73
|
+
WithViewStore(store, observe: { $0 }) { viewStore in
|
|
74
|
+
List(viewStore.facturasFiltradas) { factura in
|
|
75
|
+
FacturaRow(factura: factura)
|
|
76
|
+
.swipeActions {
|
|
77
|
+
Button(role: .destructive) {
|
|
78
|
+
viewStore.send(.facturaEliminada(id: factura.id))
|
|
79
|
+
} label: { Label("Eliminar", systemImage: "trash") }
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
.searchable(
|
|
83
|
+
text: viewStore.binding(
|
|
84
|
+
get: \.query,
|
|
85
|
+
send: FacturasFeature.Action.queryChanged
|
|
86
|
+
)
|
|
87
|
+
)
|
|
88
|
+
.task { viewStore.send(.cargarFacturas) }
|
|
89
|
+
}
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
```
|
|
93
|
+
|
|
94
|
+
## Testing de Reducer TCA
|
|
95
|
+
|
|
96
|
+
```swift
|
|
97
|
+
@Test("Reducer: cargarFacturas transiciona a exito")
|
|
98
|
+
func reducerCargaExitosa() async {
|
|
99
|
+
let store = TestStore(initialState: FacturasFeature.State()) {
|
|
100
|
+
FacturasFeature()
|
|
101
|
+
} withDependencies: {
|
|
102
|
+
$0.facturasClient.obtener = { [Factura(id: "1", folio: "F-001")] }
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
await store.send(.cargarFacturas) { state in
|
|
106
|
+
state.cargando = true
|
|
107
|
+
}
|
|
108
|
+
await store.receive(.facturasResponse(.success([...]))) { state in
|
|
109
|
+
state.cargando = false
|
|
110
|
+
state.facturas = [Factura(id: "1", folio: "F-001")]
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
```
|