API que evalúa con IA cómo atienden las inmobiliarias a clientes potenciales (mystery shopping). El flujo hace llamadas/WhatsApp automáticos, los analiza con IA y genera reportes por campaña.
http://127.0.0.1:8000
La API expone su propia doc navegable y probable:
http://127.0.0.1:8000/docs
TODAS las respuestas vienen envueltas en este sobre:
Éxito:
{
"success": true,
"status_code": 200,
"data": { ... } // aquí va el contenido real
}Error:
{
"success": false,
"status_code": 404,
"error": "Mensaje describiendo el error"
}En los ejemplos de abajo solo se muestra el contenido de
datapara no repetir el sobre.
| Código | Significado |
|---|---|
| 200 | OK |
| 201 | Creado |
| 400 | Petición mal formada |
| 404 | No encontrado |
| 422 | Datos inválidos / falta info para procesar |
| 500 | Error interno |
| 502 | Error de un servicio externo (IA / ElevenLabs) |
Por ahora no hay auth. (Pendiente para producción.)
| Campo | Valores |
|---|---|
campaign.status |
active, paused, done |
campaign.channels[] |
call, whatsapp, email |
interaction.channel |
call, whatsapp, email |
interaction.status |
sent, answered, no_answer |
message.role |
agent / bot = nuestro cliente incógnito · user / human = la empresa evaluada |
report.status |
draft, sent |
score.scored_by |
ai, auto |
Company
{ "id": "uuid", "name": "str", "industry": "str|null", "website": "str|null",
"phone": "str|null", "whatsapp_number": "str|null", "email": "str|null",
"city": "str|null", "created_at": "datetime" }Campaign
{ "id": "uuid", "company_id": "uuid", "name": "str", "status": "active|paused|done",
"channels": ["call","whatsapp"], "frequency_days": 7,
"start_date": "date|null", "end_date": "date|null", "created_at": "datetime" }Interaction
{ "id": "uuid", "campaign_id": "uuid", "channel": "call|whatsapp|email",
"status": "sent|answered|no_answer", "initiated_at": "datetime",
"responded_at": "datetime|null", "response_time_sec": "int|null",
"external_id": "str|null" }InteractionScore
{ "id": "uuid", "interaction_id": "uuid", "responded": true, "gave_price": false,
"did_followup": false, "used_name": false, "attempted_close": false,
"quality_score": 20, "reasoning": "explicación de la IA", "scored_by": "ai" }Report
{ "id": "uuid", "campaign_id": "uuid", "generated_at": "datetime",
"period_start": "date|null", "period_end": "date",
"metrics": { ... }, "findings": { ... }, "recommendations": { ... },
"status": "draft|sent" }CallDetail
{ "id": "uuid", "interaction_id": "uuid", "duration_sec": 71, "outcome": "str|null",
"transcript": [ { "role": "agent", "message": "...", "time_in_call_secs": 0, "interrupted": false } ] }Message
{ "id": "uuid", "interaction_id": "uuid", "role": "bot|human", "content": "str",
"channel": "whatsapp", "sent_at": "datetime" }KPIs globales para el home.
{
"total_companies": 2, "total_campaigns": 2, "active_campaigns": 1,
"total_interactions": 19, "scored_interactions": 3, "pending_scoring": 16,
"avg_quality_score": 28.7, "response_rate_pct": 100.0, "total_reports": 1,
"interactions_by_channel": { "call": 10, "whatsapp": 9 }
}Lista de empresas. Query: ?limit=50&offset=0
{ "items": [ { "id": "...", "name": "Inmobiliaria Test", "phone": "+52..." } ],
"limit": 50, "offset": 0 }Empresa + sus campañas anidadas.
{ "id": "...", "name": "Inmobiliaria Test", "phone": "+52...",
"campaigns": [ { "id": "...", "name": "Campaña Q2", "status": "active" } ] }{ "name": "Inmobiliaria Test", "phone": "+525529196649",
"whatsapp_number": "+52...", "email": "info@test.com", "city": "CDMX" }Solo name es obligatorio.
Actualiza solo los campos que mandes (los demás no se tocan).
{ "phone": "+525512345678", "whatsapp_number": "+525512345678" }Todos los campos son opcionales.
Lista de campañas. Query: ?limit=50&offset=0
{ "items": [ { "id": "...", "company_id": "...", "name": "Campaña Q2",
"status": "active", "channels": ["call","whatsapp"], "companies": { "name": "..." } } ],
"limit": 50, "offset": 0 }Campaña + empresa + interacciones.
{ "id": "...", "name": "Campaña Q2", "status": "active",
"companies": { "id": "...", "name": "Inmobiliaria Test" },
"interactions": [ { "id": "...", "channel": "call", "status": "answered" } ] }{ "company_id": "uuid-de-la-empresa", "name": "Campaña Q2",
"channels": ["call","whatsapp"], "frequency_days": 7,
"start_date": "2026-06-01", "end_date": "2026-06-30" }Obligatorios: company_id, name. status default = paused.
Interacciones de una campaña, cada una con su score anidado (para la tabla principal).
[
{ "id": "...", "channel": "call", "status": "answered",
"initiated_at": "2026-05-25T00:03:40Z",
"interaction_scores": [ { "quality_score": 20, "responded": true, "reasoning": "..." } ] },
{ "id": "...", "channel": "whatsapp", "status": "no_answer",
"interaction_scores": [] }
]
interaction_scoreses un array: vacío si aún no se ha evaluado, con 1 objeto si ya.
Detalle + mensajes (útil para whatsapp).
{ "id": "...", "channel": "whatsapp", "status": "answered",
"campaigns": { "name": "..." },
"messages": [ { "role": "bot", "content": "hola, busco depa...", "sent_at": "..." },
{ "role": "human", "content": "claro, ¿presupuesto?", "sent_at": "..." } ] }{ "campaign_id": "uuid-de-la-campaña", "channel": "call" }Si channel = "call" lanza la llamada automáticamente y devuelve también:
{ "id": "...", "channel": "call", "status": "sent",
"call": { "conversation_id": "conv_...", "to_number": "+52...", "status": "calling" } }Score de una interacción + la interacción anidada.
[ { "id": "...", "interaction_id": "...", "responded": true, "gave_price": false,
"did_followup": false, "used_name": false, "attempted_close": false,
"quality_score": 20, "reasoning": "La empresa respondió pero nunca dio precio ni intentó cerrar.",
"scored_by": "ai",
"interactions": { "channel": "call", "campaigns": { "name": "..." } } } ]Analiza UNA interacción a mano (normalmente es automático). Devuelve el score creado.
Evalúa todas las pendientes que ya estén listas (lo mismo que corre el scheduler solo).
{ "scored": [ { "id": "...", "quality_score": 45 } ],
"skipped": [ { "id": "...", "reason": "conversación whatsapp aún activa" } ],
"errors": [] }Reportes de una campaña.
[ {
"id": "...", "campaign_id": "...", "status": "draft", "generated_at": "...",
"metrics": {
"total_interactions": 1, "response_rate_pct": 100.0, "gave_price_pct": 0,
"followup_rate_pct": 0, "used_name_pct": 0, "attempted_close_pct": 0,
"avg_quality_score": 20, "calificacion": "F"
},
"findings": {
"veredicto": "Atención reactiva: responden pero no venden.",
"oportunidad_principal": "Dar precios proactivamente y agendar visita.",
"resumen": "...", "fortalezas": ["..."], "debilidades": ["..."]
},
"recommendations": { "prioritarias": ["..."], "secundarias": ["..."] }
} ]
metrics.calificacion(A–F) yfindings.veredicto/oportunidad_principalaparecen en reportes nuevos.
Genera un reporte nuevo con IA. Devuelve el reporte creado.
Transcript ya limpio (lista de turnos).
[ { "id": "...", "duration_sec": 71, "outcome": null,
"transcript": [ { "role": "agent", "message": "Hola, busco departamento...", "time_in_call_secs": 0, "interrupted": false },
{ "role": "user", "message": "Claro, ¿qué busca?", "time_in_call_secs": 16, "interrupted": false } ] } ]Mensajes de WhatsApp ordenables por sent_at.
[ { "id": "...", "role": "bot", "content": "hola, busco depa", "channel": "whatsapp", "sent_at": "..." },
{ "id": "...", "role": "human", "content": "claro, ¿presupuesto?", "channel": "whatsapp", "sent_at": "..." } ]POST /webhooks/elevenlabs_post_call — lo consume Make tras una llamada.
Endpoint especializado para el webscraper. Con un solo POST crea la empresa, la campaña, la interacción y dispara la llamada automáticamente.
Body:
{
"phone": "+525512345678",
"context": "Inmobiliaria Residencial del Valle, venden depas en Roma Norte CDMX"
}Solo phone es obligatorio. context es texto libre — la IA extrae name, industry, city y website de ahí. Si no se manda context, el nombre de la empresa queda como "Empresa-YYYYMMDD-HHMMSS".
Respuesta:
{
"company_id": "uuid...",
"company_name": "Residencial del Valle",
"campaign_id": "uuid...",
"interaction_id": "uuid...",
"conversation_id": "conv_...",
"status": "calling",
"to_number": "+525512345678"
}Flujo interno:
- Groq parsea el
contexty extrae datos de la empresa - Crea la empresa en DB con
phone+ datos extraídos - Crea una campaña activa con
channels: ["call", "whatsapp"] - Crea una interacción tipo
call - Dispara la llamada vía ElevenLabs → Twilio
| Pantalla | Endpoints a llamar |
|---|---|
| Home | GET /dashboard/summary |
| Lista de campañas | GET /campaigns/ |
| Detalle de campaña | GET /campaigns/{id} + GET /interactions/campaign/{id} + GET /reports/campaign/{id} |
| Detalle de interacción (llamada) | GET /interactions/{id} + GET /call-details/interaction/{id} + GET /interaction-scores/interaction/{id} |
| Detalle de interacción (whatsapp) | GET /interactions/{id} (ya trae messages) + GET /interaction-scores/interaction/{id} |
| Lanzar evaluación | POST /interactions/ con {campaign_id, channel} |
- Roles en conversaciones:
agent/bot= NUESTRO cliente incógnito ·user/human= la EMPRESA evaluada. Mapea las etiquetas en la UI según eso. interaction_scoreses un array: vacío = sin evaluar aún (muestra "Pendiente"), con 1 objeto = ya evaluado.reasoningexplica el porqué del score — ideal para un tooltip o panel de detalle.calificacion(A–F) sirve para un badge de color.veredictopara un encabezado grande.- El scoring es automático (cada ~10 min). Una interacción recién creada puede tardar en tener score; refresca o haz polling.
- Score 0 con
scored_by: "auto"= la empresa nunca respondió (no se gastó IA).