Saltar al contenido principal

Documentation Index

Fetch the complete documentation index at: https://docs.openclaw.ai/llms.txt

Use this file to discover all available pages before exploring further.

Esta guía explica cómo crear un plugin proveedor que agrega un proveedor de modelos (LLM) a OpenClaw. Al final tendrás un proveedor con un catálogo de modelos, autenticación con clave de API y resolución dinámica de modelos.
Si nunca has creado ningún plugin de OpenClaw, lee primero Primeros pasos para conocer la estructura básica del paquete y la configuración del manifiesto.
Los plugins proveedores agregan modelos al bucle de inferencia normal de OpenClaw. Si el modelo debe ejecutarse mediante un daemon de agente nativo que controla hilos, compaction o eventos de herramientas, combina el proveedor con un arnés de agente en lugar de colocar los detalles del protocolo del daemon en el núcleo.

Tutorial

1

Package and manifest

Paso 1: Paquete y manifiesto

{
  "name": "@myorg/openclaw-acme-ai",
  "version": "1.0.0",
  "type": "module",
  "openclaw": {
    "extensions": ["./index.ts"],
    "providers": ["acme-ai"],
    "compat": {
      "pluginApi": ">=2026.3.24-beta.2",
      "minGatewayVersion": "2026.3.24-beta.2"
    },
    "build": {
      "openclawVersion": "2026.3.24-beta.2",
      "pluginSdkVersion": "2026.3.24-beta.2"
    }
  }
}
El manifiesto declara providerAuthEnvVars para que OpenClaw pueda detectar credenciales sin cargar el runtime de tu plugin. Agrega providerAuthAliases cuando una variante de proveedor deba reutilizar la autenticación del id de otro proveedor. modelSupport es opcional y permite que OpenClaw cargue automáticamente tu plugin proveedor a partir de ids de modelo abreviados como acme-large antes de que existan hooks de runtime. Si publicas el proveedor en ClawHub, esos campos openclaw.compat y openclaw.build son obligatorios en package.json.
2

Register the provider

Un proveedor de texto mínimo necesita un id, label, auth y catalog. catalog es el hook de runtime/configuración propio del proveedor; puede llamar a APIs en vivo del proveedor y devuelve entradas de models.providers.
index.ts
import { definePluginEntry } from "openclaw/plugin-sdk/plugin-entry";
import { createProviderApiKeyAuthMethod } from "openclaw/plugin-sdk/provider-auth";

export default definePluginEntry({
  id: "acme-ai",
  name: "Acme AI",
  description: "Acme AI model provider",
  register(api) {
    api.registerProvider({
      id: "acme-ai",
      label: "Acme AI",
      docsPath: "/providers/acme-ai",
      envVars: ["ACME_AI_API_KEY"],

      auth: [
        createProviderApiKeyAuthMethod({
          providerId: "acme-ai",
          methodId: "api-key",
          label: "Acme AI API key",
          hint: "API key from your Acme AI dashboard",
          optionKey: "acmeAiApiKey",
          flagName: "--acme-ai-api-key",
          envVar: "ACME_AI_API_KEY",
          promptMessage: "Enter your Acme AI API key",
          defaultModel: "acme-ai/acme-large",
        }),
      ],

      catalog: {
        order: "simple",
        run: async (ctx) => {
          const apiKey =
            ctx.resolveProviderApiKey("acme-ai").apiKey;
          if (!apiKey) return null;
          return {
            provider: {
              baseUrl: "https://api.acme-ai.com/v1",
              apiKey,
              api: "openai-completions",
              models: [
                {
                  id: "acme-large",
                  name: "Acme Large",
                  reasoning: true,
                  input: ["text", "image"],
                  cost: { input: 3, output: 15, cacheRead: 0.3, cacheWrite: 3.75 },
                  contextWindow: 200000,
                  maxTokens: 32768,
                },
                {
                  id: "acme-small",
                  name: "Acme Small",
                  reasoning: false,
                  input: ["text"],
                  cost: { input: 1, output: 5, cacheRead: 0.1, cacheWrite: 1.25 },
                  contextWindow: 128000,
                  maxTokens: 8192,
                },
              ],
            },
          };
        },
      },
    });

    api.registerModelCatalogProvider({
      provider: "acme-ai",
      kinds: ["text"],
      liveCatalog: async (ctx) => {
        const apiKey = ctx.resolveProviderApiKey("acme-ai").apiKey;
        if (!apiKey) return null;
        return [
          {
            kind: "text",
            provider: "acme-ai",
            model: "acme-large",
            label: "Acme Large",
            source: "live",
          },
        ];
      },
    });
  },
});
registerModelCatalogProvider es la superficie de catálogo de plano de control más reciente para interfaces de lista/ayuda/selector. Úsala para filas de texto, generación de imágenes, generación de video y generación de música. Mantén las llamadas a endpoints del proveedor y el mapeo de respuestas en el plugin; OpenClaw controla la forma de fila compartida, las etiquetas de origen y el renderizado de ayuda.Este es un proveedor funcional. Ahora los usuarios pueden ejecutar openclaw onboard --acme-ai-api-key <key> y seleccionar acme-ai/acme-large como su modelo.Si el proveedor upstream usa tokens de control distintos a los de OpenClaw, agrega una pequeña transformación de texto bidireccional en lugar de reemplazar la ruta de streaming:
api.registerTextTransforms({
  input: [
    { from: /red basket/g, to: "blue basket" },
    { from: /paper ticket/g, to: "digital ticket" },
    { from: /left shelf/g, to: "right shelf" },
  ],
  output: [
    { from: /blue basket/g, to: "red basket" },
    { from: /digital ticket/g, to: "paper ticket" },
    { from: /right shelf/g, to: "left shelf" },
  ],
});
input reescribe el prompt de sistema final y el contenido de mensajes de texto antes del transporte. output reescribe los deltas de texto del asistente y el texto final antes de que OpenClaw analice sus propios marcadores de control o la entrega del canal.Para proveedores incluidos que solo registran un proveedor de texto con autenticación por clave de API más un único runtime respaldado por catálogo, prefiere el helper más específico defineSingleProviderPluginEntry(...):
import { defineSingleProviderPluginEntry } from "openclaw/plugin-sdk/provider-entry";

export default defineSingleProviderPluginEntry({
  id: "acme-ai",
  name: "Acme AI",
  description: "Acme AI model provider",
  provider: {
    label: "Acme AI",
    docsPath: "/providers/acme-ai",
    auth: [
      {
        methodId: "api-key",
        label: "Acme AI API key",
        hint: "API key from your Acme AI dashboard",
        optionKey: "acmeAiApiKey",
        flagName: "--acme-ai-api-key",
        envVar: "ACME_AI_API_KEY",
        promptMessage: "Enter your Acme AI API key",
        defaultModel: "acme-ai/acme-large",
      },
    ],
    catalog: {
      buildProvider: () => ({
        api: "openai-completions",
        baseUrl: "https://api.acme-ai.com/v1",
        models: [{ id: "acme-large", name: "Acme Large" }],
      }),
      buildStaticProvider: () => ({
        api: "openai-completions",
        baseUrl: "https://api.acme-ai.com/v1",
        models: [{ id: "acme-large", name: "Acme Large" }],
      }),
    },
  },
});
buildProvider es la ruta de catálogo en vivo que se usa cuando OpenClaw puede resolver la autenticación real del proveedor. Puede realizar descubrimiento específico del proveedor. Usa buildStaticProvider solo para filas offline que sean seguras de mostrar antes de configurar la autenticación; no debe requerir credenciales ni hacer solicitudes de red. La pantalla actual de models list --all de OpenClaw ejecuta catálogos estáticos solo para plugins proveedores incluidos, con configuración vacía, entorno vacío y sin rutas de agente/espacio de trabajo.Si tu flujo de autenticación también necesita parchear models.providers.*, alias y el modelo predeterminado del agente durante el onboarding, usa los helpers de preset de openclaw/plugin-sdk/provider-onboard. Los helpers más específicos son createDefaultModelPresetAppliers(...), createDefaultModelsPresetAppliers(...) y createModelCatalogPresetAppliers(...).Cuando el endpoint nativo de un proveedor admite bloques de uso en streaming en el transporte normal openai-completions, prefiere los helpers de catálogo compartidos en openclaw/plugin-sdk/provider-catalog-shared en lugar de codificar comprobaciones de id de proveedor. supportsNativeStreamingUsageCompat(...) y applyProviderNativeStreamingUsageCompat(...) detectan el soporte desde el mapa de capacidades del endpoint, por lo que endpoints nativos estilo Moonshot/DashScope siguen optando por participar aunque un plugin use un id de proveedor personalizado.
3

Add dynamic model resolution

Si tu proveedor acepta IDs de modelo arbitrarios (como un proxy o router), agrega resolveDynamicModel:
api.registerProvider({
  // ... id, label, auth, catalog from above

  resolveDynamicModel: (ctx) => ({
    id: ctx.modelId,
    name: ctx.modelId,
    provider: "acme-ai",
    api: "openai-completions",
    baseUrl: "https://api.acme-ai.com/v1",
    reasoning: false,
    input: ["text"],
    cost: { input: 0, output: 0, cacheRead: 0, cacheWrite: 0 },
    contextWindow: 128000,
    maxTokens: 8192,
  }),
});
Si la resolución requiere una llamada de red, usa prepareDynamicModel para el calentamiento asíncrono; resolveDynamicModel se ejecuta de nuevo después de que termine.
4

Add runtime hooks (as needed)

La mayoría de los proveedores solo necesitan catalog + resolveDynamicModel. Agrega hooks incrementalmente a medida que tu proveedor los requiera.Los builders de helpers compartidos ahora cubren las familias de replay/compatibilidad de herramientas más comunes, por lo que los plugins normalmente no necesitan conectar cada hook manualmente uno por uno:
import { buildProviderReplayFamilyHooks } from "openclaw/plugin-sdk/provider-model-shared";
import { buildProviderStreamFamilyHooks } from "openclaw/plugin-sdk/provider-stream";
import { buildProviderToolCompatFamilyHooks } from "openclaw/plugin-sdk/provider-tools";

const GOOGLE_FAMILY_HOOKS = {
  ...buildProviderReplayFamilyHooks({ family: "google-gemini" }),
  ...buildProviderStreamFamilyHooks("google-thinking"),
  ...buildProviderToolCompatFamilyHooks("gemini"),
};

api.registerProvider({
  id: "acme-gemini-compatible",
  // ...
  ...GOOGLE_FAMILY_HOOKS,
});
Familias de replay disponibles hoy:
FamiliaLo que integraEjemplos incluidos
openai-compatiblePolítica compartida de reproducción al estilo de OpenAI para transportes compatibles con OpenAI, incluida la limpieza de identificadores de llamadas a herramientas, correcciones del orden con el asistente primero y validación genérica de turnos de Gemini cuando el transporte la necesitamoonshot, ollama, xai, zai
anthropic-by-modelPolítica de reproducción consciente de Claude elegida por modelId, de modo que los transportes de mensajes de Anthropic solo reciben limpieza de bloques de pensamiento específica de Claude cuando el modelo resuelto es realmente un id de Claudeamazon-bedrock, anthropic-vertex
google-geminiPolítica de reproducción nativa de Gemini más limpieza de reproducción de arranque y modo de salida de razonamiento etiquetadagoogle, google-gemini-cli
passthrough-geminiLimpieza de firmas de pensamiento de Gemini para modelos Gemini que se ejecutan mediante transportes proxy compatibles con OpenAI; no habilita validación de reproducción nativa de Gemini ni reescrituras de arranqueopenrouter, kilocode, opencode, opencode-go
hybrid-anthropic-openaiPolítica híbrida para proveedores que mezclan superficies de modelos de mensajes de Anthropic y compatibles con OpenAI en un plugin; la eliminación opcional de bloques de pensamiento solo para Claude permanece acotada al lado de Anthropicminimax
Familias de transmisión disponibles actualmente:
FamiliaLo que integraEjemplos incluidos
google-thinkingNormalización de cargas útiles de pensamiento de Gemini en la ruta de transmisión compartidagoogle, google-gemini-cli
kilocode-thinkingEnvoltorio de razonamiento de Kilo en la ruta de transmisión proxy compartida, con kilo/auto y los identificadores de razonamiento proxy no compatibles omitiendo el pensamiento inyectadokilocode
moonshot-thinkingAsignación de carga útil de pensamiento nativo binario de Moonshot desde la configuración + nivel /thinkmoonshot
minimax-fast-modeReescritura del modelo de modo rápido de MiniMax en la ruta de transmisión compartidaminimax, minimax-portal
openai-responses-defaultsEnvoltorios compartidos nativos de Responses de OpenAI/Codex: encabezados de atribución, /fast/serviceTier, verbosidad del texto, búsqueda web nativa de Codex, conformación de cargas útiles de compatibilidad de razonamiento y gestión de contexto de Responsesopenai, openai-codex
openrouter-thinkingEnvoltorio de razonamiento de OpenRouter para rutas proxy, con omisiones de modelos no compatibles/auto gestionadas centralmenteopenrouter
tool-stream-default-onEnvoltorio tool_stream activado de forma predeterminada para proveedores como Z.AI que quieren transmisión de herramientas salvo que se desactive explícitamentezai
Cada constructor de familia se compone de funciones auxiliares públicas de menor nivel exportadas desde el mismo paquete, a las que puedes recurrir cuando un proveedor necesita apartarse del patrón común:
  • openclaw/plugin-sdk/provider-model-shared - ProviderReplayFamily, buildProviderReplayFamilyHooks(...) y los constructores de reproducción sin procesar (buildOpenAICompatibleReplayPolicy, buildAnthropicReplayPolicyForModel, buildGoogleGeminiReplayPolicy, buildHybridAnthropicOrOpenAIReplayPolicy). También exporta funciones auxiliares de reproducción de Gemini (sanitizeGoogleGeminiReplayHistory, resolveTaggedReasoningOutputMode) y funciones auxiliares de punto de conexión/modelo (resolveProviderEndpoint, normalizeProviderId, normalizeGooglePreviewModelId).
  • openclaw/plugin-sdk/provider-stream - ProviderStreamFamily, buildProviderStreamFamilyHooks(...), composeProviderStreamWrappers(...), además de los envoltorios compartidos de OpenAI/Codex (createOpenAIAttributionHeadersWrapper, createOpenAIFastModeWrapper, createOpenAIServiceTierWrapper, createOpenAIResponsesContextManagementWrapper, createCodexNativeWebSearchWrapper), el envoltorio compatible con OpenAI de DeepSeek V4 (createDeepSeekV4OpenAICompatibleThinkingWrapper), la limpieza de prellenado de pensamiento de Anthropic Messages (createAnthropicThinkingPrefillPayloadWrapper) y los envoltorios compartidos de proxy/proveedor (createOpenRouterWrapper, createToolStreamWrapper, createMinimaxFastModeWrapper).
  • openclaw/plugin-sdk/provider-tools - ProviderToolCompatFamily, buildProviderToolCompatFamilyHooks("gemini") y las funciones auxiliares subyacentes de esquemas de Gemini (normalizeGeminiToolSchemas, inspectGeminiToolSchemas).
Algunas funciones auxiliares de transmisión permanecen locales al proveedor a propósito. @openclaw/anthropic-provider mantiene wrapAnthropicProviderStream, resolveAnthropicBetas, resolveAnthropicFastMode, resolveAnthropicServiceTier y los constructores de envoltorios de Anthropic de menor nivel en su propio punto de extensión público api.ts / contract-api.ts porque codifican el manejo de betas de OAuth de Claude y el control de context1m. De manera similar, el plugin xAI mantiene la conformación nativa de Responses de xAI en su propio wrapStreamFn (alias de /fast, tool_stream predeterminado, limpieza de herramientas estrictas no compatibles, eliminación de cargas útiles de razonamiento específica de xAI).El mismo patrón de raíz de paquete también respalda @openclaw/openai-provider (constructores de proveedor, funciones auxiliares de modelo predeterminado, constructores de proveedor en tiempo real) y @openclaw/openrouter-provider (constructor de proveedor más funciones auxiliares de incorporación/configuración).
Para proveedores que necesitan un intercambio de tokens antes de cada llamada de inferencia:
prepareRuntimeAuth: async (ctx) => {
  const exchanged = await exchangeToken(ctx.apiKey);
  return {
    apiKey: exchanged.token,
    baseUrl: exchanged.baseUrl,
    expiresAt: exchanged.expiresAt,
  };
},
OpenClaw llama a los puntos de extensión en este orden. La mayoría de los proveedores solo usan 2 o 3: Los campos de proveedor solo para compatibilidad que OpenClaw ya no invoca, como ProviderPlugin.capabilities y suppressBuiltInModel, no se enumeran aquí.
#Punto de extensiónCuándo usarlo
1catalogCatálogo de modelos o valores predeterminados de URL base
2applyConfigDefaultsValores predeterminados globales propios del proveedor durante la materialización de la configuración
3normalizeModelIdLimpieza de alias de id de modelo heredado/vista previa antes de la búsqueda
4normalizeTransportLimpieza de api / baseUrl de familia de proveedor antes del ensamblado genérico del modelo
5normalizeConfigNormalizar la configuración models.providers.<id>
6applyNativeStreamingUsageCompatReescrituras de compatibilidad de uso de transmisión nativa para proveedores de configuración
7resolveConfigApiKeyResolución de autenticación con marcador de entorno propia del proveedor
8resolveSyntheticAuthAutenticación sintética local/autohospedada o respaldada por configuración
9shouldDeferSyntheticProfileAuthRelegar los marcadores de posición sintéticos de perfiles almacenados por detrás de la autenticación de entorno/configuración
10resolveDynamicModelAceptar IDs de modelo de origen arbitrarios
11prepareDynamicModelObtención asíncrona de metadatos antes de resolver
12normalizeResolvedModelReescrituras de transporte antes del ejecutor
13contributeResolvedModelCompatIndicadores de compatibilidad para modelos de proveedor detrás de otro transporte compatible
14normalizeToolSchemasLimpieza de esquemas de herramientas propia del proveedor antes del registro
15inspectToolSchemasDiagnósticos de esquemas de herramientas propios del proveedor
16resolveReasoningOutputModeContrato de salida de razonamiento etiquetada frente a nativa
17prepareExtraParamsParámetros de solicitud predeterminados
18createStreamFnTransporte StreamFn totalmente personalizado
19wrapStreamFnEnvoltorios personalizados de encabezados/cuerpo en la ruta de transmisión normal
20resolveTransportTurnStateEncabezados/metadatos nativos por turno
21resolveWebSocketSessionPolicyEncabezados nativos de sesión WS/periodo de enfriamiento
22formatApiKeyForma de token de tiempo de ejecución personalizada
23refreshOAuthActualización OAuth personalizada
24buildAuthDoctorHintGuía de reparación de autenticación
25matchesContextOverflowErrorDetección de desbordamiento propia del proveedor
26classifyFailoverReasonClasificación de límite de tasa/sobrecarga propia del proveedor
27isCacheTtlEligibleControl de TTL de caché de indicaciones
28buildMissingAuthMessageSugerencia personalizada para autenticación ausente
29augmentModelCatalogFilas sintéticas de compatibilidad futura
30resolveThinkingProfileConjunto de opciones /think específico del modelo
31isBinaryThinkingCompatibilidad de pensamiento binario activado/desactivado
32supportsXHighThinkingCompatibilidad de soporte de razonamiento xhigh
33resolveDefaultThinkingLevelCompatibilidad de política /think predeterminada
34isModernModelRefCoincidencia de modelos en vivo/de prueba de humo
35prepareRuntimeAuthIntercambio de tokens antes de la inferencia
36resolveUsageAuthAnálisis de credenciales de uso personalizadas
37fetchUsageSnapshotPunto de conexión de uso personalizado
38createEmbeddingProviderAdaptador de incrustaciones propio del proveedor para memoria/búsqueda
39buildReplayPolicyPolítica personalizada de reproducción/Compaction de transcripción
40sanitizeReplayHistoryReescrituras de reproducción específicas del proveedor después de la limpieza genérica
41validateReplayTurnsValidación estricta de turnos de reproducción antes del ejecutor integrado
42onModelSelectedDevolución de llamada posterior a la selección (p. ej., telemetría)
Notas de alternativa en tiempo de ejecución:
  • normalizeConfig comprueba primero el proveedor coincidente y luego otros plugins de proveedor con capacidad de puntos de extensión hasta que uno cambia realmente la configuración. Si ningún punto de extensión de proveedor reescribe una entrada de configuración compatible de la familia Google, el normalizador de configuración de Google incluido aún se aplica.
  • resolveConfigApiKey usa el punto de extensión del proveedor cuando está expuesto. La ruta incluida de amazon-bedrock también tiene aquí un resolutor integrado de marcadores de entorno de AWS, aunque la autenticación en tiempo de ejecución de Bedrock en sí aún usa la cadena predeterminada del AWS SDK.
  • resolveSystemPromptContribution permite que un proveedor inyecte guía de indicación del sistema consciente de la caché para una familia de modelos. Prefiérelo sobre before_prompt_build cuando el comportamiento pertenece a un proveedor/familia de modelos y debe preservar la división de caché estable/dinámica.
Para ver descripciones detalladas y ejemplos del mundo real, consulta Detalles internos: puntos de extensión de tiempo de ejecución de proveedores.
5

Agregar capacidades adicionales (opcional)

Paso 5: Agregar capacidades adicionales

Un plugin de proveedor puede registrar voz, transcripción en tiempo real, voz en tiempo real, comprensión de medios, generación de imágenes, generación de video, obtención web y búsqueda web junto con inferencia de texto. OpenClaw clasifica esto como un plugin de capacidad híbrida, el patrón recomendado para plugins de empresa (un plugin por proveedor). Consulta Internals: Capability Ownership.Registra cada capacidad dentro de register(api) junto con tu llamada existente a api.registerProvider(...). Elige solo las pestañas que necesitas:
import {
  assertOkOrThrowProviderError,
  postJsonRequest,
} from "openclaw/plugin-sdk/provider-http";

api.registerSpeechProvider({
  id: "acme-ai",
  label: "Acme Speech",
  isConfigured: ({ config }) => Boolean(config.messages?.tts),
  synthesize: async (req) => {
    const { response, release } = await postJsonRequest({
      url: "https://api.example.com/v1/speech",
      headers: new Headers({ "Content-Type": "application/json" }),
      body: { text: req.text },
      timeoutMs: req.timeoutMs,
      fetchFn: fetch,
      auditContext: "acme speech",
    });
    try {
      await assertOkOrThrowProviderError(response, "Acme Speech API error");
      return {
        audioBuffer: Buffer.from(await response.arrayBuffer()),
        outputFormat: "mp3",
        fileExtension: ".mp3",
        voiceCompatible: false,
      };
    } finally {
      await release();
    }
  },
});
Usa assertOkOrThrowProviderError(...) para errores HTTP del proveedor, de modo que los plugins compartan lecturas acotadas del cuerpo de error, análisis de errores JSON y sufijos de id. de solicitud.
6

Test

Paso 6: Probar

src/provider.test.ts
import { describe, it, expect } from "vitest";
// Export your provider config object from index.ts or a dedicated file
import { acmeProvider } from "./provider.js";

describe("acme-ai provider", () => {
  it("resolves dynamic models", () => {
    const model = acmeProvider.resolveDynamicModel!({
      modelId: "acme-beta-v3",
    } as any);
    expect(model.id).toBe("acme-beta-v3");
    expect(model.provider).toBe("acme-ai");
  });

  it("returns catalog when key is available", async () => {
    const result = await acmeProvider.catalog!.run({
      resolveProviderApiKey: () => ({ apiKey: "test-key" }),
    } as any);
    expect(result?.provider?.models).toHaveLength(2);
  });

  it("returns null catalog when no key", async () => {
    const result = await acmeProvider.catalog!.run({
      resolveProviderApiKey: () => ({ apiKey: undefined }),
    } as any);
    expect(result).toBeNull();
  });
});

Publicar en ClawHub

Los plugins de proveedor se publican de la misma forma que cualquier otro plugin de código externo:
clawhub package publish your-org/your-plugin --dry-run
clawhub package publish your-org/your-plugin
No uses aquí el alias de publicación heredado solo para skills; los paquetes de plugin deben usar clawhub package publish.

Estructura de archivos

<bundled-plugin-root>/acme-ai/
├── package.json              # openclaw.providers metadata
├── openclaw.plugin.json      # Manifest with provider auth metadata
├── index.ts                  # definePluginEntry + registerProvider
└── src/
    ├── provider.test.ts      # Tests
    └── usage.ts              # Usage endpoint (optional)
catalog.order controla cuándo se fusiona tu catálogo en relación con los proveedores integrados:
OrdenCuándoCaso de uso
simplePrimera pasadaProveedores sencillos con clave API
profileDespués de simpleProveedores condicionados a perfiles de autenticación
pairedDespués de profileSintetizar varias entradas relacionadas
lateÚltima pasadaAnular proveedores existentes (gana en colisiones)

Próximos pasos

Relacionado