Auditoría SV Standards — Travel (2026-07-18)
Alcance: delta post-maratón del 2026-07-14 (122 archivos, commits 2026-07-15→18: billing checkout propio, dominio
platformnuevo, traducción DeepL, paleta de marca, tema claro del admin, rework de marketing-site) contraSV_Standard_Backend.md,SV_Standard_Frontend.mdydocumentation_standard.md+ re-verificación de invariantes globales por comando. La base pre-07-14 NO se re-auditó (verde hace 4 días, vertasks_active.mdde esa fecha).Método: 1 agente auditor de backend (checklist de 75 reglas destiladas) + auditoría mecánica propia de frontends (18 Líneas Rojas grep-ables + lectura dirigida) y de documentación (checks estructurales); cada hallazgo verificado contra el código real antes de actuar — dos agentes murieron a mitad de camino por límite de sesión y sus áreas se re-ejecutaron a mano.
Veredicto por estándar
| Estándar | Veredicto | Detalle |
|---|---|---|
| SV_Standard_Backend | 🟡 → ✅ tras fixes | 1 🔴 + 10 🟡 en el delta; el 🔴 y 6 🟡 corregidos en la sesión, 4 🟡 declarados como deuda con decisión |
| SV_Standard_Frontend | 🟡 → ✅ tras fixes | 1 LR13 real + 2 LR2 + 1 LR8 sin justificar + 1 LR5; todos corregidos o con excepción documentada salvo LR5 (deuda declarada) |
| documentation_standard | ✅ (1 drift menor corregido) | AGENTS ≤150 todos, Diátaxis 4/4, auto-generados al día, CODEOWNERS ✓; único drift: MAP.md sin el dominio platform |
Hallazgos y disposición
🔴 CORREGIDO — POST /billing/charge sin idempotencia (LR#11)
charge_with_source generaba un nonce aleatorio por llamada y creaba una transacción
Wompi nueva contra la tarjeta guardada en cada POST: doble click o retry por timeout =
doble cobro real. Fix: guard in-process anti doble-cobro (_charges_in_flight +
ventana de 60s _recent_charges; un fallo del gateway libera el guard para reintentar) →
DuplicateChargeError → 409. 2 tests nuevos, incluido "una sola transacción creada en
Wompi". Deuda declarada: v1 in-process válido con 1 worker (mismo criterio que el rate
limiter); multi-worker exige Idempotency-Key DB-backed (= Fase 1b ya planificada).
🟡 CORREGIDOS (backend)
- mypy --strict roto en main (5 errores en 3 archivos, entraron con el merge remoto):
wompi.pyno-any-return ×2, asignaciónAny|None→stren billing, pseudo-Protocol de platform (clase plana que mypy rechazaba en el router — ahora heredatyping.Protocoly el contrato se valida estructuralmente), anotación faltante. + 1 error ruff. Gate en 0 de nuevo. - Webhook de payments sin las lecciones de billing: (a) referencia ajena/desconocida →
lanzaba 404 — con UN comercio Wompi y UNA URL de eventos para todas las verticales, todo
evento de Gastro/suscripciones habría hecho reintentar a Wompi en bucle → ahora ACK 200
sin efectos (propio-sin-Payment se loguea como error y ACKea); (b) sin guard de orden —
un PENDING rezagado regresaba un pago APPROVED a pending con la reserva ya
completed→ ahora se ignora el downgrade. 3 tests nuevos. /platform/audit-log: instanciaba el repo y mapeaba schemas en el router (LR#1), devolvía{"items": []}con 200 para property inexistente (inconsistente con el resto) y aceptabale=200(tope estándar 100). Movido aPlatformService.list_audit_logcon 404 consistente yle=100.- Logging (LR#9):
payments/service.pyusaba stdlib logging con %-format ybilling/service.pymezclabaextra={...}(idioma stdlib que en structlog anida un dict no consultable) — todo convertido a structlog con kwargs planos. - Tipado:
session: Any→AsyncSessionen translation_service;# type: ignorede platform/repository con justificación textual.
🟡 CORREGIDOS (frontend)
- LR13 real:
TrialBanner(features/billing) importabausePropertyProfilede features/property. Fix FSD canónico: el widgetLayout(capa que SÍ compone features) llama al hook y bajasubscriptionStatus/trialEndsAtpor props; el banner quedó presentacional. Tests reescritos a props (sin mock del hook). - LR2 con excepción documentada:
CardCheckoutForm(4 useState) yNequiCheckoutPane(1) — datos de pago PCI-sensibles y efímeros que viajan del navegador directo a Wompi (jamás a nuestro backend); mismo criterio que las 2 excepciones de FE-1.3. Comentario de justificación añadido en ambos. - LR8 con excepción documentada:
WompiCheckout.loadWompiWidgetinyecta el script third-party del Widget en<body>(lo exige Wompi) — justificado en comentario, aislado en función con nombre (mismo patrón que useSocialMeta/theme.ts).
🟡 CORREGIDO (documentación)
backend/MAP.mdno mencionaba el dominioplatform— fila añadida (responsabilidad, endpoints, auth interna, tests).
🟡 DEUDA DECLARADA (no corregida a propósito — ver TECH_DEBT de cada unidad)
billing/service.py701 líneas (partir en subpaquete en sprint propio — mueve dinero).platformimporta 3 dominios hermanos (LR#22, decisión consciente; extraer la primitiva de audit a core/ cuando haya tercer consumidor).- Tests de ruta faltantes (LR#10) en mutaciones de platform y payment-sources/charge.
X-Operator-Actorsin verificar (aceptable con el BFF como único caller).MembresiaTab.tsx294 líneas (LR5) — partir en sprint de billing UI.- Stories de
features/billing(LR10 — consistente con la práctica del repo: Stories solo en design-system; anotado por transparencia).
ℹ️ Sin acción (verificados como aceptables)
int(data["id"])sin guard en billing (Wompi cambiaría contrato → 500 visible en Sentry).travel_public_urlcon default de producción en config (patrón existente).- Posible PII en
rawtruncado de un warning del webhook (500 chars, ya truncado).
✅ Zonas verificadas limpias (evidencia, no suposición)
- Webhook de billing: firma antes de parsear,
compare_digest, anti-replay 300s, idempotencia tx+status, validación de monto contra catálogo, ERROR≠DECLINED, property ajena→200, fail-closed sin secreto — con 8 tests de integración contra PG real. core/wompi.py: timeouts en las 3 llamadas externas, fail-closed.- Migraciones 0025×2/0026×2/0027: solo columnas sobre tablas existentes (ADR-010 no aplica y lo documentan); merge revision correcta; un solo head.
translation_service+ hooks: timeout DeepL, tenant context antes de la primera query, filtroproperty_iden el UPDATE, referencias fuertes anti-GC, test cross-tenant.- Delta frontend: cero
any/@ts-ignore/console.log/<div onClick>/colores hardcodeados en los archivos nuevos;features/billingcon index.ts como API pública y 5 archivos de test. - Documentación: AGENTS.md ≤150 líneas en las 5 unidades (backend 132, el mayor), Diátaxis 4/4 en todas, references auto-generados sin drift (billing/platform/DEEPL presentes), CODEOWNERS existente, llms.txt sin links muertos.
Gates finales (post-fixes)
- Backend: pytest suite completa + ruff + mypy --strict (ver cierre de sesión en
tasks_active.md— todos en verde al momento del commit de esta auditoría). - Frontends: admin-panel 162+ tests · public-hotel 38 · marketing-site 5; tsc/lint/build verdes (línea base re-verificada al inicio y gates del pre-commit en el cierre).