Cómo trabajo (pitch 90s)
Resumen para la entrevista
Soy front senior en el ecosistema LATAM Data / Datadem.
Trabajo el día a día en tres frentes que se complementan:
-
Airtalk Chat — conversación NL→datos (lo que
históricamente se asocia a Arcwise / core-api
/agent_poc). Hoy vive en Experts: UI React + BFF
Next + agente.
-
Data Boards — dashboards HTML/React generados
desde el análisis: canvas, chat de edición vía MCP, share ACL
owner/editor/viewer.
-
Value Capture (DataHub) — módulo de gobierno de
datos: catálogo de data products, wizards multi-step, tablas
pesadas, roles Azure AD, BFF a Cloud Run.
Cómo laburo: ownership de features end-to-end (UI →
BFF → contrato con core/MCP/VC-service), lógica de dominio en
módulos puros testeables, fail-closed en auth/permisos, y UX
accionable (nunca un error sin CTA). Code review con foco en races,
tokens y verdad de permisos entre UI y API.
Día a día concreto
- Next.js App Router + TypeScript + NextAuth (Google / Azure AD).
- i18n (es/en/pt) en Airtalk; Hangar + Ant Design en DataHub.
-
Deploy Cloud Run; secrets vía Secret Manager; ambientes
local/dev/prod.
-
Selftests de lógica pura en Data Boards (~450 asserts en
accesos) cuando el dominio lo merece.
Mapa de productos ↔ repos
Gitlab
Soporte: airtalk-data-mcp (tools reportes + ACL),
airtalk-core-api / agent (/agent_poc),
data-governance-platform-value-capture-service.
Arquitectura · Airtalk Chat
Experts + agent_poc (ex conversación / Arcwise)
Qué es en la práctica
El usuario escribe en lenguaje natural; el front arma un historial
tipado, el BFF llama a core-api POST /agent_poc (o
knowledge layer en poc-2), y la UI renderiza respuestas con tablas,
SQL, costos y estados de carga. La ruta vieja
/talk/conversacion redirige a Experts cuando el flag
está on.
Composición técnica
Landing / talk (ChatInput) ──redirect──► /[locale]/experts
ExpertChat (estado + localStorage + ?thread=)
→ POST /api/experts/ask (BFF, maxDuration ~1200s, JWT NextAuth)
→ lib/experts/core-api.ts
→ Cloud Run core-api /agent_poc (agente + tools SQL / datos)
← mensajes tipados + conversation[]
→ ExpertMessagesList (markdown, tools, SQL)
→ opcional: POST /api/experts/share → GCS → /share/[id]
Shell y auth
-
MainSection: full-bleed en Experts/Reports (sin
ChatInput global); en /talk muestra sugerencias +
input que redirige a /experts al enviar.
-
SessionWrapper: NextAuth (Google + Azure), dominios
@latam*, autologin, banner si Akamai Edge Access Denied
(lib/edge-access.ts).
-
Middleware solo i18n; el gate de producto es sesión en cliente +
JWT en cada BFF.
Piezas clave (repos reales)
-
components/experts/ExpertChat.tsx — hilo, restore,
share, thinking_mode, edge errors.
-
app/api/experts/ask/route.ts +
ask-knowledge (POC knowledge layer).
-
lib/experts/core-api.ts — agent_poc; reintento con
nuevo conversation_id si cache devolvió
{}.
-
Legacy paralelo:
useChat (Vercel AI) +
/api/chat streaming + /api/stream
(Pub/Sub SQL). /talk/conversacion → redirect Experts.
-
“Arcwise” en FAQ son links externos a sql.arcwise.app; el chat
productivo que mantenemos es Experts → agent_poc.
Ejemplo de decisión Sr
El agente cachea fallos por conversation_id. En
core-api.ts detectamos body vacío y
reintentamos con UUID nuevo. Ownership del
contrato FE↔agente, no solo pintar bubbles.
Arquitectura · Data Boards
airtalk-data-frontend-service
Composición
/reportes ReportsList ← GET /api/reports + /shared
/reportes/[id] ReportViewer
GET …/meta permissions + metadata.json + probe MCP
POST …/chat modify_suggestions → write GCS (canvas)
POST …/refresh-data read_only_sql + update_data
GET/POST/DELETE …/access ACL MCP (solo owner)
POST …/publish publish_report + bake HTML opcional
PUT …/layout|instructions
MCP: JSON-RPC /mcp (identity token IAM + access-token Google user)
GCS: reports/{email}/{id}/metadata.json + _idx/{id}
Capas
-
UI:
ReportViewer,
ShareReportDialog, Canvas/ReportCanvas,
useReportRefresh.
-
Dominio puro:
shareAccessLogic, authorizeAccessLogic,
reportAccess, permissions.buildReportPermissions
— selftests (~450 asserts) sin React.
-
Adapters:
gcs.ts (owner vía _idx,
origin snapshot), mcpChat.callMcpTool,
googleAccessToken.
-
Gate BFF:
apiAuth —
allowPublished / requireEditor / requireOwner; fallback GCS si
MCP report_not_found o caído.
Ejemplo ACL (feature que lideraste)
canShare solo si rol owner y
(accessSource === "mcp" o fallback GCS con
gcsFallbackReason === "report_not_found"). Si MCP
cae (mcp_unavailable) →
acl_unconfirmed: editás el canvas pero
no Compartir (fail-closed). Chip “Solo lectura”
debía ser !canEdit, no !isOwner (editores
podían editar con label mentiroso).
Chat sobre el reporte
Mismo patrón que Experts pero tools de render:
useGoogleSession + probe MCP;
mcpChat.ts encadena modify_suggestions → persist
metadata (React canvas) o modify_report (HTML bake). Filtros
locales en chatFilters.ts cuando el pedido es
“filtrá por región” sin ir al LLM.
Arquitectura · Value Capture (DataHub)
data-governance-platform-frontend
Stack
Next.js (App Router) + React, TS strict, Hangar +
Ant Design + Tailwind, TanStack Query, NextAuth
Azure AD, i18n es/en/pt. App en
data_governance_platform_frontend/src/ dentro del
monorepo DATAHUB (~20 módulos; VC es uno).
BFF (patrón oficial del repo)
Client + React Query
→ src/services/value-capture/ (Axios → /api/value-capture-service)
→ app/api/value-capture-service/** (BFF Route Handlers)
X-Serverless-Authorization (GCP ID token → Cloud Run IAM)
Azure-User-Authorization (token usuario NextAuth)
→ FastAPI value-capture-service api/v1/*
Features que el FE posee
-
Catálogo Data Products — tabla server-side, vistas custom,
deep-link al wizard.
-
Wizard create/edit — steps ↔
POST …/step/{n} + complete (multisquad,
WAVE 409).
-
Registro valor manual / automático + historial ejecuciones.
-
Change requests (mis solicitudes / approvals) + users-settings
por rol.
Archivos para citar
-
DataProductsTable/index.tsx,
DataProductWizard/DPWizard.tsx +
steps.tsx.
-
services/value-capture/index.ts,
shared/lib/axios (dual token).
-
useValueCaptureSiderItems,
VCViewerRestrictedRoute,
RoleProtectedRoute.
Ejemplo Sr en VC
Doble capa de auth: plataforma (Azure roles) + dominio VC
(user_profile → VC_ADMIN / VIEWER / editor). El sider
no pega APIs hasta conocer el rol (evita 403 en cascada).
Fetches dedupeados; filtros UI→API con helpers puros. Escala de
gobierno, no landing.
React — con ejemplos del repo
Mental model aplicado
Estado derivado (Data Boards)
// ReportViewer — no guardamos canShare en un useState paralelo
const canEdit = Boolean(perms?.canEdit) && !isPreviewView
const canShare = Boolean(perms?.canShare) && !isPreviewView
const isReadOnlyView = !canEdit
Bug real: chip “Solo lectura” usaba !isOwner → un
editor veía solo lectura pero podía chatear. Fix:
!canEdit.
Effects + Strict Mode (Experts / Boards)
En share, fetchAccessList usa mapa
accessListInflight porque Strict Mode monta dos veces
y abortar el único fetch rompía la carga. Mismo espíritu que
inflightKeys en Value Capture.
Keys estables
Lista de accesos ACL: key = email normalizado
(emailKey en shareAccessLogic.ts), no
index — si no, al revocar/reordenar el switch “puede editar” salta
de fila.
useMemo / useCallback
En DataProductsPage hay useMemo/useCallback
donde la tabla y los filtros son caros. En Boards, no memoizo el
dialog entero “por las dudas”: memoizo reglas en módulos puros y
dejo React renderizar UI barata.
Estado & data fetching — ejemplos
BFF, races, optimistic, tokens
BFF en los tres productos
-
Chat: browser nunca llama agent_poc con secrets;
pasa por
/api/experts/ask.
-
Boards: browser →
/api/reports/... → GCS SA + MCP con identity token
+ user access-token.
-
VC: React Query →
/api/value-capture-service/... → GCP + Azure headers.
Optimistic grant (Boards)
setAccessList(list => applyOptimisticGrant(list, entry))
const res = await fetch(.../access, { method: 'POST', body })
if (!res.ok) {
setAccessList(list => rollbackOptimisticGrant(list, email))
setError(mapAccessMutationError(...)) // 401 → CTA renew
}
Sesión Google vencida (Boards)
Chat ya tenía banner + renewSession
(useGoogleSession → signIn('google')).
Share alineado: al click Compartir,
resolveShareOpenAction → renew si expired; si 401 en
/access, banner con el mismo CTA (no mensaje muerto).
Server state en VC
React Query + services Axios. Invalidación al registrar valor o
guardar wizard. Filtros de tabla se traducen a query params del
service — una sola fuente de verdad en el servidor, UI como
proyección.
Performance — ejemplos
Qué tocamos en prod
-
Boards: skeleton medido
(
ReportSkeleton / writeSkeletonMeasured
para no layout-shift al abrir un reporte; canvas React vs iframe
según capabilities.
-
Chat:
maxDuration alto en route;
UI con loading/thinking modes; no bloquear scroll del hilo
(useLayoutEffect en ExpertChat).
-
VC: paginación + filtros server-side; evitar
refetch duplicado; columnas visibles configurables (menos DOM).
-
Landing Airtalk: recovery script si chunks no hidratan (extensiones
corporativas) — resiliencia percibida como perf.
Frase: “En DataHub/Boards el cuello suele ser red + payload, no el
virtual DOM. Primero contrato/BFF, después memo.”
Calidad — ejemplos
Tests y contratos
-
lib/reports/reportAccess.integration.selftest.ts —
matriz roles, gates, optimistic, session_expired,
resolveShareOpenAction.
-
Selftests chicos:
chatFilters,
refreshAll, shareDialog.
-
VC: lint/prettier estrictos (Hangar); permisos de ruta como
config revisable en PR
(
routesPermissions/items/value-capture.ts).
-
Types discriminados: resultados
{ ok: true } | { ok: false, code } en MCP/access.
Comportamiento Sr — cómo se ve en PRs
Historias cortas
-
Verdad de permisos: escondí Compartir si
/access dice not_acl_owner; bajé flags en cliente
(onShareAccessDenied.
-
Seguridad FE: cleanup de endpoints legacy
peligrosos (MR seguridad) + no exponer tokens al browser.
-
Producto: modal Compartir alineado al share de
hilos Airtalk (misma gramática mental para el usuario).
-
Cross-squad: coordinar con MCP
(
grant_report_access / publish_report
cuando el FE solo no alcanza.
Problema → solución (casos reales)
Para “contame un feature donde hubo un problema y propusiste la
solución” · formato STAR corto
Plantilla verbal (90–120 s): Contexto →
síntoma → causa →
opción que descarté → solución →
cómo lo validé.
1. Data Boards — Compartir / ACL
Problema: GCS decía “el reporte es tuyo” (carpeta
/ metadata) pero MCP tenía otro owner o rol editor. La UI ofrecía
Compartir y fallaba / confundía (editores con chip “Solo lectura”
pero podían editar).
Solución propuesta: una sola verdad de permisos —
buildReportPermissions:
canShare solo con owner MCP confirmado, o
report_not_found (not_migrated). Si MCP cae →
acl_unconfirmed, no Compartir. Chip =
!canEdit. Si /access niega ownership →
cerrar modal y bajar flags (onShareAccessDenied).
Lógica pura + ~450 selftests.
Citar: permissions.ts, apiAuth.ts,
ShareReportDialog, shareAccessLogic.
2. Data Boards — Token Google vencido en Compartir
Problema: chat ya pedía renew de sesión; el modal
Compartir abría con error 401 / mensaje sin acción (“sesión
vencida”) y el usuario no sabía qué hacer.
Solución: mismo contrato que el chat —
useGoogleSession también para share; al click
Compartir, resolveShareOpenAction → si expired,
signIn directo; si 401 en vuelo, banner con CTA
“Renovar sesión de Google”.
3. Data Boards / MCP — owner degradado al self-grant
Problema (backend MCP):
on_conflict_do_update en grant podía pisar el rol del
owner si alguien se auto-agregaba; lista de accesos inconsistente.
Solución: en report_access.py —
rechazar grant al owner_id; autocura del rol owner en
list_report_access; owner_id manda sobre
filas degradadas en user_management. FE alinea: no te agregues a
vos mismo (invalid_target / shareSelfTarget).
4. Data Boards — Akamai rompe PATCH / bodies con SQL
Problema: edge Akamai delante de
*.appslatam.com respondía 501 a PATCH
y WAF en POSTs con SQL/markdown en claro (share de hilos / layout).
Solución: APIs de layout/instructions pasan a
POST (no PATCH). Share Experts:
encodeShareRequestBody (payload base64) para que el
WAF no inspeccione SQL; límites de tamaño en route; UX de
edge-access (no martillar login si Access Denied).
Citar: layout/route.ts,
share-payload.ts, lib/edge-access.ts.
5. Landing Airtalk — la app “no carga” (extensiones)
Problema: en corporativo, extensiones (p. ej.
LATAM Meet Tools) rompen MIME/chunks de Next: pantalla en blanco,
soporte inundado, usuario cree que Airtalk está caído.
Solución: script inline en
app/layout.tsx (no depende de chunks) +
AppLoadMarker que setea
__AIRTALK_LOADED__. Si a los 15s no hidrató → overlay
con diagnóstico (incógnito / desactivar extensiones) y recargar.
Resiliencia percibida > culpar al usuario.
6. Landing / auth — Akamai Access Denied en login
Problema: autologin Google + retries empeoraban
el handshake Bot Manager → Access Denied en loop.
Solución: SessionWrapper intercepta
fetch, marca edge-denied, frena autologin/reintentos,
banner con bypass App Engine (Cloud Run sin hostname Akamai). No
“reintentá login” a ciegas.
7. Airtalk POCs / Experts — agent_poc responde {}
Problema: core-api /agent_poc tiene
@cache(); un fallo previo quedaba cacheado por
conversation_id → UI “sin respuesta” en Experts y
POCs (/poc, /poc-2 knowledge layer).
Solución: en lib/experts/core-api.ts
detectar body vacío y reintentar con nuevo UUID;
mensaje accionable (“Nueva conversación” / Quick). POC-2 usa
/api/experts/ask-knowledge →
agent_poc_knowledge_layer (variante sin forzar email
en local ADC cuando rompe).
8. Plugin ChatGPT / Custom GPT + MCP
Problema: exponer Airtalk MCP a Custom GPT vía
Cosmos Bridge (OAuth GPT Actions). Los scopes GCP/BQ/Drive que
pide MCP no se pueden imponer bien desde la UI de GPT
Actions → integración frágil / pausada; usuarios
esperaban “plugin = mismo poder que la web”.
Solución / postura Sr: documentar el flujo real
(docs/mcp_setup_custom_gpt.md: authorize/token
Bridge, schema GPT Action, scopes). Separar expectativas: Custom
GPT como cliente OAuth del bridge, no como reemplazo del BFF web.
Guía en landing (UserGuide: chatgpt/plugin). Mientras scopes no
cierren, no vender feature a medias; priorizar Experts web + MCP
tools con auth Cloud Run + user token.
Citar: gpt_actions_schema.py, profiles
gpt_actions.bridge_server, doc Custom GPT.
9. Tools MCP — edición de reportes desde el chat
Problema: el LLM pedía cambios al dashboard pero
el FE no podía confiar solo en el model: filtros ambiguos, sesión
vencida, tools faltantes, o re-bake HTML lento.
Solución: pipeline en mcpChat.ts:
probe MCP → dual auth (IAM + access-token) →
modify_suggestions; en React canvas
metadataOnly + persist GCS en BFF (sin bake).
Filtros locales en chatFilters.ts cuando el intent
es claro (“filtrá por región”) sin round-trip. UX:
session_expired / tools_missing /
checking con CTAs distintos.
10. Tools MCP — Strict Mode / doble fetch de ACL
Problema: al abrir Compartir, Strict Mode abortaba
el único GET /access en vuelo → lista vacía o error
intermitente.
Solución: mapa
accessListInflight + cache corta (sin AbortSignal
compartido que mate el fetch único). Misma idea que
inflightKeys en Value Capture.
Cómo elegir cuál contar
-
Permisos / seguridad de producto: casos 1, 3, 4.
-
Resiliencia / plataforma LATAM: 5, 6, 4.
-
IA / agente / MCP: 7, 8, 9.
-
Detalle de ingeniería FE: 2, 10.
Puente a Humand
Misma clase de problemas
Humand: mobile-first HR + Sammy AI. Nosotros: chat NL→acción
(Experts/agent_poc), permisos finos (ACL Boards / roles VC),
wizards y tablas densas (Value Capture). El Sr une
confianza del usuario, contratos con backend y
estados honestos (loading / token / forbidden).
Banco Q&A (respuestas con repos)
90–120 s · practicar en voz alta
Q1 — Feature complejo
Data Boards share ACL. Modal
ShareReportDialog + MCP grant/revoke/publish.
permissions.ts define canShare; apiAuth autoriza;
selftests. Tradeoff: GCS owner ≠ ACL owner → fail-closed
(acl_unconfirmed).
Q2 — Chat / agente
Experts. ExpertChat → /api/experts/ask →
agent_poc. Manejo de conversation_id cacheado, thinking_mode,
share de hilo, persistencia local. El valor está en el BFF y el
contrato de mensajes, no solo en el bubble UI.
Q3 — Tabla / wizard enterprise
Value Capture. DataProductsPage + wizard
multi-step + React Query + BFF Azure/GCP. Dedupe de fetches,
vistas personalizadas, RoleProtectedRoute. Escala de gobierno de
datos, no landing marketing.
Q4 — useMemo
Casi nunca default. En VC sí aparece alrededor de tabla/filtros.
En Boards priorizo lógica pura testeada y can* derivados. Si el
Chapter Lead aprieta: “mido con profiler antes de memoizar el
árbol”.
Q5 — Sesión vencida
useGoogleSession + renew en chat Boards; mismo patrón en
Compartir (resolveShareOpenAction). En Experts, NextAuth JWT en
el BFF; sin email no llamamos agent_poc a ciegas.
Q6 — Cómo estructurás un módulo nuevo
Como VC: modules/x/{components,pages,hooks,types} +
services/x + app/api/x + permisos en
constants. En Boards: UI en components/Reports, dominio en
lib/reports, API en app/api/reports. Misma idea: bordes claros.
Q7 — Conflicto UI vs API de permisos
Historia del chip Solo lectura / canShare. Regla: la UI no puede
prometer más que el authorize del BFF. Si /access 403 → cerrar
modal y bajar canShare en estado.
Q8 — Feature con problema y solución que propusiste
Elegí de Problema → solución: ACL canShare
(fail-closed), landing load-recovery/Akamai, o agent_poc cache
{}. Contá tradeoff descartado (ej. “confiar solo en
GCS”) y cómo lo validaste (selftests / repro en edge).
Q9 — Cómo te sumás a un squad nuevo
Leo CLAUDE/README + un flujo crítico (en DataHub el BFF auth; en
Airtalk un ask Experts). Uno PR chico de bug/UX. Después ownership
de un slice (ej. share o un step del wizard). Pregunto métricas a
90 días.
Checklist del día
-
Pitch 90s: Chat (agent_poc) + Boards (MCP/ACL) + VC (DataHub BFF).
-
3 STAR listos: share ACL · landing/Akamai o load recovery ·
agent_poc
{} / MCP chat.
-
Sección Problema → solución: elegí 2 según
la pregunta (permisos vs IA vs plataforma).
-
Dibujá de memoria el diagrama BFF de al menos un producto.
-
Preguntas: ¿web o RN? ¿dolor FE? ¿éxito a 90 días?