{
    "openapi": "3.1.0",
    "info": {
        "title": "API de Nodda (VERI*FACTU)",
        "version": "1.0.0",
        "description": "API para emitir facturas VERI*FACTU a la AEAT. Autenticacion con 'Authorization: Bearer <clave API>' (integraciones) o con el token de sesion del portal. x-permiso indica el permiso de rol necesario cuando la peticion viene de una sesion del portal (las claves API no tienen rol)."
    },
    "servers": [
        {
            "url": "https:\/\/api.nodda.es",
            "description": "Produccion"
        },
        {
            "url": "https:\/\/sandbox.nodda.es",
            "description": "Sandbox (AEAT preproduccion)"
        }
    ],
    "security": [
        {
            "bearerAuth": []
        }
    ],
    "tags": [
        {
            "name": "Estado"
        },
        {
            "name": "Facturas"
        },
        {
            "name": "Emisores"
        },
        {
            "name": "Certificados"
        },
        {
            "name": "Series"
        },
        {
            "name": "Cuentas"
        },
        {
            "name": "Sesion"
        },
        {
            "name": "Usuarios"
        },
        {
            "name": "Webhooks"
        },
        {
            "name": "Administracion",
            "description": "Todas estas rutas exigen SESION web de una cuenta administradora con verificacion en dos pasos activa; si falta el 2FA responden 403 twofa_requerido. Las API Keys no tienen acceso."
        },
        {
            "name": "Clientes",
            "description": "Clientes guardados de la cuenta (destinatarios reutilizables) e importacion masiva."
        },
        {
            "name": "Productos",
            "description": "Productos y servicios guardados (conceptos reutilizables) e importacion masiva."
        },
        {
            "name": "Categorias",
            "description": "Categorias del catalogo de productos."
        },
        {
            "name": "Centros de coste",
            "description": "Centros de coste: alta, asignacion de facturas y resumen."
        },
        {
            "name": "Cobros",
            "description": "Registro de cobros y deuda de clientes por antiguedad."
        },
        {
            "name": "Saldo",
            "description": "Saldo prepago de facturas y sus movimientos (solo lectura)."
        }
    ],
    "paths": {
        "\/v1\/clientes\/import": {
            "post": {
                "operationId": "importarClientes",
                "summary": "Importa clientes en lote",
                "responses": {
                    "200": {
                        "description": "Resultado de la importacion (creados, actualizados, errores por fila).",
                        "content": {
                            "application\/json": {
                                "schema": {
                                    "type": "object"
                                }
                            }
                        }
                    },
                    "422": {
                        "description": "Datos invalidos o lista vacia."
                    }
                },
                "tags": [
                    "Clientes"
                ],
                "description": "Cada fila con NIF inv\u00e1lido (formato o d\u00edgito de control) va a errors con su \u00edndice y no se importa."
            }
        },
        "\/v1\/productos\/import": {
            "post": {
                "operationId": "importarProductos",
                "summary": "Importa productos en lote",
                "responses": {
                    "200": {
                        "description": "Resultado de la importacion (creados, actualizados, errores por fila).",
                        "content": {
                            "application\/json": {
                                "schema": {
                                    "type": "object"
                                }
                            }
                        }
                    },
                    "422": {
                        "description": "Datos invalidos o lista vacia."
                    }
                },
                "tags": [
                    "Productos"
                ]
            }
        },
        "\/v1\/centros\/{id}\/facturas": {
            "get": {
                "tags": [
                    "Centros de coste"
                ],
                "summary": "Facturas asociadas al centro",
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Facturas.",
                        "content": {
                            "application\/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "invoices": {
                                            "type": "array",
                                            "items": {
                                                "$ref": "#\/components\/schemas\/CentroCosteFactura"
                                            }
                                        }
                                    }
                                }
                            }
                        }
                    },
                    "404": {
                        "description": "No existe en la cuenta.",
                        "content": {
                            "application\/json": {
                                "schema": {
                                    "$ref": "#\/components\/schemas\/Error"
                                }
                            }
                        }
                    }
                }
            },
            "post": {
                "tags": [
                    "Centros de coste"
                ],
                "summary": "Asociar una factura existente (una factura est\u00e1 en 0 o 1 centro; si estaba en otro, se mueve)",
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application\/json": {
                            "schema": {
                                "type": "object",
                                "required": [
                                    "nif",
                                    "invoice"
                                ],
                                "properties": {
                                    "nif": {
                                        "type": "string"
                                    },
                                    "invoice": {
                                        "type": "string",
                                        "description": "Serie-n\u00famero."
                                    }
                                }
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "Asociada."
                    },
                    "404": {
                        "description": "El centro o la factura no existen en la cuenta (invoice_not_found).",
                        "content": {
                            "application\/json": {
                                "schema": {
                                    "$ref": "#\/components\/schemas\/Error"
                                }
                            }
                        }
                    },
                    "409": {
                        "description": "El centro est\u00e1 cerrado (cost_center_closed).",
                        "content": {
                            "application\/json": {
                                "schema": {
                                    "$ref": "#\/components\/schemas\/Error"
                                }
                            }
                        }
                    },
                    "422": {
                        "description": "Faltan nif e invoice.",
                        "content": {
                            "application\/json": {
                                "schema": {
                                    "$ref": "#\/components\/schemas\/Error"
                                }
                            }
                        }
                    }
                }
            },
            "delete": {
                "tags": [
                    "Centros de coste"
                ],
                "summary": "Quitar una factura del centro",
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application\/json": {
                            "schema": {
                                "type": "object",
                                "required": [
                                    "nif",
                                    "invoice"
                                ],
                                "properties": {
                                    "nif": {
                                        "type": "string"
                                    },
                                    "invoice": {
                                        "type": "string",
                                        "description": "Serie-n\u00famero."
                                    }
                                }
                            }
                        }
                    }
                },
                "responses": {
                    "204": {
                        "description": "Quitada."
                    },
                    "404": {
                        "description": "No estaba asociada.",
                        "content": {
                            "application\/json": {
                                "schema": {
                                    "$ref": "#\/components\/schemas\/Error"
                                }
                            }
                        }
                    }
                }
            }
        },
        "\/v1\/centros\/{id}\/resumen": {
            "get": {
                "tags": [
                    "Centros de coste"
                ],
                "summary": "Resumen del centro",
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Resumen.",
                        "content": {
                            "application\/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "costCenter": {
                                            "$ref": "#\/components\/schemas\/CentroCoste"
                                        },
                                        "summary": {
                                            "type": "object",
                                            "properties": {
                                                "invoiceCount": {
                                                    "type": "integer"
                                                },
                                                "base": {
                                                    "type": "string",
                                                    "pattern": "^-?\\d+\\.\\d{2}$",
                                                    "description": "Base."
                                                },
                                                "tax": {
                                                    "type": "string",
                                                    "pattern": "^-?\\d+\\.\\d{2}$",
                                                    "description": "Cuota de IVA."
                                                },
                                                "total": {
                                                    "type": "string",
                                                    "pattern": "^-?\\d+\\.\\d{2}$",
                                                    "description": "Facturado con IVA."
                                                },
                                                "pending": {
                                                    "type": "string",
                                                    "pattern": "^-?\\d+\\.\\d{2}$",
                                                    "description": "Pendiente de cobro."
                                                },
                                                "collected": {
                                                    "type": "string",
                                                    "pattern": "^-?\\d+\\.\\d{2}$",
                                                    "description": "Cobrado."
                                                },
                                                "budget": {
                                                    "type": "string",
                                                    "nullable": true
                                                }
                                            }
                                        }
                                    }
                                }
                            }
                        }
                    },
                    "404": {
                        "description": "No existe en la cuenta.",
                        "content": {
                            "application\/json": {
                                "schema": {
                                    "$ref": "#\/components\/schemas\/Error"
                                }
                            }
                        }
                    }
                }
            }
        },
        "\/v1\/cobros\/deudas": {
            "get": {
                "operationId": "listarDeudas",
                "summary": "Deuda pendiente por cliente con antiguedad",
                "responses": {
                    "200": {
                        "description": "Clientes con saldo pendiente y tramos de antiguedad.",
                        "content": {
                            "application\/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "debtors": {
                                            "type": "array",
                                            "items": {
                                                "type": "object"
                                            }
                                        },
                                        "totalPending": {
                                            "type": "string"
                                        }
                                    },
                                    "additionalProperties": false
                                }
                            }
                        }
                    }
                },
                "tags": [
                    "Cobros"
                ]
            }
        },
        "\/v1\/categorias": {
            "get": {
                "operationId": "listarCategorias",
                "summary": "Lista las categorias del catalogo",
                "responses": {
                    "200": {
                        "description": "Categorias de la cuenta.",
                        "content": {
                            "application\/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "categories": {
                                            "type": "array",
                                            "items": {
                                                "type": "object",
                                                "properties": {
                                                    "id": {
                                                        "type": "string"
                                                    },
                                                    "name": {
                                                        "type": "string"
                                                    },
                                                    "createdAt": {
                                                        "type": [
                                                            "string",
                                                            "null"
                                                        ]
                                                    },
                                                    "invoiced": {
                                                        "type": "string",
                                                        "description": "Base (sin IVA) facturada en el anio con productos de esta categoria."
                                                    }
                                                }
                                            }
                                        },
                                        "uncategorizedInvoiced": {
                                            "type": "string",
                                            "description": "Base facturada en el anio sin categoria (lineas a mano, facturas sin conceptos guardados)."
                                        },
                                        "year": {
                                            "type": "integer"
                                        }
                                    },
                                    "additionalProperties": false
                                }
                            }
                        }
                    }
                },
                "tags": [
                    "Categorias"
                ],
                "parameters": [
                    {
                        "name": "nif",
                        "in": "query",
                        "required": false,
                        "schema": {
                            "type": "string"
                        },
                        "description": "Empresa de la cuenta para el importe facturado (por defecto, toda la cuenta)."
                    }
                ]
            },
            "post": {
                "operationId": "crearCategoria",
                "summary": "Crea una categoria",
                "responses": {
                    "201": {
                        "description": "Categoria creada.",
                        "content": {
                            "application\/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "id": {
                                            "type": "string"
                                        },
                                        "name": {
                                            "type": "string"
                                        },
                                        "createdAt": {
                                            "type": [
                                                "string",
                                                "null"
                                            ]
                                        }
                                    }
                                }
                            }
                        }
                    },
                    "409": {
                        "description": "Ya existe una categoria con ese nombre."
                    },
                    "422": {
                        "description": "Datos invalidos."
                    }
                },
                "tags": [
                    "Categorias"
                ]
            }
        },
        "\/v1\/categorias\/{id}": {
            "parameters": [
                {
                    "name": "id",
                    "in": "path",
                    "required": true,
                    "schema": {
                        "type": "string"
                    }
                }
            ],
            "put": {
                "operationId": "actualizarCategoria",
                "summary": "Actualiza una categoria",
                "responses": {
                    "200": {
                        "description": "Categoria actualizada.",
                        "content": {
                            "application\/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "id": {
                                            "type": "string"
                                        },
                                        "name": {
                                            "type": "string"
                                        },
                                        "createdAt": {
                                            "type": [
                                                "string",
                                                "null"
                                            ]
                                        }
                                    }
                                }
                            }
                        }
                    },
                    "404": {
                        "description": "Categoria no encontrada."
                    },
                    "409": {
                        "description": "Otra categoria ya usa ese nombre."
                    }
                },
                "tags": [
                    "Categorias"
                ]
            },
            "delete": {
                "operationId": "eliminarCategoria",
                "summary": "Elimina una categoria",
                "responses": {
                    "204": {
                        "description": "Categoria eliminada."
                    },
                    "404": {
                        "description": "Categoria no encontrada."
                    }
                },
                "tags": [
                    "Categorias"
                ]
            }
        },
        "\/v1\/cobros": {
            "get": {
                "operationId": "listarCobros",
                "summary": "Cobros de una factura, con total\/cobrado\/pendiente",
                "parameters": [
                    {
                        "name": "nif",
                        "in": "query",
                        "required": true,
                        "schema": {
                            "type": "string"
                        }
                    },
                    {
                        "name": "invoice",
                        "in": "query",
                        "required": true,
                        "schema": {
                            "type": "string"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Cobros de la factura y estado de cobro.",
                        "content": {
                            "application\/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "payments": {
                                            "type": "array",
                                            "items": {
                                                "type": "object",
                                                "properties": {
                                                    "id": {
                                                        "type": "string"
                                                    },
                                                    "nif": {
                                                        "type": "string"
                                                    },
                                                    "invoice": {
                                                        "type": "string"
                                                    },
                                                    "amount": {
                                                        "type": "string"
                                                    },
                                                    "date": {
                                                        "type": "string"
                                                    },
                                                    "method": {
                                                        "type": [
                                                            "string",
                                                            "null"
                                                        ]
                                                    },
                                                    "reference": {
                                                        "type": [
                                                            "string",
                                                            "null"
                                                        ]
                                                    },
                                                    "note": {
                                                        "type": [
                                                            "string",
                                                            "null"
                                                        ]
                                                    },
                                                    "createdAt": {
                                                        "type": [
                                                            "string",
                                                            "null"
                                                        ]
                                                    }
                                                }
                                            }
                                        },
                                        "collected": {
                                            "type": "string"
                                        },
                                        "invoiceTotal": {
                                            "type": [
                                                "string",
                                                "null"
                                            ]
                                        },
                                        "pending": {
                                            "type": [
                                                "string",
                                                "null"
                                            ]
                                        },
                                        "paid": {
                                            "type": "boolean"
                                        }
                                    },
                                    "additionalProperties": false
                                }
                            }
                        }
                    },
                    "422": {
                        "description": "Faltan los parametros nif\/invoice."
                    }
                },
                "tags": [
                    "Cobros"
                ]
            },
            "post": {
                "operationId": "registrarCobro",
                "summary": "Registra un cobro (pago total o parcial) contra una factura",
                "responses": {
                    "201": {
                        "description": "Cobro registrado.",
                        "content": {
                            "application\/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "id": {
                                            "type": "string"
                                        },
                                        "nif": {
                                            "type": "string"
                                        },
                                        "invoice": {
                                            "type": "string"
                                        },
                                        "amount": {
                                            "type": "string"
                                        },
                                        "date": {
                                            "type": "string"
                                        },
                                        "method": {
                                            "type": [
                                                "string",
                                                "null"
                                            ]
                                        },
                                        "reference": {
                                            "type": [
                                                "string",
                                                "null"
                                            ]
                                        },
                                        "note": {
                                            "type": [
                                                "string",
                                                "null"
                                            ]
                                        },
                                        "createdAt": {
                                            "type": [
                                                "string",
                                                "null"
                                            ]
                                        }
                                    }
                                }
                            }
                        }
                    },
                    "422": {
                        "description": "Datos invalidos."
                    },
                    "409": {
                        "description": "invoice_not_payable: la factura esta anulada, sustituida por una rectificativa o aun no esta registrada."
                    }
                },
                "tags": [
                    "Cobros"
                ]
            }
        },
        "\/v1\/cobros\/{id}": {
            "parameters": [
                {
                    "name": "id",
                    "in": "path",
                    "required": true,
                    "schema": {
                        "type": "string"
                    }
                }
            ],
            "delete": {
                "operationId": "deshacerCobro",
                "summary": "Deshace un cobro",
                "responses": {
                    "204": {
                        "description": "Cobro deshecho."
                    },
                    "404": {
                        "description": "Cobro no encontrado."
                    }
                },
                "tags": [
                    "Cobros"
                ]
            }
        },
        "\/v1\/centros": {
            "get": {
                "tags": [
                    "Centros de coste"
                ],
                "summary": "Listar centros de coste con sus cifras",
                "parameters": [
                    {
                        "name": "nif",
                        "in": "query",
                        "required": false,
                        "schema": {
                            "type": "string"
                        },
                        "description": "Solo las facturas de esa empresa (debe ser de la cuenta)."
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Centros de la cuenta.",
                        "content": {
                            "application\/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "costCenters": {
                                            "type": "array",
                                            "items": {
                                                "$ref": "#\/components\/schemas\/CentroCosteConCifras"
                                            }
                                        }
                                    }
                                }
                            }
                        }
                    },
                    "403": {
                        "description": "El NIF no pertenece a la cuenta (nif_not_authorized).",
                        "content": {
                            "application\/json": {
                                "schema": {
                                    "$ref": "#\/components\/schemas\/Error"
                                }
                            }
                        }
                    }
                }
            },
            "post": {
                "tags": [
                    "Centros de coste"
                ],
                "summary": "Crear centro de coste",
                "requestBody": {
                    "required": true,
                    "content": {
                        "application\/json": {
                            "schema": {
                                "$ref": "#\/components\/schemas\/CentroCosteEntrada"
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Creado.",
                        "content": {
                            "application\/json": {
                                "schema": {
                                    "$ref": "#\/components\/schemas\/CentroCoste"
                                }
                            }
                        }
                    },
                    "409": {
                        "description": "Ya existe un centro con ese nombre (centro_duplicado).",
                        "content": {
                            "application\/json": {
                                "schema": {
                                    "$ref": "#\/components\/schemas\/Error"
                                }
                            }
                        }
                    },
                    "422": {
                        "description": "Datos no v\u00e1lidos.",
                        "content": {
                            "application\/json": {
                                "schema": {
                                    "$ref": "#\/components\/schemas\/Error"
                                }
                            }
                        }
                    }
                }
            }
        },
        "\/v1\/centros\/{id}": {
            "put": {
                "tags": [
                    "Centros de coste"
                ],
                "summary": "Actualizar centro de coste (reemplaza todos los campos; cerrar\/reabrir con status)",
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application\/json": {
                            "schema": {
                                "$ref": "#\/components\/schemas\/CentroCosteEntrada"
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "Actualizado.",
                        "content": {
                            "application\/json": {
                                "schema": {
                                    "$ref": "#\/components\/schemas\/CentroCoste"
                                }
                            }
                        }
                    },
                    "404": {
                        "description": "No existe en la cuenta.",
                        "content": {
                            "application\/json": {
                                "schema": {
                                    "$ref": "#\/components\/schemas\/Error"
                                }
                            }
                        }
                    },
                    "409": {
                        "description": "Otro centro usa ese nombre.",
                        "content": {
                            "application\/json": {
                                "schema": {
                                    "$ref": "#\/components\/schemas\/Error"
                                }
                            }
                        }
                    },
                    "422": {
                        "description": "Datos no v\u00e1lidos.",
                        "content": {
                            "application\/json": {
                                "schema": {
                                    "$ref": "#\/components\/schemas\/Error"
                                }
                            }
                        }
                    }
                }
            },
            "delete": {
                "tags": [
                    "Centros de coste"
                ],
                "summary": "Eliminar centro de coste (las facturas quedan sin centro; no se borran)",
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "responses": {
                    "204": {
                        "description": "Eliminado."
                    },
                    "404": {
                        "description": "No existe en la cuenta.",
                        "content": {
                            "application\/json": {
                                "schema": {
                                    "$ref": "#\/components\/schemas\/Error"
                                }
                            }
                        }
                    }
                }
            }
        },
        "\/v1\/saldo": {
            "get": {
                "operationId": "consultarSaldo",
                "summary": "Saldo prepago disponible de la cuenta",
                "responses": {
                    "200": {
                        "description": "Numero de facturas disponibles (gratis + compradas, no caducadas).",
                        "content": {
                            "application\/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "available": {
                                            "type": "integer"
                                        }
                                    },
                                    "additionalProperties": false
                                }
                            }
                        }
                    }
                },
                "tags": [
                    "Saldo"
                ]
            }
        },
        "\/v1\/saldo\/movimientos": {
            "get": {
                "operationId": "listarMovimientosSaldo",
                "summary": "Movimientos de saldo de la cuenta (mas reciente primero)",
                "responses": {
                    "200": {
                        "description": "Movimientos de saldo: alta de prueba, compra, consumo, reverso y ajuste.",
                        "content": {
                            "application\/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "movements": {
                                            "type": "array",
                                            "items": {
                                                "type": "object",
                                                "properties": {
                                                    "reason": {
                                                        "type": "string"
                                                    },
                                                    "delta": {
                                                        "type": "integer"
                                                    },
                                                    "reference": {
                                                        "type": [
                                                            "string",
                                                            "null"
                                                        ]
                                                    },
                                                    "createdAt": {
                                                        "type": "string"
                                                    },
                                                    "invoice": {
                                                        "type": "string",
                                                        "nullable": true,
                                                        "description": "Numero de la factura que consumio este saldo (solo en consumos)."
                                                    },
                                                    "invoiceNif": {
                                                        "type": "string",
                                                        "nullable": true,
                                                        "description": "NIF emisor de esa factura."
                                                    }
                                                }
                                            }
                                        }
                                    },
                                    "additionalProperties": false
                                }
                            }
                        }
                    }
                },
                "tags": [
                    "Saldo"
                ]
            }
        },
        "\/health": {
            "get": {
                "tags": [
                    "Estado"
                ],
                "summary": "Comprueba que el servicio responde (sin autenticacion).",
                "operationId": "health",
                "responses": {
                    "200": {
                        "description": "El servicio responde.",
                        "content": {
                            "application\/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "status": {
                                            "type": "string",
                                            "enum": [
                                                "ok"
                                            ]
                                        },
                                        "timestamp": {
                                            "type": "string",
                                            "format": "date-time"
                                        }
                                    },
                                    "additionalProperties": false
                                }
                            }
                        }
                    }
                },
                "description": "Para balanceadores de carga y monitoreo. No toca la AEAT ni la base de datos. Tambien acepta HEAD.",
                "security": []
            }
        },
        "\/v1\/health": {
            "get": {
                "tags": [
                    "Estado"
                ],
                "summary": "Salud del servicio, base de datos y cola.",
                "operationId": "healthDetallado",
                "responses": {
                    "200": {
                        "description": "Estado de salud.",
                        "content": {
                            "application\/json": {
                                "schema": {
                                    "$ref": "#\/components\/schemas\/Health"
                                }
                            }
                        }
                    },
                    "401": {
                        "$ref": "#\/components\/responses\/NoAutorizado"
                    },
                    "429": {
                        "$ref": "#\/components\/responses\/DemasiadasPeticiones"
                    }
                },
                "description": "Cada cliente ve solo sus NIF; un administrador ve el total global (alcance).",
                "security": [
                    {
                        "bearerAuth": []
                    }
                ]
            }
        },
        "\/v1\/cola": {
            "get": {
                "tags": [
                    "Estado"
                ],
                "summary": "Estado de la cola de envios a la AEAT.",
                "operationId": "cola",
                "responses": {
                    "200": {
                        "description": "Resumen de la cola.",
                        "content": {
                            "application\/json": {
                                "schema": {
                                    "$ref": "#\/components\/schemas\/Cola"
                                }
                            }
                        }
                    },
                    "401": {
                        "$ref": "#\/components\/responses\/NoAutorizado"
                    },
                    "429": {
                        "$ref": "#\/components\/responses\/DemasiadasPeticiones"
                    }
                },
                "security": [
                    {
                        "bearerAuth": []
                    }
                ]
            }
        },
        "\/v1\/invoices": {
            "post": {
                "tags": [
                    "Facturas"
                ],
                "summary": "Emite una factura (alta).",
                "operationId": "crearFactura",
                "responses": {
                    "202": {
                        "description": "Factura aceptada y puesta en cola.",
                        "content": {
                            "application\/json": {
                                "schema": {
                                    "$ref": "#\/components\/schemas\/FacturaEncolada"
                                }
                            }
                        },
                        "headers": {
                            "X-Nodda-Quota-Remaining": {
                                "description": "Facturas gratuitas que quedan (solo cuentas con limite).",
                                "schema": {
                                    "type": "string"
                                }
                            }
                        }
                    },
                    "401": {
                        "$ref": "#\/components\/responses\/NoAutorizado"
                    },
                    "402": {
                        "$ref": "#\/components\/responses\/CupoAgotado"
                    },
                    "403": {
                        "$ref": "#\/components\/responses\/Prohibido"
                    },
                    "409": {
                        "$ref": "#\/components\/responses\/Conflicto"
                    },
                    "422": {
                        "$ref": "#\/components\/responses\/Invalida"
                    },
                    "429": {
                        "$ref": "#\/components\/responses\/DemasiadasPeticiones"
                    }
                },
                "description": "Si no se envia 'number', la API asigna el siguiente de la serie (ver PUT \/v1\/emisores\/{id}\/series). Los importes van como texto decimal (ej. \"0.40\"). Si la cuenta esta bloqueada desde la cola de revision responde 403 cuenta_bloqueada.",
                "x-permiso": "emitir_facturas",
                "parameters": [
                    {
                        "name": "Idempotency-Key",
                        "in": "header",
                        "required": false,
                        "schema": {
                            "type": "string"
                        },
                        "description": "Si se repite la misma clave con el mismo cuerpo, se devuelve la respuesta original sin emitir otra factura."
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application\/json": {
                            "schema": {
                                "$ref": "#\/components\/schemas\/FacturaAlta"
                            }
                        }
                    }
                },
                "security": [
                    {
                        "bearerAuth": []
                    }
                ]
            },
            "get": {
                "tags": [
                    "Facturas"
                ],
                "summary": "Lista facturas.",
                "operationId": "listarFacturas",
                "responses": {
                    "200": {
                        "description": "Pagina de facturas.",
                        "content": {
                            "application\/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "invoices": {
                                            "type": "array",
                                            "items": {
                                                "$ref": "#\/components\/schemas\/FacturaResumen"
                                            }
                                        },
                                        "pagination": {
                                            "$ref": "#\/components\/schemas\/Paginacion"
                                        }
                                    },
                                    "additionalProperties": false
                                }
                            }
                        }
                    },
                    "401": {
                        "$ref": "#\/components\/responses\/NoAutorizado"
                    },
                    "422": {
                        "$ref": "#\/components\/responses\/Invalida"
                    },
                    "429": {
                        "$ref": "#\/components\/responses\/DemasiadasPeticiones"
                    }
                },
                "parameters": [
                    {
                        "name": "nif",
                        "in": "query",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "description": "NIF del emisor. Opcional si el token autoriza uno solo."
                        }
                    },
                    {
                        "name": "status",
                        "in": "query",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "enum": [
                                "queued",
                                "sent",
                                "permanently_failed",
                                "accepted_with_errors"
                            ]
                        },
                        "description": "Estados: queued = en cola (la AEAT aun no responde) | sent = ACEPTADA por la AEAT (en el portal: 'Aceptada') | accepted_with_errors = aceptada con errores\/observaciones | permanently_failed = rechazada en firme (no se reintenta; se corrige y se emite de nuevo)."
                    },
                    {
                        "name": "type",
                        "in": "query",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "enum": [
                                "alta",
                                "anulacion"
                            ]
                        }
                    },
                    {
                        "name": "from",
                        "in": "query",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date"
                        },
                        "description": "Desde (YYYY-MM-DD)."
                    },
                    {
                        "name": "to",
                        "in": "query",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date"
                        },
                        "description": "Hasta (YYYY-MM-DD)."
                    },
                    {
                        "name": "limit",
                        "in": "query",
                        "required": false,
                        "schema": {
                            "type": "integer",
                            "minimum": 1,
                            "maximum": 100,
                            "default": 20
                        }
                    },
                    {
                        "name": "offset",
                        "in": "query",
                        "required": false,
                        "schema": {
                            "type": "integer",
                            "minimum": 0,
                            "default": 0
                        }
                    },
                    {
                        "name": "include",
                        "in": "query",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "enum": [
                                "detail"
                            ]
                        },
                        "description": "Con \u00abdetail\u00bb, cada factura trae tambi\u00e9n su detalle (datos del registro, anulaci\u00f3n, sustituci\u00f3n, l\u00edneas, retenci\u00f3n, forma de pago) y su cobro, en una sola llamada."
                    }
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ]
            }
        },
        "\/v1\/invoices\/export": {
            "get": {
                "tags": [
                    "Facturas"
                ],
                "summary": "Exporta facturas en CSV o Excel.",
                "operationId": "exportarFacturas",
                "responses": {
                    "200": {
                        "description": "Archivo CSV (UTF-8 con BOM; columnas id, type, status, razon, creadoEn y fechaActualizacion) o, con format=xlsx, libro de Excel con mas datos de cada factura (fecha, tipo, cliente, descripcion, base, cuota, total y acuse de la AEAT).",
                        "headers": {
                            "X-Total-Rows": {
                                "description": "Filas de datos del archivo: sirve para comprobar que no llego truncado.",
                                "schema": {
                                    "type": "string"
                                }
                            },
                            "Content-Disposition": {
                                "description": "attachment; filename=\"facturas_NIF_desde_hasta.csv\" (o .xlsx)",
                                "schema": {
                                    "type": "string"
                                }
                            },
                            "Content-Length": {
                                "description": "Solo en xlsx: tamano exacto del archivo.",
                                "schema": {
                                    "type": "string"
                                }
                            }
                        },
                        "content": {
                            "text\/csv": {
                                "schema": {
                                    "type": "string"
                                }
                            },
                            "application\/vnd.openxmlformats-officedocument.spreadsheetml.sheet": {
                                "schema": {
                                    "type": "string",
                                    "format": "binary"
                                }
                            }
                        }
                    },
                    "401": {
                        "$ref": "#\/components\/responses\/NoAutorizado"
                    },
                    "403": {
                        "$ref": "#\/components\/responses\/Prohibido"
                    },
                    "429": {
                        "$ref": "#\/components\/responses\/DemasiadasPeticiones"
                    },
                    "422": {
                        "description": "Parametro invalido (code 'invalid_request') o mas de 100000 filas (code 'export_too_large': acota con from y to).",
                        "content": {
                            "application\/json": {
                                "schema": {
                                    "$ref": "#\/components\/schemas\/Error"
                                }
                            }
                        }
                    },
                    "503": {
                        "description": "Demasiadas exportaciones en curso (code 'export_busy'); reintentar segun Retry-After.",
                        "content": {
                            "application\/json": {
                                "schema": {
                                    "$ref": "#\/components\/schemas\/Error"
                                }
                            }
                        }
                    }
                },
                "description": "Mismos filtros que el listado. Se envia por lotes (no carga todo en memoria). Maximo 6 por minuto y NIF, 3 exportaciones simultaneas en el servidor y 100000 filas. Los textos que empiezan por = + - @ se anteponen con comilla simple para evitar formulas en Excel. En CSV, si algo falla a mitad, la ultima fila del archivo es #ERROR. El Excel se arma completo antes de responder: si falla, responde 500 (internal_error) y no envia nada.",
                "parameters": [
                    {
                        "name": "nif",
                        "in": "query",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "description": "NIF del emisor. Opcional si el token autoriza uno solo."
                        }
                    },
                    {
                        "name": "status",
                        "in": "query",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "enum": [
                                "queued",
                                "sent",
                                "permanently_failed",
                                "accepted_with_errors"
                            ]
                        },
                        "description": "Estados: queued = en cola (la AEAT aun no responde) | sent = ACEPTADA por la AEAT (en el portal: 'Aceptada') | accepted_with_errors = aceptada con errores\/observaciones | permanently_failed = rechazada en firme (no se reintenta; se corrige y se emite de nuevo)."
                    },
                    {
                        "name": "type",
                        "in": "query",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "enum": [
                                "alta",
                                "anulacion"
                            ]
                        }
                    },
                    {
                        "name": "from",
                        "in": "query",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date"
                        },
                        "description": "Desde (YYYY-MM-DD, UTC)."
                    },
                    {
                        "name": "to",
                        "in": "query",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date"
                        },
                        "description": "Hasta (YYYY-MM-DD, UTC)."
                    },
                    {
                        "name": "delimiter",
                        "in": "query",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "enum": [
                                "comma",
                                "semicolon"
                            ],
                            "default": "comma"
                        },
                        "description": "Solo CSV. semicolon: Excel en espanol lo abre mejor."
                    },
                    {
                        "name": "format",
                        "in": "query",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "enum": [
                                "csv",
                                "xlsx"
                            ],
                            "default": "csv"
                        },
                        "description": "xlsx: libro de Excel con importes como numeros y mas columnas que el CSV (el CSV no cambia)."
                    }
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ]
            }
        },
        "\/v1\/invoices\/{id}": {
            "get": {
                "tags": [
                    "Facturas"
                ],
                "summary": "Consulta una factura.",
                "operationId": "consultarFactura",
                "responses": {
                    "200": {
                        "description": "Detalle de la factura.",
                        "content": {
                            "application\/json": {
                                "schema": {
                                    "$ref": "#\/components\/schemas\/FacturaDetalle"
                                }
                            }
                        }
                    },
                    "401": {
                        "$ref": "#\/components\/responses\/NoAutorizado"
                    },
                    "404": {
                        "$ref": "#\/components\/responses\/NoEncontrado"
                    },
                    "429": {
                        "$ref": "#\/components\/responses\/DemasiadasPeticiones"
                    }
                },
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "description": "Identificador 'serie-numero'."
                    },
                    {
                        "name": "nif",
                        "in": "query",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "description": "NIF del emisor. Opcional si el token autoriza uno solo."
                        }
                    }
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ]
            }
        },
        "\/v1\/invoices\/{id}\/cancel": {
            "post": {
                "tags": [
                    "Facturas"
                ],
                "summary": "Anula una factura.",
                "operationId": "anularFactura",
                "responses": {
                    "202": {
                        "description": "Anulacion puesta en cola.",
                        "content": {
                            "application\/json": {
                                "schema": {
                                    "$ref": "#\/components\/schemas\/FacturaEncolada"
                                }
                            }
                        }
                    },
                    "401": {
                        "$ref": "#\/components\/responses\/NoAutorizado"
                    },
                    "403": {
                        "$ref": "#\/components\/responses\/Prohibido"
                    },
                    "422": {
                        "$ref": "#\/components\/responses\/Invalida"
                    },
                    "429": {
                        "$ref": "#\/components\/responses\/DemasiadasPeticiones"
                    }
                },
                "description": "VERI*FACTU no permite borrar facturas: la anulacion es la unica forma de dejar una sin efecto.",
                "x-permiso": "cancelar_facturas",
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "description": "Identificador 'serie-numero'."
                    },
                    {
                        "name": "nif",
                        "in": "query",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "description": "NIF del emisor. Opcional si el token autoriza uno solo."
                        }
                    }
                ],
                "requestBody": {
                    "required": false,
                    "content": {
                        "application\/json": {
                            "schema": {
                                "$ref": "#\/components\/schemas\/FacturaAnulacion"
                            }
                        }
                    }
                },
                "security": [
                    {
                        "bearerAuth": []
                    }
                ]
            }
        },
        "\/v1\/invoices\/{id}\/acuse": {
            "get": {
                "tags": [
                    "Facturas"
                ],
                "summary": "Acuse de recibo de la AEAT de una factura.",
                "operationId": "acuseFactura",
                "responses": {
                    "200": {
                        "description": "Acuse del registro mas reciente con ese id.",
                        "content": {
                            "application\/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "id": {
                                            "type": "string"
                                        },
                                        "tipoRegistro": {
                                            "type": "string",
                                            "enum": [
                                                "alta",
                                                "anulacion"
                                            ]
                                        },
                                        "status": {
                                            "type": "string",
                                            "enum": [
                                                "queued",
                                                "sent",
                                                "permanently_failed",
                                                "accepted_with_errors"
                                            ],
                                            "description": "Estados: queued = en cola (la AEAT aun no responde) | sent = ACEPTADA por la AEAT (en el portal: 'Aceptada') | accepted_with_errors = aceptada con errores\/observaciones | permanently_failed = rechazada en firme (no se reintenta; se corrige y se emite de nuevo)."
                                        },
                                        "estadoAeat": {
                                            "type": [
                                                "string",
                                                "null"
                                            ]
                                        },
                                        "csv": {
                                            "type": [
                                                "string",
                                                "null"
                                            ]
                                        },
                                        "codigoError": {
                                            "type": [
                                                "string",
                                                "null"
                                            ]
                                        },
                                        "descripcionError": {
                                            "type": [
                                                "string",
                                                "null"
                                            ]
                                        },
                                        "recibidoEn": {
                                            "type": [
                                                "string",
                                                "null"
                                            ],
                                            "format": "date-time"
                                        }
                                    },
                                    "additionalProperties": false
                                }
                            }
                        }
                    },
                    "401": {
                        "$ref": "#\/components\/responses\/NoAutorizado"
                    },
                    "404": {
                        "$ref": "#\/components\/responses\/NoEncontrado"
                    },
                    "429": {
                        "$ref": "#\/components\/responses\/DemasiadasPeticiones"
                    },
                    "409": {
                        "description": "Todavia no hay acuse (code 'acuse_not_available').",
                        "content": {
                            "application\/json": {
                                "schema": {
                                    "$ref": "#\/components\/schemas\/Error"
                                }
                            }
                        }
                    },
                    "503": {
                        "description": "No se pudo consultar en este momento.",
                        "content": {
                            "application\/json": {
                                "schema": {
                                    "$ref": "#\/components\/schemas\/Error"
                                }
                            }
                        }
                    }
                },
                "description": "La respuesta cruda de la AEAT se guarda para diagnostico pero no se devuelve.",
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "description": "Identificador 'serie-numero'."
                    },
                    {
                        "name": "nif",
                        "in": "query",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "description": "NIF del emisor. Opcional si el token autoriza uno solo."
                        }
                    }
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ]
            }
        },
        "\/v1\/invoices\/{id}\/retry": {
            "post": {
                "tags": [
                    "Facturas"
                ],
                "summary": "Fuerza el reintento de un envio fallido.",
                "operationId": "reintentarFactura",
                "responses": {
                    "202": {
                        "description": "Reintento en cola.",
                        "content": {
                            "application\/json": {
                                "schema": {
                                    "$ref": "#\/components\/schemas\/FacturaEncolada"
                                }
                            }
                        }
                    },
                    "401": {
                        "$ref": "#\/components\/responses\/NoAutorizado"
                    },
                    "403": {
                        "$ref": "#\/components\/responses\/Prohibido"
                    },
                    "404": {
                        "$ref": "#\/components\/responses\/NoEncontrado"
                    },
                    "409": {
                        "$ref": "#\/components\/responses\/Conflicto"
                    },
                    "422": {
                        "$ref": "#\/components\/responses\/Invalida"
                    },
                    "429": {
                        "$ref": "#\/components\/responses\/DemasiadasPeticiones"
                    }
                },
                "description": "409 con code 'already_sent' (ya enviado) o 'requires_manual_review' (requiere revision manual).",
                "x-permiso": "reintentar_facturas",
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "description": "Identificador 'serie-numero'."
                    },
                    {
                        "name": "nif",
                        "in": "query",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "description": "NIF del emisor. Opcional si el token autoriza uno solo."
                        }
                    },
                    {
                        "name": "type",
                        "in": "query",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "enum": [
                                "alta",
                                "anulacion"
                            ]
                        },
                        "description": "Para desambiguar si existen ambos registros."
                    }
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ]
            }
        },
        "\/v1\/invoices\/{id}\/qr": {
            "get": {
                "tags": [
                    "Facturas"
                ],
                "summary": "QR de una factura.",
                "operationId": "qrFactura",
                "responses": {
                    "200": {
                        "description": "Imagen PNG, o JSON con la URL si format=url.",
                        "content": {
                            "image\/png": {
                                "schema": {
                                    "type": "string",
                                    "format": "binary"
                                }
                            },
                            "application\/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "url": {
                                            "type": "string"
                                        }
                                    },
                                    "additionalProperties": false
                                }
                            }
                        }
                    },
                    "401": {
                        "$ref": "#\/components\/responses\/NoAutorizado"
                    },
                    "404": {
                        "$ref": "#\/components\/responses\/NoEncontrado"
                    },
                    "429": {
                        "$ref": "#\/components\/responses\/DemasiadasPeticiones"
                    }
                },
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "description": "Identificador 'serie-numero'."
                    },
                    {
                        "name": "nif",
                        "in": "query",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "description": "NIF del emisor. Opcional si el token autoriza uno solo."
                        }
                    },
                    {
                        "name": "format",
                        "in": "query",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "enum": [
                                "png",
                                "url"
                            ],
                            "default": "png"
                        }
                    }
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ]
            }
        },
        "\/v1\/registros\/calcular": {
            "post": {
                "tags": [
                    "Facturas"
                ],
                "summary": "Calcula base, cuota y total de una factura sin emitirla.",
                "operationId": "calcularFactura",
                "responses": {
                    "200": {
                        "description": "Importes calculados (texto decimal con 2 decimales).",
                        "content": {
                            "application\/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "lines": {
                                            "type": "array",
                                            "items": {
                                                "type": "object",
                                                "properties": {
                                                    "index": {
                                                        "type": "integer"
                                                    },
                                                    "taxBase": {
                                                        "type": "string"
                                                    },
                                                    "taxRate": {
                                                        "type": "string"
                                                    }
                                                },
                                                "additionalProperties": false
                                            }
                                        },
                                        "breakdown": {
                                            "type": "array",
                                            "items": {
                                                "type": "object",
                                                "properties": {
                                                    "taxRate": {
                                                        "type": "string"
                                                    },
                                                    "taxBase": {
                                                        "type": "string"
                                                    },
                                                    "taxAmount": {
                                                        "type": "string"
                                                    }
                                                },
                                                "additionalProperties": false
                                            },
                                            "description": "Una fila por tipo de IVA."
                                        },
                                        "taxBase": {
                                            "type": "string"
                                        },
                                        "taxAmount": {
                                            "type": "string"
                                        },
                                        "withholdingRate": {
                                            "type": [
                                                "string",
                                                "null"
                                            ]
                                        },
                                        "withholdingAmount": {
                                            "type": "string"
                                        },
                                        "totalAmount": {
                                            "type": "string",
                                            "description": "Base mas cuotas: lo que va al registro de la AEAT."
                                        },
                                        "amountPayable": {
                                            "type": "string",
                                            "description": "totalAmount menos la retencion."
                                        }
                                    },
                                    "additionalProperties": false
                                }
                            }
                        }
                    },
                    "401": {
                        "$ref": "#\/components\/responses\/NoAutorizado"
                    },
                    "422": {
                        "$ref": "#\/components\/responses\/Invalida"
                    },
                    "429": {
                        "$ref": "#\/components\/responses\/DemasiadasPeticiones"
                    }
                },
                "description": "Solo calcula, no emite nada. Redondeo al centimo, medio hacia arriba; la cuota se calcula una vez por tipo de IVA sobre la suma de sus bases; la retencion sobre la base total.",
                "requestBody": {
                    "required": true,
                    "content": {
                        "application\/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "lines": {
                                        "type": "array",
                                        "minItems": 1,
                                        "maxItems": 200,
                                        "items": {
                                            "type": "object",
                                            "properties": {
                                                "quantity": {
                                                    "type": [
                                                        "number",
                                                        "string"
                                                    ],
                                                    "description": "Hasta 6 enteros y 3 decimales."
                                                },
                                                "unitPrice": {
                                                    "type": [
                                                        "number",
                                                        "string"
                                                    ],
                                                    "description": "Hasta 9 enteros y 4 decimales; admite signo -."
                                                },
                                                "discount": {
                                                    "type": [
                                                        "number",
                                                        "string"
                                                    ],
                                                    "description": "Porcentaje 0 a 100, hasta 2 decimales."
                                                },
                                                "taxRate": {
                                                    "type": [
                                                        "number",
                                                        "string"
                                                    ],
                                                    "description": "Tipo de IVA del catalogo de la AEAT."
                                                }
                                            },
                                            "additionalProperties": false,
                                            "required": [
                                                "quantity",
                                                "unitPrice",
                                                "taxRate"
                                            ]
                                        }
                                    },
                                    "withholdingRate": {
                                        "type": [
                                            "number",
                                            "string",
                                            "null"
                                        ],
                                        "description": "Retencion (IRPF) 0 a 100, opcional."
                                    }
                                },
                                "additionalProperties": false,
                                "required": [
                                    "lines"
                                ]
                            }
                        }
                    }
                },
                "security": [
                    {
                        "bearerAuth": []
                    }
                ]
            }
        },
        "\/v1\/status": {
            "get": {
                "tags": [
                    "Facturas"
                ],
                "summary": "Si el emisor puede enviar ahora a la AEAT.",
                "operationId": "estadoEmisor",
                "responses": {
                    "200": {
                        "description": "Control de flujo que indica la AEAT.",
                        "content": {
                            "application\/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "canSendNow": {
                                            "type": "boolean"
                                        },
                                        "nextAllowedSendAt": {
                                            "type": [
                                                "string",
                                                "null"
                                            ],
                                            "format": "date-time"
                                        }
                                    },
                                    "additionalProperties": false
                                }
                            }
                        }
                    },
                    "401": {
                        "$ref": "#\/components\/responses\/NoAutorizado"
                    },
                    "429": {
                        "$ref": "#\/components\/responses\/DemasiadasPeticiones"
                    }
                },
                "parameters": [
                    {
                        "name": "nif",
                        "in": "query",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "description": "NIF del emisor. Opcional si el token autoriza uno solo."
                        }
                    }
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ]
            }
        },
        "\/v1\/nif\/validar": {
            "get": {
                "tags": [
                    "Emisores"
                ],
                "summary": "Valida un NIF (formato y digito de control).",
                "operationId": "validarNif",
                "responses": {
                    "200": {
                        "description": "Resultado.",
                        "content": {
                            "application\/json": {
                                "schema": {
                                    "$ref": "#\/components\/schemas\/AnalisisNif"
                                }
                            }
                        }
                    },
                    "401": {
                        "$ref": "#\/components\/responses\/NoAutorizado"
                    },
                    "422": {
                        "$ref": "#\/components\/responses\/Invalida"
                    }
                },
                "parameters": [
                    {
                        "name": "nif",
                        "in": "query",
                        "required": true,
                        "schema": {
                            "type": "string"
                        }
                    }
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ]
            }
        },
        "\/v1\/productos": {
            "get": {
                "summary": "Lista los productos guardados de la cuenta.",
                "operationId": "listarProductos",
                "responses": {
                    "200": {
                        "description": "Productos.",
                        "content": {
                            "application\/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "productos": {
                                            "type": "array",
                                            "items": {
                                                "$ref": "#\/components\/schemas\/Producto"
                                            }
                                        }
                                    },
                                    "additionalProperties": false
                                }
                            }
                        }
                    },
                    "401": {
                        "$ref": "#\/components\/responses\/NoAutorizado"
                    },
                    "429": {
                        "$ref": "#\/components\/responses\/DemasiadasPeticiones"
                    }
                },
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "tags": [
                    "Productos"
                ]
            },
            "post": {
                "summary": "Crea un producto guardado.",
                "operationId": "crearProducto",
                "description": "409 'producto_duplicado' si el nombre ya esta guardado en la cuenta.",
                "x-permiso": "administrar_productos",
                "requestBody": {
                    "required": true,
                    "content": {
                        "application\/json": {
                            "schema": {
                                "$ref": "#\/components\/schemas\/ProductoAlta"
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Producto creado.",
                        "content": {
                            "application\/json": {
                                "schema": {
                                    "$ref": "#\/components\/schemas\/Producto"
                                }
                            }
                        }
                    },
                    "401": {
                        "$ref": "#\/components\/responses\/NoAutorizado"
                    },
                    "403": {
                        "$ref": "#\/components\/responses\/Prohibido"
                    },
                    "409": {
                        "$ref": "#\/components\/responses\/Conflicto"
                    },
                    "422": {
                        "$ref": "#\/components\/responses\/Invalida"
                    },
                    "429": {
                        "$ref": "#\/components\/responses\/DemasiadasPeticiones"
                    }
                },
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "tags": [
                    "Productos"
                ]
            }
        },
        "\/v1\/productos\/{id}": {
            "put": {
                "summary": "Actualiza un producto guardado.",
                "operationId": "actualizarProducto",
                "x-permiso": "administrar_productos",
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        },
                        "description": "UUID del producto."
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application\/json": {
                            "schema": {
                                "$ref": "#\/components\/schemas\/ProductoAlta"
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "Producto actualizado.",
                        "content": {
                            "application\/json": {
                                "schema": {
                                    "$ref": "#\/components\/schemas\/Producto"
                                }
                            }
                        }
                    },
                    "401": {
                        "$ref": "#\/components\/responses\/NoAutorizado"
                    },
                    "403": {
                        "$ref": "#\/components\/responses\/Prohibido"
                    },
                    "404": {
                        "$ref": "#\/components\/responses\/NoEncontrado"
                    },
                    "409": {
                        "$ref": "#\/components\/responses\/Conflicto"
                    },
                    "422": {
                        "$ref": "#\/components\/responses\/Invalida"
                    }
                },
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "tags": [
                    "Productos"
                ]
            },
            "delete": {
                "summary": "Borra un producto guardado.",
                "operationId": "eliminarProducto",
                "x-permiso": "administrar_productos",
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        },
                        "description": "UUID del producto."
                    }
                ],
                "responses": {
                    "204": {
                        "description": "Producto borrado."
                    },
                    "401": {
                        "$ref": "#\/components\/responses\/NoAutorizado"
                    },
                    "403": {
                        "$ref": "#\/components\/responses\/Prohibido"
                    },
                    "404": {
                        "$ref": "#\/components\/responses\/NoEncontrado"
                    }
                },
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "tags": [
                    "Productos"
                ]
            }
        },
        "\/v1\/clientes": {
            "get": {
                "summary": "Lista los clientes guardados de la cuenta.",
                "operationId": "listarClientes",
                "responses": {
                    "200": {
                        "description": "Clientes.",
                        "content": {
                            "application\/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "clientes": {
                                            "type": "array",
                                            "items": {
                                                "$ref": "#\/components\/schemas\/Cliente"
                                            }
                                        }
                                    },
                                    "additionalProperties": false
                                }
                            }
                        }
                    },
                    "401": {
                        "$ref": "#\/components\/responses\/NoAutorizado"
                    },
                    "429": {
                        "$ref": "#\/components\/responses\/DemasiadasPeticiones"
                    }
                },
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "tags": [
                    "Clientes"
                ]
            },
            "post": {
                "summary": "Crea un cliente guardado.",
                "operationId": "crearCliente",
                "description": "409 'cliente_duplicado' si el NIF ya esta guardado en la cuenta. El NIF se valida: si es espa\u00f1ol (o lleva prefijo ES) se comprueban formato y d\u00edgito de control; un identificador extranjero (prefijo de pa\u00eds + d\u00edgitos, p. ej. FR12345678901) solo se comprueba en formato. Errores 422: nif_formato_invalido, nif_digito_invalido (con field: nif).",
                "x-permiso": "administrar_clientes",
                "requestBody": {
                    "required": true,
                    "content": {
                        "application\/json": {
                            "schema": {
                                "$ref": "#\/components\/schemas\/ClienteAlta"
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Cliente creado.",
                        "content": {
                            "application\/json": {
                                "schema": {
                                    "$ref": "#\/components\/schemas\/Cliente"
                                }
                            }
                        }
                    },
                    "401": {
                        "$ref": "#\/components\/responses\/NoAutorizado"
                    },
                    "403": {
                        "$ref": "#\/components\/responses\/Prohibido"
                    },
                    "409": {
                        "$ref": "#\/components\/responses\/Conflicto"
                    },
                    "422": {
                        "$ref": "#\/components\/responses\/Invalida"
                    },
                    "429": {
                        "$ref": "#\/components\/responses\/DemasiadasPeticiones"
                    }
                },
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "tags": [
                    "Clientes"
                ]
            }
        },
        "\/v1\/clientes\/{id}": {
            "put": {
                "summary": "Actualiza un cliente guardado.",
                "operationId": "actualizarCliente",
                "x-permiso": "administrar_clientes",
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        },
                        "description": "UUID del cliente."
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application\/json": {
                            "schema": {
                                "$ref": "#\/components\/schemas\/ClienteAlta"
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "Cliente actualizado.",
                        "content": {
                            "application\/json": {
                                "schema": {
                                    "$ref": "#\/components\/schemas\/Cliente"
                                }
                            }
                        }
                    },
                    "401": {
                        "$ref": "#\/components\/responses\/NoAutorizado"
                    },
                    "403": {
                        "$ref": "#\/components\/responses\/Prohibido"
                    },
                    "404": {
                        "$ref": "#\/components\/responses\/NoEncontrado"
                    },
                    "409": {
                        "$ref": "#\/components\/responses\/Conflicto"
                    },
                    "422": {
                        "$ref": "#\/components\/responses\/Invalida"
                    }
                },
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "tags": [
                    "Clientes"
                ],
                "description": "El NIF se valida: si es espa\u00f1ol (o lleva prefijo ES) se comprueban formato y d\u00edgito de control; un identificador extranjero (prefijo de pa\u00eds + d\u00edgitos, p. ej. FR12345678901) solo se comprueba en formato. Errores 422: nif_formato_invalido, nif_digito_invalido (con field: nif)."
            },
            "delete": {
                "summary": "Borra un cliente guardado.",
                "operationId": "eliminarCliente",
                "x-permiso": "administrar_clientes",
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        },
                        "description": "UUID del cliente."
                    }
                ],
                "responses": {
                    "204": {
                        "description": "Cliente borrado."
                    },
                    "401": {
                        "$ref": "#\/components\/responses\/NoAutorizado"
                    },
                    "403": {
                        "$ref": "#\/components\/responses\/Prohibido"
                    },
                    "404": {
                        "$ref": "#\/components\/responses\/NoEncontrado"
                    }
                },
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "tags": [
                    "Clientes"
                ]
            }
        },
        "\/v1\/emisores": {
            "get": {
                "tags": [
                    "Emisores"
                ],
                "summary": "Lista los emisores del token.",
                "operationId": "listarEmisores",
                "responses": {
                    "200": {
                        "description": "Emisores.",
                        "content": {
                            "application\/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "emisores": {
                                            "type": "array",
                                            "items": {
                                                "$ref": "#\/components\/schemas\/Emisor"
                                            }
                                        }
                                    },
                                    "additionalProperties": false
                                }
                            }
                        }
                    },
                    "401": {
                        "$ref": "#\/components\/responses\/NoAutorizado"
                    },
                    "429": {
                        "$ref": "#\/components\/responses\/DemasiadasPeticiones"
                    }
                },
                "security": [
                    {
                        "bearerAuth": []
                    }
                ]
            },
            "post": {
                "tags": [
                    "Emisores"
                ],
                "summary": "Da de alta un emisor en la cuenta.",
                "operationId": "crearEmisor",
                "responses": {
                    "201": {
                        "description": "Emisor creado.",
                        "content": {
                            "application\/json": {
                                "schema": {
                                    "$ref": "#\/components\/schemas\/Emisor"
                                }
                            }
                        }
                    },
                    "401": {
                        "$ref": "#\/components\/responses\/NoAutorizado"
                    },
                    "403": {
                        "$ref": "#\/components\/responses\/Prohibido"
                    },
                    "409": {
                        "$ref": "#\/components\/responses\/Conflicto"
                    },
                    "422": {
                        "$ref": "#\/components\/responses\/Invalida"
                    },
                    "429": {
                        "$ref": "#\/components\/responses\/DemasiadasPeticiones"
                    }
                },
                "description": "409 'nif_duplicado' (el NIF ya pertenece a una cuenta); 403 'business_rule_violation' (regla de negocio, p. ej. NIF no valido para el alta); 422 'nif_formato_invalido' o 'nif_digito_invalido'.",
                "x-permiso": "administrar_emisores",
                "requestBody": {
                    "required": true,
                    "content": {
                        "application\/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "nif": {
                                        "type": "string"
                                    },
                                    "nombreEmisor": {
                                        "type": "string",
                                        "maxLength": 200
                                    }
                                },
                                "additionalProperties": false,
                                "required": [
                                    "nif",
                                    "nombreEmisor"
                                ]
                            }
                        }
                    }
                },
                "security": [
                    {
                        "bearerAuth": []
                    }
                ]
            }
        },
        "\/v1\/emisores\/{id}": {
            "get": {
                "tags": [
                    "Emisores"
                ],
                "summary": "Consulta un emisor.",
                "operationId": "consultarEmisor",
                "responses": {
                    "200": {
                        "description": "Emisor.",
                        "content": {
                            "application\/json": {
                                "schema": {
                                    "$ref": "#\/components\/schemas\/Emisor"
                                }
                            }
                        }
                    },
                    "401": {
                        "$ref": "#\/components\/responses\/NoAutorizado"
                    },
                    "404": {
                        "$ref": "#\/components\/responses\/NoEncontrado"
                    }
                },
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "description": "NIF del emisor."
                    }
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ]
            },
            "put": {
                "tags": [
                    "Emisores"
                ],
                "operationId": "actualizarEmisor",
                "summary": "Actualiza los datos fiscales de un emisor (razon social, nombre comercial, domicilio, actividad, IRPF por defecto).",
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "description": "NIF del emisor."
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application\/json": {
                            "schema": {
                                "$ref": "#\/components\/schemas\/EmisorDatosFiscales"
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "Datos fiscales actualizados.",
                        "content": {
                            "application\/json": {
                                "schema": {
                                    "$ref": "#\/components\/schemas\/EmisorDatosFiscales"
                                }
                            }
                        }
                    },
                    "404": {
                        "description": "El emisor no existe o no pertenece a la cuenta.",
                        "content": {
                            "application\/json": {
                                "schema": {
                                    "$ref": "#\/components\/schemas\/Error"
                                }
                            }
                        }
                    },
                    "422": {
                        "description": "Algun campo no es valido (code invalid_request, con field).",
                        "content": {
                            "application\/json": {
                                "schema": {
                                    "$ref": "#\/components\/schemas\/Error"
                                }
                            }
                        }
                    }
                }
            },
            "delete": {
                "summary": "Eliminar una empresa (NIF) de la cuenta",
                "description": "Solo si no es la empresa principal y nunca ha emitido facturas (los registros VERI*FACTU se conservan). Borra tambien su certificado, series y borradores.",
                "responses": {
                    "200": {
                        "description": "Empresa eliminada.",
                        "content": {
                            "application\/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "nif": {
                                            "type": "string"
                                        },
                                        "eliminado": {
                                            "type": "boolean"
                                        }
                                    }
                                }
                            }
                        }
                    },
                    "404": {
                        "description": "El NIF no existe o no pertenece a la cuenta (not_found)."
                    },
                    "409": {
                        "description": "No se puede eliminar: emisor_principal o emisor_con_facturas."
                    },
                    "401": {
                        "$ref": "#\/components\/responses\/NoAutorizado"
                    }
                },
                "tags": [
                    "Emisores"
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "description": "NIF del emisor."
                    }
                ]
            }
        },
        "\/v1\/emisores\/{id}\/validar-nif": {
            "get": {
                "tags": [
                    "Emisores"
                ],
                "summary": "Valida el NIF del emisor.",
                "operationId": "validarNifEmisor",
                "responses": {
                    "200": {
                        "description": "Resultado.",
                        "content": {
                            "application\/json": {
                                "schema": {
                                    "$ref": "#\/components\/schemas\/AnalisisNif"
                                }
                            }
                        }
                    },
                    "401": {
                        "$ref": "#\/components\/responses\/NoAutorizado"
                    },
                    "404": {
                        "$ref": "#\/components\/responses\/NoEncontrado"
                    }
                },
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "description": "NIF del emisor."
                    }
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ]
            }
        },
        "\/v1\/emisores\/{id}\/certificado": {
            "get": {
                "tags": [
                    "Certificados"
                ],
                "summary": "Estado del certificado del emisor.",
                "operationId": "consultarCertificado",
                "responses": {
                    "200": {
                        "description": "Estado. Nunca incluye la clave privada.",
                        "content": {
                            "application\/json": {
                                "schema": {
                                    "$ref": "#\/components\/schemas\/CertificadoEstado"
                                }
                            }
                        }
                    },
                    "401": {
                        "$ref": "#\/components\/responses\/NoAutorizado"
                    },
                    "404": {
                        "$ref": "#\/components\/responses\/NoEncontrado"
                    }
                },
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "description": "NIF del emisor."
                    }
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ]
            },
            "post": {
                "tags": [
                    "Certificados"
                ],
                "summary": "Sube el certificado (.p12 o .pfx) del emisor.",
                "operationId": "subirCertificado",
                "responses": {
                    "201": {
                        "description": "Certificado guardado (primera vez).",
                        "content": {
                            "application\/json": {
                                "schema": {
                                    "$ref": "#\/components\/schemas\/CertificadoInfo"
                                }
                            }
                        }
                    },
                    "200": {
                        "description": "Certificado reemplazado.",
                        "content": {
                            "application\/json": {
                                "schema": {
                                    "$ref": "#\/components\/schemas\/CertificadoInfo"
                                }
                            }
                        }
                    },
                    "401": {
                        "$ref": "#\/components\/responses\/NoAutorizado"
                    },
                    "403": {
                        "$ref": "#\/components\/responses\/Prohibido"
                    },
                    "404": {
                        "$ref": "#\/components\/responses\/NoEncontrado"
                    },
                    "422": {
                        "$ref": "#\/components\/responses\/Invalida"
                    },
                    "429": {
                        "$ref": "#\/components\/responses\/DemasiadasPeticiones"
                    }
                },
                "description": "Se valida (abre, la clave corresponde, no esta caducado, es del NIF del emisor) y se guarda CIFRADO. 422 con code 'invalid_request', 'invalid_certificate', 'certificate_nif_mismatch', 'certificate_expired' o 'certificate_not_yet_valid'. Maximo 20 subidas por hora y cuenta.",
                "x-permiso": "administrar_emisores",
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "description": "NIF del emisor."
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application\/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "certificado": {
                                        "type": "string",
                                        "description": "Archivo .p12\/.pfx en base64 (maximo 100000 caracteres)."
                                    },
                                    "password": {
                                        "type": "string",
                                        "description": "Contrasena del archivo.",
                                        "maxLength": 256
                                    }
                                },
                                "additionalProperties": false,
                                "required": [
                                    "certificado",
                                    "password"
                                ]
                            }
                        }
                    }
                },
                "security": [
                    {
                        "bearerAuth": []
                    }
                ]
            }
        },
        "\/v1\/emisores\/{id}\/series": {
            "get": {
                "tags": [
                    "Series"
                ],
                "summary": "Series de numeracion del emisor.",
                "operationId": "listarSeries",
                "responses": {
                    "200": {
                        "description": "Series.",
                        "content": {
                            "application\/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "series": {
                                            "type": "array",
                                            "items": {
                                                "$ref": "#\/components\/schemas\/Serie"
                                            }
                                        }
                                    },
                                    "additionalProperties": false
                                }
                            }
                        }
                    },
                    "401": {
                        "$ref": "#\/components\/responses\/NoAutorizado"
                    },
                    "404": {
                        "$ref": "#\/components\/responses\/NoEncontrado"
                    }
                },
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "description": "NIF del emisor."
                    }
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ]
            },
            "put": {
                "tags": [
                    "Series"
                ],
                "summary": "Configura las series de numeracion.",
                "operationId": "configurarSeries",
                "responses": {
                    "200": {
                        "description": "Series resultantes.",
                        "content": {
                            "application\/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "series": {
                                            "type": "array",
                                            "items": {
                                                "$ref": "#\/components\/schemas\/Serie"
                                            }
                                        }
                                    },
                                    "additionalProperties": false
                                }
                            }
                        }
                    },
                    "401": {
                        "$ref": "#\/components\/responses\/NoAutorizado"
                    },
                    "403": {
                        "$ref": "#\/components\/responses\/Prohibido"
                    },
                    "404": {
                        "$ref": "#\/components\/responses\/NoEncontrado"
                    },
                    "409": {
                        "$ref": "#\/components\/responses\/Conflicto"
                    },
                    "422": {
                        "$ref": "#\/components\/responses\/Invalida"
                    },
                    "429": {
                        "$ref": "#\/components\/responses\/DemasiadasPeticiones"
                    }
                },
                "description": "409 'series_number_too_low' si se intenta bajar el siguiente numero de una serie que ya asigno numeros.",
                "x-permiso": "administrar_emisores",
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "description": "NIF del emisor."
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application\/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "series": {
                                        "type": "array",
                                        "maxItems": 20,
                                        "items": {
                                            "type": "object",
                                            "properties": {
                                                "serie": {
                                                    "type": "string",
                                                    "pattern": "^[A-Za-z0-9][A-Za-z0-9_-]{0,19}$"
                                                },
                                                "siguienteNumero": {
                                                    "type": "integer",
                                                    "minimum": 1,
                                                    "maximum": 999999999
                                                },
                                                "digitos": {
                                                    "type": "integer",
                                                    "minimum": 1,
                                                    "maximum": 10,
                                                    "default": 4
                                                }
                                            },
                                            "additionalProperties": false,
                                            "required": [
                                                "serie",
                                                "siguienteNumero"
                                            ]
                                        }
                                    }
                                },
                                "additionalProperties": false,
                                "required": [
                                    "series"
                                ]
                            }
                        }
                    }
                },
                "security": [
                    {
                        "bearerAuth": []
                    }
                ]
            }
        },
        "\/v1\/avisos\/certificados": {
            "get": {
                "tags": [
                    "Certificados"
                ],
                "summary": "Certificados que caducan pronto.",
                "operationId": "avisosCertificados",
                "responses": {
                    "200": {
                        "description": "Avisos, del que antes caduca al ultimo.",
                        "content": {
                            "application\/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "dias": {
                                            "type": "integer"
                                        },
                                        "avisos": {
                                            "type": "array",
                                            "items": {
                                                "$ref": "#\/components\/schemas\/AvisoCertificado"
                                            }
                                        }
                                    },
                                    "additionalProperties": false
                                }
                            }
                        }
                    },
                    "401": {
                        "$ref": "#\/components\/responses\/NoAutorizado"
                    },
                    "422": {
                        "$ref": "#\/components\/responses\/Invalida"
                    }
                },
                "description": "La API no envia correos: el portal consulta esto cada dia y avisa una vez por certificado (huellaSha256) y nivel.",
                "parameters": [
                    {
                        "name": "dias",
                        "in": "query",
                        "required": false,
                        "schema": {
                            "type": "integer",
                            "minimum": 1,
                            "maximum": 1500,
                            "default": 60
                        },
                        "description": "Plazo en dias."
                    }
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ]
            }
        },
        "\/v1\/cuentas": {
            "post": {
                "tags": [
                    "Cuentas"
                ],
                "summary": "Registra una cuenta nueva con su primer NIF.",
                "operationId": "registrarCuenta",
                "responses": {
                    "201": {
                        "description": "Cuenta creada.",
                        "content": {
                            "application\/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "cuentaId": {
                                            "type": "string",
                                            "format": "uuid"
                                        },
                                        "nifId": {
                                            "type": "string",
                                            "format": "uuid"
                                        },
                                        "apiKey": {
                                            "type": "string",
                                            "description": "Se muestra UNA sola vez."
                                        },
                                        "aviso": {
                                            "type": "string"
                                        }
                                    },
                                    "additionalProperties": false
                                }
                            }
                        }
                    },
                    "409": {
                        "$ref": "#\/components\/responses\/Conflicto"
                    },
                    "422": {
                        "$ref": "#\/components\/responses\/Invalida"
                    },
                    "429": {
                        "$ref": "#\/components\/responses\/DemasiadasPeticiones"
                    }
                },
                "requestBody": {
                    "required": true,
                    "content": {
                        "application\/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "email": {
                                        "type": "string",
                                        "format": "email"
                                    },
                                    "nif": {
                                        "type": "string"
                                    },
                                    "nombreEmisor": {
                                        "type": "string"
                                    },
                                    "telefono": {
                                        "type": "string"
                                    },
                                    "password": {
                                        "type": "string",
                                        "minLength": 8
                                    },
                                    "nombreContacto": {
                                        "type": [
                                            "string",
                                            "null"
                                        ],
                                        "maxLength": 120,
                                        "description": "Nombre de la persona que crea la cuenta."
                                    },
                                    "tipoCliente": {
                                        "type": [
                                            "string",
                                            "null"
                                        ],
                                        "maxLength": 60,
                                        "description": "Autonomo o profesional, Empresa o Gestoria."
                                    },
                                    "nombreComercial": {
                                        "type": [
                                            "string",
                                            "null"
                                        ],
                                        "maxLength": 200,
                                        "description": "Nombre comercial. Si se informa, encabeza la factura en lugar de la razon social."
                                    },
                                    "direccion": {
                                        "type": [
                                            "string",
                                            "null"
                                        ],
                                        "maxLength": 250,
                                        "description": "Domicilio fiscal (calle y numero)."
                                    },
                                    "codigoPostal": {
                                        "type": [
                                            "string",
                                            "null"
                                        ],
                                        "maxLength": 10,
                                        "description": "Codigo postal."
                                    },
                                    "ciudad": {
                                        "type": [
                                            "string",
                                            "null"
                                        ],
                                        "maxLength": 120,
                                        "description": "Ciudad."
                                    },
                                    "provincia": {
                                        "type": [
                                            "string",
                                            "null"
                                        ],
                                        "maxLength": 60,
                                        "description": "Provincia."
                                    },
                                    "actividad": {
                                        "type": [
                                            "string",
                                            "null"
                                        ],
                                        "maxLength": 80,
                                        "description": "Actividad (para proponer el IVA en el portal)."
                                    },
                                    "irpfDefecto": {
                                        "type": [
                                            "number",
                                            "null"
                                        ],
                                        "minimum": 0,
                                        "maximum": 47,
                                        "description": "Retencion de IRPF por defecto, en %."
                                    },
                                    "verificacion": {
                                        "type": "string",
                                        "description": "Ticket de POST \/v1\/auth\/registro\/verificar. Si es valido, la cuenta queda con el correo confirmado. Opcional para integraciones por API."
                                    },
                                    "idioma": {
                                        "type": "string",
                                        "enum": [
                                            "es",
                                            "ca",
                                            "en"
                                        ],
                                        "example": "es",
                                        "description": "Opcional. Idioma de la cuenta para los correos de Nodda (es, ca, en). Por defecto, es."
                                    }
                                },
                                "additionalProperties": false,
                                "required": [
                                    "email",
                                    "nif",
                                    "nombreEmisor"
                                ]
                            }
                        }
                    }
                },
                "security": []
            }
        },
        "\/v1\/auth\/login": {
            "post": {
                "tags": [
                    "Sesion"
                ],
                "summary": "Inicia sesion en el portal.",
                "operationId": "login",
                "responses": {
                    "200": {
                        "description": "Token de sesion (usar como Bearer).",
                        "content": {
                            "application\/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "token": {
                                            "type": "string"
                                        },
                                        "twofaRequired": {
                                            "type": "boolean",
                                            "description": "true si la cuenta tiene 2FA: no se entrega token, hay que llamar a \/v1\/auth\/2fa\/verificar."
                                        },
                                        "desafio": {
                                            "type": "string",
                                            "description": "Solo con twofaRequired=true. Caduca a los 5 minutos."
                                        }
                                    },
                                    "additionalProperties": false
                                }
                            }
                        }
                    },
                    "401": {
                        "description": "Credenciales incorrectas (code 'invalid_credentials').",
                        "content": {
                            "application\/json": {
                                "schema": {
                                    "$ref": "#\/components\/schemas\/Error"
                                }
                            }
                        }
                    },
                    "422": {
                        "$ref": "#\/components\/responses\/Invalida"
                    },
                    "429": {
                        "$ref": "#\/components\/responses\/DemasiadasPeticiones"
                    }
                },
                "requestBody": {
                    "required": true,
                    "content": {
                        "application\/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "email": {
                                        "type": "string",
                                        "format": "email"
                                    },
                                    "password": {
                                        "type": "string"
                                    }
                                },
                                "additionalProperties": false,
                                "required": [
                                    "email",
                                    "password"
                                ]
                            }
                        }
                    }
                },
                "security": [],
                "description": "Si la cuenta tiene verificacion en dos pasos, responde {twofaRequired: true, desafio} en lugar del token; ver \/v1\/auth\/2fa\/verificar."
            }
        },
        "\/v1\/auth\/logout": {
            "post": {
                "tags": [
                    "Sesion"
                ],
                "summary": "Cierra la sesion.",
                "operationId": "logout",
                "responses": {
                    "200": {
                        "description": "Sesion cerrada.",
                        "content": {
                            "application\/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "cerrado": {
                                            "type": "boolean"
                                        }
                                    },
                                    "additionalProperties": false
                                }
                            }
                        }
                    },
                    "401": {
                        "$ref": "#\/components\/responses\/NoAutorizado"
                    }
                },
                "security": [
                    {
                        "bearerAuth": []
                    }
                ]
            }
        },
        "\/v1\/auth\/me": {
            "get": {
                "tags": [
                    "Sesion"
                ],
                "summary": "Datos de la sesion actual.",
                "operationId": "sesionActual",
                "responses": {
                    "200": {
                        "description": "Sesion vigente.",
                        "content": {
                            "application\/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "nifs": {
                                            "type": "array",
                                            "items": {
                                                "type": "object",
                                                "properties": {
                                                    "nif": {
                                                        "type": "string"
                                                    },
                                                    "nombreEmisor": {
                                                        "type": "string"
                                                    },
                                                    "cupoIncluido": {
                                                        "type": [
                                                            "integer",
                                                            "null"
                                                        ]
                                                    },
                                                    "cupoConsumido": {
                                                        "type": "integer"
                                                    }
                                                },
                                                "additionalProperties": false,
                                                "required": [
                                                    "nif",
                                                    "nombreEmisor"
                                                ]
                                            }
                                        },
                                        "esAdmin": {
                                            "type": "boolean"
                                        },
                                        "rol": {
                                            "type": [
                                                "string",
                                                "null"
                                            ],
                                            "enum": [
                                                "admin",
                                                "developer",
                                                "lectura",
                                                null
                                            ],
                                            "description": "Rol de quien inicio sesion; null para el propietario."
                                        },
                                        "emailCuenta": {
                                            "type": [
                                                "string",
                                                "null"
                                            ]
                                        },
                                        "twofaActivado": {
                                            "type": "boolean"
                                        }
                                    },
                                    "additionalProperties": false
                                }
                            }
                        }
                    },
                    "401": {
                        "description": "Sesion invalida o vencida.",
                        "content": {
                            "application\/json": {
                                "schema": {
                                    "$ref": "#\/components\/schemas\/Error"
                                }
                            }
                        }
                    }
                },
                "description": "Usa el token de sesion (no la clave API). emailCuenta es el propietario real, que no es quien inicio sesion cuando entra un usuario invitado.",
                "security": [
                    {
                        "bearerAuth": []
                    }
                ]
            }
        },
        "\/v1\/webhooks\/stripe": {
            "post": {
                "tags": [
                    "Pagos"
                ],
                "summary": "Recibe los eventos de Stripe (pagos de packs de facturas). Publico; seguridad por firma del webhook.",
                "operationId": "webhookStripe",
                "responses": {
                    "200": {
                        "description": "Evento recibido."
                    },
                    "400": {
                        "description": "Firma del webhook invalida o cuerpo no valido.",
                        "content": {
                            "application\/json": {
                                "schema": {
                                    "$ref": "#\/components\/schemas\/Error"
                                }
                            }
                        }
                    },
                    "503": {
                        "description": "Pagos no configurados (code 'pagos_no_configurados').",
                        "content": {
                            "application\/json": {
                                "schema": {
                                    "$ref": "#\/components\/schemas\/Error"
                                }
                            }
                        }
                    }
                }
            }
        },
        "\/v1\/errores-cliente": {
            "post": {
                "tags": [
                    "Sesion"
                ],
                "summary": "El portal informa de un error del navegador.",
                "operationId": "errorCliente",
                "responses": {
                    "204": {
                        "description": "Registrado (o ignorado en silencio si fallo el registro: este endpoint nunca rompe al usuario)."
                    },
                    "400": {
                        "description": "El registro no trae ningun dato util (code 'invalid_request').",
                        "content": {
                            "application\/json": {
                                "schema": {
                                    "$ref": "#\/components\/schemas\/Error"
                                }
                            }
                        }
                    },
                    "429": {
                        "$ref": "#\/components\/responses\/DemasiadasPeticiones"
                    }
                },
                "description": "Publico a proposito. Maximo 120 por hora y direccion IP, y 5000 por hora en total.",
                "requestBody": {
                    "required": true,
                    "content": {
                        "application\/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "clasificacion": {
                                        "type": "string",
                                        "enum": [
                                            "esperado",
                                            "inesperado"
                                        ],
                                        "default": "inesperado"
                                    },
                                    "metodo": {
                                        "type": "string",
                                        "maxLength": 10
                                    },
                                    "ruta": {
                                        "type": "string",
                                        "maxLength": 300
                                    },
                                    "codigoHttp": {
                                        "type": "integer",
                                        "minimum": 100,
                                        "maximum": 599
                                    },
                                    "codigoError": {
                                        "type": "string",
                                        "maxLength": 100
                                    },
                                    "mensaje": {
                                        "type": "string",
                                        "maxLength": 2000
                                    },
                                    "email": {
                                        "type": "string",
                                        "maxLength": 254
                                    }
                                },
                                "additionalProperties": false,
                                "description": "Los campos se acotan; hace falta al menos mensaje, ruta o un codigo."
                            }
                        }
                    }
                },
                "security": []
            }
        },
        "\/v1\/usuarios\/activar": {
            "post": {
                "tags": [
                    "Usuarios"
                ],
                "summary": "Activa un usuario invitado con su codigo.",
                "operationId": "activarUsuario",
                "responses": {
                    "200": {
                        "description": "Usuario activado.",
                        "content": {
                            "application\/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "activado": {
                                            "type": "boolean"
                                        }
                                    },
                                    "additionalProperties": false
                                }
                            }
                        }
                    },
                    "429": {
                        "$ref": "#\/components\/responses\/DemasiadasPeticiones"
                    },
                    "422": {
                        "description": "Datos invalidos (code 'invalid_request'), contrasena de menos de 8 caracteres ('password_debil') o codigo no valido\/caducado\/usado ('codigo_invalido').",
                        "content": {
                            "application\/json": {
                                "schema": {
                                    "$ref": "#\/components\/schemas\/Error"
                                }
                            }
                        }
                    }
                },
                "description": "La persona invitada elige su propia contrasena. Maximo 10 intentos por 15 minutos y direccion IP, y 10 por hora y correo.",
                "requestBody": {
                    "required": true,
                    "content": {
                        "application\/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "email": {
                                        "type": "string",
                                        "format": "email",
                                        "maxLength": 254
                                    },
                                    "codigo": {
                                        "type": "string",
                                        "description": "Codigo de 8 caracteres hexadecimales (caduca a los 7 dias).",
                                        "maxLength": 32
                                    },
                                    "password": {
                                        "type": "string",
                                        "minLength": 8,
                                        "maxLength": 72
                                    }
                                },
                                "additionalProperties": false,
                                "required": [
                                    "email",
                                    "codigo",
                                    "password"
                                ]
                            }
                        }
                    }
                },
                "security": []
            }
        },
        "\/v1\/usuarios": {
            "get": {
                "tags": [
                    "Usuarios"
                ],
                "summary": "Lista los usuarios de la cuenta.",
                "operationId": "listarUsuarios",
                "responses": {
                    "200": {
                        "description": "Usuarios de la cuenta (dentro de \u00abusuarios\u00bb), del m\u00e1s antiguo al m\u00e1s reciente.",
                        "content": {
                            "application\/json": {
                                "schema": {
                                    "type": "object",
                                    "required": [
                                        "usuarios"
                                    ],
                                    "properties": {
                                        "usuarios": {
                                            "type": "array",
                                            "items": {
                                                "type": "object",
                                                "properties": {
                                                    "id": {
                                                        "type": "string",
                                                        "format": "uuid"
                                                    },
                                                    "email": {
                                                        "type": "string",
                                                        "format": "email"
                                                    },
                                                    "nombre": {
                                                        "type": "string",
                                                        "nullable": true
                                                    },
                                                    "rol": {
                                                        "type": "string",
                                                        "enum": [
                                                            "admin",
                                                            "developer",
                                                            "lectura"
                                                        ],
                                                        "description": "admin: control total \u00b7 developer: factura, clientes, productos, centros, claves y webhooks \u00b7 lectura: ve y descarga."
                                                    },
                                                    "invitadoEn": {
                                                        "type": "string",
                                                        "format": "date-time"
                                                    },
                                                    "activadoEn": {
                                                        "type": "string",
                                                        "format": "date-time",
                                                        "nullable": true,
                                                        "description": "null mientras la invitaci\u00f3n est\u00e1 pendiente."
                                                    }
                                                }
                                            }
                                        }
                                    }
                                }
                            }
                        }
                    },
                    "401": {
                        "$ref": "#\/components\/responses\/NoAutorizado"
                    },
                    "403": {
                        "$ref": "#\/components\/responses\/Prohibido"
                    }
                },
                "description": "Los campos de cada usuario pueden incluir mas datos de los listados. Solo para sesiones del portal: con una clave API responde 403.",
                "x-permiso": "administrar_usuarios",
                "security": [
                    {
                        "bearerAuth": []
                    }
                ]
            },
            "post": {
                "tags": [
                    "Usuarios"
                ],
                "summary": "Invita a un usuario a la cuenta.",
                "operationId": "invitarUsuario",
                "responses": {
                    "201": {
                        "description": "Invitacion creada.",
                        "content": {
                            "application\/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "id": {
                                            "type": "string",
                                            "format": "uuid"
                                        },
                                        "email": {
                                            "type": "string",
                                            "format": "email"
                                        },
                                        "nombre": {
                                            "type": [
                                                "string",
                                                "null"
                                            ]
                                        },
                                        "rol": {
                                            "type": "string",
                                            "enum": [
                                                "admin",
                                                "developer",
                                                "lectura"
                                            ]
                                        },
                                        "invitadoEn": {
                                            "type": "string"
                                        }
                                    },
                                    "additionalProperties": true
                                }
                            }
                        }
                    },
                    "401": {
                        "$ref": "#\/components\/responses\/NoAutorizado"
                    },
                    "403": {
                        "$ref": "#\/components\/responses\/Prohibido"
                    },
                    "409": {
                        "$ref": "#\/components\/responses\/Conflicto"
                    },
                    "422": {
                        "description": "Campos obligatorios 'email' y 'rol', correo invalido, o rol no valido (code 'rol_invalido').",
                        "content": {
                            "application\/json": {
                                "schema": {
                                    "$ref": "#\/components\/schemas\/Error"
                                }
                            }
                        }
                    }
                },
                "description": "Se registra en la auditoria. Solo para sesiones del portal: con una clave API responde 403.",
                "x-permiso": "administrar_usuarios",
                "requestBody": {
                    "required": true,
                    "content": {
                        "application\/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "email": {
                                        "type": "string",
                                        "format": "email",
                                        "maxLength": 254
                                    },
                                    "nombre": {
                                        "type": "string",
                                        "maxLength": 200
                                    },
                                    "rol": {
                                        "type": "string",
                                        "enum": [
                                            "admin",
                                            "developer",
                                            "lectura"
                                        ]
                                    }
                                },
                                "additionalProperties": false,
                                "required": [
                                    "email",
                                    "rol"
                                ]
                            }
                        }
                    }
                },
                "security": [
                    {
                        "bearerAuth": []
                    }
                ]
            }
        },
        "\/v1\/usuarios\/{id}": {
            "delete": {
                "tags": [
                    "Usuarios"
                ],
                "summary": "Quita a un usuario de la cuenta.",
                "operationId": "quitarUsuario",
                "responses": {
                    "204": {
                        "description": "Usuario eliminado."
                    },
                    "401": {
                        "$ref": "#\/components\/responses\/NoAutorizado"
                    },
                    "403": {
                        "$ref": "#\/components\/responses\/Prohibido"
                    },
                    "404": {
                        "$ref": "#\/components\/responses\/NoEncontrado"
                    }
                },
                "description": "Se registra en la auditoria. Solo para sesiones del portal: con una clave API responde 403.",
                "x-permiso": "administrar_usuarios",
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "description": "UUID del usuario."
                    }
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ]
            },
            "put": {
                "tags": [
                    "Usuarios"
                ],
                "summary": "Actualiza un usuario de la cuenta.",
                "operationId": "actualizarUsuario",
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "description": "Identificador.",
                        "schema": {
                            "type": "string"
                        }
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application\/json": {
                            "schema": {
                                "type": "object"
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "Usuario actualizado.",
                        "content": {
                            "application\/json": {
                                "schema": {
                                    "type": "object"
                                }
                            }
                        }
                    },
                    "401": {
                        "$ref": "#\/components\/responses\/NoAutorizado"
                    },
                    "429": {
                        "$ref": "#\/components\/responses\/DemasiadasPeticiones"
                    }
                },
                "security": [
                    {
                        "bearerAuth": []
                    }
                ]
            }
        },
        "\/v1\/claves\/regenerar": {
            "post": {
                "tags": [
                    "Cuentas"
                ],
                "summary": "Regenera la clave API de la cuenta.",
                "operationId": "regenerarClave",
                "responses": {
                    "200": {
                        "description": "Clave nueva; la anterior deja de funcionar.",
                        "content": {
                            "application\/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "apiKey": {
                                            "type": "string",
                                            "description": "Se muestra UNA sola vez."
                                        },
                                        "aviso": {
                                            "type": "string"
                                        }
                                    },
                                    "additionalProperties": false
                                }
                            }
                        }
                    },
                    "401": {
                        "$ref": "#\/components\/responses\/NoAutorizado"
                    },
                    "403": {
                        "$ref": "#\/components\/responses\/Prohibido"
                    },
                    "429": {
                        "$ref": "#\/components\/responses\/DemasiadasPeticiones"
                    }
                },
                "description": "Se registra en la auditoria. Solo para sesiones del portal: con una clave API responde 403.",
                "x-permiso": "regenerar_clave",
                "security": [
                    {
                        "bearerAuth": []
                    }
                ]
            }
        },
        "\/v1\/client": {
            "get": {
                "tags": [
                    "Cuentas"
                ],
                "summary": "NIF autorizados para el token.",
                "operationId": "perfilCliente",
                "responses": {
                    "200": {
                        "description": "NIF del token.",
                        "content": {
                            "application\/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "nifs": {
                                            "type": "array",
                                            "items": {
                                                "type": "object",
                                                "properties": {
                                                    "nif": {
                                                        "type": "string"
                                                    },
                                                    "nombreEmisor": {
                                                        "type": "string"
                                                    }
                                                },
                                                "additionalProperties": false,
                                                "required": [
                                                    "nif",
                                                    "nombreEmisor"
                                                ]
                                            }
                                        }
                                    },
                                    "additionalProperties": false
                                }
                            }
                        }
                    },
                    "401": {
                        "$ref": "#\/components\/responses\/NoAutorizado"
                    }
                },
                "security": [
                    {
                        "bearerAuth": []
                    }
                ]
            }
        },
        "\/v1\/webhooks": {
            "put": {
                "tags": [
                    "Webhooks"
                ],
                "summary": "Configura el webhook de un emisor.",
                "operationId": "configurarWebhook",
                "responses": {
                    "200": {
                        "description": "Webhook configurado.",
                        "content": {
                            "application\/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "nif": {
                                            "type": "string"
                                        },
                                        "url": {
                                            "type": "string"
                                        },
                                        "secreto": {
                                            "type": "string",
                                            "description": "Lo genera el servidor. Se muestra UNA sola vez; sirve para verificar la firma de cada notificacion."
                                        },
                                        "aviso": {
                                            "type": "string"
                                        }
                                    },
                                    "additionalProperties": false
                                }
                            }
                        }
                    },
                    "401": {
                        "$ref": "#\/components\/responses\/NoAutorizado"
                    },
                    "403": {
                        "$ref": "#\/components\/responses\/Prohibido"
                    },
                    "429": {
                        "$ref": "#\/components\/responses\/DemasiadasPeticiones"
                    },
                    "422": {
                        "description": "'url' obligatoria, valida y https:\/\/ (code 'invalid_request'), o NIF sin cuenta ('nif_no_encontrado').",
                        "content": {
                            "application\/json": {
                                "schema": {
                                    "$ref": "#\/components\/schemas\/Error"
                                }
                            }
                        }
                    }
                },
                "description": "Cada llamada genera un secreto nuevo.",
                "x-permiso": "configurar_webhooks",
                "parameters": [
                    {
                        "name": "nif",
                        "in": "query",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "description": "NIF del emisor. Opcional si el token autoriza uno solo."
                        }
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application\/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "url": {
                                        "type": "string",
                                        "description": "Debe empezar por https:\/\/.",
                                        "format": "uri"
                                    },
                                    "nif": {
                                        "type": "string",
                                        "description": "Opcional si el token autoriza uno solo."
                                    }
                                },
                                "additionalProperties": false,
                                "required": [
                                    "url"
                                ]
                            }
                        }
                    }
                },
                "security": [
                    {
                        "bearerAuth": []
                    }
                ]
            },
            "delete": {
                "tags": [
                    "Webhooks"
                ],
                "summary": "Quita el webhook de un emisor.",
                "operationId": "quitarWebhook",
                "responses": {
                    "200": {
                        "description": "Webhook eliminado.",
                        "content": {
                            "application\/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "nif": {
                                            "type": "string"
                                        },
                                        "eliminado": {
                                            "type": "boolean"
                                        }
                                    },
                                    "additionalProperties": false
                                }
                            }
                        }
                    },
                    "401": {
                        "$ref": "#\/components\/responses\/NoAutorizado"
                    },
                    "403": {
                        "$ref": "#\/components\/responses\/Prohibido"
                    },
                    "429": {
                        "$ref": "#\/components\/responses\/DemasiadasPeticiones"
                    }
                },
                "x-permiso": "configurar_webhooks",
                "parameters": [
                    {
                        "name": "nif",
                        "in": "query",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "description": "NIF del emisor. Opcional si el token autoriza uno solo."
                        }
                    }
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ]
            }
        },
        "\/v1\/admin\/pagos\/reconciliacion": {
            "get": {
                "operationId": "adminReconciliacionPagos",
                "summary": "Reconciliacion de pagos de Stripe (backoffice)",
                "description": "Lista los pagos de Stripe de tipo pack y marca los cobrados que no tienen saldo acreditado. Solo administradores.",
                "responses": {
                    "200": {
                        "description": "Resumen de reconciliacion"
                    },
                    "503": {
                        "description": "Pagos no configurados en este entorno"
                    }
                },
                "tags": [
                    "Administracion"
                ]
            }
        },
        "\/v1\/admin\/resumen": {
            "get": {
                "tags": [
                    "Administracion"
                ],
                "summary": "Resumen global de cuentas.",
                "operationId": "adminResumen",
                "responses": {
                    "200": {
                        "description": "Resumen.",
                        "content": {
                            "application\/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "totalCuentas": {
                                            "type": "integer"
                                        },
                                        "conCorreoVerificado": {
                                            "type": "integer"
                                        },
                                        "conPlanDePago": {
                                            "type": "integer"
                                        },
                                        "dadasDeBaja": {
                                            "type": "integer"
                                        },
                                        "conNifVerificado": {
                                            "type": "integer"
                                        },
                                        "conProduccionHabilitada": {
                                            "type": "integer"
                                        },
                                        "conAvisoCupo": {
                                            "type": "integer"
                                        },
                                        "requierenAtencion": {
                                            "type": "array",
                                            "items": {
                                                "type": "object",
                                                "properties": {
                                                    "id": {
                                                        "type": "string",
                                                        "format": "uuid"
                                                    },
                                                    "email": {
                                                        "type": "string"
                                                    },
                                                    "puntuacionRiesgo": {
                                                        "type": "integer"
                                                    },
                                                    "nif": {
                                                        "type": [
                                                            "string",
                                                            "null"
                                                        ]
                                                    },
                                                    "nombreEmisor": {
                                                        "type": [
                                                            "string",
                                                            "null"
                                                        ]
                                                    }
                                                },
                                                "additionalProperties": false
                                            }
                                        }
                                    },
                                    "additionalProperties": false
                                }
                            }
                        }
                    },
                    "401": {
                        "$ref": "#\/components\/responses\/NoAutorizado"
                    },
                    "403": {
                        "$ref": "#\/components\/responses\/Prohibido"
                    }
                },
                "description": "Solo administradores de Nodda.",
                "security": [
                    {
                        "bearerAuth": []
                    }
                ]
            }
        },
        "\/v1\/admin\/revision": {
            "get": {
                "tags": [
                    "Administracion"
                ],
                "summary": "Cuentas en cola de revision por riesgo (puntuacion 30 o mas).",
                "operationId": "adminRevision",
                "responses": {
                    "200": {
                        "description": "Cuentas.",
                        "content": {
                            "application\/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "cuentas": {
                                            "type": "array",
                                            "items": {
                                                "type": "object",
                                                "properties": {
                                                    "id": {
                                                        "type": "string",
                                                        "format": "uuid"
                                                    },
                                                    "email": {
                                                        "type": "string"
                                                    },
                                                    "nif": {
                                                        "type": [
                                                            "string",
                                                            "null"
                                                        ]
                                                    },
                                                    "nombreEmisor": {
                                                        "type": [
                                                            "string",
                                                            "null"
                                                        ]
                                                    },
                                                    "puntuacionRiesgo": {
                                                        "type": "integer"
                                                    },
                                                    "tramo": {
                                                        "type": [
                                                            "string",
                                                            "null"
                                                        ]
                                                    },
                                                    "senalPrincipal": {
                                                        "type": [
                                                            "string",
                                                            "null"
                                                        ]
                                                    },
                                                    "cupoConsumido": {
                                                        "type": [
                                                            "integer",
                                                            "null"
                                                        ]
                                                    },
                                                    "dadaDeBajaEn": {
                                                        "type": [
                                                            "string",
                                                            "null"
                                                        ],
                                                        "format": "date-time"
                                                    }
                                                },
                                                "additionalProperties": false
                                            }
                                        }
                                    },
                                    "additionalProperties": false
                                }
                            }
                        }
                    },
                    "401": {
                        "$ref": "#\/components\/responses\/NoAutorizado"
                    },
                    "403": {
                        "$ref": "#\/components\/responses\/Prohibido"
                    }
                },
                "description": "Solo administradores de Nodda. Criterios: puntuacion de riesgo >= 30 (misma huella de navegador +30, mismo dominio en mas de 2 cuentas gratuitas +20, correo secuencial +20, misma IP en 7 dias +15, NIF verificado -40). Incluye ademas las cuentas bloqueadas o con documentacion solicitada; una cuenta aprobada no vuelve salvo que su riesgo suba. Cada fila trae revisionEstado, revisionNota y revisionActualizadaEn.",
                "security": [
                    {
                        "bearerAuth": []
                    }
                ]
            }
        },
        "\/v1\/admin\/consumo": {
            "get": {
                "tags": [
                    "Administracion"
                ],
                "summary": "Consumo de cupo por cuenta y NIF.",
                "operationId": "adminConsumo",
                "responses": {
                    "200": {
                        "description": "Consumo.",
                        "content": {
                            "application\/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "cuentas": {
                                            "type": "array",
                                            "items": {
                                                "type": "object",
                                                "properties": {
                                                    "id": {
                                                        "type": "string",
                                                        "format": "uuid"
                                                    },
                                                    "email": {
                                                        "type": "string"
                                                    },
                                                    "nif": {
                                                        "type": [
                                                            "string",
                                                            "null"
                                                        ]
                                                    },
                                                    "nombreEmisor": {
                                                        "type": [
                                                            "string",
                                                            "null"
                                                        ]
                                                    },
                                                    "plan": {
                                                        "type": "string",
                                                        "deprecated": true,
                                                        "description": "Obsoleto: Nodda funciona con saldo prepago por packs, ya no hay planes. Se mantiene por compatibilidad."
                                                    },
                                                    "cupoIncluido": {
                                                        "type": [
                                                            "integer",
                                                            "null"
                                                        ]
                                                    },
                                                    "cupoConsumido": {
                                                        "type": "integer"
                                                    }
                                                },
                                                "additionalProperties": false
                                            }
                                        }
                                    },
                                    "additionalProperties": false
                                }
                            }
                        }
                    },
                    "401": {
                        "$ref": "#\/components\/responses\/NoAutorizado"
                    },
                    "403": {
                        "$ref": "#\/components\/responses\/Prohibido"
                    }
                },
                "description": "Solo administradores de Nodda.",
                "security": [
                    {
                        "bearerAuth": []
                    }
                ]
            }
        },
        "\/v1\/admin\/cuentas": {
            "get": {
                "tags": [
                    "Administracion"
                ],
                "summary": "Todas las cuentas.",
                "operationId": "adminCuentas",
                "responses": {
                    "200": {
                        "description": "Cuentas.",
                        "content": {
                            "application\/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "cuentas": {
                                            "type": "array",
                                            "items": {
                                                "type": "object",
                                                "properties": {
                                                    "id": {
                                                        "type": "string",
                                                        "format": "uuid"
                                                    },
                                                    "email": {
                                                        "type": "string"
                                                    },
                                                    "plan": {
                                                        "type": "string",
                                                        "deprecated": true,
                                                        "description": "Obsoleto: Nodda funciona con saldo prepago por packs, ya no hay planes. Se mantiene por compatibilidad."
                                                    },
                                                    "creadaEn": {
                                                        "type": [
                                                            "string",
                                                            "null"
                                                        ],
                                                        "format": "date-time"
                                                    },
                                                    "dadaDeBajaEn": {
                                                        "type": [
                                                            "string",
                                                            "null"
                                                        ],
                                                        "format": "date-time"
                                                    },
                                                    "puntuacionRiesgo": {
                                                        "type": "integer"
                                                    },
                                                    "nif": {
                                                        "type": [
                                                            "string",
                                                            "null"
                                                        ]
                                                    },
                                                    "nombreEmisor": {
                                                        "type": [
                                                            "string",
                                                            "null"
                                                        ]
                                                    },
                                                    "cupoConsumido": {
                                                        "type": [
                                                            "integer",
                                                            "null"
                                                        ]
                                                    }
                                                },
                                                "additionalProperties": false
                                            }
                                        }
                                    },
                                    "additionalProperties": false
                                }
                            }
                        }
                    },
                    "401": {
                        "$ref": "#\/components\/responses\/NoAutorizado"
                    },
                    "403": {
                        "$ref": "#\/components\/responses\/Prohibido"
                    }
                },
                "description": "Solo administradores de Nodda.",
                "security": [
                    {
                        "bearerAuth": []
                    }
                ]
            }
        },
        "\/v1\/admin\/invoices": {
            "get": {
                "tags": [
                    "Administracion"
                ],
                "summary": "Facturas de todas las cuentas.",
                "operationId": "adminFacturas",
                "responses": {
                    "200": {
                        "description": "Pagina de facturas.",
                        "content": {
                            "application\/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "invoices": {
                                            "type": "array",
                                            "items": {
                                                "type": "object",
                                                "properties": {
                                                    "nif": {
                                                        "type": "string"
                                                    },
                                                    "id": {
                                                        "type": "string"
                                                    },
                                                    "type": {
                                                        "type": "string",
                                                        "enum": [
                                                            "alta",
                                                            "anulacion"
                                                        ]
                                                    },
                                                    "status": {
                                                        "type": "string",
                                                        "enum": [
                                                            "queued",
                                                            "sent",
                                                            "permanently_failed",
                                                            "accepted_with_errors"
                                                        ],
                                                        "description": "Estados: queued = en cola (la AEAT aun no responde) | sent = ACEPTADA por la AEAT (en el portal: 'Aceptada') | accepted_with_errors = aceptada con errores\/observaciones | permanently_failed = rechazada en firme (no se reintenta; se corrige y se emite de nuevo)."
                                                    },
                                                    "razon": {
                                                        "type": [
                                                            "string",
                                                            "null"
                                                        ]
                                                    },
                                                    "creadoEn": {
                                                        "type": [
                                                            "string",
                                                            "null"
                                                        ],
                                                        "format": "date-time"
                                                    },
                                                    "fechaActualizacion": {
                                                        "type": [
                                                            "string",
                                                            "null"
                                                        ],
                                                        "format": "date-time"
                                                    }
                                                },
                                                "additionalProperties": false
                                            }
                                        },
                                        "pagination": {
                                            "$ref": "#\/components\/schemas\/Paginacion"
                                        }
                                    },
                                    "additionalProperties": false
                                }
                            }
                        }
                    },
                    "401": {
                        "$ref": "#\/components\/responses\/NoAutorizado"
                    },
                    "403": {
                        "$ref": "#\/components\/responses\/Prohibido"
                    },
                    "422": {
                        "$ref": "#\/components\/responses\/Invalida"
                    }
                },
                "description": "Solo administradores de Nodda.",
                "parameters": [
                    {
                        "name": "nif",
                        "in": "query",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "maxLength": 20
                        }
                    },
                    {
                        "name": "status",
                        "in": "query",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "enum": [
                                "queued",
                                "sent",
                                "permanently_failed",
                                "accepted_with_errors"
                            ]
                        },
                        "description": "Estados: queued = en cola (la AEAT aun no responde) | sent = ACEPTADA por la AEAT (en el portal: 'Aceptada') | accepted_with_errors = aceptada con errores\/observaciones | permanently_failed = rechazada en firme (no se reintenta; se corrige y se emite de nuevo)."
                    },
                    {
                        "name": "type",
                        "in": "query",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "enum": [
                                "alta",
                                "anulacion"
                            ]
                        }
                    },
                    {
                        "name": "from",
                        "in": "query",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date"
                        }
                    },
                    {
                        "name": "to",
                        "in": "query",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "date"
                        }
                    },
                    {
                        "name": "limit",
                        "in": "query",
                        "required": false,
                        "schema": {
                            "type": "integer",
                            "minimum": 1,
                            "maximum": 100,
                            "default": 20
                        }
                    },
                    {
                        "name": "offset",
                        "in": "query",
                        "required": false,
                        "schema": {
                            "type": "integer",
                            "minimum": 0,
                            "default": 0
                        }
                    }
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ]
            }
        },
        "\/v1\/cuentas\/{id}\/baja": {
            "post": {
                "tags": [
                    "Administracion"
                ],
                "summary": "Da de baja una cuenta (archiva sus NIF, nunca la borra).",
                "operationId": "adminBaja",
                "responses": {
                    "200": {
                        "description": "Cuenta dada de baja.",
                        "content": {
                            "application\/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "nifsArchivados": {
                                            "description": "NIF archivados por la baja."
                                        }
                                    },
                                    "additionalProperties": false
                                }
                            }
                        }
                    },
                    "401": {
                        "$ref": "#\/components\/responses\/NoAutorizado"
                    },
                    "403": {
                        "$ref": "#\/components\/responses\/Prohibido"
                    },
                    "404": {
                        "$ref": "#\/components\/responses\/NoEncontrado"
                    },
                    "422": {
                        "$ref": "#\/components\/responses\/Invalida"
                    }
                },
                "description": "Solo administradores de Nodda.",
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "description": "UUID de la cuenta."
                    }
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ]
            }
        },
        "\/v1\/cuentas\/{id}\/riesgo": {
            "get": {
                "tags": [
                    "Administracion"
                ],
                "summary": "Evalua el riesgo de una cuenta.",
                "operationId": "adminRiesgo",
                "responses": {
                    "200": {
                        "description": "Evaluacion de riesgo.",
                        "content": {
                            "application\/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "puntuacion": {
                                            "type": "integer",
                                            "description": "Puntuacion de riesgo acumulada de la cuenta."
                                        },
                                        "tramo": {
                                            "type": "string",
                                            "description": "Tramo de riesgo derivado de la puntuacion (p. ej. 'revision' al superar el umbral)."
                                        }
                                    },
                                    "required": [
                                        "puntuacion",
                                        "tramo"
                                    ],
                                    "additionalProperties": false
                                }
                            }
                        }
                    },
                    "401": {
                        "$ref": "#\/components\/responses\/NoAutorizado"
                    },
                    "403": {
                        "$ref": "#\/components\/responses\/Prohibido"
                    },
                    "404": {
                        "$ref": "#\/components\/responses\/NoEncontrado"
                    }
                },
                "description": "Solo administradores de Nodda.",
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "description": "UUID de la cuenta."
                    }
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ]
            }
        },
        "\/v1\/cuentas\/nifs\/{nif}\/reactivar": {
            "post": {
                "tags": [
                    "Administracion"
                ],
                "summary": "Reactiva produccion de un NIF.",
                "operationId": "adminReactivar",
                "responses": {
                    "200": {
                        "description": "NIF reactivado.",
                        "content": {
                            "application\/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "nif": {
                                            "type": "string"
                                        },
                                        "reactivado": {
                                            "type": "boolean"
                                        }
                                    },
                                    "additionalProperties": false
                                }
                            }
                        }
                    },
                    "401": {
                        "$ref": "#\/components\/responses\/NoAutorizado"
                    },
                    "403": {
                        "$ref": "#\/components\/responses\/Prohibido"
                    },
                    "404": {
                        "$ref": "#\/components\/responses\/NoEncontrado"
                    },
                    "422": {
                        "$ref": "#\/components\/responses\/Invalida"
                    }
                },
                "description": "Solo administradores de Nodda.",
                "parameters": [
                    {
                        "name": "nif",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string"
                        },
                        "description": "NIF."
                    }
                ],
                "security": [
                    {
                        "bearerAuth": []
                    }
                ]
            }
        },
        "\/v1\/borradores": {
            "get": {
                "tags": [
                    "Facturas"
                ],
                "summary": "Lista los borradores de factura guardados.",
                "operationId": "listarBorradores",
                "responses": {
                    "200": {
                        "description": "Borradores.",
                        "content": {
                            "application\/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "borradores": {
                                            "type": "array",
                                            "items": {
                                                "type": "object"
                                            }
                                        }
                                    }
                                }
                            }
                        }
                    },
                    "401": {
                        "$ref": "#\/components\/responses\/NoAutorizado"
                    },
                    "429": {
                        "$ref": "#\/components\/responses\/DemasiadasPeticiones"
                    }
                },
                "security": [
                    {
                        "bearerAuth": []
                    }
                ]
            },
            "post": {
                "tags": [
                    "Facturas"
                ],
                "summary": "Guarda un borrador de factura (contenido libre en JSON).",
                "operationId": "guardarBorrador",
                "requestBody": {
                    "required": true,
                    "content": {
                        "application\/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "etiqueta": {
                                        "type": "string"
                                    },
                                    "contenido": {
                                        "type": "object"
                                    }
                                },
                                "required": [
                                    "contenido"
                                ]
                            }
                        }
                    }
                },
                "responses": {
                    "201": {
                        "description": "Guardado.",
                        "content": {
                            "application\/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "id": {
                                            "type": "string",
                                            "format": "uuid"
                                        }
                                    }
                                }
                            }
                        }
                    },
                    "401": {
                        "$ref": "#\/components\/responses\/NoAutorizado"
                    },
                    "429": {
                        "$ref": "#\/components\/responses\/DemasiadasPeticiones"
                    }
                },
                "security": [
                    {
                        "bearerAuth": []
                    }
                ]
            }
        },
        "\/v1\/borradores\/{id}": {
            "get": {
                "tags": [
                    "Facturas"
                ],
                "summary": "Obtiene un borrador por id.",
                "operationId": "obtenerBorrador",
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "description": "Identificador.",
                        "schema": {
                            "type": "string"
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Borrador.",
                        "content": {
                            "application\/json": {
                                "schema": {
                                    "type": "object"
                                }
                            }
                        }
                    },
                    "404": {
                        "description": "No existe."
                    },
                    "401": {
                        "$ref": "#\/components\/responses\/NoAutorizado"
                    },
                    "429": {
                        "$ref": "#\/components\/responses\/DemasiadasPeticiones"
                    }
                },
                "security": [
                    {
                        "bearerAuth": []
                    }
                ]
            },
            "delete": {
                "tags": [
                    "Facturas"
                ],
                "summary": "Elimina un borrador.",
                "operationId": "eliminarBorrador",
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "description": "Identificador.",
                        "schema": {
                            "type": "string"
                        }
                    }
                ],
                "responses": {
                    "204": {
                        "description": "Eliminado."
                    },
                    "404": {
                        "description": "No existe."
                    },
                    "401": {
                        "$ref": "#\/components\/responses\/NoAutorizado"
                    },
                    "429": {
                        "$ref": "#\/components\/responses\/DemasiadasPeticiones"
                    }
                },
                "security": [
                    {
                        "bearerAuth": []
                    }
                ]
            }
        },
        "\/v1\/cobros\/pendientes": {
            "get": {
                "tags": [
                    "Cuentas"
                ],
                "summary": "Facturas pendientes de cobro con antiguedad (aging).",
                "operationId": "cobrosPendientes",
                "responses": {
                    "200": {
                        "description": "Pendientes.",
                        "content": {
                            "application\/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "summary": {
                                            "type": "object"
                                        },
                                        "invoices": {
                                            "type": "array",
                                            "items": {
                                                "type": "object",
                                                "properties": {
                                                    "costCenter": {
                                                        "type": "string",
                                                        "nullable": true,
                                                        "description": "Nombre del centro de coste de la factura."
                                                    }
                                                }
                                            }
                                        }
                                    }
                                }
                            }
                        }
                    },
                    "401": {
                        "$ref": "#\/components\/responses\/NoAutorizado"
                    },
                    "429": {
                        "$ref": "#\/components\/responses\/DemasiadasPeticiones"
                    }
                },
                "security": [
                    {
                        "bearerAuth": []
                    }
                ]
            }
        },
        "\/v1\/cobros\/pagos": {
            "get": {
                "tags": [
                    "Cuentas"
                ],
                "summary": "Pagos (cobros) registrados.",
                "operationId": "cobrosPagos",
                "responses": {
                    "200": {
                        "description": "Pagos.",
                        "content": {
                            "application\/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "payments": {
                                            "type": "array",
                                            "items": {
                                                "type": "object"
                                            }
                                        }
                                    }
                                }
                            }
                        }
                    },
                    "401": {
                        "$ref": "#\/components\/responses\/NoAutorizado"
                    },
                    "429": {
                        "$ref": "#\/components\/responses\/DemasiadasPeticiones"
                    }
                },
                "security": [
                    {
                        "bearerAuth": []
                    }
                ]
            }
        },
        "\/v1\/cuenta\/facturas-nodda": {
            "get": {
                "tags": [
                    "Cuentas"
                ],
                "summary": "Facturas que Nodda te ha emitido (tus recibos).",
                "operationId": "facturasNodda",
                "responses": {
                    "200": {
                        "description": "Facturas de Nodda.",
                        "content": {
                            "application\/json": {
                                "schema": {
                                    "type": "object"
                                }
                            }
                        }
                    },
                    "401": {
                        "$ref": "#\/components\/responses\/NoAutorizado"
                    },
                    "429": {
                        "$ref": "#\/components\/responses\/DemasiadasPeticiones"
                    }
                },
                "security": [
                    {
                        "bearerAuth": []
                    }
                ]
            }
        },
        "\/v1\/cuenta\/preferencias": {
            "get": {
                "tags": [
                    "Cuentas"
                ],
                "summary": "Preferencias de la cuenta (avisos de saldo, branding de factura).",
                "operationId": "obtenerPreferencias",
                "responses": {
                    "200": {
                        "description": "Preferencias.",
                        "content": {
                            "application\/json": {
                                "schema": {
                                    "$ref": "#\/components\/schemas\/PreferenciasCuenta"
                                }
                            }
                        }
                    },
                    "401": {
                        "$ref": "#\/components\/responses\/NoAutorizado"
                    },
                    "429": {
                        "$ref": "#\/components\/responses\/DemasiadasPeticiones"
                    }
                },
                "security": [
                    {
                        "bearerAuth": []
                    }
                ]
            },
            "put": {
                "tags": [
                    "Cuentas"
                ],
                "summary": "Actualiza las preferencias de la cuenta.",
                "operationId": "actualizarPreferencias",
                "requestBody": {
                    "required": true,
                    "content": {
                        "application\/json": {
                            "schema": {
                                "$ref": "#\/components\/schemas\/PreferenciasCuentaEntrada"
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "Actualizadas.",
                        "content": {
                            "application\/json": {
                                "schema": {
                                    "$ref": "#\/components\/schemas\/PreferenciasCuenta"
                                }
                            }
                        }
                    },
                    "401": {
                        "$ref": "#\/components\/responses\/NoAutorizado"
                    },
                    "429": {
                        "$ref": "#\/components\/responses\/DemasiadasPeticiones"
                    }
                },
                "security": [
                    {
                        "bearerAuth": []
                    }
                ]
            }
        },
        "\/v1\/invoices\/{id}\/email": {
            "post": {
                "tags": [
                    "Facturas"
                ],
                "summary": "Envia la factura por correo al destinatario.",
                "operationId": "enviarFacturaEmail",
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "description": "Identificador.",
                        "schema": {
                            "type": "string"
                        }
                    }
                ],
                "requestBody": {
                    "required": false,
                    "content": {
                        "application\/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "email": {
                                        "type": "string",
                                        "format": "email"
                                    },
                                    "idioma": {
                                        "type": "string",
                                        "enum": [
                                            "es",
                                            "ca",
                                            "en"
                                        ],
                                        "example": "es",
                                        "description": "Opcional. Idioma del texto del correo (el PDF no cambia). Si no se indica, el de la cuenta."
                                    }
                                }
                            }
                        }
                    }
                },
                "responses": {
                    "202": {
                        "description": "Correo encolado."
                    },
                    "404": {
                        "description": "No existe."
                    },
                    "401": {
                        "$ref": "#\/components\/responses\/NoAutorizado"
                    },
                    "429": {
                        "$ref": "#\/components\/responses\/DemasiadasPeticiones"
                    }
                },
                "security": [
                    {
                        "bearerAuth": []
                    }
                ]
            }
        },
        "\/v1\/pagos\/checkout-pack": {
            "post": {
                "tags": [
                    "Cuentas"
                ],
                "summary": "Crea una sesion de pago (Stripe) para comprar un pack de facturas.",
                "operationId": "checkoutPack",
                "requestBody": {
                    "required": true,
                    "content": {
                        "application\/json": {
                            "schema": {
                                "type": "object",
                                "properties": {
                                    "pack": {
                                        "type": "integer"
                                    }
                                },
                                "required": [
                                    "pack"
                                ]
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "URL de checkout.",
                        "content": {
                            "application\/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "url": {
                                            "type": "string"
                                        }
                                    }
                                }
                            }
                        }
                    },
                    "401": {
                        "$ref": "#\/components\/responses\/NoAutorizado"
                    },
                    "429": {
                        "$ref": "#\/components\/responses\/DemasiadasPeticiones"
                    }
                },
                "security": [
                    {
                        "bearerAuth": []
                    }
                ],
                "description": "Si la cuenta esta bloqueada desde la cola de revision responde 403 cuenta_bloqueada."
            }
        },
        "\/v1\/auth\/password\/olvide": {
            "post": {
                "tags": [
                    "Sesion"
                ],
                "operationId": "olvidePassword",
                "summary": "Solicitar enlace de recuperacion de contrasena",
                "description": "Publico. Si el correo pertenece a una cuenta (propietario o usuario invitado activado), envia un enlace de un solo uso valido 30 minutos. Responde SIEMPRE 200 con el mismo mensaje, exista o no el correo (anti-enumeracion). Limite: 5 por IP cada 15 min y 5 por correo cada hora.",
                "requestBody": {
                    "required": true,
                    "content": {
                        "application\/json": {
                            "schema": {
                                "type": "object",
                                "required": [
                                    "email"
                                ],
                                "properties": {
                                    "email": {
                                        "type": "string",
                                        "format": "email"
                                    },
                                    "idioma": {
                                        "type": "string",
                                        "enum": [
                                            "es",
                                            "ca",
                                            "en"
                                        ],
                                        "example": "es",
                                        "description": "Opcional. Idioma del correo de recuperacion. Si no se indica, el de la cuenta."
                                    }
                                }
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "OK",
                        "content": {
                            "application\/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "mensaje": {
                                            "type": "string"
                                        }
                                    }
                                }
                            }
                        }
                    },
                    "429": {
                        "description": "Demasiadas solicitudes.",
                        "content": {
                            "application\/json": {
                                "schema": {
                                    "$ref": "#\/components\/schemas\/Error"
                                }
                            }
                        }
                    }
                },
                "security": []
            }
        },
        "\/v1\/auth\/password\/restablecer": {
            "post": {
                "tags": [
                    "Sesion"
                ],
                "operationId": "restablecerPassword",
                "summary": "Fijar contrasena nueva con el token del correo",
                "description": "Publico. Canjea el token del enlace por una contrasena nueva (minimo 8 caracteres). El token es de un solo uso y caduca a los 30 minutos. Cierra todas las sesiones abiertas de ese usuario.",
                "requestBody": {
                    "required": true,
                    "content": {
                        "application\/json": {
                            "schema": {
                                "type": "object",
                                "required": [
                                    "token",
                                    "password"
                                ],
                                "properties": {
                                    "token": {
                                        "type": "string"
                                    },
                                    "password": {
                                        "type": "string",
                                        "minLength": 8
                                    }
                                }
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "OK",
                        "content": {
                            "application\/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "restablecido": {
                                            "type": "boolean"
                                        }
                                    }
                                }
                            }
                        }
                    },
                    "422": {
                        "description": "token_invalido (no existe, caduco o ya se uso) o password_debil.",
                        "content": {
                            "application\/json": {
                                "schema": {
                                    "$ref": "#\/components\/schemas\/Error"
                                }
                            }
                        }
                    },
                    "429": {
                        "description": "Demasiados intentos.",
                        "content": {
                            "application\/json": {
                                "schema": {
                                    "$ref": "#\/components\/schemas\/Error"
                                }
                            }
                        }
                    }
                },
                "security": []
            }
        },
        "\/v1\/auth\/2fa\/verificar": {
            "post": {
                "tags": [
                    "Sesion"
                ],
                "operationId": "verificar2fa",
                "summary": "Segundo paso del login con 2FA",
                "description": "Publico. Si \/v1\/auth\/login respondio twofaRequired=true, se envia aqui el desafio recibido junto con el codigo TOTP de 6 digitos o un codigo de respaldo (cada respaldo sirve una sola vez). El desafio caduca a los 5 minutos. Devuelve el token de sesion.",
                "requestBody": {
                    "required": true,
                    "content": {
                        "application\/json": {
                            "schema": {
                                "type": "object",
                                "required": [
                                    "desafio",
                                    "codigo"
                                ],
                                "properties": {
                                    "desafio": {
                                        "type": "string"
                                    },
                                    "codigo": {
                                        "type": "string"
                                    }
                                }
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "OK",
                        "content": {
                            "application\/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "token": {
                                            "type": "string"
                                        }
                                    }
                                }
                            }
                        }
                    },
                    "401": {
                        "description": "codigo_invalido: codigo incorrecto o desafio caducado.",
                        "content": {
                            "application\/json": {
                                "schema": {
                                    "$ref": "#\/components\/schemas\/Error"
                                }
                            }
                        }
                    },
                    "429": {
                        "description": "Demasiados intentos.",
                        "content": {
                            "application\/json": {
                                "schema": {
                                    "$ref": "#\/components\/schemas\/Error"
                                }
                            }
                        }
                    }
                },
                "security": []
            }
        },
        "\/v1\/auth\/2fa\/iniciar": {
            "post": {
                "tags": [
                    "Sesion"
                ],
                "operationId": "iniciar2fa",
                "summary": "Iniciar el alta del 2FA",
                "description": "Solo sesion del propietario. Genera un secreto TOTP (aun NO activo), la URI otpauth:\/\/ para el QR y 8 codigos de respaldo, que solo se muestran esta vez. Si ya habia un alta pendiente, la reemplaza.",
                "requestBody": {
                    "required": true,
                    "content": {
                        "application\/json": {
                            "schema": {
                                "type": "object",
                                "required": [],
                                "properties": []
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "OK",
                        "content": {
                            "application\/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "secret": {
                                            "type": "string"
                                        },
                                        "uri": {
                                            "type": "string"
                                        },
                                        "codigosRespaldo": {
                                            "type": "array",
                                            "items": {
                                                "type": "string"
                                            }
                                        }
                                    }
                                }
                            }
                        }
                    },
                    "401": {
                        "description": "Sin sesion de propietario.",
                        "content": {
                            "application\/json": {
                                "schema": {
                                    "$ref": "#\/components\/schemas\/Error"
                                }
                            }
                        }
                    }
                },
                "security": [
                    {
                        "bearerAuth": []
                    }
                ]
            }
        },
        "\/v1\/auth\/2fa\/activar": {
            "post": {
                "tags": [
                    "Sesion"
                ],
                "operationId": "activar2fa",
                "summary": "Activar el 2FA",
                "description": "Solo sesion del propietario. Confirma el primer codigo de la app de autenticacion y activa el segundo factor.",
                "requestBody": {
                    "required": true,
                    "content": {
                        "application\/json": {
                            "schema": {
                                "type": "object",
                                "required": [
                                    "codigo"
                                ],
                                "properties": {
                                    "codigo": {
                                        "type": "string"
                                    }
                                }
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "OK",
                        "content": {
                            "application\/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "activado": {
                                            "type": "boolean"
                                        }
                                    }
                                }
                            }
                        }
                    },
                    "401": {
                        "description": "Sin sesion de propietario.",
                        "content": {
                            "application\/json": {
                                "schema": {
                                    "$ref": "#\/components\/schemas\/Error"
                                }
                            }
                        }
                    },
                    "422": {
                        "description": "codigo_invalido.",
                        "content": {
                            "application\/json": {
                                "schema": {
                                    "$ref": "#\/components\/schemas\/Error"
                                }
                            }
                        }
                    }
                },
                "security": [
                    {
                        "bearerAuth": []
                    }
                ]
            }
        },
        "\/v1\/auth\/2fa\/desactivar": {
            "post": {
                "tags": [
                    "Sesion"
                ],
                "operationId": "desactivar2fa",
                "summary": "Desactivar el 2FA",
                "description": "Solo sesion del propietario. Requiere un codigo TOTP actual o uno de respaldo.",
                "requestBody": {
                    "required": true,
                    "content": {
                        "application\/json": {
                            "schema": {
                                "type": "object",
                                "required": [
                                    "codigo"
                                ],
                                "properties": {
                                    "codigo": {
                                        "type": "string"
                                    }
                                }
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "OK",
                        "content": {
                            "application\/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "desactivado": {
                                            "type": "boolean"
                                        }
                                    }
                                }
                            }
                        }
                    },
                    "401": {
                        "description": "Sin sesion de propietario.",
                        "content": {
                            "application\/json": {
                                "schema": {
                                    "$ref": "#\/components\/schemas\/Error"
                                }
                            }
                        }
                    },
                    "422": {
                        "description": "codigo_invalido.",
                        "content": {
                            "application\/json": {
                                "schema": {
                                    "$ref": "#\/components\/schemas\/Error"
                                }
                            }
                        }
                    }
                },
                "security": [
                    {
                        "bearerAuth": []
                    }
                ]
            }
        },
        "\/v1\/admin\/bitacora": {
            "get": {
                "tags": [
                    "Administracion"
                ],
                "operationId": "adminBitacora",
                "summary": "Bitacora de acciones del backoffice",
                "description": "Quien hizo que, cuando y sobre que cuenta. Acciones registradas: login_exitoso (inicio de sesion completo; si fue con 2FA, detalle.metodo = \"2fa\"), login_2fa_pendiente (contrasena correcta, falta el codigo 2FA), 2fa_activado, 2fa_desactivado, admin_baja_cuenta (detalle.resultado) y admin_reactivar_nif (detalle.nif). Mas recientes primero.",
                "parameters": [
                    {
                        "name": "accion",
                        "in": "query",
                        "required": false,
                        "schema": {
                            "type": "string"
                        },
                        "description": "Filtra por accion: login_exitoso, login_2fa_pendiente, 2fa_activado, 2fa_desactivado, admin_baja_cuenta, admin_reactivar_nif."
                    },
                    {
                        "name": "cuentaId",
                        "in": "query",
                        "required": false,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        },
                        "description": "Filtra por la cuenta afectada."
                    },
                    {
                        "name": "limite",
                        "in": "query",
                        "required": false,
                        "schema": {
                            "type": "integer",
                            "minimum": 1,
                            "maximum": 500,
                            "default": 200
                        }
                    }
                ],
                "responses": {
                    "200": {
                        "description": "OK",
                        "content": {
                            "application\/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "eventos": {
                                            "type": "array",
                                            "items": {
                                                "type": "object",
                                                "properties": {
                                                    "id": {
                                                        "type": "string"
                                                    },
                                                    "accion": {
                                                        "type": "string"
                                                    },
                                                    "cuentaId": {
                                                        "type": "string",
                                                        "nullable": true
                                                    },
                                                    "email": {
                                                        "type": "string",
                                                        "nullable": true,
                                                        "description": "Correo de quien actuo (o de quien inicio sesion)."
                                                    },
                                                    "detalle": {
                                                        "type": "object"
                                                    },
                                                    "ip": {
                                                        "type": "string",
                                                        "nullable": true
                                                    },
                                                    "creadoEn": {
                                                        "type": "string",
                                                        "format": "date-time"
                                                    }
                                                }
                                            }
                                        }
                                    }
                                }
                            }
                        }
                    },
                    "401": {
                        "$ref": "#\/components\/responses\/NoAutorizado"
                    },
                    "403": {
                        "$ref": "#\/components\/responses\/Prohibido"
                    }
                },
                "security": [
                    {
                        "bearerAuth": []
                    }
                ]
            }
        },
        "\/v1\/admin\/cuentas\/{id}\/revision": {
            "post": {
                "tags": [
                    "Administracion"
                ],
                "operationId": "adminAccionRevision",
                "summary": "Accion de la cola de revision",
                "description": "Aplica una accion a una cuenta de la cola de revision y la registra en la bitacora (revision_<accion>). aprobar: sale de la cola hasta que su riesgo suba | bloquear: no puede emitir facturas ni comprar paquetes (portal y API; responde 403 cuenta_bloqueada), pero si entrar y descargar lo emitido | pedir_documentacion: marca el estado y envia un correo al cliente (sin la nota interna) | desbloquear \/ reabrir: vuelve a pendiente.",
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    }
                ],
                "requestBody": {
                    "required": true,
                    "content": {
                        "application\/json": {
                            "schema": {
                                "type": "object",
                                "required": [
                                    "accion"
                                ],
                                "properties": {
                                    "accion": {
                                        "type": "string",
                                        "enum": [
                                            "aprobar",
                                            "bloquear",
                                            "pedir_documentacion",
                                            "desbloquear",
                                            "reabrir"
                                        ]
                                    },
                                    "nota": {
                                        "type": "string",
                                        "maxLength": 1000,
                                        "description": "Nota interna del equipo (no se envia al cliente)."
                                    }
                                }
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "OK",
                        "content": {
                            "application\/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "estado": {
                                            "type": "string",
                                            "enum": [
                                                "pendiente",
                                                "aprobada",
                                                "bloqueada",
                                                "documentacion_solicitada"
                                            ]
                                        },
                                        "correoEnviado": {
                                            "type": "boolean",
                                            "nullable": true,
                                            "description": "Solo en pedir_documentacion."
                                        }
                                    }
                                }
                            }
                        }
                    },
                    "404": {
                        "description": "No existe esa cuenta."
                    },
                    "422": {
                        "description": "accion_invalida."
                    },
                    "401": {
                        "$ref": "#\/components\/responses\/NoAutorizado"
                    },
                    "403": {
                        "$ref": "#\/components\/responses\/Prohibido"
                    }
                },
                "security": [
                    {
                        "bearerAuth": []
                    }
                ]
            }
        },
        "\/v1\/auth\/registro\/codigo": {
            "post": {
                "tags": [
                    "Cuentas"
                ],
                "operationId": "enviarCodigoRegistro",
                "security": [],
                "summary": "Alta, paso 1: envia al correo un codigo de 6 digitos para confirmarlo (vale 15 minutos).",
                "requestBody": {
                    "required": true,
                    "content": {
                        "application\/json": {
                            "schema": {
                                "type": "object",
                                "additionalProperties": false,
                                "required": [
                                    "email",
                                    "aceptaCondiciones"
                                ],
                                "properties": {
                                    "email": {
                                        "type": "string",
                                        "format": "email"
                                    },
                                    "aceptaCondiciones": {
                                        "type": "boolean",
                                        "const": true,
                                        "description": "Obligatorio: el usuario acepta las condiciones y la politica de privacidad. Se guarda la fecha de aceptacion en la cuenta."
                                    },
                                    "idioma": {
                                        "type": "string",
                                        "enum": [
                                            "es",
                                            "ca",
                                            "en"
                                        ],
                                        "example": "es",
                                        "description": "Opcional. Idioma del correo con el codigo (es, ca, en). Por defecto, es."
                                    }
                                }
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "Codigo enviado.",
                        "content": {
                            "application\/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "enviado": {
                                            "type": "boolean"
                                        },
                                        "minutos": {
                                            "type": "integer"
                                        }
                                    }
                                }
                            }
                        }
                    },
                    "409": {
                        "description": "Ya existe una cuenta con ese correo (code 'email_en_uso').",
                        "content": {
                            "application\/json": {
                                "schema": {
                                    "$ref": "#\/components\/schemas\/Error"
                                }
                            }
                        }
                    },
                    "422": {
                        "description": "Correo no valido o condiciones no aceptadas (code 'condiciones_no_aceptadas').",
                        "content": {
                            "application\/json": {
                                "schema": {
                                    "$ref": "#\/components\/schemas\/Error"
                                }
                            }
                        }
                    },
                    "429": {
                        "description": "Demasiadas solicitudes.",
                        "content": {
                            "application\/json": {
                                "schema": {
                                    "$ref": "#\/components\/schemas\/Error"
                                }
                            }
                        }
                    },
                    "503": {
                        "description": "No se pudo enviar el correo (code 'correo_no_enviado').",
                        "content": {
                            "application\/json": {
                                "schema": {
                                    "$ref": "#\/components\/schemas\/Error"
                                }
                            }
                        }
                    }
                }
            }
        },
        "\/v1\/auth\/registro\/verificar": {
            "post": {
                "tags": [
                    "Cuentas"
                ],
                "operationId": "verificarCodigoRegistro",
                "security": [],
                "summary": "Alta, paso 2: comprueba el codigo y devuelve el ticket 'verificacion' (vale 60 minutos) para POST \/v1\/cuentas.",
                "requestBody": {
                    "required": true,
                    "content": {
                        "application\/json": {
                            "schema": {
                                "type": "object",
                                "additionalProperties": false,
                                "required": [
                                    "email",
                                    "codigo"
                                ],
                                "properties": {
                                    "email": {
                                        "type": "string",
                                        "format": "email"
                                    },
                                    "codigo": {
                                        "type": "string",
                                        "pattern": "^[0-9]{6}$"
                                    }
                                }
                            }
                        }
                    }
                },
                "responses": {
                    "200": {
                        "description": "Correo confirmado.",
                        "content": {
                            "application\/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "verificado": {
                                            "type": "boolean"
                                        },
                                        "verificacion": {
                                            "type": "string"
                                        }
                                    }
                                }
                            }
                        }
                    },
                    "422": {
                        "description": "Codigo incorrecto ('codigo_incorrecto') o caducado ('codigo_caducado').",
                        "content": {
                            "application\/json": {
                                "schema": {
                                    "$ref": "#\/components\/schemas\/Error"
                                }
                            }
                        }
                    },
                    "429": {
                        "description": "Demasiados intentos ('demasiados_intentos' o 'too_many_requests'): pide un codigo nuevo.",
                        "content": {
                            "application\/json": {
                                "schema": {
                                    "$ref": "#\/components\/schemas\/Error"
                                }
                            }
                        }
                    }
                }
            }
        },
        "\/v1\/pagos\/packs": {
            "get": {
                "summary": "Packs de facturas y su precio",
                "description": "Los packs prepago que se pueden comprar con POST \/v1\/pagos\/checkout-pack, con su precio sin IVA, el IVA (21 %), el total que cobra Stripe y el precio por factura.",
                "tags": [
                    "Pagos"
                ],
                "responses": {
                    "200": {
                        "description": "Lista de packs",
                        "content": {
                            "application\/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "packs": {
                                            "type": "array",
                                            "items": {
                                                "type": "object",
                                                "properties": {
                                                    "pack": {
                                                        "type": "string",
                                                        "example": "50"
                                                    },
                                                    "invoices": {
                                                        "type": "integer",
                                                        "example": 50
                                                    },
                                                    "base": {
                                                        "type": "string",
                                                        "example": "15.00"
                                                    },
                                                    "vat": {
                                                        "type": "string",
                                                        "example": "3.15"
                                                    },
                                                    "total": {
                                                        "type": "string",
                                                        "example": "18.15"
                                                    },
                                                    "perInvoice": {
                                                        "type": "string",
                                                        "example": "0.300"
                                                    }
                                                }
                                            }
                                        },
                                        "currency": {
                                            "type": "string",
                                            "example": "EUR"
                                        },
                                        "vatRate": {
                                            "type": "string",
                                            "example": "21"
                                        }
                                    }
                                }
                            }
                        }
                    }
                }
            }
        },
        "\/v1\/centros\/{id}\/disponibles": {
            "get": {
                "tags": [
                    "Centros de coste"
                ],
                "summary": "Facturas de la cuenta que a\u00fan no est\u00e1n en ning\u00fan centro (para asociarlas)",
                "parameters": [
                    {
                        "name": "id",
                        "in": "path",
                        "required": true,
                        "schema": {
                            "type": "string",
                            "format": "uuid"
                        }
                    },
                    {
                        "name": "nif",
                        "in": "query",
                        "required": false,
                        "schema": {
                            "type": "string"
                        },
                        "description": "Solo las facturas de esa empresa (debe ser de la cuenta)."
                    }
                ],
                "responses": {
                    "200": {
                        "description": "Facturas sin centro (m\u00e1x. 300, m\u00e1s recientes primero).",
                        "content": {
                            "application\/json": {
                                "schema": {
                                    "type": "object",
                                    "properties": {
                                        "invoices": {
                                            "type": "array",
                                            "items": {
                                                "$ref": "#\/components\/schemas\/CentroCosteFactura"
                                            }
                                        }
                                    }
                                }
                            }
                        }
                    },
                    "404": {
                        "description": "No existe en la cuenta.",
                        "content": {
                            "application\/json": {
                                "schema": {
                                    "$ref": "#\/components\/schemas\/Error"
                                }
                            }
                        }
                    }
                }
            }
        }
    },
    "components": {
        "securitySchemes": {
            "bearerAuth": {
                "type": "http",
                "scheme": "bearer",
                "description": "Clave API o token de sesion."
            }
        },
        "schemas": {
            "Error": {
                "type": "object",
                "properties": {
                    "error": {
                        "type": "object",
                        "properties": {
                            "code": {
                                "type": "string"
                            },
                            "message": {
                                "type": "string"
                            }
                        },
                        "additionalProperties": false,
                        "required": [
                            "code",
                            "message"
                        ]
                    }
                },
                "additionalProperties": false,
                "required": [
                    "error"
                ]
            },
            "Paginacion": {
                "type": "object",
                "properties": {
                    "total": {
                        "type": "integer"
                    },
                    "limit": {
                        "type": "integer"
                    },
                    "offset": {
                        "type": "integer"
                    }
                },
                "additionalProperties": false
            },
            "FacturaEncolada": {
                "type": "object",
                "properties": {
                    "id": {
                        "type": "string",
                        "description": "'serie-numero'"
                    },
                    "status": {
                        "type": "string",
                        "enum": [
                            "queued"
                        ]
                    },
                    "warnings": {
                        "type": "array",
                        "items": {
                            "type": "string"
                        },
                        "description": "Avisos no bloqueantes de coherencia de importes (desvios entre 0,02 EUR y 10 EUR, dentro del margen que admite la AEAT). Solo en el alta; la factura se emite igual."
                    }
                },
                "additionalProperties": false
            },
            "Destinatario": {
                "type": "object",
                "properties": {
                    "nif": {
                        "type": "string"
                    },
                    "name": {
                        "type": "string"
                    }
                },
                "additionalProperties": false,
                "required": [
                    "nif",
                    "name"
                ]
            },
            "Producto": {
                "type": "object",
                "description": "Producto guardado de la cuenta (concepto reutilizable).",
                "properties": {
                    "id": {
                        "type": "string",
                        "format": "uuid"
                    },
                    "name": {
                        "type": "string"
                    },
                    "description": {
                        "type": "string"
                    },
                    "taxRate": {
                        "type": "string"
                    },
                    "unitPrice": {
                        "type": "string"
                    },
                    "createdAt": {
                        "type": "string"
                    },
                    "updatedAt": {
                        "type": "string"
                    }
                },
                "additionalProperties": false
            },
            "ProductoAlta": {
                "type": "object",
                "properties": {
                    "name": {
                        "type": "string",
                        "maxLength": 200
                    },
                    "taxRate": {
                        "type": "string"
                    },
                    "description": {
                        "type": "string",
                        "maxLength": 500
                    },
                    "unitPrice": {
                        "type": "string"
                    }
                },
                "additionalProperties": false,
                "required": [
                    "name",
                    "taxRate"
                ]
            },
            "Cliente": {
                "type": "object",
                "description": "Cliente guardado de la cuenta (destinatario reutilizable).",
                "properties": {
                    "id": {
                        "type": "string",
                        "format": "uuid"
                    },
                    "nif": {
                        "type": "string"
                    },
                    "name": {
                        "type": "string"
                    },
                    "createdAt": {
                        "type": "string"
                    },
                    "updatedAt": {
                        "type": "string"
                    },
                    "contactName": {
                        "type": "string",
                        "nullable": true,
                        "description": "Persona de contacto.",
                        "maxLength": 200
                    },
                    "contactEmail": {
                        "type": "string",
                        "nullable": true,
                        "description": "Correo de contacto (se usa para enviar facturas).",
                        "maxLength": 200,
                        "format": "email"
                    },
                    "contactPhone": {
                        "type": "string",
                        "nullable": true,
                        "description": "Telefono de contacto.",
                        "maxLength": 50
                    },
                    "address": {
                        "type": "string",
                        "nullable": true,
                        "description": "Domicilio: calle, numero, piso. Obligatorio para facturas completas (art. 6 del Reglamento de facturacion); se imprime en el PDF.",
                        "maxLength": 250
                    },
                    "postalCode": {
                        "type": "string",
                        "nullable": true,
                        "description": "Codigo postal.",
                        "maxLength": 10,
                        "example": "28001"
                    },
                    "city": {
                        "type": "string",
                        "nullable": true,
                        "description": "Ciudad o municipio.",
                        "maxLength": 120
                    },
                    "province": {
                        "type": "string",
                        "nullable": true,
                        "description": "Provincia.",
                        "maxLength": 60
                    },
                    "country": {
                        "type": "string",
                        "nullable": true,
                        "description": "Pais, codigo ISO 3166-1 alfa-2. Se guarda en mayusculas.",
                        "maxLength": 2,
                        "example": "ES"
                    }
                },
                "additionalProperties": false
            },
            "ClienteAlta": {
                "type": "object",
                "properties": {
                    "nif": {
                        "type": "string",
                        "maxLength": 20
                    },
                    "name": {
                        "type": "string",
                        "maxLength": 200
                    },
                    "contactName": {
                        "type": "string",
                        "nullable": true,
                        "description": "Persona de contacto.",
                        "maxLength": 200
                    },
                    "contactEmail": {
                        "type": "string",
                        "nullable": true,
                        "description": "Correo de contacto (se usa para enviar facturas).",
                        "maxLength": 200,
                        "format": "email"
                    },
                    "contactPhone": {
                        "type": "string",
                        "nullable": true,
                        "description": "Telefono de contacto.",
                        "maxLength": 50
                    },
                    "address": {
                        "type": "string",
                        "nullable": true,
                        "description": "Domicilio: calle, numero, piso. Obligatorio para facturas completas (art. 6 del Reglamento de facturacion); se imprime en el PDF.",
                        "maxLength": 250
                    },
                    "postalCode": {
                        "type": "string",
                        "nullable": true,
                        "description": "Codigo postal.",
                        "maxLength": 10,
                        "example": "28001"
                    },
                    "city": {
                        "type": "string",
                        "nullable": true,
                        "description": "Ciudad o municipio.",
                        "maxLength": 120
                    },
                    "province": {
                        "type": "string",
                        "nullable": true,
                        "description": "Provincia.",
                        "maxLength": 60
                    },
                    "country": {
                        "type": "string",
                        "nullable": true,
                        "description": "Pais, codigo ISO 3166-1 alfa-2. Se guarda en mayusculas.",
                        "maxLength": 2,
                        "example": "ES"
                    }
                },
                "additionalProperties": false,
                "required": [
                    "nif",
                    "name"
                ]
            },
            "DesgloseLinea": {
                "type": "object",
                "description": "Un tramo del desglose multi-IVA (una linea por tipo de IVA).",
                "properties": {
                    "taxRate": {
                        "type": "string",
                        "description": "Tipo impositivo como texto decimal (ej. \"21\", \"10\")."
                    },
                    "taxBase": {
                        "type": "string",
                        "description": "Base imponible del tramo, decimal como texto."
                    },
                    "taxAmount": {
                        "type": "string",
                        "description": "Cuota repercutida del tramo, decimal como texto."
                    },
                    "operationQualification": {
                        "type": "string",
                        "description": "S1\/S2\/N1\/N2. Excluyente con taxExemptionCode. Si falta, hereda la de cabecera."
                    },
                    "taxExemptionCode": {
                        "type": "string",
                        "description": "E1-E8. Excluyente con operationQualification. Si falta, hereda la de cabecera."
                    },
                    "taxRegimeKey": {
                        "type": "string",
                        "description": "Clave de regimen del tramo. Si falta, hereda la de cabecera."
                    },
                    "tax": {
                        "type": "string",
                        "description": "Impuesto: 01=IVA, 02=IPSI, 03=IGIC, 05=Otros. Opcional."
                    },
                    "tipoRecargoEquivalencia": {
                        "type": "string",
                        "description": "Recargo de equivalencia: tipo como texto decimal (ej. \"5.2\"). Va en pareja con cuotaRecargoEquivalencia. No aplica a operaciones exentas."
                    },
                    "cuotaRecargoEquivalencia": {
                        "type": "string",
                        "description": "Recargo de equivalencia: cuota como texto decimal. Va en pareja con tipoRecargoEquivalencia."
                    }
                },
                "additionalProperties": false,
                "required": [
                    "taxRate",
                    "taxBase",
                    "taxAmount"
                ]
            },
            "FacturaRectificada": {
                "type": "object",
                "description": "Identificacion de una factura original que una rectificativa (R1-R5) corrige.",
                "properties": {
                    "nif": {
                        "type": "string",
                        "description": "NIF del emisor de la factura original."
                    },
                    "number": {
                        "type": "string",
                        "description": "Numero de la factura original (serie + numero)."
                    },
                    "issueDate": {
                        "type": "string",
                        "format": "date",
                        "description": "Fecha de expedicion de la factura original (AAAA-MM-DD)."
                    }
                },
                "additionalProperties": false,
                "required": [
                    "nif",
                    "number",
                    "issueDate"
                ]
            },
            "ImporteRectificacion": {
                "type": "object",
                "description": "Importes de la factura original rectificada. Obligatorio en rectificativas por sustitucion (correctiveType=S); no se informa en las de por diferencias (I).",
                "properties": {
                    "taxBase": {
                        "type": "string",
                        "description": "Base rectificada, decimal como texto."
                    },
                    "taxAmount": {
                        "type": "string",
                        "description": "Cuota rectificada, decimal como texto."
                    },
                    "surchargeAmount": {
                        "type": "string",
                        "description": "Cuota de recargo de equivalencia rectificada, decimal como texto. Opcional."
                    }
                },
                "additionalProperties": false,
                "required": [
                    "taxBase",
                    "taxAmount"
                ]
            },
            "FacturaAlta": {
                "type": "object",
                "properties": {
                    "nif": {
                        "type": "string",
                        "description": "Emisor. Opcional si el token autoriza uno solo."
                    },
                    "series": {
                        "type": "string"
                    },
                    "number": {
                        "type": "string",
                        "description": "Opcional: si falta, la API asigna el siguiente de la serie."
                    },
                    "issueDate": {
                        "type": "string",
                        "format": "date"
                    },
                    "invoiceType": {
                        "type": "string",
                        "description": "Tipo de factura segun la AEAT (F1, F2, F3, R1...R5). F1 exige recipients."
                    },
                    "description": {
                        "type": "string"
                    },
                    "taxRegimeKey": {
                        "type": "string",
                        "description": "Ej. \"01\"."
                    },
                    "operationQualification": {
                        "type": "string",
                        "description": "Operacion sujeta (ej. S1). Excluyente con taxExemptionCode."
                    },
                    "taxExemptionCode": {
                        "type": "string",
                        "description": "Operacion exenta (ej. E1). Excluyente con operationQualification."
                    },
                    "taxBase": {
                        "type": "string",
                        "description": "Decimal como texto."
                    },
                    "taxRate": {
                        "type": "string",
                        "description": "Decimal como texto. Obligatorio salvo que se envie 'breakdown' (desglose multi-IVA), en cuyo caso es opcional."
                    },
                    "taxAmount": {
                        "type": "string",
                        "description": "Decimal como texto."
                    },
                    "totalAmount": {
                        "type": "string",
                        "description": "Decimal como texto."
                    },
                    "recipients": {
                        "type": "array",
                        "items": {
                            "$ref": "#\/components\/schemas\/Destinatario"
                        }
                    },
                    "correctiveType": {
                        "type": "string",
                        "description": "Rectificativa: S (sustitucion) o I (diferencias)."
                    },
                    "breakdown": {
                        "type": "array",
                        "minItems": 1,
                        "maxItems": 12,
                        "description": "Desglose multi-IVA opcional: una linea por cada tipo de IVA (1 a 12), misma forma que el array 'breakdown' que devuelve POST \/v1\/registros\/calcular (se pueden encadenar). Si se informa, taxBase\/taxAmount\/totalAmount de cabecera son los TOTALES y el taxRate de cabecera es opcional. Cada tramo que no traiga su propia operationQualification\/taxExemptionCode ni taxRegimeKey hereda la de cabecera.",
                        "items": {
                            "$ref": "#\/components\/schemas\/DesgloseLinea"
                        }
                    },
                    "rectifiedInvoices": {
                        "type": "array",
                        "minItems": 1,
                        "maxItems": 1000,
                        "description": "Rectificativas (R1-R5): facturas originales que se rectifican. Su envio es obligatorio cuando invoiceType es R1-R5.",
                        "items": {
                            "$ref": "#\/components\/schemas\/FacturaRectificada"
                        }
                    },
                    "rectificationAmounts": {
                        "$ref": "#\/components\/schemas\/ImporteRectificacion"
                    },
                    "paymentMethod": {
                        "type": "string",
                        "maxLength": 40,
                        "example": "Transferencia",
                        "description": "Opcional. Forma de pago (transferencia, Bizum, tarjeta...). No se envia a la AEAT; se guarda y se imprime en el PDF."
                    },
                    "lines": {
                        "type": "array",
                        "maxItems": 200,
                        "description": "Opcional. Conceptos de la factura para imprimirla (art. 6.1.f RD 1619\/2012: descripcion y precio unitario sin IVA). No se envian a la AEAT ni cambian los importes: los totales siguen saliendo de taxBase\/taxAmount\/totalAmount\/breakdown. Si alguna linea no es valida, no se guarda ninguna (la factura se emite igual).",
                        "items": {
                            "type": "object",
                            "required": [
                                "unitPrice",
                                "taxRate"
                            ],
                            "properties": {
                                "description": {
                                    "type": "string",
                                    "maxLength": 250,
                                    "example": "Consultoria"
                                },
                                "quantity": {
                                    "oneOf": [
                                        {
                                            "type": "number"
                                        },
                                        {
                                            "type": "string"
                                        }
                                    ],
                                    "description": "Cantidad (por defecto 1).",
                                    "example": 1
                                },
                                "unitPrice": {
                                    "oneOf": [
                                        {
                                            "type": "number"
                                        },
                                        {
                                            "type": "string"
                                        }
                                    ],
                                    "description": "Precio unitario sin IVA.",
                                    "example": 100
                                },
                                "taxRate": {
                                    "oneOf": [
                                        {
                                            "type": "number"
                                        },
                                        {
                                            "type": "string"
                                        }
                                    ],
                                    "description": "Tipo de IVA en % (0-100).",
                                    "example": 21
                                },
                                "discount": {
                                    "oneOf": [
                                        {
                                            "type": "number"
                                        },
                                        {
                                            "type": "string"
                                        }
                                    ],
                                    "description": "Descuento en % sobre la linea (opcional).",
                                    "example": 0
                                },
                                "productId": {
                                    "type": "string",
                                    "format": "uuid",
                                    "description": "Opcional. Producto del catalogo del que sale la linea (para sumar lo facturado por categoria)."
                                }
                            }
                        }
                    },
                    "note": {
                        "type": "string",
                        "maxLength": 500,
                        "description": "Opcional. Nota para el cliente (agradecimiento, datos bancarios...). NO se envia a la AEAT: la descripcion de la operacion va en description. Se devuelve en el detalle para imprimirla."
                    },
                    "costCenterId": {
                        "type": "string",
                        "format": "uuid",
                        "description": "Centro de coste ABIERTO de la cuenta al que se asocia la factura. Si no existe o est\u00e1 cerrado responde 422 invalid_cost_center sin emitir ni gastar saldo. No va a la AEAT."
                    }
                },
                "additionalProperties": false,
                "required": [
                    "series",
                    "issueDate",
                    "invoiceType",
                    "description",
                    "taxRegimeKey",
                    "taxBase",
                    "taxAmount",
                    "totalAmount"
                ]
            },
            "FacturaAnulacion": {
                "type": "object",
                "properties": {
                    "nif": {
                        "type": "string"
                    },
                    "noPriorRecord": {
                        "type": "boolean"
                    },
                    "priorRejection": {
                        "type": "string"
                    },
                    "generatedBy": {
                        "type": "string"
                    },
                    "generator": {
                        "$ref": "#\/components\/schemas\/Destinatario"
                    }
                },
                "additionalProperties": false
            },
            "FacturaResumen": {
                "type": "object",
                "properties": {
                    "detail": {
                        "type": "object",
                        "description": "Solo con include=detail. Mismos campos que GET \/v1\/invoices\/{id} (registro, cancelled, substitutedBy, lines, withholdingAmount, paymentMethod, intentos, lastError) m\u00e1s \u00abpayment\u00bb.",
                        "properties": {
                            "registro": {
                                "type": "object"
                            },
                            "cancelled": {
                                "type": "boolean"
                            },
                            "substitutedBy": {
                                "type": "string"
                            },
                            "lines": {
                                "type": "array",
                                "items": {
                                    "type": "object"
                                }
                            },
                            "withholdingAmount": {
                                "type": "string"
                            },
                            "paymentMethod": {
                                "type": "string"
                            },
                            "intentos": {
                                "type": "integer"
                            },
                            "lastError": {
                                "type": "string"
                            },
                            "payment": {
                                "type": "object",
                                "properties": {
                                    "collected": {
                                        "type": "string"
                                    },
                                    "invoiceTotal": {
                                        "type": "string",
                                        "nullable": true,
                                        "description": "Lo que paga el cliente (total - retenci\u00f3n); null si est\u00e1 anulada o sustituida."
                                    },
                                    "paid": {
                                        "type": "boolean"
                                    }
                                }
                            }
                        }
                    }
                },
                "additionalProperties": true,
                "description": "Resumen de una factura en el listado."
            },
            "FacturaDetalle": {
                "type": "object",
                "properties": {
                    "id": {
                        "type": "string"
                    },
                    "status": {
                        "type": "string",
                        "enum": [
                            "queued",
                            "sent",
                            "permanently_failed",
                            "accepted_with_errors"
                        ],
                        "description": "Estados: queued = en cola (la AEAT aun no responde) | sent = ACEPTADA por la AEAT (en el portal: 'Aceptada') | accepted_with_errors = aceptada con errores\/observaciones | permanently_failed = rechazada en firme (no se reintenta; se corrige y se emite de nuevo)."
                    },
                    "tipoRegistro": {
                        "type": "string",
                        "enum": [
                            "alta",
                            "anulacion"
                        ]
                    },
                    "fecha": {
                        "type": "string"
                    },
                    "creadoEn": {
                        "type": "string",
                        "format": "date-time"
                    },
                    "fechaActualizacion": {
                        "type": "string",
                        "format": "date-time"
                    },
                    "intentos": {
                        "type": "integer"
                    },
                    "proximoIntentoEn": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "format": "date-time"
                    },
                    "registro": {
                        "type": "object",
                        "properties": {
                            "numSerieFactura": {
                                "type": "string"
                            },
                            "fechaExpedicion": {
                                "type": "string",
                                "format": "date"
                            },
                            "tipoFactura": {
                                "type": "string"
                            },
                            "cuotaTotal": {
                                "type": "string"
                            },
                            "importeTotal": {
                                "type": "string"
                            },
                            "fechaHoraGeneracion": {
                                "type": "string",
                                "format": "date-time"
                            },
                            "tipoHuella": {
                                "type": "string"
                            },
                            "huella": {
                                "type": "string"
                            },
                            "huellaAnterior": {
                                "type": "string"
                            },
                            "tipoRectificativa": {
                                "type": "string",
                                "enum": [
                                    "S",
                                    "I"
                                ],
                                "description": "Solo rectificativas: S por sustitucion, I por diferencias."
                            },
                            "facturasRectificadas": {
                                "type": "array",
                                "description": "Solo rectificativas: facturas que corrige.",
                                "items": {
                                    "type": "object",
                                    "properties": {
                                        "nif": {
                                            "type": "string"
                                        },
                                        "numSerieFactura": {
                                            "type": "string"
                                        },
                                        "fechaExpedicion": {
                                            "type": "string",
                                            "description": "dd-mm-aaaa, como en el XML."
                                        }
                                    }
                                }
                            },
                            "importeRectificacion": {
                                "type": "object",
                                "description": "Solo rectificativas por sustitucion: importes de la factura original.",
                                "properties": {
                                    "baseRectificada": {
                                        "type": "string"
                                    },
                                    "cuotaRectificada": {
                                        "type": "string"
                                    },
                                    "cuotaRecargoRectificado": {
                                        "type": "string",
                                        "nullable": true
                                    }
                                }
                            }
                        },
                        "additionalProperties": true
                    },
                    "paymentMethod": {
                        "type": "string",
                        "nullable": true,
                        "description": "Forma de pago indicada al emitir, si se indico."
                    },
                    "withholdingAmount": {
                        "type": "string",
                        "nullable": true,
                        "example": "15.00",
                        "description": "Retencion de IRPF indicada al emitir (euros). No se comunica a la AEAT: el importe total del registro no la incluye. Total a cobrar = importeTotal - withholdingAmount."
                    },
                    "cancelled": {
                        "type": "boolean",
                        "description": "true si la factura tiene un registro de anulacion. Importes y cliente siguen siendo los del alta."
                    },
                    "cancellationStatus": {
                        "type": "string",
                        "description": "Estado interno del envio de la anulacion."
                    },
                    "cancelledAt": {
                        "type": "string",
                        "format": "date-time",
                        "nullable": true
                    },
                    "lines": {
                        "type": "array",
                        "description": "Conceptos guardados al emitir (solo si se enviaron en lines). Importes como texto con 2 decimales.",
                        "items": {
                            "type": "object",
                            "properties": {
                                "description": {
                                    "type": "string"
                                },
                                "quantity": {
                                    "type": "string",
                                    "example": "1"
                                },
                                "unitPrice": {
                                    "type": "string",
                                    "example": "100.00"
                                },
                                "taxRate": {
                                    "type": "string",
                                    "example": "21"
                                },
                                "discount": {
                                    "type": "string"
                                },
                                "amount": {
                                    "type": "string",
                                    "description": "quantity x unitPrice menos descuento, sin IVA.",
                                    "example": "100.00"
                                }
                            }
                        }
                    },
                    "substitutedBy": {
                        "type": "string",
                        "description": "Solo si una rectificativa por sustitucion (aceptada por la AEAT) reemplazo esta factura: su numero. La factura ya no admite cobros ni cuenta en deudas.",
                        "example": "2026-0009"
                    },
                    "note": {
                        "type": "string",
                        "description": "Nota para el cliente guardada al emitir (solo si se envio)."
                    },
                    "costCenter": {
                        "type": "object",
                        "nullable": true,
                        "description": "Centro de coste de la factura, si tiene.",
                        "properties": {
                            "id": {
                                "type": "string",
                                "format": "uuid"
                            },
                            "name": {
                                "type": "string"
                            },
                            "showOnInvoice": {
                                "type": "boolean"
                            }
                        }
                    }
                },
                "additionalProperties": true
            },
            "AnalisisNif": {
                "type": "object",
                "properties": {
                    "nif": {
                        "type": "string"
                    },
                    "valido": {
                        "type": "boolean"
                    },
                    "tipo": {
                        "type": "string"
                    },
                    "persona": {
                        "type": "string"
                    },
                    "verificado": {
                        "type": "boolean"
                    },
                    "motivo": {
                        "type": [
                            "string",
                            "null"
                        ]
                    }
                },
                "additionalProperties": true
            },
            "Emisor": {
                "type": "object",
                "properties": {
                    "id": {
                        "type": "string"
                    },
                    "nif": {
                        "type": "string"
                    },
                    "nombreEmisor": {
                        "type": "string"
                    },
                    "tipo": {
                        "type": "string"
                    },
                    "persona": {
                        "type": "string"
                    },
                    "esPrincipal": {
                        "type": "boolean"
                    },
                    "produccionHabilitada": {
                        "type": "boolean"
                    },
                    "verificacionMetodo": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "enum": [
                            "certificado",
                            "sepa",
                            "censal",
                            null
                        ]
                    },
                    "verificacionFecha": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "format": "date-time"
                    },
                    "certificado": {
                        "type": "object",
                        "properties": {
                            "cargado": {
                                "type": "boolean"
                            }
                        },
                        "additionalProperties": false
                    },
                    "nombreComercial": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "maxLength": 200,
                        "description": "Nombre comercial. Si se informa, encabeza la factura en lugar de la razon social."
                    },
                    "direccion": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "maxLength": 250,
                        "description": "Domicilio fiscal (calle y numero)."
                    },
                    "codigoPostal": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "maxLength": 10,
                        "description": "Codigo postal."
                    },
                    "ciudad": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "maxLength": 120,
                        "description": "Ciudad."
                    },
                    "provincia": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "maxLength": 60,
                        "description": "Provincia."
                    },
                    "actividad": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "maxLength": 80,
                        "description": "Actividad (para proponer el IVA en el portal)."
                    },
                    "irpfDefecto": {
                        "type": [
                            "number",
                            "null"
                        ],
                        "minimum": 0,
                        "maximum": 47,
                        "description": "Retencion de IRPF por defecto, en %."
                    }
                },
                "additionalProperties": false
            },
            "CertificadoInfo": {
                "type": "object",
                "properties": {
                    "cargado": {
                        "type": "boolean",
                        "enum": [
                            true
                        ]
                    },
                    "titular": {
                        "type": [
                            "string",
                            "null"
                        ]
                    },
                    "emisorCertificado": {
                        "type": [
                            "string",
                            "null"
                        ]
                    },
                    "nifs": {
                        "type": "array",
                        "items": {
                            "type": "string"
                        }
                    },
                    "validoDesde": {
                        "type": "string",
                        "format": "date-time"
                    },
                    "caducaEn": {
                        "type": "string",
                        "format": "date-time"
                    },
                    "diasRestantes": {
                        "type": "integer"
                    },
                    "estado": {
                        "type": "string",
                        "enum": [
                            "aun_no_valido",
                            "caducado",
                            "por_caducar",
                            "vigente"
                        ]
                    },
                    "huellaSha256": {
                        "type": "string"
                    }
                },
                "additionalProperties": false
            },
            "CertificadoEstado": {
                "oneOf": [
                    {
                        "type": "object",
                        "properties": {
                            "cargado": {
                                "type": "boolean",
                                "enum": [
                                    false
                                ]
                            }
                        },
                        "additionalProperties": false
                    },
                    {
                        "$ref": "#\/components\/schemas\/CertificadoInfo"
                    },
                    {
                        "type": "object",
                        "properties": {
                            "cargado": {
                                "type": "boolean",
                                "enum": [
                                    true
                                ]
                            },
                            "legible": {
                                "type": "boolean",
                                "enum": [
                                    false
                                ]
                            }
                        },
                        "additionalProperties": false,
                        "description": "Existe pero no se puede leer."
                    }
                ]
            },
            "AvisoCertificado": {
                "type": "object",
                "properties": {
                    "nif": {
                        "type": "string"
                    },
                    "nombreEmisor": {
                        "type": "string"
                    },
                    "titular": {
                        "type": [
                            "string",
                            "null"
                        ]
                    },
                    "caducaEn": {
                        "type": "string",
                        "format": "date-time"
                    },
                    "diasRestantes": {
                        "type": "integer"
                    },
                    "nivel": {
                        "type": "string",
                        "enum": [
                            "caducado",
                            "30",
                            "60",
                            "vigente"
                        ]
                    },
                    "huellaSha256": {
                        "type": [
                            "string",
                            "null"
                        ]
                    }
                },
                "additionalProperties": false
            },
            "Serie": {
                "type": "object",
                "properties": {
                    "serie": {
                        "type": "string"
                    },
                    "siguienteNumero": {
                        "type": "integer"
                    },
                    "digitos": {
                        "type": "integer"
                    },
                    "asignadas": {
                        "type": "integer"
                    }
                },
                "additionalProperties": false
            },
            "Cola": {
                "type": "object",
                "properties": {
                    "alcance": {
                        "type": "string",
                        "enum": [
                            "cuenta",
                            "global"
                        ]
                    },
                    "enCola": {
                        "type": "integer"
                    },
                    "requierenRevision": {
                        "type": "integer"
                    },
                    "fallidosPermanentes": {
                        "type": "integer"
                    },
                    "masAntiguoDesde": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "format": "date-time"
                    },
                    "proximoIntentoEn": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "format": "date-time"
                    }
                },
                "additionalProperties": false
            },
            "Health": {
                "type": "object",
                "properties": {
                    "status": {
                        "type": "string",
                        "enum": [
                            "ok"
                        ]
                    },
                    "timestamp": {
                        "type": "string",
                        "format": "date-time"
                    },
                    "alcance": {
                        "type": "string",
                        "enum": [
                            "cuenta",
                            "global"
                        ]
                    },
                    "baseDeDatos": {
                        "type": "object",
                        "properties": {
                            "ok": {
                                "type": "boolean"
                            },
                            "latenciaMs": {
                                "type": "integer"
                            }
                        },
                        "additionalProperties": false
                    },
                    "cola": {
                        "type": "object",
                        "properties": {
                            "enCola": {
                                "type": "integer"
                            },
                            "requierenRevision": {
                                "type": "integer"
                            },
                            "fallidosPermanentes": {
                                "type": "integer"
                            },
                            "masAntiguoDesde": {
                                "type": [
                                    "string",
                                    "null"
                                ],
                                "format": "date-time"
                            },
                            "proximoIntentoEn": {
                                "type": [
                                    "string",
                                    "null"
                                ],
                                "format": "date-time"
                            }
                        },
                        "additionalProperties": false
                    },
                    "ultimas24h": {
                        "type": "object",
                        "properties": {
                            "aceptados": {
                                "type": "integer"
                            },
                            "rechazados": {
                                "type": "integer"
                            },
                            "tasaAceptacion": {
                                "type": [
                                    "number",
                                    "null"
                                ]
                            },
                            "latenciaMediaSegundos": {
                                "type": [
                                    "number",
                                    "null"
                                ]
                            }
                        },
                        "additionalProperties": false
                    }
                },
                "additionalProperties": false
            },
            "EmisorDatosFiscales": {
                "type": "object",
                "additionalProperties": false,
                "description": "Campos editables del emisor. Los que no se envian no cambian; null los borra (salvo nombreEmisor).",
                "properties": {
                    "nombreEmisor": {
                        "type": "string",
                        "maxLength": 200
                    },
                    "nombreComercial": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "maxLength": 200,
                        "description": "Nombre comercial. Si se informa, encabeza la factura en lugar de la razon social."
                    },
                    "direccion": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "maxLength": 250,
                        "description": "Domicilio fiscal (calle y numero)."
                    },
                    "codigoPostal": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "maxLength": 10,
                        "description": "Codigo postal."
                    },
                    "ciudad": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "maxLength": 120,
                        "description": "Ciudad."
                    },
                    "provincia": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "maxLength": 60,
                        "description": "Provincia."
                    },
                    "actividad": {
                        "type": [
                            "string",
                            "null"
                        ],
                        "maxLength": 80,
                        "description": "Actividad (para proponer el IVA en el portal)."
                    },
                    "irpfDefecto": {
                        "type": [
                            "number",
                            "null"
                        ],
                        "minimum": 0,
                        "maximum": 47,
                        "description": "Retencion de IRPF por defecto, en %."
                    }
                }
            },
            "PreferenciasCuenta": {
                "type": "object",
                "properties": {
                    "avisoSaldo": {
                        "type": "boolean",
                        "description": "Aviso por correo cuando el saldo baja del umbral."
                    },
                    "avisoUmbral": {
                        "type": "integer",
                        "minimum": 0,
                        "maximum": 100000
                    },
                    "recargaAuto": {
                        "type": "boolean",
                        "description": "Recarga automatica de saldo."
                    },
                    "recargaUmbral": {
                        "type": "integer",
                        "minimum": 0,
                        "maximum": 100000
                    },
                    "recargaPack": {
                        "type": "integer",
                        "enum": [
                            50,
                            150,
                            500,
                            1500
                        ]
                    },
                    "marcaLogo": {
                        "type": "string",
                        "nullable": true,
                        "description": "Logotipo para el PDF (data URI de imagen, menos de 500 KB)."
                    },
                    "marcaColor": {
                        "type": "string",
                        "nullable": true,
                        "pattern": "^#[0-9a-fA-F]{6}$",
                        "example": "#2f6bff"
                    },
                    "marcaPie": {
                        "type": "string",
                        "nullable": true,
                        "maxLength": 300,
                        "description": "Texto del pie de la factura."
                    },
                    "idioma": {
                        "type": "string",
                        "enum": [
                            "es",
                            "ca",
                            "en"
                        ],
                        "description": "Idioma de la cuenta para los correos de Nodda."
                    },
                    "recargaConfigurada": {
                        "type": "boolean",
                        "readOnly": true
                    }
                }
            },
            "PreferenciasCuentaEntrada": {
                "type": "object",
                "description": "Solo se cambian los campos que se envian.",
                "properties": {
                    "avisoSaldo": {
                        "type": "boolean",
                        "description": "Aviso por correo cuando el saldo baja del umbral."
                    },
                    "avisoUmbral": {
                        "type": "integer",
                        "minimum": 0,
                        "maximum": 100000
                    },
                    "recargaAuto": {
                        "type": "boolean",
                        "description": "Recarga automatica de saldo."
                    },
                    "recargaUmbral": {
                        "type": "integer",
                        "minimum": 0,
                        "maximum": 100000
                    },
                    "recargaPack": {
                        "type": "integer",
                        "enum": [
                            50,
                            150,
                            500,
                            1500
                        ]
                    },
                    "marcaLogo": {
                        "type": "string",
                        "nullable": true,
                        "description": "Logotipo para el PDF (data URI de imagen, menos de 500 KB)."
                    },
                    "marcaColor": {
                        "type": "string",
                        "nullable": true,
                        "pattern": "^#[0-9a-fA-F]{6}$",
                        "example": "#2f6bff"
                    },
                    "marcaPie": {
                        "type": "string",
                        "nullable": true,
                        "maxLength": 300,
                        "description": "Texto del pie de la factura."
                    },
                    "idioma": {
                        "type": "string",
                        "enum": [
                            "es",
                            "ca",
                            "en"
                        ],
                        "description": "Idioma de la cuenta para los correos de Nodda."
                    }
                }
            },
            "CentroCosteEntrada": {
                "type": "object",
                "required": [
                    "name"
                ],
                "properties": {
                    "name": {
                        "type": "string",
                        "maxLength": 200,
                        "description": "Nombre \u00fanico en la cuenta."
                    },
                    "description": {
                        "type": "string",
                        "maxLength": 500,
                        "nullable": true,
                        "description": "Descripci\u00f3n interna."
                    },
                    "mainClientId": {
                        "type": "string",
                        "format": "uuid",
                        "nullable": true,
                        "description": "Cliente principal. En el portal, sus nuevas facturas se proponen en este centro."
                    },
                    "budget": {
                        "type": "string",
                        "nullable": true,
                        "example": "12000.00",
                        "description": "Presupuesto o importe acordado (decimal con punto). Se compara con lo facturado (total con IVA)."
                    },
                    "startDate": {
                        "type": "string",
                        "format": "date",
                        "nullable": true
                    },
                    "endDate": {
                        "type": "string",
                        "format": "date",
                        "nullable": true,
                        "description": "No puede ser anterior a startDate."
                    },
                    "status": {
                        "type": "string",
                        "enum": [
                            "abierto",
                            "cerrado"
                        ],
                        "default": "abierto",
                        "description": "Un centro cerrado no acepta facturas nuevas."
                    },
                    "showOnInvoice": {
                        "type": "boolean",
                        "default": false,
                        "description": "Imprimir \u00abCentro de coste: <nombre>\u00bb en el PDF de la factura."
                    }
                }
            },
            "CentroCoste": {
                "allOf": [
                    {
                        "$ref": "#\/components\/schemas\/CentroCosteEntrada"
                    },
                    {
                        "type": "object",
                        "properties": {
                            "id": {
                                "type": "string",
                                "format": "uuid"
                            },
                            "createdAt": {
                                "type": "string"
                            },
                            "updatedAt": {
                                "type": "string"
                            }
                        }
                    }
                ]
            },
            "CentroCosteConCifras": {
                "allOf": [
                    {
                        "$ref": "#\/components\/schemas\/CentroCoste"
                    },
                    {
                        "type": "object",
                        "description": "Cifras sin facturas anuladas ni sustituidas por una rectificativa aceptada.",
                        "properties": {
                            "invoiceCount": {
                                "type": "integer"
                            },
                            "invoicedBase": {
                                "type": "string",
                                "pattern": "^-?\\d+\\.\\d{2}$",
                                "description": "Suma de bases imponibles."
                            },
                            "invoiced": {
                                "type": "string",
                                "pattern": "^-?\\d+\\.\\d{2}$",
                                "description": "Facturado: suma de totales con IVA."
                            },
                            "pending": {
                                "type": "string",
                                "pattern": "^-?\\d+\\.\\d{2}$",
                                "description": "Pendiente de cobro: total menos retenci\u00f3n de IRPF menos cobros registrados."
                            },
                            "collected": {
                                "type": "string",
                                "pattern": "^-?\\d+\\.\\d{2}$",
                                "description": "Cobrado (cobros registrados)."
                            }
                        }
                    }
                ]
            },
            "CentroCosteFactura": {
                "type": "object",
                "properties": {
                    "nif": {
                        "type": "string"
                    },
                    "invoice": {
                        "type": "string",
                        "description": "Serie-n\u00famero."
                    },
                    "issueDate": {
                        "type": "string",
                        "format": "date",
                        "nullable": true
                    },
                    "client": {
                        "type": "string",
                        "nullable": true
                    },
                    "clientNif": {
                        "type": "string",
                        "nullable": true
                    },
                    "description": {
                        "type": "string",
                        "nullable": true
                    },
                    "base": {
                        "type": "string",
                        "pattern": "^-?\\d+\\.\\d{2}$",
                        "description": "Base imponible."
                    },
                    "total": {
                        "type": "string",
                        "pattern": "^-?\\d+\\.\\d{2}$",
                        "description": "Total con IVA."
                    },
                    "pending": {
                        "type": "string",
                        "pattern": "^-?\\d+\\.\\d{2}$",
                        "description": "Pendiente de cobro (null si no suma).",
                        "nullable": true
                    },
                    "state": {
                        "type": "string",
                        "enum": [
                            "valid",
                            "processing",
                            "cancelled",
                            "substituted"
                        ],
                        "description": "Solo las \u00abvalid\u00bb suman en el centro."
                    }
                }
            }
        },
        "responses": {
            "Invalida": {
                "description": "Peticion invalida (code 'invalid_request', 'business_rule_violation' u otro especifico).",
                "content": {
                    "application\/json": {
                        "schema": {
                            "$ref": "#\/components\/schemas\/Error"
                        }
                    }
                }
            },
            "NoAutorizado": {
                "description": "Falta la clave o no es valida.",
                "content": {
                    "application\/json": {
                        "schema": {
                            "$ref": "#\/components\/schemas\/Error"
                        }
                    }
                }
            },
            "Prohibido": {
                "description": "El rol no tiene permiso para esta operacion.",
                "content": {
                    "application\/json": {
                        "schema": {
                            "$ref": "#\/components\/schemas\/Error"
                        }
                    }
                }
            },
            "NoEncontrado": {
                "description": "No existe, o no esta autorizado para este token.",
                "content": {
                    "application\/json": {
                        "schema": {
                            "$ref": "#\/components\/schemas\/Error"
                        }
                    }
                }
            },
            "Conflicto": {
                "description": "Conflicto con el estado actual.",
                "content": {
                    "application\/json": {
                        "schema": {
                            "$ref": "#\/components\/schemas\/Error"
                        }
                    }
                }
            },
            "CupoAgotado": {
                "description": "Se agoto el cupo gratuito (code 'quota_exceeded').",
                "content": {
                    "application\/json": {
                        "schema": {
                            "$ref": "#\/components\/schemas\/Error"
                        }
                    }
                }
            },
            "DemasiadasPeticiones": {
                "description": "Limite de uso superado; ver Retry-After.",
                "headers": {
                    "Retry-After": {
                        "schema": {
                            "type": "string"
                        },
                        "description": "Segundos hasta poder reintentar."
                    }
                },
                "content": {
                    "application\/json": {
                        "schema": {
                            "$ref": "#\/components\/schemas\/Error"
                        }
                    }
                }
            }
        }
    }
}