Imported from Xamppy/Entity (
AGENTS.md). Install upstream withnpx skills add Xamppy/Entity. Copyright stays with the author.
Entity - AI Visibility Audit Platform (GEO/AEO)
Generative Engine Optimization & Answer Engine Optimization Plataforma SaaS multi-tenant para auditar la "AI Readiness" de sitios web.
Project Overview
Entity mide qué tan bien están optimizados los sitios web para motores de búsqueda con IA y comprensión de LLMs. Analiza factores como:
- LLM Readability Score: Qué tan fácil es para un LLM entender el contenido
- AI Readiness Score: Puntuación compuesta de preparación para IA
- Share of AI Voice: Visibilidad estimada en respuestas de IA
- robots.txt Compliance: Compatibilidad con bots de IA (GPTBot, ClaudeBot, etc.)
Tech Stack
| Componente | Tecnología |
|---|---|
| Framework | Next.js 15.2.4 (App Router + Server Actions + Turbopack) |
| Database | PostgreSQL 16 con Prisma ORM 6.5 |
| State Management | Zustand 5 (solo UI state, con persist) |
| Workflows | Inngest 3.30 (tareas de larga duración) |
| Crawler | Playwright 1.58 (headless Chromium) |
| AI | Google Gemini 2.0 Flash via Vercel AI SDK (generateObject) |
Resend + React Email (@react-email/components) |
|
| UI | shadcn/ui + Tailwind CSS 3.4 + Framer Motion 12 + Recharts 2.15 |
| Icons | Lucide React |
| Fonts | Instrument Serif (display) + Geist (sans) |
| Deploy | Docker en VPS (docker-compose con 4 servicios) |
Project Structure (Actual — Implemented)
root/
├── AGENTS.md # Este archivo — spec del proyecto para agentes IA
├── PROJECT-CONTEXT.md # Contexto completo del proyecto para LLM handoff
├── package.json # Dependencias (npm, no pnpm)
├── tsconfig.json # Strict mode + @/* path alias
├── tailwind.config.ts # Observatory theme tokens + keyframes
├── postcss.config.js # Tailwind + autoprefixer
├── next.config.ts # Next.js config
├── docker-compose.yml # PostgreSQL + app + crawler + Inngest dev
├── Dockerfile.crawler # Playwright container (1GB RAM, --ipc=host)
├── .env.example # Variables de entorno requeridas
├── prisma/
│ └── schema.prisma # 10 modelos, 6 enums, indexes optimizados
│
└── src/
├── app/ # Next.js App Router
│ ├── layout.tsx # Root layout (dark mode, Instrument Serif)
│ ├── page.tsx # Redirect → /dashboard
│ ├── globals.css # Observatory design system CSS
│ ├── api/
│ │ └── inngest/
│ │ └── route.ts # Inngest webhook handler
│ └── (dashboard)/
│ ├── layout.tsx # Sidebar + main content shell
│ ├── page.tsx # Redirect → /dashboard
│ └── dashboard/
│ ├── page.tsx # Dashboard home (Server Component)
│ ├── sites/
│ │ └── page.tsx # Sites list (Server Component)
│ ├── audits/
│ │ ├── page.tsx # Audits list (Server Component)
│ │ └── [id]/
│ │ └── page.tsx # Audit detail (Server Component, dynamic)
│ ├── insights/
│ │ └── page.tsx # Insights (Server Component)
│ └── settings/
│ └── page.tsx # Settings ("use client", placeholder)
│
├── components/
│ ├── ui/ # shadcn/ui primitives
│ │ ├── accordion.tsx # Radix Accordion
│ │ ├── badge.tsx # CVA Badge (6 variants)
│ │ ├── button.tsx # CVA Button (6 variants, 5 sizes)
│ │ ├── progress.tsx # Radix Progress
│ │ ├── scroll-area.tsx # Radix ScrollArea
│ │ ├── separator.tsx # Radix Separator
│ │ └── tooltip.tsx # Radix Tooltip
│ │
│ └── dashboard/ # Componentes específicos del dashboard
│ ├── sidebar.tsx # Collapsible sidebar + mobile drawer
│ ├── org-switcher.tsx # Organization dropdown con badges de plan
│ ├── score-radial.tsx # Animated SVG radial gauge
│ ├── trend-chart.tsx # Recharts AreaChart (score over time)
│ ├── category-radar.tsx # Recharts RadarChart (5 categories)
│ ├── quick-wins.tsx # Bento grid of quick win cards
│ ├── activity-feed.tsx # Recent audit activity list
│ ├── audit-progress.tsx # Vertical stepper with Framer Motion
│ ├── dashboard-view.tsx # Client view for /dashboard
│ ├── sites-view.tsx # Client view for /dashboard/sites
│ ├── audits-view.tsx # Client view for /dashboard/audits
│ ├── insights-view.tsx # Client view for /dashboard/insights
│ └── audit-detail-view.tsx # Client view for /dashboard/audits/[id]
│
├── lib/
│ ├── db.ts # Prisma client singleton
│ ├── auth.ts # Mock session for dev (NextAuth placeholder)
│ ├── ai.ts # Gemini engine (generateObject + Zod schema)
│ └── utils.ts # cn() + safeAction() utility
│
├── inngest/
│ ├── client.ts # Inngest client instance
│ ├── index.ts # Re-exports all functions
│ └── functions/
│ ├── audit.crawl.ts # Step 1: Crawl page with Playwright
│ ├── audit.analyze.ts # Step 2: Calculate scores
│ ├── audit.generate-insights.ts # Step 3: AI insights with Gemini
│ └── email.weekly-digest.ts # Cron: Monday 9 AM weekly email
│
├── crawler/
│ ├── browser.ts # Playwright launcher + crawlPage() orchestrator
│ ├── extractors/
│ │ ├── robots.ts # 7 AI bots compliance check
│ │ ├── schema.ts # Schema.org JSON-LD + Author/ProfilePage
│ │ ├── content.ts # Headings, readability, FAQ/HowTo
│ │ └── performance.ts # INP measurement (Docker-aware)
│ └── analyzers/
│ ├── ai-readiness.ts # 0-100 composite score (5 categories)
│ └── llm-readability.ts # 0-100 LLM parsability score
│
├── actions/ # Server Actions
│ ├── site.actions.ts # CRUD + getSites (soft delete)
│ ├── audit.actions.ts # startAudit, getAudit, getRecentAudits, getAuditHistory
│ └── insight.actions.ts # getAuditInsights, getQuickWins, getInsightStats
│
├── stores/ # Zustand stores
│ ├── ui.store.ts # Sidebar state + theme (with persist)
│ └── org.store.ts # Organization switcher state
│
├── emails/ # React Email templates
│ ├── weekly-digest.tsx # Organization-wide stats digest
│ └── audit-complete.tsx # Post-audit notification with scores
│
└── types/
└── index.ts # Zod schemas + TypeScript types
Architecture Pattern: Server/Client Split
Todas las páginas del dashboard siguen este patrón:
Server Component (page.tsx) Client Component (*-view.tsx)
┌─────────────────────────┐ ┌──────────────────────────┐
│ - Calls server actions │ data │ - "use client" │
│ - safeAction() fallback │ ───────► │ - Framer Motion animations│
│ - Transforms Prisma data │ props │ - Recharts charts │
│ - Demo data if no DB │ │ - Interactive UI │
└─────────────────────────┘ └──────────────────────────┘
// safeAction() — graceful fallback when DB is unavailable
export async function safeAction<T>(action: () => Promise<T>, fallback: T): Promise<T> {
try { return await action() } catch { return fallback }
}
Every page has comprehensive demo data hardcoded as a fallback constant. This means the dashboard renders beautifully even without a running database.
Code Standards
TypeScript
- Strict mode (
"strict": true) - Prefiere
typesobreinterfacepara consistencia - Usa Zod para validación en runtime
- NO uses
any— usaunknowncon type guards
React / Next.js
- Usa Server Components por defecto
- Client Components solo para interactividad (
"use client") - Usa Server Actions para mutaciones
- Patrón Server Component page → Client Component view para TODAS las páginas
Database (Prisma)
- Todas las queries en Server Actions o API routes
- Usa transacciones para operaciones multi-tabla
- Indexa campos frecuentemente consultados
- Usa soft deletes donde sea apropiado (
deletedAten Site)
Inngest Workflows
- Cada step debe ser idempotente
- Usa
step.run()para operaciones con retry onFailurehandlers actualizan el audit status a FAILED- Los eventos siguen el patrón
domain/action.past-tense
Styling — "Observatory" Design System
- Dark cinematic canvas (
#09090b) con tokens semánticos - Colores:
canvas(backgrounds),ink(text),emerald(success),amber(warning),rose(danger),iris(AI/accent) - Fuentes: Instrument Serif (display/headings), Geist (body)
.ai-glow-border— animated mesh gradient border for AI content.ai-glow-border-static— subtle version for secondary AI elements.metric-card— dark glass cards with hover effect.noise-overlay— film-grain depth texture- Mobile-first responsive. NO estilos inline.
Naming Conventions
| Elemento | Convención | Ejemplo |
|---|---|---|
| Archivos | kebab-case | audit-detail-view.tsx |
| Componentes | PascalCase | AuditDetailView |
| Funciones/Variables | camelCase | calculateScore |
| Constantes | SCREAMING_SNAKE_CASE | MAX_RETRIES |
| Types | PascalCase descriptivo | AuditDetailData |
| Zustand Stores | use[Name]Store |
useUIStore |
| Server Actions | [verb][Noun] |
createAudit, getSites |
| Inngest Functions | [domain].[action] |
audit.crawl |
| Eventos Inngest | [domain]/[action].[past-tense] |
audit/crawl.requested |
| View Components | [Page]View |
DashboardView, AuditDetailView |
AI Readiness Score Algorithm
El score compuesto (0-100) se calcula así:
| Categoría | Puntos | Criterio |
|---|---|---|
| Heading Hierarchy | 25 | H1 presente + jerarquía válida |
| Schema.org | 25 | Tipos detectados (5 pts c/u, máx 25) |
| Content Structure | 20 | FAQ (10) + HowTo (10) |
| Readability | 15 | Grado óptimo 8-10 (Flesch-Kincaid) |
| Meta Quality | 15 | Descripción entre 120-160 caracteres |
AI Bot Compliance
Se verifican 7 bots de IA en robots.txt:
- GPTBot (OpenAI)
- OAI-SearchBot (OpenAI Search)
- ClaudeBot (Anthropic)
- Anthropic-AI (Anthropic)
- Google-Extended (Google AI)
- PerplexityBot (Perplexity)
- Applebot-Extended (Apple Intelligence)
Key Technical Discoveries
Cosas que ya se resolvieron y NO deben re-introducirse:
@radix-ui/react-badgeNO EXISTE — Badge es pure Tailwind via CVA- Ruta
(dashboard)no añade URL segment — Las páginas están en(dashboard)/dashboard/para que los URLs sean/dashboard/* - Vercel AI SDK: Usar
generateObject()para output estructurado, NOgenerateText()conOutput.object - Inngest client: No usar
schemas: new Map()— causa type errors - Inngest
onFailure: Castevent.dataviaRecord<string, unknown>para acceder a campos - Prisma enums vs Zod:
InsightCategoryenum usa snake_case (llm_readability), Zod usa kebab-case — hay uncategoryMapenaudit.generate-insights.ts - PerformanceEventTiming: TypeScript no tiene
interactionId/durationThreshold— haydeclare globalaugmentations enperformance.ts - React Email
<Preview>: Números deben ser wrapped conString()por constraintReactNode & string - Prisma JSON fields:
Record<string, unknown>[]no es assignable aInputJsonValue— usarJSON.parse(JSON.stringify(...)) - INP en Docker: Necesita ≥1GB RAM,
--ipc=host, múltiples muestras con P75 + detección de varianza autoprefixer: Necesario para PostCSS — instalado como devDependencyAuditActivity.status: El tipo del activity feed debe incluirGENERATING_INSIGHTSademás de los otros 5 status
Build Info
Route (app) Size First Load JS
┌ ○ / 140 B 101 kB
├ ○ /_not-found 986 B 102 kB
├ ƒ /api/inngest 140 B 101 kB
├ ○ /dashboard 116 kB 268 kB
├ ○ /dashboard/audits 2.69 kB 154 kB
├ ƒ /dashboard/audits/[id] 9.05 kB 167 kB
├ ○ /dashboard/insights 2.84 kB 155 kB
├ ○ /dashboard/settings 4.76 kB 154 kB
└ ○ /dashboard/sites 4.09 kB 156 kB
○ (Static) prerendered as static content
ƒ (Dynamic) server-rendered on demand
npx tsc --noEmit→ 0 errorsnpm run build→ exitoso, 10 rutas
Development Commands
# Desarrollo
npm run dev # Next.js dev server (Turbopack)
npm run build # Build de producción
npm run start # Iniciar servidor de producción
npm run typecheck # npx tsc --noEmit
# Base de datos
npm run db:push # Push schema a DB (desarrollo)
npm run db:migrate # Crear migración
npm run db:studio # Abrir Prisma Studio
npm run db:seed # Ejecutar seeds (tsx prisma/seed.ts)
# Inngest
npm run inngest:dev # Iniciar servidor de desarrollo de Inngest
# Testing (no hay tests escritos aún)
npm test # Vitest
npm run test:unit # Vitest run
npm run test:e2e # Playwright test
npm run test:coverage # Vitest + coverage
Environment Variables
Ver .env.example para todas las variables necesarias.
Variables críticas:
DATABASE_URL— Conexión a PostgreSQLINNGEST_SIGNING_KEY— Clave de InngestINNGEST_EVENT_KEY— Event key de InngestGEMINI_API_KEY— API key de Google AI StudioRESEND_API_KEY— API key de ResendAUTH_SECRET— Para NextAuth.js (cuando se implemente)
What's NOT Done Yet
- Autenticación real —
auth.tses un mock que devuelve sesión hardcoded. NextAuth.js no está configurado. No existen páginas de login/register. - Tests — Vitest y Playwright están en scripts pero no hay archivos de test.
- Settings page — Renderiza un placeholder estático.
- Dockerfile del app — Solo existe
Dockerfile.crawler, falta el principal para Next.js. - Score breakdown real — El dashboard usa datos demo para breakdown y radar cuando hay datos reales (falta calcular desde PageAudit).
- Suspense boundaries — No hay loading states ni error boundaries en las páginas.
- Empty states — Las vistas no tienen diseño para "sin datos" (siempre hay demo data como fallback).
Git Workflow
- Crear branch desde
main:git checkout -b feature/nombre - Commits con conventional commits:
feat:,fix:,docs:,refactor: - Push y crear PR
- Review y merge a
main
# Ejemplos de commits
git commit -m "feat(crawler): add robots.txt parser for AI bots"
git commit -m "fix(dashboard): correct score calculation rounding"
git commit -m "refactor(inngest): split audit workflow into steps"