openapi: 3.0.3
info:
  title: Mayan Sync Catrina Control API
  version: 1.0.0
  description: |
    API oficial del tablero editorial ::::mayan::sync::catrina::control::::
    Fuente canónica: final_production_package_control.csv
    Clave primaria: ID_Post | secundaria: URL
    Posts en memoria (sesión): 10 | Columnas modelo: 51 | Endpoints: 10
    Analytics: Streamlit + Plotly / Power BI
    Ingesta CSV: multipart/form-data + upsert por ID_Post
    Observabilidad: Teams webhook + logs JSON
  contact:
    name: alfonso @ terrauniversalis
    email: sistemas@terrauniversalis.mx
servers:
  - url: https://api.terrauniversalis.mx/catrina/v1
    description: Producción
  - url: https://staging.api.terrauniversalis.mx/catrina/v1
    description: Staging
  - url: http://127.0.0.1:8800/api/catrina/v1
    description: Dev Bastion local
security:
  - bearerAuth: []
components:
  securitySchemes:
    bearerAuth:
      type: http
      scheme: bearer
      bearerFormat: JWT
      description: |
        JWT emitido por Azure AD (tenant Terrauniversalis).
        Header: Authorization: Bearer <token>
        Rate limit: 120 req/min por token.
        Roles: editor | revisor | produccion | admin
  schemas:
    CatrinaPost:
      type: object
      required: [ID_Post, Handle, URL]
      properties:
        Handle: { type: string }
        URL: { type: string, format: uri, description: Clave secundaria }
        Tweet_Media_URL: { type: string, format: uri }
        Texto_Completo: { type: string }
        Autor: { type: string }
        De_quien_habla: { type: string }
        Handles_mencionados: { type: string }
        A_quien_va_dirigido: { type: string }
        Tema: { type: string }
        Subtema: { type: string }
        Tipo_de_post: { type: string }
        Requiere_respuesta: { type: string, enum: [Si, No] }
        Requiere_imagen: { type: string, enum: [Si, No] }
        Requiere_video: { type: string, enum: [Si, No] }
        Requiere_verificacion: { type: string, enum: [Si, No] }
        Prioridad: { type: string, enum: [ALTO, MEDIO, BAJO] }
        Estado: { type: string, enum: [PENDIENTE, REVISAR, APROBADO, PROCESADO] }
        Observacion_breve: { type: string }
        Prompt_Imagen: { type: string }
        Prompt_Video: { type: string }
        Idea_Creativa: { type: string }
        Fecha_Post: { type: string, format: date }
        ID_Post: { type: string, description: Clave primaria }
        Link_Asset: { type: string }
        Estado_Produccion: { type: string, enum: [PENDIENTE, GENERADO, PUBLICADO] }
        Duracion_Video: { type: string }
        Hashtags_Sugeridos: { type: string }
        Notas_Produccion: { type: string }
        Community_Principal: { type: string }
        Community_Secundaria: { type: string }
        Verification_Status: { type: string, enum: [VERIFICADO, REVISAR FUENTE, NO VERIFICADO] }
        Verification_Notes: { type: string }
        Panchita_Narration: { type: string }
        QA_Status: { type: string, enum: [PASS, FAIL, REVIEW] }
        QA_Notes: { type: string }
        Batch_Number: { type: string }
        Processing_Order: { type: string }
        Comentario_Editorial: { type: string }
        Decision_Editorial: { type: string }
        Herramienta_Asignada: { type: string }
        Prompt_Imagen_Ajustado: { type: string }
        Prompt_Video_Ajustado: { type: string }
        Estado_Revision: { type: string }
        Revision_Marca: { type: string, enum: [Si, No] }
        Revision_Fuente: { type: string, enum: [Si, No] }
        Aprobado_Imagen: { type: string, enum: [Si, No] }
        Aprobado_Video: { type: string, enum: [Si, No] }
        Link_Respuesta: { type: string }
        Fecha_Revision: { type: string, format: date-time }
        Revisor: { type: string }
        Siguiente_Accion: { type: string }
    PaginatedPosts:
      type: object
      properties:
        data:
          type: array
          items: { $ref: '#/components/schemas/CatrinaPost' }
        pagination:
          type: object
          properties:
            page: { type: integer }
            limit: { type: integer }
            total: { type: integer }
            pages: { type: integer }
    Stats:
      type: object
      properties:
        total: { type: integer }
        por_estado: { type: object, additionalProperties: { type: integer } }
        por_community: { type: object, additionalProperties: { type: integer } }
        qa: { type: object, additionalProperties: { type: integer } }
    IngestResult:
      type: object
      properties:
        inserted: { type: integer }
        updated: { type: integer }
        skipped: { type: integer }
        errors: { type: array, items: { type: object } }
paths:
  /posts:
    get:
      summary: Listar posts
      description: Listado completo con filtros y paginación
      x-roles: [editor, revisor, produccion, admin]
      parameters:
        - { in: query, name: estado, schema: { type: string, enum: [PENDIENTE, REVISAR, APROBADO, PROCESADO] } }
        - { in: query, name: estado_revision, schema: { type: string } }
        - { in: query, name: community, schema: { type: string } }
        - { in: query, name: verification, schema: { type: string } }
        - { in: query, name: qa, schema: { type: string, enum: [PASS, FAIL, REVIEW] } }
        - { in: query, name: batch, schema: { type: string } }
        - { in: query, name: page, schema: { type: integer, default: 1 } }
        - { in: query, name: limit, schema: { type: integer, default: 50 } }
        - { in: query, name: order_by, schema: { type: string, default: Processing_Order } }
        - { in: query, name: order_dir, schema: { type: string, enum: [asc, desc], default: asc } }
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema: { $ref: '#/components/schemas/PaginatedPosts' }
    post:
      summary: Crear post
      description: Ingesta un nuevo post. Idempotente por ID_Post (upsert con ?upsert=1)
      x-roles: [produccion, admin]
      parameters:
        - { in: query, name: upsert, schema: { type: integer, enum: [0, 1] } }
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: '#/components/schemas/CatrinaPost' }
      responses:
        '201':
          description: Creado
  /posts/{ID_Post}:
    parameters:
      - { in: path, name: ID_Post, required: true, schema: { type: string }, description: Clave primaria }
    get:
      summary: Obtener post por ID
      x-roles: [editor, revisor, produccion, admin]
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema:
                type: object
                properties:
                  data: { $ref: '#/components/schemas/CatrinaPost' }
    patch:
      summary: Actualizar post
      description: Actualización parcial
      x-roles: [editor, revisor, produccion, admin]
      requestBody:
        required: true
        content:
          application/json:
            schema: { $ref: '#/components/schemas/CatrinaPost' }
      responses:
        '200':
          description: Actualizado
    delete:
      summary: Eliminar post
      description: Soft-delete por defecto. ?hard=1 requiere rol admin
      x-roles: [admin]
      parameters:
        - { in: query, name: hard, schema: { type: integer, enum: [0, 1] } }
      responses:
        '200':
          description: Eliminado
  /export/csv:
    get:
      summary: Exportar CSV completo (51 columnas)
      x-roles: [revisor, produccion, admin]
      responses:
        '200':
          description: text/csv
          content:
            text/csv: {}
  /export/json:
    get:
      summary: Exportar snapshot JSON completo
      x-roles: [revisor, produccion, admin]
      responses:
        '200':
          description: application/json
          content:
            application/json: {}
  /ingest/csv:
    post:
      summary: Ingesta masiva CSV
      description: multipart/form-data field file — upsert por ID_Post. Compatible con final_production_package_control.csv
      x-roles: [produccion, admin]
      requestBody:
        required: true
        content:
          multipart/form-data:
            schema:
              type: object
              properties:
                file: { type: string, format: binary }
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema: { $ref: '#/components/schemas/IngestResult' }
  /stats:
    get:
      summary: Métricas agregadas (Streamlit / Power BI)
      x-roles: [editor, revisor, produccion, admin]
      responses:
        '200':
          description: OK
          content:
            application/json:
              schema: { $ref: '#/components/schemas/Stats' }
  /webhook/post-updated:
    post:
      summary: Webhook post actualizado
      description: Notificación Power Automate / Teams + logs JSON
      x-roles: [produccion, admin]
      requestBody:
        required: true
        content:
          application/json:
            schema:
              type: object
              properties:
                ID_Post: { type: string }
                evento: { type: string }
      responses:
        '200':
          description: OK
