openapi: 3.1.0 info: title: GestorVet MCP Bridge description: Puente REST solo lectura. Respuestas flexibles con GenericResponse válido para Actions. version: 1.5.42 x-gestorvet-change-log: 'v1.5.42 añade desglose emergente de Gastos grupo y expone desglose_gastos_grupo en resultados de comisiones.' servers: - url: https://crm.animalsalut.com/wp-json/gestorvet-mcp/v1 components: securitySchemes: bearerAuth: type: http scheme: bearer schemas: ToolName: type: string enum: - obtener_estado_puente - listar_herramientas - obtener_resumen_negocio - comparar_periodo_secciones - obtener_ingresos_periodo - obtener_kpis_periodo - detectar_facturas_duplicadas - detectar_lineas_manuales - detectar_abonos - obtener_ventas_departamento - obtener_pvp_servicios_con_ventas_reales - obtener_ventas_servicio_periodo - obtener_tiempos_servicios - obtener_articulos_servicio_mapeados - obtener_ventas_empleado - generar_datos_informe_diario - generar_datos_informe_periodo - generar_contexto_informe_negocio - buscar_mascota_o_cliente - obtener_facturas_mascota - obtener_facturas_cliente - buscar_facturas_por_texto - obtener_detalle_abonos - obtener_detalle_abono - obtener_estado_plugin_principal - listar_funcionalidades_plugin - buscar_funcionalidad_plugin - diagnosticar_funcionalidad_plugin - inspeccionar_funcion_plugin - verificar_dependencias_funcionales - system_status - list_available_tools - inspect_business_plugin - get_schema_overview - query_employees - get_employee_detail - query_salaries - explain_salary_calculation - query_schedules - explain_capacity_calculation - query_objective_profit_floor - query_objective_scenarios - explain_capacity_adjusted_objective - query_capacity_adjusted_objectives - query_objective_capacity_settings - query_employee_sellable_capacity - explain_invoice_source - query_commissions - query_commission_screen_results - explain_commission_calculation - query_objectives - explain_objective_calculation - query_price_simulator - simulate_price_scenario - query_simulator_options - explain_price_simulation - query_bonus - explain_bonus_calculation - query_kpis - explain_kpi_calculation - query_fixed_expenses - explain_fixed_expense_allocation - query_config - explain_config_impact - query_audit_snapshots - explain_data_lineage - search_business_data - answer_business_question ToolParameters: type: object additionalProperties: true properties: from: type: string format: date description: Fecha inicial en formato YYYY-MM-DD. to: type: string format: date description: Fecha final en formato YYYY-MM-DD. date: type: string format: date description: Fecha única en formato YYYY-MM-DD. year: type: integer description: Año de configuración o consulta, por ejemplo 2026. month: type: integer minimum: 1 maximum: 12 description: Mes del 1 al 12. month_from: type: integer minimum: 1 maximum: 12 description: Mes inicial del rango mensual. En salarios y costes combínalo con year y month_to. month_to: type: integer minimum: 1 maximum: 12 description: Mes final del rango mensual. En salarios y costes combínalo con year y month_from. mes_desde: type: integer minimum: 1 maximum: 12 description: Alias en español de month_from. mes_hasta: type: integer minimum: 1 maximum: 12 description: Alias en español de month_to. limit: type: integer minimum: 1 maximum: 500 description: Número máximo de resultados. employee_id: type: string description: ID interno del empleado si se conoce. Para nombres visibles como VETE 2 usa employee o employee_name. employee: type: string description: Nombre o texto de empleado. Preferir para nombres visibles como VETE 2, PELU 1 o RECEPCION. employee_name: type: string description: Nombre visible del empleado. department: type: string description: Departamento exacto o texto de departamento. En costes/salarios se usa para filtrar asignaciones o gastos imputados. service: type: string description: Servicio exacto o texto de servicio. En PVP rentable conviene filtrar por servicio si la respuesta completa excede tamaño. group_by: type: string description: Agrupación. Para PVP usa department_service_subservice. include_units: type: boolean description: Incluir unidades reales de facturas si existen. include_credit_notes: type: boolean description: Incluir abonos/rectificativas restados en ventas reales. include_products: type: boolean description: Incluir productos además de servicios si la consulta lo permite. include_services: type: boolean description: Incluir servicios además de productos si la consulta lo permite. service_level: type: string description: 'Nivel de PVP/ventas: parent, subservice o department_service_subservice.' margin_target_pct: type: string description: Margen objetivo para PVP rentable. Acepta 15 o 0.15 para 15%. only_real_sales: type: boolean description: Ventas desde facturas reales; nunca unidades×PVP. include_service_time: type: boolean description: Incluir tiempo_minutos real de servicios. include_time_costing: type: boolean description: Incluir minutos totales y coste por minuto si hay tiempo. type: type: string description: 'Tipo de consulta. Para facturas: invoice, credit_note o all. Para simulador/gastos puede usarse como filtro si el backend lo soporta.' invoice_uid: type: string description: UID interno de factura o abono. num_factura: type: string description: Número visible de factura o abono. include_lines: type: boolean description: Incluir líneas de factura o abono si están disponibles. include_explanation: type: boolean description: Incluir explicación. Recomendado en gastos, salarios y capacidad para revisar pesos, alta/baja, días no trabajados, horas y comisión activa. query: type: string description: Texto de búsqueda. area: type: string description: Área funcional a diagnosticar. function: type: string description: Nombre de función del plugin principal a inspeccionar. kpi: type: string description: Nombre del KPI a explicar. config_key: type: string description: Clave de configuración cuyo impacto se quiere explicar. scope: type: string description: 'Ámbito de configuración: global, anual, departamento, servicio o empleado.' entity: type: string description: Entidad para linaje de datos. id: type: string description: Identificador de entidad para linaje de datos. cuenta_key: type: string description: Clave de cuenta de gasto. question: type: string description: Pregunta de negocio en lenguaje natural. table: type: string description: Tabla concreta para inspección de esquema. include_counts: type: boolean description: Incluir conteos de filas si la herramienta lo permite. format: type: string description: Formato de respuesta. Usa compact para simulador, gastos, salarios o respuestas agregadas grandes. sim_global_pct: type: string description: Subida global de precios del simulador. Acepta 5 o 0.05 para 5%. sim_services_pct: type: string description: Subida solo para servicios. sim_products_pct: type: string description: Subida solo para productos. sim_department_name: type: string description: Departamento específico al que aplicar una subida adicional. sim_department_pct: type: string description: Subida adicional del departamento específico. sim_service_name: type: string description: Servicio específico al que aplicar una subida adicional. sim_service_pct: type: string description: Subida adicional del servicio específico. recalculate_objectives: type: boolean description: Recalcular objetivos del escenario con la subida de precios. units_from: type: string format: date description: Inicio del rango de unidades PVP. No cambia ventas/gastos. units_to: type: string format: date description: Fin del rango de unidades PVP. No cambia ventas/gastos. salary_overrides: type: object additionalProperties: true description: Overrides temporales de coste salarial por empleado en simulaciones. No guardar. No usar para explicar costes actuales salvo que el usuario pida un escenario simulado. properties: _example_key: type: string description: Opcional. El objeto acepta claves dinámicas adicionales. fixed_overrides: type: object additionalProperties: true description: Overrides temporales de gastos fijos por cuenta en simulaciones. No guardar. No confundir con gastos_fijos imputados ya calculados por pesos. properties: _example_key: type: string description: Opcional. El objeto acepta claves dinámicas adicionales. config_overrides: type: object additionalProperties: true description: Overrides temporales de tramos, bonus o crecimiento. properties: _example_key: type: string description: Opcional. El objeto acepta claves dinámicas adicionales. pvp_manual_units: type: object additionalProperties: true description: Unidades manuales por group_key para PVP rentable. properties: _example_key: type: string description: Opcional. El objeto acepta claves dinámicas adicionales. pvp_manual_units_base: type: object additionalProperties: true description: Unidades automáticas base para comparar overrides manuales. properties: _example_key: type: string description: Opcional. El objeto acepta claves dinámicas adicionales. pvp_reassign_ineligible_department_activity: type: boolean description: Solo PVP rentable por subservicio. No cambia KPIs ni histórico. sim_reassign_ineligible_department_activity: type: boolean description: Alias antiguo de pvp_reassign_ineligible_department_activity. Mantener solo por compatibilidad. reassign_ineligible_department_activity: type: boolean description: Alias antiguo de pvp_reassign_ineligible_department_activity para automatizaciones. include_rows: type: boolean description: Incluir filas de comparación del simulador. Puede devolver mucho contenido; usa false para totales oficiales y true solo si necesitas detalle. article: type: string description: Texto de artículo/referencia para mapear a subservicio. articulo: type: string description: Alias de article. include_derived_from_invoices: type: boolean description: Incluir mapeo deducido de factura_lineas. use_article_mapping: type: boolean description: Cruzar ventas por Artículos del apartado Servicios. previous_year: type: integer description: Año anterior automático year-1. base_year: type: integer description: Alias heredado de previous_year. occupancy_from: type: string description: Inicio ocupación; debe estar en el año anterior. occupancy_to: type: string description: Fin ocupación; debe estar en el año anterior. occupancy_target_pct: type: string description: Override opcional; por defecto se lee del CRM. sellable_pct: type: string description: Legacy opcional; no resta objetivo si hay ocupación CRM. only_commissionable: type: boolean description: Usar solo departamentos con comisión activa. include_real_occupancy: type: boolean description: Incluir horas reales del rango de ocupación. profit_margin_target_pct: type: string description: Margen objetivo para rentabilidad. use_crm_capacity_settings: type: boolean description: Usar configuración real de CRM para capacidad. description: Parámetros comunes. Ventas reales y gastos deben usar el mismo periodo. units_from/units_to solo definen unidades del PVP. ToolCallRequest: type: object required: - tool additionalProperties: false properties: tool: $ref: '#/components/schemas/ToolName' parameters: $ref: '#/components/schemas/ToolParameters' description: Solicitud de herramienta interna. Envía tool y parameters. No usar para SQL libre ni cambios de datos. McpJsonRpcRequest: type: object required: - jsonrpc - method additionalProperties: true properties: jsonrpc: type: string enum: - '2.0' id: type: string description: ID opcional de la llamada JSON-RPC. method: type: string enum: - initialize - tools/list - tools/call params: type: object additionalProperties: true properties: name: $ref: '#/components/schemas/ToolName' arguments: $ref: '#/components/schemas/ToolParameters' GenericResponse: type: object description: Respuesta JSON flexible del puente. Schema válido para Actions; acepta campos dinámicos. properties: ok: type: boolean description: Indica si la operación fue correcta. success: type: boolean description: Alias de éxito si el backend lo devuelve. status: type: string description: Estado textual. version: type: string description: Versión del componente que responde. tool: type: string description: Herramienta ejecutada, si aplica. message: type: string description: Mensaje legible. error: type: string description: Error si existe. code: type: string description: Código de error o estado. data: &id001 type: object description: Objeto flexible devuelto por el backend. properties: value: type: string total: type: number count: type: integer rows: type: array items: &id002 type: object description: Objeto flexible con campos dinámicos. properties: id: type: string name: type: string key: type: string value: type: string amount: type: number total: type: number additionalProperties: true additionalProperties: true result: *id001 summary: *id001 period: type: object description: Periodo de consulta, si aplica. properties: from: type: string to: type: string date: type: string year: type: integer month: type: integer month_from: type: integer month_to: type: integer additionalProperties: true totals: type: object description: Totales agregados, si aplica. properties: total: type: number count: type: integer amount: type: number additionalProperties: true meta: *id001 warnings: type: array description: Avisos devueltos por la API. items: type: string alerts: type: array description: Alertas devueltas por la API. items: type: string limitations: type: array description: Limitaciones devueltas por la API. items: type: string rows: type: array description: Filas de datos cuando aplique. items: *id002 items: type: array description: Lista flexible cuando aplique. items: *id002 additionalProperties: true security: - bearerAuth: [] paths: /status: get: operationId: obtener_estado_puente summary: Obtener estado del puente GestorVet MCP Bridge. x-openai-isConsequential: false responses: '200': description: Estado del puente. content: application/json: schema: $ref: '#/components/schemas/GenericResponse' '400': description: Error de validación o herramienta no permitida. '401': description: Token ausente, inválido o puente desactivado. /tools: get: operationId: listar_herramientas summary: Listar herramientas disponibles. x-openai-isConsequential: false responses: '200': description: Lista de herramientas disponibles. content: application/json: schema: $ref: '#/components/schemas/GenericResponse' '400': description: Error de validación o herramienta no permitida. '401': description: Token ausente, inválido o puente desactivado. /manifest: get: operationId: obtener_manifiesto_mcp summary: Obtener manifiesto de herramientas MCP disponibles. x-openai-isConsequential: false responses: '200': description: Manifiesto de herramientas. content: application/json: schema: $ref: '#/components/schemas/GenericResponse' '400': description: Error de validación o herramienta no permitida. '401': description: Token ausente, inválido o puente desactivado. /call: post: operationId: ejecutar_herramienta summary: Ejecutar una herramienta interna de solo lectura del MCP. description: Ejecuta herramientas MCP de solo lectura. PVP usa ventas reales, gastos del mismo periodo y tiempo_minutos real si existe. x-openai-isConsequential: false requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/ToolCallRequest' responses: '200': description: Respuesta JSON de la herramienta. content: application/json: schema: $ref: '#/components/schemas/GenericResponse' '400': description: Error de validación o herramienta no permitida. '401': description: Token ausente, inválido o puente desactivado. /mcp: post: operationId: llamar_mcp_jsonrpc summary: Endpoint MCP JSON-RPC compatible con initialize, tools/list y tools/call. description: Uso opcional. Para ChatGPT Actions normales se recomienda ejecutar_herramienta sobre /call. x-openai-isConsequential: false requestBody: required: true content: application/json: schema: $ref: '#/components/schemas/McpJsonRpcRequest' responses: '200': description: Respuesta JSON-RPC del endpoint MCP. content: application/json: schema: $ref: '#/components/schemas/GenericResponse' '400': description: Error de validación o herramienta no permitida. '401': description: Token ausente, inválido o puente desactivado. /report/daily: get: operationId: obtener_informe_diario summary: Obtener datos para informe diario. x-openai-isConsequential: false parameters: - name: date in: query required: true schema: type: string format: date description: Fecha única en formato YYYY-MM-DD. description: Fecha única en formato YYYY-MM-DD. responses: '200': description: Datos del informe diario. content: application/json: schema: $ref: '#/components/schemas/GenericResponse' '400': description: Error de validación o herramienta no permitida. '401': description: Token ausente, inválido o puente desactivado. /report/period: get: operationId: obtener_informe_periodo summary: Obtener datos para informe por periodo. x-openai-isConsequential: false parameters: - name: from in: query required: true schema: type: string format: date description: Fecha inicial en formato YYYY-MM-DD. description: Fecha inicial en formato YYYY-MM-DD. - name: to in: query required: true schema: type: string format: date description: Fecha final en formato YYYY-MM-DD. description: Fecha final en formato YYYY-MM-DD. responses: '200': description: Datos del informe por periodo. content: application/json: schema: $ref: '#/components/schemas/GenericResponse' '400': description: Error de validación o herramienta no permitida. '401': description: Token ausente, inválido o puente desactivado. /compare: get: operationId: comparar_secciones_periodo summary: Comparar ingresos, KPIs y secciones del plugin en un periodo. x-openai-isConsequential: false parameters: - name: from in: query required: true schema: type: string format: date description: Fecha inicial en formato YYYY-MM-DD. description: Fecha inicial en formato YYYY-MM-DD. - name: to in: query required: true schema: type: string format: date description: Fecha final en formato YYYY-MM-DD. description: Fecha final en formato YYYY-MM-DD. responses: '200': description: Comparativa de secciones. content: application/json: schema: $ref: '#/components/schemas/GenericResponse' '400': description: Error de validación o herramienta no permitida. '401': description: Token ausente, inválido o puente desactivado. /income: get: operationId: obtener_ingresos_periodo_directo summary: Obtener ingresos/facturación de un periodo description: Endpoint directo recomendado para preguntas de facturación, ingresos o ventas totales. Equivale a ejecutar la herramienta obtener_ingresos_periodo. parameters: - name: from in: query required: true schema: type: string format: date - name: to in: query required: true schema: type: string format: date responses: '200': description: Ingresos del periodo content: application/json: schema: $ref: '#/components/schemas/GenericResponse' '400': description: Error de validación content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' '401': description: Token ausente o inválido content: application/json: schema: $ref: '#/components/schemas/ErrorResponse' /sales: get: operationId: obtener_ingresos_periodo_directo_alias_sales summary: Alias directo para obtener ingresos/facturación de un periodo description: Alias de /income para compatibilidad. Úsalo igual con from y to. parameters: - name: from in: query required: true schema: type: string format: date - name: to in: query required: true schema: type: string format: date responses: '200': description: Ingresos del periodo content: application/json: schema: $ref: '#/components/schemas/GenericResponse' /plugin/status: get: operationId: obtener_estado_plugin_principal_endpoint summary: Obtener estado del plugin principal GestorVet. x-openai-isConsequential: false responses: '200': description: Estado del plugin principal. content: application/json: schema: $ref: '#/components/schemas/GenericResponse' '400': description: Error de validación o herramienta no permitida. '401': description: Token ausente, inválido o puente desactivado. /plugin/search: get: operationId: buscar_funcionalidad_plugin_endpoint summary: Buscar funcionalidades internas del plugin GestorVet. x-openai-isConsequential: false parameters: - name: q in: query required: true schema: type: string description: Texto a buscar, por ejemplo franjas, bonus, gastos o comisiones. responses: '200': description: Resultados de búsqueda funcional. content: application/json: schema: $ref: '#/components/schemas/GenericResponse' '400': description: Error de validación o herramienta no permitida. '401': description: Token ausente, inválido o puente desactivado. /plugin/diagnose: get: operationId: diagnosticar_funcionalidad_plugin_endpoint summary: Diagnosticar un área funcional del plugin GestorVet. x-openai-isConsequential: false parameters: - name: area in: query required: true schema: type: string description: Área funcional a diagnosticar. description: Área funcional a diagnosticar. responses: '200': description: Diagnóstico funcional. content: application/json: schema: $ref: '#/components/schemas/GenericResponse' '400': description: Error de validación o herramienta no permitida. '401': description: Token ausente, inválido o puente desactivado. /pet-search: get: operationId: buscar_mascota_cliente_endpoint summary: Buscar mascota o cliente en facturas. x-openai-isConsequential: false parameters: - name: q in: query required: true schema: type: string description: Nombre o texto de mascota o cliente. - name: from in: query required: false schema: type: string format: date description: Fecha inicial YYYY-MM-DD. - name: to in: query required: false schema: type: string format: date description: Fecha final YYYY-MM-DD. - name: limit in: query required: false schema: type: integer minimum: 1 maximum: 500 description: Número máximo de resultados. responses: '200': description: Coincidencias de mascota o cliente. content: application/json: schema: $ref: '#/components/schemas/GenericResponse' '400': description: Error de validación o herramienta no permitida. '401': description: Token ausente, inválido o puente desactivado. /credit-note: get: operationId: obtener_detalle_abono_endpoint summary: Obtener detalle de un abono concreto. x-openai-isConsequential: false parameters: - name: invoice_uid in: query required: false schema: type: string description: UID interno del abono. - name: num_factura in: query required: false schema: type: string description: Número visible del abono. - name: include_lines in: query required: false schema: type: boolean description: Incluir líneas. - name: limit in: query required: false schema: type: integer minimum: 1 maximum: 500 description: Número máximo de líneas. responses: '200': description: Detalle de abono. content: application/json: schema: $ref: '#/components/schemas/GenericResponse' '400': description: Error de validación o herramienta no permitida. '401': description: Token ausente, inválido o puente desactivado. /credit-notes: get: operationId: obtener_detalle_abonos_endpoint summary: Obtener listado de abonos por periodo. x-openai-isConsequential: false parameters: - name: from in: query required: false schema: type: string format: date description: Fecha inicial YYYY-MM-DD. - name: to in: query required: false schema: type: string format: date description: Fecha final YYYY-MM-DD. - name: include_lines in: query required: false schema: type: boolean description: Incluir líneas. - name: limit in: query required: false schema: type: integer minimum: 1 maximum: 500 description: Número máximo de resultados. responses: '200': description: Listado de abonos. content: application/json: schema: $ref: '#/components/schemas/GenericResponse' '400': description: Error de validación o herramienta no permitida. '401': description: Token ausente, inválido o puente desactivado. x-gestorvet-cost-semantics: version: 1.5.18 purpose: Evitar mezclar capas de costes al responder sobre rentabilidad, gastos y salarios. cost_layers: - name: cuentas_gasto_brutas source: query_fixed_expenses.data.accounts / gastos_cuentas meaning: Importes por cuenta y mes. Pueden incluir salarios, seguridad social, indemnizaciones o cuentas sin reparto. do_not: No usar como coste imputado por servicio ni sumar a salarios si hay riesgo de doble conteo. - name: gastos_fijos_imputados_con_pesos source: query_fixed_expenses.data.fixed_expenses / gastos_fijos meaning: Resultado de aplicar gastos_pesos a servicios/departamentos. use_for: Rentabilidad por servicio/departamento con asignación de pesos. - name: cuentas_sin_reparto source: query_fixed_expenses.data.unallocated_accounts meaning: Cuentas sin pesos. Informar por separado; no añadirlas automáticamente al coste imputado. - name: gasto_fijo_estimado_simulador source: query_price_simulator.summary.totals.official.gasto_fijo_estimado meaning: Métrica oficial de pantalla Simulador/Comisiones. No asumir que coincide con gastos fijos con pesos. - name: salarios_configurados source: query_salaries / gastos_empleados meaning: 'Configuración salarial por empleado: sueldo_mes, alta, baja, días no trabajados, horas y salario anual.' - name: asignacion_salarial_departamentos source: query_schedules.data.department_assignments / gastos_empleado_departamentos meaning: Horas por departamento, comisión activa, servicios, horas de servicio y ocupación. - name: gasto_salarial_estimado_simulador source: query_price_simulator.summary.totals.official.gasto_salarial_estimado meaning: Coste salarial interpretado por el motor del Simulador/Comisiones. mandatory_answering_rule: En rentabilidad/PVP indicar base, mismo periodo para ventas y gastos, y separar unidades históricas. No inventar ventas por servicio. recommended_calls_for_profitability: - obtener_ingresos_periodo - query_fixed_expenses - explain_fixed_expense_allocation - query_salaries - query_schedules - query_price_simulator - obtener_ventas_servicio_periodo - obtener_pvp_servicios_con_ventas_reales pvp_rule: pvp_reassign_ineligible_department_activity solo afecta al bloque PVP rentable por subservicio; no altera rentabilidad global, KPIs, comisiones oficiales ni histórico. pvp_real_sales_rule: En PVP, no sustituir ventas reales por volumen teórico calculado con unidades por PVP. x-gestorvet-salary-semantics: version: 1.5.18 purpose: Asegurar que los cálculos salariales respetan la sección completa de cada empleado. sources: salary_config: query_salaries / gastos_empleados department_assignment: query_schedules.data.department_assignments / gastos_empleado_departamentos explain: - explain_salary_calculation - explain_capacity_calculation must_consider_fields: - fecha_alta - fecha_baja - salario_anual - sueldo_mes_* - dias_trabajados - festivos_anuales - no_trabajados_* - horas_contrato_semanales - horas_contrato_mes_* - department_assignments.horas_semanales - department_assignments.comision_activa - department_assignments.ocupacion_cache rules: - No imputar sueldo antes de fecha_alta ni después de fecha_baja. - Si el mes completo está fuera del contrato, coste devengado = 0 aunque exista sueldo_mes configurado. - Si alta/baja cae dentro del mes, usar coste oficial/devengado del plugin si está disponible; si no, avisar que es estimación. - Si no_trabajados cubre el mes o coste salarial del periodo es 0, no imputar sueldo salvo que el usuario pida configuración bruta. - Distinguir sueldo configurado bruto de coste salarial devengado/corregido por fechas. - No sumar cuentas salariales de gastos_cuentas con query_salaries o gasto_salarial_estimado sin explicar doble conteo. - En tablas de PVP, salarios imputables deben respetar alta, baja y días no trabajados del mismo periodo que ventas. example: Si VETE 1 tiene fecha_alta 2026-06-15, enero-abril deben ser 0 como coste devengado aunque sueldo_mes_* tenga 1470. x-gestorvet-pvp-real-sales-semantics: version: 1.5.18 rules: - Ventas reales y gastos deben usar el mismo periodo contable. - units_from y units_to solo sirven para unidades PVP; no cambian ventas ni gastos. - Nunca llamar ventas reales a unidades por PVP. - obtener_ventas_servicio_periodo devuelve ventas reales desde factura_lineas por group_key de subservicio. - obtener_pvp_servicios_con_ventas_reales cruza ventas reales, gastos imputables y PVP por group_key. - La tabla PVP completa debe venir de obtener_pvp_servicios_con_ventas_reales, no de consultas parciales. - Si la herramienta no está disponible, actualizar MCP Bridge/YAML; no reconstruir la tabla con sustitutos. required_columns_for_pvp_table: - departamento - servicio - servicio_padre - subservicio - group_key - unidades_pvp - ventas_usadas_pvp - ventas_reales_periodo - gasto_fijo_imputable_periodo - gasto_salarial_imputable_periodo - gasto_total_imputable_periodo - gasto_imputable_pvp - margen_actual - pvp_actual - pvp_minimo_rentable - pvp_recomendado - estado - aggregation_level - mapping_confidence - ventas_source - pvp_source preferred_tools: - obtener_pvp_servicios_con_ventas_reales - obtener_ventas_servicio_periodo x-gestorvet-pvp-real-sales: obtener_pvp_servicios_con_ventas_reales cruza factura_lineas por group_key de subservicio con PVP rentable; no usa consultas parciales para completar la tabla. x-gestorvet-objective-capacity-semantics: version: 1.5.23 rules: - La configuración real de ocupación y horas no facturadas vive en el CRM. - El MCP debe leer gvfcrm_objective_capacity_settings_v1 y no fijar porcentajes por defecto. - occupancy_from/occupancy_to son selector visual en CRM; en Action solo son override explícito. - horas_no_vendibles se descuentan por empleado/departamento antes de aplicar ocupación objetivo. - La ocupación real no rebaja objetivos; sirve para auditoría. tools: - query_objective_capacity_settings - query_employee_sellable_capacity - query_capacity_adjusted_objectives - query_objective_scenarios