openapi: 3.0.3

info:
  title: DEFUNCIA.COM API
  version: "v1"
  description: |
    API REST de **DEFUNCIA.COM**, la plataforma SaaS multitenant de gestión del
    legado digital post-mortem para aseguradoras de decesos y particulares.

    Esta especificación documenta **exactamente** los endpoints implementados en
    el gateway `api.php`. Permite a las aseguradoras (tenants) crear casos de
    forma programática, abrir siniestros desde sus pólizas de decesos, consultar
    el estado de los casos y sus cuentas digitales, suscribirse a webhooks y
    obtener métricas de siniestralidad.

    ## Autenticación

    La API soporta dos mecanismos de autenticación, según el actor:

    | Actor | Mecanismo | Cabecera |
    |-------|-----------|----------|
    | Tenant (aseguradora / sistema externo) | API Key | `X-API-Key: dfk_...` |
    | Operador / Admin | JWT Bearer | `Authorization: Bearer {jwt}` |

    Los endpoints bajo `/api/v1/` aceptan **cualquiera** de los dos mecanismos
    (la API Key tiene prioridad si ambos están presentes), salvo `GET /me`, que
    requiere obligatoriamente JWT.

    ## Obtención de la API Key

    Las API Keys se generan desde el backoffice del tenant (sección
    Configuración). Tienen el formato `dfk_` seguido de 96 caracteres hex, se
    almacenan hasheadas (SHA-256) en el servidor y **solo se muestran una vez**
    en el momento de su creación. Cada key lleva asociado un `rate_limit`
    (por defecto 1000 peticiones/hora) y una lista de permisos.

    ## Formato de respuesta

    Todas las respuestas usan un envoltorio JSON uniforme:

    ```json
    { "success": true, "data": { } }
    { "success": false, "error": "mensaje de error" }
    ```

    Los listados paginados incluyen además un objeto `meta` con la paginación.

    ## Rate limiting

    Cada respuesta autenticada por API Key incluye las cabeceras
    `X-RateLimit-Limit`, `X-RateLimit-Remaining` y `X-RateLimit-Reset`.
    Al superar el límite se devuelve un `429` con cabecera `Retry-After`.
    El endpoint `POST /auth/login` está limitado a 5 intentos cada 15 minutos por IP.
  contact:
    name: Integraciones DEFUNCIA.COM
    email: integraciones@defuncia.com
    url: https://defuncia.com

servers:
  - url: https://{tenant}.defuncia.com/api/v1
    description: Endpoints v1 del tenant (aseguradora). Requieren autenticación.
    variables:
      tenant:
        default: demo
        description: Subdominio del tenant (aseguradora).
  - url: https://{tenant}.defuncia.com
    description: |
      Raíz para endpoints de autenticación JWT (`/auth/login`, `/auth/logout`)
      y para `/me`. Nota - en la implementación estos cuelgan de `/api.php`.
    variables:
      tenant:
        default: demo
        description: Subdominio del tenant.

tags:
  - name: Información
    description: Metadatos de la API.
  - name: Autenticación
    description: Login y logout JWT para operadores/admins.
  - name: Casos
    description: Creación y consulta de casos de gestión post-mortem.
  - name: Siniestros
    description: Apertura automática de casos desde pólizas de decesos.
  - name: Reporting
    description: Métricas de siniestralidad para la aseguradora / DGSFP.
  - name: Catálogo
    description: Catálogo global de plataformas digitales.
  - name: Webhooks
    description: Registro y gestión de notificaciones salientes.

security:
  - ApiKeyAuth: []
  - BearerAuth: []

paths:

  /:
    get:
      tags: [Información]
      summary: Información de la API
      description: |
        Devuelve metadatos de la API y la lista de endpoints principales.
        No requiere autenticación.
      security: []
      responses:
        "200":
          description: Información de la API.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/RespuestaInfo"

  # ===================================================================
  # AUTENTICACIÓN JWT
  # ===================================================================

  /auth/login:
    post:
      tags: [Autenticación]
      summary: Login JWT (operadores/admins)
      description: |
        Autentica a un operador o admin del tenant mediante usuario y
        contraseña y devuelve un token JWT válido durante 24 horas.
        Limitado a 5 intentos cada 15 minutos por IP.
      security: []
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              required: [username, password]
              properties:
                username:
                  type: string
                  example: operador1
                password:
                  type: string
                  format: password
                  example: "MiClaveSegura2026"
      responses:
        "200":
          description: Autenticación correcta.
          content:
            application/json:
              schema:
                allOf:
                  - $ref: "#/components/schemas/RespuestaOk"
                  - type: object
                    properties:
                      data:
                        $ref: "#/components/schemas/LoginData"
        "400":
          $ref: "#/components/responses/Error400"
        "401":
          $ref: "#/components/responses/Error401"
        "429":
          $ref: "#/components/responses/Error429"

  /auth/logout:
    post:
      tags: [Autenticación]
      summary: Logout JWT
      description: |
        Cierra la sesión. Como los JWT no se almacenan en servidor, esta
        operación es informativa - el cliente debe descartar su token.
      responses:
        "200":
          description: Sesión cerrada.
          content:
            application/json:
              schema:
                allOf:
                  - $ref: "#/components/schemas/RespuestaOk"
                  - type: object
                    properties:
                      data:
                        type: object
                        properties:
                          message:
                            type: string
                            example: Sesión cerrada

  /me:
    get:
      tags: [Autenticación]
      summary: Usuario autenticado actual (solo JWT)
      description: |
        Devuelve los datos del usuario asociado al JWT. Requiere
        **obligatoriamente** autenticación JWT (no acepta API Key).
        El campo `password_hash` nunca se devuelve.
      security:
        - BearerAuth: []
      responses:
        "200":
          description: Datos del usuario.
          content:
            application/json:
              schema:
                allOf:
                  - $ref: "#/components/schemas/RespuestaOk"
                  - type: object
                    properties:
                      data:
                        $ref: "#/components/schemas/Usuario"
        "401":
          $ref: "#/components/responses/Error401"
        "403":
          description: El endpoint requiere JWT y se ha usado otro mecanismo.
          content:
            application/json:
              schema:
                $ref: "#/components/schemas/Error"

  # ===================================================================
  # CASOS
  # ===================================================================

  /casos:
    post:
      tags: [Casos]
      summary: Crear caso
      description: |
        Crea un nuevo caso de gestión post-mortem. Genera automáticamente un
        token de acceso para el heredero (portal familiar) y despacha el
        webhook `caso_abierto` a los webhooks suscritos del tenant.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/CrearCasoRequest"
            examples:
              decesos:
                summary: Caso desde póliza de decesos
                value:
                  fallecido:
                    nombre: Manuel
                    apellidos: Rodríguez Sáez
                    dni: "12345678Z"
                    fecha_fallecimiento: "2026-05-30"
                  heredero:
                    nombre: Lucía
                    apellidos: Rodríguez Pons
                    dni: "87654321X"
                    email: lucia.rodriguez@example.com
                    telefono: "+34600112233"
                    parentesco: hija
                  poliza_numero: "DEC-2025-558721"
                  modulos_contratados: [borrado, suscripciones]
                  canal: poliza
      responses:
        "201":
          description: Caso creado.
          content:
            application/json:
              schema:
                allOf:
                  - $ref: "#/components/schemas/RespuestaOk"
                  - type: object
                    properties:
                      data:
                        $ref: "#/components/schemas/CrearCasoResponse"
        "400":
          $ref: "#/components/responses/Error400"
        "401":
          $ref: "#/components/responses/Error401"
        "422":
          $ref: "#/components/responses/Error422"
        "429":
          $ref: "#/components/responses/Error429"
    get:
      tags: [Casos]
      summary: Listar casos del tenant
      description: |
        Devuelve un listado paginado de casos del tenant, con filtros
        opcionales por estado, póliza y rango de fechas de creación.
      parameters:
        - $ref: "#/components/parameters/Page"
        - $ref: "#/components/parameters/PerPage"
        - $ref: "#/components/parameters/Sort"
        - $ref: "#/components/parameters/Order"
        - name: estado
          in: query
          schema:
            $ref: "#/components/schemas/EstadoCaso"
          description: Filtrar por estado del caso.
        - name: poliza
          in: query
          schema: { type: string }
          description: Filtrar por número de póliza.
        - name: desde
          in: query
          schema: { type: string, format: date }
          description: Fecha de creación desde (YYYY-MM-DD).
        - name: hasta
          in: query
          schema: { type: string, format: date }
          description: Fecha de creación hasta (YYYY-MM-DD).
      responses:
        "200":
          description: Listado de casos.
          content:
            application/json:
              schema:
                allOf:
                  - $ref: "#/components/schemas/RespuestaOk"
                  - type: object
                    properties:
                      data:
                        type: array
                        items:
                          $ref: "#/components/schemas/CasoResumen"
                      meta:
                        $ref: "#/components/schemas/Paginacion"
        "401":
          $ref: "#/components/responses/Error401"
        "429":
          $ref: "#/components/responses/Error429"

  /casos/{id}:
    get:
      tags: [Casos]
      summary: Detalle de un caso
      description: |
        Devuelve el estado completo de un caso, incluyendo un resumen de sus
        cuentas digitales y documentos asociados.
      parameters:
        - $ref: "#/components/parameters/CasoId"
      responses:
        "200":
          description: Detalle del caso.
          content:
            application/json:
              schema:
                allOf:
                  - $ref: "#/components/schemas/RespuestaOk"
                  - type: object
                    properties:
                      data:
                        $ref: "#/components/schemas/CasoDetalle"
        "401":
          $ref: "#/components/responses/Error401"
        "404":
          $ref: "#/components/responses/Error404"

  /casos/{id}/cuentas:
    get:
      tags: [Casos]
      summary: Cuentas digitales de un caso
      description: |
        Devuelve el estado de cada cuenta digital del fallecido asociada al
        caso, con la acción solicitada y las fechas de envío/respuesta.
      parameters:
        - $ref: "#/components/parameters/CasoId"
      responses:
        "200":
          description: Listado de cuentas del caso.
          content:
            application/json:
              schema:
                allOf:
                  - $ref: "#/components/schemas/RespuestaOk"
                  - type: object
                    properties:
                      data:
                        type: array
                        items:
                          $ref: "#/components/schemas/Cuenta"
        "401":
          $ref: "#/components/responses/Error401"
        "404":
          $ref: "#/components/responses/Error404"

  # ===================================================================
  # SINIESTRO
  # ===================================================================

  /siniestro:
    post:
      tags: [Siniestros]
      summary: Apertura automática de caso desde póliza de decesos
      description: |
        Endpoint pensado para la integración directa con el sistema de la
        aseguradora (canal M29). Abre un caso a partir de los datos de la
        póliza y del fallecido, encola el email de bienvenida al heredero y
        despacha el webhook `siniestro_abierto`.

        Diferencias respecto a `POST /casos` - acepta `prioridad`
        (`normal`|`urgente`), valida los `modulos` contra el conjunto
        permitido (`borrado`, `suscripciones`, `certificados`) y fuerza
        `canal = poliza` y `origen_api = siniestro`.
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/SiniestroRequest"
            examples:
              urgente:
                summary: Siniestro urgente con tres módulos
                value:
                  poliza_numero: "DEC-2025-558721"
                  fallecido:
                    nombre: Manuel
                    apellidos: Rodríguez Sáez
                    dni: "12345678Z"
                    fecha_fallecimiento: "2026-06-01"
                  heredero:
                    nombre: Lucía
                    apellidos: Rodríguez Pons
                    email: lucia.rodriguez@example.com
                    telefono: "+34600112233"
                    parentesco: hija
                  modulos: [borrado, suscripciones, certificados]
                  prioridad: urgente
                  notas_aseguradora: "Cliente premium. Tramitar con prioridad."
      responses:
        "201":
          description: Siniestro abierto y caso creado.
          content:
            application/json:
              schema:
                allOf:
                  - $ref: "#/components/schemas/RespuestaOk"
                  - type: object
                    properties:
                      data:
                        $ref: "#/components/schemas/SiniestroResponse"
        "400":
          $ref: "#/components/responses/Error400"
        "401":
          $ref: "#/components/responses/Error401"
        "422":
          $ref: "#/components/responses/Error422"
        "429":
          $ref: "#/components/responses/Error429"

  # ===================================================================
  # REPORTING
  # ===================================================================

  /reporting:
    get:
      tags: [Reporting]
      summary: Métricas de siniestralidad (DGSFP)
      description: |
        Devuelve métricas agregadas de los casos del tenant en un periodo -
        conteo por estado y por módulo, tiempo medio de resolución, tasa de
        éxito y facturación del periodo. Útil para los informes de
        siniestralidad de la aseguradora.
      parameters:
        - name: desde
          in: query
          schema: { type: string, format: date }
          description: "Inicio del periodo (YYYY-MM-DD). Por defecto - inicio del mes actual."
        - name: hasta
          in: query
          schema: { type: string, format: date }
          description: "Fin del periodo (YYYY-MM-DD). Por defecto - hoy."
      responses:
        "200":
          description: Métricas del periodo.
          content:
            application/json:
              schema:
                allOf:
                  - $ref: "#/components/schemas/RespuestaOk"
                  - type: object
                    properties:
                      data:
                        $ref: "#/components/schemas/Reporting"
        "400":
          $ref: "#/components/responses/Error400"
        "401":
          $ref: "#/components/responses/Error401"

  # ===================================================================
  # CATÁLOGO
  # ===================================================================

  /catalogo:
    get:
      tags: [Catálogo]
      summary: Catálogo de plataformas digitales
      description: |
        Devuelve la lista de plataformas digitales del catálogo global (M01),
        en formato selector (id + nombre + metadatos básicos).
      responses:
        "200":
          description: Listado de plataformas.
          content:
            application/json:
              schema:
                allOf:
                  - $ref: "#/components/schemas/RespuestaOk"
                  - type: object
                    properties:
                      data:
                        type: array
                        items:
                          $ref: "#/components/schemas/PlataformaCatalogo"
        "401":
          $ref: "#/components/responses/Error401"

  # ===================================================================
  # WEBHOOKS
  # ===================================================================

  /webhooks/register:
    post:
      tags: [Webhooks]
      summary: Registrar webhook
      description: |
        Registra una URL HTTPS de callback para recibir notificaciones de
        eventos. Devuelve el `secret` (en claro, **solo una vez**) que debe
        usarse para verificar la firma HMAC-SHA256 de cada entrega.
        Equivale a `POST /webhooks` (ambas rutas registran).
      requestBody:
        required: true
        content:
          application/json:
            schema:
              $ref: "#/components/schemas/WebhookRegisterRequest"
            examples:
              ejemplo:
                value:
                  url: https://erp.miaseguradora.com/hooks/defuncia
                  eventos: [caso_abierto, caso_cerrado, siniestro_abierto]
      responses:
        "201":
          description: Webhook registrado.
          content:
            application/json:
              schema:
                allOf:
                  - $ref: "#/components/schemas/RespuestaOk"
                  - type: object
                    properties:
                      data:
                        $ref: "#/components/schemas/WebhookRegisterResponse"
        "400":
          $ref: "#/components/responses/Error400"
        "401":
          $ref: "#/components/responses/Error401"

  /webhooks:
    get:
      tags: [Webhooks]
      summary: Listar webhooks
      description: Devuelve los webhooks activos del tenant.
      responses:
        "200":
          description: Listado de webhooks.
          content:
            application/json:
              schema:
                allOf:
                  - $ref: "#/components/schemas/RespuestaOk"
                  - type: object
                    properties:
                      data:
                        type: array
                        items:
                          $ref: "#/components/schemas/Webhook"
        "401":
          $ref: "#/components/responses/Error401"

  /webhooks/{id}:
    delete:
      tags: [Webhooks]
      summary: Eliminar webhook
      description: |
        Desactiva (soft-delete) un webhook del tenant. Solo el tenant
        propietario puede eliminarlo.
      parameters:
        - name: id
          in: path
          required: true
          schema: { type: string }
          example: WHK-2026-04812
      responses:
        "200":
          description: Webhook eliminado.
          content:
            application/json:
              schema:
                allOf:
                  - $ref: "#/components/schemas/RespuestaOk"
                  - type: object
                    properties:
                      data:
                        type: object
                        properties:
                          message:
                            type: string
                            example: Webhook eliminado
        "401":
          $ref: "#/components/responses/Error401"
        "404":
          $ref: "#/components/responses/Error404"

# =====================================================================
# COMPONENTS
# =====================================================================

components:

  securitySchemes:
    ApiKeyAuth:
      type: apiKey
      in: header
      name: X-API-Key
      description: |
        API Key del tenant (formato `dfk_...`). Generada desde el backoffice.
        Se almacena hasheada en el servidor.
    BearerAuth:
      type: http
      scheme: bearer
      bearerFormat: JWT
      description: |
        Token JWT (HS256) obtenido en `POST /auth/login`. Validez 24 horas.

  parameters:
    CasoId:
      name: id
      in: path
      required: true
      schema: { type: string }
      example: CAS-2026-00042
      description: Identificador del caso (formato `CAS-AAAA-NNNNN`).
    Page:
      name: page
      in: query
      schema: { type: integer, minimum: 1, default: 1 }
      description: Página (1-indexada).
    PerPage:
      name: per_page
      in: query
      schema: { type: integer, minimum: 1, maximum: 100, default: 20 }
      description: Resultados por página (máx. 100).
    Sort:
      name: sort
      in: query
      schema: { type: string, default: _created }
      description: Campo de ordenación.
    Order:
      name: order
      in: query
      schema: { type: string, enum: [asc, desc], default: desc }
      description: Sentido de ordenación.

  responses:
    Error400:
      description: Petición incorrecta.
      content:
        application/json:
          schema: { $ref: "#/components/schemas/Error" }
    Error401:
      description: Autenticación requerida o inválida.
      content:
        application/json:
          schema: { $ref: "#/components/schemas/Error" }
          example: { success: false, error: "Autenticación requerida. Use X-API-Key o Authorization: Bearer {jwt}" }
    Error404:
      description: Recurso no encontrado.
      content:
        application/json:
          schema: { $ref: "#/components/schemas/Error" }
          example: { success: false, error: "Caso no encontrado" }
    Error422:
      description: Error de validación de los datos enviados.
      content:
        application/json:
          schema: { $ref: "#/components/schemas/Error" }
          example: { success: false, error: "El nombre del fallecido es obligatorio. El email del heredero no es válido" }
    Error429:
      description: Límite de peticiones excedido.
      content:
        application/json:
          schema: { $ref: "#/components/schemas/Error" }
          example: { success: false, error: "Límite de peticiones excedido" }

  schemas:

    # ---- Envoltorios ----
    RespuestaOk:
      type: object
      properties:
        success:
          type: boolean
          example: true
        data:
          description: Carga útil de la respuesta (varía por endpoint).
      required: [success]

    Error:
      type: object
      properties:
        success:
          type: boolean
          example: false
        error:
          type: string
          example: Endpoint no encontrado
      required: [success, error]

    RespuestaInfo:
      type: object
      properties:
        success: { type: boolean, example: true }
        data:
          type: object
          properties:
            name: { type: string, example: DEFUNCIA.COM API }
            version: { type: string, example: "1.0.0" }
            status: { type: string, example: operational }
            endpoints:
              type: object
              additionalProperties: { type: string }

    Paginacion:
      type: object
      properties:
        page: { type: integer, example: 1 }
        per_page: { type: integer, example: 20 }
        total: { type: integer, example: 137 }
        total_pages: { type: integer, example: 7 }

    # ---- Enums ----
    EstadoCaso:
      type: string
      enum:
        - abierto
        - documentacion_pendiente
        - validando
        - en_proceso
        - en_espera
        - cerrado_completado
        - cerrado_parcial
        - cancelado
      description: Estado del caso (8 estados posibles).

    EstadoCuenta:
      type: string
      enum:
        - pendiente
        - preparando_solicitud
        - traduciendo
        - solicitud_enviada
        - respuesta_recibida
        - resuelta_eliminada
        - resuelta_memorizada
        - resuelta_traspasada
        - denegada
        - sin_respuesta
        - no_aplica
      description: Estado de la cuenta digital (11 estados posibles).

    Modulo:
      type: string
      enum: [borrado, suscripciones, certificados]
      description: |
        Módulo de negocio contratado. En la apertura por `/siniestro` solo se
        aceptan estos tres valores.

    # ---- Auth ----
    LoginData:
      type: object
      properties:
        token:
          type: string
          example: eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiJVU1...
        expires_at:
          type: integer
          description: Timestamp UNIX de expiración del token.
          example: 1781000000
        user:
          $ref: "#/components/schemas/UsuarioResumen"

    UsuarioResumen:
      type: object
      properties:
        id: { type: string, example: USR-2026-00007 }
        username: { type: string, example: operador1 }
        nombre: { type: string, example: Ana Gómez }
        rol: { type: string, example: operador }

    Usuario:
      type: object
      description: Registro completo del usuario (sin `password_hash`).
      properties:
        _id: { type: string, example: USR-2026-00007 }
        username: { type: string, example: operador1 }
        nombre: { type: string, example: Ana Gómez }
        rol: { type: string, example: operador }
        email: { type: string, format: email, example: ana.gomez@miaseguradora.com }
        _created: { type: string, format: date-time }
      additionalProperties: true

    # ---- Casos: entrada ----
    Fallecido:
      type: object
      required: [nombre, apellidos, fecha_fallecimiento]
      properties:
        nombre: { type: string, example: Manuel }
        apellidos: { type: string, example: Rodríguez Sáez }
        dni:
          type: string
          description: DNI/NIE/pasaporte del fallecido (opcional). Se cifra en reposo.
          example: "12345678Z"
        fecha_fallecimiento:
          type: string
          format: date
          description: Fecha de fallecimiento (YYYY-MM-DD). No puede ser futura.
          example: "2026-05-30"

    Heredero:
      type: object
      required: [nombre, apellidos, email, parentesco]
      properties:
        nombre: { type: string, example: Lucía }
        apellidos: { type: string, example: Rodríguez Pons }
        dni:
          type: string
          description: DNI/NIE/pasaporte del heredero (opcional). Se cifra en reposo.
          example: "87654321X"
        email:
          type: string
          format: email
          description: Email del heredero. Se cifra en reposo.
          example: lucia.rodriguez@example.com
        telefono:
          type: string
          description: Teléfono del heredero (opcional). Se cifra en reposo.
          example: "+34600112233"
        parentesco:
          type: string
          example: hija
          description: Parentesco con el fallecido (hijo/a, cónyuge, hermano/a, etc.).

    CrearCasoRequest:
      type: object
      required: [fallecido, heredero]
      properties:
        fallecido: { $ref: "#/components/schemas/Fallecido" }
        heredero: { $ref: "#/components/schemas/Heredero" }
        poliza_numero:
          type: string
          example: "DEC-2025-558721"
        tipo_origen:
          type: string
          enum: [poliza, directo]
          default: poliza
        canal:
          type: string
          enum: [poliza, directo]
          default: poliza
        modulos_contratados:
          type: array
          items: { $ref: "#/components/schemas/Modulo" }
          default: [borrado]

    SiniestroRequest:
      type: object
      required: [poliza_numero, fallecido, heredero]
      properties:
        poliza_numero:
          type: string
          example: "DEC-2025-558721"
        fallecido: { $ref: "#/components/schemas/Fallecido" }
        heredero: { $ref: "#/components/schemas/Heredero" }
        modulos:
          type: array
          items: { $ref: "#/components/schemas/Modulo" }
          default: [borrado]
          description: Módulos a contratar. Solo borrado, suscripciones y certificados.
        prioridad:
          type: string
          enum: [normal, urgente]
          default: normal
        notas_aseguradora:
          type: string
          example: "Cliente premium. Tramitar con prioridad."

    # ---- Casos: salida ----
    CrearCasoResponse:
      type: object
      properties:
        caso_id: { type: string, example: CAS-2026-00042 }
        estado: { type: string, example: abierto }
        portal_url:
          type: string
          format: uri
          example: https://defuncia.com/portal/?token=abc123...
        token_heredero:
          type: string
          description: Token de acceso del heredero al portal familiar.
        token_expiracion:
          type: string
          format: date-time
          description: Caducidad del token (6 meses).

    SiniestroResponse:
      type: object
      properties:
        caso_id: { type: string, example: CAS-2026-00042 }
        estado: { type: string, example: abierto }
        poliza_numero: { type: string, example: "DEC-2025-558721" }
        modulos:
          type: array
          items: { $ref: "#/components/schemas/Modulo" }
        prioridad: { type: string, example: urgente }
        token_heredero: { type: string }
        token_expiracion: { type: string, format: date-time }
        portal_url: { type: string, format: uri }

    CasoResumen:
      type: object
      properties:
        caso_id: { type: string, example: CAS-2026-00042 }
        estado: { $ref: "#/components/schemas/EstadoCaso" }
        estado_label: { type: string, example: En proceso }
        fallecido_nombre: { type: string, example: Manuel Rodríguez Sáez }
        heredero_nombre: { type: string, example: Lucía Rodríguez Pons }
        poliza_numero: { type: string, nullable: true, example: "DEC-2025-558721" }
        tipo_origen: { type: string, example: poliza }
        fecha_apertura: { type: string, format: date-time }
        fecha_cierre: { type: string, format: date-time, nullable: true }

    CasoDetalle:
      type: object
      properties:
        caso_id: { type: string, example: CAS-2026-00042 }
        estado: { $ref: "#/components/schemas/EstadoCaso" }
        estado_label: { type: string, example: En proceso }
        fallecido:
          type: object
          properties:
            nombre: { type: string, example: Manuel Rodríguez Sáez }
            fecha_fallecimiento: { type: string, format: date, example: "2026-05-30" }
        heredero:
          type: object
          properties:
            nombre: { type: string, example: Lucía Rodríguez Pons }
            parentesco: { type: string, example: hija }
        poliza_numero: { type: string, nullable: true }
        tipo_origen: { type: string, example: poliza }
        documentos_completos: { type: boolean, example: false }
        cuentas:
          type: array
          items: { $ref: "#/components/schemas/CuentaResumen" }
        documentos:
          type: array
          items: { $ref: "#/components/schemas/Documento" }
        fecha_apertura: { type: string, format: date-time }
        fecha_cierre: { type: string, format: date-time, nullable: true }

    CuentaResumen:
      type: object
      properties:
        cuenta_id: { type: string, example: CTA-2026-00101 }
        plataforma: { type: string, example: Facebook }
        accion: { type: string, example: eliminar }
        estado: { $ref: "#/components/schemas/EstadoCuenta" }
        estado_label: { type: string, example: Enviada }

    Cuenta:
      type: object
      properties:
        cuenta_id: { type: string, example: CTA-2026-00101 }
        plataforma_id: { type: string, example: facebook }
        plataforma_nombre: { type: string, example: Facebook }
        accion_solicitada:
          type: string
          example: eliminar
          description: Acción solicitada (eliminar, memorizar, traspasar, etc.).
        estado: { $ref: "#/components/schemas/EstadoCuenta" }
        estado_label: { type: string, example: Enviada }
        fecha_envio: { type: string, format: date-time, nullable: true }
        fecha_respuesta: { type: string, format: date-time, nullable: true }
        resultado: { type: string, nullable: true }
        es_final:
          type: boolean
          description: Indica si el estado es terminal.
          example: false

    Documento:
      type: object
      properties:
        documento_id: { type: string, example: DOC-2026-00088 }
        tipo: { type: string, example: certificado_defuncion }
        tipo_label: { type: string, example: Certificado de defunción }
        estado: { type: string, example: validado }
        fecha: { type: string, format: date-time }

    # ---- Reporting ----
    Reporting:
      type: object
      properties:
        periodo:
          type: object
          properties:
            desde: { type: string, format: date, example: "2026-06-01" }
            hasta: { type: string, format: date, example: "2026-06-20" }
        total_casos: { type: integer, example: 137 }
        por_estado:
          type: object
          additionalProperties: { type: integer }
          example:
            abierto: 12
            en_proceso: 45
            cerrado_completado: 60
            cancelado: 5
        por_modulo:
          type: object
          additionalProperties: { type: integer }
          example:
            borrado: 137
            suscripciones: 88
            certificados: 21
        tiempo_medio_resolucion_dias: { type: number, example: 18.4 }
        tasa_exito:
          type: number
          description: Porcentaje de casos cerrados completados sobre cerrados no cancelados.
          example: 92.3
        facturacion:
          type: object
          properties:
            total_periodo: { type: number, example: 4350.00 }
            casos_facturados: { type: integer, example: 137 }

    # ---- Catálogo ----
    PlataformaCatalogo:
      type: object
      description: Entrada del catálogo de plataformas en formato selector.
      properties:
        id: { type: string, example: facebook }
        nombre: { type: string, example: Facebook }
      additionalProperties: true

    # ---- Webhooks ----
    WebhookEvento:
      type: string
      enum:
        - caso_abierto
        - caso_cerrado
        - documentacion_completa
        - cuenta_resuelta
        - siniestro_abierto
        - modulo_completado
        - documentacion_recibida
      description: Eventos de webhook disponibles.

    WebhookRegisterRequest:
      type: object
      required: [url, eventos]
      properties:
        url:
          type: string
          format: uri
          description: URL HTTPS de callback (obligatoriamente https://).
          example: https://erp.miaseguradora.com/hooks/defuncia
        eventos:
          type: array
          minItems: 1
          items: { $ref: "#/components/schemas/WebhookEvento" }

    WebhookRegisterResponse:
      type: object
      properties:
        webhook_id: { type: string, example: WHK-2026-04812 }
        url: { type: string, format: uri }
        eventos:
          type: array
          items: { $ref: "#/components/schemas/WebhookEvento" }
        secret:
          type: string
          description: |
            Secreto para verificar la firma HMAC-SHA256. Se muestra **solo una
            vez** - guárdelo de forma segura.
        nota:
          type: string
          example: "Guarde el secret de forma segura. No se mostrará de nuevo."

    Webhook:
      type: object
      properties:
        webhook_id: { type: string, example: WHK-2026-04812 }
        url: { type: string, format: uri }
        eventos:
          type: array
          items: { $ref: "#/components/schemas/WebhookEvento" }
        activo: { type: boolean, example: true }
        creado: { type: string, format: date-time }
        ultimo_envio: { type: string, format: date-time, nullable: true }
        envios_totales: { type: integer, example: 342 }

    # ---- Payload de webhook saliente (documental) ----
    WebhookPayload:
      type: object
      description: |
        Cuerpo que DEFUNCIA.COM envía (POST) a la URL registrada. Cabeceras -
        `X-Webhook-Signature: sha256={hmac}`, `X-Webhook-Event: {evento}`,
        `User-Agent: DEFUNCIA-Webhooks/1.0`. La firma es
        `HMAC-SHA256(cuerpo_json, secret)`. Reintentos - 3 con backoff
        exponencial; se considera éxito cualquier respuesta 2xx.
      properties:
        evento: { $ref: "#/components/schemas/WebhookEvento" }
        timestamp: { type: string, format: date-time }
        webhook_id: { type: string, example: WHK-2026-04812 }
        data:
          type: object
          description: Datos específicos del evento.
          example:
            caso_id: CAS-2026-00042
            estado: abierto
            portal_url: https://demo.defuncia.com/portal/?token=abc123
            timestamp: "2026-06-20T10:15:00+02:00"

webhooks:

  casoAbierto:
    post:
      summary: Evento caso_abierto / siniestro_abierto
      description: |
        Se dispara al crear un caso (`POST /casos` emite `caso_abierto`;
        `POST /siniestro` emite `siniestro_abierto`).
      requestBody:
        content:
          application/json:
            schema: { $ref: "#/components/schemas/WebhookPayload" }
      responses:
        "200":
          description: El receptor confirma la recepción (cualquier 2xx).

  casoCerrado:
    post:
      summary: Evento caso_cerrado
      description: Se dispara cuando un caso finaliza.
      requestBody:
        content:
          application/json:
            schema: { $ref: "#/components/schemas/WebhookPayload" }
      responses:
        "200":
          description: Recepción confirmada.

  eventosGestion:
    post:
      summary: Eventos de gestión (documentacion_completa, cuenta_resuelta, modulo_completado, documentacion_recibida)
      description: |
        Eventos transversales del ciclo de vida del caso -
        `documentacion_completa`, `cuenta_resuelta`, `modulo_completado` y
        `documentacion_recibida`.
      requestBody:
        content:
          application/json:
            schema: { $ref: "#/components/schemas/WebhookPayload" }
      responses:
        "200":
          description: Recepción confirmada.
