.tsx
search-clients.tsx — Lista de clientes activos
List View · useFetch · LocalStorage
// src/search-clients.tsx — Busca y gestiona clientes de CULTIVA IA
import { List, Action, ActionPanel, Icon, Color, getPreferenceValues } from "@raycast/api";
import { useFetch } from "@raycast/utils";
import { Client } from "./lib/types";
const PLAN_COLORS = { Starter: Color.Green, Pro: Color.Blue, Enterprise: Color.Purple };
export default function SearchClients() {
const { apiKey } = getPreferenceValues<{ apiKey: string }>();
const { data, isLoading } = useFetch<{ clients: Client[] }>(
"https://api.cultivaia.com/v1/clients?status=active",
{ headers: { "x-api-key": apiKey }, keepPreviousData: true }
);
return (
<List isLoading={isLoading} searchBarPlaceholder="Buscar cliente...">
{data?.clients.map((c) => (
<List.Item
key={c.id}
title={c.name}
subtitle={c.sector}
accessories={[
{ tag: { value: c.plan, color: PLAN_COLORS[c.plan] } },
{ text: `€${c.mrr}/mes`, tooltip: "MRR" },
{ icon: c.alerts > 0 ? Icon.ExclamationMark : Icon.Checkmark,
tintColor: c.alerts > 0 ? Color.Orange : Color.Green },
]}
actions={<ActionPanel>
<Action.OpenInBrowser url={c.notionUrl} title="Abrir en Notion" />
<Action title="Ver Métricas IA" icon={Icon.BarChart} onAction={() => push(<AIMetrics clientId={c.id} />)} />
<Action.CopyToClipboard content={c.id} title="Copiar ID Cliente" />
</ActionPanel>}
/>
))}
</List>
);
}
.tsx
create-client.tsx — Onboarding rápido
Form View · Toast · popToRoot
// src/create-client.tsx — Onboarding nuevo cliente CULTIVA IA
import { Form, Action, ActionPanel, showToast, Toast, popToRoot } from "@raycast/api";
import { useState } from "react";
export default function CreateClient() {
const [isLoading, setIsLoading] = useState(false);
async function handleSubmit(v: { name: string; sector: string; plan: string; services: string[] }) {
setIsLoading(true);
try {
const res = await fetch("https://api.cultivaia.com/v1/clients", {
method: "POST",
body: JSON.stringify(v),
headers: { "Content-Type": "application/json", "x-api-key": getPreferenceValues().apiKey }
});
const client = await res.json();
await showToast({ style: Toast.Style.Success,
title: "Cliente creado", message: `${v.name} · ID ${client.id}` });
popToRoot();
} catch (err) {
await showToast({ style: Toast.Style.Failure, title: "Error", message: String(err) });
} finally { setIsLoading(false); }
}
return (
<Form isLoading={isLoading} actions={<ActionPanel><Action.SubmitForm title="Crear Cliente" onSubmit={handleSubmit}/></ActionPanel>}>
<Form.TextField id="name" title="Empresa" placeholder="InflaMax SL" />
<Form.TextField id="sector" title="Sector" placeholder="eCommerce" />
<Form.Dropdown id="plan" title="Plan" defaultValue="Pro">
{["Starter","Pro","Enterprise"].map((p) => (
<Form.Dropdown.Item key={p} value={p} title={p} />
))}
</Form.Dropdown>
<Form.TagPicker id="services" title="Servicios contratados">
<Form.TagPicker.Item value="automation" title="🤖 Automatización" />
<Form.TagPicker.Item value="content" title="✍️ Contenido IA" />
<Form.TagPicker.Item value="agents" title="🧠 Agentes IA" />
<Form.TagPicker.Item value="seo" title="🔍 SEO IA" />
</Form.TagPicker>
</Form>
);
}
.tsx
menu-bar.tsx — Alertas en la barra de menú
MenuBarExtra · Interval 5m · Notificaciones
// src/menu-bar.tsx — Status CULTIVA IA en la menubar
import { MenuBarExtra, Icon, open } from "@raycast/api";
import { useFetch } from "@raycast/utils";
export default function CultivaMenuBar() {
const { data } = useFetch("https://api.cultivaia.com/v1/alerts",
{ keepPreviousData: true, initialData: { alerts: [], tasks: [] } });
const count = data.alerts.length;
return (
<MenuBarExtra
icon={Icon.TwoPeople}
title={count > 0 ? `⚠️ ${count}` : undefined}>
<MenuBarExtra.Section title="Alertas de clientes">
{data.alerts.map((a) => (
<MenuBarExtra.Item key={a.id}
title={a.client} subtitle={a.message}
onAction={() => open(a.url)} />
))}
</MenuBarExtra.Section>
<MenuBarExtra.Section title="Tareas urgentes">
{data.tasks.slice(0,5).map((t) => (
<MenuBarExtra.Item key={t.id} title={t.title}
subtitle={t.client} onAction={() => open(t.notionUrl)} />
))}
</MenuBarExtra.Section>
</MenuBarExtra>
);
}
Preview
.json
package.json — Configuración de la extensión
Manifest · Preferences · Commands
{
"name": "cultiva-ia-tools",
"title": "CULTIVA IA Tools",
"description": "Gestión de clientes, tareas y métricas de IA desde Raycast",
"author": "cultiva-ia",
"categories": ["Productivity", "Developer Tools"],
"commands": [
{ "name": "search-clients", "title": "Buscar Clientes", "mode": "view" },
{ "name": "create-client", "title": "Nuevo Cliente", "mode": "view" },
{ "name": "daily-tasks", "title": "Tareas de Hoy", "mode": "view" },
{ "name": "ai-metrics", "title": "Métricas IA", "mode": "view" },
{ "name": "status-bar", "title": "CULTIVA Status", "mode": "menu-bar", "interval": "5m" }
],
"preferences": [
{ "name": "apiKey", "title": "API Key CULTIVA", "type": "password", "required": true },
{ "name": "teamId", "title": "ID de Equipo", "type": "textfield", "required": false },
{ "name": "notionToken", "title": "Notion Token", "type": "password", "required": false }
]
}
⚡
Buenas prácticas aplicadas
Fast time-to-interaction
keepPreviousData: true — datos inmediatos mientras revalida en background
Secrets en Preferences
API Key y tokens como type: "password", nunca hardcodeados en el código
Toast para feedback
Success/Failure toasts en cada acción crítica sin bloquear la UI
Accessories informativos
Plan, MRR y alertas visibles en la lista sin abrir detalle
Menu Bar para monitoreo
Alertas de clientes siempre visibles, intervalo de refresco 5 minutos
Cache agresiva
LocalStorage para favoritos + Cache API con TTL 5 min para repos
ActionPanel jerárquico
Acción más común primero (Abrir Notion), Cmd+Enter la dispara
useFetch de @raycast/utils
Maneja caché, revalidación y loading states automáticamente
bash
Instalación y desarrollo
# 1. Crear extensión
$ npx create-raycast-extension cultiva-ia-tools
# 2. Instalar dependencias
$ npm install @raycast/api @raycast/utils
# 3. Desarrollo con hot reload
$ npm run dev
# 4. Build + lint
$ npm run build && npm run fix-lint
# 5. Publicar en Raycast Store
$ npx ray publish