{
  "openapi": "3.0.3",
  "info": {
    "title": "Conversor IAE/CNAE API",
    "description": "API REST para convertir y consultar códigos fiscales españoles IAE ↔ CNAE 2025, correspondencias CNAE 2009, régimen de módulos (estimación objetiva), obligaciones fiscales (modelos AEAT) y calendario de plazos. La primera clave de cada email empieza con 14 días de prueba Profesional (2.000 peticiones/día, sin tarjeta); después pasa al plan Gratis (250 peticiones/día).\n\nEN: REST API to convert and look up Spanish tax activity codes (IAE ↔ CNAE 2025), CNAE 2009 correspondences, módulos regime, fiscal obligations (AEAT modelos) and the fiscal deadline calendar. Each email's first key starts with a 14-day Profesional trial (2,000 requests/day, no card), then moves to the Free plan (250 requests/day).\n\n## Autenticación\nLos endpoints con clave requieren la cabecera `X-API-Key: cvr_…`. Obtén una clave gratuita (sin tarjeta) en `POST /signup` o en https://www.conversoriaecnae.es/precios.\n\n## Planes y cuotas\n- **Free**: 250 peticiones/día (sin tarjeta)\n- **Profesional**: 29€/mes — 2.000 peticiones/día\n- **Empresa**: 79€/mes — 10.000 peticiones/día\n- **Compliance (Enterprise)**: 199€/mes — 30.000 peticiones/día (900.000/mes)\n\nLímite de ráfaga según plan: 10 (Free/Desarrollador), 30 (Profesional) y 100 (Empresa/Compliance) peticiones/minuto por clave. La cuota diaria se reinicia a las 00:00 (Europe/Madrid). Las respuestas 429 incluyen la cabecera `Retry-After` (segundos).\n\nLos endpoints `cnae-2009`, `bulk` y `tarifa-at-ep` requieren plan Profesional o superior (403 en plan Free). Los endpoints `kyb/enrich` y `onboarding/activity` requieren plan Empresa o superior (403 en Free y Profesional). Los endpoints `modulos` y `obligaciones` están disponibles en el plan Free (requieren clave, pero cualquier plan sirve). Los endpoints `calendario`, `dias-habiles`, `utils/nif-info`, `signup` y `subscribe` son públicos.\n\nNota: el plan gratuito (valor interno `free`, mostrado como \"Free\" en las respuestas de la API) se comercializa públicamente como \"Desarrollador\" en las páginas de precios.",
    "version": "1.0.0",
    "contact": {
      "name": "Conversor IAE CNAE",
      "email": "brian@conversoriaecnae.es",
      "url": "https://www.conversoriaecnae.es/precios"
    },
    "license": {
      "name": "CC-BY-4.0",
      "url": "https://creativecommons.org/licenses/by/4.0/"
    },
    "termsOfService": "https://www.conversoriaecnae.es/terminos-condiciones"
  },
  "servers": [
    {
      "url": "https://www.conversoriaecnae.es/api/v1",
      "description": "Producción"
    }
  ],
  "tags": [
    { "name": "Códigos", "description": "Detalle de códigos IAE, CNAE 2025 y CNAE 2009, y lookup masivo." },
    { "name": "Búsqueda", "description": "Búsqueda híbrida (léxica + semántica) sobre los catálogos IAE y CNAE." },
    { "name": "Fiscal", "description": "Régimen de módulos, obligaciones (modelos AEAT) y calendario de plazos." },
    { "name": "Cuenta", "description": "Alta de clave, suscripción a planes de pago y consulta de uso." },
    { "name": "B2B", "description": "Endpoints de enriquecimiento para bancos (KYB) y SaaS de facturación / onboarding. Requieren plan Empresa o superior." }
  ],
  "security": [
    { "ApiKeyAuth": [] }
  ],
  "paths": {
    "/iae/{code}": {
      "get": {
        "operationId": "getIaeByCode",
        "summary": "Detalle de un código IAE",
        "description": "Devuelve el detalle de un código IAE (epígrafe). El código admite forma con o sin puntos (`501.3` o `5013`). Si un código numérico corresponde a varias filas (p. ej. aparece en secciones Empresarial y Profesional), responde 409 con `candidates[]`; usa `?slug=` para desambiguar. En plan Free, `associated_cnae` es un objeto bloqueado (`locked: true`, `count`, `upgrade_url`) y `related_iae` se omite; el array completo requiere plan Profesional, prueba Profesional activa o superior. En esos planes la respuesta incluye además `cnae_2025_mapping` (`1:1`, `1:n` o `sin_correspondencia`); en plan Free el campo no aparece.",
        "tags": ["Códigos"],
        "security": [{ "ApiKeyAuth": [] }],
        "parameters": [
          { "$ref": "#/components/parameters/IaeCodePath" },
          { "$ref": "#/components/parameters/SlugQuery" }
        ],
        "responses": {
          "200": {
            "description": "Detalle del código IAE",
            "headers": {
              "X-RateLimit-Limit": { "$ref": "#/components/headers/XRateLimitLimit" },
              "X-RateLimit-Remaining": { "$ref": "#/components/headers/XRateLimitRemaining" },
              "X-RateLimit-Reset": { "$ref": "#/components/headers/XRateLimitReset" },
              "X-Conversor-Plan": { "$ref": "#/components/headers/XConversorPlan" }
            },
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/IaeDetail" }
              }
            }
          },
          "400": { "$ref": "#/components/responses/BadRequest" },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "404": { "$ref": "#/components/responses/NotFound" },
          "409": { "$ref": "#/components/responses/AmbiguousCode" },
          "429": { "$ref": "#/components/responses/RateLimited" },
          "500": { "$ref": "#/components/responses/ServerError" }
        }
      }
    },
    "/cnae/{code}": {
      "get": {
        "operationId": "getCnaeByCode",
        "summary": "Detalle de un código CNAE 2025",
        "description": "Devuelve el detalle de un código CNAE 2025 con su equivalencia europea NACE Rev. 2.1, sus epígrafes IAE asociados y equivalencias CNAE 2009. El código admite forma con o sin puntos (`68.20` o `6820`). El campo `nace` está incluido en todos los planes (también en el gratuito); en plan Free, `associated_iae` es un objeto bloqueado (`locked: true`, `count`, `upgrade_url`) y `related_cnae` y `cnae_2009` se omiten; el array completo y esos campos requieren plan Profesional, prueba Profesional activa o superior.",
        "tags": ["Códigos"],
        "security": [{ "ApiKeyAuth": [] }],
        "parameters": [
          { "$ref": "#/components/parameters/CnaeCodePath" }
        ],
        "responses": {
          "200": {
            "description": "Detalle del código CNAE 2025",
            "headers": {
              "X-RateLimit-Limit": { "$ref": "#/components/headers/XRateLimitLimit" },
              "X-RateLimit-Remaining": { "$ref": "#/components/headers/XRateLimitRemaining" },
              "X-RateLimit-Reset": { "$ref": "#/components/headers/XRateLimitReset" },
              "X-Conversor-Plan": { "$ref": "#/components/headers/XConversorPlan" }
            },
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/CnaeDetail" }
              }
            }
          },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "404": { "$ref": "#/components/responses/NotFound" },
          "429": { "$ref": "#/components/responses/RateLimited" },
          "500": { "$ref": "#/components/responses/ServerError" }
        }
      }
    },
    "/cnae-2009/{code}": {
      "get": {
        "operationId": "getCnae2009Correspondence",
        "summary": "Correspondencia CNAE 2009 → CNAE 2025",
        "description": "Devuelve las correspondencias de un código CNAE 2009 (obsoleto) hacia CNAE 2025. Requiere plan Profesional o superior. Incluido en la prueba Profesional de 14 días.",
        "tags": ["Códigos"],
        "security": [{ "ApiKeyAuth": [] }],
        "parameters": [
          { "$ref": "#/components/parameters/Cnae2009CodePath" }
        ],
        "responses": {
          "200": {
            "description": "Correspondencias CNAE 2009 → CNAE 2025",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/Cnae2009Correspondence" }
              }
            }
          },
          "400": { "$ref": "#/components/responses/BadRequest" },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "403": { "$ref": "#/components/responses/PaidRequired" },
          "404": { "$ref": "#/components/responses/NotFound" },
          "429": { "$ref": "#/components/responses/RateLimited" },
          "500": { "$ref": "#/components/responses/ServerError" }
        }
      }
    },
    "/search": {
      "get": {
        "operationId": "searchCodes",
        "summary": "Buscar códigos IAE/CNAE",
        "description": "Búsqueda híbrida sobre los catálogos IAE y/o CNAE 2025. El parámetro `q` requiere al menos 2 caracteres.",
        "tags": ["Búsqueda"],
        "security": [{ "ApiKeyAuth": [] }],
        "parameters": [
          {
            "name": "q",
            "in": "query",
            "required": true,
            "description": "Texto de búsqueda (mínimo 2 caracteres).",
            "schema": { "type": "string", "minLength": 2, "example": "albañilería" }
          },
          {
            "name": "type",
            "in": "query",
            "required": false,
            "description": "Catálogo a consultar.",
            "schema": { "type": "string", "enum": ["iae", "cnae", "both"], "default": "both" }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Número máximo de resultados (1–50).",
            "schema": { "type": "integer", "minimum": 1, "maximum": 50, "default": 20 }
          }
        ],
        "responses": {
          "200": {
            "description": "Resultados de búsqueda",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/SearchResponse" }
              }
            }
          },
          "400": { "$ref": "#/components/responses/BadRequest" },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "429": { "$ref": "#/components/responses/RateLimited" },
          "500": { "$ref": "#/components/responses/ServerError" }
        }
      }
    },
    "/modulos/{code}": {
      "get": {
        "operationId": "getModulosByCode",
        "summary": "Régimen de módulos de un código IAE",
        "description": "Devuelve el régimen de módulos (estimación objetiva) para un código IAE: módulos IRPF/IVA, umbrales e inteligencia fiscal. Disponible en el plan Free (requiere una clave de API, cualquier plan sirve). Usa `?slug=` para desambiguar códigos que mapean a varias filas (409).",
        "tags": ["Fiscal"],
        "security": [{ "ApiKeyAuth": [] }],
        "parameters": [
          { "$ref": "#/components/parameters/IaeCodePath" },
          { "$ref": "#/components/parameters/SlugQuery" }
        ],
        "responses": {
          "200": {
            "description": "Régimen de módulos del código IAE",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/ModulosDetail" }
              }
            }
          },
          "400": { "$ref": "#/components/responses/BadRequest" },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "404": { "$ref": "#/components/responses/NotFound" },
          "409": { "$ref": "#/components/responses/AmbiguousCode" },
          "429": { "$ref": "#/components/responses/RateLimited" },
          "500": { "$ref": "#/components/responses/ServerError" }
        }
      }
    },
    "/tarifa-at-ep/{code}": {
      "get": {
        "operationId": "getTarifaAtEpByCode",
        "summary": "Tarifa AT/EP (cotización por cuenta ajena) de un código CNAE",
        "description": "Devuelve el tipo de cotización AT/EP (Accidentes de Trabajo / Enfermedades Profesionales, D.A. 61ª TRLGSS, Cuadro I) aplicable a un código CNAE 2025. Esta tarifa es la que paga la EMPRESA por sus TRABAJADORES POR CUENTA AJENA en la actividad — no es la cuota RETA del autónomo (que aplica el tipo fijo del 1,30%, ver `nota_reta`). La resolución usa un fallback de prefijo (código exacto → 3 dígitos → 2 dígitos), sin ambigüedad de sección/slug. Requiere plan Profesional o superior. Incluido en la prueba Profesional de 14 días.",
        "tags": ["Fiscal"],
        "security": [{ "ApiKeyAuth": [] }],
        "parameters": [
          { "$ref": "#/components/parameters/CnaeCodePath" }
        ],
        "responses": {
          "200": {
            "description": "Tarifa AT/EP del código CNAE",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/TarifaAtEpDetail" }
              }
            }
          },
          "400": { "$ref": "#/components/responses/BadRequest" },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "403": { "$ref": "#/components/responses/PaidRequired" },
          "404": { "$ref": "#/components/responses/NotFound" },
          "429": { "$ref": "#/components/responses/RateLimited" },
          "500": { "$ref": "#/components/responses/ServerError" }
        }
      }
    },
    "/obligaciones/{code_type}/{code}": {
      "get": {
        "operationId": "getObligaciones",
        "summary": "Obligaciones fiscales de un código",
        "description": "Devuelve las obligaciones fiscales (modelos AEAT) aplicables a un código IAE o CNAE. Disponible en el plan Free (requiere una clave de API, cualquier plan sirve). Si el código no tiene modelos aplicables, el endpoint responde 200 con la lista `obligaciones` vacía y `total: 0` (no devuelve 404). `fecha_proximo_plazo` es la fecha nominal del calendario AEAT (sin prórroga de días inhábiles aplicada).",
        "tags": ["Fiscal"],
        "security": [{ "ApiKeyAuth": [] }],
        "parameters": [
          { "$ref": "#/components/parameters/CodeTypePath" },
          { "$ref": "#/components/parameters/GenericCodePath" }
        ],
        "responses": {
          "200": {
            "description": "Obligaciones fiscales aplicables",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/ObligacionesResponse" }
              }
            }
          },
          "400": { "$ref": "#/components/responses/BadRequest" },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "429": { "$ref": "#/components/responses/RateLimited" },
          "500": { "$ref": "#/components/responses/ServerError" }
        }
      }
    },
    "/bulk": {
      "post": {
        "operationId": "bulkLookup",
        "summary": "Lookup masivo de códigos",
        "description": "Consulta hasta 50 códigos IAE o CNAE en una sola petición. Requiere plan Profesional o superior. No incluido en la prueba de 14 días. Devuelve los resultados en el mismo orden que se solicitan, con `found: false` para los no encontrados.",
        "tags": ["Códigos"],
        "security": [{ "ApiKeyAuth": [] }],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": { "$ref": "#/components/schemas/BulkRequest" }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Resultados del lookup masivo",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/BulkResponse" }
              }
            }
          },
          "400": { "$ref": "#/components/responses/BadRequest" },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "403": { "$ref": "#/components/responses/PaidRequired" },
          "429": { "$ref": "#/components/responses/RateLimited" },
          "500": { "$ref": "#/components/responses/ServerError" }
        }
      }
    },
    "/calendario/{code_type}/{code}": {
      "get": {
        "operationId": "getCalendario",
        "summary": "Calendario fiscal (iCalendar)",
        "description": "Devuelve un archivo iCalendar (RFC 5545, `text/calendar`) con los plazos AEAT aplicables al código, para suscripción en cualquier app de calendario. Endpoint público (sin autenticación). El código admite sufijo `.ics`. Cacheado 1 hora.",
        "tags": ["Fiscal"],
        "security": [],
        "parameters": [
          { "$ref": "#/components/parameters/CodeTypePath" },
          { "$ref": "#/components/parameters/CalendarioCodePath" }
        ],
        "responses": {
          "200": {
            "description": "Archivo iCalendar con los plazos fiscales",
            "headers": {
              "Cache-Control": {
                "description": "Política de caché.",
                "schema": { "type": "string", "example": "public, max-age=3600, s-maxage=3600" }
              },
              "Content-Disposition": {
                "description": "Nombre de archivo sugerido.",
                "schema": { "type": "string", "example": "inline; filename=\"calendario-iae-5013.ics\"" }
              }
            },
            "content": {
              "text/calendar": {
                "schema": { "type": "string", "format": "binary" },
                "example": "BEGIN:VCALENDAR\r\nVERSION:2.0\r\nPRODID:-//Conversor IAE CNAE//Calendario Fiscal//ES\r\nEND:VCALENDAR\r\n"
              }
            }
          },
          "400": {
            "description": "Tipo de código no válido (debe ser `iae` o `cnae`).",
            "content": {
              "text/plain": {
                "schema": { "type": "string", "example": "Invalid code_type" }
              }
            }
          },
          "404": {
            "description": "No hay modelos/plazos aplicables al código.",
            "content": {
              "text/plain": {
                "schema": { "type": "string", "example": "No applicable modelos found" }
              }
            }
          },
          "500": {
            "description": "Error al obtener los plazos.",
            "content": {
              "text/plain": {
                "schema": { "type": "string", "example": "Error fetching plazos" }
              }
            }
          }
        }
      }
    },
    "/signup": {
      "post": {
        "operationId": "createApiKey",
        "summary": "Crear una API key gratuita",
        "description": "Crea una API key y la envía por email. La primera clave de cada email empieza con 14 días de prueba Profesional (2.000 peticiones/día, sin tarjeta); después pasa al plan Gratis (250 peticiones/día). Endpoint público con límite de 5 peticiones/hora por IP. La clave se envía únicamente por email a la dirección indicada; la respuesta nunca la incluye. Te enviaremos emails sobre tu clave y, si te corresponde, tu prueba de 14 días. Puedes darte de baja en cualquier momento desde cualquier email.",
        "tags": ["Cuenta"],
        "security": [],
        "parameters": [
          {
            "name": "source",
            "in": "query",
            "required": false,
            "description": "Atribución de canal de alta (p. ej. `docs`, `homepage_cta`). Máx. 64 caracteres.",
            "schema": { "type": "string", "maxLength": 64, "example": "docs" }
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": { "$ref": "#/components/schemas/SignupRequest" }
            }
          }
        },
        "responses": {
          "200": {
            "description": "API key creada y enviada por email",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/SignupResponse" }
              }
            }
          },
          "400": { "$ref": "#/components/responses/BadRequestPublic" },
          "409": {
            "description": "Este email ya tiene una API key activa.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/PublicError" }
              }
            }
          },
          "429": { "$ref": "#/components/responses/RateLimitedPublic" },
          "500": { "$ref": "#/components/responses/ServerErrorPublic" }
        }
      }
    },
    "/subscribe": {
      "post": {
        "operationId": "createSubscription",
        "summary": "Suscribirse a un plan de pago",
        "description": "Crea una sesión de Stripe Checkout para un plan de pago (Profesional o Empresa) y devuelve la URL de pago. Endpoint público con límite por IP.",
        "tags": ["Cuenta"],
        "security": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": { "$ref": "#/components/schemas/SubscribeRequest" }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Sesión de Checkout creada",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/SubscribeResponse" }
              }
            }
          },
          "400": { "$ref": "#/components/responses/BadRequestPublic" },
          "429": { "$ref": "#/components/responses/RateLimitedPublic" },
          "500": { "$ref": "#/components/responses/ServerErrorPublic" }
        }
      }
    },
    "/usage": {
      "get": {
        "operationId": "getUsage",
        "summary": "Consultar uso de la API key",
        "description": "Devuelve el uso actual de la clave (consumo del día, límite, plan, endpoints más usados). No consume cuota diaria. Requiere la cabecera `X-API-Key`. Tiene límite por IP para evitar enumeración de claves.",
        "tags": ["Cuenta"],
        "security": [{ "ApiKeyAuth": [] }],
        "responses": {
          "200": {
            "description": "Estado de uso de la clave",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/UsageResponse" }
              }
            }
          },
          "401": { "$ref": "#/components/responses/UnauthorizedPublic" },
          "403": {
            "description": "API key desactivada.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/PublicError" }
              }
            }
          },
          "429": { "$ref": "#/components/responses/RateLimitedPublic" },
          "500": { "$ref": "#/components/responses/ServerErrorPublic" }
        }
      }
    },
    "/keys": {
      "post": {
        "operationId": "rotateOrRevokeApiKey",
        "summary": "Rotar o revocar tu propia API key",
        "description": "Permite al titular de una clave rotarla (genera una clave nueva; la anterior deja de funcionar de inmediato) o revocarla (desactivación inmediata) sin pasar por soporte. Autenticación por posesión: se valida la cabecera `X-API-Key` con la clave ACTUAL — no se admite ninguna otra credencial ni sesión. Disponible para cualquier plan. La clave nueva devuelta en `rotate` solo se muestra una vez en la respuesta: no se reenvía por email ni se registra en logs. Límite de 30 peticiones/hora por IP.",
        "tags": ["Cuenta"],
        "security": [{ "ApiKeyAuth": [] }],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": { "$ref": "#/components/schemas/KeysRequest" }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Acción aplicada correctamente",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/KeysResponse" }
              }
            }
          },
          "400": { "$ref": "#/components/responses/BadRequestPublic" },
          "401": { "$ref": "#/components/responses/UnauthorizedPublic" },
          "429": { "$ref": "#/components/responses/RateLimitedPublic" },
          "500": { "$ref": "#/components/responses/ServerErrorPublic" }
        }
      }
    },
    "/kyb/enrich": {
      "post": {
        "operationId": "kybEnrich",
        "summary": "Enriquecer actividad económica para KYB bancario",
        "description": "Vista KYB (Know Your Business) para entidades financieras. Devuelve la clasificación IAE/CNAE del cliente, perfil fiscal completo (sector de riesgo, regímenes IVA/IRPF, retenciones, casillas Modelo 036) y obligaciones fiscales aplicables. Requiere plan Empresa o superior. Envía `code` (+ `code_type` opcional) O `activity_text` (2–200 caracteres), nunca ambos a la vez.",
        "tags": ["B2B"],
        "security": [{ "ApiKeyAuth": [] }],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": { "$ref": "#/components/schemas/KybEnrichRequest" }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Perfil fiscal completo para KYB",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/KybEnrichResponse" }
              }
            }
          },
          "400": { "$ref": "#/components/responses/BadRequest" },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "403": { "$ref": "#/components/responses/EmpresaRequired" },
          "429": { "$ref": "#/components/responses/RateLimited" },
          "500": { "$ref": "#/components/responses/ServerError" }
        }
      }
    },
    "/onboarding/activity": {
      "post": {
        "operationId": "onboardingActivity",
        "summary": "Configurar actividad para onboarding SaaS / facturación",
        "description": "Vista de onboarding para SaaS de facturación y ERPs. Dado un código IAE/CNAE o texto de actividad libre, devuelve toda la configuración fiscal necesaria para pre-rellenar un alta de autónomo o empresa: epígrafe IAE, casillas Modelo 036, tipo y régimen de IVA, retención IRPF (tipo general y tipo inicio de actividad), régimen recomendado y próximos plazos fiscales. Requiere plan Empresa o superior. Envía `code` (+ `code_type` opcional) O `activity_text` (2–200 caracteres), nunca ambos a la vez.",
        "tags": ["B2B"],
        "security": [{ "ApiKeyAuth": [] }],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": { "$ref": "#/components/schemas/ActivityLookupRequest" }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Configuración fiscal para onboarding",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/OnboardingActivityResponse" }
              }
            }
          },
          "400": { "$ref": "#/components/responses/BadRequest" },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "403": { "$ref": "#/components/responses/EmpresaRequired" },
          "429": { "$ref": "#/components/responses/RateLimited" },
          "500": { "$ref": "#/components/responses/ServerError" }
        }
      }
    },
    "/dias-habiles": {
      "get": {
        "operationId": "getDiasHabiles",
        "summary": "Consultar si una fecha es día hábil",
        "description": "Devuelve si una fecha es día hábil bajo el calendario estatal de días inhábiles (sábados, domingos y festivos nacionales; art. 30 Ley 39/2015) y, si no lo es, el siguiente día hábil. Endpoint público (sin autenticación), sin efectos colaterales. Sin `fecha`, usa hoy (UTC).",
        "tags": ["Fiscal"],
        "security": [],
        "parameters": [
          {
            "name": "fecha",
            "in": "query",
            "required": false,
            "description": "Fecha a consultar en formato YYYY-MM-DD. Por defecto, hoy (UTC).",
            "schema": { "type": "string", "pattern": "^\\d{4}-\\d{2}-\\d{2}$", "example": "2026-04-18" }
          }
        ],
        "responses": {
          "200": {
            "description": "Resultado de la consulta de día hábil",
            "headers": {
              "Cache-Control": {
                "description": "Política de caché.",
                "schema": { "type": "string", "example": "public, max-age=86400" }
              }
            },
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/DiasHabilesResponse" },
                "example": {
                  "fecha": "2026-04-18",
                  "es_habil": false,
                  "motivo": "sábado",
                  "siguiente_habil": "2026-04-20",
                  "fuente": "BOE-A-2025-23702 (art. 30 Ley 39/2015)",
                  "cobertura": "calendario estatal 2026 (BOE-A-2025-23702)"
                }
              }
            }
          },
          "400": {
            "description": "Fecha con formato inválido, o año fuera de la cobertura del calendario.",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/Error" },
                "example": { "error": "Formato de fecha inválido. Usa YYYY-MM-DD (p. ej. 2026-04-18)." }
              }
            }
          }
        }
      }
    },
    "/utils/nif-info/{nif}": {
      "get": {
        "operationId": "getNifInfo",
        "summary": "Validar el dígito/letra de control de un NIF/DNI/NIE/CIF",
        "description": "Valida el carácter de control de un NIF/DNI/NIE/CIF español mediante el algoritmo oficial (Orden EHA/451/2008, Anexo I; RD 1065/2007, arts. 18-24). Endpoint público (sin autenticación), sin efectos colaterales. Puramente algorítmico: NO consulta el censo de la AEAT ni confirma que el NIF exista o esté activo (ver el campo `nota` de la respuesta).",
        "tags": ["Fiscal"],
        "security": [],
        "parameters": [
          {
            "name": "nif",
            "in": "path",
            "required": true,
            "description": "NIF/DNI/NIE/CIF a validar. Admite prefijo ES y guiones/espacios (se normaliza antes de validar).",
            "schema": { "type": "string", "example": "B12345674" }
          }
        ],
        "responses": {
          "200": {
            "description": "Resultado de la validación del carácter de control",
            "headers": {
              "Cache-Control": {
                "description": "Política de caché.",
                "schema": { "type": "string", "example": "public, max-age=86400" }
              }
            },
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/NifInfoResponse" },
                "example": {
                  "nif": "B12345674",
                  "tipo": "cif",
                  "valido": true,
                  "tipo_entidad": "Sociedad de Responsabilidad Limitada",
                  "provincia_registro": "Castellón",
                  "nota": "Validación algorítmica del dígito de control; no confirma la existencia del NIF en el censo de la AEAT."
                }
              }
            }
          },
          "400": {
            "description": "NIF/NIE/CIF con formato inválido (no cumple la forma letra/dígito + 7 dígitos + letra/dígito de control).",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/Error" },
                "example": { "error": "NIF/NIE/CIF con formato inválido. Debe tener 9 caracteres: letra o dígito, 7 dígitos, y letra o dígito de control (p. ej. 12345678Z, X1234567L, B12345674)." }
              }
            }
          }
        }
      }
    },
    "/subvenciones": {
      "get": {
        "operationId": "listSubvenciones",
        "summary": "Subvenciones BDNS abiertas por código CNAE",
        "description": "Devuelve convocatorias de subvenciones (BDNS) aplicables a un código CNAE 2025. El parámetro `cnae` es obligatorio. Con clave gratuita solo se honra `cnae`: la respuesta se limita a 5 resultados con campos de descubrimiento (`codigo_bdns`, `titulo`, `organo`, `nivel1`, `cnae_seccion`, `url_oficial`, `importe`, `estado`). Con plan Profesional o superior se honran además `region`, `estado`, `solo_actividad`, `importe_min`, `limit` (máx. 100) y `offset`, y la respuesta incluye todos los campos (`finalidad`, `tipos_beneficiarios`, `regiones`, `instrumentos`, `sectores`, `fecha_apertura`, `fecha_recepcion`, `url_bases`, `abierto`). `importe` es la dotación total de la convocatoria (no por beneficiario). El campo `fecha_cierre` nunca se expone — el dato de BDNS es poco fiable como plazo y no debe usarse para tomar decisiones de fecha límite. Nota sobre filtros y paginación: `region`, `estado` e `importe_min` se aplican sobre la ventana de filas ya obtenida (hasta `offset + limit`, con techo de 200), no sobre el conjunto completo de convocatorias del CNAE — una respuesta filtrada puede devolver menos filas que `limit`, y paginar más allá de esa ventana no garantiza cobertura exhaustiva.",
        "tags": ["Fiscal"],
        "security": [{ "ApiKeyAuth": [] }],
        "parameters": [
          {
            "name": "cnae",
            "in": "query",
            "required": true,
            "description": "Código CNAE 2025 con o sin puntos (p. ej. `47.11` o `4711`).",
            "schema": { "type": "string", "example": "4711" }
          },
          {
            "name": "region",
            "in": "query",
            "required": false,
            "description": "Filtra por Comunidad Autónoma (nombre normalizado, p. ej. `Madrid`). Solo plan Profesional o superior.",
            "schema": { "type": "string", "example": "Madrid" }
          },
          {
            "name": "estado",
            "in": "query",
            "required": false,
            "description": "Filtra por estado (`abierto`, `cerrado`, u otro valor libre comparado contra el campo `estado`). Solo plan Profesional o superior. Nota: este endpoint solo expone convocatorias ABIERTAS, por lo que filtrar por un estado cerrado (p. ej. `cerrado`) siempre devuelve un resultado vacío.",
            "schema": { "type": "string", "example": "abierto" }
          },
          {
            "name": "solo_actividad",
            "in": "query",
            "required": false,
            "description": "Si es `true`, limita a convocatorias cuyos beneficiarios encajan con autónomos/pymes (señal `tipos_beneficiarios`, no `es_para_empresas`). Solo plan Profesional o superior.",
            "schema": { "type": "boolean", "default": false }
          },
          {
            "name": "importe_min",
            "in": "query",
            "required": false,
            "description": "Importe (dotación) mínimo de la convocatoria. Solo plan Profesional o superior.",
            "schema": { "type": "number", "example": 100000 }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Número máximo de resultados (1–100, por defecto 50). Ignorado en plan Free (fijo en 5). Solo plan Profesional o superior.",
            "schema": { "type": "integer", "minimum": 1, "maximum": 100, "default": 50 }
          },
          {
            "name": "offset",
            "in": "query",
            "required": false,
            "description": "Desplazamiento de paginación. Se aplica sobre una ventana máxima de 200 filas — offsets mayores devuelven una página vacía. Solo plan Profesional o superior.",
            "schema": { "type": "integer", "minimum": 0, "default": 0 }
          }
        ],
        "responses": {
          "200": {
            "description": "Subvenciones BDNS aplicables al código CNAE",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/SubvencionesResponse" }
              }
            }
          },
          "400": { "$ref": "#/components/responses/BadRequest" },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "429": { "$ref": "#/components/responses/RateLimited" },
          "500": { "$ref": "#/components/responses/ServerError" }
        }
      }
    },
    "/legislacion": {
      "get": {
        "operationId": "searchLegislacion",
        "summary": "Búsqueda de texto legislativo BOE consolidado",
        "description": "Búsqueda de texto completo (FTS en español) sobre el corpus de artículos vigentes del BOE consolidado que alimenta las páginas públicas `/legislacion`. El parámetro `q` es obligatorio (consulta en lenguaje natural, p. ej. `alta autónomo actividad económica`). Con clave gratuita solo se honra `q`: la respuesta se limita a los 3 resultados más relevantes con campos de descubrimiento (`boe_id`, `articulo_norm`, `bloque_titulo`, `snippet` — el `snippet` es un fragmento HTML con los términos coincidentes resaltados en `<b>`); no se expone `rank` (puntuación interna de relevancia) ni se admite filtro por norma o paginación. Con plan Profesional o superior se honran además `boe_id` (limita la búsqueda a una norma concreta), `limit` (máx. 100, por defecto 50) y `offset` (paginación real a nivel de base de datos, no una ventana recortada), y cada resultado incluye `rank`. `_meta` siempre incluye `plan` y `remaining`; en plan Free añade `note` explicando el techo de 3 resultados; en Profesional+ añade `offset` y, si se aplicó, `boe_id`.",
        "tags": ["Fiscal"],
        "security": [{ "ApiKeyAuth": [] }],
        "parameters": [
          {
            "name": "q",
            "in": "query",
            "required": true,
            "description": "Consulta en lenguaje natural (español). Si falta o está vacía, responde 400.",
            "schema": { "type": "string", "example": "alta autónomo actividad económica" }
          },
          {
            "name": "boe_id",
            "in": "query",
            "required": false,
            "description": "Limita la búsqueda a una norma concreta (p. ej. `BOE-A-2006-20764`). Solo plan Profesional o superior — ignorado en plan Free.",
            "schema": { "type": "string", "example": "BOE-A-2006-20764" }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Número máximo de resultados (1–100, por defecto 50). Ignorado en plan Free (fijo en 3). Solo plan Profesional o superior.",
            "schema": { "type": "integer", "minimum": 1, "maximum": 100, "default": 50 }
          },
          {
            "name": "offset",
            "in": "query",
            "required": false,
            "description": "Desplazamiento de paginación, aplicado directamente en base de datos (sin techo artificial de ventana). Solo plan Profesional o superior — ignorado en plan Free.",
            "schema": { "type": "integer", "minimum": 0, "default": 0 }
          }
        ],
        "responses": {
          "200": {
            "description": "Resultados de la búsqueda de texto legislativo",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "q": { "type": "string" },
                    "resultados": {
                      "type": "array",
                      "items": {
                        "type": "object",
                        "properties": {
                          "boe_id": { "type": "string", "example": "BOE-A-2006-20764" },
                          "articulo_norm": { "type": "string", "example": "31" },
                          "bloque_titulo": { "type": "string" },
                          "snippet": { "type": "string", "description": "Fragmento HTML con los términos coincidentes envueltos en <b>." },
                          "rank": { "type": "number", "description": "Puntuación interna de relevancia (ts_rank). Solo plan Profesional o superior — ausente en la respuesta Free." }
                        }
                      }
                    },
                    "total": { "type": "integer" },
                    "_meta": {
                      "type": "object",
                      "properties": {
                        "plan": { "type": "string" },
                        "count": { "type": "integer" },
                        "remaining": { "type": "integer" },
                        "note": { "type": "string", "description": "Solo en plan Free — explica el techo de 3 resultados." },
                        "offset": { "type": "integer", "description": "Solo Profesional+ — el offset realmente aplicado." },
                        "boe_id": { "type": "string", "description": "Solo Profesional+ — el filtro por norma aplicado, si lo hubo." },
                        "upgrade_hint": { "type": "string" }
                      }
                    }
                  }
                },
                "example": {
                  "q": "alta autónomo actividad económica",
                  "resultados": [
                    {
                      "boe_id": "BOE-A-2006-20764",
                      "articulo_norm": "31",
                      "bloque_titulo": "Artículo 31. Método de estimación objetiva.",
                      "snippet": "…el <b>alta</b> en el <b>autónomo</b> régimen de estimación objetiva…"
                    }
                  ],
                  "total": 1,
                  "_meta": { "plan": "free", "count": 1, "remaining": 249, "note": "Vista limitada a 3 resultados…" }
                }
              }
            }
          },
          "400": { "$ref": "#/components/responses/BadRequest" },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "429": { "$ref": "#/components/responses/RateLimited" },
          "500": { "$ref": "#/components/responses/ServerError" }
        }
      }
    },
    "/legislacion/{boe_id}": {
      "get": {
        "operationId": "getLegislacionArticulo",
        "summary": "Artículo consolidado del BOE (y grafo de hechos en Profesional+)",
        "description": "Devuelve el texto vigente de un artículo del BOE consolidado (`boe_id` en la ruta, p. ej. `BOE-A-2006-20764`; `articulo` como query, p. ej. `31`). `articulo` es obligatorio salvo que se use `full=true` con plan Profesional o superior (para volcar la norma completa sin fijar un artículo base) — si falta sin esa combinación, responde 400. Si el artículo no existe vigente para esa norma, responde 200 con `articulo_texto: null` y `_meta.nota` (nunca 404). Con clave gratuita solo se devuelve `articulo_texto` (el mismo texto público que renderizan las páginas `/legislacion`); los flags de pago `facts`, `full` y `historial` se ignoran silenciosamente y `_meta.upgrade_hint` indica que requieren plan Profesional o superior. Con plan Profesional o superior: `facts=true` añade el grafo de hechos (Fact↔Law) del artículo, `historial=true` añade el historial de versiones, y `full=true` añade `norma_bloques` con todos los artículos vigentes de la norma (si se combina con la ausencia de `articulo`, la respuesta es solo `{ boe_id, norma_bloques, _meta }`, sin `articulo_texto`).",
        "tags": ["Fiscal"],
        "security": [{ "ApiKeyAuth": [] }],
        "parameters": [
          {
            "name": "boe_id",
            "in": "path",
            "required": true,
            "description": "Identificador BOE de la norma (p. ej. `BOE-A-2006-20764`).",
            "schema": { "type": "string", "example": "BOE-A-2006-20764" }
          },
          {
            "name": "articulo",
            "in": "query",
            "required": false,
            "description": "Número de artículo (p. ej. `31`). Obligatorio salvo que se use `full=true` con plan Profesional o superior; si falta sin esa combinación, responde 400.",
            "schema": { "type": "string", "example": "31" }
          },
          {
            "name": "facts",
            "in": "query",
            "required": false,
            "description": "Si es `true`, añade el grafo de hechos (Fact↔Law) del artículo. Solo plan Profesional o superior — ignorado en plan Free.",
            "schema": { "type": "boolean", "default": false }
          },
          {
            "name": "full",
            "in": "query",
            "required": false,
            "description": "Si es `true`, añade (o, sin `articulo`, devuelve en solitario) `norma_bloques` con todos los artículos vigentes de la norma. Solo plan Profesional o superior — ignorado en plan Free.",
            "schema": { "type": "boolean", "default": false }
          },
          {
            "name": "historial",
            "in": "query",
            "required": false,
            "description": "Si es `true`, añade el historial de versiones del artículo. Solo plan Profesional o superior — ignorado en plan Free.",
            "schema": { "type": "boolean", "default": false }
          }
        ],
        "responses": {
          "200": {
            "description": "Artículo consolidado (y, en Profesional+, grafo de hechos / norma completa / historial cuando se solicitan)",
            "content": {
              "application/json": {
                "schema": {
                  "type": "object",
                  "properties": {
                    "boe_id": { "type": "string" },
                    "articulo_texto": {
                      "nullable": true,
                      "type": "object",
                      "description": "Texto vigente del artículo, o null si no se encontró vigente para esta norma (ver _meta.nota). Ausente en el modo bulk (full=true sin articulo).",
                      "properties": {
                        "bloque_id": { "type": "string" },
                        "bloque_titulo": { "type": "string" },
                        "articulo_norm": { "type": "string", "nullable": true },
                        "version_idx": { "type": "integer" },
                        "fecha_vigencia": { "type": "string", "nullable": true, "format": "date" },
                        "valid_to": { "type": "string", "nullable": true, "format": "date" },
                        "es_vigente": { "type": "boolean" },
                        "texto_html": { "type": "string" },
                        "id_norma_modificadora": { "type": "string", "nullable": true }
                      }
                    },
                    "facts": {
                      "type": "array",
                      "description": "Solo Profesional+ y facts=true — grafo de hechos (Fact↔Law) del artículo.",
                      "items": {
                        "type": "object",
                        "properties": {
                          "fact_domain": { "type": "string" },
                          "fact_key": { "type": "string" },
                          "valor": {},
                          "unidad": { "type": "string", "nullable": true },
                          "articulo": { "type": "string", "nullable": true },
                          "source_anchor": { "type": "string", "nullable": true },
                          "vigente": { "type": "boolean" },
                          "valid_from": { "type": "string", "nullable": true, "format": "date" },
                          "valid_to": { "type": "string", "nullable": true, "format": "date" }
                        }
                      }
                    },
                    "historial": {
                      "type": "array",
                      "description": "Solo Profesional+ y historial=true — historial de versiones del artículo.",
                      "items": {
                        "type": "object",
                        "properties": {
                          "version_idx": { "type": "integer" },
                          "bloque_titulo": { "type": "string" },
                          "fecha_vigencia": { "type": "string", "nullable": true, "format": "date" },
                          "valid_to": { "type": "string", "nullable": true, "format": "date" },
                          "es_vigente": { "type": "boolean" },
                          "id_norma_modificadora": { "type": "string", "nullable": true }
                        }
                      }
                    },
                    "norma_bloques": {
                      "type": "array",
                      "description": "Solo Profesional+ y full=true — todos los artículos vigentes de la norma.",
                      "items": { "type": "object" }
                    },
                    "_meta": {
                      "type": "object",
                      "properties": {
                        "plan": { "type": "string" },
                        "count": { "type": "integer" },
                        "remaining": { "type": "integer" },
                        "nota": { "type": "string", "description": "Presente cuando el artículo solicitado no se encontró vigente para esta norma." },
                        "upgrade_hint": { "type": "string" }
                      }
                    }
                  }
                },
                "example": {
                  "boe_id": "BOE-A-2006-20764",
                  "articulo_texto": {
                    "bloque_id": "a31",
                    "bloque_titulo": "Artículo 31. Método de estimación objetiva.",
                    "articulo_norm": "31",
                    "version_idx": 3,
                    "fecha_vigencia": "2023-01-01",
                    "valid_to": null,
                    "es_vigente": true,
                    "texto_html": "<p>…</p>",
                    "id_norma_modificadora": null
                  },
                  "_meta": { "plan": "free", "count": 1, "remaining": 249, "upgrade_hint": "El grafo de hechos (facts), el histórico de versiones (historial) y la norma completa (full) requieren un plan Profesional o superior." }
                }
              }
            }
          },
          "400": { "$ref": "#/components/responses/BadRequest" },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "429": { "$ref": "#/components/responses/RateLimited" },
          "500": { "$ref": "#/components/responses/ServerError" }
        }
      }
    },
    "/empresas/{cnae}": {
      "get": {
        "operationId": "getEmpresasPorActividad",
        "summary": "Firmografía de empresas constituidas por código CNAE (BORME)",
        "description": "Devuelve estadísticas de constitución de empresas (BORME) para un código CNAE 2025: recuento total, capital medio/mediano, tendencia mensual y provincias — datos agregados a nivel de empresa (Ley 37/2007), sin datos personales. El parámetro de ruta `cnae` es obligatorio. Con clave gratuita los agregados se devuelven íntegros, pero la lista de constituciones recientes se limita a 3 con campos básicos (`denominacion`, `provincia`) — `provincia`/`desde`/`hasta` se ignoran silenciosamente en este plan. Con plan Profesional o superior se honran `limit` (máx. 100), `offset` y los filtros `provincia`/`desde`/`hasta`, y cada empresa incluye `capital_eur`, `published_at`, `cnae_primary`, `anuncio` (enlace directo al PDF oficial del BORME en boe.es, más año/número de boletín/código de provincia INE) y `datos_registrales` (tomo/folio/sección/hoja/inscripción/fecha registrales de la empresa); `_meta.filters` confirma los filtros aplicados. `stats` NUNCA se filtra ni se pagina — siempre refleja el CNAE completo. La paginación de `recent` es siempre nativa de base de datos (`.range()`), con o sin filtros activos.",
        "tags": ["Fiscal"],
        "security": [{ "ApiKeyAuth": [] }],
        "parameters": [
          { "name": "cnae", "in": "path", "required": true, "description": "Código CNAE 2025 con o sin puntos (p. ej. `68.20` o `6820`).", "schema": { "type": "string", "pattern": "^[\\d.]+$", "example": "6820" } },
          { "name": "limit", "in": "query", "required": false, "description": "Número máximo de constituciones recientes (1–100, por defecto 50). Ignorado en plan Free (fijo en 3). Solo plan Profesional o superior.", "schema": { "type": "integer", "minimum": 1, "maximum": 100, "default": 50 } },
          { "name": "offset", "in": "query", "required": false, "description": "Desplazamiento de paginación sobre `recent`. Paginación nativa de base de datos (`.range()`), con o sin filtros activos. Solo plan Profesional o superior.", "schema": { "type": "integer", "minimum": 0, "default": 0 } },
          { "name": "provincia", "in": "query", "required": false, "description": "Slug de provincia (p. ej. `madrid`, `alicante`) para filtrar `recent`. Ignorado en plan Free. En Profesional+, una provincia no reconocida devuelve 400.", "schema": { "type": "string", "example": "madrid" } },
          { "name": "desde", "in": "query", "required": false, "description": "Fecha mínima de constitución (AAAA-MM-DD, inclusive) para filtrar `recent`. Ignorado en plan Free. En Profesional+, una fecha inválida o posterior a `hasta` devuelve 400.", "schema": { "type": "string", "format": "date", "example": "2026-01-01" } },
          { "name": "hasta", "in": "query", "required": false, "description": "Fecha máxima de constitución (AAAA-MM-DD, inclusive) para filtrar `recent`. Ignorado en plan Free. En Profesional+, una fecha inválida devuelve 400.", "schema": { "type": "string", "format": "date", "example": "2026-06-30" } }
        ],
        "responses": {
          "200": { "description": "Firmografía de empresas para el código CNAE", "content": { "application/json": { "schema": { "$ref": "#/components/schemas/EmpresasResponse" } } } },
          "400": { "$ref": "#/components/responses/BadRequest" },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "429": { "$ref": "#/components/responses/RateLimited" },
          "500": { "$ref": "#/components/responses/ServerError" }
        }
      }
    },
    "/dgt": {
      "get": {
        "operationId": "searchDgtConsultas",
        "summary": "Búsqueda de consultas vinculantes de la DGT",
        "description": "Búsqueda de texto completo sobre consultas vinculantes y generales de la Dirección General de Tributos (`dgt_consultas`). El parámetro `q` es obligatorio. Con clave gratuita solo se honra `q`: la respuesta se limita a 3 resultados con campos de descubrimiento (`titulo_seo`, `resumen`), sin filtro por código ni paginación. Con plan Profesional o superior se honran además `code` (filtra por código IAE asociado), `limit` (máx. 100, por defecto 20) y `offset`, y la respuesta incluye todos los campos (`numero`, `slug`, `titulo_seo`, `resumen`, `fecha`, `organo`, `rank`).",
        "tags": ["Fiscal"],
        "security": [{ "ApiKeyAuth": [] }],
        "parameters": [
          {
            "name": "q",
            "in": "query",
            "required": true,
            "description": "Texto de búsqueda libre sobre las consultas de la DGT.",
            "schema": { "type": "string", "example": "arrendamiento de inmuebles" }
          },
          {
            "name": "code",
            "in": "query",
            "required": false,
            "description": "Filtra por código IAE asociado. Debe ir con puntos (p. ej. `501.3`) — el filtro compara contra los códigos IAE almacenados en formato con puntos; un código sin puntos no devuelve resultados. Solo plan Profesional o superior.",
            "schema": { "type": "string", "example": "501.3" }
          },
          {
            "name": "limit",
            "in": "query",
            "required": false,
            "description": "Número máximo de resultados (1–100, por defecto 20). Ignorado en plan Free (fijo en 3). Solo plan Profesional o superior.",
            "schema": { "type": "integer", "minimum": 1, "maximum": 100, "default": 20 }
          },
          {
            "name": "offset",
            "in": "query",
            "required": false,
            "description": "Desplazamiento de paginación (paginación nativa de la RPC subyacente). Solo plan Profesional o superior.",
            "schema": { "type": "integer", "minimum": 0, "default": 0 }
          }
        ],
        "responses": {
          "200": {
            "description": "Consultas de la DGT que coinciden con la búsqueda",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/DgtSearchResponse" }
              }
            }
          },
          "400": { "$ref": "#/components/responses/BadRequest" },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "429": { "$ref": "#/components/responses/RateLimited" },
          "500": { "$ref": "#/components/responses/ServerError" }
        }
      }
    },
    "/dgt/{slug}": {
      "get": {
        "operationId": "getDgtConsultaBySlug",
        "summary": "Detalle de una consulta vinculante de la DGT",
        "description": "Devuelve el detalle de una consulta de la DGT identificada por su `slug` (no el `numero`, p. ej. `V1234-23`, que no es válido en una URL). Con clave gratuita se devuelve identidad y resumen (`titulo_seo`, `resumen`, `normativa`); el texto íntegro (`hechos`, `cuestion`, `contestacion`) se omite por completo. Con plan Profesional o superior se devuelve además el texto íntegro de la consulta, `normas_citadas` y `codes_iae`.",
        "tags": ["Fiscal"],
        "security": [{ "ApiKeyAuth": [] }],
        "parameters": [
          {
            "name": "slug",
            "in": "path",
            "required": true,
            "description": "Slug de la consulta DGT (identificador URL-safe, distinto del número de consulta).",
            "schema": { "type": "string", "example": "v1234-23-arrendamiento-inmuebles" }
          }
        ],
        "responses": {
          "200": {
            "description": "Detalle de la consulta DGT",
            "content": {
              "application/json": {
                "schema": { "$ref": "#/components/schemas/DgtDetailResponse" }
              }
            }
          },
          "400": { "$ref": "#/components/responses/BadRequest" },
          "401": { "$ref": "#/components/responses/Unauthorized" },
          "404": { "$ref": "#/components/responses/NotFound" },
          "429": { "$ref": "#/components/responses/RateLimited" },
          "500": { "$ref": "#/components/responses/ServerError" }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "ApiKeyAuth": {
        "type": "apiKey",
        "in": "header",
        "name": "X-API-Key",
        "description": "Clave de API con prefijo `cvr_`. Obtén una gratis en POST /signup."
      }
    },
    "parameters": {
      "IaeCodePath": {
        "name": "code",
        "in": "path",
        "required": true,
        "description": "Código IAE con o sin puntos (p. ej. `501.3` o `5013`).",
        "schema": { "type": "string", "pattern": "^[\\d.]+$", "example": "5013" }
      },
      "CnaeCodePath": {
        "name": "code",
        "in": "path",
        "required": true,
        "description": "Código CNAE 2025 con o sin puntos (p. ej. `68.20` o `6820`).",
        "schema": { "type": "string", "pattern": "^[\\d.]+$", "example": "6820" }
      },
      "Cnae2009CodePath": {
        "name": "code",
        "in": "path",
        "required": true,
        "description": "Código CNAE 2009 con o sin puntos (máx. 20 caracteres).",
        "schema": { "type": "string", "pattern": "^[\\d.]+$", "maxLength": 20, "example": "4711" }
      },
      "GenericCodePath": {
        "name": "code",
        "in": "path",
        "required": true,
        "description": "Código IAE o CNAE con o sin puntos (solo dígitos y puntos, máx. 20).",
        "schema": { "type": "string", "pattern": "^[\\d.]+$", "maxLength": 20, "example": "5013" }
      },
      "CalendarioCodePath": {
        "name": "code",
        "in": "path",
        "required": true,
        "description": "Código IAE o CNAE con o sin puntos (solo dígitos y puntos, máx. 20). Admite sufijo `.ics` (p. ej. `5013.ics`).",
        "schema": { "type": "string", "pattern": "^[\\d.]+$", "maxLength": 20, "example": "5013" }
      },
      "CodeTypePath": {
        "name": "code_type",
        "in": "path",
        "required": true,
        "description": "Tipo de catálogo del código.",
        "schema": { "type": "string", "enum": ["iae", "cnae"], "example": "iae" }
      },
      "SlugQuery": {
        "name": "slug",
        "in": "query",
        "required": false,
        "description": "Slug para desambiguar cuando un código numérico mapea a varias filas. 1–120 caracteres, `^[a-z0-9-]+$`.",
        "schema": { "type": "string", "minLength": 1, "maxLength": 120, "pattern": "^[a-z0-9-]+$", "example": "albanileria" }
      }
    },
    "headers": {
      "XRateLimitLimit": {
        "description": "Cuota diaria total de la clave (draft-ietf-httpapi-ratelimit-headers).",
        "schema": { "type": "integer", "example": 250 }
      },
      "XRateLimitRemaining": {
        "description": "Peticiones restantes de la cuota diaria tras esta petición.",
        "schema": { "type": "integer", "example": 249 }
      },
      "XRateLimitReset": {
        "description": "Segundos (delta-seconds) hasta que se reinicia la cuota: medianoche Europe/Madrid.",
        "schema": { "type": "integer", "example": 40000 }
      },
      "XConversorPlan": {
        "description": "Plan de la clave que ha servido la petición (free, trial, profesional, empresa, enterprise).",
        "schema": { "type": "string", "example": "free" }
      }
    },
    "responses": {
      "BadRequest": {
        "description": "Petición inválida (parámetro o cuerpo mal formado).",
        "content": {
          "application/json": {
            "schema": { "$ref": "#/components/schemas/Error" },
            "example": { "error": "Invalid slug format", "_meta": { "plan": "free", "remaining": 248 } }
          }
        }
      },
      "BadRequestPublic": {
        "description": "Petición inválida (datos o cuerpo mal formado).",
        "content": {
          "application/json": {
            "schema": { "$ref": "#/components/schemas/PublicError" }
          }
        }
      },
      "Unauthorized": {
        "description": "Falta la cabecera `X-API-Key` o la clave es inválida/desactivada.",
        "content": {
          "application/json": {
            "schema": { "$ref": "#/components/schemas/Error" },
            "example": { "error": "Missing X-API-Key header" }
          }
        }
      },
      "UnauthorizedPublic": {
        "description": "Clave ausente, con formato inválido, o no encontrada.",
        "content": {
          "application/json": {
            "schema": { "$ref": "#/components/schemas/PublicError" }
          }
        }
      },
      "PaidRequired": {
        "description": "El endpoint requiere un plan de pago (Profesional o superior).",
        "content": {
          "application/json": {
            "schema": { "$ref": "#/components/schemas/PaidRequiredError" },
            "example": {
              "error": "Este endpoint requiere un plan de pago (Profesional o superior).",
              "current_plan": "free",
              "upgrade_url": "https://www.conversoriaecnae.es/precios"
            }
          }
        }
      },
      "EmpresaRequired": {
        "description": "El endpoint requiere el plan Empresa (o Enterprise/Compliance) — Free y Profesional reciben 403.",
        "content": {
          "application/json": {
            "schema": { "$ref": "#/components/schemas/PaidRequiredError" },
            "example": {
              "error": "Este endpoint requiere el plan Empresa (o Enterprise).",
              "current_plan": "profesional",
              "upgrade_url": "https://www.conversoriaecnae.es/precios"
            }
          }
        }
      },
      "NotFound": {
        "description": "Código no encontrado.",
        "content": {
          "application/json": {
            "schema": { "$ref": "#/components/schemas/Error" },
            "example": { "error": "IAE code not found", "_meta": { "plan": "free", "remaining": 247 } }
          }
        }
      },
      "AmbiguousCode": {
        "description": "El código mapea a varias filas. Usa `?slug=` (de `candidates[].url`) para desambiguar.",
        "content": {
          "application/json": {
            "schema": { "$ref": "#/components/schemas/AmbiguousCodeError" }
          }
        }
      },
      "RateLimited": {
        "description": "Límite de cuota diaria o de ráfaga superado. La cabecera `Retry-After` indica los segundos hasta poder reintentar. El 429 por cuota diaria incluye además `X-RateLimit-Limit`, `X-RateLimit-Remaining` (0), `X-RateLimit-Reset` y `X-Conversor-Plan`.",
        "headers": {
          "Retry-After": {
            "description": "Segundos hasta el siguiente reintento permitido (ráfaga: 1–12 s hasta el siguiente token, según plan; cuota diaria: hasta medianoche Europe/Madrid).",
            "schema": { "type": "integer", "example": 3600 }
          }
        },
        "content": {
          "application/json": {
            "schema": { "$ref": "#/components/schemas/RateLimitError" }
          }
        }
      },
      "RateLimitedPublic": {
        "description": "Demasiadas peticiones desde tu IP. Reintenta más tarde.",
        "headers": {
          "Retry-After": {
            "description": "Segundos sugeridos hasta el siguiente reintento (cuando aplica).",
            "schema": { "type": "integer", "example": 3600 }
          }
        },
        "content": {
          "application/json": {
            "schema": { "$ref": "#/components/schemas/PublicError" }
          }
        }
      },
      "ServerError": {
        "description": "Error interno del servidor.",
        "content": {
          "application/json": {
            "schema": { "$ref": "#/components/schemas/Error" },
            "example": { "error": "Internal error" }
          }
        }
      },
      "ServerErrorPublic": {
        "description": "Error interno del servidor.",
        "content": {
          "application/json": {
            "schema": { "$ref": "#/components/schemas/PublicError" }
          }
        }
      }
    },
    "schemas": {
      "Meta": {
        "type": "object",
        "description": "Metadatos del envelope para endpoints con clave: plan actual, cuota restante y días restantes de la prueba (solo plan trial) y nudge de upgrade a Empresa (solo plan Profesional al ≥80% de cuota).",
        "required": ["plan", "remaining"],
        "properties": {
          "plan": { "type": "string", "example": "free", "description": "Plan de la clave (free, trial, profesional, empresa, enterprise). `trial` = prueba Profesional de 14 días sin tarjeta; al terminar, la clave pasa a free." },
          "remaining": { "type": "integer", "example": 248, "description": "Peticiones restantes de la cuota diaria." },
          "trial_ends_in_days": { "type": "integer", "minimum": 0, "example": 9, "description": "Días completos que quedan de la prueba Profesional (redondeo hacia arriba). Presente solo con plan `trial`." },
          "upgrade_hint": {
            "type": "string",
            "description": "Mensaje legible de upgrade a Empresa. Presente solo en plan Profesional al consumir ≥80% de la cuota. Las claves Free ya no reciben este nudge: ven el campo bloqueado (`locked: true`) en `associated_cnae` / `associated_iae`.",
            "example": "Estás usando más del 80% de tu cuota diaria de Profesional. Empresa (79€/mes) amplía a 300.000 consultas/mes: https://www.conversoriaecnae.es/precios."
          },
          "upgrade": { "$ref": "#/components/schemas/UpgradeMeta" }
        }
      },
      "LockedTeaser": {
        "type": "object",
        "description": "Campo de correspondencia bloqueado en respuestas Free: muestra que existe y cuántos códigos contiene, sin revelar ninguno.",
        "required": ["locked", "count", "upgrade_url"],
        "properties": {
          "locked": { "type": "boolean", "enum": [true] },
          "count": { "type": "integer", "minimum": 0, "example": 3, "description": "Número real de códigos asociados." },
          "upgrade_url": { "type": "string", "format": "uri", "example": "https://www.conversoriaecnae.es/precios" }
        }
      },
      "UpgradeMeta": {
        "type": "object",
        "description": "Forma estructurada del nudge de upgrade para renderizar UI nativa. Presente solo en plan Profesional al ≥80% de cuota (nudge a Empresa).",
        "properties": {
          "hint": { "type": "string" },
          "url": { "type": "string", "format": "uri", "example": "https://www.conversoriaecnae.es/precios" },
          "current_plan": { "type": "string", "example": "free" },
          "recommended_plan": {
            "type": "object",
            "properties": {
              "id": { "type": "string", "example": "profesional" },
              "label": { "type": "string", "example": "Profesional" },
              "daily_limit": { "type": "integer", "example": 2000 },
              "monthly_price_eur": { "type": "integer", "example": 29 }
            }
          },
          "daily_limit_after": { "type": "integer", "example": 2000 },
          "price_eur_monthly": { "type": "integer", "example": 29 }
        }
      },
      "Error": {
        "type": "object",
        "description": "Cuerpo de error estándar de endpoints con clave.",
        "required": ["error"],
        "properties": {
          "error": { "type": "string", "example": "IAE code not found" },
          "_meta": { "$ref": "#/components/schemas/Meta" }
        }
      },
      "DiasHabilesResponse": {
        "type": "object",
        "description": "Resultado de la consulta de día hábil para una fecha.",
        "required": ["fecha", "es_habil", "motivo", "siguiente_habil", "fuente", "cobertura"],
        "properties": {
          "fecha": { "type": "string", "format": "date", "example": "2026-04-18", "description": "Fecha consultada (YYYY-MM-DD)." },
          "es_habil": { "type": "boolean", "example": false, "description": "Si la fecha es día hábil." },
          "motivo": {
            "type": "string",
            "nullable": true,
            "example": "sábado",
            "description": "Motivo por el que la fecha es inhábil: 'sábado', 'domingo', o el nombre del festivo nacional. Null cuando es_habil es true."
          },
          "siguiente_habil": {
            "type": "string",
            "format": "date",
            "nullable": true,
            "example": "2026-04-20",
            "description": "Siguiente día hábil (YYYY-MM-DD) cuando la fecha consultada es inhábil. Null cuando es_habil es true."
          },
          "fuente": { "type": "string", "example": "BOE-A-2025-23702 (art. 30 Ley 39/2015)", "description": "Referencia normativa del calendario de días inhábiles." },
          "cobertura": { "type": "string", "example": "calendario estatal 2026 (BOE-A-2025-23702)", "description": "Años cubiertos por el calendario nacional de días inhábiles." }
        }
      },
      "NifInfoResponse": {
        "type": "object",
        "description": "Resultado de la validación algorítmica de un NIF/DNI/NIE/CIF.",
        "required": ["nif", "tipo", "valido", "nota"],
        "properties": {
          "nif": { "type": "string", "example": "B12345674", "description": "NIF normalizado (mayúsculas, sin prefijo ES ni guiones/espacios)." },
          "tipo": { "type": "string", "enum": ["dni", "nie", "cif"], "example": "cif", "description": "Tipo de identificador detectado." },
          "valido": { "type": "boolean", "example": true, "description": "Si el dígito/letra de control coincide con el calculado por el algoritmo oficial." },
          "tipo_entidad": { "type": "string", "example": "Sociedad de Responsabilidad Limitada", "description": "Naturaleza jurídica según la letra inicial del CIF (Orden EHA/451/2008, Anexo II). Solo presente cuando tipo es 'cif' y la letra es reconocida." },
          "provincia_registro": { "type": "string", "example": "Castellón", "description": "Provincia asociada al prefijo de 2 dígitos del CIF (delegación de registro). Solo presente cuando tipo es 'cif', la letra es de una entidad registrada (no K/L/M) y el prefijo es reconocido." },
          "nota": { "type": "string", "example": "Validación algorítmica del dígito de control; no confirma la existencia del NIF en el censo de la AEAT.", "description": "Aviso obligatorio: la validación es puramente algorítmica y no consulta el censo de la AEAT." }
        }
      },
      "PublicError": {
        "type": "object",
        "description": "Cuerpo de error de endpoints públicos / de cuenta.",
        "required": ["success", "error"],
        "properties": {
          "success": { "type": "boolean", "example": false },
          "error": { "type": "string", "example": "Datos inválidos." },
          "fields": {
            "type": "object",
            "additionalProperties": { "type": "array", "items": { "type": "string" } },
            "description": "Errores de validación por campo (cuando aplica)."
          }
        }
      },
      "PaidRequiredError": {
        "type": "object",
        "required": ["error"],
        "properties": {
          "error": { "type": "string", "example": "Este endpoint requiere un plan de pago (Profesional o superior)." },
          "current_plan": { "type": "string", "example": "free" },
          "upgrade_url": { "type": "string", "format": "uri", "example": "https://www.conversoriaecnae.es/precios" }
        }
      },
      "RateLimitError": {
        "type": "object",
        "required": ["error"],
        "properties": {
          "error": { "type": "string", "example": "Rate limit exceeded" },
          "limit": { "type": "integer", "example": 250 },
          "plan": { "type": "string", "example": "free" },
          "retry_after_seconds": { "type": "integer", "example": 3600 },
          "current_plan_label": { "type": "string", "example": "Free (250 peticiones/día)" },
          "upgrade_url": { "type": "string", "format": "uri", "example": "https://www.conversoriaecnae.es/precios" },
          "recommended_plan": {
            "type": "object",
            "properties": {
              "id": { "type": "string", "example": "profesional" },
              "label": { "type": "string", "example": "Profesional" },
              "daily_limit": { "type": "integer", "example": 2000 },
              "monthly_price_eur": { "type": "integer", "example": 29 }
            }
          }
        }
      },
      "AmbiguousCodeError": {
        "type": "object",
        "required": ["error", "code", "candidates"],
        "properties": {
          "error": { "type": "string", "example": "ambiguous_code" },
          "code": { "type": "string", "example": "834" },
          "message": { "type": "string", "example": "Multiple IAE rows match this code. Use ?slug= to disambiguate." },
          "candidates": {
            "type": "array",
            "items": { "$ref": "#/components/schemas/AmbiguousCandidate" }
          },
          "_meta": { "$ref": "#/components/schemas/Meta" }
        }
      },
      "AmbiguousCandidate": {
        "type": "object",
        "description": "Una de las filas que coinciden con el código ambiguo. Sigue `url` para la consulta desambiguada.",
        "properties": {
          "code": { "type": "string", "example": "834", "description": "Código con puntos." },
          "cod_integro": { "type": "string", "nullable": true, "example": "834" },
          "slug": { "type": "string", "example": "servicios-relativos-a-la-propiedad-inmobiliaria" },
          "title": { "type": "string", "nullable": true, "example": "Servicios relativos a la propiedad inmobiliaria" },
          "url": { "type": "string", "example": "/api/v1/iae/834?slug=servicios-relativos-a-la-propiedad-inmobiliaria" }
        }
      },
      "IaeDetail": {
        "type": "object",
        "description": "Detalle de un código IAE.",
        "properties": {
          "code": { "type": "string", "example": "501.3", "description": "Código con puntos." },
          "cod_integro": { "type": "string", "nullable": true, "example": "501.3" },
          "slug": { "type": "string", "example": "albanileria-y-pequenos-trabajos-de-construccion" },
          "seccion_label": { "type": "string", "nullable": true, "example": "Sección Primera: Actividades Empresariales" },
          "title": { "type": "string", "nullable": true, "example": "Albañilería y pequeños trabajos de construcción en general" },
          "description": { "type": "string", "nullable": true },
          "category": { "type": "string", "nullable": true, "example": "Construcción" },
          "level": { "type": "integer", "nullable": true, "example": 3 },
          "modulos_eligible": { "type": "boolean", "nullable": true, "example": true },
          "associated_cnae": { "oneOf": [ { "type": "array", "nullable": true, "items": {} }, { "$ref": "#/components/schemas/LockedTeaser" } ], "description": "Códigos CNAE 2025 asociados. Plan Profesional, prueba activa o superior: array de códigos. Plan Free (o prueba caducada): objeto `LockedTeaser` con el número real de códigos y sin códigos ni títulos." },
          "cnae_2025_mapping": { "type": "string", "enum": ["1:1", "1:n", "sin_correspondencia"], "example": "1:n", "description": "Clasificación de la correspondencia del epígrafe con CNAE 2025: `1:1` (un solo código), `1:n` (varios) o `sin_correspondencia` (ninguno). Solo plan Profesional, prueba activa o superior; en plan Free la clave no aparece." },
          "related_iae": { "type": "array", "nullable": true, "items": {}, "description": "Códigos IAE relacionados (solo plan Profesional o superior)." },
          "keywords": { "type": "array", "nullable": true, "items": {}, "description": "Palabras clave del epígrafe." },
          "last_updated": { "type": "string", "nullable": true, "example": "2026-01-15" },
          "_meta": { "$ref": "#/components/schemas/Meta" }
        }
      },
      "CnaeDetail": {
        "type": "object",
        "description": "Detalle de un código CNAE 2025.",
        "properties": {
          "code": { "type": "string", "example": "68.20", "description": "Código con puntos." },
          "title": { "type": "string", "nullable": true, "example": "Alquiler de bienes inmobiliarios por cuenta propia" },
          "description": { "type": "string", "nullable": true },
          "category": { "type": "string", "nullable": true },
          "level": { "type": "integer", "nullable": true, "example": 4 },
          "associated_iae": { "oneOf": [ { "type": "array", "nullable": true, "items": {} }, { "$ref": "#/components/schemas/LockedTeaser" } ], "description": "Códigos IAE asociados. Plan Profesional, prueba activa o superior: array de códigos. Plan Free (o prueba caducada): objeto `LockedTeaser` con el número real de códigos y sin códigos ni títulos." },
          "related_cnae": { "type": "array", "nullable": true, "items": {}, "description": "Códigos CNAE relacionados (solo plan Profesional o superior)." },
          "cnae_2009": { "type": "array", "nullable": true, "items": {}, "description": "Equivalencias CNAE 2009 (solo plan Profesional o superior)." },
          "keywords": { "type": "array", "nullable": true, "items": {}, "description": "Palabras clave." },
          "last_updated": { "type": "string", "nullable": true, "example": "2026-01-15" },
          "nace": {
            "type": "object",
            "nullable": true,
            "description": "Equivalencia europea NACE Rev. 2.1 del código CNAE 2025, según la tabla oficial de correspondencias del INE. Disponible en todos los planes, incluido el gratuito. `null` si el código no tiene equivalencia. Atención: la mayoría son equivalencias 1:1, pero España subdivide algunas clases europeas (p. ej. CNAE 41.01 y 41.02 corresponden ambas a NACE 41.00).",
            "properties": {
              "code": { "type": "string", "example": "68.20", "description": "Código NACE con puntos." },
              "revision": { "type": "string", "enum": ["NACE Rev. 2.1"], "example": "NACE Rev. 2.1", "description": "Versión de la NACE emparejada con la CNAE 2025." },
              "fuente": { "type": "string", "format": "uri", "example": "https://www.ine.es/daco/daco42/clasificaciones/cnae25/Correspondencias_CNAE25_NACE_Rev_2_1.xlsx", "description": "Fuente primaria (INE) de la que se generó la correspondencia." }
            }
          },
          "_meta": { "$ref": "#/components/schemas/Meta" }
        }
      },
      "Cnae2009Correspondence": {
        "type": "object",
        "description": "Correspondencias CNAE 2009 → CNAE 2025.",
        "properties": {
          "cnae_2009": { "type": "string", "example": "4711", "description": "Código CNAE 2009 normalizado (sin puntos)." },
          "correspondences": {
            "type": "array",
            "description": "Filas de correspondencia hacia CNAE 2025.",
            "items": { "type": "object", "additionalProperties": true }
          },
          "_meta": { "$ref": "#/components/schemas/Meta" }
        }
      },
      "SearchResponse": {
        "type": "object",
        "properties": {
          "query": { "type": "string", "example": "albañilería" },
          "type": { "type": "string", "enum": ["iae", "cnae", "both"], "example": "both" },
          "count": { "type": "integer", "example": 12 },
          "results": {
            "type": "array",
            "items": { "$ref": "#/components/schemas/SearchResult" }
          },
          "_meta": { "$ref": "#/components/schemas/Meta" }
        }
      },
      "SearchResult": {
        "type": "object",
        "description": "Resultado de búsqueda. Incluye las columnas del RPC `search_*_codes` más un campo `type`.",
        "additionalProperties": true,
        "properties": {
          "type": { "type": "string", "enum": ["iae", "cnae"], "example": "iae" }
        }
      },
      "ModulosDetail": {
        "type": "object",
        "description": "Régimen de módulos (estimación objetiva) de un código IAE.",
        "properties": {
          "code": { "type": "string", "example": "5013", "description": "Código normalizado, sin puntos (este endpoint devuelve la forma normalizada, a diferencia de /iae/{code} que devuelve la forma con puntos)." },
          "slug": { "type": "string", "example": "albanileria-y-pequenos-trabajos-de-construccion" },
          "titulo": { "type": "string", "nullable": true, "example": "Albañilería y pequeños trabajos de construcción" },
          "modulos_eligible": { "type": "boolean", "nullable": true, "example": true },
          "regimen": { "$ref": "#/components/schemas/Regimen" },
          "citation": {
            "type": "object",
            "nullable": true,
            "description": "Cita de fuente y frescura del régimen de módulos, leída del sidecar `modulos_snapshot` (Orden HAC/1425/2025). `null` cuando no hay snapshot sincronizado para este código — no es un error.",
            "properties": {
              "orden": { "type": "string", "nullable": true, "example": "Orden HAC/1425/2025" },
              "source_anchor": { "type": "string", "nullable": true, "example": "Orden HAC/1425/2025, Anexo II" },
              "ejercicio": { "type": "integer", "nullable": true, "example": 2026 },
              "verified_at": { "type": "string", "format": "date-time", "nullable": true, "example": "2026-06-01T00:00:00.000Z" }
            }
          },
          "_meta": { "$ref": "#/components/schemas/Meta" }
        }
      },
      "Regimen": {
        "type": "object",
        "properties": {
          "actividad": { "type": "object", "nullable": true, "additionalProperties": true },
          "modulos_irpf": { "type": "array", "items": { "type": "object", "additionalProperties": true } },
          "modulos_iva": { "type": "array", "items": { "type": "object", "additionalProperties": true } },
          "umbrales_actividad": { "type": "object", "nullable": true, "additionalProperties": true },
          "umbrales_generales": { "type": "object", "nullable": true, "additionalProperties": true },
          "intelligence": {
            "type": "object",
            "nullable": true,
            "properties": {
              "modulos_summary": { "type": "string", "nullable": true },
              "calculo_hints": {
                "type": "object",
                "nullable": true,
                "properties": {
                  "primary_modulos": { "type": "array", "items": {} },
                  "common_pitfalls": { "type": "array", "items": {} }
                }
              },
              "citations": { "type": "array", "items": {} }
            }
          }
        }
      },
      "TarifaAtEpDetail": {
        "type": "object",
        "description": "Tarifa AT/EP (cotización por cuenta ajena) de un código CNAE 2025.",
        "properties": {
          "code": { "type": "string", "example": "6820", "description": "Código CNAE consultado, normalizado sin puntos." },
          "cnae_code": { "type": "string", "example": "6820", "description": "Código CNAE efectivamente resuelto (puede ser un prefijo del código consultado, ver `resolved_via`)." },
          "ejercicio": { "type": "integer", "example": 2026 },
          "tipo_it": { "type": "number", "nullable": true, "example": 1.5, "description": "Tipo de Incapacidad Temporal, en %." },
          "tipo_ims": { "type": "number", "nullable": true, "example": 1.1, "description": "Tipo de Incapacidad Muerte y Supervivencia, en %." },
          "tipo_total": { "type": "number", "nullable": true, "example": 2.6, "description": "Suma tipo_it + tipo_ims, en %." },
          "descripcion": { "type": "string", "nullable": true, "example": "Alquiler de bienes inmobiliarios por cuenta propia" },
          "source_anchor": { "type": "string", "nullable": true, "example": "D.A. 61ª TRLGSS, Cuadro I" },
          "resolved_via": { "type": "string", "enum": ["exact", "prefix3", "prefix2"], "example": "exact", "description": "Nivel de resolución usado para encontrar la tarifa: coincidencia exacta, o fallback a prefijo de 3 o 2 dígitos." },
          "aplicacion": { "type": "string", "enum": ["cuenta_ajena"], "example": "cuenta_ajena", "description": "Esta tarifa aplica a trabajadores POR CUENTA AJENA (empleados). El autónomo cotiza AT/EP al tipo fijo indicado en `nota_reta`, no a este tipo." },
          "nota_reta": { "type": "string", "example": "1.30", "description": "Tipo fijo (%) que aplica el autónomo (RETA) por AT/EP, independiente de la actividad — no confundir con `tipo_total`." },
          "_meta": { "$ref": "#/components/schemas/Meta" }
        }
      },
      "SubvencionesResponse": {
        "type": "object",
        "description": "Convocatorias de subvenciones BDNS aplicables a un código CNAE. La forma de cada elemento de `subvenciones` depende del plan — ver SubvencionDiscoveryItem (Free) y SubvencionFullItem (Profesional+).",
        "properties": {
          "cnae": { "type": "string", "example": "4711", "description": "Código CNAE consultado, tal y como se envió." },
          "subvenciones": {
            "type": "array",
            "items": {
              "oneOf": [
                { "$ref": "#/components/schemas/SubvencionDiscoveryItem" },
                { "$ref": "#/components/schemas/SubvencionFullItem" }
              ]
            }
          },
          "total": { "type": "integer", "example": 3, "description": "Número de filas devueltas en esta página." },
          "_meta": { "$ref": "#/components/schemas/SubvencionesMeta" }
        }
      },
      "SubvencionesMeta": {
        "allOf": [
          { "$ref": "#/components/schemas/Meta" },
          {
            "type": "object",
            "properties": {
              "count": { "type": "integer", "example": 3 },
              "note": { "type": "string", "nullable": true, "description": "Presente solo en plan Free — explica el tope de 5 filas y que los filtros requieren plan Profesional o superior." },
              "offset": { "type": "integer", "nullable": true, "description": "Presente solo en plan Profesional o superior — el offset efectivamente aplicado." }
            }
          }
        ]
      },
      "SubvencionDiscoveryItem": {
        "type": "object",
        "description": "Proyección de campos de descubrimiento (plan Free) — máx. 5 filas por respuesta.",
        "properties": {
          "codigo_bdns": { "type": "string", "example": "800123" },
          "titulo": { "type": "string", "example": "Ayudas a la digitalización de pymes" },
          "organo": { "type": "string", "nullable": true, "example": "Ministerio de Industria" },
          "nivel1": { "type": "string", "nullable": true, "example": "Estado" },
          "cnae_seccion": { "type": "string", "nullable": true, "example": "G", "description": "Primera sección CNAE asociada a la convocatoria." },
          "url_oficial": { "type": "string", "nullable": true, "format": "uri" },
          "importe": { "type": "number", "nullable": true, "example": 5000000, "description": "Dotación total de la convocatoria (no por beneficiario)." },
          "estado": { "type": "string", "nullable": true, "example": "abierto" }
        }
      },
      "SubvencionFullItem": {
        "type": "object",
        "description": "Proyección completa (plan Profesional o superior). NUNCA incluye `fecha_cierre` — el dato de BDNS es poco fiable como plazo.",
        "properties": {
          "codigo_bdns": { "type": "string", "example": "800123" },
          "titulo": { "type": "string", "example": "Ayudas a la digitalización de pymes" },
          "finalidad": { "type": "string", "nullable": true },
          "organo": { "type": "string", "nullable": true, "example": "Ministerio de Industria" },
          "nivel1": { "type": "string", "nullable": true, "example": "Estado" },
          "importe": { "type": "number", "nullable": true, "example": 5000000, "description": "Dotación total de la convocatoria (no por beneficiario)." },
          "fecha_apertura": { "type": "string", "nullable": true, "example": "2026-01-15" },
          "fecha_recepcion": { "type": "string", "nullable": true, "example": "2026-01-10" },
          "abierto": { "type": "boolean", "example": true },
          "estado": { "type": "string", "nullable": true, "example": "abierto" },
          "tipo_convocatoria": { "type": "string", "nullable": true, "example": "concurrencia competitiva" },
          "sectores": { "description": "Array jsonb de {codigo, descripcion} (estructura variable)." },
          "cnae_secciones": { "type": "array", "items": { "type": "string" }, "example": ["G"] },
          "tipos_beneficiarios": { "type": "array", "items": { "type": "string" }, "example": ["Autónomos", "Pymes"], "description": "Señal de autónomo/pyme — úsala en vez de es_para_empresas." },
          "instrumentos": { "type": "array", "items": { "type": "string" }, "example": ["Subvención"] },
          "regiones": { "type": "array", "items": { "type": "string" }, "example": ["ES30 - MADRID"], "description": "Valores libres de BDNS; normalízalos con normalizeRegionToCCAA si necesitas el nombre canónico de la CCAA." },
          "url_bases": { "type": "string", "nullable": true, "format": "uri" },
          "url_oficial": { "type": "string", "nullable": true, "format": "uri" }
        }
      },
      "EmpresasResponse": {
        "type": "object",
        "description": "Firmografía BORME por CNAE. Los agregados (recuento, capital, tendencia, provincias) son libres; la forma de cada elemento de `firmographics.recent` depende del plan — ver BormeRecentTeaser (Free) y BormeRecentFull (Profesional+).",
        "properties": {
          "cnae": { "type": "string", "example": "6820", "description": "Código CNAE consultado, tal y como se envió." },
          "firmographics": { "$ref": "#/components/schemas/EmpresasFirmographics" },
          "_meta": { "$ref": "#/components/schemas/EmpresasMeta" }
        }
      },
      "EmpresasFirmographics": {
        "type": "object",
        "properties": {
          "code": { "type": "string", "example": "6820" },
          "total_count": { "type": "integer", "example": 1234 },
          "capital_avg": { "type": "number", "nullable": true, "example": 15000 },
          "capital_median": { "type": "number", "nullable": true, "example": 3000 },
          "first_published": { "type": "string", "nullable": true, "example": "2024-01-05" },
          "last_published": { "type": "string", "nullable": true, "example": "2026-06-30" },
          "monthly": { "type": "array", "items": { "type": "object", "properties": { "month": { "type": "string", "example": "2026-06" }, "count": { "type": "integer", "example": 42 } } } },
          "top_provincias": { "type": "array", "items": { "type": "object", "properties": { "provincia": { "type": "string", "example": "Madrid" }, "count": { "type": "integer", "example": 300 } } } },
          "recent": { "type": "array", "items": { "oneOf": [ { "$ref": "#/components/schemas/BormeRecentTeaser" }, { "$ref": "#/components/schemas/BormeRecentFull" } ] } }
        }
      },
      "EmpresasMeta": {
        "allOf": [
          { "$ref": "#/components/schemas/Meta" },
          { "type": "object", "properties": {
            "count": { "type": "integer", "example": 3, "description": "Número de constituciones recientes devueltas en esta página." },
            "note": { "type": "string", "nullable": true, "description": "Presente solo en plan Free — explica el tope de 3 filas y los campos básicos." },
            "offset": { "type": "integer", "nullable": true, "description": "Presente solo en plan Profesional o superior — el offset efectivamente aplicado." },
            "filters": { "$ref": "#/components/schemas/EmpresasAppliedFilters" }
          } }
        ]
      },
      "EmpresasAppliedFilters": {
        "type": "object",
        "nullable": true,
        "description": "Presente solo en plan Profesional o superior y solo cuando se aplicó al menos un filtro — confirma los filtros de provincia/fecha efectivamente aplicados (provincia normalizada, no el slug enviado).",
        "properties": {
          "provincia": { "type": "string", "nullable": true, "example": "MADRID" },
          "desde": { "type": "string", "nullable": true, "example": "2026-01-01" },
          "hasta": { "type": "string", "nullable": true, "example": "2026-06-30" }
        }
      },
      "BormeRecentTeaser": {
        "type": "object",
        "description": "Proyección básica de una constitución reciente (plan Free) — máx. 3 filas.",
        "properties": {
          "denominacion": { "type": "string", "example": "EJEMPLO SL" },
          "provincia": { "type": "string", "nullable": true, "example": "Madrid" }
        }
      },
      "BormeRecentFull": {
        "type": "object",
        "description": "Proyección completa de una constitución reciente (plan Profesional o superior).",
        "properties": {
          "denominacion": { "type": "string", "example": "EJEMPLO SL" },
          "provincia": { "type": "string", "nullable": true, "example": "Madrid" },
          "capital_eur": { "type": "number", "nullable": true, "example": 3000 },
          "published_at": { "type": "string", "nullable": true, "example": "2026-06-15" },
          "cnae_primary": { "type": "string", "nullable": true, "example": "6820" },
          "anuncio": { "$ref": "#/components/schemas/EmpresasAnuncio" },
          "datos_registrales": { "$ref": "#/components/schemas/EmpresasDatosRegistrales" }
        }
      },
      "EmpresasAnuncio": {
        "type": "object",
        "nullable": true,
        "description": "Enlace directo al anuncio BORME oficial en boe.es, más el identificador BORME descompuesto. `null` cuando el identificador de origen no tiene el formato esperado.",
        "properties": {
          "identificador_borme": { "type": "string", "example": "BORME-A-2026-150-12", "description": "Identificador de publicación BORME tal cual, formato BORME-A-{año}-{nº boletín}-{cód. provincia INE}." },
          "anio": { "type": "integer", "example": 2026 },
          "numero_boletin": { "type": "integer", "example": 150, "description": "Número del boletín BORME-A de ese día." },
          "codigo_provincia": { "type": "integer", "example": 12, "description": "Código de provincia del INE (01–52) que identifica la sección provincial del BORME-A de ese día en la que se publicó el anuncio de esta empresa (12 = Castellón/Castelló). No es un número de página: `pdf_url` apunta al PDF de esa sección provincial. El valor 99 corresponde al índice alfabético de sociedades del día." },
          "pdf_url": { "type": "string", "nullable": true, "example": "https://www.boe.es/borme/dias/2026/06/30/pdfs/BORME-A-2026-150-12.pdf", "description": "URL directa al PDF oficial en boe.es. `null` cuando `published_at` falta o no es una fecha ISO válida, aunque el resto del objeto `anuncio` sí exista porque el identificador se pudo parsear." }
        }
      },
      "EmpresasDatosRegistrales": {
        "type": "object",
        "nullable": true,
        "description": "Datos registrales de la empresa (tomo/folio/sección/hoja/inscripción/fecha) — identidad registral de la empresa, no datos personales. Cualquiera de los seis campos puede ser `null` cuando esa clave no está presente en el registro de origen (o su valor no es una cadena en el jsonb original de BORME) — tomo/folio lo son con frecuencia en inscripciones recientes según la fuente. Además, el objeto completo solo se rellena para los anuncios publicados a partir de la fecha de despliegue (2026-08-24): no hay carga retroactiva, así que las empresas históricas devuelven `datos_registrales: null` de forma permanente y al paginar hacia atrás en `recent` (ordenado por `published_at` descendente) verás páginas enteras a `null` — es el comportamiento esperado, no un fallo.",
        "properties": {
          "tomo": { "type": "string", "nullable": true, "example": "1234" },
          "folio": { "type": "string", "nullable": true, "example": "56" },
          "seccion": { "type": "string", "nullable": true, "example": "8" },
          "hoja": { "type": "string", "nullable": true, "example": "M-853607" },
          "inscripcion": { "type": "string", "nullable": true, "example": "1" },
          "fecha": { "type": "string", "nullable": true, "example": "22.05.25" }
        }
      },
      "ActivitySubvencionesBlock": {
        "type": "object",
        "nullable": true,
        "description": "Subvenciones BDNS/SNPSAP abiertas para la sección CNAE de la actividad resuelta. `null` cuando la actividad no tiene correspondencia CNAE 2025 (no se pudo derivar sección) o si el dato no está disponible. NUNCA incluye `fecha_cierre` — el dato de BDNS es poco fiable como plazo y no debe usarse para tomar decisiones de fecha límite.",
        "properties": {
          "seccion": { "type": "string", "nullable": true, "example": "G", "description": "Sección CNAE (A–U) derivada del código CNAE 2025 resuelto." },
          "abiertas_count": { "type": "integer", "example": 42, "description": "Convocatorias abiertas en esta sección CNAE." },
          "para_actividad_economica_count": { "type": "integer", "example": 17, "description": "Subconjunto de abiertas_count dirigido a autónomos/pyme/empresa (tipos_beneficiarios)." },
          "destacadas": {
            "type": "array",
            "maxItems": 3,
            "description": "Hasta 3 convocatorias abiertas más relevantes para la actividad.",
            "items": {
              "type": "object",
              "properties": {
                "codigo_bdns": { "type": "string", "example": "800123" },
                "titulo": { "type": "string", "example": "Ayudas a la digitalización de pymes" },
                "organo": { "type": "string", "nullable": true, "example": "Red.es" },
                "importe": { "type": "number", "nullable": true, "example": 250000, "description": "Dotación de la convocatoria (no por beneficiario)." },
                "url_oficial": { "type": "string", "nullable": true, "format": "uri" }
              }
            }
          },
          "fuente": { "type": "string", "example": "BDNS / SNPSAP (IGAE)" }
        }
      },
      "DgtSearchResponse": {
        "type": "object",
        "description": "Resultado de la búsqueda de consultas vinculantes de la DGT. La forma de cada elemento de `consultas` depende del plan — ver DgtDiscoveryItem (Free) y DgtSearchFullItem (Profesional+).",
        "properties": {
          "query": { "type": "string", "example": "arrendamiento de inmuebles", "description": "Texto de búsqueda enviado." },
          "consultas": {
            "type": "array",
            "items": {
              "oneOf": [
                { "$ref": "#/components/schemas/DgtDiscoveryItem" },
                { "$ref": "#/components/schemas/DgtSearchFullItem" }
              ]
            }
          },
          "total": { "type": "integer", "example": 3, "description": "Número de filas devueltas en esta página." },
          "_meta": { "$ref": "#/components/schemas/DgtSearchMeta" }
        }
      },
      "DgtSearchMeta": {
        "allOf": [
          { "$ref": "#/components/schemas/Meta" },
          {
            "type": "object",
            "properties": {
              "count": { "type": "integer", "example": 3 },
              "note": { "type": "string", "nullable": true, "description": "Presente solo en plan Free — explica el tope de 3 filas y que el filtro por código y la paginación requieren plan Profesional o superior." },
              "offset": { "type": "integer", "nullable": true, "description": "Presente solo en plan Profesional o superior — el offset efectivamente aplicado." }
            }
          }
        ]
      },
      "DgtDiscoveryItem": {
        "type": "object",
        "description": "Proyección de campos de descubrimiento (plan Free) — máx. 3 filas por respuesta.",
        "properties": {
          "titulo_seo": { "type": "string", "nullable": true, "example": "Tributación del arrendamiento de inmuebles por autónomos" },
          "resumen": { "type": "string", "nullable": true, "example": "La DGT concluye que el arrendamiento de inmuebles tributa por el epígrafe 861 del IAE cuando..." }
        }
      },
      "DgtSearchFullItem": {
        "type": "object",
        "description": "Proyección completa (plan Profesional o superior).",
        "properties": {
          "numero": { "type": "string", "example": "V1234-23" },
          "slug": { "type": "string", "example": "v1234-23-arrendamiento-inmuebles" },
          "titulo_seo": { "type": "string", "nullable": true, "example": "Tributación del arrendamiento de inmuebles por autónomos" },
          "resumen": { "type": "string", "nullable": true, "example": "La DGT concluye que el arrendamiento de inmuebles tributa por el epígrafe 861 del IAE cuando..." },
          "fecha": { "type": "string", "nullable": true, "example": "2023-05-12" },
          "organo": { "type": "string", "nullable": true, "example": "SG de Impuestos sobre la Renta de las Personas Físicas" },
          "rank": { "type": "number", "example": 0.87, "description": "Relevancia de la búsqueda de texto completo (mayor es más relevante)." }
        }
      },
      "DgtDetailResponse": {
        "type": "object",
        "description": "Detalle de una consulta DGT. Con plan Free solo se devuelven los campos de DgtDetailFreeItem (identidad + resumen); con plan Profesional o superior se añade el texto íntegro (hechos, cuestión y contestación vinculante).",
        "properties": {
          "numero": { "type": "string", "example": "V1234-23" },
          "slug": { "type": "string", "example": "v1234-23-arrendamiento-inmuebles" },
          "tipo": { "type": "string", "enum": ["vinculante", "general"], "example": "vinculante" },
          "fecha": { "type": "string", "nullable": true, "example": "2023-05-12" },
          "organo": { "type": "string", "nullable": true, "example": "SG de Impuestos sobre la Renta de las Personas Físicas" },
          "titulo_seo": { "type": "string", "nullable": true, "example": "Tributación del arrendamiento de inmuebles por autónomos" },
          "resumen": { "type": "string", "nullable": true, "example": "La DGT concluye que el arrendamiento de inmuebles tributa por el epígrafe 861 del IAE cuando..." },
          "normativa": { "type": "string", "nullable": true, "example": "Art. 78 y 79 RDLeg 1175/1990" },
          "hechos": { "type": "string", "nullable": true, "description": "Solo plan Profesional o superior. Antecedentes de hecho planteados en la consulta." },
          "cuestion": { "type": "string", "nullable": true, "description": "Solo plan Profesional o superior. Cuestión planteada a la DGT." },
          "contestacion": { "type": "string", "description": "Solo plan Profesional o superior. Contestación íntegra de la DGT — la conclusión vinculante se sitúa al FINAL del texto, no al principio." },
          "normas_citadas": { "type": "array", "items": { "type": "string" }, "description": "Solo plan Profesional o superior. Normas citadas en la contestación." },
          "codes_iae": { "type": "array", "items": { "type": "string" }, "description": "Solo plan Profesional o superior. Epígrafes IAE relacionados con la consulta." },
          "_meta": { "$ref": "#/components/schemas/DgtDetailMeta" }
        }
      },
      "DgtDetailMeta": {
        "allOf": [
          { "$ref": "#/components/schemas/Meta" },
          {
            "type": "object",
            "properties": {
              "note": { "type": "string", "nullable": true, "description": "Presente solo en plan Free — explica que el texto íntegro requiere plan Profesional o superior." }
            }
          }
        ]
      },
      "ObligacionesResponse": {
        "type": "object",
        "properties": {
          "code": { "type": "string", "example": "5013" },
          "type": { "type": "string", "enum": ["iae", "cnae"], "example": "iae" },
          "obligaciones": {
            "type": "array",
            "items": { "$ref": "#/components/schemas/Obligacion" }
          },
          "total": { "type": "integer", "example": 4 },
          "_meta": { "$ref": "#/components/schemas/Meta" }
        }
      },
      "Obligacion": {
        "type": "object",
        "description": "Una obligación fiscal (modelo AEAT) aplicable al código.",
        "properties": {
          "modelo_id": { "type": "string", "example": "303" },
          "name": { "type": "string", "nullable": true, "example": "IVA — Autoliquidación trimestral" },
          "periodicidad": { "type": "string", "nullable": true, "example": "trimestral" },
          "confidence": { "type": "number", "nullable": true, "example": 0.95 },
          "reason": { "type": "string", "nullable": true, "example": "Actividad sujeta a IVA" },
          "fecha_proximo_plazo": { "type": "string", "nullable": true, "example": "2026-07-20", "description": "Fecha nominal del calendario AEAT (sin prórroga de días inhábiles aplicada)." }
        }
      },
      "BulkRequest": {
        "type": "object",
        "required": ["codes", "type"],
        "properties": {
          "codes": {
            "type": "array",
            "description": "Lista de códigos (1–50). Cada uno solo dígitos y puntos, máx. 20 caracteres.",
            "minItems": 1,
            "maxItems": 50,
            "items": { "type": "string", "pattern": "^[\\d.]+$", "maxLength": 20 },
            "example": ["501.3", "504.1", "505.4"]
          },
          "type": { "type": "string", "enum": ["iae", "cnae"], "example": "iae" }
        }
      },
      "BulkResponse": {
        "type": "object",
        "properties": {
          "type": { "type": "string", "enum": ["iae", "cnae"], "example": "iae" },
          "results": {
            "type": "array",
            "items": { "$ref": "#/components/schemas/BulkResult" }
          },
          "total": { "type": "integer", "example": 3 },
          "found": { "type": "integer", "example": 2 },
          "_meta": { "$ref": "#/components/schemas/Meta" }
        }
      },
      "BulkResult": {
        "type": "object",
        "required": ["code", "found"],
        "properties": {
          "code": { "type": "string", "example": "501.3" },
          "found": { "type": "boolean", "example": true },
          "data": {
            "type": "object",
            "nullable": true,
            "additionalProperties": true,
            "description": "Fila completa del catálogo cuando `found` es true; ausente cuando es false. Para `type=cnae`, el campo `iae_correspondiente` contiene códigos IAE cualificados por sección (`cod_integro_iae`, p. ej. `\"A011\"`), no códigos numéricos simples — esto desambigua los epígrafes que comparten número entre secciones A/B/C. Para reutilizar uno en `GET /iae/{code}`, quita la letra de sección inicial (`\"A011\"` → `\"011\"`). Para `type=iae`, el campo `relaciones_iae` contiene códigos IAE relacionados cualificados por sección (`cod_integro_iae`, p. ej. `\"A011\"`), no códigos numéricos simples — el mismo criterio de desambiguación. Para reutilizar uno en `GET /iae/{code}`, quita igualmente la letra de sección inicial (`\"A011\"` → `\"011\"`)."
          }
        }
      },
      "SignupRequest": {
        "type": "object",
        "required": ["email", "name", "terms_accepted"],
        "properties": {
          "email": { "type": "string", "format": "email", "maxLength": 254, "example": "dev@ejemplo.es" },
          "name": { "type": "string", "minLength": 1, "maxLength": 100, "example": "Ana García" },
          "terms_accepted": { "type": "boolean", "enum": [true], "example": true, "description": "Debe ser `true`; aceptación de los términos de uso." }
        }
      },
      "SignupResponse": {
        "type": "object",
        "properties": {
          "success": { "type": "boolean", "example": true },
          "message": { "type": "string", "example": "Te hemos enviado tu API key por email. Revisa tu correo (y la carpeta de spam si no la ves en unos minutos). Te enviaremos emails sobre tu clave y, si te corresponde, tu prueba de 14 días. Puedes darte de baja en cualquier momento desde cualquier email.", "description": "La API key se entrega únicamente por email; la respuesta nunca la incluye." }
        }
      },
      "SubscribeRequest": {
        "type": "object",
        "required": ["email", "name", "plan"],
        "properties": {
          "email": { "type": "string", "format": "email", "maxLength": 254, "example": "dev@ejemplo.es" },
          "name": { "type": "string", "minLength": 1, "maxLength": 100, "example": "Ana García" },
          "plan": { "type": "string", "enum": ["profesional", "empresa"], "example": "profesional" },
          "billing_cycle": { "type": "string", "enum": ["monthly", "annual"], "default": "monthly", "example": "monthly" },
          "utm_source": { "type": "string", "minLength": 1, "maxLength": 64, "pattern": "^[a-z0-9_-]+$", "example": "landing_api" },
          "email_lead_id": { "type": "string", "format": "uuid" }
        }
      },
      "SubscribeResponse": {
        "type": "object",
        "properties": {
          "success": { "type": "boolean", "example": true },
          "url": { "type": "string", "format": "uri", "description": "URL de Stripe Checkout.", "example": "https://checkout.stripe.com/c/pay/cs_test_..." }
        }
      },
      "UsageResponse": {
        "type": "object",
        "properties": {
          "success": { "type": "boolean", "example": true },
          "data": {
            "type": "object",
            "properties": {
              "plan": { "type": "string", "example": "free" },
              "requests_used_today": { "type": "integer", "example": 12 },
              "rate_limit_per_day": { "type": "integer", "example": 250 },
              "remaining_today": { "type": "integer", "example": 238 },
              "is_active": { "type": "boolean", "example": true },
              "created_at": { "type": "string", "format": "date-time", "example": "2026-05-01T10:00:00Z" },
              "last_used_at": { "type": "string", "format": "date-time", "nullable": true, "example": "2026-06-02T09:15:00Z" },
              "top_endpoints": {
                "type": "array",
                "items": {
                  "type": "object",
                  "properties": {
                    "endpoint": { "type": "string", "example": "/api/v1/search" },
                    "count": { "type": "integer", "example": 8 }
                  }
                }
              }
            }
          }
        }
      },
      "KeysRequest": {
        "type": "object",
        "required": ["action"],
        "properties": {
          "action": { "type": "string", "enum": ["rotate", "revoke"], "example": "rotate", "description": "Acción a realizar: rotate (genera una clave nueva) o revoke (desactiva la clave actual)." }
        }
      },
      "KeysResponse": {
        "type": "object",
        "properties": {
          "success": { "type": "boolean", "example": true },
          "api_key": { "type": "string", "example": "cvr_z9y8x7w6v5u4t3s2r1q0p9o8n7m6l5k4", "description": "Presente solo cuando action=rotate. Clave nueva completa, se devuelve una única vez." },
          "message": { "type": "string", "example": "Clave rotada. Guárdala: no se vuelve a mostrar." }
        }
      },
      "ActivityLookupRequest": {
        "type": "object",
        "description": "Cuerpo de petición compartido por `/kyb/enrich` y `/onboarding/activity`. Envía un epígrafe IAE via `code` con `code_type:\"iae\"` (o sin `code_type`, ya que `\"iae\"` es el valor por defecto) O describe la actividad con `activity_text` (2–200 caracteres). Nunca envíes `code` y `activity_text` a la vez (devuelve 400). La búsqueda directa por código CNAE (`code_type:\"cnae\"`) no está soportada en estos endpoints — devuelve 400; usa el epígrafe IAE correspondiente o `activity_text`.",
        "properties": {
          "code": {
            "type": "string",
            "pattern": "^[\\dA-Za-z.]+$",
            "maxLength": 20,
            "example": "5013",
            "description": "Epígrafe IAE con o sin puntos (solo alfanuméricos y puntos, máx. 20 caracteres). Mutuamente excluyente con `activity_text`. Úsalo siempre junto a `code_type:\"iae\"` (o sin `code_type`); `code_type:\"cnae\"` devuelve 400 en estos endpoints."
          },
          "code_type": {
            "type": "string",
            "enum": ["iae", "cnae"],
            "example": "iae",
            "description": "Tipo de catálogo del código. Opcional; por defecto `\"iae\"`. Solo se admite `\"iae\"` en estos endpoints — enviar `\"cnae\"` devuelve 400. Para buscar por actividad sin conocer el epígrafe IAE, usa `activity_text` en su lugar."
          },
          "activity_text": {
            "type": "string",
            "minLength": 2,
            "maxLength": 200,
            "example": "reformas de albañilería",
            "description": "Descripción libre de la actividad económica (2–200 caracteres). Mutuamente excluyente con `code`."
          }
        }
      },
      "KybEnrichRequest": {
        "description": "Cuerpo de petición de `/kyb/enrich`: extiende `ActivityLookupRequest` con `company_name`/`provincia` (BORME registro fusion) y `nif` (validación VIES/ROI + contraste con la publicación oficial de deudores, art. 95 bis LGT). `company_name` y `nif` ENRIQUECEN una actividad ya identificada con `code` o `activity_text` — enviarlos en solitario devuelve 400 (este endpoint no es una búsqueda de empresas ni una consulta de NIF).",
        "allOf": [
          { "$ref": "#/components/schemas/ActivityLookupRequest" },
          {
            "type": "object",
            "properties": {
              "company_name": {
                "type": "string",
                "minLength": 2,
                "maxLength": 200,
                "example": "Ejemplo Digital, S.L.",
                "description": "Denominación social de la empresa a enriquecer con datos de constitución BORME. Debe enviarse junto con `code` o `activity_text`; en solitario devuelve 400."
              },
              "provincia": {
                "type": "string",
                "minLength": 2,
                "maxLength": 100,
                "example": "Madrid",
                "description": "Provincia de la empresa, para desambiguar homónimos en la búsqueda BORME. Opcional, solo válido junto a `company_name`."
              },
              "nif": {
                "type": "string",
                "minLength": 2,
                "maxLength": 20,
                "example": "B12345678",
                "description": "NIF/CIF español a validar en el censo VIES (IVA intracomunitario) de la Comisión Europea y a contrastar con la publicación oficial de deudores (art. 95 bis LGT) como señal dentro del expediente de diligencia debida. Debe enviarse junto con `code` o `activity_text`; en solitario devuelve 400. Un formato inválido (con o sin prefijo `ES`) también devuelve 400."
              }
            }
          }
        ]
      },
      "ViesResult": {
        "type": "object",
        "description": "Resultado de la validación VIES (Comisión Europea) del NIF/CIF enviado en `nif`. Dato meramente informativo, no constituye asesoramiento fiscal.",
        "properties": {
          "consultado": { "type": "boolean", "example": true, "description": "false cuando VIES no respondió (timeout, error de red o payload inesperado) — el resto del dossier se devuelve igualmente." },
          "valido": { "type": "boolean", "example": true, "description": "Presente solo cuando consultado=true. Indica si el NIF/CIF está registrado como operador intracomunitario válido en VIES." },
          "fecha_consulta": { "type": "string", "example": "2026-07-23T10:00:00.000Z", "description": "Presente solo cuando consultado=true. Fecha/hora de la consulta a VIES (ISO 8601)." },
          "nota": { "type": "string", "example": "Dato meramente informativo (VIES, Comisión Europea).", "description": "Presente solo cuando consultado=true." },
          "error": { "type": "string", "enum": ["no disponible"], "description": "Presente solo cuando consultado=false." }
        }
      },
      "DeudoresResult": {
        "type": "object",
        "description": "Contraste del NIF/CIF enviado en `nif` con la publicación oficial de deudores a la Hacienda Pública (art. 95 bis LGT), como señal dentro del expediente de diligencia debida — nunca un listado independiente. Art. 95 bis.4 LGT: la publicación oficial no debe indexarse ni republicarse tal cual.",
        "properties": {
          "comprobado": { "type": "boolean", "example": true, "description": "false cuando la consulta a AEAT no respondió (timeout, error de red o payload inesperado) — el resto del dossier se devuelve igualmente." },
          "aparece": { "type": "boolean", "example": false, "description": "Presente solo cuando comprobado=true. Indica si el NIF/CIF figura en la publicación vigente de deudores." },
          "importe_eur": { "type": "number", "example": 750000, "description": "Presente solo cuando comprobado=true y aparece=true. Importe publicado en la lista oficial de deudores (art. 95 bis LGT)." },
          "edicion": { "type": "string", "example": "13ª (30-jun-2026)", "description": "Presente solo cuando comprobado=true. Edición de la publicación consultada." },
          "fuente": { "type": "string", "enum": ["AEAT art. 95 bis LGT"], "description": "Presente solo cuando comprobado=true." }
        }
      },
      "BormeRegistroBlock": {
        "type": "object",
        "description": "Datos de constitución de empresa (BORME) fusionados en el dossier cuando se envía `company_name` y hay coincidencia. Cobertura: constituciones desde 2026-01 (no es el Registro Mercantil completo).",
        "properties": {
          "denominacion": { "type": "string", "example": "Ejemplo Digital, S.L.", "description": "Denominación social tal como consta en BORME." },
          "provincia": { "type": "string", "nullable": true, "example": "Madrid" },
          "capital_eur": { "type": "number", "nullable": true, "example": 3000 },
          "objeto_social": { "type": "string", "nullable": true, "example": "Desarrollo de software a medida" },
          "cnae_registral": { "type": "string", "nullable": true, "example": "62.01", "description": "CNAE declarado en la constitución (puede diferir del CNAE clasificado por actividad)." },
          "cnae_confidence": { "type": "number", "nullable": true, "example": 0.91 },
          "fecha_constitucion": { "type": "string", "nullable": true, "example": "2026-03-10", "description": "Fecha de publicación en BORME del acto de constitución (proxy — BORME no expone una fecha de constitución independiente)." },
          "match_quality": { "type": "string", "enum": ["exact", "multiple", "fuzzy"], "example": "exact", "description": "`exact`: única coincidencia tras normalizar la denominación. `multiple`: varias empresas homónimas — se devuelve la constitución más reciente. `fuzzy`: coincidencia aproximada (reservado, no emitido actualmente)." },
          "fuente": { "type": "string", "enum": ["BORME"], "example": "BORME" },
          "cobertura": { "type": "string", "enum": ["constituciones desde 2026-01"], "example": "constituciones desde 2026-01" }
        }
      },
      "EstadoRegistral": {
        "type": "object",
        "description": "Ciclo de vida de la empresa en el Registro Mercantil (BORME) y señales de drift, fusionado en el dossier cuando se envía `company_name` y hay coincidencia en el histórico de actos BORME (vía normativa-api, distinto de `registro`, que lee la constitución local). Cobertura: actos BORME desde 2026-01 (cobertura parcial del historial registral, no el Registro Mercantil completo).",
        "properties": {
          "estado": { "type": "string", "enum": ["activa", "extinguida", "disuelta", "en_concurso"], "example": "activa", "description": "Precedencia: concurso > extincion > disolucion > activa (por defecto cuando ninguno de los anteriores aplica)." },
          "ultimo_acto": { "type": "string", "example": "cambio_domicilio", "description": "Tipo del acto BORME más reciente por fecha." },
          "fecha_ultimo_acto": { "type": "string", "example": "2026-06-02", "description": "Fecha del acto más reciente." },
          "senales_drift": {
            "type": "array",
            "description": "Actos que suponen una divergencia entre la actividad/datos declarados y los registrados — señal de re-verificación (art. 10.2 RD 304/2014).",
            "items": {
              "type": "object",
              "properties": {
                "tipo": { "type": "string", "enum": ["cambio_objeto_social", "cambio_denominacion", "cambio_domicilio"], "example": "cambio_domicilio" },
                "fecha": { "type": "string", "example": "2026-06-02" }
              }
            }
          },
          "hoja": { "type": "string", "example": "M-123456", "description": "Hoja registral BORME de la empresa emparejada." },
          "match_quality": { "type": "string", "enum": ["exact", "multiple"], "example": "exact", "description": "`exact`: única coincidencia tras normalizar la denominación (con desambiguación por provincia si hacía falta). `multiple`: varias empresas homónimas sin provincia que las distinga — se devuelve el acto más reciente." },
          "fuente": { "type": "string", "enum": ["BORME (Registro Mercantil)"], "example": "BORME (Registro Mercantil)" },
          "cobertura": { "type": "string", "example": "actos BORME desde 2026-01 (cobertura parcial del historial registral)" }
        }
      },
      "RiesgoResumen": {
        "type": "object",
        "description": "Semáforo de riesgo que agrega estado_registral, vies, deudores_aeat y el sector de riesgo fiscal. Se incluye siempre que la petición envíe `company_name` o `nif` (aunque los bloques subyacentes no encuentren coincidencia) — un resultado limpio se expresa como `nivel:\"verde\"` explícito, nunca omitido.",
        "properties": {
          "nivel": { "type": "string", "enum": ["verde", "ambar", "rojo"], "example": "ambar", "description": "rojo: estado extinguida/en_concurso, deudores_aeat.aparece, o vies.valido=false. ambar: estado disuelta, cualquier señal de drift, o sector_riesgo=\"alto\". verde: ninguno de los anteriores." },
          "motivos": {
            "type": "array",
            "items": { "type": "string" },
            "example": ["Se han detectado 1 cambio(s) registral(es) (objeto social, denominación o domicilio) — señal de re-verificación (art. 10.2 RD 304/2014)."],
            "description": "Explicación en español de cada señal que activó el nivel — vacío cuando nivel=\"verde\"."
          },
          "senales_evaluadas": { "type": "integer", "example": 4, "description": "Cuántos de los 4 bloques de entrada (estado_registral, vies, deudores_aeat, sector_riesgo) estaban presentes en la petición — no cuántos dispararon una alerta." }
        }
      },
      "ActivityMatch": {
        "type": "object",
        "description": "Resultado de la resolución del código o actividad.",
        "properties": {
          "confidence": { "type": "number", "nullable": true, "example": 0.97, "description": "Confianza del match (0.0–1.0). null para lexical/no-match." },
          "method": { "type": "string", "enum": ["exact_code", "semantic", "lexical"], "example": "semantic", "description": "Método de resolución: `exact_code` (código exacto), `semantic` (búsqueda semántica) o `lexical` (búsqueda léxica sin embedding)." },
          "alternatives": {
            "type": "array",
            "description": "Alternativas de menor confianza cuando el match semántico es ambiguo.",
            "items": {
              "type": "object",
              "properties": {
                "code": { "type": "string", "example": "504.1" },
                "titulo": { "type": "string", "example": "Instalaciones eléctricas en general" },
                "type": { "type": "string", "enum": ["iae", "cnae"], "example": "iae" },
                "score": { "type": "number", "nullable": true, "example": 0.71 }
              }
            }
          }
        }
      },
      "ActivityClassification": {
        "type": "object",
        "description": "Clasificación IAE y CNAE 2025 del match.",
        "properties": {
          "iae": {
            "type": "object",
            "nullable": true,
            "properties": {
              "code": { "type": "string", "example": "501.3", "description": "Código con puntos." },
              "codeNormalized": { "type": "string", "example": "5013", "description": "Código sin puntos (normalizado)." },
              "titulo": { "type": "string", "example": "Albañilería y pequeños trabajos de construcción" }
            }
          },
          "cnae2025": {
            "type": "object",
            "nullable": true,
            "properties": {
              "code": { "type": "string", "example": "43.99", "description": "Código con puntos." },
              "codeNormalized": { "type": "string", "example": "4399", "description": "Código sin puntos (normalizado)." },
              "titulo": { "type": "string", "example": "Otras actividades de construcción especializada" }
            }
          }
        }
      },
      "KybEnrichResponse": {
        "type": "object",
        "description": "Respuesta del endpoint `/kyb/enrich`.",
        "properties": {
          "match": { "$ref": "#/components/schemas/ActivityMatch" },
          "classification": { "$ref": "#/components/schemas/ActivityClassification" },
          "fiscal_profile": {
            "type": "object",
            "description": "Perfil fiscal de la actividad para evaluación de riesgo KYB.",
            "properties": {
              "sector_riesgo": { "type": "string", "example": "medio", "description": "Nivel de riesgo fiscal del sector (bajo, medio, alto)." },
              "iva_regimen": { "type": "string", "example": "general", "description": "Régimen de IVA aplicable." },
              "irpf_regimen": { "type": "string", "example": "estimacion_directa", "description": "Régimen de IRPF aplicable." },
              "retencion_irpf_pct": { "type": "number", "nullable": true, "example": 15, "description": "Porcentaje de retención IRPF aplicable." },
              "modelo_036_casillas_relevantes": {
                "type": "array",
                "items": { "type": "string" },
                "example": ["601", "602"],
                "description": "Casillas del Modelo 036 relevantes para esta actividad."
              }
            }
          },
          "obligaciones": {
            "type": "array",
            "description": "Obligaciones fiscales (modelos AEAT) aplicables.",
            "items": { "$ref": "#/components/schemas/Obligacion" }
          },
          "subvenciones": { "$ref": "#/components/schemas/ActivitySubvencionesBlock" },
          "registro": {
            "allOf": [{ "$ref": "#/components/schemas/BormeRegistroBlock" }],
            "description": "Presente SOLO cuando se envía `company_name` en la petición y hay coincidencia BORME. Se omite por completo (no aparece la clave) cuando no se envía `company_name` o no hay match — nunca se emite como `null`."
          },
          "vies": {
            "allOf": [{ "$ref": "#/components/schemas/ViesResult" }],
            "description": "Presente SOLO cuando se envía `nif` en la petición (con `code` o `activity_text`). Se omite por completo (no aparece la clave) cuando no se envía `nif` — nunca se emite como `null`. Un fallo de VIES nunca hace fallar el resto del dossier: el bloque se incluye igualmente con `consultado:false`."
          },
          "deudores_aeat": {
            "allOf": [{ "$ref": "#/components/schemas/DeudoresResult" }],
            "description": "Presente SOLO cuando se envía `nif` en la petición (con `code` o `activity_text`). Se omite por completo (no aparece la clave) cuando no se envía `nif` — nunca se emite como `null`. Un fallo de la consulta de deudores nunca hace fallar el resto del dossier: el bloque se incluye igualmente con `comprobado:false`. Señal dentro del expediente de diligencia debida, nunca un listado independiente."
          },
          "estado_registral": {
            "allOf": [{ "$ref": "#/components/schemas/EstadoRegistral" }],
            "description": "Presente SOLO cuando se envía `company_name` en la petición (con `code` o `activity_text`) y hay coincidencia en el histórico de actos BORME (normativa-api). Se omite por completo (no aparece la clave) cuando no se envía `company_name`, la consulta upstream falla, o el nombre es ambiguo sin `provincia` — nunca se emite como `null`."
          },
          "riesgo_resumen": {
            "allOf": [{ "$ref": "#/components/schemas/RiesgoResumen" }],
            "description": "Presente siempre que la petición envíe `company_name` o `nif` (junto a `code` o `activity_text`) — agrega estado_registral, vies, deudores_aeat y sector_riesgo. Se omite por completo cuando la petición no envía ninguna de esas dos señales (solo `code`/`activity_text`)."
          },
          "audit": {
            "type": "object",
            "description": "Metadatos de auditoría y trazabilidad de la respuesta.",
            "properties": {
              "source": { "type": "string", "example": "conversoriaecnae.es", "description": "Identificador de la fuente de los datos." },
              "sources_oficiales": {
                "type": "array",
                "items": { "type": "string" },
                "example": ["AEAT", "BOE", "INE"],
                "description": "Fuentes oficiales que respaldan la clasificación. Incluye 'BORME' cuando la respuesta trae un bloque 'registro' o un bloque 'estado_registral' (una sola vez aunque traiga ambos), 'VIES' cuando trae un bloque 'vies' con consultado:true, y 'AEAT (art. 95 bis)' cuando trae un bloque 'deudores_aeat' con comprobado:true."
              },
              "data_version": { "type": "string", "nullable": true, "example": "2026-06-01T10:00:00Z", "description": "Versión del catálogo usado (ISO timestamp)." },
              "generated_at": { "type": "string", "format": "date-time", "example": "2026-06-12T10:00:00Z" },
              "data_residency": { "type": "string", "example": "CH (eu-central-2, Zürich; EU adequacy decision)", "description": "Región de residencia de los datos (RGPD)." }
            }
          },
          "_meta": { "$ref": "#/components/schemas/Meta" }
        }
      },
      "OnboardingActivityResponse": {
        "type": "object",
        "description": "Respuesta del endpoint `/onboarding/activity`.",
        "properties": {
          "match": { "$ref": "#/components/schemas/ActivityMatch" },
          "classification": { "$ref": "#/components/schemas/ActivityClassification" },
          "alta_036": {
            "type": "object",
            "description": "Datos para pre-rellenar el Modelo 036 de alta.",
            "properties": {
              "epigrafe_iae": { "type": "string", "nullable": true, "example": "501.3", "description": "Epígrafe IAE a consignar en el Modelo 036." },
              "casillas_relevantes": {
                "type": "array",
                "items": { "type": "string" },
                "example": ["601", "602"],
                "description": "Casillas del Modelo 036 relevantes para esta alta."
              }
            }
          },
          "facturacion": {
            "type": "object",
            "description": "Configuración de facturación para pre-rellenar el SaaS.",
            "properties": {
              "iva_tipo": { "oneOf": [{ "type": "number" }, { "type": "string", "enum": ["exento"] }], "nullable": true, "example": 21, "description": "Tipo de IVA aplicable (número, p. ej. 21, o 'exento')." },
              "iva_regimen": { "type": "string", "nullable": true, "example": "general", "description": "Régimen de IVA aplicable." },
              "retencion_irpf": {
                "type": "object",
                "properties": {
                  "tipo": { "type": "number", "nullable": true, "example": 15, "description": "Tipo de retención IRPF general (número, p. ej. 15)." },
                  "tipo_inicio_actividad": { "type": "number", "nullable": true, "example": 7, "description": "Tipo reducido para los 3 primeros años de actividad (p. ej. 7). Null cuando no aplica." }
                }
              },
              "verified": { "type": "boolean", "example": true, "description": "Si la configuración está verificada contra fuentes oficiales." }
            }
          },
          "regimen_recomendado": {
            "type": "object",
            "nullable": true,
            "description": "Régimen fiscal recomendado para esta actividad. Null si no se pudo determinar.",
            "properties": {
              "modulos_elegible": { "type": "boolean", "example": false },
              "modulos_summary": { "type": "string", "nullable": true, "example": "No apto para módulos" },
              "calculo_hints": { "nullable": true, "description": "Hints de cálculo (estructura variable)." },
              "verified": { "type": "boolean", "example": true }
            }
          },
          "calendario": {
            "type": "array",
            "description": "Próximos plazos fiscales aplicables (misma estructura que Obligacion).",
            "items": { "$ref": "#/components/schemas/Obligacion" }
          },
          "subvenciones": { "$ref": "#/components/schemas/ActivitySubvencionesBlock" },
          "reta": {
            "type": "object",
            "description": "Información orientativa sobre la cuota RETA.",
            "properties": {
              "tarifa_plana_eur_mes": { "type": "number", "nullable": true, "example": 80, "description": "Cuota de tarifa plana (€/mes). Null si no aplica." },
              "base_minima_aplica": { "type": "boolean", "example": true, "description": "Si se puede optar por la base mínima RETA." },
              "nota": { "type": "string", "example": "Cuota de partida (tarifa plana): punto de partida del alta. La cuota definitiva depende del rendimiento neto anual.", "description": "Nota informativa sobre la cuota RETA." },
              "mei_eur_mes": { "type": "number", "nullable": true, "example": null, "description": "Cuota MEI mensual (€) cuando aplica." },
              "total_primer_periodo_eur_mes": { "type": "number", "nullable": true, "example": null, "description": "Total primer periodo RETA (€/mes) cuando aplica." },
              "source_anchor": { "type": "string", "nullable": true, "example": null, "description": "Referencia normativa de la tarifa plana." }
            }
          },
          "_meta": { "$ref": "#/components/schemas/Meta" }
        }
      }
    }
  }
}
