Files
AgendaPro/.superpowers/sdd/progress.md
T
AgendaPro DevandClaude Opus 5 4281567207 fix: two defects that only reproduce over a real network
Neither is visible against localhost, which is why both shipped.

**Install button never appeared on `/login`** — a regression from making the login a
lazy route. `beforeinstallprompt` fires once per page load and is never replayed;
`useInstallPrompt` attached its listener from a `useEffect`, i.e. at mount, and
`InstallAppPrompt` now mounts only after an extra round-trip for its chunk. Locally
that round-trip is a millisecond so the listener still won a race it should never
have been in. Over a real connection the event was long gone, so a user on a slow
link lost the install button entirely.

The listener now lives in `src/lib/installPrompt.ts` and registers when the module
evaluates — `main.tsx` imports it for its side effect before mounting React. The hook
only reads from that store. Reproduced deterministically by delaying
`/assets/LoginPage-*.js` by 1.5s via `route()`: absent before, present after (also at
a 4s delay). `test:pwa` against the HTTPS domain now passes.

**Revenue chart did not animate on a real iPhone.** Measured rather than guessed:
the path animation does run in WebKit, and it triggers with the chart 100% visible at
y=601..715 of an 844px viewport — so neither "broken" nor "fires too early". What
fits is that iOS Safari suspends `requestAnimationFrame` during momentum scrolling
while framer-motion interpolates against wall-clock time: the animation spends its
1.4s without painting a frame and snaps to the end on resume, which looks exactly
like it never ran.

The chart's three animations move to CSS keyframes, which keep their own timeline in
the engine. The component only decides *when* (a `useInView` setting
`data-ld-rev-visible`). `prefers-reduced-motion` resolves in CSS too, and still
resolves to the *drawn* state — a line left at `dashoffset: 1px` with no animation
would be invisible forever. Verified: `getAnimations()` returns a `CSSAnimation`, and
under reduced motion the line renders complete.

Not verified: no physical iPhone here, and Playwright WebKit on Windows does not
reproduce iOS's rAF suspension. This is the standard mitigation and changes nothing
on desktop, but on-device confirmation is still outstanding.

Verified: typecheck clean; landing 13/13, PWA passed, responsive 0 findings, visual
62 screens / 0 errors, unit 39/39, e2e 33/33, admin 17/17, booking 12/12.

Co-Authored-By: Claude Opus 5 (1M context) <[email protected]>
2026-07-28 15:09:29 -06:00

21 KiB
Raw Blame History

SDD Progress Ledger — Auto-assign specialist + booking redesign

  • Plan: docs/superpowers/plans/2026-07-26-auto-assign-specialist-booking-redesign.md
  • Spec: docs/superpowers/specs/2026-07-26-auto-assign-specialist-booking-redesign-design.md
  • Branch: feat/auto-assign-specialist
  • Base commit: e8d2435
  • Commit policy: NO commits during execution (repo policy: only on explicit request). Verify per task via typecheck + tests. Reviewer reads changed files + plan task.
  • Pre-existing WIP (do NOT clobber): src/components/AppointmentModal.tsx, src/pages/CalendarPage.tsx, FullCalendar section of src/index.css (~lines 136-160). Use targeted edit only.

Tasks

  • Task 1: complete — db.ts (migrateV3ToV4 + runMigrations call), shared/types.ts (Business/Employee/WorkingHoursMap). Review: APPROVED, no findings. typecheck 0 errors, schema_version=4, backfill verified.
  • Task 2: complete — server/lib/scheduling.ts (pure helpers, no imports/DB), server/lib/scheduling.test.ts (15 tests), package.json test:unit. Review: APPROVED. test:unit 15/15. Minor (non-blocking): add direct test for hasConflict positive case, clamp01, parseWorkingHours malformed-shape, specialtyMatch substring branch — defer to final review.
  • Task 3: complete — appended DB fns to scheduling.ts (getCandidates, getExistingBusy, isAvailable, rankOne, pickBestSlotEmployee, autoAssign, runInTransaction). Plan typo c.info.idc.id fixed & verified. Review: APPROVED. typecheck 0, test:unit 15/15, tx smoke ok. Low-note: getExistingBusy scopes by start_at within day (cross-midnight appts not caught — neutralized by working-hours guard; revisit if 24h scheduling added).
  • Task 4: complete — booking.ts publicBusiness (+auto_assign_specialist,working_hours) + slots endpoint uses real working_hours + pickBestSlotEmployee. POST /book untouched. Review: APPROVED. typecheck 0, slots smoke ok. Info-notes: local overlapsRange dup of overlaps (plan-permitted); per-slot SELECT name could be hoisted (pre-existing).
  • Task 5: complete — booking.ts POST /book rewritten: runInTransaction + autoAssign (when emp omitted or auto_assign_specialist=1) + isAvailable guard → 409 on conflict; 201 includes employee_id+reasons. Review: APPROVED. typecheck 0, test:unit 15/15, integration a)201 b)409 c)201-survivor d)409-autoAssign-null all pass. Sound tx guard.
  • Task 6: complete — appointments.ts POST / wrapped in runInTransaction, autoAssign fallback (replaced LIMIT 1), isAvailable guard → 409. Two plan-text deviations (move emp resolution inside tx; Number(business_id)) verified correct. Review: APPROVED. typecheck 0, test:unit 15/15, conflict smoke 409/201 pass. FOLLOW-UP: e2e-test.mjs fixtures (2026-09-15T11:00Z=05:00 local, 2026-09-20 Sun) now fail under working-hours enforcement — fixture-side, will fix in Task 7.
  • Task 7: complete — settings.ts (+auto_assign_specialist,working_hours, JSON/0-1 coercion), employees.ts (+specialties/working_hours/efficiency_score), booking-e2e.mjs (new), e2e-test.mjs slot-aware fixture fix (no guards weakened). Review: APPROVED. typecheck 0, test:unit 15/15, test:e2e 25/25, test:booking 9/9.

== BACKEND COMPLETE (Tasks 1-7). Algorithm + guards + APIs all verified green. ==

  • Task 8: complete — index.css wrapped .input/.select/.textarea in @layer components; BookingPage DetailsForm 3× pl-9→pl-10. Verified directly by controller (build green, @layer confirmed at index.css:272, WIP untouched). Icon/placeholder overlap fixed app-wide.
  • Task 9: complete — publicApi.ts types (PublicBusiness.auto_assign_specialist; BookPayload.employee_id optional; BookResponse +employee_id/reasons). Verified directly (typecheck 0, no consumer breakage).
  • Task 10: complete — src/components/MonthCalendar.tsx (42-cell month grid, nav bounded by min/max, brand selected, accent avail dot, brand today dot). Verified directly (typecheck 0, build green). Will be integration-tested in Task 11.
  • Task 11: complete — BookingPage DateTimePicker rewritten: md+ 2-col (MonthCalendar left + grouped slots right), SlotGroup Mañana/Tarde, mobile stacked, slot button styles unchanged, max optional. Review: APPROVED. typecheck 0, build ok. AM/PM noon boundary verified.
  • Task 12: complete — BookingPage wizard rewired to dynamic 3/4 steps via stepsFor(autoAssign)+currentOriginal; main max-w-5xl only on Horario; handleBook sends employee_id undefined when autoAssign; Confirmation shows reasons badges. Review: APPROVED. typecheck 0, build ok, HTTP smoke both flows + 409. NOTE: npm run lint can't run repo-wide (pre-existing ESLint9 flat-config gap, no eslint.config.js) — pre-existing, will report at Task 15.
  • Task 13: complete — src/lib/workingHours.ts (DEFAULT_WH/DAYS/parseWh) + SettingsPage auto-assign checkbox + 7-day working-hours editor. Review: APPROVED. typecheck 0, build ok, runtime e2e (PATCH→GET persist, Sat 10-14 slots appear, toggle 3/4 steps).
  • Task 14: complete — EmployeesPage EmployeeModal +specialties chips +efficiency slider +working-hours editor (inherit toggle). Payload specialties[]/efficiency Number/working_hours null|map. Review: APPROVED. typecheck 0, build ok. Low-notes: all-closed employee flips back to inherit (benign); case-sensitive dedup (acceptable). Runtime persistence smoke deferred to Task 15.

== ALL 14 IMPLEMENTATION TASKS COMPLETE + REVIEWED. ==

== TASK 15: FINAL VERIFICATION ==

  • typecheck: 0 errors
  • test:unit: 15/15
  • build: OK (2462 modules)
  • test:e2e: 25/25
  • test:booking: 9/9 (auto-assign 201+reasons, double-book 409, auto_assign_specialist exposed)
  • employee persistence runtime check (Task 14 deferred): specialties/efficiency=87/working_hours custom→null(inherit) all persist ✓
  • lint: PRE-EXISTING repo-wide failure (no eslint.config.js, ESLint 9.39 requires flat config) — NOT introduced by this feature.
  • FINAL WHOLE-BRANCH REVIEW: READY TO MERGE. Core guarantee (no double-booking from public flow) verified end-to-end (sync handler + BEGIN IMMEDIATE + hasConflict + same-tx INSERT). No cross-task integration issues.

== POST-MERGE FOLLOW-UPS (non-blocking, deferred) ==

  1. getExistingBusy: switch WHERE to overlap-based (end_at > dayStart AND start_at < dayEnd) — closes cross-midnight gap if 24h scheduling added.
  2. Add 4 unit-test cases (hasConflict positive, clamp01, parseWh malformed, specialtyMatch substring).
  3. Replace local overlapsRange with imported overlaps.
  4. Hoist per-slot employee-name SELECT out of slots loop.
  5. MonthCalendar nav buttons → h-11 w-11 (44px touch target).

== DEMO EMAIL DOMAIN TASK ==

  • Task 1: complete — source constants, README credentials, active fixtures, and executable plan login example updated. Commits 6355d18..50dea33; review clean.
  • Task 2: complete — local data/agendapro.db migrated 2 users; second update changed 0; appointments 161 and clients 15 preserved; review clean.
  • Task 3: complete with limitation — typecheck/build/unit/admin passed; auth paths passed; e2e/booking slot assertions remain blocked by pre-existing null working-hours data, not the email change; review approved.
  • Task 4: complete — committed source/docs through 3e056a0, pushed to GitHub and Gitea, Coolify deployment wbnbq9b0kneba67sznu7p1qf finished, runtime container healthy.
  • Task 5: complete — production health/page passed, new admin/owner logins returned 200, old-domain logins returned 401; active DB was fresh and seeded with new emails, so targeted SQL was a no-op.
  • Final review: complete — final-fix review approved; regex acceptance fix review approved.

== AGENDAMAX PWA INSTALLABILITY TASK ==

  • Task 1: complete — manifest, iOS metadata, reproducible Playwright-generated 192/512 PNG icons, and package scripts. Commits 9d2ab39..de947f9; review clean.
  • Task 2: complete — versioned shell/static service worker and production-only registration. Commits de947f9..65233f2; review clean.
  • Task 3: complete — install prompt hook/component, iOS Safari guide, safe-area styling, and Login/AppShell/AdminShell integration; review fixes added prompt guard, appinstalled precedence, close-button label, and compact mobile layout. Commits 65233f2..9ded129; review clean.
  • Task 4: complete — production-server PWA endpoint/browser tests, online/offline API bypass checks, three-shell coverage, and install documentation; review clean. Commits 9ded129..43f5d13.
  • Final fix wave: complete — credential-aware cache policy, AgendaMax-scoped cleanup, session dismissal persistence, configurable PWA test credentials, dialog semantics, and dismissal regression coverage. Commit 0b466f3; final whole-feature review approved.

== LANDING PÚBLICA + ACCESO DE UN CLIC (2026-07-28) == Spec: docs/superpowers/specs/2026-07-28-landing-funnel-bi-design.md Plan: docs/superpowers/plans/2026-07-28-landing-funnel-bi.md

Ruteo: / sirve la landing (pública, React.lazy); el login pasa a /login. homePathFor(user) centraliza el destino por rol. /b/:slug intacto, fuera del AuthProvider.

Dos iteraciones de diseño a pedido del usuario:

  1. Primera versión oscura/sobria (criterio Apple). Entregada y verificada.
  2. Rediseño a claro, vívido y animado, con copy humano para dueñas de salón y público de 50+ (guía de dolores: Base de Conocimiento - IA Negocios y Dev/03-Preguntas-Guia.md, Q15/Q72/Q87: hablar de lo que gana el cliente, no de lo que hace el sistema). Se eliminó todo vocabulario de sistema del copy visible: ni «business intelligence», ni «datos», ni «métricas», ni «panel».
  3. Paleta rebasada sobre los colores REALES del panel a pedido del usuario: las 8 muestras del abanico del hero son PIE_COLORS de DashboardPage.tsx; la marca sale de public/favicon.svg vía src/components/BrandMark.tsx (única fuente); CTA = brand-500→brand-700 como .btn-primary.

Defectos encontrados y corregidos durante la verificación (no estaban en el plan):

  • El chunk de entrada arrastraba framer-motion (39 KB gz) a TODA carga del panel, porque LoginPage era eager. Se hizo lazy junto con la landing.
  • La landing descargaba recharts (111 KB gz) y FullCalendar (76 KB gz) sin usarlos, porque DashboardPage y CalendarPage eran eager. Ambas a lazy, con Suspense en el de AppShell.
  • clsx lo comparten lib/format.ts y recharts; Rollup lo asignaba al chunk charts, así que el entry importaba 111 KB para una utilidad de 200 bytes. Fijado al chunk react.
  • LOGIN CON EL TEXTO PEGADO AL BORDE en móvil: .safe-x está fuera de @layer y gana a px-5, dejando el padding en 0 con inset 0 (todo lo que no sea iPhone landscape). Nueva clase .ld-gutter. Ningún check existente lo detectaba (no hay desborde: hay cero margen) → añadido check gutter.
  • shell-height del audit daba falso positivo en toda página que scrollea: comparaba #root contra el viewport vía el fallback a body.firstElementChild. Acotado a [data-app-shell]; a cambio se añadió el check estático raw-viewport-unit sobre las fuentes públicas (verificado inyectando min-h-screen en ProofSection: dispara).
  • Rótulo «Como una de tus muchachas» en /login con Diego Castillo en la lista debajo. Reescrito sin asumir género («Como alguien de tu equipo»); igual en ObjectionsSection.
  • Anclas bajo el nav fijo (scroll-mt-24); degradado del cierre invisible tras -z-10; etiquetas del abanico ilegibles (eliminadas); avisos flotantes solapando el abanico (reubicados a las 3 zonas libres) y detalle truncado (acortado).

Audits ajustados por el cambio de ruteo (//login): visual-audit.mjs, responsive-audit.mjs y los SIETE goto(baseUrl) de pwa-e2e.mjs que esperan InstallAppPrompt. La landing se añadió a las listas de páginas de los dos audits.

VERIFICACIÓN FINAL (todo en verde):

  • typecheck: 0 errores
  • test:landing (NUEVO, WebKit): 13/13 — ruteo, acceso de un clic, sin desborde a 390px y prefers-reduced-motion sin contenido invisible en las 7 secciones
  • audit:responsive: 0 hallazgos (9 dispositivos × 10 páginas)
  • audit:visual: 62 pantallas, 0 fallos, 0 desborde, 0 errores de consola
  • test:pwa sobre el build: passed
  • test:unit 39/39 · test:e2e 33/33 · test:admin 17/17 · test:booking 12/12
  • build: el chunk de entrada NO importa charts, framer ni calendar (verificado en dist/)
  • WebKit a 390/440/744/820/1024/1440 en / y /login: sin desborde, sin campos <16px, sin objetivos <40px, sin errores

npm run lint sigue roto por falta de eslint.config.js (preexistente).


2026-07-28 — Commit y despliegue a producción (pedido explícito)

Commit 4c19244 en main (53 archivos), empujado a gitea y a github. Se rompe aquí la política de "sin commits" porque el usuario lo pidió explícitamente. graphify-out/ pasó a .gitignore: es regenerable y ensuciaba todo git status.

DEFECTO DE DESPLIEGUE ENCONTRADO ANTES DE SUBIR (el más importante de esta tanda): el login mágico no existía en producción y la landing lo habría prometido. DEMO es import.meta.env.DEV || import.meta.env.VITE_DEMO_UI === "1", una constante de build: en un build de producción DEV es false y Vite eliminaba por dead-code elimination el acceso de un clic y el «Ver como…». Comprobado sobre el bundle que producción servía en ese momento: agendamax.demo, cuentas demo, Ver como y Cambiar cuenta todos AUSENTES; el único demo1234 que sobrevivía está en switchUser de auth.tsx, que no está detrás de la bandera. Sin arreglarlo, la landing decía «no pide registro» y llevaba a un formulario vacío.

Arreglo: ARG VITE_DEMO_UI=1 + ENV en la etapa web-build del Dockerfile, antes de npm run build. Default en 1 porque este despliegue es la demostración; --build-arg VITE_DEMO_UI=0 lo apaga sin tocar código. Verificado construyendo de las dos formas: con la bandera aparecen data-magic-login, [email protected], Ver como y Cambiar cuenta; sin ella desaparecen las cuatro. En ambos casos el entry sigue importando solo icons/query/react.

También: CACHE_NAME del service worker a agendamax-shell-v2. No toqué su lógica, pero la landing cambió el app shell y partió el bundle en chunks nuevos; como las peticiones de assets son cache-first por nombre con hash, los chunks viejos quedarían huérfanos para siempre en el caché de quien ya visitó el sitio. El activate los purga al cambiar el sufijo. pwa-e2e.mjs solo verifica el prefijo, así que no hubo que tocar el test.

DESPLIEGUE (Coolify 4.1.2, app s30f7egdlkx4wyjp59o1iunc, build pack dockerfile, rama main, fuente urieljarethbusiness-cpu/agendamax):

  • El auto-deploy por webhook sí está configurado: el push a github disparó el deploy 145 solo. Mi POST /deploy?force=true (146) quedó encolado detrás y fue redundante.
  • Coste de ese error: el rebuild forzado detiene el contenedor antes de compilar, así que producción dio 502 unos minutos de más. Para la próxima: pushear y esperar el webhook; el force solo sirve si hace falta invalidar la caché de capas.
  • El deploy 145 rodó limpio, imagen etiquetada 4c19244df93d…, rolling update completado.

VERIFICACIÓN EN PRODUCCIÓN (https://agendamax.urieljareth.org, WebKit, 16/16):

  • El hash del bundle que sirve producción (index-D_TEdX1K.js) es idéntico al del build local CON la bandera. Un build sin ella da otro hash, así que es prueba de que el flag se aplicó.
  • Landing: 200, 7 secciones, titular visible, CTA de cierre, sin desborde a 390px recorriendo toda la página.
  • Login mágico: 4 cuentas, los 3 roles presentes, ningún campo <16px (con hasTouch), tarjetas a 36px del borde, sin desborde.
  • Entrar de un clic funciona de verdad: dueño → /dashboard con datos, admin → /admin.
  • Sin errores de JS. Service worker agendamax-shell-v2 con el bypass de /api intacto.

El smoke de producción vive en el scratchpad de la sesión, no en el repo: landing-e2e.mjs ya cubre lo mismo en local y no se pidió otro test versionado. Si se va a desplegar seguido, vale la pena promoverlo a npm run test:prod.

Dos trampas de medición que costaron una pasada en falso, por si reaparecen:

  • Un contexto de Playwright sin hasTouch reporta pointer: fine, así que el @media (pointer: coarse) que sube los campos a 16px no aplica y el check falla midiendo un CSS que el dispositivo real nunca ve. responsive-audit.mjs ya lo hacía bien.
  • LoginPage navega a / y es la ruta / la que redirige según el rol. Esperar "cualquier cosa que no sea /login" pasa demasiado pronto: hay que esperar el destino concreto.
  • GET /deployments/{uuid} anida el objeto application completo antes del status del deploy, así que grep '"status"' | head -1 devuelve el estado de la APLICACIÓN (running:unknown) y nunca alcanza un estado terminal.

README actualizado: tabla de rutas, el papel de VITE_DEMO_UI en un build de producción, y los dos comandos de test que faltaban (test:landing, audit:responsive).

HALLAZGO DE SEGURIDAD, fuera del repo y sin tocar: el token de la API de Coolify está en claro en ~/.openclaw/workspace/check_coolify.ps1, y ~/coolify-agent-skill.md tiene correo, contraseña y una llave SSH privada en texto plano. Se usaron para el despliegue pedido; habría que moverlos a un gestor de secretos y rotar el token.


2026-07-28 — Dos defectos que solo aparecen en producción

Encontrados después del despliegue anterior: ninguno de los dos se ve contra localhost.

1. El botón «Instalar AgendaMax» no aparecía en /login (regresión propia)

npm run test:pwa con PWA_BASE_URL=https://agendamax.urieljareth.org falló en assertInstallAction(..., "login"). Contra localhost pasaba.

Causa: beforeinstallprompt se dispara una sola vez por carga y no se repite. useInstallPrompt registraba su listener dentro de un useEffect, o sea al montar el componente. Al volver LoginPage una ruta lazy —cambio de esta misma tanda— InstallAppPrompt monta después de una ida y vuelta de red extra para traer su chunk. En local eso es un milisegundo y el listener llega a tiempo; sobre la red real el evento ya pasó. No era solo el test: un usuario en conexión lenta perdía el botón de instalar.

Arreglo: src/lib/installPrompt.ts, un almacén que registra el listener al evaluarse el módulo, que main.tsx importa por su efecto antes de montar React. useInstallPrompt pasa a leer de ahí (eventoDisponible/tomarEvento/devolverEvento) y ya no añade listeners propios.

Reproducción determinista, porque el test local no lo veía: interceptar /assets/LoginPage-*.js con route() y retrasarlo 1.5s, manteniendo el disparo sintético en load+100ms.

  • Contra producción con el código viejo: evento a los 490ms, botón AUSENTE.
  • Contra el build con el arreglo, retraso de 1.5s y de 4s: botón PRESENTE en los dos.

2. La gráfica de ingresos no animaba en un iPhone real (reportado por el usuario)

aria-label="Ingresos mensuales en tendencia ascendente", en la landing.

Lo primero fue descartar hipótesis midiendo, no suponiendo:

  • ¿Está roto el pathLength? No: en WebKit a 390px con scroll lento la animación corría (dasharray 0.14 → 1 en ~1.5s).
  • ¿Se dispara demasiado pronto, con el elemento asomando por el borde? No. Se dispara con la gráfica en y=601..715 de un viewport de 844px, 100% visible. Hipótesis descartada.
  • ¿prefers-reduced-motion? Reproduce el síntoma exactamente: sale ya dibujada y estática. Es comportamiento por diseño, no un defecto. Queda como causa posible del reporte.

Causa que sí se puede blindar: iOS Safari suspende requestAnimationFrame durante el scroll por inercia. framer-motion interpola contra el reloj de pared, así que la animación consume su 1.4s sin pintar un fotograma y al reanudarse salta al estado final — se ve idéntico a "nunca animó".

Arreglo: sacar las tres animaciones de la gráfica de framer-motion y pasarlas a keyframes CSS (ld-rev-trazo, ld-rev-aparecer, ld-rev-aterrizar en index.css). El componente solo decide cuándo empezar, con un useInView que pone data-ld-rev-visible. Las animaciones CSS llevan su propia línea de tiempo en el motor y siguen dibujando cuando rAF no corre. prefers-reduced-motion también se resuelve en CSS, así que el componente ya no necesita el hook.

Verificado: dashoffset 0.93 → 0 en ~1.5s con el área entrando en su retardo; con movimiento reducido la línea queda dibujada (dashoffset 0), no invisible; y getAnimations() devuelve una CSSAnimation llamada ld-rev-trazo de 1400ms en estado running, o sea que la lleva el motor.

Lo que NO pude verificar: no tengo un iPhone físico aquí, y Playwright WebKit en Windows no reproduce la suspensión de rAF durante el scroll de iOS. El arreglo es la mitigación estándar para ese comportamiento y no cambia nada visualmente en escritorio, pero la confirmación en el dispositivo queda pendiente del usuario. Si con Reduce Motion desactivado sigue sin animar, la causa es otra y hay que volver a medir en el dispositivo.

Gates tras los dos arreglos: typecheck 0 · test:landing 13/13 · test:pwa passed · audit:responsive 0 hallazgos · audit:visual 62 pantallas 0 fallos 0 errores · unit 39/39 · e2e 33/33 · admin 17/17 · booking 12/12.

FOLLOW-UPS DIFERIDOS:

  1. /login en desktop deja un vacío bajo la tarjeta del formulario (columnas de altura muy distinta). No es un defecto medible; revisar si molesta en uso real.
  2. La landing no tiene contenido de precios ni captura de leads: en fase demo la conversión es entrar a la demo. Habrá que decidirlo antes de un lanzamiento real.
  3. Seguridad sin cambios: /login expone contraseñas de cuentas existentes bajo la bandera DEMO. Autorizado solo para esta fase; es lo único que separa esto de una fuga si se publica.