# Oportunidades

Lee desde el SDK los contadores, las acciones recomendadas, la salud del embudo y los motivos de pérdida de las oportunidades del CRM, por MCP.

`sdk.mcp.opportunities` lleva a tu integración las cifras que el CRM enseña en [Oportunidades](https://os.utilia.ai/dashboard/crm/opportunities): cuántas son tuyas, de tu equipo o de todos; qué conviene hacer hoy; qué oportunidades están estancadas o vencidas; por qué se pierden y quién gana más. Todo llega por el carril MCP, con la herramienta `crm_opportunities_stats` del servidor del CRM, y en nombre de una persona: necesitas su sesión OAuth.

> **Atención: Casi todo exige Analítica avanzada del CRM**
>
> Todos los métodos de esta página salvo `listStages()` usan la herramienta `crm_opportunities_stats`, que solo funciona si el plan de tu organización incluye **Analítica avanzada del CRM**. `listStages()` usa otra herramienta, `crm_opportunities`, y le basta con que el plan incluya **Oportunidades**. Si falta la capacidad, la herramienta responde con un error que explica qué falta. No se resuelve dando permisos, sino ampliando el plan: lo detalla [Errores de plan](https://os.utilia.ai/docs/developers/sdk/plan-errors).

## Antes de empezar

- **Credencial**: el carril MCP actúa en nombre de una persona, así que el SDK tiene que estar configurado con [OAuth](https://os.utilia.ai/docs/developers/sdk/oauth). Con la clave de una aplicación externa (`X-Api-Key`) el servidor MCP rechaza la petición.
- **Scope** del token: `crm:opportunities:view` (o un conjunto que lo incluya, como `crm:read` o `crm:full`).
- **Permisos** de la persona: «Ver oportunidades» (`OPPORTUNITIES_VIEW`) para casi todo; «Ver el embudo del equipo» (`OPPORTUNITIES_VIEW_TEAM_PIPELINE`) para los mejores contribuidores; y «Ver información financiera de oportunidades» (`OPPORTUNITIES_VIEW_FINANCIALS`) para las cifras económicas de `getPipelineStats` y `getForecast`.

Si falta un permiso, el servidor responde con un mensaje en castellano que lo explica y el SDK lo lanza como `UtiliaSDKError`: captúralo y enséñalo.

| Qué | JavaScript | Python |
| - | - | - |
| Contadores «Mías», «Equipo» y «Todas» | `assignmentCounts(filtros?)` | `assignment_counts(filtros)` |
| Acciones recomendadas para hoy | `recommendations()` | `recommendations()` |
| Oportunidades estancadas y vencidas | `analyticsStaleDetection()` | `analytics_stale_detection()` |
| Motivos de pérdida más frecuentes | `analyticsLossReasons(periodo)` | `analytics_loss_reasons(periodo)` |
| Mejores contribuidores | `analyticsTopContributors(periodo)` | `analytics_top_contributors(periodo)` |
| Estadísticas del embudo | `getPipelineStats()` | `get_pipeline_stats()` |
| Previsión | `getForecast(meses?)` | `get_forecast(months)` |
| Actividad de una oportunidad | `getTimeline(...)` | `get_timeline(...)` |
| Etapas configuradas | `listStages()` | `list_stages()` |

En JavaScript cada método devuelve `{ data }`; en Python, directamente el modelo con los campos en `snake_case`. `getPipelineStats`, `getForecast` y `getTimeline` devuelven un objeto sin tipar: su contrato aún puede cambiar.

## Ejemplo

### JavaScript

```typescript
import { UtiliaSDK } from '@utilia-os/sdk-js';

const sdk = new UtiliaSDK({
  baseURL: 'https://os.utilia.ai/api',
  oauth: { clientId: process.env.UTILIA_CLIENT_ID!, redirectUri: 'http://localhost:3000/callback' },
});

const { data: counts } = await sdk.mcp.opportunities.assignmentCounts({ status: ['OPEN'] });
console.log(counts.mine, counts.team, counts.all); // team vale 0 sin permiso de equipo

const { data: recs } = await sdk.mcp.opportunities.recommendations();
for (const item of recs.items) console.log(item.type, item.priority, item.title);

const { data: health } = await sdk.mcp.opportunities.analyticsStaleDetection();
console.log('Estancadas:', health.overallStaleCount, 'Vencidas:', health.overdueCount);

const { data: losses } = await sdk.mcp.opportunities.analyticsLossReasons('90d');
for (const r of losses.reasons) console.log(r.reason, r.count, r.totalLostValue);
```

### Python

```python
import os

from utilia_sdk import UtiliaSDK
from utilia_sdk.mcp.types import AssignmentCountsFilters

sdk = UtiliaSDK(
    base_url="https://os.utilia.ai/api",
    oauth={"client_id": os.environ["UTILIA_CLIENT_ID"], "redirect_uri": "http://localhost:8000/callback"},
)

counts = await sdk.mcp.opportunities.assignment_counts(AssignmentCountsFilters(status=["OPEN"]))
print(counts.mine, counts.team, counts.all)  # team vale 0 sin permiso de equipo

recs = await sdk.mcp.opportunities.recommendations()
for item in recs.items:
    print(item.type, item.priority, item.title)

health = await sdk.mcp.opportunities.analytics_stale_detection()
print("Estancadas:", health.overall_stale_count, "Vencidas:", health.overdue_count)

losses = await sdk.mcp.opportunities.analytics_loss_reasons("90d")
for r in losses.reasons:
    print(r.reason, r.count, r.total_lost_value)
```

Las recomendaciones son hasta cinco fichas ordenadas por gravedad, con `type` igual a `overdue` (vencida), `won_no_invoice` (ganada sin factura), `aging` (envejecida), `won_no_project` (ganada sin proyecto) o `budget_pending` (presupuesto pendiente): cada una apunta al siguiente paso natural del ciclo de venta.

No hay un método para la tasa de conversión suelta: viene dentro de `getPipelineStats()`, con el permiso «Ver información financiera de oportunidades».

## Sin el SDK

Si tu copiloto habla MCP directamente, el contrato es el mismo: la herramienta `crm_opportunities_stats` en `POST /api/mcp/crm`, con `operation` igual a `assignment_counts`, `recommendations`, `analytics_stale_detection`, `loss_reasons`, `by_owner`, `get_pipeline_stats`, `get_forecast` o `get_timeline`. Los nombres `analytics_loss_reasons` y `analytics_top_contributors`, que usan los métodos del SDK, son los antiguos de `loss_reasons` y `by_owner`: ya no se anuncian y responden con un aviso de operación obsoleta hasta el 8 de enero de 2027. Las etapas salen de la herramienta `crm_opportunities`, con `operation` igual a `list_stages`.

```http
POST /api/mcp/crm
Content-Type: application/json
Authorization: Bearer <token>

{
  "jsonrpc": "2.0",
  "id": 1,
  "method": "tools/call",
  "params": {
    "name": "crm_opportunities_stats",
    "arguments": { "operation": "loss_reasons", "period": "90d" }
  }
}
```

## Siguiente paso

- [Oportunidades en el CRM](https://os.utilia.ai/docs/guides/crm/opportunities): Cómo las usa tu equipo comercial, del lead a la venta.
- [Analítica del CRM](https://os.utilia.ai/docs/guides/crm/analytics): Qué cuenta cada cifra del embudo y de la conversión.
