Renderz Studio
Academia
Proyectos
Servicios
Arsenal
Repo
Herramientas
Skills
El Oráculo
Guía Gratis
← El Arsenal
BackendAvanzado55 min

Supabase + IA: El Stack Completo

pgvector · Edge Functions · Realtime · Auth · todo conectado con Claude

SupabasepgvectorEdge Functions
✓ Guía gratuita · Acceso completo

Mapa rápido · salta al paso que te interese

Setup Supabase para IA

pgvector, RLS, y estructura

Edge Functions + Claude

Serverless AI backend

Realtime para streaming

Respuestas en tiempo real

01

Setup Supabase para IA

Supabase es la opción ideal para backends de IA: Postgres con pgvector, Edge Functions en Deno, Auth, y Realtime. Todo en un lugar.

Habilitar pgvector y crear tablas
-- 1. Habilitar extensiones
create extension if not exists vector;
create extension if not exists pg_trgm; -- para búsqueda de texto

-- 2. Tabla de documentos con embeddings
create table documents (
  id uuid primary key default gen_random_uuid(),
  user_id uuid references auth.users(id) on delete cascade,
  title text not null,
  content text not null,
  metadata jsonb default '{}',
  embedding vector(1536),
  created_at timestamptz default now(),
  updated_at timestamptz default now()
);

-- 3. Tabla de conversaciones
create table conversations (
  id uuid primary key default gen_random_uuid(),
  user_id uuid references auth.users(id) on delete cascade,
  title text,
  created_at timestamptz default now()
);

-- 4. Tabla de mensajes
create table messages (
  id uuid primary key default gen_random_uuid(),
  conversation_id uuid references conversations(id) on delete cascade,
  role text check (role in ('user', 'assistant', 'system')),
  content text not null,
  tokens_used int,
  created_at timestamptz default now()
);

-- 5. Índices
create index on documents using ivfflat (embedding vector_cosine_ops) with (lists = 100);
create index on documents using gin (to_tsvector('spanish', content));
create index on messages (conversation_id, created_at);
Row Level Security (RLS)
-- Users solo ven sus propios documentos
alter table documents enable row level security;

create policy "Users can CRUD own documents"
on documents for all
using (auth.uid() = user_id)
with check (auth.uid() = user_id);

-- Conversaciones
alter table conversations enable row level security;

create policy "Users can CRUD own conversations"
on conversations for all
using (auth.uid() = user_id)
with check (auth.uid() = user_id);

-- Mensajes (via conversation ownership)
alter table messages enable row level security;

create policy "Users can CRUD messages in own conversations"
on messages for all
using (
  conversation_id in (
    select id from conversations where user_id = auth.uid()
  )
);
Función de búsqueda semántica
create or replace function search_documents(
  query_embedding vector(1536),
  match_count int default 5,
  filter_user_id uuid default null
)
returns table (
  id uuid,
  title text,
  content text,
  metadata jsonb,
  similarity float
)
language plpgsql
as $$
begin
  return query
  select
    d.id,
    d.title,
    d.content,
    d.metadata,
    1 - (d.embedding <=> query_embedding) as similarity
  from documents d
  where
    (filter_user_id is null or d.user_id = filter_user_id)
    and d.embedding is not null
  order by d.embedding <=> query_embedding
  limit match_count;
end;
$$;
02

Edge Functions + Claude

Edge Functions corren en Deno cerca del usuario. Perfectas para llamadas a Claude con baja latencia.

supabase/functions/chat/index.ts
import { serve } from 'https://deno.land/std@0.168.0/http/server.ts'
import { createClient } from 'https://esm.sh/@supabase/supabase-js@2'
import Anthropic from 'https://esm.sh/@anthropic-ai/sdk'

const anthropic = new Anthropic({
  apiKey: Deno.env.get('ANTHROPIC_API_KEY')!,
})

serve(async (req) => {
  // CORS
  if (req.method === 'OPTIONS') {
    return new Response('ok', {
      headers: {
        'Access-Control-Allow-Origin': '*',
        'Access-Control-Allow-Headers': 'authorization, content-type',
      },
    })
  }

  try {
    // Auth
    const authHeader = req.headers.get('Authorization')!
    const supabase = createClient(
      Deno.env.get('SUPABASE_URL')!,
      Deno.env.get('SUPABASE_ANON_KEY')!,
      { global: { headers: { Authorization: authHeader } } }
    )

    const { data: { user }, error: authError } = await supabase.auth.getUser()
    if (authError || !user) {
      return new Response(JSON.stringify({ error: 'Unauthorized' }), { status: 401 })
    }

    // Parse request
    const { message, conversationId } = await req.json()

    // Get conversation history
    const { data: history } = await supabase
      .from('messages')
      .select('role, content')
      .eq('conversation_id', conversationId)
      .order('created_at', { ascending: true })
      .limit(20)

    // Build messages
    const messages = [
      ...(history || []).map(m => ({ role: m.role, content: m.content })),
      { role: 'user', content: message }
    ]

    // Call Claude
    const response = await anthropic.messages.create({
      model: 'claude-sonnet-4-20250514',
      max_tokens: 2048,
      system: 'Eres un asistente útil y conciso.',
      messages,
    })

    const assistantMessage = response.content[0].text

    // Save messages
    await supabase.from('messages').insert([
      { conversation_id: conversationId, role: 'user', content: message },
      { conversation_id: conversationId, role: 'assistant', content: assistantMessage, tokens_used: response.usage.output_tokens },
    ])

    return new Response(JSON.stringify({
      message: assistantMessage,
      usage: response.usage,
    }), {
      headers: { 'Content-Type': 'application/json' },
    })

  } catch (error) {
    return new Response(JSON.stringify({ error: error.message }), { status: 500 })
  }
})

Tip Pro

Deploy con: supabase functions deploy chat
03

Realtime para streaming

Supabase Realtime permite que el frontend reciba updates instantáneos cuando el backend inserta mensajes.

Frontend: suscribirse a mensajes
import { useEffect, useState } from 'react'
import { createClient } from '@supabase/supabase-js'

const supabase = createClient(SUPABASE_URL, SUPABASE_ANON_KEY)

function useConversation(conversationId: string) {
  const [messages, setMessages] = useState<Message[]>([])

  useEffect(() => {
    // Load initial messages
    supabase
      .from('messages')
      .select('*')
      .eq('conversation_id', conversationId)
      .order('created_at')
      .then(({ data }) => setMessages(data || []))

    // Subscribe to new messages
    const channel = supabase
      .channel(`conversation:${conversationId}`)
      .on(
        'postgres_changes',
        {
          event: 'INSERT',
          schema: 'public',
          table: 'messages',
          filter: `conversation_id=eq.${conversationId}`,
        },
        (payload) => {
          setMessages(prev => [...prev, payload.new as Message])
        }
      )
      .subscribe()

    return () => {
      supabase.removeChannel(channel)
    }
  }, [conversationId])

  return messages
}

⚠ Advertencia

Realtime tiene límites en el free tier. 200 conexiones concurrentes. Pro tier: ilimitadas.

"Supabase es el backend que Firebase prometió pero nunca entregó. pgvector, Edge Functions, Realtime, Auth — todo lo que necesitás para una app de IA en un solo lugar."

— El Arsenal, Renderz Studio

Guías relacionadas

RAG desde Cero

pgvector en acción

Agentes Autónomos

Supabase como storage

¿Quieres el sistema completo?

500+ páginas, 25 capítulos, código de producción, acceso a todas las guías del Arsenal.

Curso Completo · €29

PayPal · Acceso inmediato

¿Hablamos?