T-HUB/docs/T-HUB-VALIDACION.txt
hacklab cfabbf20ef style: light theme and hand-inked logo matching acampada26s.net
The previous pass kept the dark base and the red icons, which did not
read as the acampada. Now:

- Light theme: white page, black ink and borders, black buttons with a
  hard neon offset shadow, neon for focus and the active nav item;
  h1/h2 in Anton with the stepped neon shadow of the site headings.
- Nav, empty-state, spinner and pattern assets in black; platform icons
  keep their dark disc with a neon stroke.
- New mark: three dome tents wired from their doors into a switch with
  an antenna, drawn in the inked style of the acampada logo (curved
  poles, ball ends, hatching) over a neon offset copy. LogoMark renders
  it inline; visible product name is T-HUB.
- docs/brand: t-hub.svg and a lockup with the Anton wordmark converted
  to outlines so it renders without the font.
- Public submission page moved to the light palette.

Checked with headless Chromium screenshots (offline sample data) on
desktop and mobile.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-15 14:40:02 +02:00

162 lines
7.3 KiB
Text

T-HUB - Guía de validación de cambios
=====================================
Para: quien revise y valide los cambios de T-HUB antes de llevarlos a
upstream (gitlab.moxexley.com/mediatools/tacticalmediahub) o a producción.
Base: upstream main a6843e9.
Commits a revisar (en orden):
feat: wire the Instagram connector into connect and publishing
feat: outreach contacts - track hand-written DMs per collective
chore(db): force row-level security on media_assets
style / docs: estilo de la web, logo y documentación
Cada commit se puede revisar y aplicar por separado.
0. PREPARACIÓN
--------------
corepack enable
pnpm install --frozen-lockfile
pnpm exec turbo run build typecheck --filter=@tactical-media-broadcast-hub/api...
Para los tests que usan Postgres hacen falta las variables *_IT_DATABASE_URL
de cada suite; sin ellas se saltan (comportamiento de upstream).
Lint: apps/api ya tenía 54 errores antes de estos cambios y sigue teniendo
los mismos 54. Los archivos tocados pasan eslint sin errores.
1. INSTAGRAM
----------------------
Qué cambia
- createInstagramConnector registrado en
apps/api/src/connections/connector-registry.ts y en connectorFor de
apps/api/src/publishing/publishing.service.ts.
- Política de publicación instagram: { idempotency: "none" } en
packages/connector-runtime/src/policy.ts (Graph no tiene clave de
idempotencia; un envío dudoso no se repite).
- Las credenciales de la app de Meta se leen SOLO de
INSTAGRAM_CLIENT_ID / INSTAGRAM_CLIENT_SECRET. Se quitan los campos
clientId/clientSecret del formulario y ya no se copian al vault.
- El proveedor sale desactivado (connectEnabled=false) sin esas dos
variables, con una nota que lo explica.
- Si la persona autoriza varias cuentas de Instagram en el diálogo de
Meta, la conexión falla en vez de elegir la primera.
Tests automáticos
pnpm --filter @tactical-media-broadcast-hub/connector-instagram test
esperado: 37 pass
cd apps/api && npx vitest run src/connections
esperado: 18 pass (incluye "offers Instagram only with the Hub's
Facebook app configured, and keeps the app secret out of the browser")
Prueba manual (entorno de pruebas, app de Meta en modo desarrollo)
1. Sin variables: GET /connections/providers -> instagram enabled=false.
2. Con INSTAGRAM_CLIENT_ID/SECRET: enabled=true.
3. En la app de Meta, Valid OAuth Redirect URI =
${API_URL}/connections/callback (exacto).
4. Canales -> Conectar Instagram con una cuenta Tester Business/Creator
vinculada a una página -> vuelve con connect=success y la conexión
sale healthy con @usuario.
5. Autorizar dos cuentas a la vez -> connect=failure.
Límites conocidos (no resueltos en este commit)
- Publicar imágenes NO funciona todavía: Meta descarga la imagen de una
URL pública y los medios están cifrados en el almacén. Hace falta un
endpoint temporal que sirva el JPEG descifrado (TTL corto, solo
borradores aprobados). Además la regla del proyecto es que los bytes
no pasen por la API. Decisión de diseño pendiente.
- refreshCredentials usa grant_type=ig_refresh_token contra
graph.facebook.com, que es el método de "Instagram Login", no de
Facebook Login. Comprobar con un token real; posible caducidad a 60
días.
2. OUTREACH
---------------------
Qué cambia
- Migración 041_outreach.sql: tabla outreach_contacts con RLS
ENABLE+FORCE y WITH CHECK, índice único (workspace, platform,
lower(handle)), trigger de workspace inmutable, permisos
outreach.view (Owner, Admin, Editor, Moderator) y outreach.manage
(Owner, Admin, Editor).
- API: GET/POST/PATCH/DELETE /outreach/contacts (apps/api/src/outreach).
Importación de hasta 500 por petición; las cuentas ya existentes se
saltan. La auditoría guarda altas, bajas y cambios de estado, nunca el
texto del mensaje.
- Web: /outreach (barra lateral y "More"). Importar CSV, plantilla con
{name} y {handle}, filtros, "Copy & open profile", contador diario
con aviso a partir de 20.
- El Hub no envía mensajes. No hay llamadas a APIs de redes en este
módulo. Ver README, sección "Panel Outreach", para el motivo.
Tests automáticos
cd apps/api && npx vitest run src/outreach esperado: 8 pass
pnpm --filter @tactical-media-broadcast-hub/web test:outreach
esperado: 4 pass
Verificado además (fuera del repo): la migración 041 y todas las
consultas del servicio se ejecutaron sobre PGlite (Postgres compilado a WASM)
como rol sin privilegios con app.workspace_id fijado: aislamiento entre
colectivos, salto de duplicados en mayúsculas/minúsculas, sellos de
sent_at/replied_at, vuelta a "todo" limpia los sellos.
Prueba manual
1. Aplicar migraciones (src/db/migrate.cli.ts).
2. Como Editor: importar
name,platform,handle,profileUrl,category,language
Prueba,bluesky,prueba.bsky.social,https://bsky.app/profile/prueba.bsky.social,test,es
-> "1 contacts added". Reimportar -> "0 added, 1 already on the list".
3. Plantilla "Hola {name}" -> el contacto pendiente recibe "Hola Prueba".
4. Copy & open profile -> abre el perfil en otra pestaña con el texto
en el portapapeles.
5. Mark sent -> estado sent y sello de fecha; aparece outreach.sent en
/audit sin el texto del mensaje.
6. Como Analyst (sin outreach.view) -> GET /outreach/contacts da 403.
7. Desde otro colectivo -> la lista no muestra los contactos del primero
y PATCH sobre su id da 404.
8. profileUrl con http:// o javascript: -> 400.
3. RLS EN media_assets
----------------------
Qué cambia
- 042_media_assets_rls.sql: ENABLE + FORCE RLS y política con
WITH CHECK.
- Las consultas a media_assets (media.service.ts, publishing.service.ts)
filtran también por app.workspace_id.
Verificación
- cd apps/api && npx vitest run src/media src/publishing
esperado: 114 pass, 49 skipped (necesitan Postgres).
- En la base de datos, después de migrar:
SELECT relrowsecurity, relforcerowsecurity
FROM pg_class WHERE relname = 'media_assets'; -> t, t
- Prueba manual: subir un medio en el colectivo A y pedir
GET /media/<id> con sesión del colectivo B -> 404.
4. ESTILO Y LOGO
----------------
- apps/web/src/app/globals.css: paleta de acampada26s.net. Fondo
blanco, texto y bordes negros, botones negros con sombra neón
desplazada, foco y elemento activo en neón #00e5ff. Títulos h1/h2 en
Anton en mayúsculas con la sombra neón escalonada de la web.
- Iconos de navegación en negro; iconos de redes con trazo neón.
- Logo: tres tiendas iglú cableadas a un switch con antena, en el
estilo a tinta del logo de la acampada (docs/brand/t-hub.svg;
t-hub-lockup.svg/png con el nombre en Anton convertido a trazos).
Componente LogoMark sustituido; nombre visible "T-HUB".
- Fuente Anton en apps/web/public/fonts (licencia SIL OFL incluida).
- Página pública de buzón (/submit/[token]) en claro.
Revisado con capturas (Chromium sin interfaz, modo offline con datos de
ejemplo): Streams, Compose, Outreach, Channels, Approvals, Onboarding,
/submit en escritorio y móvil. Falta revisar Control Room y Calendar con
datos reales.
5. SEGURIDAD
------------
La revisión de seguridad se entrega en privado a quien mantiene el
despliegue. Los fallos se comunican por canal privado, no en issues.