@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,550 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: stripe-pagos
|
|
3
|
+
description: >
|
|
4
|
+
Integración de Stripe: Payment Intents, Checkout Sessions, suscripciones,
|
|
5
|
+
webhooks con verificación de firma, idempotency keys y manejo de eventos
|
|
6
|
+
async. Cargar cuando se implemente cualquier flujo de pago con Stripe:
|
|
7
|
+
cobros únicos, suscripciones recurrentes, reembolsos, o configuración
|
|
8
|
+
de webhooks.
|
|
9
|
+
version: "1.0.0"
|
|
10
|
+
herramientasPermitidas: [Read, Bash]
|
|
11
|
+
evolvable: true # default para skill estandar
|
|
12
|
+
exclusiones:
|
|
13
|
+
- "No cargar para integrar otros procesadores de pago (Conekta, Mercado Pago, PayPal, Adyen) — este skill es exclusivo de la API de Stripe; para otros procesadores cargar o crear el skill correspondiente."
|
|
14
|
+
- "No cargar para análisis de fraude o reglas de detección en Stripe Radar — para configurar Radar y reglas de fraude usar la documentación de Stripe Radar directamente."
|
|
15
|
+
- "No cargar para facturación electrónica fiscal (CFDI en México, facturas SAT) — la factura Stripe no es equivalente al CFDI; para CFDI cargar el skill de facturación fiscal correspondiente."
|
|
16
|
+
- "No cargar para implementar criptomonedas o pagos en cripto con Stripe Crypto — este skill cubre flujos de pago fiat; para cripto consultar la documentación de Stripe Crypto directamente."
|
|
17
|
+
---
|
|
18
|
+
# Stripe Pagos — Guía de Integración Completa
|
|
19
|
+
|
|
20
|
+
## Cuándo NO cargar
|
|
21
|
+
|
|
22
|
+
- La tarea es integrar Conekta, Mercado Pago, PayPal o Adyen: cargar el skill del procesador correspondiente.
|
|
23
|
+
- La tarea es facturación electrónica fiscal CFDI (SAT México): la factura Stripe no es CFDI; cargar el skill de facturación fiscal.
|
|
24
|
+
- La tarea es configurar reglas de fraude en Stripe Radar: usar la documentación de Stripe Radar directamente.
|
|
25
|
+
- La tarea es pagos en criptomonedas: usar la documentación de Stripe Crypto directamente.
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
Stripe es el procesador de pagos más completo disponible. Esta guía cubre
|
|
29
|
+
los patrones de implementación correctos para cada flujo de pago, con énfasis
|
|
30
|
+
en seguridad, idempotencia y manejo correcto del ciclo de vida de los pagos.
|
|
31
|
+
|
|
32
|
+
---
|
|
33
|
+
|
|
34
|
+
## 1. Flujos de pago disponibles y cuándo usar cada uno
|
|
35
|
+
|
|
36
|
+
```
|
|
37
|
+
Payment Intents (API directa) → Control máximo, SPA, móvil nativo
|
|
38
|
+
Checkout Session → Página de pago hosted por Stripe (más simple, más seguro)
|
|
39
|
+
PaymentElement → UI embedding en tu app, Stripe maneja seguridad
|
|
40
|
+
Subscriptions + Price → Cobros recurrentes (mensual, anual, uso medido)
|
|
41
|
+
Connect → Marketplace: pagos entre plataforma y vendedores
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
### Matriz de decisión detallada
|
|
45
|
+
|
|
46
|
+
| Criterio | Checkout Session | Payment Intents | Subscriptions |
|
|
47
|
+
|----------|-----------------|-----------------|---------------|
|
|
48
|
+
| Alcance PCI | SAQ-A (mínimo) | SAQ-A-EP | SAQ-A-EP |
|
|
49
|
+
| Control de UI | Ninguno (Stripe UI) | Total | Total |
|
|
50
|
+
| Apple/Google Pay | Automático | Manual | Manual |
|
|
51
|
+
| Cobros recurrentes | No | No (usar Subscriptions) | Sí |
|
|
52
|
+
| Plataformas móviles | Redirección | Nativo (SDK) | Nativo (SDK) |
|
|
53
|
+
| Implementación | Muy simple | Moderada | Moderada-compleja |
|
|
54
|
+
|
|
55
|
+
**Regla**: usar Checkout Session por defecto. Cambiar a Payment Intents solo si
|
|
56
|
+
se requiere control total del UI o la integración es móvil nativa.
|
|
57
|
+
|
|
58
|
+
---
|
|
59
|
+
|
|
60
|
+
## 2. Webhook handler seguro (CRÍTICO)
|
|
61
|
+
|
|
62
|
+
El webhook es la fuente de verdad de todos los eventos de pago. La verificación
|
|
63
|
+
de firma es OBLIGATORIA — sin ella cualquiera puede enviar eventos falsos.
|
|
64
|
+
|
|
65
|
+
```python
|
|
66
|
+
# routes/webhooks.py
|
|
67
|
+
import stripe
|
|
68
|
+
from fastapi import APIRouter, BackgroundTasks, HTTPException, Request
|
|
69
|
+
from app.core.config import settings
|
|
70
|
+
from app.services.pagos import PagosService
|
|
71
|
+
|
|
72
|
+
router = APIRouter()
|
|
73
|
+
stripe.api_key = settings.STRIPE_SECRET_KEY
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
@router.post("/webhook/stripe")
|
|
77
|
+
async def stripe_webhook(
|
|
78
|
+
request: Request,
|
|
79
|
+
background_tasks: BackgroundTasks,
|
|
80
|
+
) -> dict:
|
|
81
|
+
payload = await request.body()
|
|
82
|
+
sig_header = request.headers.get("stripe-signature")
|
|
83
|
+
|
|
84
|
+
# SIEMPRE verificar la firma antes de procesar
|
|
85
|
+
try:
|
|
86
|
+
evento = stripe.Webhook.construct_event(
|
|
87
|
+
payload, sig_header, settings.STRIPE_WEBHOOK_SECRET
|
|
88
|
+
)
|
|
89
|
+
except stripe.error.SignatureVerificationError:
|
|
90
|
+
raise HTTPException(status_code=400, detail="Firma inválida")
|
|
91
|
+
except ValueError:
|
|
92
|
+
raise HTTPException(status_code=400, detail="Payload inválido")
|
|
93
|
+
|
|
94
|
+
# Responder 200 inmediatamente — procesar en background
|
|
95
|
+
# Stripe reintenta si no recibe respuesta en 30 segundos
|
|
96
|
+
background_tasks.add_task(PagosService.procesar_evento_webhook, evento)
|
|
97
|
+
return {"status": "recibido"}
|
|
98
|
+
|
|
99
|
+
|
|
100
|
+
# services/pagos.py
|
|
101
|
+
class PagosService:
|
|
102
|
+
|
|
103
|
+
@staticmethod
|
|
104
|
+
async def procesar_evento_webhook(evento: stripe.Event) -> None:
|
|
105
|
+
"""Procesa un evento de Stripe. Idempotente por diseño."""
|
|
106
|
+
from app.db.session import AsyncSessionLocal
|
|
107
|
+
from app.models.eventos import EventoStripe
|
|
108
|
+
|
|
109
|
+
async with AsyncSessionLocal() as db:
|
|
110
|
+
# Idempotencia: verificar si ya procesamos este evento
|
|
111
|
+
if await PagosService._evento_ya_procesado(evento.id, db):
|
|
112
|
+
return
|
|
113
|
+
|
|
114
|
+
match evento.type:
|
|
115
|
+
case "checkout.session.completed":
|
|
116
|
+
await PagosService._procesar_checkout_completado(
|
|
117
|
+
evento.data.object, db
|
|
118
|
+
)
|
|
119
|
+
case "customer.subscription.created":
|
|
120
|
+
await PagosService._procesar_subscripcion_creada(
|
|
121
|
+
evento.data.object, db
|
|
122
|
+
)
|
|
123
|
+
case "customer.subscription.updated":
|
|
124
|
+
await PagosService._procesar_subscripcion_actualizada(
|
|
125
|
+
evento.data.object, db
|
|
126
|
+
)
|
|
127
|
+
case "customer.subscription.deleted":
|
|
128
|
+
await PagosService._procesar_subscripcion_cancelada(
|
|
129
|
+
evento.data.object, db
|
|
130
|
+
)
|
|
131
|
+
case "invoice.payment_failed":
|
|
132
|
+
await PagosService._procesar_pago_fallido(
|
|
133
|
+
evento.data.object, db
|
|
134
|
+
)
|
|
135
|
+
case "invoice.payment_succeeded":
|
|
136
|
+
await PagosService._procesar_pago_exitoso(
|
|
137
|
+
evento.data.object, db
|
|
138
|
+
)
|
|
139
|
+
case _:
|
|
140
|
+
pass # Ignorar eventos no manejados — no es un error
|
|
141
|
+
|
|
142
|
+
await PagosService._registrar_evento_procesado(evento.id, db)
|
|
143
|
+
await db.commit()
|
|
144
|
+
|
|
145
|
+
@staticmethod
|
|
146
|
+
async def _evento_ya_procesado(evento_id: str, db: AsyncSession) -> bool:
|
|
147
|
+
from sqlalchemy import select
|
|
148
|
+
from app.models.eventos import EventoStripe
|
|
149
|
+
|
|
150
|
+
resultado = await db.execute(
|
|
151
|
+
select(EventoStripe).where(EventoStripe.stripe_event_id == evento_id)
|
|
152
|
+
)
|
|
153
|
+
return resultado.scalar_one_or_none() is not None
|
|
154
|
+
|
|
155
|
+
@staticmethod
|
|
156
|
+
async def _registrar_evento_procesado(evento_id: str, db: AsyncSession) -> None:
|
|
157
|
+
from app.models.eventos import EventoStripe
|
|
158
|
+
|
|
159
|
+
db.add(EventoStripe(stripe_event_id=evento_id))
|
|
160
|
+
await db.flush()
|
|
161
|
+
```
|
|
162
|
+
|
|
163
|
+
---
|
|
164
|
+
|
|
165
|
+
## 3. Idempotency keys obligatorias
|
|
166
|
+
|
|
167
|
+
Una idempotency key garantiza que si la operación se envía dos veces
|
|
168
|
+
(retry de red, doble clic, worker duplicado), Stripe solo la ejecuta una vez.
|
|
169
|
+
Stripe retiene el resultado por 24 horas.
|
|
170
|
+
|
|
171
|
+
```python
|
|
172
|
+
import stripe
|
|
173
|
+
from app.core.config import settings
|
|
174
|
+
|
|
175
|
+
stripe.api_key = settings.STRIPE_SECRET_KEY
|
|
176
|
+
|
|
177
|
+
|
|
178
|
+
async def crear_payment_intent(orden: Orden) -> stripe.PaymentIntent:
|
|
179
|
+
"""NUNCA crear un PaymentIntent sin idempotency key."""
|
|
180
|
+
return stripe.PaymentIntent.create(
|
|
181
|
+
amount=int(orden.total * 100), # Stripe trabaja en centavos
|
|
182
|
+
currency="mxn",
|
|
183
|
+
customer=orden.usuario.stripe_customer_id,
|
|
184
|
+
description=f"Orden #{orden.numero}",
|
|
185
|
+
metadata={
|
|
186
|
+
"orden_id": str(orden.id),
|
|
187
|
+
"usuario_id": str(orden.usuario_id),
|
|
188
|
+
},
|
|
189
|
+
idempotency_key=f"pi_{orden.id}", # OBLIGATORIO
|
|
190
|
+
)
|
|
191
|
+
|
|
192
|
+
|
|
193
|
+
async def crear_subscripcion(usuario: Usuario, price_id: str) -> stripe.Subscription:
|
|
194
|
+
"""Idempotency key incluye usuario Y plan — evita duplicados por plan."""
|
|
195
|
+
return stripe.Subscription.create(
|
|
196
|
+
customer=usuario.stripe_customer_id,
|
|
197
|
+
items=[{"price": price_id}],
|
|
198
|
+
expand=["latest_invoice.payment_intent"],
|
|
199
|
+
metadata={
|
|
200
|
+
"usuario_id": str(usuario.id),
|
|
201
|
+
"price_id": price_id,
|
|
202
|
+
},
|
|
203
|
+
idempotency_key=f"sub_{usuario.id}_{price_id}", # OBLIGATORIO
|
|
204
|
+
)
|
|
205
|
+
```
|
|
206
|
+
|
|
207
|
+
### Convención de idempotency keys por operación
|
|
208
|
+
|
|
209
|
+
| Operación | Formato recomendado |
|
|
210
|
+
|-----------|---------------------|
|
|
211
|
+
| PaymentIntent.create | `pi_{orden.id}` |
|
|
212
|
+
| Subscription.create | `sub_{usuario.id}_{price_id}` |
|
|
213
|
+
| checkout.Session.create | `checkout_{orden.id}` |
|
|
214
|
+
| Refund.create (total) | `refund_{pago.id}` |
|
|
215
|
+
| Refund.create (parcial) | `refund_partial_{pago.id}_{monto}` |
|
|
216
|
+
| Customer.create | `customer_{usuario.id}` |
|
|
217
|
+
|
|
218
|
+
---
|
|
219
|
+
|
|
220
|
+
## 4. Checkout Session completo
|
|
221
|
+
|
|
222
|
+
```python
|
|
223
|
+
import time
|
|
224
|
+
import stripe
|
|
225
|
+
from app.core.config import settings
|
|
226
|
+
from app.models.ordenes import Orden
|
|
227
|
+
from app.models.usuarios import Usuario
|
|
228
|
+
|
|
229
|
+
stripe.api_key = settings.STRIPE_SECRET_KEY
|
|
230
|
+
|
|
231
|
+
|
|
232
|
+
async def crear_checkout_session(orden: Orden, usuario: Usuario) -> str:
|
|
233
|
+
"""Crea una Checkout Session y retorna la URL de pago."""
|
|
234
|
+
session = stripe.checkout.Session.create(
|
|
235
|
+
payment_method_types=["card"],
|
|
236
|
+
mode="payment", # "subscription" para cobros recurrentes
|
|
237
|
+
customer_email=usuario.email,
|
|
238
|
+
customer=usuario.stripe_customer_id, # Si ya existe el customer
|
|
239
|
+
line_items=[
|
|
240
|
+
{
|
|
241
|
+
"price_data": {
|
|
242
|
+
"currency": "mxn",
|
|
243
|
+
"unit_amount": int(orden.total * 100), # centavos
|
|
244
|
+
"product_data": {
|
|
245
|
+
"name": orden.descripcion,
|
|
246
|
+
"description": f"Orden #{orden.numero}",
|
|
247
|
+
},
|
|
248
|
+
},
|
|
249
|
+
"quantity": 1,
|
|
250
|
+
}
|
|
251
|
+
],
|
|
252
|
+
# URLs de retorno — NUNCA asumir que success_url = pago completado
|
|
253
|
+
success_url=(
|
|
254
|
+
f"{settings.BASE_URL}/pagos/exitoso"
|
|
255
|
+
"?session_id={CHECKOUT_SESSION_ID}"
|
|
256
|
+
),
|
|
257
|
+
cancel_url=f"{settings.BASE_URL}/pagos/cancelado",
|
|
258
|
+
metadata={
|
|
259
|
+
"orden_id": str(orden.id),
|
|
260
|
+
"usuario_id": str(usuario.id),
|
|
261
|
+
},
|
|
262
|
+
expires_at=int(time.time()) + 1800, # Expira en 30 minutos
|
|
263
|
+
idempotency_key=f"checkout_{orden.id}", # OBLIGATORIO
|
|
264
|
+
)
|
|
265
|
+
return session.url
|
|
266
|
+
|
|
267
|
+
|
|
268
|
+
async def verificar_checkout_session(session_id: str) -> stripe.checkout.Session:
|
|
269
|
+
"""Verificar el estado real de la sesión — no confiar solo en el redirect."""
|
|
270
|
+
return stripe.checkout.Session.retrieve(
|
|
271
|
+
session_id,
|
|
272
|
+
expand=["payment_intent", "customer"],
|
|
273
|
+
)
|
|
274
|
+
```
|
|
275
|
+
|
|
276
|
+
---
|
|
277
|
+
|
|
278
|
+
## 5. Suscripciones con estado correcto
|
|
279
|
+
|
|
280
|
+
Los estados de Stripe tienen semántica específica. Mapearlos correctamente
|
|
281
|
+
es crítico para dar o quitar acceso al servicio:
|
|
282
|
+
|
|
283
|
+
```python
|
|
284
|
+
from datetime import datetime
|
|
285
|
+
from sqlalchemy.ext.asyncio import AsyncSession
|
|
286
|
+
import stripe
|
|
287
|
+
|
|
288
|
+
# Mapeo canónico de estados de Stripe a estados de la app
|
|
289
|
+
ESTADOS_STRIPE: dict[str, str] = {
|
|
290
|
+
"active": "activa", # Todo correcto — dar acceso
|
|
291
|
+
"trialing": "en_prueba", # Periodo de prueba — dar acceso
|
|
292
|
+
"past_due": "pago_vencido", # Reintentos en progreso — dar acceso temporal
|
|
293
|
+
"unpaid": "suspendida", # Reintentos agotados — restringir acceso
|
|
294
|
+
"canceled": "cancelada", # Cancelada definitivamente — revocar acceso
|
|
295
|
+
"incomplete": "incompleta", # Pago inicial fallido — no dar acceso
|
|
296
|
+
"incomplete_expired": "expirada", # Nunca completó pago inicial
|
|
297
|
+
"paused": "pausada", # Pausada manualmente — sin cobros
|
|
298
|
+
}
|
|
299
|
+
|
|
300
|
+
|
|
301
|
+
async def sincronizar_subscripcion(
|
|
302
|
+
sub: stripe.Subscription, db: AsyncSession
|
|
303
|
+
) -> None:
|
|
304
|
+
"""Sincroniza el estado local con el estado real de Stripe."""
|
|
305
|
+
from sqlalchemy import select, update
|
|
306
|
+
from app.models.subscripciones import Subscripcion
|
|
307
|
+
|
|
308
|
+
nuevo_estado = ESTADOS_STRIPE.get(sub.status, "desconocido")
|
|
309
|
+
|
|
310
|
+
await db.execute(
|
|
311
|
+
update(Subscripcion)
|
|
312
|
+
.where(Subscripcion.stripe_subscription_id == sub.id)
|
|
313
|
+
.values(
|
|
314
|
+
estado=nuevo_estado,
|
|
315
|
+
periodo_actual_inicio=datetime.fromtimestamp(sub.current_period_start),
|
|
316
|
+
periodo_actual_fin=datetime.fromtimestamp(sub.current_period_end),
|
|
317
|
+
cancelar_al_periodo_fin=sub.cancel_at_period_end,
|
|
318
|
+
actualizado_en=datetime.utcnow(),
|
|
319
|
+
)
|
|
320
|
+
)
|
|
321
|
+
await db.flush()
|
|
322
|
+
|
|
323
|
+
|
|
324
|
+
async def cancelar_al_periodo_fin(
|
|
325
|
+
subscripcion_id: str, db: AsyncSession
|
|
326
|
+
) -> stripe.Subscription:
|
|
327
|
+
"""Cancela la suscripción al final del periodo pagado (no inmediatamente)."""
|
|
328
|
+
from sqlalchemy import select
|
|
329
|
+
from app.models.subscripciones import Subscripcion
|
|
330
|
+
|
|
331
|
+
sub_local = await db.scalar(
|
|
332
|
+
select(Subscripcion).where(Subscripcion.id == subscripcion_id)
|
|
333
|
+
)
|
|
334
|
+
if sub_local is None:
|
|
335
|
+
raise ValueError(f"Subscripción {subscripcion_id} no encontrada")
|
|
336
|
+
|
|
337
|
+
sub_stripe = stripe.Subscription.modify(
|
|
338
|
+
sub_local.stripe_subscription_id,
|
|
339
|
+
cancel_at_period_end=True,
|
|
340
|
+
)
|
|
341
|
+
await sincronizar_subscripcion(sub_stripe, db)
|
|
342
|
+
return sub_stripe
|
|
343
|
+
```
|
|
344
|
+
|
|
345
|
+
---
|
|
346
|
+
|
|
347
|
+
## 6. Reembolsos
|
|
348
|
+
|
|
349
|
+
```python
|
|
350
|
+
import stripe
|
|
351
|
+
from sqlalchemy.ext.asyncio import AsyncSession
|
|
352
|
+
from app.core.config import settings
|
|
353
|
+
|
|
354
|
+
stripe.api_key = settings.STRIPE_SECRET_KEY
|
|
355
|
+
|
|
356
|
+
|
|
357
|
+
async def reembolsar_pago(
|
|
358
|
+
pago_id: str,
|
|
359
|
+
db: AsyncSession,
|
|
360
|
+
monto_parcial_centavos: int | None = None,
|
|
361
|
+
razon: str = "requested_by_customer",
|
|
362
|
+
) -> stripe.Refund:
|
|
363
|
+
"""
|
|
364
|
+
Reembolsa un pago. Si monto_parcial_centavos es None, reembolsa el total.
|
|
365
|
+
razon: 'requested_by_customer' | 'fraudulent' | 'duplicate'
|
|
366
|
+
"""
|
|
367
|
+
from sqlalchemy import select
|
|
368
|
+
from app.models.pagos import Pago
|
|
369
|
+
|
|
370
|
+
pago = await db.scalar(select(Pago).where(Pago.id == pago_id))
|
|
371
|
+
if pago is None:
|
|
372
|
+
raise ValueError(f"Pago {pago_id} no encontrado")
|
|
373
|
+
if pago.estado in ("reembolsado", "cancelado"):
|
|
374
|
+
raise ValueError(f"Pago {pago_id} ya fue reembolsado o cancelado")
|
|
375
|
+
|
|
376
|
+
# Construir idempotency key diferenciada por tipo de reembolso
|
|
377
|
+
if monto_parcial_centavos:
|
|
378
|
+
idem_key = f"refund_partial_{pago_id}_{monto_parcial_centavos}"
|
|
379
|
+
else:
|
|
380
|
+
idem_key = f"refund_{pago_id}"
|
|
381
|
+
|
|
382
|
+
kwargs: dict = {
|
|
383
|
+
"payment_intent": pago.stripe_payment_intent_id,
|
|
384
|
+
"reason": razon,
|
|
385
|
+
"idempotency_key": idem_key,
|
|
386
|
+
}
|
|
387
|
+
if monto_parcial_centavos:
|
|
388
|
+
kwargs["amount"] = monto_parcial_centavos
|
|
389
|
+
|
|
390
|
+
reembolso = stripe.Refund.create(**kwargs)
|
|
391
|
+
|
|
392
|
+
# Actualizar estado local — el webhook también llegará, debe ser idempotente
|
|
393
|
+
nuevo_estado = "parcialmente_reembolsado" if monto_parcial_centavos else "reembolsado"
|
|
394
|
+
from sqlalchemy import update
|
|
395
|
+
await db.execute(
|
|
396
|
+
update(Pago)
|
|
397
|
+
.where(Pago.id == pago_id)
|
|
398
|
+
.values(
|
|
399
|
+
estado=nuevo_estado,
|
|
400
|
+
stripe_refund_id=reembolso.id,
|
|
401
|
+
)
|
|
402
|
+
)
|
|
403
|
+
await db.flush()
|
|
404
|
+
return reembolso
|
|
405
|
+
```
|
|
406
|
+
|
|
407
|
+
---
|
|
408
|
+
|
|
409
|
+
## 7. MUST DO / MUST NOT DO
|
|
410
|
+
|
|
411
|
+
### MUST DO
|
|
412
|
+
|
|
413
|
+
- **SIEMPRE** verificar la firma del webhook con `stripe.Webhook.construct_event`
|
|
414
|
+
antes de procesar cualquier evento.
|
|
415
|
+
- **Idempotency key** en TODA operación de creación de Stripe (`create`).
|
|
416
|
+
- Usar modo `test` (`sk_test_...`) en desarrollo. NUNCA live keys en código.
|
|
417
|
+
- Almacenar en tu BD: `stripe_customer_id`, `stripe_subscription_id`,
|
|
418
|
+
`stripe_payment_intent_id`, `stripe_refund_id`.
|
|
419
|
+
- Manejar webhooks de forma asíncrona: responder 200 rápido, procesar en background.
|
|
420
|
+
- Reconciliar estados: siempre confiar en el webhook, no en el redirect de `success_url`.
|
|
421
|
+
- Verificar el estado de la sesión o intent vía API al mostrar la pantalla de éxito.
|
|
422
|
+
- Registrar todos los eventos procesados en una tabla para garantizar idempotencia.
|
|
423
|
+
- Manejar `invoice.payment_failed` para suspender acceso cuando los reintentos se agotan.
|
|
424
|
+
|
|
425
|
+
### MUST NOT DO
|
|
426
|
+
|
|
427
|
+
- **NUNCA** almacenar números de tarjeta, CVV ni fechas de expiración.
|
|
428
|
+
- **NUNCA** procesar un webhook sin verificar la firma.
|
|
429
|
+
- **NUNCA** asumir que `success_url` significa que el pago fue completado.
|
|
430
|
+
- **NUNCA** reutilizar idempotency keys entre operaciones distintas.
|
|
431
|
+
- **NUNCA** hardcodear stripe keys — siempre variables de entorno.
|
|
432
|
+
- **NUNCA** ignorar el evento `invoice.payment_failed` en suscripciones.
|
|
433
|
+
- **NUNCA** loggear payloads de webhooks sin filtrar datos sensibles.
|
|
434
|
+
- **NUNCA** hacer `db.commit()` dentro del service — solo en el endpoint o tarea.
|
|
435
|
+
- **NUNCA** construir un formulario que reciba el número de tarjeta en tu servidor.
|
|
436
|
+
|
|
437
|
+
---
|
|
438
|
+
|
|
439
|
+
## 8. Testing con Stripe CLI
|
|
440
|
+
|
|
441
|
+
```bash
|
|
442
|
+
# Paso 1: instalar y autenticar Stripe CLI
|
|
443
|
+
# https://stripe.com/docs/stripe-cli
|
|
444
|
+
stripe login
|
|
445
|
+
|
|
446
|
+
# Paso 2: escuchar webhooks localmente (mantener en terminal aparte)
|
|
447
|
+
stripe listen --forward-to localhost:8000/webhook/stripe
|
|
448
|
+
|
|
449
|
+
# Paso 3: disparar eventos específicos en otra terminal
|
|
450
|
+
stripe trigger checkout.session.completed
|
|
451
|
+
stripe trigger customer.subscription.created
|
|
452
|
+
stripe trigger customer.subscription.updated
|
|
453
|
+
stripe trigger customer.subscription.deleted
|
|
454
|
+
stripe trigger invoice.payment_failed
|
|
455
|
+
stripe trigger invoice.payment_succeeded
|
|
456
|
+
stripe trigger charge.dispute.created
|
|
457
|
+
|
|
458
|
+
# Disparar con datos personalizados (fixture)
|
|
459
|
+
stripe trigger payment_intent.succeeded \
|
|
460
|
+
--override payment_intent:metadata.orden_id=test-123
|
|
461
|
+
|
|
462
|
+
# Ver eventos recientes
|
|
463
|
+
stripe events list --limit 10
|
|
464
|
+
|
|
465
|
+
# Tarjetas de prueba para Payment Intents
|
|
466
|
+
# 4242 4242 4242 4242 → pago exitoso
|
|
467
|
+
# 4000 0000 0000 0002 → tarjeta declinada (insufficient_funds)
|
|
468
|
+
# 4000 0025 0000 3155 → requiere 3D Secure
|
|
469
|
+
# 4000 0000 0000 9995 → fondos insuficientes
|
|
470
|
+
# 4000 0000 0000 0069 → tarjeta expirada
|
|
471
|
+
```
|
|
472
|
+
|
|
473
|
+
### Test unitario de webhook con firma simulada
|
|
474
|
+
|
|
475
|
+
```python
|
|
476
|
+
# tests/test_webhook_stripe.py
|
|
477
|
+
import time
|
|
478
|
+
import json
|
|
479
|
+
import stripe
|
|
480
|
+
import pytest
|
|
481
|
+
from httpx import AsyncClient
|
|
482
|
+
|
|
483
|
+
WEBHOOK_SECRET = "whsec_test_secret"
|
|
484
|
+
|
|
485
|
+
def firmar_payload(payload: dict, secret: str) -> tuple[bytes, str]:
|
|
486
|
+
"""Genera un payload firmado para tests de webhook."""
|
|
487
|
+
body = json.dumps(payload).encode()
|
|
488
|
+
timestamp = int(time.time())
|
|
489
|
+
sig = stripe.WebhookSignature.compute_signature(
|
|
490
|
+
f"{timestamp}.{body.decode()}", secret
|
|
491
|
+
)
|
|
492
|
+
header = f"t={timestamp},v1={sig}"
|
|
493
|
+
return body, header
|
|
494
|
+
|
|
495
|
+
|
|
496
|
+
@pytest.mark.asyncio
|
|
497
|
+
async def test_webhook_checkout_completado(client: AsyncClient):
|
|
498
|
+
payload = {
|
|
499
|
+
"id": "evt_test_001",
|
|
500
|
+
"type": "checkout.session.completed",
|
|
501
|
+
"data": {
|
|
502
|
+
"object": {
|
|
503
|
+
"id": "cs_test_abc",
|
|
504
|
+
"payment_status": "paid",
|
|
505
|
+
"metadata": {"orden_id": "orden-123"},
|
|
506
|
+
}
|
|
507
|
+
},
|
|
508
|
+
}
|
|
509
|
+
body, header = firmar_payload(payload, WEBHOOK_SECRET)
|
|
510
|
+
response = await client.post(
|
|
511
|
+
"/webhook/stripe",
|
|
512
|
+
content=body,
|
|
513
|
+
headers={"stripe-signature": header, "content-type": "application/json"},
|
|
514
|
+
)
|
|
515
|
+
assert response.status_code == 200
|
|
516
|
+
|
|
517
|
+
|
|
518
|
+
@pytest.mark.asyncio
|
|
519
|
+
async def test_webhook_firma_invalida_retorna_400(client: AsyncClient):
|
|
520
|
+
response = await client.post(
|
|
521
|
+
"/webhook/stripe",
|
|
522
|
+
json={"type": "checkout.session.completed"},
|
|
523
|
+
headers={"stripe-signature": "firma_invalida"},
|
|
524
|
+
)
|
|
525
|
+
assert response.status_code == 400
|
|
526
|
+
```
|
|
527
|
+
|
|
528
|
+
---
|
|
529
|
+
|
|
530
|
+
## 9. Tabla de referencias
|
|
531
|
+
|
|
532
|
+
| Tema | Archivo |
|
|
533
|
+
|------|---------|
|
|
534
|
+
| Connect: marketplace, transfers, splits de pago | [recursos/stripe-connect.md](recursos/stripe-connect.md) |
|
|
535
|
+
| Manejo de errores Stripe y reintentos | [recursos/errores-reintentos.md](recursos/errores-reintentos.md) |
|
|
536
|
+
| Documentación oficial Stripe | https://docs.stripe.com |
|
|
537
|
+
| Stripe CLI reference | https://docs.stripe.com/stripe-cli |
|
|
538
|
+
| Tarjetas de prueba | https://docs.stripe.com/testing |
|
|
539
|
+
|
|
540
|
+
---
|
|
541
|
+
|
|
542
|
+
## Gotchas / Errores comunes no obvios
|
|
543
|
+
|
|
544
|
+
**`checkout.session.completed` llega con `payment_status: "unpaid"` para suscripciones con trial period y el código que asume `payment_status: "paid"` nunca activa el acceso del usuario**: un Checkout Session de suscripción con `trial_period_days: 14` dispara `checkout.session.completed` inmediatamente después del registro, pero el campo `payment_status` es `"unpaid"` porque no hubo cobro real. Causa: para suscripciones con trial, Stripe completa la sesión (el usuario dio sus datos de pago) antes del primer cobro. Fix: al procesar `checkout.session.completed` para suscripciones, verificar `session.mode == "subscription"` y activar el acceso basándose en el estado de la suscripción (campo `subscription`), no en `payment_status`. Escuchar también `customer.subscription.created` con `status: "trialing"` como señal canónica de trial activo.
|
|
545
|
+
|
|
546
|
+
**Las idempotency keys con `idem_key = f"create_payment_{order_id}"` causan errores `IdempotencyError` cuando el mismo endpoint se llama con parámetros diferentes para la misma orden (ej: montos actualizados)**: si el usuario modifica su carrito y vuelve a intentar pagar, la segunda llamada a `PaymentIntent.create` con la misma `order_id` pero diferente `amount` choca con el PaymentIntent ya creado. Stripe retorna error porque los parámetros no coinciden con el primer intento. Causa: la idempotency key en Stripe es una promesa de que "esta llamada idéntica puede enviarse múltiples veces"; si los parámetros cambian, es una operación diferente. Fix: incluir un hash de los parámetros relevantes en la key: `idem_key = f"create_pi_{order_id}_{amount_cents}_{currency}"`, o usar un UUID v4 por intento y manejar idempotencia en tu propio backend comparando el estado de la orden.
|
|
547
|
+
|
|
548
|
+
**Los webhooks de Stripe pueden llegar fuera de orden y procesarlos en secuencia sin verificar el estado actual puede revertir actualizaciones válidas**: el evento `customer.subscription.updated` con `status: "active"` puede llegar después de `customer.subscription.deleted` si hubo reintento de un evento antiguo, sobreescribiendo el estado `canceled` correcto con `active`. Causa: Stripe garantiza entrega at-least-once pero no orden de llegada; los reintentos de eventos fallidos anteriores pueden llegar después que eventos más recientes. Fix: siempre verificar el timestamp del evento (`event.created`) antes de actualizar la BD: solo procesar si `event.created > ultima_actualizacion_en_bd`. Alternativamente, consultar el estado actual vía API Stripe antes de cada actualización: `stripe.Subscription.retrieve(subscription_id)`.
|
|
549
|
+
|
|
550
|
+
**`stripe.Webhook.construct_event` lanza `SignatureVerificationError` en producción con FastAPI porque el body fue parseado como JSON antes de llegar al handler del webhook, y `request.body()` devuelve bytes del JSON serializado en lugar del payload raw original**: el middleware de FastAPI que hace `await request.json()` consume el stream del body; cuando el handler del webhook intenta leer el body raw para verificar la firma, recibe el JSON re-serializado que puede tener diferente formato (ordenamiento de claves, espacios) que el payload original de Stripe. Causa: la firma HMAC de Stripe se calcula sobre el payload binario exacto; cualquier transformación invalida la firma. Fix: en el endpoint de webhook, leer el body raw directamente sin pasar por middleware de parsing: `raw_body = await request.body()` y no declarar el parámetro como `body: dict` en la firma de la función FastAPI.
|