{
  "openapi": "3.1.0",
  "info": {
    "title": "API de Carlos Jofré: perfil, servicios, contratación y prefactibilidad solar",
    "version": "1.2.0",
    "description": "API de Carlos Jofré (fundador y CEO de SolarPro Chile): perfil verificado con fuentes, catálogo de servicios y precios, preparación de contrataciones por UCP y ACP (la persona confirma en continue_url; los servicios se pagan según una propuesta escrita), la API de miembros de Radar Solar protegida con OAuth 2.1 y una API de pago por uso (x402 y MPP) con estimaciones de prefactibilidad fotovoltaica. Autenticación: https://carlosjofre.com/auth.md",
    "contact": {
      "url": "https://carlosjofre.com/contacto/"
    }
  },
  "servers": [
    {
      "url": "https://carlosjofre.com"
    }
  ],
  "x-service-info": {
    "categories": [
      "data"
    ],
    "docs": {
      "homepage": "https://carlosjofre.com/",
      "apiReference": "https://carlosjofre.com/openapi.json",
      "llms": "https://carlosjofre.com/llms.txt"
    }
  },
  "tags": [
    {
      "name": "UCP",
      "description": "Universal Commerce Protocol 2026-08-25; perfil en https://carlosjofre.com/.well-known/ucp"
    },
    {
      "name": "ACP",
      "description": "Agentic Commerce Protocol 2026-04-17; descubrimiento en https://carlosjofre.com/.well-known/acp.json"
    },
    {
      "name": "Pago por uso",
      "description": "x402 v2 y MPP"
    }
  ],
  "paths": {
    "/api/perfil": {
      "get": {
        "operationId": "obtenerPerfil",
        "summary": "Perfil verificado",
        "parameters": [
          {
            "name": "seccion",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "persona",
                "hitos",
                "fuentes",
                "publicaciones",
                "desambiguacion",
                "medios",
                "empresas"
              ]
            }
          },
          {
            "name": "q",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Filtra hitos, fuentes y publicaciones por texto."
          }
        ],
        "responses": {
          "200": {
            "description": "Perfil en JSON",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          }
        }
      }
    },
    "/api/servicios": {
      "get": {
        "operationId": "listarServicios",
        "summary": "Servicios y precios (o cotización de un servicio)",
        "parameters": [
          {
            "name": "categoria",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string",
              "enum": [
                "asesoria",
                "tecnico",
                "formacion",
                "suscripcion"
              ]
            }
          },
          {
            "name": "q",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Filtro por texto."
          },
          {
            "name": "id",
            "in": "query",
            "required": false,
            "schema": {
              "type": "string"
            },
            "description": "Id del servicio: devuelve su cotización."
          },
          {
            "name": "kwp",
            "in": "query",
            "required": false,
            "schema": {
              "type": "number"
            },
            "description": "Potencia en kWp, para elegir la variante de precio."
          }
        ],
        "responses": {
          "200": {
            "description": "Catálogo o cotización en JSON",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "404": {
            "description": "Servicio desconocido"
          }
        }
      }
    },
    "/api/miembros/radar": {
      "get": {
        "operationId": "radarMiembros",
        "summary": "Boletines y alertas de Radar Solar (plan Empresas)",
        "security": [
          {
            "oauth2": [
              "radar:read"
            ]
          },
          {
            "bearer": []
          }
        ],
        "responses": {
          "200": {
            "description": "Contenido del plan Empresas",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "401": {
            "description": "Sin token o token inválido (WWW-Authenticate apunta a la Protected Resource Metadata)"
          },
          "403": {
            "description": "El token no tiene radar:read (las suscripciones están en preventa y el scope todavía no se otorga)"
          }
        }
      }
    },
    "/api/yo": {
      "get": {
        "operationId": "verificarToken",
        "summary": "Claims del token de acceso presentado (para comprobar un token)",
        "security": [
          {
            "bearer": []
          }
        ],
        "responses": {
          "200": {
            "description": "Claims del token",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "sub": {
                      "type": "string"
                    },
                    "client_id": {
                      "type": "string"
                    },
                    "scope": {
                      "type": "string"
                    },
                    "contacto": {
                      "type": [
                        "object",
                        "null"
                      ]
                    },
                    "exp": {
                      "type": "integer"
                    },
                    "vence": {
                      "type": "string",
                      "format": "date-time"
                    },
                    "registration_id": {
                      "type": [
                        "string",
                        "null"
                      ]
                    },
                    "pre_claim": {
                      "type": "boolean"
                    }
                  }
                }
              }
            }
          },
          "401": {
            "description": "Sin token o token inválido (WWW-Authenticate con resource_metadata)"
          }
        }
      }
    },
    "/api/ucp/checkout-sessions": {
      "post": {
        "operationId": "ucpCrearCheckout",
        "summary": "Crea una solicitud de contratación (UCP)",
        "tags": [
          "UCP"
        ],
        "parameters": [
          {
            "name": "UCP-Agent",
            "in": "header",
            "required": false,
            "description": "Perfil de la plataforma en sintaxis de diccionario RFC 8941: profile=\"https://…/.well-known/ucp\". Se descarga (sin redirecciones, caché ≥ 60 s) para negociar la versión y capacidades; si falta, la respuesta lleva un aviso.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "Request-Id",
            "in": "header",
            "required": false,
            "description": "Identificador de la petición (se devuelve en la respuesta).",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": true,
            "description": "Obligatoria en POST y PUT. Se guarda 48 h con la huella del cuerpo: la misma clave con otro cuerpo responde 409.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "201": {
            "description": "Checkout en requires_escalation con continue_url (la persona confirma; nada se cobra), o error_response si ningún ítem existe",
            "content": {
              "application/json": {
                "schema": {
                  "oneOf": [
                    {
                      "$ref": "https://ucp.dev/2026-08-25/schemas/shopping/checkout.json"
                    },
                    {
                      "$ref": "https://ucp.dev/2026-08-25/schemas/common/types/error_response.json"
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Petición inválida o sin Idempotency-Key ({code, content})",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorUcp"
                }
              }
            }
          },
          "409": {
            "description": "Idempotency-Key reutilizada con otro cuerpo",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorUcp"
                }
              }
            }
          },
          "422": {
            "description": "version_unsupported o profile_malformed (perfil de la plataforma)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorUcp"
                }
              }
            }
          },
          "424": {
            "description": "profile_unreachable: no se pudo descargar el perfil de UCP-Agent",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorUcp"
                }
              }
            }
          },
          "429": {
            "description": "Demasiadas solicitudes nuevas desde esta IP (Retry-After)"
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PedidoUcp"
              }
            }
          }
        }
      }
    },
    "/api/ucp/checkout-sessions/{id}": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "description": "Id de la sesión (chk_ + 22 caracteres base64url).",
          "schema": {
            "type": "string",
            "pattern": "^chk_[A-Za-z0-9_-]{22}$"
          }
        }
      ],
      "get": {
        "operationId": "ucpObtenerCheckout",
        "summary": "Estado de una solicitud (UCP)",
        "tags": [
          "UCP"
        ],
        "parameters": [
          {
            "name": "UCP-Agent",
            "in": "header",
            "required": false,
            "description": "Perfil de la plataforma en sintaxis de diccionario RFC 8941: profile=\"https://…/.well-known/ucp\". Se descarga (sin redirecciones, caché ≥ 60 s) para negociar la versión y capacidades; si falta, la respuesta lleva un aviso.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "Request-Id",
            "in": "header",
            "required": false,
            "description": "Identificador de la petición (se devuelve en la respuesta).",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Checkout, o error_response not_found",
            "content": {
              "application/json": {
                "schema": {
                  "oneOf": [
                    {
                      "$ref": "https://ucp.dev/2026-08-25/schemas/shopping/checkout.json"
                    },
                    {
                      "$ref": "https://ucp.dev/2026-08-25/schemas/common/types/error_response.json"
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Petición inválida o sin Idempotency-Key ({code, content})",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorUcp"
                }
              }
            }
          },
          "409": {
            "description": "Idempotency-Key reutilizada con otro cuerpo",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorUcp"
                }
              }
            }
          },
          "422": {
            "description": "version_unsupported o profile_malformed (perfil de la plataforma)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorUcp"
                }
              }
            }
          },
          "424": {
            "description": "profile_unreachable: no se pudo descargar el perfil de UCP-Agent",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorUcp"
                }
              }
            }
          }
        }
      },
      "put": {
        "operationId": "ucpActualizarCheckout",
        "summary": "Reemplaza líneas, comprador y contexto (UCP, reemplazo completo)",
        "tags": [
          "UCP"
        ],
        "parameters": [
          {
            "name": "UCP-Agent",
            "in": "header",
            "required": false,
            "description": "Perfil de la plataforma en sintaxis de diccionario RFC 8941: profile=\"https://…/.well-known/ucp\". Se descarga (sin redirecciones, caché ≥ 60 s) para negociar la versión y capacidades; si falta, la respuesta lleva un aviso.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "Request-Id",
            "in": "header",
            "required": false,
            "description": "Identificador de la petición (se devuelve en la respuesta).",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": true,
            "description": "Obligatoria en POST y PUT. Se guarda 48 h con la huella del cuerpo: la misma clave con otro cuerpo responde 409.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Checkout actualizado",
            "content": {
              "application/json": {
                "schema": {
                  "oneOf": [
                    {
                      "$ref": "https://ucp.dev/2026-08-25/schemas/shopping/checkout.json"
                    },
                    {
                      "$ref": "https://ucp.dev/2026-08-25/schemas/common/types/error_response.json"
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Petición inválida o sin Idempotency-Key ({code, content})",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorUcp"
                }
              }
            }
          },
          "409": {
            "description": "Idempotency-Key reutilizada con otro cuerpo",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorUcp"
                }
              }
            }
          },
          "422": {
            "description": "version_unsupported o profile_malformed (perfil de la plataforma)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorUcp"
                }
              }
            }
          },
          "424": {
            "description": "profile_unreachable: no se pudo descargar el perfil de UCP-Agent",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorUcp"
                }
              }
            }
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PedidoUcp"
              }
            }
          }
        }
      }
    },
    "/api/ucp/checkout-sessions/{id}/complete": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "description": "Id de la sesión (chk_ + 22 caracteres base64url).",
          "schema": {
            "type": "string",
            "pattern": "^chk_[A-Za-z0-9_-]{22}$"
          }
        }
      ],
      "post": {
        "operationId": "ucpCompletarCheckout",
        "summary": "No completa ni cobra: devuelve el checkout en requires_escalation (UCP)",
        "tags": [
          "UCP"
        ],
        "parameters": [
          {
            "name": "UCP-Agent",
            "in": "header",
            "required": false,
            "description": "Perfil de la plataforma en sintaxis de diccionario RFC 8941: profile=\"https://…/.well-known/ucp\". Se descarga (sin redirecciones, caché ≥ 60 s) para negociar la versión y capacidades; si falta, la respuesta lleva un aviso.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "Request-Id",
            "in": "header",
            "required": false,
            "description": "Identificador de la petición (se devuelve en la respuesta).",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": true,
            "description": "Obligatoria en POST y PUT. Se guarda 48 h con la huella del cuerpo: la misma clave con otro cuerpo responde 409.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Checkout en requires_escalation con un mensaje que explica que el pago se acuerda en una propuesta escrita",
            "content": {
              "application/json": {
                "schema": {
                  "oneOf": [
                    {
                      "$ref": "https://ucp.dev/2026-08-25/schemas/shopping/checkout.json"
                    },
                    {
                      "$ref": "https://ucp.dev/2026-08-25/schemas/common/types/error_response.json"
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Petición inválida o sin Idempotency-Key ({code, content})",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorUcp"
                }
              }
            }
          },
          "409": {
            "description": "Idempotency-Key reutilizada con otro cuerpo",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorUcp"
                }
              }
            }
          },
          "422": {
            "description": "version_unsupported o profile_malformed (perfil de la plataforma)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorUcp"
                }
              }
            }
          },
          "424": {
            "description": "profile_unreachable: no se pudo descargar el perfil de UCP-Agent",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorUcp"
                }
              }
            }
          }
        }
      }
    },
    "/api/ucp/checkout-sessions/{id}/cancel": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "description": "Id de la sesión (chk_ + 22 caracteres base64url).",
          "schema": {
            "type": "string",
            "pattern": "^chk_[A-Za-z0-9_-]{22}$"
          }
        }
      ],
      "post": {
        "operationId": "ucpCancelarCheckout",
        "summary": "Cancela una solicitud (UCP)",
        "tags": [
          "UCP"
        ],
        "parameters": [
          {
            "name": "UCP-Agent",
            "in": "header",
            "required": false,
            "description": "Perfil de la plataforma en sintaxis de diccionario RFC 8941: profile=\"https://…/.well-known/ucp\". Se descarga (sin redirecciones, caché ≥ 60 s) para negociar la versión y capacidades; si falta, la respuesta lleva un aviso.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "Request-Id",
            "in": "header",
            "required": false,
            "description": "Identificador de la petición (se devuelve en la respuesta).",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": true,
            "description": "Obligatoria en POST y PUT. Se guarda 48 h con la huella del cuerpo: la misma clave con otro cuerpo responde 409.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Checkout en canceled (o con error not_cancelable si ya lo estaba)",
            "content": {
              "application/json": {
                "schema": {
                  "oneOf": [
                    {
                      "$ref": "https://ucp.dev/2026-08-25/schemas/shopping/checkout.json"
                    },
                    {
                      "$ref": "https://ucp.dev/2026-08-25/schemas/common/types/error_response.json"
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "description": "Petición inválida o sin Idempotency-Key ({code, content})",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorUcp"
                }
              }
            }
          },
          "409": {
            "description": "Idempotency-Key reutilizada con otro cuerpo",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorUcp"
                }
              }
            }
          },
          "422": {
            "description": "version_unsupported o profile_malformed (perfil de la plataforma)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorUcp"
                }
              }
            }
          },
          "424": {
            "description": "profile_unreachable: no se pudo descargar el perfil de UCP-Agent",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorUcp"
                }
              }
            }
          }
        }
      }
    },
    "/api/acp/checkout_sessions": {
      "post": {
        "operationId": "acpCrearSesion",
        "summary": "Crea una sesión de checkout (ACP)",
        "tags": [
          "ACP"
        ],
        "security": [
          {
            "oauth2": [
              "cotizaciones:write"
            ]
          },
          {
            "bearer": []
          }
        ],
        "parameters": [
          {
            "name": "API-Version",
            "in": "header",
            "required": true,
            "description": "Obligatoria: 2026-04-17. Otra versión o ninguna responde 400 unsupported_api_version.",
            "schema": {
              "type": "string",
              "enum": [
                "2026-04-17"
              ]
            }
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": true,
            "description": "Obligatoria en todo POST (máx. 255 caracteres). La misma clave con otro cuerpo responde 422 idempotency_conflict; la repetición idéntica devuelve la respuesta original con Idempotent-Replayed: true.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "Request-Id",
            "in": "header",
            "required": false,
            "description": "Se devuelve en la respuesta.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "201": {
            "description": "Sesión en not_ready_for_payment con continue_url: la persona confirma y el pago se acuerda en una propuesta escrita",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SesionAcp"
                }
              }
            }
          },
          "400": {
            "description": "Petición inválida, sin Idempotency-Key o con otra API-Version",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorAcp"
                }
              }
            }
          },
          "401": {
            "description": "Sin token o token inválido (WWW-Authenticate: Bearer resource_metadata=\"…\")"
          },
          "403": {
            "description": "El token no tiene cotizaciones:write"
          },
          "404": {
            "description": "La sesión no existe, venció o es de otro cliente",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorAcp"
                }
              }
            }
          },
          "422": {
            "description": "idempotency_conflict",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorAcp"
                }
              }
            }
          },
          "429": {
            "description": "Demasiadas solicitudes nuevas desde esta IP"
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PedidoAcp"
              }
            }
          }
        }
      }
    },
    "/api/acp/checkout_sessions/{id}": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "description": "Id de la sesión (chk_ + 22 caracteres base64url).",
          "schema": {
            "type": "string",
            "pattern": "^chk_[A-Za-z0-9_-]{22}$"
          }
        }
      ],
      "get": {
        "operationId": "acpObtenerSesion",
        "summary": "Estado de una sesión (ACP)",
        "tags": [
          "ACP"
        ],
        "security": [
          {
            "oauth2": [
              "cotizaciones:write"
            ]
          },
          {
            "bearer": []
          }
        ],
        "parameters": [
          {
            "name": "API-Version",
            "in": "header",
            "required": true,
            "description": "Obligatoria: 2026-04-17. Otra versión o ninguna responde 400 unsupported_api_version.",
            "schema": {
              "type": "string",
              "enum": [
                "2026-04-17"
              ]
            }
          },
          {
            "name": "Request-Id",
            "in": "header",
            "required": false,
            "description": "Se devuelve en la respuesta.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Sesión",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SesionAcp"
                }
              }
            }
          },
          "400": {
            "description": "Petición inválida, sin Idempotency-Key o con otra API-Version",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorAcp"
                }
              }
            }
          },
          "401": {
            "description": "Sin token o token inválido (WWW-Authenticate: Bearer resource_metadata=\"…\")"
          },
          "403": {
            "description": "El token no tiene cotizaciones:write"
          },
          "404": {
            "description": "La sesión no existe, venció o es de otro cliente",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorAcp"
                }
              }
            }
          },
          "422": {
            "description": "idempotency_conflict",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorAcp"
                }
              }
            }
          }
        }
      },
      "post": {
        "operationId": "acpActualizarSesion",
        "summary": "Actualiza ítems, comprador o notas (ACP)",
        "tags": [
          "ACP"
        ],
        "security": [
          {
            "oauth2": [
              "cotizaciones:write"
            ]
          },
          {
            "bearer": []
          }
        ],
        "parameters": [
          {
            "name": "API-Version",
            "in": "header",
            "required": true,
            "description": "Obligatoria: 2026-04-17. Otra versión o ninguna responde 400 unsupported_api_version.",
            "schema": {
              "type": "string",
              "enum": [
                "2026-04-17"
              ]
            }
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": true,
            "description": "Obligatoria en todo POST (máx. 255 caracteres). La misma clave con otro cuerpo responde 422 idempotency_conflict; la repetición idéntica devuelve la respuesta original con Idempotent-Replayed: true.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "Request-Id",
            "in": "header",
            "required": false,
            "description": "Se devuelve en la respuesta.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Sesión actualizada",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SesionAcp"
                }
              }
            }
          },
          "400": {
            "description": "Petición inválida, sin Idempotency-Key o con otra API-Version",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorAcp"
                }
              }
            }
          },
          "401": {
            "description": "Sin token o token inválido (WWW-Authenticate: Bearer resource_metadata=\"…\")"
          },
          "403": {
            "description": "El token no tiene cotizaciones:write"
          },
          "404": {
            "description": "La sesión no existe, venció o es de otro cliente",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorAcp"
                }
              }
            }
          },
          "422": {
            "description": "idempotency_conflict",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorAcp"
                }
              }
            }
          },
          "409": {
            "description": "La sesión está cancelada",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorAcp"
                }
              }
            }
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/PedidoAcp"
              }
            }
          }
        }
      }
    },
    "/api/acp/checkout_sessions/{id}/complete": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "description": "Id de la sesión (chk_ + 22 caracteres base64url).",
          "schema": {
            "type": "string",
            "pattern": "^chk_[A-Za-z0-9_-]{22}$"
          }
        }
      ],
      "post": {
        "operationId": "acpCompletarSesion",
        "summary": "No completa ni cobra: responde 409 requires_escalation con continue_url (ACP)",
        "tags": [
          "ACP"
        ],
        "security": [
          {
            "oauth2": [
              "cotizaciones:write"
            ]
          },
          {
            "bearer": []
          }
        ],
        "parameters": [
          {
            "name": "API-Version",
            "in": "header",
            "required": true,
            "description": "Obligatoria: 2026-04-17. Otra versión o ninguna responde 400 unsupported_api_version.",
            "schema": {
              "type": "string",
              "enum": [
                "2026-04-17"
              ]
            }
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": true,
            "description": "Obligatoria en todo POST (máx. 255 caracteres). La misma clave con otro cuerpo responde 422 idempotency_conflict; la repetición idéntica devuelve la respuesta original con Idempotent-Replayed: true.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "Request-Id",
            "in": "header",
            "required": false,
            "description": "Se devuelve en la respuesta.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "409": {
            "description": "Error requires_escalation: los servicios no se pagan en línea (no hay pago delegado)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorAcp"
                }
              }
            }
          },
          "400": {
            "description": "Petición inválida, sin Idempotency-Key o con otra API-Version",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorAcp"
                }
              }
            }
          },
          "401": {
            "description": "Sin token o token inválido (WWW-Authenticate: Bearer resource_metadata=\"…\")"
          },
          "403": {
            "description": "El token no tiene cotizaciones:write"
          },
          "404": {
            "description": "La sesión no existe, venció o es de otro cliente",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorAcp"
                }
              }
            }
          },
          "422": {
            "description": "idempotency_conflict",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorAcp"
                }
              }
            }
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object"
              }
            }
          }
        }
      }
    },
    "/api/acp/checkout_sessions/{id}/cancel": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "description": "Id de la sesión (chk_ + 22 caracteres base64url).",
          "schema": {
            "type": "string",
            "pattern": "^chk_[A-Za-z0-9_-]{22}$"
          }
        }
      ],
      "post": {
        "operationId": "acpCancelarSesion",
        "summary": "Cancela una sesión (ACP)",
        "tags": [
          "ACP"
        ],
        "security": [
          {
            "oauth2": [
              "cotizaciones:write"
            ]
          },
          {
            "bearer": []
          }
        ],
        "parameters": [
          {
            "name": "API-Version",
            "in": "header",
            "required": true,
            "description": "Obligatoria: 2026-04-17. Otra versión o ninguna responde 400 unsupported_api_version.",
            "schema": {
              "type": "string",
              "enum": [
                "2026-04-17"
              ]
            }
          },
          {
            "name": "Idempotency-Key",
            "in": "header",
            "required": true,
            "description": "Obligatoria en todo POST (máx. 255 caracteres). La misma clave con otro cuerpo responde 422 idempotency_conflict; la repetición idéntica devuelve la respuesta original con Idempotent-Replayed: true.",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "Request-Id",
            "in": "header",
            "required": false,
            "description": "Se devuelve en la respuesta.",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Sesión en canceled",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SesionAcp"
                }
              }
            }
          },
          "400": {
            "description": "Petición inválida, sin Idempotency-Key o con otra API-Version",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorAcp"
                }
              }
            }
          },
          "401": {
            "description": "Sin token o token inválido (WWW-Authenticate: Bearer resource_metadata=\"…\")"
          },
          "403": {
            "description": "El token no tiene cotizaciones:write"
          },
          "404": {
            "description": "La sesión no existe, venció o es de otro cliente",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorAcp"
                }
              }
            }
          },
          "422": {
            "description": "idempotency_conflict",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorAcp"
                }
              }
            }
          },
          "405": {
            "description": "La sesión ya estaba cancelada",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorAcp"
                }
              }
            }
          }
        },
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object"
              }
            }
          }
        }
      }
    },
    "/api/comercio/sesiones/{id}": {
      "parameters": [
        {
          "name": "id",
          "in": "path",
          "required": true,
          "description": "Id de la sesión (chk_ + 22 caracteres base64url).",
          "schema": {
            "type": "string",
            "pattern": "^chk_[A-Za-z0-9_-]{22}$"
          }
        }
      ],
      "get": {
        "operationId": "solicitudWeb",
        "summary": "Solicitud creada por un agente (UCP, ACP o MCP), para precargar https://carlosjofre.com/checkout/?sesion={id}",
        "responses": {
          "200": {
            "description": "Solicitud",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/SolicitudWeb"
                }
              }
            }
          },
          "404": {
            "description": "No existe o venció (7 días)"
          },
          "410": {
            "description": "Cancelada"
          }
        }
      }
    },
    "/api/v1": {
      "get": {
        "operationId": "prefactibilidadSolar",
        "summary": "Estimación de prefactibilidad fotovoltaica (pago por uso)",
        "description": "Estimación de prefactibilidad fotovoltaica con datos PVGIS, de pago por uso (1 USDC con x402 o MPP). El cobro todavía no está habilitado: responde 503 pagos_no_configurados. Ejemplo gratuito en /api/v1/ejemplo.",
        "tags": [
          "Pago por uso"
        ],
        "parameters": [
          {
            "name": "lat",
            "in": "query",
            "required": true,
            "schema": {
              "type": "number",
              "minimum": -90,
              "maximum": 90
            },
            "description": "Latitud en grados decimales."
          },
          {
            "name": "lon",
            "in": "query",
            "required": true,
            "schema": {
              "type": "number",
              "minimum": -180,
              "maximum": 180
            },
            "description": "Longitud en grados decimales."
          },
          {
            "name": "consumo_kwh_mes",
            "in": "query",
            "required": true,
            "schema": {
              "type": "number",
              "exclusiveMinimum": 0,
              "maximum": 5000000
            },
            "description": "Consumo eléctrico mensual en kWh."
          },
          {
            "name": "tarifa_clp_kwh",
            "in": "query",
            "required": false,
            "schema": {
              "type": "number",
              "minimum": 1,
              "maximum": 2000
            },
            "description": "Tarifa en pesos por kWh, para estimar el ahorro."
          },
          {
            "name": "autoconsumo",
            "in": "query",
            "required": false,
            "schema": {
              "type": "number",
              "minimum": 0,
              "maximum": 1,
              "default": 0.5
            },
            "description": "Fracción de la generación consumida en el lugar."
          },
          {
            "name": "valor_inyeccion",
            "in": "query",
            "required": false,
            "schema": {
              "type": "number",
              "minimum": 0,
              "maximum": 1,
              "default": 0.5
            },
            "description": "Valor de la energía inyectada como fracción de la tarifa."
          },
          {
            "name": "cobertura",
            "in": "query",
            "required": false,
            "schema": {
              "type": "number",
              "minimum": 0.1,
              "maximum": 2,
              "default": 1
            },
            "description": "Fracción del consumo anual a cubrir."
          },
          {
            "name": "perdidas",
            "in": "query",
            "required": false,
            "schema": {
              "type": "number",
              "minimum": 0,
              "maximum": 40,
              "default": 14
            },
            "description": "Pérdidas del sistema en %."
          },
          {
            "name": "costo_clp_wp",
            "in": "query",
            "required": false,
            "schema": {
              "type": "number",
              "minimum": 50,
              "maximum": 5000
            },
            "description": "Costo de instalación en pesos por Wp, para inversión y retorno."
          }
        ],
        "responses": {
          "200": {
            "description": "Estimación (con PAYMENT-RESPONSE si se pagó con x402, o Payment-Receipt si fue con MPP)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Prefactibilidad"
                }
              }
            }
          },
          "400": {
            "description": "Parámetros inválidos: no se liquida ningún pago",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "502": {
            "description": "PVGIS o el facilitador no respondieron: no se liquida ningún pago",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "503": {
            "description": "pagos_no_configurados: el cobro todavía no está habilitado (el cuerpo describe el producto)",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          }
        }
      },
      "post": {
        "operationId": "prefactibilidadSolarPost",
        "summary": "Estimación de prefactibilidad fotovoltaica (pago por uso, cuerpo JSON)",
        "description": "Estimación de prefactibilidad fotovoltaica con datos PVGIS, de pago por uso (1 USDC con x402 o MPP). El cobro todavía no está habilitado: responde 503 pagos_no_configurados. Ejemplo gratuito en /api/v1/ejemplo.",
        "tags": [
          "Pago por uso"
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "lat",
                  "lon",
                  "consumo_kwh_mes"
                ],
                "properties": {
                  "lat": {
                    "type": "number",
                    "minimum": -90,
                    "maximum": 90,
                    "description": "Latitud en grados decimales."
                  },
                  "lon": {
                    "type": "number",
                    "minimum": -180,
                    "maximum": 180,
                    "description": "Longitud en grados decimales."
                  },
                  "consumo_kwh_mes": {
                    "type": "number",
                    "exclusiveMinimum": 0,
                    "maximum": 5000000,
                    "description": "Consumo eléctrico mensual en kWh."
                  },
                  "tarifa_clp_kwh": {
                    "type": "number",
                    "minimum": 1,
                    "maximum": 2000,
                    "description": "Tarifa en pesos por kWh, para estimar el ahorro."
                  },
                  "autoconsumo": {
                    "type": "number",
                    "minimum": 0,
                    "maximum": 1,
                    "default": 0.5,
                    "description": "Fracción de la generación consumida en el lugar."
                  },
                  "valor_inyeccion": {
                    "type": "number",
                    "minimum": 0,
                    "maximum": 1,
                    "default": 0.5,
                    "description": "Valor de la energía inyectada como fracción de la tarifa."
                  },
                  "cobertura": {
                    "type": "number",
                    "minimum": 0.1,
                    "maximum": 2,
                    "default": 1,
                    "description": "Fracción del consumo anual a cubrir."
                  },
                  "perdidas": {
                    "type": "number",
                    "minimum": 0,
                    "maximum": 40,
                    "default": 14,
                    "description": "Pérdidas del sistema en %."
                  },
                  "costo_clp_wp": {
                    "type": "number",
                    "minimum": 50,
                    "maximum": 5000,
                    "description": "Costo de instalación en pesos por Wp, para inversión y retorno."
                  }
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Estimación (con PAYMENT-RESPONSE si se pagó con x402, o Payment-Receipt si fue con MPP)",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Prefactibilidad"
                }
              }
            }
          },
          "400": {
            "description": "Parámetros inválidos: no se liquida ningún pago",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "502": {
            "description": "PVGIS o el facilitador no respondieron: no se liquida ningún pago",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          },
          "503": {
            "description": "pagos_no_configurados: el cobro todavía no está habilitado (el cuerpo describe el producto)",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/ejemplo": {
      "get": {
        "operationId": "prefactibilidadEjemplo",
        "summary": "Informe de muestra gratuito (estático) de la API de prefactibilidad",
        "tags": [
          "Pago por uso"
        ],
        "responses": {
          "200": {
            "description": "Ejemplo con parámetros, informe y descripción del producto",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object"
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "oauth2": {
        "type": "oauth2",
        "description": "Servidor de autorización propio (issuer https://carlosjofre.com). Agentes sin usuario: registro en POST https://carlosjofre.com/agent/identity y grant jwt-bearer en /oauth/token (ver https://carlosjofre.com/auth.md). Metadatos: https://carlosjofre.com/.well-known/oauth-authorization-server.",
        "flows": {
          "authorizationCode": {
            "authorizationUrl": "https://carlosjofre.com/oauth/authorize",
            "tokenUrl": "https://carlosjofre.com/oauth/token",
            "refreshUrl": "https://carlosjofre.com/oauth/token",
            "scopes": {
              "perfil:read": "Leer el perfil público de Carlos: trayectoria, fuentes y publicaciones.",
              "servicios:read": "Leer el catálogo de servicios y sus precios.",
              "cotizaciones:write": "Crear solicitudes de cotización y de checkout de servicios.",
              "pedidos:write": "Enviar pedidos a Carlos a tu nombre, con los datos de contacto que declares.",
              "radar:read": "Leer boletines y alertas de Radar Solar (suscriptores; todavía no se otorga)."
            }
          },
          "clientCredentials": {
            "tokenUrl": "https://carlosjofre.com/oauth/token",
            "scopes": {
              "perfil:read": "Leer el perfil público de Carlos: trayectoria, fuentes y publicaciones.",
              "servicios:read": "Leer el catálogo de servicios y sus precios.",
              "cotizaciones:write": "Crear solicitudes de cotización y de checkout de servicios."
            }
          }
        }
      },
      "bearer": {
        "type": "http",
        "scheme": "bearer",
        "bearerFormat": "JWT",
        "description": "Token de acceso (RFC 9068, at+jwt) emitido por https://carlosjofre.com/oauth/token; audiencia https://carlosjofre.com/api/."
      }
    },
    "schemas": {
      "ErrorUcp": {
        "type": "object",
        "required": [
          "code",
          "content"
        ],
        "properties": {
          "code": {
            "type": "string"
          },
          "content": {
            "type": "string"
          },
          "continue_url": {
            "type": "string",
            "format": "uri"
          }
        }
      },
      "PedidoUcp": {
        "type": "object",
        "required": [
          "line_items"
        ],
        "description": "Cuerpo de creación o reemplazo. item.id: \"servicio\" o \"servicio:modalidad\" (índice en /api/servicios).",
        "properties": {
          "line_items": {
            "type": "array",
            "minItems": 1,
            "maxItems": 10,
            "items": {
              "type": "object",
              "required": [
                "item"
              ],
              "properties": {
                "item": {
                  "type": "object",
                  "required": [
                    "id"
                  ],
                  "properties": {
                    "id": {
                      "type": "string",
                      "examples": [
                        "due-diligence",
                        "auditoria-de-plantas:2"
                      ]
                    }
                  }
                },
                "quantity": {
                  "type": "integer",
                  "minimum": 1,
                  "maximum": 100,
                  "default": 1
                }
              }
            }
          },
          "buyer": {
            "type": "object",
            "properties": {
              "first_name": {
                "type": "string"
              },
              "last_name": {
                "type": "string"
              },
              "email": {
                "type": "string"
              },
              "phone_number": {
                "type": "string"
              },
              "company": {
                "type": "string"
              }
            }
          },
          "context": {
            "type": "object",
            "properties": {
              "intent": {
                "type": "string",
                "description": "Contexto del encargo para Carlos."
              },
              "kwp": {
                "type": "number",
                "description": "Potencia del proyecto en kWp."
              },
              "language": {
                "type": "string",
                "examples": [
                  "es",
                  "en"
                ]
              }
            }
          }
        }
      },
      "ErrorAcp": {
        "type": "object",
        "required": [
          "type",
          "code",
          "message"
        ],
        "properties": {
          "type": {
            "type": "string",
            "enum": [
              "invalid_request",
              "processing_error",
              "service_unavailable"
            ]
          },
          "code": {
            "type": "string"
          },
          "message": {
            "type": "string"
          },
          "param": {
            "type": "string"
          },
          "supported_versions": {
            "type": "array",
            "items": {
              "type": "string"
            }
          }
        }
      },
      "PedidoAcp": {
        "type": "object",
        "description": "line_items[].id: \"servicio\" o \"servicio:modalidad\". metadata.kwp: potencia del proyecto. order_notes: contexto del encargo.",
        "properties": {
          "line_items": {
            "type": "array",
            "minItems": 1,
            "maxItems": 10,
            "items": {
              "type": "object",
              "required": [
                "id"
              ],
              "properties": {
                "id": {
                  "type": "string"
                },
                "quantity": {
                  "type": "integer",
                  "minimum": 1,
                  "maximum": 100,
                  "default": 1
                }
              }
            }
          },
          "currency": {
            "type": "string",
            "enum": [
              "clp"
            ]
          },
          "capabilities": {
            "type": "object"
          },
          "buyer": {
            "type": "object",
            "properties": {
              "first_name": {
                "type": "string"
              },
              "last_name": {
                "type": "string"
              },
              "email": {
                "type": "string",
                "format": "email"
              },
              "phone_number": {
                "type": "string"
              },
              "company": {
                "type": "object",
                "properties": {
                  "name": {
                    "type": "string"
                  }
                }
              }
            }
          },
          "metadata": {
            "type": "object",
            "properties": {
              "kwp": {
                "type": "number"
              }
            }
          },
          "order_notes": {
            "type": "string",
            "maxLength": 5000
          },
          "locale": {
            "type": "string",
            "examples": [
              "es-CL",
              "en-US"
            ]
          }
        }
      },
      "SesionAcp": {
        "type": "object",
        "description": "CheckoutSession de ACP 2026-04-17: status not_ready_for_payment (o canceled), currency clp, capabilities.payment.handlers vacío (no hay pago delegado), continue_url para que la persona confirme.",
        "required": [
          "id",
          "status",
          "currency",
          "line_items",
          "totals",
          "fulfillment_options",
          "messages",
          "links",
          "capabilities"
        ],
        "properties": {
          "id": {
            "type": "string"
          },
          "status": {
            "type": "string",
            "enum": [
              "not_ready_for_payment",
              "canceled"
            ]
          },
          "currency": {
            "type": "string",
            "const": "clp"
          },
          "line_items": {
            "type": "array",
            "items": {
              "type": "object"
            }
          },
          "totals": {
            "type": "array",
            "items": {
              "type": "object"
            }
          },
          "fulfillment_options": {
            "type": "array",
            "maxItems": 0
          },
          "messages": {
            "type": "array",
            "items": {
              "type": "object"
            }
          },
          "links": {
            "type": "array",
            "items": {
              "type": "object"
            }
          },
          "capabilities": {
            "type": "object"
          },
          "continue_url": {
            "type": "string",
            "format": "uri"
          }
        }
      },
      "SolicitudWeb": {
        "type": "object",
        "required": [
          "solicitud"
        ],
        "properties": {
          "solicitud": {
            "type": "object",
            "required": [
              "servicio",
              "modalidad"
            ],
            "properties": {
              "servicio": {
                "type": "string"
              },
              "modalidad": {
                "type": "integer",
                "minimum": 0
              },
              "nombre": {
                "type": "string"
              },
              "empresa": {
                "type": "string"
              },
              "correo": {
                "type": "string"
              },
              "kwp": {
                "type": "string"
              },
              "mensaje": {
                "type": "string"
              }
            }
          },
          "estado": {
            "type": "string"
          },
          "vence": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "Prefactibilidad": {
        "type": "object",
        "properties": {
          "ubicacion": {
            "type": "object"
          },
          "sistema": {
            "type": "object"
          },
          "generacion": {
            "type": "object"
          },
          "supuestos": {
            "type": "object"
          },
          "fuentes": {
            "type": "array",
            "items": {
              "type": "object"
            }
          },
          "descargo": {
            "type": "string"
          },
          "economia": {
            "type": "object"
          },
          "pago": {
            "type": "object",
            "description": "Protocolo usado y referencia de la transacción."
          }
        }
      }
    }
  }
}
