{
  "openapi": "3.0.3",
  "info": {
    "title": "API de Sereno Fiscal",
    "version": "1.0.0",
    "description": "API pública de solo lectura de Sereno Fiscal: consulta las listas del SAT y OFAC por RFC, la ficha fiscal de tus empresas y recibe avisos automáticos (webhooks) cuando algo cambia.\n\nNunca presenta nada ante el SAT ni expone la e.firma. La llave se manda en el encabezado `Authorization: Bearer srn_live_…`. JSON en UTF-8, fechas en ISO 8601 UTC, dinero como texto decimal.\n\nEs de servidor a servidor: no se puede llamar desde un navegador, porque una llave en el navegador se filtraría.\n\nDocumentación completa: https://serenofiscal.mx/docs/api",
    "contact": {
      "name": "Sereno Fiscal",
      "email": "hola@serenofiscal.mx",
      "url": "https://serenofiscal.mx/docs/api"
    }
  },
  "servers": [
    {
      "url": "https://api.serenofiscal.mx/v1",
      "description": "Producción"
    }
  ],
  "security": [
    {
      "llave": []
    }
  ],
  "tags": [
    {
      "name": "Estado",
      "description": "Salud del servicio y plan de tu llave. No gasta cuota."
    },
    {
      "name": "Listas",
      "description": "Consulta de RFC en las listas del SAT y OFAC. Alcance: listas. Gasta consultas de listas."
    },
    {
      "name": "Empresas",
      "description": "Ficha y datos fiscales de las empresas de tu llave. Alcance: empresas. Cada petición es una lectura."
    },
    {
      "name": "Documentos",
      "description": "Enlaces temporales a los PDF. Alcance: documentos."
    },
    {
      "name": "Eventos",
      "description": "Eventos de los últimos 30 días. Alcance: empresas."
    },
    {
      "name": "Webhooks",
      "description": "Avisos automáticos a tu sistema. Alcance: webhooks."
    },
    {
      "name": "Despacho",
      "description": "Solo para llaves de despacho. Alcance: empresas."
    }
  ],
  "paths": {
    "/estado": {
      "get": {
        "tags": [
          "Estado"
        ],
        "operationId": "estadoApi",
        "summary": "Estado del servicio y cortes de las listas",
        "description": "Sin llave responde si el servicio está sano y la fecha de corte de cada lista. Con llave añade tu plan, los alcances efectivos de la llave, tus límites y lo que llevas gastado hoy. No gasta cuota: úsalo para comprobar la llave y para monitorear.",
        "security": [
          {},
          {
            "llave": []
          }
        ],
        "responses": {
          "200": {
            "description": "Estado actual.",
            "headers": {
              "X-Id-Peticion": {
                "$ref": "#/components/headers/XIdPeticion"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Estado"
                },
                "example": {
                  "estado": "ok",
                  "version": "1",
                  "consultado_en": "2026-10-19T14:03:00Z",
                  "cortes": {
                    "69B": "2026-10-19",
                    "69_NO_LOCALIZADOS": "2026-10-19",
                    "69_CSD_SIN_EFECTOS": "2026-10-19",
                    "69_FIRMES": "2026-10-19",
                    "DOF_49BIS": "2026-10-18",
                    "OFAC_SDN": "2026-10-18"
                  },
                  "plan": {
                    "codigo": "pro",
                    "nombre": "Pro"
                  },
                  "alcances": [
                    "listas",
                    "empresas"
                  ],
                  "limites": {
                    "listas_dia": 10000,
                    "lecturas_dia": 50000,
                    "por_minuto": 120
                  },
                  "uso_hoy": {
                    "listas": 320,
                    "lecturas": 41
                  }
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/E401"
          },
          "403": {
            "$ref": "#/components/responses/E403"
          },
          "429": {
            "$ref": "#/components/responses/E429"
          },
          "500": {
            "$ref": "#/components/responses/E500"
          },
          "503": {
            "$ref": "#/components/responses/E503"
          }
        },
        "x-alcance": "ninguno",
        "x-cuota": "No gasta cuota",
        "x-idempotente": false,
        "x-codeSamples": [
          {
            "lang": "Shell",
            "label": "curl",
            "source": "curl \"https://api.serenofiscal.mx/v1/estado\" \\\n  -H \"Authorization: Bearer $SERENO_LLAVE\""
          }
        ]
      }
    },
    "/listas/{rfc}": {
      "get": {
        "tags": [
          "Listas"
        ],
        "operationId": "consultarRfc",
        "summary": "Revisa un RFC en las listas negras",
        "description": "Busca el RFC en las listas 69-B, 69 (no localizados, CSD sin efectos y créditos firmes), 49 Bis del DOF y OFAC. Devuelve las coincidencias vigentes con su riesgo, la fecha de corte y la fuente. Los RFC genéricos (XAXX010101000 y XEXX010101000) responden en_listas: false.",
        "parameters": [
          {
            "$ref": "#/components/parameters/RfcLista"
          }
        ],
        "responses": {
          "200": {
            "description": "Resultado de la consulta.",
            "headers": {
              "X-Id-Peticion": {
                "$ref": "#/components/headers/XIdPeticion"
              },
              "X-Limite-Dia": {
                "$ref": "#/components/headers/XLimiteDia"
              },
              "X-Restante-Dia": {
                "$ref": "#/components/headers/XRestanteDia"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ResultadoListas"
                },
                "example": {
                  "rfc": "DEL120301AB3",
                  "en_listas": true,
                  "riesgo": "alto",
                  "consultado_en": "2026-10-19T14:03:00Z",
                  "mis_cfdi_recibidos": 3,
                  "coincidencias": [
                    {
                      "lista": "69B",
                      "situacion": "Definitivo",
                      "nombre": "EJEMPLO DELTA SA DE CV",
                      "fecha_publicacion": "2026-06-15",
                      "riesgo": "alto",
                      "corte": "2026-10-19",
                      "fuente_url": "https://fuente.ejemplo.mx/listas/69b.csv"
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/E400"
          },
          "401": {
            "$ref": "#/components/responses/E401"
          },
          "403": {
            "$ref": "#/components/responses/E403"
          },
          "404": {
            "$ref": "#/components/responses/E404"
          },
          "429": {
            "$ref": "#/components/responses/E429"
          },
          "500": {
            "$ref": "#/components/responses/E500"
          },
          "503": {
            "$ref": "#/components/responses/E503"
          }
        },
        "x-alcance": "listas",
        "x-cuota": "1 consulta de listas",
        "x-idempotente": false,
        "x-codeSamples": [
          {
            "lang": "Shell",
            "label": "curl",
            "source": "curl \"https://api.serenofiscal.mx/v1/listas/DEL120301AB3\" \\\n  -H \"Authorization: Bearer $SERENO_LLAVE\""
          }
        ]
      }
    },
    "/listas/lote": {
      "post": {
        "tags": [
          "Listas"
        ],
        "operationId": "consultarLote",
        "summary": "Revisa hasta 500 RFC de una vez",
        "description": "Un resultado por RFC válido y distinto, en el mismo orden en que los mandaste. Los que no tienen forma de RFC salen en invalidos y no se cobran; los repetidos se consultan y se cobran una sola vez. Acepta Idempotency-Key.",
        "parameters": [
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "requestBody": {
          "required": true,
          "description": "Lista de 1 a 500 RFC.",
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "rfcs"
                ],
                "properties": {
                  "rfcs": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "description": "RFC"
                    },
                    "minItems": 1,
                    "maxItems": 500
                  }
                }
              },
              "example": {
                "rfcs": [
                  "DEL120301AB3",
                  "ABC010101AB1",
                  "mal"
                ]
              }
            }
          }
        },
        "responses": {
          "200": {
            "description": "Un resultado por RFC válido.",
            "headers": {
              "X-Id-Peticion": {
                "$ref": "#/components/headers/XIdPeticion"
              },
              "X-Limite-Dia": {
                "$ref": "#/components/headers/XLimiteDia"
              },
              "X-Restante-Dia": {
                "$ref": "#/components/headers/XRestanteDia"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RespuestaLote"
                },
                "example": {
                  "consultados": 2,
                  "con_coincidencia": 1,
                  "invalidos": [
                    "mal"
                  ],
                  "cortes": {
                    "69B": "2026-10-19",
                    "69_NO_LOCALIZADOS": "2026-10-19",
                    "69_CSD_SIN_EFECTOS": "2026-10-19",
                    "69_FIRMES": "2026-10-19",
                    "DOF_49BIS": "2026-10-18",
                    "OFAC_SDN": "2026-10-18"
                  },
                  "consultado_en": "2026-10-19T14:03:00Z",
                  "resultados": [
                    {
                      "rfc": "DEL120301AB3",
                      "en_listas": true,
                      "riesgo": "alto",
                      "mis_cfdi_recibidos": 3,
                      "coincidencias": [
                        {
                          "lista": "69B",
                          "situacion": "Definitivo",
                          "nombre": "EJEMPLO DELTA SA DE CV",
                          "fecha_publicacion": "2026-06-15",
                          "riesgo": "alto"
                        }
                      ]
                    },
                    {
                      "rfc": "ABC010101AB1",
                      "en_listas": false,
                      "riesgo": "ninguno",
                      "mis_cfdi_recibidos": 0,
                      "coincidencias": []
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/E400"
          },
          "401": {
            "$ref": "#/components/responses/E401"
          },
          "403": {
            "$ref": "#/components/responses/E403"
          },
          "409": {
            "$ref": "#/components/responses/E409"
          },
          "429": {
            "$ref": "#/components/responses/E429"
          },
          "500": {
            "$ref": "#/components/responses/E500"
          },
          "503": {
            "$ref": "#/components/responses/E503"
          }
        },
        "x-alcance": "listas",
        "x-cuota": "1 consulta de listas por cada RFC válido y distinto del lote (máximo 500)",
        "x-idempotente": true,
        "x-codeSamples": [
          {
            "lang": "Shell",
            "label": "curl",
            "source": "curl -X POST \"https://api.serenofiscal.mx/v1/listas/lote\" \\\n  -H \"Authorization: Bearer $SERENO_LLAVE\" \\\n  -H \"Content-Type: application/json\" \\\n  -H \"Idempotency-Key: $(uuidgen)\" \\\n  -d '{\"rfcs\":[\"DEL120301AB3\",\"ABC010101AB1\"]}'"
          }
        ]
      }
    },
    "/empresas": {
      "get": {
        "tags": [
          "Empresas"
        ],
        "operationId": "listarEmpresas",
        "summary": "Lista las empresas de la llave con su semáforo",
        "description": "Las empresas que ve la llave (todas las de la cuenta, o las que elegiste al crearla), con semáforo y conteo de alertas abiertas, ordenadas por RFC. Una llave de despacho ve las empresas de todos sus clientes.",
        "parameters": [
          {
            "name": "semaforo",
            "in": "query",
            "required": false,
            "description": "Solo las empresas de ese color.",
            "schema": {
              "type": "string",
              "enum": [
                "rojo",
                "ambar",
                "verde",
                "gris"
              ]
            }
          },
          {
            "name": "activa",
            "in": "query",
            "required": false,
            "description": "true = ya dadas de alta; false = en proceso de alta.",
            "schema": {
              "type": "boolean"
            }
          },
          {
            "$ref": "#/components/parameters/Limite"
          },
          {
            "$ref": "#/components/parameters/Cursor"
          }
        ],
        "responses": {
          "200": {
            "description": "Página de empresas.",
            "headers": {
              "X-Id-Peticion": {
                "$ref": "#/components/headers/XIdPeticion"
              },
              "X-Limite-Dia": {
                "$ref": "#/components/headers/XLimiteDia"
              },
              "X-Restante-Dia": {
                "$ref": "#/components/headers/XRestanteDia"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PaginaEmpresas"
                },
                "example": {
                  "datos": [
                    {
                      "rfc": "ABC010101AB1",
                      "razon_social": "Ejemplo SA de CV",
                      "tipo_persona": "moral",
                      "activa": true,
                      "semaforo": {
                        "color": "ambar",
                        "motivos": [
                          {
                            "codigo": "alertas_altas",
                            "detalle": "2 alertas altas abiertas"
                          }
                        ],
                        "calculado_en": "2026-10-19T13:50:00Z"
                      },
                      "alertas": {
                        "abiertas": 3,
                        "criticas": 0,
                        "altas": 2,
                        "medias": 1
                      }
                    }
                  ],
                  "siguiente_cursor": null
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/E400"
          },
          "401": {
            "$ref": "#/components/responses/E401"
          },
          "403": {
            "$ref": "#/components/responses/E403"
          },
          "404": {
            "$ref": "#/components/responses/E404"
          },
          "429": {
            "$ref": "#/components/responses/E429"
          },
          "500": {
            "$ref": "#/components/responses/E500"
          },
          "503": {
            "$ref": "#/components/responses/E503"
          }
        },
        "x-alcance": "empresas",
        "x-cuota": "1 lectura",
        "x-idempotente": false,
        "x-codeSamples": [
          {
            "lang": "Shell",
            "label": "curl",
            "source": "curl \"https://api.serenofiscal.mx/v1/empresas?semaforo=rojo&limite=50\" \\\n  -H \"Authorization: Bearer $SERENO_LLAVE\""
          }
        ]
      }
    },
    "/empresas/{rfc}": {
      "get": {
        "tags": [
          "Empresas"
        ],
        "operationId": "fichaEmpresa",
        "summary": "Ficha fiscal completa de una empresa",
        "description": "Semáforo con sus motivos, alertas abiertas, declaraciones, opinión de cumplimiento y constancia, resumen de facturas del mes, contrapartes en lista, estado de la e.firma (sin exponer el certificado) y de la sincronización con el SAT. Lo que aún no existe viene como null; nunca se inventan ceros.",
        "parameters": [
          {
            "$ref": "#/components/parameters/Rfc"
          }
        ],
        "responses": {
          "200": {
            "description": "Ficha de la empresa.",
            "headers": {
              "X-Id-Peticion": {
                "$ref": "#/components/headers/XIdPeticion"
              },
              "X-Limite-Dia": {
                "$ref": "#/components/headers/XLimiteDia"
              },
              "X-Restante-Dia": {
                "$ref": "#/components/headers/XRestanteDia"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Ficha"
                },
                "example": {
                  "rfc": "ABC010101AB1",
                  "razon_social": "Ejemplo SA de CV",
                  "tipo_persona": "moral",
                  "activa": true,
                  "semaforo": {
                    "color": "ambar",
                    "motivos": [
                      {
                        "codigo": "alertas_altas",
                        "detalle": "2 alertas altas abiertas"
                      }
                    ],
                    "calculado_en": "2026-10-19T13:50:00Z"
                  },
                  "alertas": {
                    "abiertas": 3,
                    "criticas": 0,
                    "altas": 2,
                    "medias": 1
                  },
                  "declaraciones": {
                    "presentadas": 22,
                    "pendientes_de_lectura": 0,
                    "faltantes": 1,
                    "ultimo_periodo": "2026-09"
                  },
                  "cumplimiento": {
                    "opinion": {
                      "sentido": "positivo",
                      "fecha": "2026-10-02T09:00:00Z"
                    },
                    "constancia": {
                      "fecha": "2026-10-02T09:01:00Z",
                      "estatus_padron": "activo"
                    }
                  },
                  "cfdi": {
                    "total": 4210,
                    "ultima_fecha": "2026-10-18",
                    "mes": {
                      "periodo": "2026-10",
                      "ingresos": "1250300.00",
                      "egresos": "840100.50",
                      "moneda": "MXN"
                    }
                  },
                  "contrapartes_riesgo": {
                    "alto": 1,
                    "medio": 2
                  },
                  "efirma": {
                    "estado": "valida",
                    "vigente_hasta": "2028-03-14"
                  },
                  "sincronizacion": {
                    "ultima_descarga": "2026-10-19T06:10:00Z",
                    "solicitudes_pendientes": 0
                  }
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/E400"
          },
          "401": {
            "$ref": "#/components/responses/E401"
          },
          "403": {
            "$ref": "#/components/responses/E403"
          },
          "404": {
            "$ref": "#/components/responses/E404"
          },
          "429": {
            "$ref": "#/components/responses/E429"
          },
          "500": {
            "$ref": "#/components/responses/E500"
          },
          "503": {
            "$ref": "#/components/responses/E503"
          }
        },
        "x-alcance": "empresas",
        "x-cuota": "1 lectura",
        "x-idempotente": false,
        "x-codeSamples": [
          {
            "lang": "Shell",
            "label": "curl",
            "source": "curl \"https://api.serenofiscal.mx/v1/empresas/ABC010101AB1\" \\\n  -H \"Authorization: Bearer $SERENO_LLAVE\""
          }
        ]
      }
    },
    "/empresas/{rfc}/alertas": {
      "get": {
        "tags": [
          "Empresas"
        ],
        "operationId": "alertasEmpresa",
        "summary": "Alertas de una empresa",
        "description": "De la más reciente a la más antigua. Las notas y responsables internos nunca salen; el detalle trae solo claves de una lista blanca.",
        "parameters": [
          {
            "$ref": "#/components/parameters/Rfc"
          },
          {
            "name": "estado",
            "in": "query",
            "required": false,
            "description": "Solo alertas en ese estado.",
            "schema": {
              "type": "string",
              "enum": [
                "abierta",
                "en_revision",
                "cerrada"
              ]
            }
          },
          {
            "name": "severidad",
            "in": "query",
            "required": false,
            "description": "Solo de esa severidad.",
            "schema": {
              "type": "string",
              "enum": [
                "critica",
                "alta",
                "media"
              ]
            }
          },
          {
            "name": "desde",
            "in": "query",
            "required": false,
            "description": "Solo creadas desde esa fecha (AAAA-MM-DD, hora de México).",
            "schema": {
              "type": "string",
              "format": "date"
            }
          },
          {
            "name": "id",
            "in": "query",
            "required": false,
            "description": "Solo la alerta con ese id.",
            "schema": {
              "type": "string",
              "format": "uuid"
            }
          },
          {
            "$ref": "#/components/parameters/Limite"
          },
          {
            "$ref": "#/components/parameters/Cursor"
          }
        ],
        "responses": {
          "200": {
            "description": "Página de alertas.",
            "headers": {
              "X-Id-Peticion": {
                "$ref": "#/components/headers/XIdPeticion"
              },
              "X-Limite-Dia": {
                "$ref": "#/components/headers/XLimiteDia"
              },
              "X-Restante-Dia": {
                "$ref": "#/components/headers/XRestanteDia"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PaginaAlertas"
                },
                "example": {
                  "datos": [
                    {
                      "id": "3f2e8c1a-5b7d-4e90-a1c2-9d4f6b8e0a12",
                      "regla": "L-02",
                      "origen": "lista",
                      "severidad": "alta",
                      "estado": "abierta",
                      "titulo": "Proveedor en lista 69-B",
                      "rfc_contraparte": "DEL120301AB3",
                      "periodo": "2026-10",
                      "fecha_limite": null,
                      "creada": "2026-10-19T14:02:11Z",
                      "cerrada": null,
                      "detalle": {
                        "lista": "69B",
                        "situacion": "Definitivo",
                        "relacion": "proveedor",
                        "cfdis": 3,
                        "importe_neto": "84500.00"
                      }
                    }
                  ],
                  "siguiente_cursor": null
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/E400"
          },
          "401": {
            "$ref": "#/components/responses/E401"
          },
          "403": {
            "$ref": "#/components/responses/E403"
          },
          "404": {
            "$ref": "#/components/responses/E404"
          },
          "429": {
            "$ref": "#/components/responses/E429"
          },
          "500": {
            "$ref": "#/components/responses/E500"
          },
          "503": {
            "$ref": "#/components/responses/E503"
          }
        },
        "x-alcance": "empresas",
        "x-cuota": "1 lectura",
        "x-idempotente": false,
        "x-codeSamples": [
          {
            "lang": "Shell",
            "label": "curl",
            "source": "curl \"https://api.serenofiscal.mx/v1/empresas/ABC010101AB1/alertas?estado=abierta&severidad=alta\" \\\n  -H \"Authorization: Bearer $SERENO_LLAVE\""
          }
        ]
      }
    },
    "/empresas/{rfc}/declaraciones": {
      "get": {
        "tags": [
          "Empresas"
        ],
        "operationId": "declaracionesEmpresa",
        "summary": "Declaraciones presentadas y faltantes",
        "description": "Las declaraciones presentadas del ejercicio (sin las anuladas) y los periodos con obligación calculada pero sin acuse. Los PDF se piden con la ruta de descarga.",
        "parameters": [
          {
            "$ref": "#/components/parameters/Rfc"
          },
          {
            "name": "ejercicio",
            "in": "query",
            "required": false,
            "description": "Año del ejercicio, de 2000 a 2100. Por omisión, el año en curso (hora de México).",
            "schema": {
              "type": "integer",
              "minimum": 2000,
              "maximum": 2100
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Declaraciones del ejercicio.",
            "headers": {
              "X-Id-Peticion": {
                "$ref": "#/components/headers/XIdPeticion"
              },
              "X-Limite-Dia": {
                "$ref": "#/components/headers/XLimiteDia"
              },
              "X-Restante-Dia": {
                "$ref": "#/components/headers/XRestanteDia"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RespuestaDeclaraciones"
                },
                "example": {
                  "ejercicio": 2026,
                  "presentadas": [
                    {
                      "id": "9b1c4d2e-7a3f-4c55-8e16-2f0a9d7b3c48",
                      "ejercicio": 2026,
                      "periodo": "2026-09",
                      "anual": false,
                      "tipo": "normal",
                      "fuente": "sat",
                      "numero_operacion": "1234567890",
                      "fecha_presentacion": "2026-10-16",
                      "estado_lectura": "leida",
                      "con_pdf": true,
                      "con_acuse": true
                    }
                  ],
                  "faltantes": [
                    {
                      "periodo": "2026-08",
                      "reglas": [
                        "D-01"
                      ]
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/E400"
          },
          "401": {
            "$ref": "#/components/responses/E401"
          },
          "403": {
            "$ref": "#/components/responses/E403"
          },
          "404": {
            "$ref": "#/components/responses/E404"
          },
          "429": {
            "$ref": "#/components/responses/E429"
          },
          "500": {
            "$ref": "#/components/responses/E500"
          },
          "503": {
            "$ref": "#/components/responses/E503"
          }
        },
        "x-alcance": "empresas",
        "x-cuota": "1 lectura",
        "x-idempotente": false,
        "x-codeSamples": [
          {
            "lang": "Shell",
            "label": "curl",
            "source": "curl \"https://api.serenofiscal.mx/v1/empresas/ABC010101AB1/declaraciones?ejercicio=2026\" \\\n  -H \"Authorization: Bearer $SERENO_LLAVE\""
          }
        ]
      }
    },
    "/empresas/{rfc}/cumplimiento": {
      "get": {
        "tags": [
          "Empresas"
        ],
        "operationId": "cumplimientoEmpresa",
        "summary": "Opiniones de cumplimiento y constancias por mes",
        "description": "Metadatos de las opiniones de cumplimiento y constancias de situación fiscal que Sereno ha guardado, del mes más reciente al más antiguo. Para bajar el PDF usa la ruta de descarga.",
        "parameters": [
          {
            "$ref": "#/components/parameters/Rfc"
          },
          {
            "$ref": "#/components/parameters/Limite"
          },
          {
            "$ref": "#/components/parameters/Cursor"
          }
        ],
        "responses": {
          "200": {
            "description": "Página de documentos.",
            "headers": {
              "X-Id-Peticion": {
                "$ref": "#/components/headers/XIdPeticion"
              },
              "X-Limite-Dia": {
                "$ref": "#/components/headers/XLimiteDia"
              },
              "X-Restante-Dia": {
                "$ref": "#/components/headers/XRestanteDia"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PaginaCumplimiento"
                },
                "example": {
                  "datos": [
                    {
                      "id": "9b1c4d2e-7a3f-4c55-8e16-2f0a9d7b3c48",
                      "tipo": "opinion",
                      "mes": "2026-10",
                      "fecha": "2026-10-02T09:00:00Z",
                      "sentido": "positivo",
                      "estatus_padron": null,
                      "folio": "26ABCD0001",
                      "bytes": 48213,
                      "descargable": true
                    },
                    {
                      "id": "5d0e7a21-6c3b-4f18-b9a4-0e2c8d1f7a63",
                      "tipo": "constancia",
                      "mes": "2026-10",
                      "fecha": "2026-10-02T09:01:00Z",
                      "sentido": null,
                      "estatus_padron": "activo",
                      "folio": null,
                      "bytes": 61044,
                      "descargable": true
                    }
                  ],
                  "siguiente_cursor": null
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/E400"
          },
          "401": {
            "$ref": "#/components/responses/E401"
          },
          "403": {
            "$ref": "#/components/responses/E403"
          },
          "404": {
            "$ref": "#/components/responses/E404"
          },
          "429": {
            "$ref": "#/components/responses/E429"
          },
          "500": {
            "$ref": "#/components/responses/E500"
          },
          "503": {
            "$ref": "#/components/responses/E503"
          }
        },
        "x-alcance": "empresas",
        "x-cuota": "1 lectura",
        "x-idempotente": false,
        "x-codeSamples": [
          {
            "lang": "Shell",
            "label": "curl",
            "source": "curl \"https://api.serenofiscal.mx/v1/empresas/ABC010101AB1/cumplimiento\" \\\n  -H \"Authorization: Bearer $SERENO_LLAVE\""
          }
        ]
      }
    },
    "/empresas/{rfc}/cumplimiento/{id}/descarga": {
      "get": {
        "tags": [
          "Documentos"
        ],
        "operationId": "descargarCumplimiento",
        "summary": "Enlace para descargar una opinión o constancia",
        "description": "Devuelve un enlace firmado al PDF que vale 5 minutos. Cada enlace que se emite queda en la bitácora. No sigas el enlace desde un navegador público ni lo guardes: pide uno nuevo cada vez que lo necesites.",
        "parameters": [
          {
            "$ref": "#/components/parameters/Rfc"
          },
          {
            "$ref": "#/components/parameters/IdUuid"
          }
        ],
        "responses": {
          "200": {
            "description": "Enlace temporal.",
            "headers": {
              "X-Id-Peticion": {
                "$ref": "#/components/headers/XIdPeticion"
              },
              "X-Limite-Dia": {
                "$ref": "#/components/headers/XLimiteDia"
              },
              "X-Restante-Dia": {
                "$ref": "#/components/headers/XRestanteDia"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Descarga"
                },
                "example": {
                  "url": "https://descargas.ejemplo.mx/firmado/opinion-2026-10.pdf?token=EJEMPLO",
                  "expira_en": "2026-10-19T14:08:00Z"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/E400"
          },
          "401": {
            "$ref": "#/components/responses/E401"
          },
          "403": {
            "$ref": "#/components/responses/E403"
          },
          "404": {
            "$ref": "#/components/responses/E404"
          },
          "429": {
            "$ref": "#/components/responses/E429"
          },
          "500": {
            "$ref": "#/components/responses/E500"
          },
          "503": {
            "$ref": "#/components/responses/E503"
          }
        },
        "x-alcance": "documentos",
        "x-cuota": "1 lectura",
        "x-idempotente": false,
        "x-codeSamples": [
          {
            "lang": "Shell",
            "label": "curl",
            "source": "curl \"https://api.serenofiscal.mx/v1/empresas/ABC010101AB1/cumplimiento/9b1c4d2e-7a3f-4c55-8e16-2f0a9d7b3c48/descarga\" \\\n  -H \"Authorization: Bearer $SERENO_LLAVE\""
          }
        ]
      }
    },
    "/empresas/{rfc}/declaraciones/{id}/descarga": {
      "get": {
        "tags": [
          "Documentos"
        ],
        "operationId": "descargarDeclaracion",
        "summary": "Enlace para descargar una declaración o su acuse",
        "description": "Igual que la descarga de cumplimiento: enlace firmado de 5 minutos al PDF de la declaración (por omisión) o a su acuse. El id sale de GET /empresas/{rfc}/declaraciones.",
        "parameters": [
          {
            "$ref": "#/components/parameters/Rfc"
          },
          {
            "$ref": "#/components/parameters/IdUuid"
          },
          {
            "name": "tipo",
            "in": "query",
            "required": false,
            "description": "Qué PDF quieres.",
            "schema": {
              "type": "string",
              "enum": [
                "declaracion",
                "acuse"
              ],
              "default": "declaracion"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Enlace temporal.",
            "headers": {
              "X-Id-Peticion": {
                "$ref": "#/components/headers/XIdPeticion"
              },
              "X-Limite-Dia": {
                "$ref": "#/components/headers/XLimiteDia"
              },
              "X-Restante-Dia": {
                "$ref": "#/components/headers/XRestanteDia"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/Descarga"
                },
                "example": {
                  "url": "https://descargas.ejemplo.mx/firmado/declaracion-1234567890.pdf?token=EJEMPLO",
                  "expira_en": "2026-10-19T14:08:00Z"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/E400"
          },
          "401": {
            "$ref": "#/components/responses/E401"
          },
          "403": {
            "$ref": "#/components/responses/E403"
          },
          "404": {
            "$ref": "#/components/responses/E404"
          },
          "429": {
            "$ref": "#/components/responses/E429"
          },
          "500": {
            "$ref": "#/components/responses/E500"
          },
          "503": {
            "$ref": "#/components/responses/E503"
          }
        },
        "x-alcance": "documentos",
        "x-cuota": "1 lectura",
        "x-idempotente": false,
        "x-codeSamples": [
          {
            "lang": "Shell",
            "label": "curl",
            "source": "curl \"https://api.serenofiscal.mx/v1/empresas/ABC010101AB1/declaraciones/9b1c4d2e-7a3f-4c55-8e16-2f0a9d7b3c48/descarga?tipo=acuse\" \\\n  -H \"Authorization: Bearer $SERENO_LLAVE\""
          }
        ]
      }
    },
    "/empresas/{rfc}/cfdi/resumen": {
      "get": {
        "tags": [
          "Empresas"
        ],
        "operationId": "resumenCfdi",
        "summary": "Resumen mensual de facturas",
        "description": "Por mes: facturas emitidas y recibidas (vigentes y canceladas) e ingresos y egresos netos en pesos. Rango máximo de 24 meses; por omisión, los últimos 12. El dinero va como texto decimal con dos decimales.",
        "parameters": [
          {
            "$ref": "#/components/parameters/Rfc"
          },
          {
            "name": "desde",
            "in": "query",
            "required": false,
            "description": "Primer mes, AAAA-MM. Por omisión, 11 meses antes de hasta.",
            "schema": {
              "type": "string",
              "pattern": "^\\d{4}-(0[1-9]|1[0-2])$",
              "example": "2026-01"
            }
          },
          {
            "name": "hasta",
            "in": "query",
            "required": false,
            "description": "Último mes, AAAA-MM. Por omisión, el mes en curso.",
            "schema": {
              "type": "string",
              "pattern": "^\\d{4}-(0[1-9]|1[0-2])$",
              "example": "2026-10"
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Resumen por mes.",
            "headers": {
              "X-Id-Peticion": {
                "$ref": "#/components/headers/XIdPeticion"
              },
              "X-Limite-Dia": {
                "$ref": "#/components/headers/XLimiteDia"
              },
              "X-Restante-Dia": {
                "$ref": "#/components/headers/XRestanteDia"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ResumenCfdi"
                },
                "example": {
                  "desde": "2026-09",
                  "hasta": "2026-10",
                  "moneda": "MXN",
                  "meses": [
                    {
                      "periodo": "2026-09",
                      "emitidos": {
                        "vigentes": 212,
                        "cancelados": 3
                      },
                      "recibidos": {
                        "vigentes": 301,
                        "cancelados": 5
                      },
                      "ingresos": "1190450.00",
                      "egresos": "790220.75"
                    },
                    {
                      "periodo": "2026-10",
                      "emitidos": {
                        "vigentes": 118,
                        "cancelados": 1
                      },
                      "recibidos": {
                        "vigentes": 160,
                        "cancelados": 2
                      },
                      "ingresos": "1250300.00",
                      "egresos": "840100.50"
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/E400"
          },
          "401": {
            "$ref": "#/components/responses/E401"
          },
          "403": {
            "$ref": "#/components/responses/E403"
          },
          "404": {
            "$ref": "#/components/responses/E404"
          },
          "429": {
            "$ref": "#/components/responses/E429"
          },
          "500": {
            "$ref": "#/components/responses/E500"
          },
          "503": {
            "$ref": "#/components/responses/E503"
          }
        },
        "x-alcance": "empresas",
        "x-cuota": "1 lectura",
        "x-idempotente": false,
        "x-codeSamples": [
          {
            "lang": "Shell",
            "label": "curl",
            "source": "curl \"https://api.serenofiscal.mx/v1/empresas/ABC010101AB1/cfdi/resumen?desde=2026-01&hasta=2026-10\" \\\n  -H \"Authorization: Bearer $SERENO_LLAVE\""
          }
        ]
      }
    },
    "/empresas/{rfc}/contrapartes": {
      "get": {
        "tags": [
          "Empresas"
        ],
        "operationId": "contrapartesEmpresa",
        "summary": "Clientes y proveedores en listas",
        "description": "Las contrapartes de la empresa que aparecen en alguna lista, primero las de riesgo alto.",
        "parameters": [
          {
            "$ref": "#/components/parameters/Rfc"
          },
          {
            "name": "riesgo",
            "in": "query",
            "required": false,
            "description": "Solo de ese riesgo.",
            "schema": {
              "type": "string",
              "enum": [
                "alto",
                "medio"
              ]
            }
          },
          {
            "$ref": "#/components/parameters/Limite"
          },
          {
            "$ref": "#/components/parameters/Cursor"
          }
        ],
        "responses": {
          "200": {
            "description": "Página de contrapartes.",
            "headers": {
              "X-Id-Peticion": {
                "$ref": "#/components/headers/XIdPeticion"
              },
              "X-Limite-Dia": {
                "$ref": "#/components/headers/XLimiteDia"
              },
              "X-Restante-Dia": {
                "$ref": "#/components/headers/XRestanteDia"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PaginaContrapartes"
                },
                "example": {
                  "datos": [
                    {
                      "rfc": "DEL120301AB3",
                      "nombre": "EJEMPLO DELTA SA DE CV",
                      "relacion": "proveedor",
                      "riesgo": "alto",
                      "cfdis": 3,
                      "importe_neto": "84500.00",
                      "moneda": "MXN",
                      "ultimo_cfdi": "2026-10-12",
                      "listas": [
                        {
                          "lista": "69B",
                          "situacion": "Definitivo",
                          "fecha_publicacion": "2026-06-15"
                        }
                      ]
                    }
                  ],
                  "siguiente_cursor": null
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/E400"
          },
          "401": {
            "$ref": "#/components/responses/E401"
          },
          "403": {
            "$ref": "#/components/responses/E403"
          },
          "404": {
            "$ref": "#/components/responses/E404"
          },
          "429": {
            "$ref": "#/components/responses/E429"
          },
          "500": {
            "$ref": "#/components/responses/E500"
          },
          "503": {
            "$ref": "#/components/responses/E503"
          }
        },
        "x-alcance": "empresas",
        "x-cuota": "1 lectura",
        "x-idempotente": false,
        "x-codeSamples": [
          {
            "lang": "Shell",
            "label": "curl",
            "source": "curl \"https://api.serenofiscal.mx/v1/empresas/ABC010101AB1/contrapartes?riesgo=alto\" \\\n  -H \"Authorization: Bearer $SERENO_LLAVE\""
          }
        ]
      }
    },
    "/empresas/{rfc}/vencimientos": {
      "get": {
        "tags": [
          "Empresas"
        ],
        "operationId": "vencimientosEmpresa",
        "summary": "Vencimientos próximos",
        "description": "Vencimientos de la e.firma y alertas con fecha límite dentro de la ventana (los ya vencidos también salen). La declaración mensual se agregará cuando se apruebe el calendario por régimen; el campo incluye dice qué tipos entran hoy.",
        "parameters": [
          {
            "$ref": "#/components/parameters/Rfc"
          },
          {
            "name": "dias",
            "in": "query",
            "required": false,
            "description": "Ventana en días, de 1 a 365.",
            "schema": {
              "type": "integer",
              "minimum": 1,
              "maximum": 365,
              "default": 60
            }
          }
        ],
        "responses": {
          "200": {
            "description": "Vencimientos ordenados por fecha.",
            "headers": {
              "X-Id-Peticion": {
                "$ref": "#/components/headers/XIdPeticion"
              },
              "X-Limite-Dia": {
                "$ref": "#/components/headers/XLimiteDia"
              },
              "X-Restante-Dia": {
                "$ref": "#/components/headers/XRestanteDia"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/RespuestaVencimientos"
                },
                "example": {
                  "dias": 60,
                  "incluye": [
                    "efirma",
                    "alertas"
                  ],
                  "vencimientos": [
                    {
                      "tipo": "alerta",
                      "fecha_limite": "2026-10-29",
                      "dias_restantes": 10,
                      "titulo": "Falta la declaración mensual de septiembre",
                      "severidad": "alta",
                      "alerta_id": "3f2e8c1a-5b7d-4e90-a1c2-9d4f6b8e0a12",
                      "regla": "D-01"
                    },
                    {
                      "tipo": "efirma",
                      "fecha_limite": "2026-12-14",
                      "dias_restantes": 56,
                      "titulo": "Vencimiento de la e.firma"
                    }
                  ]
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/E400"
          },
          "401": {
            "$ref": "#/components/responses/E401"
          },
          "403": {
            "$ref": "#/components/responses/E403"
          },
          "404": {
            "$ref": "#/components/responses/E404"
          },
          "429": {
            "$ref": "#/components/responses/E429"
          },
          "500": {
            "$ref": "#/components/responses/E500"
          },
          "503": {
            "$ref": "#/components/responses/E503"
          }
        },
        "x-alcance": "empresas",
        "x-cuota": "1 lectura",
        "x-idempotente": false,
        "x-codeSamples": [
          {
            "lang": "Shell",
            "label": "curl",
            "source": "curl \"https://api.serenofiscal.mx/v1/empresas/ABC010101AB1/vencimientos?dias=60\" \\\n  -H \"Authorization: Bearer $SERENO_LLAVE\""
          }
        ]
      }
    },
    "/eventos": {
      "get": {
        "tags": [
          "Eventos"
        ],
        "operationId": "listarEventos",
        "summary": "Eventos recientes de tus empresas",
        "description": "Los eventos de los últimos 30 días de las empresas de la llave, del más viejo al más nuevo (sin los de prueba). Sirve como disparador en Zapier o Make y para recuperar avisos que tu servidor se perdió. Guarda el id del último evento y pídelo con desde_id para traer solo lo nuevo.",
        "parameters": [
          {
            "name": "desde_id",
            "in": "query",
            "required": false,
            "description": "Solo los eventos posteriores al que tiene este id (evt_…).",
            "schema": {
              "type": "string",
              "pattern": "^evt_[a-z0-9]{10,40}$"
            }
          },
          {
            "$ref": "#/components/parameters/Limite"
          },
          {
            "$ref": "#/components/parameters/Cursor"
          }
        ],
        "responses": {
          "200": {
            "description": "Página de eventos.",
            "headers": {
              "X-Id-Peticion": {
                "$ref": "#/components/headers/XIdPeticion"
              },
              "X-Limite-Dia": {
                "$ref": "#/components/headers/XLimiteDia"
              },
              "X-Restante-Dia": {
                "$ref": "#/components/headers/XRestanteDia"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PaginaEventos"
                },
                "example": {
                  "datos": [
                    {
                      "id": "evt_261019140211a1b2c3d4e5f6",
                      "tipo": "alerta.creada",
                      "creado": "2026-10-19T14:02:11Z",
                      "empresa_rfc": "ABC010101AB1",
                      "datos": {
                        "alerta_id": "3f2e8c1a-5b7d-4e90-a1c2-9d4f6b8e0a12",
                        "severidad": "alta",
                        "regla": "L-02",
                        "titulo": "Proveedor en lista 69-B",
                        "rfc_contraparte": "DEL120301AB3",
                        "fecha_limite": null
                      },
                      "url": "/v1/empresas/ABC010101AB1/alertas?id=3f2e8c1a-5b7d-4e90-a1c2-9d4f6b8e0a12"
                    }
                  ],
                  "siguiente_cursor": null
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/E400"
          },
          "401": {
            "$ref": "#/components/responses/E401"
          },
          "403": {
            "$ref": "#/components/responses/E403"
          },
          "404": {
            "$ref": "#/components/responses/E404"
          },
          "429": {
            "$ref": "#/components/responses/E429"
          },
          "500": {
            "$ref": "#/components/responses/E500"
          },
          "503": {
            "$ref": "#/components/responses/E503"
          }
        },
        "x-alcance": "empresas",
        "x-cuota": "1 lectura",
        "x-idempotente": false,
        "x-codeSamples": [
          {
            "lang": "Shell",
            "label": "curl",
            "source": "curl \"https://api.serenofiscal.mx/v1/eventos?desde_id=evt_261019140211a1b2c3d4e5f6\" \\\n  -H \"Authorization: Bearer $SERENO_LLAVE\""
          }
        ]
      }
    },
    "/webhooks": {
      "post": {
        "tags": [
          "Webhooks"
        ],
        "operationId": "crearWebhook",
        "summary": "Crea un webhook",
        "description": "Registra la dirección de tu sistema para recibir avisos. Responde 201 con el secreto (whsec_…), que se muestra UNA sola vez. Si la llave está limitada a ciertas empresas, el webhook queda limitado a las mismas. Para eventos distintos de ping la llave necesita también el alcance empresas (si no, 403 alcance_insuficiente). La dirección debe ser https, pública, sin usuario ni contraseña, puerto 443, y no puede redirigir. No acepta Idempotency-Key.",
        "requestBody": {
          "required": true,
          "description": "Dirección, eventos y empresas.",
          "content": {
            "application/json": {
              "schema": {
                "type": "object",
                "required": [
                  "url",
                  "eventos"
                ],
                "properties": {
                  "url": {
                    "type": "string",
                    "description": "Dirección https pública que recibirá los avisos.",
                    "maxLength": 500,
                    "format": "uri"
                  },
                  "eventos": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "description": "Tipo de evento",
                      "enum": [
                        "alerta.creada",
                        "alerta.cerrada",
                        "semaforo.cambiado",
                        "documento.nuevo",
                        "contraparte.en_lista",
                        "ping"
                      ]
                    },
                    "minItems": 1
                  },
                  "empresas": {
                    "type": "array",
                    "items": {
                      "type": "string",
                      "description": "RFC"
                    },
                    "maxItems": 500,
                    "description": "RFC de las empresas que quieres cubrir. null = todas las que ve la llave.",
                    "nullable": true
                  }
                }
              },
              "example": {
                "url": "https://erp.ejemplo.mx/sereno",
                "eventos": [
                  "alerta.creada",
                  "semaforo.cambiado",
                  "documento.nuevo"
                ],
                "empresas": null
              }
            }
          }
        },
        "responses": {
          "201": {
            "description": "Webhook creado, con su secreto.",
            "headers": {
              "X-Id-Peticion": {
                "$ref": "#/components/headers/XIdPeticion"
              },
              "X-Limite-Dia": {
                "$ref": "#/components/headers/XLimiteDia"
              },
              "X-Restante-Dia": {
                "$ref": "#/components/headers/XRestanteDia"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WebhookCreado"
                },
                "example": {
                  "id": "c7a5e0d4-1b2f-4a68-93de-5e8f1a2b7c90",
                  "url": "https://erp.ejemplo.mx/sereno",
                  "eventos": [
                    "alerta.creada",
                    "semaforo.cambiado",
                    "documento.nuevo"
                  ],
                  "empresas": null,
                  "activo": true,
                  "pausado_en": null,
                  "motivo_pausa": null,
                  "secreto_prefijo": "whsec_Ab3d",
                  "creado": "2026-10-19T14:00:00Z",
                  "secreto": "whsec_EJEMPLO_no_es_un_secreto"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/E400"
          },
          "401": {
            "$ref": "#/components/responses/E401"
          },
          "403": {
            "$ref": "#/components/responses/E403"
          },
          "409": {
            "$ref": "#/components/responses/E409"
          },
          "429": {
            "$ref": "#/components/responses/E429"
          },
          "500": {
            "$ref": "#/components/responses/E500"
          },
          "503": {
            "$ref": "#/components/responses/E503"
          }
        },
        "x-alcance": "webhooks",
        "x-cuota": "1 lectura",
        "x-idempotente": false,
        "x-codeSamples": [
          {
            "lang": "Shell",
            "label": "curl",
            "source": "curl -X POST \"https://api.serenofiscal.mx/v1/webhooks\" \\\n  -H \"Authorization: Bearer $SERENO_LLAVE\" \\\n  -H \"Content-Type: application/json\" \\\n  -d '{\"url\":\"https://erp.ejemplo.mx/sereno\",\"eventos\":[\"alerta.creada\",\"semaforo.cambiado\",\"documento.nuevo\"],\"empresas\":null}'"
          }
        ]
      },
      "get": {
        "tags": [
          "Webhooks"
        ],
        "operationId": "listarWebhooks",
        "summary": "Lista tus webhooks",
        "description": "Los webhooks que ve la llave. Nunca incluye el secreto, solo su prefijo. La lista no se pagina.",
        "responses": {
          "200": {
            "description": "Webhooks de la llave.",
            "headers": {
              "X-Id-Peticion": {
                "$ref": "#/components/headers/XIdPeticion"
              },
              "X-Limite-Dia": {
                "$ref": "#/components/headers/XLimiteDia"
              },
              "X-Restante-Dia": {
                "$ref": "#/components/headers/XRestanteDia"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/ListaWebhooks"
                },
                "example": {
                  "datos": [
                    {
                      "id": "c7a5e0d4-1b2f-4a68-93de-5e8f1a2b7c90",
                      "url": "https://erp.ejemplo.mx/sereno",
                      "eventos": [
                        "alerta.creada",
                        "semaforo.cambiado",
                        "documento.nuevo"
                      ],
                      "empresas": null,
                      "activo": true,
                      "pausado_en": null,
                      "motivo_pausa": null,
                      "secreto_prefijo": "whsec_Ab3d",
                      "creado": "2026-10-19T14:00:00Z"
                    }
                  ],
                  "siguiente": null
                }
              }
            }
          },
          "401": {
            "$ref": "#/components/responses/E401"
          },
          "403": {
            "$ref": "#/components/responses/E403"
          },
          "429": {
            "$ref": "#/components/responses/E429"
          },
          "500": {
            "$ref": "#/components/responses/E500"
          },
          "503": {
            "$ref": "#/components/responses/E503"
          }
        },
        "x-alcance": "webhooks",
        "x-cuota": "1 lectura",
        "x-idempotente": false,
        "x-codeSamples": [
          {
            "lang": "Shell",
            "label": "curl",
            "source": "curl \"https://api.serenofiscal.mx/v1/webhooks\" \\\n  -H \"Authorization: Bearer $SERENO_LLAVE\""
          }
        ]
      }
    },
    "/webhooks/{id}": {
      "delete": {
        "tags": [
          "Webhooks"
        ],
        "operationId": "borrarWebhook",
        "summary": "Borra un webhook",
        "description": "Deja de enviar avisos a esa dirección y borra su historial de entregas.",
        "parameters": [
          {
            "$ref": "#/components/parameters/IdWebhook"
          }
        ],
        "responses": {
          "200": {
            "description": "Webhook borrado.",
            "headers": {
              "X-Id-Peticion": {
                "$ref": "#/components/headers/XIdPeticion"
              },
              "X-Limite-Dia": {
                "$ref": "#/components/headers/XLimiteDia"
              },
              "X-Restante-Dia": {
                "$ref": "#/components/headers/XRestanteDia"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/WebhookBorrado"
                },
                "example": {
                  "id": "c7a5e0d4-1b2f-4a68-93de-5e8f1a2b7c90",
                  "borrado": true
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/E400"
          },
          "401": {
            "$ref": "#/components/responses/E401"
          },
          "403": {
            "$ref": "#/components/responses/E403"
          },
          "404": {
            "$ref": "#/components/responses/E404"
          },
          "429": {
            "$ref": "#/components/responses/E429"
          },
          "500": {
            "$ref": "#/components/responses/E500"
          },
          "503": {
            "$ref": "#/components/responses/E503"
          }
        },
        "x-alcance": "webhooks",
        "x-cuota": "1 lectura",
        "x-idempotente": false,
        "x-codeSamples": [
          {
            "lang": "Shell",
            "label": "curl",
            "source": "curl -X DELETE \"https://api.serenofiscal.mx/v1/webhooks/c7a5e0d4-1b2f-4a68-93de-5e8f1a2b7c90\" \\\n  -H \"Authorization: Bearer $SERENO_LLAVE\""
          }
        ]
      }
    },
    "/webhooks/{id}/probar": {
      "post": {
        "tags": [
          "Webhooks"
        ],
        "operationId": "probarWebhook",
        "summary": "Manda un aviso de prueba",
        "description": "Crea un evento ping y lo entrega a ese webhook en el siguiente minuto, firmado igual que los reales. Responde 202. Si el webhook está pausado responde 409. Acepta Idempotency-Key.",
        "parameters": [
          {
            "$ref": "#/components/parameters/IdWebhook"
          },
          {
            "$ref": "#/components/parameters/IdempotencyKey"
          }
        ],
        "responses": {
          "202": {
            "description": "Prueba en cola.",
            "headers": {
              "X-Id-Peticion": {
                "$ref": "#/components/headers/XIdPeticion"
              },
              "X-Limite-Dia": {
                "$ref": "#/components/headers/XLimiteDia"
              },
              "X-Restante-Dia": {
                "$ref": "#/components/headers/XRestanteDia"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PruebaWebhook"
                },
                "example": {
                  "evento_id": "evt_261019140500f6e5d4c3b2a1",
                  "webhook_id": "c7a5e0d4-1b2f-4a68-93de-5e8f1a2b7c90",
                  "estado": "pendiente"
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/E400"
          },
          "401": {
            "$ref": "#/components/responses/E401"
          },
          "403": {
            "$ref": "#/components/responses/E403"
          },
          "404": {
            "$ref": "#/components/responses/E404"
          },
          "409": {
            "$ref": "#/components/responses/E409"
          },
          "429": {
            "$ref": "#/components/responses/E429"
          },
          "500": {
            "$ref": "#/components/responses/E500"
          },
          "503": {
            "$ref": "#/components/responses/E503"
          }
        },
        "x-alcance": "webhooks",
        "x-cuota": "1 lectura",
        "x-idempotente": true,
        "x-codeSamples": [
          {
            "lang": "Shell",
            "label": "curl",
            "source": "curl -X POST \"https://api.serenofiscal.mx/v1/webhooks/c7a5e0d4-1b2f-4a68-93de-5e8f1a2b7c90/probar\" \\\n  -H \"Authorization: Bearer $SERENO_LLAVE\" \\\n  -H \"Idempotency-Key: $(uuidgen)\""
          }
        ]
      }
    },
    "/webhooks/{id}/entregas": {
      "get": {
        "tags": [
          "Webhooks"
        ],
        "operationId": "entregasWebhook",
        "summary": "Entregas de un webhook",
        "description": "El historial de envíos a ese webhook, del más reciente al más antiguo: estado, intentos, código HTTP de tu servidor y cuándo se reintenta.",
        "parameters": [
          {
            "$ref": "#/components/parameters/IdWebhook"
          },
          {
            "$ref": "#/components/parameters/Limite"
          },
          {
            "$ref": "#/components/parameters/Cursor"
          }
        ],
        "responses": {
          "200": {
            "description": "Página de entregas.",
            "headers": {
              "X-Id-Peticion": {
                "$ref": "#/components/headers/XIdPeticion"
              },
              "X-Limite-Dia": {
                "$ref": "#/components/headers/XLimiteDia"
              },
              "X-Restante-Dia": {
                "$ref": "#/components/headers/XRestanteDia"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PaginaEntregas"
                },
                "example": {
                  "datos": [
                    {
                      "id": 1042,
                      "evento_id": "evt_261019140211a1b2c3d4e5f6",
                      "tipo": "alerta.creada",
                      "estado": "entregada",
                      "intento": 1,
                      "http_status": 200,
                      "error": null,
                      "creada": "2026-10-19T14:02:11Z",
                      "ultimo_intento": "2026-10-19T14:02:12Z",
                      "proximo_intento": null
                    }
                  ],
                  "siguiente_cursor": null
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/E400"
          },
          "401": {
            "$ref": "#/components/responses/E401"
          },
          "403": {
            "$ref": "#/components/responses/E403"
          },
          "404": {
            "$ref": "#/components/responses/E404"
          },
          "429": {
            "$ref": "#/components/responses/E429"
          },
          "500": {
            "$ref": "#/components/responses/E500"
          },
          "503": {
            "$ref": "#/components/responses/E503"
          }
        },
        "x-alcance": "webhooks",
        "x-cuota": "1 lectura",
        "x-idempotente": false,
        "x-codeSamples": [
          {
            "lang": "Shell",
            "label": "curl",
            "source": "curl \"https://api.serenofiscal.mx/v1/webhooks/c7a5e0d4-1b2f-4a68-93de-5e8f1a2b7c90/entregas?limite=20\" \\\n  -H \"Authorization: Bearer $SERENO_LLAVE\""
          }
        ]
      }
    },
    "/despacho/clientes": {
      "get": {
        "tags": [
          "Despacho"
        ],
        "operationId": "clientesDespacho",
        "summary": "Clientes de un despacho",
        "description": "Solo para llaves de despacho: una fila por cliente con semáforo agregado (el peor color de sus empresas), alertas abiertas y última descarga. Si la llave se limitó a ciertas empresas, solo salen los clientes que las tienen. Con una llave de cuenta responde 403.",
        "parameters": [
          {
            "$ref": "#/components/parameters/Limite"
          },
          {
            "$ref": "#/components/parameters/Cursor"
          }
        ],
        "responses": {
          "200": {
            "description": "Página de clientes.",
            "headers": {
              "X-Id-Peticion": {
                "$ref": "#/components/headers/XIdPeticion"
              },
              "X-Limite-Dia": {
                "$ref": "#/components/headers/XLimiteDia"
              },
              "X-Restante-Dia": {
                "$ref": "#/components/headers/XRestanteDia"
              }
            },
            "content": {
              "application/json": {
                "schema": {
                  "$ref": "#/components/schemas/PaginaClientes"
                },
                "example": {
                  "datos": [
                    {
                      "cliente_id": "a14d9e6b-2c70-4f3a-8b15-7e0d3c9f6a21",
                      "nombre": "Ejemplo Comercial",
                      "rfc": "ABC010101AB1",
                      "razon_social": "Ejemplo SA de CV",
                      "empresas": 2,
                      "semaforo": {
                        "color": "ambar",
                        "motivos": [
                          "alertas_altas"
                        ]
                      },
                      "alertas": {
                        "criticas": 0,
                        "altas": 2,
                        "medias": 1
                      },
                      "ultima_descarga": "2026-10-19T06:10:00Z"
                    }
                  ],
                  "siguiente_cursor": null
                }
              }
            }
          },
          "400": {
            "$ref": "#/components/responses/E400"
          },
          "401": {
            "$ref": "#/components/responses/E401"
          },
          "403": {
            "$ref": "#/components/responses/E403"
          },
          "404": {
            "$ref": "#/components/responses/E404"
          },
          "429": {
            "$ref": "#/components/responses/E429"
          },
          "500": {
            "$ref": "#/components/responses/E500"
          },
          "503": {
            "$ref": "#/components/responses/E503"
          }
        },
        "x-alcance": "empresas",
        "x-cuota": "1 lectura",
        "x-idempotente": false,
        "x-codeSamples": [
          {
            "lang": "Shell",
            "label": "curl",
            "source": "curl \"https://api.serenofiscal.mx/v1/despacho/clientes\" \\\n  -H \"Authorization: Bearer $SERENO_LLAVE\""
          }
        ]
      }
    }
  },
  "components": {
    "securitySchemes": {
      "llave": {
        "type": "http",
        "scheme": "bearer",
        "bearerFormat": "srn_live_ + 43 caracteres",
        "description": "Llave de la cuenta o del despacho. Se crea en Cuenta > API y se muestra una sola vez."
      }
    },
    "parameters": {
      "Rfc": {
        "name": "rfc",
        "in": "path",
        "required": true,
        "description": "RFC de 12 o 13 caracteres. Se acepta en minúsculas y con espacios en los bordes.",
        "schema": {
          "type": "string",
          "pattern": "^[A-ZÑ&]{3,4}[0-9]{6}[A-Z0-9]{3}$",
          "example": "ABC010101AB1"
        }
      },
      "RfcLista": {
        "name": "rfc",
        "in": "path",
        "required": true,
        "description": "RFC que quieres revisar en las listas.",
        "schema": {
          "type": "string",
          "pattern": "^[A-ZÑ&]{3,4}[0-9]{6}[A-Z0-9]{3}$",
          "example": "DEL120301AB3"
        }
      },
      "IdUuid": {
        "name": "id",
        "in": "path",
        "required": true,
        "description": "Identificador (UUID).",
        "schema": {
          "type": "string",
          "format": "uuid"
        }
      },
      "IdWebhook": {
        "name": "id",
        "in": "path",
        "required": true,
        "description": "Identificador del webhook.",
        "schema": {
          "type": "string",
          "format": "uuid",
          "example": "c7a5e0d4-1b2f-4a68-93de-5e8f1a2b7c90"
        }
      },
      "Limite": {
        "name": "limite",
        "in": "query",
        "required": false,
        "description": "Cuántos resultados por página, de 1 a 200.",
        "schema": {
          "type": "integer",
          "minimum": 1,
          "maximum": 200,
          "default": 100
        }
      },
      "Cursor": {
        "name": "cursor",
        "in": "query",
        "required": false,
        "description": "El siguiente_cursor de la página anterior. Solo sirve con los mismos filtros.",
        "schema": {
          "type": "string"
        }
      },
      "IdempotencyKey": {
        "name": "Idempotency-Key",
        "in": "header",
        "required": false,
        "description": "Hasta 64 caracteres (letras, números y . _ : ~ -). Repetir la misma petición con la misma clave devuelve la misma respuesta y no vuelve a gastar cuota; la misma clave con otro cuerpo da 409. Se guarda 24 horas.",
        "schema": {
          "type": "string",
          "maxLength": 64,
          "pattern": "^[A-Za-z0-9._:~-]{1,64}$"
        }
      }
    },
    "headers": {
      "XIdPeticion": {
        "description": "Identificador de la petición (el mismo id_peticion de los errores).",
        "schema": {
          "type": "string",
          "example": "req_8f2c1a9e04"
        }
      },
      "XLimiteDia": {
        "description": "Tope diario del grupo de cuota que gastó esta petición.",
        "schema": {
          "type": "integer",
          "example": 10000
        }
      },
      "XRestanteDia": {
        "description": "Cuánto queda hoy en ese grupo de cuota.",
        "schema": {
          "type": "integer",
          "example": 9680
        }
      }
    },
    "responses": {
      "E400": {
        "description": "Algún parámetro o el cuerpo no es válido.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "error": {
                "codigo": "parametro_invalido",
                "mensaje": "\"limite\" debe ser un número entre 1 y 200.",
                "id_peticion": "req_8f2c1a9e04"
              }
            }
          }
        }
      },
      "E401": {
        "description": "Falta la llave, no tiene la forma correcta, no existe, está revocada o venció. Todos estos casos dan el mismo 401.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "error": {
                "codigo": "llave_invalida",
                "mensaje": "La llave no es válida, está revocada o venció.",
                "id_peticion": "req_8f2c1a9e04"
              }
            }
          }
        }
      },
      "E403": {
        "description": "La llave no tiene el alcance que pide la ruta (alcance_insuficiente), el paquete no incluye la API (plan_sin_api) o la cuenta está suspendida (cuenta_suspendida).",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "error": {
                "codigo": "alcance_insuficiente",
                "mensaje": "Esta llave no tiene el alcance \"empresas\". Créala de nuevo con ese alcance desde el panel.",
                "id_peticion": "req_8f2c1a9e04"
              }
            }
          }
        }
      },
      "E404": {
        "description": "No existe, o no es de las empresas de tu llave: un RFC ajeno responde igual que uno inexistente.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "error": {
                "codigo": "no_encontrado",
                "mensaje": "No encontramos ese recurso.",
                "id_peticion": "req_8f2c1a9e04"
              }
            }
          }
        }
      },
      "E405": {
        "description": "La ruta existe pero no acepta ese método.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "error": {
                "codigo": "metodo_no_permitido",
                "mensaje": "Ese método no se acepta en esta ruta.",
                "id_peticion": "req_8f2c1a9e04"
              }
            }
          }
        }
      },
      "E409": {
        "description": "La Idempotency-Key ya se usó con otro cuerpo o su primera petición sigue en curso (idempotencia_conflicto), o el recurso no está en un estado que lo permita (conflicto): por ejemplo, ya tienes el máximo de webhooks del plan.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "error": {
                "codigo": "idempotencia_conflicto",
                "mensaje": "Esa Idempotency-Key ya se usó con otro cuerpo. Usa una clave nueva.",
                "id_peticion": "req_8f2c1a9e04"
              }
            }
          }
        }
      },
      "E429": {
        "description": "Pasaste el límite diario (limite_diario) o el de peticiones por minuto (limite_minuto). Espera lo que diga Retry-After.",
        "headers": {
          "Retry-After": {
            "description": "Segundos que faltan para poder reintentar.",
            "schema": {
              "type": "integer"
            }
          },
          "X-Limite-Dia": {
            "$ref": "#/components/headers/XLimiteDia"
          },
          "X-Restante-Dia": {
            "$ref": "#/components/headers/XRestanteDia"
          }
        },
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "error": {
                "codigo": "limite_diario",
                "mensaje": "Llegaste a 1,000 consultas de listas hoy; se reinicia a las 00:00 (hora de México).",
                "id_peticion": "req_8f2c1a9e04",
                "reintentar_en": 5400
              }
            }
          }
        }
      },
      "E500": {
        "description": "Algo falló de nuestro lado. Reintenta; si sigue, escríbenos con el id_peticion.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "error": {
                "codigo": "error_interno",
                "mensaje": "Algo falló de nuestro lado. Inténtalo de nuevo y, si sigue, escríbenos con el id_peticion.",
                "id_peticion": "req_8f2c1a9e04"
              }
            }
          }
        }
      },
      "E503": {
        "description": "El servicio no está disponible un momento. Reintenta con espera creciente.",
        "content": {
          "application/json": {
            "schema": {
              "$ref": "#/components/schemas/Error"
            },
            "example": {
              "error": {
                "codigo": "no_disponible",
                "mensaje": "El servicio no está disponible un momento. Inténtalo de nuevo.",
                "id_peticion": "req_8f2c1a9e04"
              }
            }
          }
        }
      }
    },
    "schemas": {
      "Error": {
        "type": "object",
        "required": [
          "error"
        ],
        "properties": {
          "error": {
            "type": "object",
            "required": [
              "codigo",
              "mensaje",
              "id_peticion"
            ],
            "properties": {
              "codigo": {
                "type": "string",
                "description": "Código estable que tu programa puede comparar.",
                "enum": [
                  "parametro_invalido",
                  "llave_invalida",
                  "alcance_insuficiente",
                  "plan_sin_api",
                  "cuenta_suspendida",
                  "no_encontrado",
                  "metodo_no_permitido",
                  "idempotencia_conflicto",
                  "conflicto",
                  "limite_diario",
                  "limite_minuto",
                  "error_interno",
                  "no_disponible"
                ]
              },
              "mensaje": {
                "type": "string",
                "description": "Explicación en español, lista para mostrarse a una persona."
              },
              "id_peticion": {
                "type": "string",
                "description": "Identificador de la petición (también viaja en el encabezado X-Id-Peticion). Menciónalo si nos escribes.",
                "example": "req_8f2c1a9e04"
              },
              "reintentar_en": {
                "type": "integer",
                "description": "Solo en los 429: segundos que faltan para poder reintentar (igual que Retry-After)."
              }
            }
          }
        }
      },
      "Cortes": {
        "type": "object",
        "description": "Fecha de la última actualización de cada lista. Si algún corte tiene más de 3 días, GET /estado marca el servicio como degradado.",
        "properties": {
          "69B": {
            "type": "string",
            "format": "date",
            "description": "Fecha de corte de la lista 69-B.",
            "nullable": true
          },
          "69_NO_LOCALIZADOS": {
            "type": "string",
            "format": "date",
            "description": "Corte de los no localizados (art. 69).",
            "nullable": true
          },
          "69_CSD_SIN_EFECTOS": {
            "type": "string",
            "format": "date",
            "description": "Corte de CSD sin efectos (art. 69).",
            "nullable": true
          },
          "69_FIRMES": {
            "type": "string",
            "format": "date",
            "description": "Corte de créditos firmes (art. 69).",
            "nullable": true
          },
          "DOF_49BIS": {
            "type": "string",
            "format": "date",
            "description": "Corte de la lista del art. 49 Bis publicada en el DOF.",
            "nullable": true
          },
          "OFAC_SDN": {
            "type": "string",
            "format": "date",
            "description": "Corte de la lista OFAC SDN.",
            "nullable": true
          }
        }
      },
      "Coincidencia": {
        "type": "object",
        "required": [
          "lista",
          "riesgo"
        ],
        "properties": {
          "lista": {
            "type": "string",
            "description": "Código de la lista (congelado en v1).",
            "enum": [
              "69B",
              "69_NO_LOCALIZADOS",
              "69_CSD_SIN_EFECTOS",
              "69_FIRMES",
              "DOF_49BIS",
              "OFAC_SDN"
            ]
          },
          "situacion": {
            "type": "string",
            "description": "Situación del contribuyente en esa lista, como la publica el SAT (por ejemplo Definitivo, Presunto, Desvirtuado)."
          },
          "nombre": {
            "type": "string",
            "description": "Nombre o razón social tal como aparece en la lista."
          },
          "fecha_publicacion": {
            "type": "string",
            "format": "date",
            "description": "Fecha de publicación del registro."
          },
          "riesgo": {
            "type": "string",
            "description": "Riesgo de esa coincidencia.",
            "enum": [
              "alto",
              "medio",
              "ninguno"
            ]
          },
          "corte": {
            "type": "string",
            "format": "date",
            "description": "Corte de la lista (solo en GET /listas/{rfc})."
          },
          "fuente_url": {
            "type": "string",
            "description": "Dirección del archivo de origen (solo en GET /listas/{rfc})."
          },
          "supuesto_csd": {
            "type": "string",
            "description": "Supuesto de cancelación del CSD (solo en la lista 69_CSD_SIN_EFECTOS)."
          }
        }
      },
      "ResultadoListas": {
        "type": "object",
        "required": [
          "rfc",
          "en_listas",
          "riesgo",
          "consultado_en",
          "coincidencias"
        ],
        "properties": {
          "rfc": {
            "type": "string",
            "description": "RFC consultado, en mayúsculas.",
            "example": "DEL120301AB3"
          },
          "en_listas": {
            "type": "boolean",
            "description": "true si aparece en al menos una lista."
          },
          "riesgo": {
            "type": "string",
            "description": "El riesgo más alto entre sus coincidencias.",
            "enum": [
              "alto",
              "medio",
              "ninguno"
            ]
          },
          "consultado_en": {
            "type": "string",
            "format": "date-time",
            "description": "Cuándo se hizo la consulta (UTC)."
          },
          "mis_cfdi_recibidos": {
            "type": "integer",
            "description": "Facturas vigentes que ese RFC te emitió, contadas SOLO en las empresas que ve tu llave. Solo viene si la llave tiene el alcance empresas."
          },
          "coincidencias": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Coincidencia"
            }
          }
        }
      },
      "ResultadoLote": {
        "type": "object",
        "required": [
          "rfc",
          "en_listas",
          "riesgo",
          "coincidencias"
        ],
        "properties": {
          "rfc": {
            "type": "string",
            "description": "RFC consultado, en mayúsculas."
          },
          "en_listas": {
            "type": "boolean",
            "description": "true si aparece en al menos una lista."
          },
          "riesgo": {
            "type": "string",
            "description": "El riesgo más alto entre sus coincidencias.",
            "enum": [
              "alto",
              "medio",
              "ninguno"
            ]
          },
          "mis_cfdi_recibidos": {
            "type": "integer",
            "description": "Igual que en GET /listas/{rfc}; solo con el alcance empresas."
          },
          "coincidencias": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Coincidencia"
            },
            "description": "En el lote cada coincidencia trae lo esencial (sin corte ni fuente_url; esos van en cortes)."
          }
        }
      },
      "RespuestaLote": {
        "type": "object",
        "required": [
          "consultados",
          "con_coincidencia",
          "invalidos",
          "cortes",
          "consultado_en",
          "resultados"
        ],
        "properties": {
          "consultados": {
            "type": "integer",
            "description": "Cuántos RFC válidos y distintos se consultaron (los que se cobran)."
          },
          "con_coincidencia": {
            "type": "integer",
            "description": "Cuántos de ellos aparecen en alguna lista."
          },
          "invalidos": {
            "type": "array",
            "items": {
              "type": "string",
              "description": "Entrada recibida"
            },
            "description": "Las entradas que no tienen forma de RFC, tal como llegaron (hasta 40 caracteres cada una). No se cobran."
          },
          "cortes": {
            "$ref": "#/components/schemas/Cortes"
          },
          "consultado_en": {
            "type": "string",
            "format": "date-time",
            "description": "Cuándo se hizo la consulta (UTC)."
          },
          "resultados": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ResultadoLote"
            },
            "description": "Un resultado por RFC válido, sin repetidos y en el orden en que los mandaste."
          }
        }
      },
      "MotivoSemaforo": {
        "type": "object",
        "required": [
          "codigo",
          "detalle"
        ],
        "properties": {
          "codigo": {
            "type": "string",
            "description": "Motivo del color.",
            "enum": [
              "alerta_critica",
              "efirma_vencida",
              "efirma_rechazada",
              "sin_descarga",
              "en_alta",
              "alertas_altas",
              "alertas_medias",
              "efirma_por_vencer",
              "sin_datos",
              "declaracion_vencida"
            ]
          },
          "detalle": {
            "type": "string",
            "description": "Explicación breve en español."
          }
        }
      },
      "Semaforo": {
        "type": "object",
        "required": [
          "color",
          "motivos",
          "calculado_en"
        ],
        "properties": {
          "color": {
            "type": "string",
            "description": "rojo = alerta crítica abierta, e.firma vencida o descarga detenida; ámbar = alerta alta o media, e.firma a menos de 60 días, 7 días sin datos o declaración vencida; gris = empresa en alta; verde = lo demás.",
            "enum": [
              "rojo",
              "ambar",
              "verde",
              "gris"
            ]
          },
          "motivos": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/MotivoSemaforo"
            }
          },
          "calculado_en": {
            "type": "string",
            "format": "date-time",
            "description": "Cuándo se calculó (siempre al momento de la petición)."
          }
        }
      },
      "ResumenAlertas": {
        "type": "object",
        "required": [
          "abiertas",
          "criticas",
          "altas",
          "medias"
        ],
        "properties": {
          "abiertas": {
            "type": "integer",
            "description": "Alertas sin cerrar."
          },
          "criticas": {
            "type": "integer",
            "description": "Abiertas de severidad crítica."
          },
          "altas": {
            "type": "integer",
            "description": "Abiertas de severidad alta."
          },
          "medias": {
            "type": "integer",
            "description": "Abiertas de severidad media."
          }
        }
      },
      "Empresa": {
        "type": "object",
        "required": [
          "rfc",
          "razon_social",
          "tipo_persona",
          "activa",
          "semaforo",
          "alertas"
        ],
        "properties": {
          "rfc": {
            "type": "string",
            "description": "RFC de la empresa.",
            "example": "ABC010101AB1"
          },
          "razon_social": {
            "type": "string",
            "description": "Razón social."
          },
          "tipo_persona": {
            "type": "string",
            "description": "Moral (RFC de 12) o física (RFC de 13).",
            "enum": [
              "moral",
              "fisica"
            ]
          },
          "activa": {
            "type": "boolean",
            "description": "false mientras la empresa está en proceso de alta."
          },
          "semaforo": {
            "$ref": "#/components/schemas/Semaforo"
          },
          "alertas": {
            "$ref": "#/components/schemas/ResumenAlertas"
          }
        }
      },
      "Ficha": {
        "allOf": [
          {
            "$ref": "#/components/schemas/Empresa"
          },
          {
            "type": "object",
            "required": [
              "declaraciones",
              "cumplimiento",
              "cfdi",
              "contrapartes_riesgo",
              "efirma",
              "sincronizacion"
            ],
            "properties": {
              "declaraciones": {
                "type": "object",
                "properties": {
                  "presentadas": {
                    "type": "integer",
                    "description": "Declaraciones presentadas (sin las anuladas)."
                  },
                  "pendientes_de_lectura": {
                    "type": "integer",
                    "description": "Declaraciones que Sereno aún no termina de leer."
                  },
                  "faltantes": {
                    "type": "integer",
                    "description": "Periodos con obligación calculada y sin acuse."
                  },
                  "ultimo_periodo": {
                    "type": "string",
                    "description": "Último periodo presentado, AAAA-MM.",
                    "nullable": true
                  }
                }
              },
              "cumplimiento": {
                "type": "object",
                "properties": {
                  "opinion": {
                    "type": "object",
                    "properties": {
                      "sentido": {
                        "type": "string",
                        "description": "Sentido de la opinión de cumplimiento (positivo, negativo…)."
                      },
                      "fecha": {
                        "type": "string",
                        "format": "date-time",
                        "description": "Fecha del documento."
                      }
                    },
                    "nullable": true
                  },
                  "constancia": {
                    "type": "object",
                    "properties": {
                      "fecha": {
                        "type": "string",
                        "format": "date-time",
                        "description": "Fecha del documento."
                      },
                      "estatus_padron": {
                        "type": "string",
                        "description": "Estatus en el padrón de contribuyentes."
                      }
                    },
                    "nullable": true
                  }
                }
              },
              "cfdi": {
                "type": "object",
                "properties": {
                  "total": {
                    "type": "integer",
                    "description": "Facturas guardadas de la empresa."
                  },
                  "ultima_fecha": {
                    "type": "string",
                    "format": "date",
                    "description": "Fecha de la última factura.",
                    "nullable": true
                  },
                  "mes": {
                    "type": "object",
                    "properties": {
                      "periodo": {
                        "type": "string",
                        "description": "Mes en curso, AAAA-MM."
                      },
                      "ingresos": {
                        "type": "string",
                        "description": "Ingresos netos vigentes del mes.",
                        "pattern": "^-?\\d+\\.\\d{2}$",
                        "example": "1250300.00"
                      },
                      "egresos": {
                        "type": "string",
                        "description": "Egresos netos vigentes del mes.",
                        "pattern": "^-?\\d+\\.\\d{2}$",
                        "example": "1250300.00"
                      },
                      "moneda": {
                        "type": "string",
                        "description": "Siempre MXN."
                      }
                    },
                    "nullable": true
                  }
                }
              },
              "contrapartes_riesgo": {
                "type": "object",
                "properties": {
                  "alto": {
                    "type": "integer",
                    "description": "Contrapartes en una lista con riesgo alto."
                  },
                  "medio": {
                    "type": "integer",
                    "description": "Contrapartes en una lista con riesgo medio."
                  }
                }
              },
              "efirma": {
                "type": "object",
                "properties": {
                  "estado": {
                    "type": "string",
                    "description": "Estado de la e.firma registrada. Nunca se expone el certificado ni la llave.",
                    "enum": [
                      "valida",
                      "vencida",
                      "revocada",
                      "invalida",
                      "sin_registrar"
                    ]
                  },
                  "vigente_hasta": {
                    "type": "string",
                    "format": "date",
                    "description": "Fecha en que vence.",
                    "nullable": true
                  }
                }
              },
              "sincronizacion": {
                "type": "object",
                "properties": {
                  "ultima_descarga": {
                    "type": "string",
                    "format": "date-time",
                    "description": "Última descarga de facturas del SAT.",
                    "nullable": true
                  },
                  "solicitudes_pendientes": {
                    "type": "integer",
                    "description": "Solicitudes de descarga en proceso."
                  }
                }
              }
            }
          }
        ]
      },
      "Alerta": {
        "type": "object",
        "required": [
          "id",
          "regla",
          "origen",
          "severidad",
          "estado",
          "titulo",
          "creada"
        ],
        "properties": {
          "id": {
            "type": "string",
            "description": "Identificador de la alerta.",
            "format": "uuid"
          },
          "regla": {
            "type": "string",
            "description": "Código de la regla que la generó (por ejemplo L-02)."
          },
          "origen": {
            "type": "string",
            "description": "Cómo nació la alerta.",
            "enum": [
              "lista",
              "regla",
              "tecnica"
            ]
          },
          "severidad": {
            "type": "string",
            "description": "Gravedad.",
            "enum": [
              "critica",
              "alta",
              "media"
            ]
          },
          "estado": {
            "type": "string",
            "description": "Estado.",
            "enum": [
              "abierta",
              "en_revision",
              "cerrada"
            ]
          },
          "titulo": {
            "type": "string",
            "description": "Título en español."
          },
          "rfc_contraparte": {
            "type": "string",
            "description": "RFC del cliente o proveedor involucrado.",
            "nullable": true
          },
          "periodo": {
            "type": "string",
            "description": "Periodo, AAAA-MM.",
            "nullable": true
          },
          "fecha_limite": {
            "type": "string",
            "format": "date",
            "description": "Fecha límite para atenderla.",
            "nullable": true
          },
          "creada": {
            "type": "string",
            "format": "date-time",
            "description": "Cuándo se creó."
          },
          "cerrada": {
            "type": "string",
            "format": "date-time",
            "description": "Cuándo se cerró.",
            "nullable": true
          },
          "detalle": {
            "type": "object",
            "additionalProperties": true,
            "description": "Datos de apoyo. Solo salen claves de una lista blanca (lista, situacion, fecha_publicacion, relacion, efecto, accion, cfdis, importe_neto, ultimo_cfdi, obligacion, declarado, calculado, diferencia, ppd, conciliadas, pendientes). Notas internas y responsables nunca salen."
          }
        }
      },
      "Declaracion": {
        "type": "object",
        "required": [
          "id",
          "ejercicio",
          "anual",
          "tipo",
          "estado_lectura",
          "con_pdf",
          "con_acuse"
        ],
        "properties": {
          "id": {
            "type": "string",
            "description": "Identificador de la declaración.",
            "format": "uuid"
          },
          "ejercicio": {
            "type": "integer",
            "description": "Año del ejercicio."
          },
          "periodo": {
            "type": "string",
            "description": "Periodo mensual AAAA-MM; null en la anual.",
            "nullable": true
          },
          "anual": {
            "type": "boolean",
            "description": "true si es la declaración anual."
          },
          "tipo": {
            "type": "string",
            "description": "Normal o complementaria.",
            "enum": [
              "normal",
              "complementaria"
            ]
          },
          "fuente": {
            "type": "string",
            "description": "De dónde la obtuvo Sereno.",
            "nullable": true
          },
          "numero_operacion": {
            "type": "string",
            "description": "Número de operación del SAT.",
            "nullable": true
          },
          "fecha_presentacion": {
            "type": "string",
            "format": "date",
            "description": "Fecha de presentación.",
            "nullable": true
          },
          "estado_lectura": {
            "type": "string",
            "description": "Si Sereno ya leyó el PDF.",
            "enum": [
              "pendiente",
              "leida",
              "error"
            ]
          },
          "con_pdf": {
            "type": "boolean",
            "description": "Hay PDF de la declaración para descargar."
          },
          "con_acuse": {
            "type": "boolean",
            "description": "Hay PDF del acuse para descargar."
          }
        }
      },
      "RespuestaDeclaraciones": {
        "type": "object",
        "required": [
          "ejercicio",
          "presentadas",
          "faltantes"
        ],
        "properties": {
          "ejercicio": {
            "type": "integer",
            "description": "Año consultado."
          },
          "presentadas": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Declaracion"
            }
          },
          "faltantes": {
            "type": "array",
            "items": {
              "type": "object",
              "required": [
                "periodo",
                "reglas"
              ],
              "properties": {
                "periodo": {
                  "type": "string",
                  "description": "Periodo AAAA-MM."
                },
                "reglas": {
                  "type": "array",
                  "items": {
                    "type": "string",
                    "description": "Código de regla"
                  }
                }
              }
            },
            "description": "Periodos con obligación calculada y sin acuse."
          }
        }
      },
      "DocumentoCumplimiento": {
        "type": "object",
        "required": [
          "id",
          "tipo",
          "mes",
          "fecha",
          "descargable"
        ],
        "properties": {
          "id": {
            "type": "string",
            "description": "Identificador del documento.",
            "format": "uuid"
          },
          "tipo": {
            "type": "string",
            "description": "Opinión de cumplimiento o constancia de situación fiscal.",
            "enum": [
              "opinion",
              "constancia"
            ]
          },
          "mes": {
            "type": "string",
            "description": "Mes al que corresponde, AAAA-MM."
          },
          "fecha": {
            "type": "string",
            "format": "date-time",
            "description": "Fecha del documento."
          },
          "sentido": {
            "type": "string",
            "description": "Sentido de la opinión (positivo, negativo…).",
            "nullable": true
          },
          "estatus_padron": {
            "type": "string",
            "description": "Estatus en el padrón (solo constancia).",
            "nullable": true
          },
          "folio": {
            "type": "string",
            "description": "Folio del documento.",
            "nullable": true
          },
          "bytes": {
            "type": "integer",
            "description": "Tamaño del PDF.",
            "nullable": true
          },
          "descargable": {
            "type": "boolean",
            "description": "Hay PDF para pedir con la ruta de descarga."
          }
        }
      },
      "Descarga": {
        "type": "object",
        "required": [
          "url",
          "expira_en"
        ],
        "properties": {
          "url": {
            "type": "string",
            "description": "Enlace firmado para bajar el PDF. Vale 5 minutos y se pide con una llamada nueva cada vez.",
            "format": "uri"
          },
          "expira_en": {
            "type": "string",
            "format": "date-time",
            "description": "Cuándo deja de servir el enlace."
          }
        }
      },
      "ConteoCfdi": {
        "type": "object",
        "required": [
          "vigentes",
          "cancelados"
        ],
        "properties": {
          "vigentes": {
            "type": "integer",
            "description": "Facturas vigentes."
          },
          "cancelados": {
            "type": "integer",
            "description": "Facturas canceladas."
          }
        }
      },
      "ResumenCfdi": {
        "type": "object",
        "required": [
          "desde",
          "hasta",
          "moneda",
          "meses"
        ],
        "properties": {
          "desde": {
            "type": "string",
            "description": "Primer mes, AAAA-MM."
          },
          "hasta": {
            "type": "string",
            "description": "Último mes, AAAA-MM."
          },
          "moneda": {
            "type": "string",
            "description": "Siempre MXN. Las facturas en otra moneda se convierten con el tipo de cambio del CFDI."
          },
          "meses": {
            "type": "array",
            "items": {
              "type": "object",
              "required": [
                "periodo",
                "emitidos",
                "recibidos",
                "ingresos",
                "egresos"
              ],
              "properties": {
                "periodo": {
                  "type": "string",
                  "description": "Mes, AAAA-MM."
                },
                "emitidos": {
                  "$ref": "#/components/schemas/ConteoCfdi"
                },
                "recibidos": {
                  "$ref": "#/components/schemas/ConteoCfdi"
                },
                "ingresos": {
                  "type": "string",
                  "description": "Ingresos netos (facturas de ingreso emitidas, vigentes), como texto decimal.",
                  "pattern": "^-?\\d+\\.\\d{2}$",
                  "example": "1250300.00"
                },
                "egresos": {
                  "type": "string",
                  "description": "Egresos netos (facturas de ingreso recibidas, vigentes), como texto decimal.",
                  "pattern": "^-?\\d+\\.\\d{2}$",
                  "example": "1250300.00"
                }
              }
            }
          }
        }
      },
      "Contraparte": {
        "type": "object",
        "required": [
          "rfc",
          "relacion",
          "riesgo",
          "cfdis",
          "importe_neto",
          "moneda",
          "listas"
        ],
        "properties": {
          "rfc": {
            "type": "string",
            "description": "RFC de la contraparte."
          },
          "nombre": {
            "type": "string",
            "description": "Nombre más reciente en las facturas.",
            "nullable": true
          },
          "relacion": {
            "type": "string",
            "description": "Si te vende (proveedor) o le vendes (cliente).",
            "enum": [
              "proveedor",
              "cliente"
            ]
          },
          "riesgo": {
            "type": "string",
            "description": "Riesgo de las listas en que aparece.",
            "enum": [
              "alto",
              "medio"
            ]
          },
          "cfdis": {
            "type": "integer",
            "description": "Facturas con esa contraparte."
          },
          "importe_neto": {
            "type": "string",
            "description": "Importe neto acumulado, como texto decimal.",
            "pattern": "^-?\\d+\\.\\d{2}$",
            "example": "1250300.00"
          },
          "moneda": {
            "type": "string",
            "description": "Siempre MXN."
          },
          "ultimo_cfdi": {
            "type": "string",
            "format": "date",
            "description": "Fecha de la última factura.",
            "nullable": true
          },
          "listas": {
            "type": "array",
            "items": {
              "type": "object",
              "properties": {
                "lista": {
                  "type": "string",
                  "description": "Código de la lista."
                },
                "situacion": {
                  "type": "string",
                  "description": "Situación en la lista.",
                  "nullable": true
                },
                "fecha_publicacion": {
                  "type": "string",
                  "format": "date",
                  "description": "Fecha de publicación.",
                  "nullable": true
                }
              }
            }
          }
        }
      },
      "Vencimiento": {
        "type": "object",
        "required": [
          "tipo",
          "fecha_limite",
          "dias_restantes",
          "titulo"
        ],
        "properties": {
          "tipo": {
            "type": "string",
            "description": "De dónde sale el vencimiento.",
            "enum": [
              "efirma",
              "alerta"
            ]
          },
          "fecha_limite": {
            "type": "string",
            "format": "date",
            "description": "Fecha límite."
          },
          "dias_restantes": {
            "type": "integer",
            "description": "Días que faltan; negativo si ya venció."
          },
          "titulo": {
            "type": "string",
            "description": "Descripción en español."
          },
          "severidad": {
            "type": "string",
            "description": "Solo en las alertas.",
            "enum": [
              "critica",
              "alta",
              "media"
            ]
          },
          "alerta_id": {
            "type": "string",
            "description": "Solo en las alertas.",
            "format": "uuid"
          },
          "regla": {
            "type": "string",
            "description": "Solo en las alertas."
          }
        }
      },
      "RespuestaVencimientos": {
        "type": "object",
        "required": [
          "dias",
          "incluye",
          "vencimientos"
        ],
        "properties": {
          "dias": {
            "type": "integer",
            "description": "Ventana consultada."
          },
          "incluye": {
            "type": "array",
            "items": {
              "type": "string",
              "description": "Qué tipos de vencimiento entran hoy: efirma y alertas. La declaración mensual se agregará cuando se apruebe el calendario por régimen."
            }
          },
          "vencimientos": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Vencimiento"
            }
          }
        }
      },
      "Evento": {
        "type": "object",
        "required": [
          "id",
          "tipo",
          "creado",
          "empresa_rfc",
          "datos",
          "url"
        ],
        "properties": {
          "id": {
            "type": "string",
            "description": "Identificador del evento. Sirve para no procesarlo dos veces.",
            "example": "evt_261019140211a1b2c3d4e5f6"
          },
          "tipo": {
            "type": "string",
            "description": "Tipo de evento.",
            "enum": [
              "alerta.creada",
              "alerta.cerrada",
              "semaforo.cambiado",
              "documento.nuevo",
              "contraparte.en_lista",
              "ping"
            ]
          },
          "creado": {
            "type": "string",
            "format": "date-time",
            "description": "Cuándo ocurrió (UTC)."
          },
          "empresa_rfc": {
            "type": "string",
            "description": "RFC de la empresa; null en el ping.",
            "nullable": true
          },
          "datos": {
            "type": "object",
            "additionalProperties": true,
            "description": "Datos del evento; su forma depende del tipo (ver la sección Webhooks)."
          },
          "url": {
            "type": "string",
            "description": "Ruta de la API donde ver el detalle.",
            "nullable": true
          }
        }
      },
      "Webhook": {
        "type": "object",
        "required": [
          "id",
          "url",
          "eventos",
          "empresas",
          "activo",
          "secreto_prefijo",
          "creado"
        ],
        "properties": {
          "id": {
            "type": "string",
            "description": "Identificador del webhook.",
            "format": "uuid"
          },
          "url": {
            "type": "string",
            "description": "Dirección que recibe los avisos."
          },
          "eventos": {
            "type": "array",
            "items": {
              "type": "string",
              "description": "Tipo de evento"
            }
          },
          "empresas": {
            "type": "array",
            "items": {
              "type": "string",
              "description": "RFC"
            },
            "description": "RFC de las empresas cubiertas; null = todas las que ve la llave.",
            "nullable": true
          },
          "activo": {
            "type": "boolean",
            "description": "false si está pausado."
          },
          "pausado_en": {
            "type": "string",
            "format": "date-time",
            "description": "Cuándo se pausó.",
            "nullable": true
          },
          "motivo_pausa": {
            "type": "string",
            "description": "Por qué se pausó.",
            "nullable": true
          },
          "secreto_prefijo": {
            "type": "string",
            "description": "Primeros caracteres del secreto, para reconocerlo. El secreto completo solo se ve al crearlo."
          },
          "creado": {
            "type": "string",
            "format": "date-time",
            "description": "Cuándo se creó."
          }
        }
      },
      "WebhookCreado": {
        "allOf": [
          {
            "$ref": "#/components/schemas/Webhook"
          },
          {
            "type": "object",
            "required": [
              "secreto"
            ],
            "properties": {
              "secreto": {
                "type": "string",
                "description": "Secreto para verificar la firma (whsec_…). SE MUESTRA UNA SOLA VEZ: guárdalo ahora.",
                "example": "whsec_EJEMPLO_no_es_un_secreto"
              }
            }
          }
        ]
      },
      "ListaWebhooks": {
        "type": "object",
        "required": [
          "datos"
        ],
        "properties": {
          "datos": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Webhook"
            }
          },
          "siguiente": {
            "type": "string",
            "description": "Esta lista no se pagina: trae todos los webhooks que ve la llave y este campo siempre es null.",
            "nullable": true
          }
        }
      },
      "WebhookBorrado": {
        "type": "object",
        "required": [
          "id",
          "borrado"
        ],
        "properties": {
          "id": {
            "type": "string",
            "description": "Identificador del webhook borrado.",
            "format": "uuid"
          },
          "borrado": {
            "type": "boolean",
            "description": "Siempre true."
          }
        }
      },
      "PruebaWebhook": {
        "type": "object",
        "required": [
          "evento_id",
          "webhook_id",
          "estado"
        ],
        "properties": {
          "evento_id": {
            "type": "string",
            "description": "Identificador del evento ping que se mandará."
          },
          "webhook_id": {
            "type": "string",
            "description": "Identificador del webhook.",
            "format": "uuid"
          },
          "estado": {
            "type": "string",
            "description": "Siempre pendiente: el envío ocurre en el siguiente minuto.",
            "enum": [
              "pendiente"
            ]
          }
        }
      },
      "Entrega": {
        "type": "object",
        "required": [
          "id",
          "evento_id",
          "tipo",
          "estado",
          "intento",
          "creada"
        ],
        "properties": {
          "id": {
            "type": "integer",
            "description": "Identificador de la entrega."
          },
          "evento_id": {
            "type": "string",
            "description": "Evento que se entrega."
          },
          "tipo": {
            "type": "string",
            "description": "Tipo del evento."
          },
          "estado": {
            "type": "string",
            "description": "pendiente = en cola o esperando reintento; entregada = tu servidor respondió 2xx; fallida = se agotaron los 7 intentos; cancelada = ya no aplicaba.",
            "enum": [
              "pendiente",
              "entregada",
              "fallida",
              "cancelada"
            ]
          },
          "intento": {
            "type": "integer",
            "description": "Intentos hechos."
          },
          "http_status": {
            "type": "integer",
            "description": "Código HTTP de la última respuesta de tu servidor.",
            "nullable": true
          },
          "error": {
            "type": "string",
            "description": "Qué falló, en español.",
            "nullable": true
          },
          "creada": {
            "type": "string",
            "format": "date-time",
            "description": "Cuándo se creó la entrega."
          },
          "ultimo_intento": {
            "type": "string",
            "format": "date-time",
            "description": "Cuándo se intentó por última vez.",
            "nullable": true
          },
          "proximo_intento": {
            "type": "string",
            "format": "date-time",
            "description": "Cuándo se volverá a intentar.",
            "nullable": true
          }
        }
      },
      "ClienteDespacho": {
        "type": "object",
        "required": [
          "cliente_id",
          "nombre",
          "empresas",
          "semaforo",
          "alertas"
        ],
        "properties": {
          "cliente_id": {
            "type": "string",
            "description": "Identificador de la cuenta del cliente.",
            "format": "uuid"
          },
          "nombre": {
            "type": "string",
            "description": "Nombre de la cuenta."
          },
          "rfc": {
            "type": "string",
            "description": "RFC de facturación.",
            "nullable": true
          },
          "razon_social": {
            "type": "string",
            "description": "Razón social de facturación.",
            "nullable": true
          },
          "empresas": {
            "type": "integer",
            "description": "Empresas del cliente que ve la llave."
          },
          "semaforo": {
            "type": "object",
            "required": [
              "color",
              "motivos"
            ],
            "properties": {
              "color": {
                "type": "string",
                "description": "El peor color entre sus empresas; gris si ninguna está activa.",
                "enum": [
                  "rojo",
                  "ambar",
                  "verde",
                  "gris"
                ]
              },
              "motivos": {
                "type": "array",
                "items": {
                  "type": "string",
                  "description": "Código de motivo"
                }
              }
            }
          },
          "alertas": {
            "type": "object",
            "required": [
              "criticas",
              "altas",
              "medias"
            ],
            "properties": {
              "criticas": {
                "type": "integer",
                "description": "Abiertas críticas."
              },
              "altas": {
                "type": "integer",
                "description": "Abiertas altas."
              },
              "medias": {
                "type": "integer",
                "description": "Abiertas medias."
              }
            }
          },
          "ultima_descarga": {
            "type": "string",
            "format": "date-time",
            "description": "Última descarga de facturas de sus empresas.",
            "nullable": true
          }
        }
      },
      "Estado": {
        "type": "object",
        "description": "Sin llave solo trae estado, versión y cortes. Con llave añade plan, alcances, límites y uso de hoy.",
        "required": [
          "estado",
          "version",
          "consultado_en",
          "cortes"
        ],
        "properties": {
          "estado": {
            "type": "string",
            "description": "ok, o degradado si alguna lista tiene un corte de más de 3 días.",
            "enum": [
              "ok",
              "degradado"
            ]
          },
          "version": {
            "type": "string",
            "description": "Versión de la API.",
            "example": "1"
          },
          "consultado_en": {
            "type": "string",
            "format": "date-time",
            "description": "Cuándo se consultó."
          },
          "cortes": {
            "$ref": "#/components/schemas/Cortes"
          },
          "plan": {
            "type": "object",
            "properties": {
              "codigo": {
                "type": "string",
                "description": "Código del plan."
              },
              "nombre": {
                "type": "string",
                "description": "Nombre del plan."
              }
            }
          },
          "alcances": {
            "type": "array",
            "items": {
              "type": "string",
              "description": "Alcance efectivo de la llave"
            }
          },
          "limites": {
            "type": "object",
            "properties": {
              "listas_dia": {
                "type": "integer",
                "description": "Consultas de listas por día."
              },
              "lecturas_dia": {
                "type": "integer",
                "description": "Lecturas por día."
              },
              "por_minuto": {
                "type": "integer",
                "description": "Peticiones por minuto."
              }
            }
          },
          "uso_hoy": {
            "type": "object",
            "properties": {
              "listas": {
                "type": "integer",
                "description": "Consultas de listas hechas hoy."
              },
              "lecturas": {
                "type": "integer",
                "description": "Lecturas hechas hoy."
              }
            }
          }
        }
      },
      "PaginaEmpresas": {
        "type": "object",
        "required": [
          "datos",
          "siguiente_cursor"
        ],
        "properties": {
          "datos": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Empresa"
            },
            "description": "Empresas que ve la llave, ordenadas por RFC."
          },
          "siguiente_cursor": {
            "type": "string",
            "description": "Cursor para pedir la página siguiente con ?cursor=…. null cuando ya no hay más. Es opaco y solo sirve con los mismos filtros.",
            "nullable": true
          }
        }
      },
      "PaginaAlertas": {
        "type": "object",
        "required": [
          "datos",
          "siguiente_cursor"
        ],
        "properties": {
          "datos": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Alerta"
            },
            "description": "Alertas de la más reciente a la más antigua."
          },
          "siguiente_cursor": {
            "type": "string",
            "description": "Cursor para pedir la página siguiente con ?cursor=…. null cuando ya no hay más. Es opaco y solo sirve con los mismos filtros.",
            "nullable": true
          }
        }
      },
      "PaginaCumplimiento": {
        "type": "object",
        "required": [
          "datos",
          "siguiente_cursor"
        ],
        "properties": {
          "datos": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/DocumentoCumplimiento"
            },
            "description": "Documentos de cumplimiento, del mes más reciente al más antiguo."
          },
          "siguiente_cursor": {
            "type": "string",
            "description": "Cursor para pedir la página siguiente con ?cursor=…. null cuando ya no hay más. Es opaco y solo sirve con los mismos filtros.",
            "nullable": true
          }
        }
      },
      "PaginaContrapartes": {
        "type": "object",
        "required": [
          "datos",
          "siguiente_cursor"
        ],
        "properties": {
          "datos": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Contraparte"
            },
            "description": "Contrapartes en lista: primero las de riesgo alto."
          },
          "siguiente_cursor": {
            "type": "string",
            "description": "Cursor para pedir la página siguiente con ?cursor=…. null cuando ya no hay más. Es opaco y solo sirve con los mismos filtros.",
            "nullable": true
          }
        }
      },
      "PaginaEventos": {
        "type": "object",
        "required": [
          "datos",
          "siguiente_cursor"
        ],
        "properties": {
          "datos": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Evento"
            },
            "description": "Eventos del más viejo al más nuevo (ordenados por id)."
          },
          "siguiente_cursor": {
            "type": "string",
            "description": "Cursor para pedir la página siguiente con ?cursor=…. null cuando ya no hay más. Es opaco y solo sirve con los mismos filtros.",
            "nullable": true
          }
        }
      },
      "PaginaEntregas": {
        "type": "object",
        "required": [
          "datos",
          "siguiente_cursor"
        ],
        "properties": {
          "datos": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/Entrega"
            },
            "description": "Entregas de la más reciente a la más antigua."
          },
          "siguiente_cursor": {
            "type": "string",
            "description": "Cursor para pedir la página siguiente con ?cursor=…. null cuando ya no hay más. Es opaco y solo sirve con los mismos filtros.",
            "nullable": true
          }
        }
      },
      "PaginaClientes": {
        "type": "object",
        "required": [
          "datos",
          "siguiente_cursor"
        ],
        "properties": {
          "datos": {
            "type": "array",
            "items": {
              "$ref": "#/components/schemas/ClienteDespacho"
            },
            "description": "Clientes del despacho ordenados por nombre."
          },
          "siguiente_cursor": {
            "type": "string",
            "description": "Cursor para pedir la página siguiente con ?cursor=…. null cuando ya no hay más. Es opaco y solo sirve con los mismos filtros.",
            "nullable": true
          }
        }
      }
    }
  }
}
