{
  "openapi": "3.1.0",
  "info": {
    "title": "Openpay Paynet Water Payments API",
    "summary": "API multitenant para generar referencias Paynet, procesar layouts masivos, recibir webhooks, enviar callbacks y conciliar pagos.",
    "description": "Plataforma API para integrar sistemas comerciales de cobro de agua potable con Openpay Paynet. Incluye idempotencia, auditoria, webhooks, callbacks firmados, conciliacion automatica y procesamiento masivo JSON/CSV/XLSX.",
    "version": "1.0.0",
    "contact": {
      "name": "Equipo de Integraciones",
      "email": "integraciones@example.com"
    }
  },
  "jsonSchemaDialect": "https://json-schema.org/draft/2020-12/schema",
  "servers": [
    {
      "url": "https://api.example.com",
      "description": "Produccion"
    },
    {
      "url": "https://sandbox-api.example.com",
      "description": "Sandbox"
    },
    {
      "url": "http://localhost:3000",
      "description": "Local"
    }
  ],
  "tags": [
    {
      "name": "Auth",
      "description": "Autenticacion, API keys y permisos."
    },
    {
      "name": "Payment References",
      "description": "Generacion y consulta de referencias Openpay Paynet."
    },
    {
      "name": "Batches",
      "description": "Layouts masivos JSON, CSV y XLSX."
    },
    {
      "name": "Webhooks",
      "description": "Recepcion y reproceso de eventos Openpay."
    },
    {
      "name": "Callbacks",
      "description": "Entrega y reintento de callbacks hacia sistemas externos."
    },
    {
      "name": "Reconciliation",
      "description": "Conciliacion manual, masiva y automatica contra Openpay."
    },
    {
      "name": "Events",
      "description": "Eventos, auditoria y trazabilidad."
    },
    {
      "name": "Operations",
      "description": "Dashboard operativo, health y version."
    }
  ],
  "security": [
    {
      "ApiKeyAuth": []
    },
    {
      "BearerAuth": []
    }
  ],
  "paths": {
    "/api/v1/health": {
      "get": {
        "tags": ["Operations"],
        "summary": "Estado de salud",
        "security": [],
        "responses": {
          "200": {
            "description": "Servicio disponible",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/HealthResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/version": {
      "get": {
        "tags": ["Operations"],
        "summary": "Version de API",
        "security": [],
        "responses": {
          "200": {
            "description": "Version actual",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/VersionResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/auth/token": {
      "post": {
        "tags": ["Auth"],
        "summary": "Emite Bearer Token administrativo",
        "security": [],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/AuthTokenRequest"
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Token emitido",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AuthTokenResponse"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          }
        }
      }
    },
    "/api/v1/payment-references": {
      "post": {
        "tags": ["Payment References"],
        "summary": "Genera una referencia Openpay Paynet",
        "description": "Crea un cargo Openpay con `method=store` y devuelve datos listos para imprimir en un recibo de agua potable. Requiere `Idempotency-Key` para evitar duplicados.",
        "operationId": "create_payment_reference",
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          },
          {
            "$ref": "#/components/parameters/ClientRequestId"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreatePaymentReferenceRequest"
              },
              "examples": {
                "water_receipt": {
                  "$ref": "#/components/examples/CreatePaymentReference"
                }
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Referencia creada",
            "headers": {
              "X-RateLimit-Limit": {
                "$ref": "#/components/headers/RateLimitLimit"
              },
              "X-RateLimit-Remaining": {
                "$ref": "#/components/headers/RateLimitRemaining"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PaymentReferenceResponse"
                }
              }
            }
          },
          "200": {
            "description": "Respuesta idempotente; la referencia ya existia",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PaymentReferenceResponse"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/ValidationError"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "403": {
            "$ref": "#/components/responses/Forbidden"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          },
          "502": {
            "$ref": "#/components/responses/OpenpayError"
          }
        }
      }
    },
    "/api/v1/payment-references/batches": {
      "post": {
        "tags": ["Batches"],
        "summary": "Crea un lote masivo de referencias desde JSON",
        "operationId": "create_payment_reference_batch",
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/CreateBatchRequest"
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "Lote aceptado para procesamiento asincrono",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/BatchResponse"
                }
              }
            }
          },
          "207": {
            "description": "Procesamiento parcial",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/BatchResponse"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/ValidationError"
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "409": {
            "$ref": "#/components/responses/Conflict"
          },
          "429": {
            "$ref": "#/components/responses/RateLimited"
          }
        }
      }
    },
    "/api/v1/payment-references/batches/upload": {
      "post": {
        "tags": ["Batches"],
        "summary": "Carga layout masivo CSV o XLSX",
        "operationId": "upload_payment_reference_batch",
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "multipart/form-data": {
              "schema": {
                "$ref": "#/components/schemas/BatchUploadRequest"
              },
              "encoding": {
                "file": {
                  "contentType": "text/csv, application/vnd.openxmlformats-officedocument.spreadsheetml.sheet"
                }
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "Archivo aceptado",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/BatchResponse"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/ValidationError"
          },
          "415": {
            "description": "Formato no soportado",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ErrorResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/payment-references/{id}": {
      "get": {
        "tags": ["Payment References"],
        "summary": "Consulta una referencia por ID interno",
        "operationId": "get_payment_reference",
        "parameters": [
          {
            "$ref": "#/components/parameters/ReferenceId"
          }
        ],
        "responses": {
          "200": {
            "description": "Referencia encontrada",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PaymentReferenceResponse"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/Unauthorized"
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/api/v1/payment-references/by-external/{external_reference}": {
      "get": {
        "tags": ["Payment References"],
        "summary": "Consulta una referencia por folio externo",
        "operationId": "get_payment_reference_by_external",
        "parameters": [
          {
            "$ref": "#/components/parameters/ExternalReference"
          }
        ],
        "responses": {
          "200": {
            "description": "Referencia encontrada",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PaymentReferenceResponse"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/api/v1/payment-references/batches/{batch_id}": {
      "get": {
        "tags": ["Batches"],
        "summary": "Consulta estado de lote",
        "parameters": [
          {
            "$ref": "#/components/parameters/BatchId"
          }
        ],
        "responses": {
          "200": {
            "description": "Lote encontrado",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/BatchResponse"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/api/v1/payment-references/batches/{batch_id}/items": {
      "get": {
        "tags": ["Batches"],
        "summary": "Lista items de lote con errores por linea",
        "parameters": [
          {
            "$ref": "#/components/parameters/BatchId"
          },
          {
            "$ref": "#/components/parameters/Page"
          },
          {
            "$ref": "#/components/parameters/PageSize"
          },
          {
            "name": "status",
            "in": "query",
            "schema": {
              "$ref": "#/components/schemas/BatchItemStatus"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Items paginados",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/BatchItemsResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/webhooks/openpay": {
      "post": {
        "tags": ["Webhooks"],
        "summary": "Recibe eventos oficiales de Openpay",
        "description": "Almacena evento crudo, valida firma si esta configurada, actualiza estado y dispara callback.",
        "security": [],
        "parameters": [
          {
            "$ref": "#/components/parameters/OpenpaySignature"
          }
        ],
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/OpenpayWebhookEvent"
              },
              "examples": {
                "payment_paid": {
                  "$ref": "#/components/examples/OpenpayPaymentPaidWebhook"
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Evento recibido",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WebhookAcceptedResponse"
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/InvalidSignature"
          }
        }
      }
    },
    "/api/v1/webhooks/reprocess": {
      "post": {
        "tags": ["Webhooks"],
        "summary": "Reprocesa eventos historicos",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ReprocessWebhooksRequest"
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "Reproceso aceptado",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ReprocessResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/callbacks/retry/{event_id}": {
      "post": {
        "tags": ["Callbacks"],
        "summary": "Reenvia un callback fallido",
        "parameters": [
          {
            "$ref": "#/components/parameters/EventId"
          }
        ],
        "responses": {
          "202": {
            "description": "Reintento aceptado",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/CallbackRetryResponse"
                }
              }
            }
          },
          "404": {
            "$ref": "#/components/responses/NotFound"
          }
        }
      }
    },
    "/api/v1/reconciliation": {
      "post": {
        "tags": ["Reconciliation"],
        "summary": "Concilia referencias puntuales contra Openpay",
        "description": "Busca cada referencia, consulta Openpay, compara estado/importe, actualiza diferencias y crea bitacora.",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ReconciliationRequest"
              },
              "examples": {
                "basic": {
                  "$ref": "#/components/examples/ReconciliationRequest"
                }
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Conciliacion ejecutada",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ReconciliationResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/reconciliation/batches": {
      "post": {
        "tags": ["Reconciliation"],
        "summary": "Crea una conciliacion masiva JSON/CSV/XLSX",
        "requestBody": {
          "required": true,
          "content": {
            "application/json": {
              "schema": {
                "$ref": "#/components/schemas/ReconciliationBatchJsonRequest"
              }
            },
            "multipart/form-data": {
              "schema": {
                "$ref": "#/components/schemas/ReconciliationBatchUploadRequest"
              }
            }
          }
        },
        "responses": {
          "202": {
            "description": "Conciliacion masiva aceptada",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ReconciliationJobResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/events": {
      "get": {
        "tags": ["Events"],
        "summary": "Consulta eventos operativos",
        "parameters": [
          {
            "$ref": "#/components/parameters/DateFrom"
          },
          {
            "$ref": "#/components/parameters/DateTo"
          },
          {
            "name": "reference",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "batch_id",
            "in": "query",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "name": "status",
            "in": "query",
            "schema": {
              "$ref": "#/components/schemas/PaymentReferenceStatus"
            }
          },
          {
            "name": "event_type",
            "in": "query",
            "schema": {
              "$ref": "#/components/schemas/EventType"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Eventos filtrados",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/EventListResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/audit": {
      "get": {
        "tags": ["Events"],
        "summary": "Consulta auditoria completa",
        "parameters": [
          {
            "$ref": "#/components/parameters/DateFrom"
          },
          {
            "$ref": "#/components/parameters/DateTo"
          },
          {
            "name": "actor_id",
            "in": "query",
            "schema": {
              "type": "string"
            }
          },
          {
            "name": "reference",
            "in": "query",
            "schema": {
              "type": "string"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Auditoria filtrada",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/AuditListResponse"
                }
              }
            }
          }
        }
      }
    },
    "/api/v1/operations/dashboard": {
      "get": {
        "tags": ["Operations"],
        "summary": "Dashboard operativo",
        "security": [
          {
            "BearerAuth": []
          }
        ],
        "responses": {
          "200": {
            "description": "Indicadores operativos",
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/OperationsDashboardResponse"
                }
              }
            }
          }
        }
      }
    }
  },
  "components": {
    "securitySchemes": {
      "ApiKeyAuth": {
        "type": "apiKey",
        "in": "header",
        "name": "x-api-key",
        "description": "API key unica por cliente/sistema externo."
      },
      "BearerAuth": {
        "type": "http",
        "scheme": "bearer",
        "bearerFormat": "JWT"
      },
      "CallbackHmac": {
        "type": "apiKey",
        "in": "header",
        "name": "x-signature",
        "description": "HMAC-SHA256 del cuerpo del callback usando el secreto configurado por cliente."
      }
    },
    "parameters": {
      "IdempotencyKey": {
        "name": "Idempotency-Key",
        "in": "header",
        "required": true,
        "description": "UUID o clave unica por operacion. Evita referencias, lotes y pagos duplicados.",
        "schema": {
          "type": "string",
          "minLength": 8,
          "maxLength": 120
        }
      },
      "ClientRequestId": {
        "name": "X-Client-Request-Id",
        "in": "header",
        "required": false,
        "schema": {
          "type": "string",
          "maxLength": 120
        }
      },
      "OpenpaySignature": {
        "name": "x-openpay-signature",
        "in": "header",
        "required": false,
        "schema": {
          "type": "string"
        }
      },
      "ReferenceId": {
        "name": "id",
        "in": "path",
        "required": true,
        "schema": {
          "type": "string",
          "format": "uuid"
        }
      },
      "ExternalReference": {
        "name": "external_reference",
        "in": "path",
        "required": true,
        "schema": {
          "type": "string",
          "minLength": 1,
          "maxLength": 120
        }
      },
      "BatchId": {
        "name": "batch_id",
        "in": "path",
        "required": true,
        "schema": {
          "type": "string",
          "format": "uuid"
        }
      },
      "EventId": {
        "name": "event_id",
        "in": "path",
        "required": true,
        "schema": {
          "type": "string",
          "format": "uuid"
        }
      },
      "Page": {
        "name": "page",
        "in": "query",
        "schema": {
          "type": "integer",
          "minimum": 1,
          "default": 1
        }
      },
      "PageSize": {
        "name": "page_size",
        "in": "query",
        "schema": {
          "type": "integer",
          "minimum": 1,
          "maximum": 500,
          "default": 50
        }
      },
      "DateFrom": {
        "name": "date_from",
        "in": "query",
        "schema": {
          "type": "string",
          "format": "date-time"
        }
      },
      "DateTo": {
        "name": "date_to",
        "in": "query",
        "schema": {
          "type": "string",
          "format": "date-time"
        }
      }
    },
    "headers": {
      "RateLimitLimit": {
        "schema": {
          "type": "integer"
        },
        "description": "Limite de solicitudes por ventana."
      },
      "RateLimitRemaining": {
        "schema": {
          "type": "integer"
        },
        "description": "Solicitudes restantes en la ventana actual."
      }
    },
    "responses": {
      "Unauthorized": {
        "description": "No autenticado o credenciales invalidas",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorResponse"
            }
          }
        }
      },
      "Forbidden": {
        "description": "Sin permisos para el recurso",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorResponse"
            }
          }
        }
      },
      "ValidationError": {
        "description": "Error de validacion",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ValidationErrorResponse"
            }
          }
        }
      },
      "Conflict": {
        "description": "Operacion duplicada o conflicto de estado",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorResponse"
            }
          }
        }
      },
      "NotFound": {
        "description": "Recurso no encontrado",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorResponse"
            }
          }
        }
      },
      "RateLimited": {
        "description": "Rate limit excedido",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorResponse"
            }
          }
        }
      },
      "InvalidSignature": {
        "description": "Firma invalida",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/ErrorResponse"
            }
          }
        }
      },
      "OpenpayError": {
        "description": "Error al comunicarse con Openpay",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/OpenpayErrorResponse"
            }
          }
        }
      }
    },
    "schemas": {
      "AuthTokenRequest": {
        "type": "object",
        "required": ["email", "password"],
        "properties": {
          "email": {
            "type": "string",
            "format": "email"
          },
          "password": {
            "type": "string",
            "format": "password"
          }
        },
        "additionalProperties": false
      },
      "AuthTokenResponse": {
        "type": "object",
        "required": ["access_token", "token_type", "expires_in"],
        "properties": {
          "access_token": {
            "type": "string"
          },
          "token_type": {
            "type": "string",
            "const": "Bearer"
          },
          "expires_in": {
            "type": "integer",
            "example": 3600
          },
          "scope": {
            "type": "array",
            "items": {
              "type": "string"
            }
          }
        }
      },
      "CreatePaymentReferenceRequest": {
        "type": "object",
        "required": ["external_reference", "contract_number", "customer_name", "amount", "currency", "description", "due_date", "period"],
        "properties": {
          "external_reference": {
            "type": "string",
            "minLength": 1,
            "maxLength": 120,
            "description": "Folio unico del sistema comercial."
          },
          "contract_number": {
            "type": "string",
            "minLength": 1,
            "maxLength": 120
          },
          "customer_name": {
            "type": "string",
            "minLength": 1,
            "maxLength": 180
          },
          "customer_email": {
            "type": "string",
            "format": "email"
          },
          "customer_phone": {
            "type": "string",
            "pattern": "^[0-9+()\\-\\s]{7,20}$"
          },
          "amount": {
            "type": "number",
            "exclusiveMinimum": 0,
            "maximum": 9999999
          },
          "currency": {
            "type": "string",
            "const": "MXN"
          },
          "description": {
            "type": "string",
            "minLength": 1,
            "maxLength": 250
          },
          "due_date": {
            "type": "string",
            "format": "date-time"
          },
          "period": {
            "type": "string",
            "examples": ["2026-06"]
          },
          "callback_url": {
            "type": "string",
            "format": "uri",
            "description": "Sobrescribe callback configurado para esta referencia."
          },
          "metadata": {
            "type": "object",
            "additionalProperties": true
          }
        },
        "additionalProperties": false
      },
      "PaymentReferenceResponse": {
        "type": "object",
        "required": ["id", "external_reference", "status", "paynet"],
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "client_id": {
            "type": "string",
            "format": "uuid"
          },
          "external_reference": {
            "type": "string"
          },
          "contract_number": {
            "type": "string"
          },
          "amount": {
            "type": "number"
          },
          "currency": {
            "type": "string"
          },
          "description": {
            "type": "string"
          },
          "due_date": {
            "type": "string",
            "format": "date-time"
          },
          "status": {
            "$ref": "#/components/schemas/PaymentReferenceStatus"
          },
          "idempotent": {
            "type": "boolean"
          },
          "paynet": {
            "$ref": "#/components/schemas/PaynetData"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          },
          "updated_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "PaynetData": {
        "type": "object",
        "required": ["openpay_charge_id", "reference", "payment_method"],
        "properties": {
          "openpay_charge_id": {
            "type": "string"
          },
          "openpay_authorization": {
            "type": "string"
          },
          "reference": {
            "type": "string"
          },
          "barcode_url": {
            "type": "string",
            "format": "uri"
          },
          "barcode_number": {
            "type": "string"
          },
          "payment_method": {
            "type": "string",
            "const": "store"
          },
          "instructions": {
            "type": "string"
          }
        }
      },
      "PaymentReferenceStatus": {
        "type": "string",
        "enum": ["pending", "paid", "expired", "cancelled", "failed", "duplicated", "callback_failed", "partially_processed", "webhook_received", "webhook_processed", "callback_sent", "reconciled", "reconciliation_error"]
      },
      "CreateBatchRequest": {
        "type": "object",
        "required": ["items"],
        "properties": {
          "batch_external_id": {
            "type": "string",
            "maxLength": 120
          },
          "processing_mode": {
            "type": "string",
            "enum": ["sync", "async"],
            "default": "async"
          },
          "items": {
            "type": "array",
            "minItems": 1,
            "maxItems": 10000,
            "items": {
              "$ref": "#/components/schemas/CreatePaymentReferenceRequest"
            }
          }
        },
        "additionalProperties": false
      },
      "BatchUploadRequest": {
        "type": "object",
        "required": ["file"],
        "properties": {
          "file": {
            "type": "string",
            "format": "binary"
          },
          "format": {
            "type": "string",
            "enum": ["csv", "xlsx"]
          },
          "batch_external_id": {
            "type": "string"
          }
        }
      },
      "BatchResponse": {
        "type": "object",
        "required": ["batch_id", "status", "total_items", "processed_items", "failed_items"],
        "properties": {
          "batch_id": {
            "type": "string",
            "format": "uuid"
          },
          "status": {
            "$ref": "#/components/schemas/BatchStatus"
          },
          "total_items": {
            "type": "integer"
          },
          "processed_items": {
            "type": "integer"
          },
          "failed_items": {
            "type": "integer"
          },
          "errors": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/BatchLineError"
            }
          }
        }
      },
      "BatchStatus": {
        "type": "string",
        "enum": ["pending", "processing", "processed", "partially_processed", "failed", "duplicated"]
      },
      "BatchItemStatus": {
        "type": "string",
        "enum": ["pending", "processed", "failed", "duplicated"]
      },
      "BatchLineError": {
        "type": "object",
        "required": ["line", "code", "message"],
        "properties": {
          "line": {
            "type": "integer"
          },
          "external_reference": {
            "type": "string"
          },
          "code": {
            "type": "string"
          },
          "message": {
            "type": "string"
          },
          "field": {
            "type": "string"
          }
        }
      },
      "BatchItemsResponse": {
        "type": "object",
        "required": ["items", "pagination"],
        "properties": {
          "items": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/BatchItem"
            }
          },
          "pagination": {
            "$ref": "#/components/schemas/Pagination"
          }
        }
      },
      "BatchItem": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "line_number": {
            "type": "integer"
          },
          "external_reference": {
            "type": "string"
          },
          "status": {
            "$ref": "#/components/schemas/BatchItemStatus"
          },
          "payment_reference_id": {
            "type": "string",
            "format": "uuid"
          },
          "error": {
            "$ref": "#/components/schemas/BatchLineError"
          }
        }
      },
      "OpenpayWebhookEvent": {
        "type": "object",
        "required": ["type", "transaction"],
        "properties": {
          "id": {
            "type": "string"
          },
          "type": {
            "$ref": "#/components/schemas/WebhookEventType"
          },
          "event_date": {
            "type": "string",
            "format": "date-time"
          },
          "transaction": {
            "type": "object",
            "required": ["id", "status"],
            "properties": {
              "id": {
                "type": "string"
              },
              "order_id": {
                "type": "string"
              },
              "status": {
                "type": "string"
              },
              "amount": {
                "type": "number"
              },
              "currency": {
                "type": "string"
              },
              "authorization": {
                "type": "string"
              },
              "payment_method": {
                "type": "object",
                "additionalProperties": true
              }
            },
            "additionalProperties": true
          }
        },
        "additionalProperties": true
      },
      "WebhookEventType": {
        "type": "string",
        "enum": ["payment.created", "payment.pending", "payment.paid", "payment.expired", "payment.cancelled", "payment.failed", "charge.created", "charge.succeeded", "charge.failed", "charge.cancelled"]
      },
      "WebhookAcceptedResponse": {
        "type": "object",
        "properties": {
          "received": {
            "type": "boolean"
          },
          "event_id": {
            "type": "string",
            "format": "uuid"
          },
          "status": {
            "type": "string",
            "enum": ["webhook_received", "webhook_processed"]
          }
        }
      },
      "ReprocessWebhooksRequest": {
        "type": "object",
        "properties": {
          "event_ids": {
            "type": "array",
            "items": {
              "type": "string",
              "format": "uuid"
            }
          },
          "filters": {
            "$ref": "#/components/schemas/EventFilters"
          }
        }
      },
      "ReprocessResponse": {
        "type": "object",
        "properties": {
          "job_id": {
            "type": "string",
            "format": "uuid"
          },
          "accepted_events": {
            "type": "integer"
          }
        }
      },
      "CallbackRetryResponse": {
        "type": "object",
        "properties": {
          "retry_id": {
            "type": "string",
            "format": "uuid"
          },
          "status": {
            "type": "string",
            "enum": ["callback_sent", "callback_failed"]
          },
          "next_retry_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "ReconciliationRequest": {
        "type": "object",
        "required": ["references"],
        "properties": {
          "references": {
            "type": "array",
            "minItems": 1,
            "maxItems": 10000,
            "items": {
              "type": "string"
            }
          }
        },
        "additionalProperties": false
      },
      "ReconciliationResponse": {
        "type": "object",
        "required": ["job_id", "status", "items"],
        "properties": {
          "job_id": {
            "type": "string",
            "format": "uuid"
          },
          "status": {
            "type": "string",
            "enum": ["reconciled", "partially_processed", "reconciliation_error"]
          },
          "items": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ReconciliationItem"
            }
          }
        }
      },
      "ReconciliationItem": {
        "type": "object",
        "properties": {
          "reference": {
            "type": "string"
          },
          "internal_status": {
            "$ref": "#/components/schemas/PaymentReferenceStatus"
          },
          "openpay_status": {
            "type": "string"
          },
          "updated": {
            "type": "boolean"
          },
          "differences": {
            "type": "array",
            "items": {
              "type": "string",
              "enum": ["status", "amount", "currency", "missing_internal", "missing_openpay"]
            }
          },
          "error": {
            "type": "string"
          }
        }
      },
      "ReconciliationBatchJsonRequest": {
        "$ref": "#/components/schemas/ReconciliationRequest"
      },
      "ReconciliationBatchUploadRequest": {
        "type": "object",
        "required": ["file"],
        "properties": {
          "file": {
            "type": "string",
            "format": "binary"
          },
          "format": {
            "type": "string",
            "enum": ["csv", "xlsx"]
          }
        }
      },
      "ReconciliationJobResponse": {
        "type": "object",
        "properties": {
          "job_id": {
            "type": "string",
            "format": "uuid"
          },
          "status": {
            "type": "string",
            "enum": ["pending", "processing"]
          },
          "total_references": {
            "type": "integer"
          }
        }
      },
      "EventType": {
        "type": "string",
        "enum": ["payment.created", "payment.pending", "payment.paid", "payment.expired", "payment.cancelled", "payment.failed", "webhook_received", "webhook_processed", "callback_sent", "callback_failed", "reconciled", "reconciliation_error", "audit"]
      },
      "EventFilters": {
        "type": "object",
        "properties": {
          "date_from": {
            "type": "string",
            "format": "date-time"
          },
          "date_to": {
            "type": "string",
            "format": "date-time"
          },
          "reference": {
            "type": "string"
          },
          "status": {
            "$ref": "#/components/schemas/PaymentReferenceStatus"
          },
          "event_type": {
            "$ref": "#/components/schemas/EventType"
          }
        }
      },
      "EventListResponse": {
        "type": "object",
        "properties": {
          "events": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/EventLog"
            }
          },
          "pagination": {
            "$ref": "#/components/schemas/Pagination"
          }
        }
      },
      "EventLog": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "event_type": {
            "$ref": "#/components/schemas/EventType"
          },
          "reference": {
            "type": "string"
          },
          "status": {
            "$ref": "#/components/schemas/PaymentReferenceStatus"
          },
          "payload": {
            "type": "object",
            "additionalProperties": true
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "AuditListResponse": {
        "type": "object",
        "properties": {
          "audit_logs": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/AuditLog"
            }
          },
          "pagination": {
            "$ref": "#/components/schemas/Pagination"
          }
        }
      },
      "AuditLog": {
        "type": "object",
        "properties": {
          "id": {
            "type": "string",
            "format": "uuid"
          },
          "client_id": {
            "type": "string",
            "format": "uuid"
          },
          "actor_type": {
            "type": "string",
            "enum": ["user", "api_token", "system", "openpay"]
          },
          "actor_id": {
            "type": "string"
          },
          "action": {
            "type": "string"
          },
          "resource_type": {
            "type": "string"
          },
          "resource_id": {
            "type": "string"
          },
          "source_ip": {
            "type": "string"
          },
          "created_at": {
            "type": "string",
            "format": "date-time"
          }
        }
      },
      "OperationsDashboardResponse": {
        "type": "object",
        "properties": {
          "references_created_today": {
            "type": "integer"
          },
          "pending_references": {
            "type": "integer"
          },
          "paid_references": {
            "type": "integer"
          },
          "expired_references": {
            "type": "integer"
          },
          "webhooks_received": {
            "type": "integer"
          },
          "failed_callbacks": {
            "type": "integer"
          },
          "detected_differences": {
            "type": "integer"
          },
          "reconciliations_executed": {
            "type": "integer"
          },
          "alerts": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/OperationalAlert"
            }
          }
        }
      },
      "OperationalAlert": {
        "type": "object",
        "properties": {
          "type": {
            "type": "string",
            "enum": ["webhook_failed", "callback_failed", "amount_difference", "status_difference", "openpay_unavailable", "batch_errors"]
          },
          "severity": {
            "type": "string",
            "enum": ["low", "medium", "high", "critical"]
          },
          "message": {
            "type": "string"
          }
        }
      },
      "HealthResponse": {
        "type": "object",
        "properties": {
          "ok": {
            "type": "boolean"
          }
        }
      },
      "VersionResponse": {
        "type": "object",
        "properties": {
          "name": {
            "type": "string"
          },
          "version": {
            "type": "string"
          },
          "api": {
            "type": "string"
          }
        }
      },
      "Pagination": {
        "type": "object",
        "properties": {
          "page": {
            "type": "integer"
          },
          "page_size": {
            "type": "integer"
          },
          "total_items": {
            "type": "integer"
          },
          "total_pages": {
            "type": "integer"
          }
        }
      },
      "ErrorResponse": {
        "type": "object",
        "required": ["error"],
        "properties": {
          "error": {
            "type": "string"
          },
          "code": {
            "type": "string"
          },
          "request_id": {
            "type": "string"
          }
        }
      },
      "ValidationErrorResponse": {
        "type": "object",
        "properties": {
          "error": {
            "type": "string",
            "example": "Datos invalidos"
          },
          "details": {
            "type": "object",
            "additionalProperties": true
          }
        }
      },
      "OpenpayErrorResponse": {
        "type": "object",
        "properties": {
          "error": {
            "type": "string"
          },
          "openpay": {
            "type": "object",
            "additionalProperties": true
          }
        }
      }
    },
    "examples": {
      "CreatePaymentReference": {
        "summary": "Recibo de agua potable",
        "value": {
          "external_reference": "RECIBO-2026-000001",
          "contract_number": "CONTRATO-123456",
          "customer_name": "Juan Perez",
          "customer_email": "juan@example.com",
          "customer_phone": "5555555555",
          "amount": 248.5,
          "currency": "MXN",
          "description": "Pago de agua potable junio 2026",
          "due_date": "2026-07-15T23:59:59-06:00",
          "period": "2026-06",
          "metadata": {
            "municipality": "Municipio Demo",
            "route": "A-12"
          }
        }
      },
      "OpenpayPaymentPaidWebhook": {
        "summary": "Pago confirmado",
        "value": {
          "id": "evt_123",
          "type": "payment.paid",
          "event_date": "2026-06-22T10:20:30-06:00",
          "transaction": {
            "id": "tr_123",
            "order_id": "RECIBO-2026-000001",
            "status": "completed",
            "amount": 248.5,
            "currency": "MXN",
            "authorization": "AUTH123"
          }
        }
      },
      "ReconciliationRequest": {
        "summary": "Conciliacion puntual",
        "value": {
          "references": ["RECIBO-2026-000001", "RECIBO-2026-000002", "RECIBO-2026-000003"]
        }
      }
    }
  },
  "x-webhook-events": ["payment.created", "payment.pending", "payment.paid", "payment.expired", "payment.cancelled", "payment.failed"],
  "x-callback-events": ["pending", "paid", "expired", "cancelled", "failed", "callback_failed", "reconciled"],
  "x-automatic-jobs": [
    {
      "name": "pending_references_reconciliation",
      "schedule": "*/15 * * * *",
      "description": "Cada 15 minutos revisa referencias pendientes."
    },
    {
      "name": "failed_callbacks_retry",
      "schedule": "0 * * * *",
      "description": "Cada hora revisa callbacks fallidos."
    },
    {
      "name": "daily_full_reconciliation",
      "schedule": "0 2 * * *",
      "description": "Conciliacion completa diaria."
    },
    {
      "name": "weekly_historical_reconciliation",
      "schedule": "0 3 * * 0",
      "description": "Conciliacion historica semanal."
    }
  ]
}
