MANUAL INTERNO — AURA
Documentación de Aura
Producto de software independiente de MayaCode (no comparte código, base de datos ni autenticación con Obsidian): un sistema de gestión de marketing multi-workspace, pensado para venderse a terceros, con generación de contenido por IA, conexión a redes sociales por OAuth y publicación programada.
00 Qué es Aura
Aura es un SaaS multi-tenant: cualquier persona puede registrarse, crear su propio workspace y gestionar su marketing ahí — a diferencia de Obsidian, que administra el negocio de MayaCode puertas adentro, Aura está diseñada para venderse a clientes externos. Por eso no puede depender de Cloudflare Access (exigiría que cada cliente comprador tuviera su propio tenant de CF Access) y trae su propio registro/login.
El eje del producto es el contenido de marketing, de la idea a la publicación:
Contactos/listas/etiquetas (un CRM ligero) fue lo primero que se construyó como base multi-tenant; el pilar de redes sociales se construyó después sobre el mismo esquema.
01 Arquitectura
Tres Workers de Cloudflare independientes — el tercero existe solo porque el framework del backend no expone un handler de cron.
| Rutas | app/api/* | route.ts con GET/POST/PATCH/DELETE, igual convención que Obsidian |
| Base de datos | env.DB | D1, SQL crudo, sin FK — la integridad la garantiza la app |
| IA | _lib/ai.ts, _lib/prompts.ts | fetch directo a la API de Anthropic (sin SDK) + Workers AI para imágenes |
| Adaptadores sociales | _lib/platforms/*.ts | Facebook, Instagram, X, LinkedIn, TikTok |
| Vistas | src/views/*.tsx | sin router de URL, un tab en memoria + switch |
| Cliente API | src/api.ts | api() sobre fetch, header X-Workspace-Id |
| Único trabajo | scheduled() | cron cada minuto → POST a aura-backend con un secret compartido |
vinext (el framework del backend) bundlea a un handler que solo expone fetch, no scheduled — intentar agregarlo arriesgaba romper el pipeline de build de Next.js/RSC. Un Worker aparte con su propio cron trigger, que solo llama a un endpoint interno protegido (/api/internal/publish-cron, header X-Cron-Secret), evita ese riesgo por completo.02 Autenticación y sesión
Sesión propia por cookie — nada de Cloudflare Access, nada de JWT.
Contraseñas con PBKDF2-SHA256 (100,000 iteraciones — crypto.subtle de Workers no soporta más), salt aleatorio por usuario. Sesión: token aleatorio de 32 bytes, solo su hash SHA-256 se guarda en sessions; el token crudo va en una cookie aura_session (HttpOnly, SameSite=Lax, 30 días) — a diferencia de un JWT, es revocable en el momento con solo borrar la fila.
03 Workspaces y roles
Un usuario, varios workspaces, un rol distinto en cada uno.
workspace_members (roles owner/admin/member, texto libre) es la única fuente de autorización — cada ruta de recurso resuelve el workspace activo desde el header X-Workspace-Id (no query param, no hay restricción de preflight de Access que lo impida aquí) y confirma membresía antes de tocar nada.
04 Contactos, listas y etiquetas
Fase 1 del producto — la base multi-tenant sobre la que se construyó todo lo demás.
CRUD estándar de contacts/tags/contact_lists, con listas estáticas o por segmento (segment_rules en JSON). Sirve tanto de piso de pruebas del modelo multi-tenant como de audiencia futura para email marketing (esquema ya migrado, sin rutas todavía).
05 Brand kit
El contexto de marca que se inyecta en cada generación de IA — sin esto, la IA no sabe qué marca es.
Un brand_profiles por workspace: misión, público objetivo, tono de voz, valores, colores, palabras prohibidas, competidores a diferenciarse de, y ejemplos de tono previo (few-shot). _lib/brand.ts lo carga y arma el system prompt que Claude recibe en cada una de las llamadas de §06 — el mismo contexto de marca, siempre, sin que cada endpoint lo repita.
06 Generación de contenido con IA
Siete llamadas a Claude, cada una con reglas de formato específicas por red social.
Cada llamada queda auditada en ai_generations (tipo, modelo usado, resumen del input). Modelo por defecto: claude-sonnet-5, vía fetch directo a la API de Anthropic — sin SDK, para no sumar una dependencia a un backend que hasta ahora no tenía ninguna de IA.
08 Posts y calendario
Una fila por cuenta destino — no un post "padre" con variantes.
social_posts modela "1 fila = 1 publicación en 1 cuenta": crear un post con varias cuentas destino hace fan-out explícito a N filas en la creación, en vez de un concepto de post+variantes. Estados: draft → scheduled → processing → published / failed. Programar (POST /posts/:id/schedule) no crea ninguna cola aparte — el cron de §09 escanea social_posts directo por status+scheduled_at.
09 Publicación automatizada
Un tick de cron por minuto, con reintentos y backoff.
_lib/publisher.ts reclama hasta 20 posts vencidos por tick (UPDATE ... WHERE status='scheduled', comprobando filas afectadas para evitar doble proceso), descifra el token de la cuenta, y llama al adaptador de la plataforma correspondiente. Un fallo incrementa attempts y reprograma con backoff (5 min × intento) hasta agotar max_attempts, momento en que el post queda failed con el error real de la plataforma en error_message.
10 Analítica
Ingesta manual por ahora — no hay sincronización automática desde cada red todavía.
social_post_metrics guarda snapshots por post (impresiones, alcance, likes, engagement rate...). /analytics/summary agrega por plataforma en un rango de fechas; /analytics/top-posts ordena por engagement. La carga de métricas (POST /analytics/metrics) es manual/externa hoy — ninguna ruta hace pull automático desde las APIs de Facebook/Instagram/X/LinkedIn/TikTok.
11 Referencia de API
Misma convención que Obsidian: {success, data} / {success: false, error}. Todo bajo /api/*.
12 Estado de despliegue
En producción, con dos cosas pendientes de configuración externa — no de código.
activo aura-backend, aura-admin, aura-cron desplegados; contactos/listas/etiquetas, brand kit, posts/scheduling, y el cron de publicación funcionando de punta a punta en producción.
bloqueado — R2 generación de imágenes con IA (/ai/image): R2 no está habilitado en la cuenta de Cloudflare (solo se activa desde el dashboard). El binding es opcional en código — sin él, el endpoint responde un error claro en vez de romper el despliegue.
bloqueado — credenciales
generación de copy (falta ANTHROPIC_API_KEY) y publicación real a Facebook/Instagram/X/LinkedIn/TikTok (faltan las apps de desarrollador de cada plataforma) — la revisión de la app de Meta en particular tarda días o semanas.
13 Despliegue y secrets
Nada de esto requiere volver a tocar código — son pasos de cuenta/CLI.
# infraestructura pendiente
# 1. Cloudflare Dashboard → R2 → Enable R2, luego:
wrangler r2 bucket create aura-media
# descomentar r2_buckets en aura-backend/wrangler.jsonc, habilitar acceso público
# al bucket, llenar PUBLIC_MEDIA_BASE_URL, y `npm run cf-typegen`
# 2. secrets con valores reales del usuario
wrangler secret put ANTHROPIC_API_KEY # aura-backend
wrangler secret put META_APP_ID # + META_APP_SECRET
wrangler secret put X_CLIENT_ID # + X_CLIENT_SECRET
wrangler secret put LINKEDIN_CLIENT_ID # + LINKEDIN_CLIENT_SECRET
wrangler secret put TIKTOK_CLIENT_KEY # + TIKTOK_CLIENT_SECRET
# 3. desplegar
cd aura-backend && npm run deploy # vinext build + wrangler deploy
cd aura-admin && npm run deploy # vite build + wrangler deploy
cd aura-cron && npm run deploy # wrangler deploy (cron trigger)
TOKEN_ENCRYPTION_KEY y CRON_SECRET ya están configurados — se generaron internamente (no dependen de ningún proveedor externo) y no hace falta volver a tocarlos.
Aura · MayaCode — documento vivo, generado a partir del estado real del código. ← Volver al ecosistema
07 Cuentas sociales y OAuth
Facebook, Instagram, X, LinkedIn y TikTok — flujo de autorización real, tokens cifrados.
/startgenera un nonce de un solo uso enoauth_states(concode_verifierpara el PKCE que X exige) y redirige a la plataforma;/callbackvalida ese nonce, intercambia elcodepor tokens, y los cifra con AES-GCM (TOKEN_ENCRYPTION_KEY) antes de guardarlos — nunca en texto plano en D1. Desconectar una cuenta es un borrado lógico (status='revoked'): la fila se conserva porque posts pasados siguen apuntando a su id.Facebook usa Facebook Login clásico (Página administrada, se resuelve la primera vía
/me/accounts). Instagram usa el producto separado Instagram API with Instagram Login — se conecta directo con la cuenta profesional, sin ninguna Página de Facebook de por medio. Ambos tokens se cambian de inmediato por uno de larga duración (60 días) y se renuevan solos antes de vencer — ver el manual de configuración para el detalle completo.