KOVA Cost Intelligence Center
Alfester Technologies OÜ · Token Cost Engine + Fair Use Control
← Suite
Modelo: Mayo 2026
👥 Usuarios
Free200
Pro ($29/mes)80
Enterprise ($99/mes)20
Msgs/mes Pro120
Msgs/mes Enterprise400
💸 Desglose mensual
🤖 Mix de agentes
🔍 NOVA · Prospecting40%
🎯 ALEX · Sales Closing35%
✨ MAIA · Content25%
📊 Costo/msg por modelo
ModeloSin cachéCon cachéAhorro
¿Por qué "ilimitado" necesita Fair Use?

KOVA cobra precio plano mensual, pero paga la API de Anthropic por cada token. Sin tope, un usuario abusivo consume más de lo que paga. El Fair Use establece el umbral donde el margen se mantiene positivo — sin afectar al usuario promedio (80–150 msgs/mes).

PRO · $29/mes
Fair Use: 500 msgs/mes
ENTERPRISE · $99/mes
Fair Use: 2.000 msgs/mes
📊 Valor real vs precio de plan
Msgs/mesCOGS ProPrecio ProMargen ProCOGS EntPrecio EntMargen Ent
🎯 Política Fair Use recomendada
🛡️ 4 pasos de implementación · Stack: Supabase + Vercel + Node.js

Implementa en orden. Cada paso activa protección incremental. Sin reiniciar la app.

PASO 1 · Tabla en Supabase SQL Editor
1

Crear tabla user_usage

El month_key ("2026-06") cambia automáticamente cada mes — sin cron de reset.

-- Supabase → SQL Editor → New query CREATE TABLE user_usage ( id uuid DEFAULT gen_random_uuid() PRIMARY KEY, user_id uuid REFERENCES auth.users(id) ON DELETE CASCADE, month_key text NOT NULL, msg_count integer DEFAULT 0, token_input bigint DEFAULT 0, token_output bigint DEFAULT 0, updated_at timestamptz DEFAULT now(), UNIQUE(user_id, month_key) ); ALTER TABLE user_usage ENABLE ROW LEVEL SECURITY; CREATE POLICY "read own" ON user_usage FOR SELECT USING(auth.uid()=user_id); CREATE INDEX idx_usage ON user_usage(user_id, month_key);
2

Función RPC atómica

Valida el límite Y suma en una transacción. Sin race conditions.

CREATE OR REPLACE FUNCTION check_and_increment_usage( p_user_id uuid, p_plan text, p_tokens_in integer DEFAULT 0, p_tokens_out integer DEFAULT 0 ) RETURNS jsonb LANGUAGE plpgsql SECURITY DEFINER AS $$ DECLARE v_month text := to_char(now(),'YYYY-MM'); v_limit integer; v_count integer; BEGIN v_limit := CASE p_plan WHEN 'free' THEN 15 WHEN 'pro' THEN 500 WHEN 'enterprise' THEN 2000 ELSE 15 END; INSERT INTO user_usage(user_id,month_key,msg_count) VALUES(p_user_id,v_month,0) ON CONFLICT(user_id,month_key) DO NOTHING; SELECT msg_count INTO v_count FROM user_usage WHERE user_id=p_user_id AND month_key=v_month FOR UPDATE; IF p_plan='free' AND v_count>=v_limit THEN RETURN jsonb_build_object('allowed',false,'count',v_count,'limit',v_limit); END IF; UPDATE user_usage SET msg_count=msg_count+1,token_input=token_input+p_tokens_in, token_output=token_output+p_tokens_out,updated_at=now() WHERE user_id=p_user_id AND month_key=v_month; RETURN jsonb_build_object('allowed',true,'count',v_count+1, 'limit',v_limit,'throttle',(v_count+1)>(v_limit*0.8)); END; $$;
PASO 2 · Middleware Vercel
3

middleware/fairUse.js — antes de llamar a Claude

// middleware/fairUse.js const { createClient } = require('@supabase/supabase-js') const THROTTLE = { free:0, pro:2000, enterprise:1000 } async function checkFairUse(req, res, next) { const { id:userId, plan } = req.user const sb = createClient(process.env.SUPABASE_URL, process.env.SUPABASE_SERVICE_KEY) const { data, error } = await sb.rpc('check_and_increment_usage', { p_user_id:userId, p_plan:plan }) if (error) return next() // fail-open if (!data.allowed) return res.status(429).json({ error:'monthly_limit_reached', message:'Límite mensual alcanzado. Actualiza tu plan.', upgrade:'https://app.kova.ai/upgrade' }) if (data.throttle && THROTTLE[plan]>0) await new Promise(r=>setTimeout(r,THROTTLE[plan])) res.setHeader('X-Usage-Count', data.count) res.setHeader('X-Usage-Limit', data.limit) res.setHeader('X-Usage-Pct', Math.round(data.count/data.limit*100)) req.usage = data; return next() } module.exports = { checkFairUse }
4

Aplicar en api/chat.js

const { checkFairUse } = require('../middleware/fairUse') export default async function handler(req, res) { await authMiddleware(req, res, async () => { await checkFairUse(req, res, async () => { // ✅ Solo llega aquí si tiene cupo const resp = await anthropic.messages.create({ model: 'claude-sonnet-4-6', max_tokens:1000, system: req.body.systemPrompt, messages: req.body.messages, }) res.json({ content: resp.content[0].text }) }) }) }
PASO 3 · Hook React Native
5

hooks/useUsageGuard.js

Lee headers X-Usage del backend. Sin llamadas extra. Banner al 80%, bloqueo al 100%.

export function useUsageGuard() { const [usage, setUsage] = useState({count:0,limit:500,pct:0}) const updateFromHeaders = useCallback(headers => { setUsage({ count:parseInt(headers.get('x-usage-count')||'0'), limit:parseInt(headers.get('x-usage-limit')||'500'), pct: parseInt(headers.get('x-usage-pct')||'0'), }) },[]) return { usage, updateFromHeaders, isNearLimit: usage.pct>=80, isAtLimit: usage.pct>=100 } }
PASO 4 · Cron limpieza + View admin
6

pg_cron y view v_current_usage

-- Supabase → Extensions → habilitar pg_cron SELECT cron.schedule('cleanup','0 0 1 * *',$$ DELETE FROM user_usage WHERE month_key<to_char(now()-INTERVAL '3 months','YYYY-MM');$$); CREATE VIEW v_current_usage AS SELECT p.email, p.plan, uu.msg_count, ROUND(uu.msg_count::numeric/CASE p.plan WHEN 'free' THEN 15 WHEN 'pro' THEN 500 WHEN 'enterprise' THEN 2000 END*100,1) AS usage_pct FROM user_usage uu JOIN profiles p ON p.id=uu.user_id WHERE uu.month_key=to_char(now(),'YYYY-MM');
📡 3 capas de protección — sin sorpresas de facturación

Capa 1 (Anthropic Console): hard cap absoluto mensual. Capa 2 (n8n): alertas proactivas al 90% de uso. Capa 3 (Supabase): queries de anomalías en tiempo real.

🔴 Capa 1 · Hard Cap en Anthropic Console

console.anthropic.com → Settings → Billing → Usage limits

Early 0–100 usuarios
$50
hard cap/mes
Growth 100–500
$300
hard cap/mes
Scale 500+
$1.5K
+ alertas 80%
🟡 Capa 2 · n8n workflow — alerta diaria 09:00
// HTTP Request → Supabase REST API GET /rest/v1/v_current_usage?usage_pct=gte.90 Headers: apikey: ${SUPABASE_SERVICE_KEY} // IF hay resultados → Slack/Email: "⚠️ KOVA: {{$json.length}} usuarios al 90%+ de su límite" "{{$json.map(u=>u.email+' — '+u.plan+' — '+u.usage_pct+'%').join('\n')}}"
🟢 Capa 3 · Query de anomalías en Supabase
SELECT p.email, p.plan, uu.msg_count, ROUND((uu.token_input*0.000003+uu.token_output*0.000015)::numeric,4) AS cost_usd FROM user_usage uu JOIN profiles p ON p.id=uu.user_id WHERE uu.month_key=to_char(now(),'YYYY-MM') AND uu.msg_count>200 ORDER BY cost_usd DESC LIMIT 20;
✅ Checklist