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.

Panelaura.mayacode.dev
APIaura-api.mayacode.dev
Base de datosCloudflare D1 (aura-db)
AutenticaciónSesión propia (cookie)

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:

Brand kit→ Generar copy con IA→ Post→ Programar→ Publicar→ Analítica

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.

aura-backendNext.js/vinext + D1
Rutasapp/api/*route.ts con GET/POST/PATCH/DELETE, igual convención que Obsidian
Base de datosenv.DBD1, SQL crudo, sin FK — la integridad la garantiza la app
IA_lib/ai.ts, _lib/prompts.tsfetch directo a la API de Anthropic (sin SDK) + Workers AI para imágenes
Adaptadores sociales_lib/platforms/*.tsFacebook, Instagram, X, LinkedIn, TikTok
aura-adminVite + React
Vistassrc/views/*.tsxsin router de URL, un tab en memoria + switch
Cliente APIsrc/api.tsapi() sobre fetch, header X-Workspace-Id
aura-cronWorker aparte, sin Next.js
Único trabajoscheduled()cron cada minuto → POST a aura-backend con un secret compartido
Por qué un tercer Worker: 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.

POST/ai/captioncopy para una plataforma, con hashtags y alt-text sugerido
POST/ai/adapt-platformsre-adapta un copy ya escrito a otras plataformas
POST/ai/content-calendarplanificación estratégica (sin copy final) por pilares de contenido
POST/ai/imageprompt de imagen (Claude) + generación real (Workers AI Flux) + sube a R2
POST/ai/hashtagshashtags en 3 categorías: nicho, comunidad, alcance amplio
POST/ai/comment-replysugiere respuesta o marca para escalar a un humano
POST/ai/insightsinterpreta métricas de un periodo y da recomendaciones

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.

07 Cuentas sociales y OAuth

Facebook, Instagram, X, LinkedIn y TikTok — flujo de autorización real, tokens cifrados.

GET /social-accounts/oauth/:platform/start→ consentimiento en la plataforma→ /callback→ social_accounts

/start genera un nonce de un solo uso en oauth_states (con code_verifier para el PKCE que X exige) y redirige a la plataforma; /callback valida ese nonce, intercambia el code por 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.

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/*.

PUT/workspaces/:id/brandupsert del brand kit
GET/social-accountscuentas conectadas (sin exponer tokens)
DELETE/social-accounts/:iddesconectar (soft, status='revoked')
POST/postscrea, con fan-out a N cuentas destino
POST/posts/:id/scheduledraft → scheduled
POST/internal/publish-croninterno, llamado solo por aura-cron (X-Cron-Secret)

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