# API V2 — Documentación de endpoints **Base URL real:** `/2.0` **Base URL mock:** `/2.0-mock` *(sin autenticación, datos de ejemplo)* > Las rutas protegidas requieren el header `Authorization: Bearer {token}` obtenido en `/login`. > Todas las respuestas son `Content-Type: application/json`. --- ## Índice 1. [Auth](#1-auth) 2. [People (abonado)](#2-people-abonado) 3. [Activities (actividades)](#3-activities-actividades) 4. [Booking (reservas — app)](#4-booking-reservas--app) 5. [Payments](#5-payments) 6. [Wallet (monedero)](#6-wallet-monedero) 7. [Search (búsqueda)](#7-search-búsqueda) 8. [Members (socios — admin)](#8-members-socios--admin) 9. [Courses (cursos — admin)](#9-courses-cursos--admin) 10. [Bills (recibos — admin)](#10-bills-recibos--admin) 11. [Booking Admin (reservas — admin)](#11-booking-admin-reservas--admin) 12. [Cash (caja — admin)](#12-cash-caja--admin) 13. [Dayoff / Celebrations](#13-dayoff--celebrations) 14. [Remittance (remesas especiales)](#14-remittance-remesas-especiales) 15. [Inscriptions (preinscripciones)](#15-inscriptions-preinscripciones) 16. [Admin Misc](#16-admin-misc) 17. [Printer](#17-printer) 18. [Hash (público)](#18-hash-público) --- ## 1. Auth ### `POST /login` Autentica al usuario y devuelve un token Bearer. **Body (form/JSON)** | Campo | Tipo | Descripción | |----------|--------|------------------| | email | string | Email del abonado | | password | string | Contraseña | **Respuesta 200** ```json { "access_token": "4Km9xQzLp2VnRtYwEoJbFsUcXgHdAiMkNlOqPrSvTu" } ``` **Respuesta 401** ```json { "message": "Contraseña incorrecta." } ``` --- ### `POST /forgot` Envía un email de recuperación de contraseña. **Body** | Campo | Tipo | |-------|--------| | email | string | **Respuesta 200** ```json { "error": "Se le ha enviado un email con instrucciones" } ``` --- ### `GET /logout` 🔒 Cierra la sesión del usuario autenticado. **Respuesta 200** ```json { "logout": "ok" } ``` --- ## 2. People (abonado) ### `GET /people/me` 🔒 Devuelve los datos del abonado autenticado. **Respuesta 200** ```json { "member": "00042", "family": 1, "first_name": "María", "last_name": "García López", "email": "maria@example.com", "phone": "612345678", "dni": "12345678A", "birth_date": "15/03/1985", "address": "Calle Mayor 12", "city": "Madrid", "zip": "28001", "type": "FAMILIAR", "balance": "12.50 €", "is_unreg": false, "b_paid": 0 } ``` --- ### `POST /people/qr` 🔒 Genera un código QR temporal (válido 15 minutos) para el abonado. **Body** | Campo | Tipo | Descripción | |--------|---------|--------------------------------------| | family | integer | (opcional) Número de familiar de la unidad | **Respuesta 200** ```json { "m": "00042", "f": "01", "code": "ABCDEF123456" } ``` --- ### `GET /people/card` 🔒 Devuelve el carnet digital del abonado en base64. **Respuesta 200** ```json { "card": "iVBORw0KGgoAAAANSUhEUgAA...", "me": 1 } ``` > `card` es una imagen PNG codificada en base64. Si no existe, devuelve `""`. --- ### `GET /people` 🔒 Devuelve todos los miembros de la unidad familiar del abonado autenticado. **Respuesta 200** ```json { "people": [ { "member": "00042", "family": 1, "first_name": "María", "last_name": "García López", "email": "maria@example.com", "phone": "612345678", "is_unreg": false }, { "member": "00042", "family": 2, "first_name": "Carlos", "last_name": "García López", "email": "carlos@example.com", "phone": "623456789", "is_unreg": false } ], "me": 1 } ``` > `me` indica el número de familiar del usuario autenticado dentro del array. --- ## 3. Activities (actividades) ### `GET /activities` 🔒 Devuelve el árbol completo de actividades/cursos disponibles. **Respuesta 200** ```json [ { "id": "001", "name": "PÁDEL", "levels": [ { "id": "00101", "name": "Iniciación", "groups": [ { "id": "0010101", "name": "Lunes 18h", "monitor": "Carlos", "place": "Pista 1", "hour": "L 18:00-19:00", "price": "25.00" }, { "id": "0010102", "name": "Miércoles 19h", "monitor": "Carlos", "place": "Pista 2", "hour": "X 19:00-20:00", "price": "25.00" } ] }, { "id": "00102", "name": "Avanzado", "groups": [ { "id": "0010201", "name": "Martes 20h", "monitor": "Ana", "place": "Pista 3", "hour": "M 20:00-21:00", "price": "30.00" } ] } ] }, { "id": "002", "name": "NATACIÓN", "levels": [ { "id": "00201", "name": "Adultos", "groups": [ { "id": "0020101", "name": "Lunes y Miércoles", "monitor": "María", "place": "Piscina", "hour": "L-X 07:30-08:30", "price": "35.00" } ] } ] } ] ``` --- ### `POST /activities` 🔒 Preinscribe a un miembro de la familia en una actividad. **Body** | Campo | Tipo | Descripción | |----------------|---------|--------------------------------------------| | activity | string | Código de actividad (7 dígitos, ej. `0010101`) | | family | integer | Número de familiar | | date_reg | string | Fecha de alta `dd/mm/yyyy` (opcional) | | test_run | string | `permanent` o `test` | | comment | string | Comentario / cuestionario de salud | | privacy_policy | boolean | Aceptación política de privacidad | | privacy_sports | boolean | Aceptación cláusula deportiva | **Respuesta 200 — éxito** ```json { "msg": "Su preinscripción está siendo procesada, cuando sea aceptado/a en el grupo le llegará un mensaje de confirmación.", "error": "0" } ``` **Respuesta 200 — error** ```json { "msg": "El abonado/a ya está inscrito en la actividad", "error": "3" } ``` > Códigos de error: `1` actividad no permitida · `2` ya en lista de espera · `3` ya inscrito · `4` error al guardar. --- ### `GET /activities/unreg` 🔒 Devuelve las actividades e inscripciones de lista de espera de todos los miembros de la familia para solicitar bajas. **Respuesta 200** ```json [ { "fk_i_member_id": "00042", "i_family": 1, "s_first_name": "María", "s_last_name": "García", "courses": [ { "i_activity": "0010101", "s_name": "Pádel Iniciación - Lunes 18h", "d_reg_date": "01/09/2025" }, { "i_activity": "0030101", "s_name": "Fitness Mañanas", "d_reg_date": "15/09/2025" } ], "waitlist": [ { "i_activity": "0020101", "s_name": "Natación Adultos - L-X", "dt_date": "10/10/2025" } ] } ] ``` --- ### `POST /activities/unreg` 🔒 Solicita la baja de actividades e inscripciones de lista de espera. **Body** | Campo | Tipo | Descripción | |---------|--------|---------------------------------------------------------------| | acs | object | Mapa `"{family}-{activity}": 1` de actividades a dar de baja | | wls | object | Mapa `"{family}-{activity}": 1` de listas de espera | | comment | string | Motivo / comentario | | reason | string | Razón de baja | | deftemp | string | `definitiva` o `temporal` | **Respuesta 200** ```json { "msg": "Su solicitud de baja ha sido procesada. Muchas gracias.", "error": "0" } ``` --- ## 4. Booking (reservas — app) ### `GET /booking` 🔒 Devuelve la rejilla de disponibilidad de pistas para una actividad y fecha. **Query params** | Param | Tipo | Ejemplo | |----------|--------|----------------| | actividad | integer | `1` | | fecha | string | `18/03/2026` | **Respuesta 200** ```json { "columns": ["Horarios", "01", "02", "03", "04"], "data": [ [ { "row": 0, "hour_id": 1, "text": "0900-1000", "type": "H" }, { "id": "", "paid": "", "hourText": "0900-1000", "courtText": "01", "hour": 1, "court": 0, "type": "E", "text": "" }, { "id": 4521, "paid": 0, "hourText": "0900-1000", "courtText": "02", "hour": 1, "court": 1, "type": "R", "text": "R" }, { "id": "", "paid": "", "hourText": "0900-1000", "courtText": "03", "hour": 1, "court": 2, "type": "E", "text": "" }, { "id": "", "paid": "", "hourText": "0900-1000", "courtText": "04", "hour": 1, "court": 3, "type": "E", "text": "" } ] ], "ac": { "pk_i_activity_id": 1, "s_name": "PÁDEL MURO", "i_courts": 4 }, "light": "1.00", "extra": "1.50", "amount": "6.00" } ``` > Tipos de celda: `H` cabecera horario · `E` disponible · `R` ocupada (otro) · `I` tuya sin pagar · `P` tuya pagada. --- ### `GET /booking/book/{id}` 🔒 Devuelve el detalle de una reserva concreta. **Respuesta 200** ```json { "pk_i_id": 1234, "fk_i_activity_id": 1, "d_date": "2026-03-18", "fk_i_court_id": 2, "fk_i_hour": 3, "s_hour": "1100-1200", "fk_i_member_id": 42, "fk_i_family": 1, "b_paid": 0, "s_source": "APP" } ``` --- ### `GET /booking/checkout/{id}` 🔒 Devuelve los datos de pago para una reserva. **Respuesta 200** ```json { "booking_id": 1234, "amount": "6.00", "status": "pending", "payment_url": "https://example.com/checkout/1234" } ``` --- ### `POST /booking` 🔒 Realiza una reserva de pista. **Body** | Campo | Tipo | Descripción | |-------------------|---------|------------------------------| | fk_i_activity_id | integer | ID de actividad | | hour | integer | ID de hora | | d_date | string | Fecha `dd/mm/yyyy` | | court | integer | Número de pista (base 0) | | light | boolean | Solicitar luz | **Respuesta 200** ```json { "msg": "Reserva realizada correctamente", "error": "0", "id": 5678 } ``` --- ### `POST /booking/cancel` 🔒 Cancela una reserva existente. **Body** | Campo | Tipo | |-------|---------| | id | integer | **Respuesta 200** ```json { "msg": "Reserva cancelada correctamente", "error": "0" } ``` --- ### `POST /booking/edit` 🔒 Edita una reserva existente. **Body** | Campo | Tipo | Descripción | |-------|---------|-------------| | id | integer | ID de reserva | | light | boolean | Activar/desactivar luz | **Respuesta 200** ```json { "msg": "Reserva editada correctamente", "error": "0" } ``` --- ### `POST /booking/refresh` 🔒 Refresca el estado de una reserva (actualiza pagos pendientes). **Respuesta 200** ```json { "msg": "ok", "error": "0" } ``` --- ## 5. Payments ### `GET /payments` 🔒 Devuelve el historial de pagos del abonado. **Respuesta 200** ```json { "payments": [ { "id": 1, "description": "Cuota mensual", "amount": "35.00", "date": "01/03/2026", "status": "Pagado", "type": "REMESA" }, { "id": 2, "description": "Actividad pádel", "amount": "25.00", "date": "15/02/2026", "status": "Pagado", "type": "CAJA" } ], "error": "0" } ``` --- ## 6. Wallet (monedero) ### `GET /wallet` 🔒 Devuelve el saldo y el historial de transacciones del monedero. **Respuesta 200** ```json { "transactions": [ { "id": 1, "concept": "Recarga monedero", "amount": "20.00", "date": "10/03/2026 09:15", "balance": "20.00" }, { "id": 2, "concept": "Pago reserva pádel", "amount": "-6.00", "date": "12/03/2026 18:32", "balance": "14.00" } ], "balance": "14.00 €", "error": "0" } ``` --- ## 7. Search (búsqueda) ### `GET /search` 🔒 Búsqueda rápida de socios por nombre, apellido, número o DNI. **Query params** | Param | Tipo | Descripción | |-------|--------|-------------------| | q | string | Término de búsqueda | **Respuesta 200** ```json [ { "id": "0004201", "member": "00042", "family": "01", "name": "García, María", "phone": "612345678" }, { "id": "0012301", "member": "00123", "family": "01", "name": "García, Antonio", "phone": "623456789" } ] ``` --- ### `GET /search/new` 🔒 Búsqueda avanzada con filtros de tipo y estado. **Query params** | Param | Tipo | Valores | |--------|--------|-----------------------------| | q | string | Término | | type | string | `all` · `FAMILIAR` · `INDIVIDUAL` · `SABIC` | | status | string | `all` · `active` · `inactive` | **Respuesta 200** ```json { "results": [ { "id": "0004201", "member": "00042", "family": "01", "name": "García, María", "phone": "612345678", "email": "maria@example.com", "dni": "12345678A", "type": "FAMILIAR", "is_unreg": false, "unreg_reason": "", "b_paid": 0 } ], "total": 1 } ``` --- ### `GET /search/doorlog` 🔒 Devuelve el log de accesos por torniquete de un abonado. **Query params** | Param | Tipo | Descripción | |--------|---------|----------------------------| | member | string | Número de socio (5 dígitos)| | family | integer | Número de familiar (def. 1)| | limit | integer | Máx. registros (def. 100) | **Respuesta 200** ```json [ { "pk_i_id": 1, "fk_i_member_id": "00042", "fk_i_people_id": 1, "dt_date": "2026-03-15 09:23:11", "s_source": "TORNIQUETE" }, { "pk_i_id": 2, "fk_i_member_id": "00042", "fk_i_people_id": 1, "dt_date": "2026-03-14 18:45:02", "s_source": "APP" } ] ``` --- ## 8. Members (socios — admin) > Todos los endpoints de esta sección requieren autenticación 🔒. El objeto **member** tiene la siguiente estructura completa: ```json { "pk_i_id": 42, "d_reg_date": "2018-03-15", "s_address": "Calle Mayor 12", "s_city": "Madrid", "s_zip": "28001", "s_email": "socio@example.com", "s_phone": "612345678", "s_bank": "0049", "s_bank_office": "1500", "s_bank_sec_code": "42", "s_bank_account": "0123456789", "s_bank_name": "Santander", "s_mandate": "MNDT-00042", "s_type": "FAMILIAR", "s_comment": "", "s_comment2": "", "b_paid": 0, "b_rem_special": 0, "b_retenido": 0, "d_unreg_date": null, "s_unreg_reason": "", "people": [ { "fk_i_member_id": 42, "i_family": 1, "s_first_name": "María", "s_last_name": "García", "s_dni": "12345678A", "d_birth_date": "1985-03-15", "s_phone": "612345678", "s_email": "maria@example.com", "b_lopd": 1, "b_newsletter": 1, "b_authorized": 1, "b_authorized2": 0, "s_photo_path": null } ], "cautions": [], "comments": [] } ``` --- ### `GET /members/quick` — Búsqueda rápida por número **Query:** `fk_i_member_id=42` · `i_family=1` (opcional) **Respuesta:** objeto `member` completo --- ### `GET /members/first` — Primer socio ### `GET /members/last` — Último socio ### `GET /members/{id}` — Socio por ID ### `GET /members/{id}/prev` — Socio anterior ### `GET /members/{id}/next` — Socio siguiente Todas devuelven el objeto `member` completo. --- ### `GET /members/search` — Autocompletar socios **Query:** `term=garcia` **Respuesta 200** ```json { "0004201": "(00042-01) García, María", "0004202": "(00042-02) García, Carlos" } ``` --- ### `POST /members/edit` **Body:** campos del objeto member a actualizar (`pk_i_id` requerido). **Respuesta 200** ```json { "msg": "Socio editado correctamente", "member": { "...": "objeto member completo" } } ``` --- ### `POST /members/paid-caution` — Toggle flag recibos pendientes **Body:** `pk_i_id` **Respuesta:** objeto `member` actualizado --- ### `POST /members/recover` — Reactivar socio dado de baja **Body:** `pk_i_id` **Respuesta:** objeto `member` actualizado --- ### `POST /members/pre-delete` — Pre-verificación antes de dar de baja **Body:** `pk_i_id` **Respuesta 200** ```json { "pendientes": 0, "devueltos": 2, "revision": 0 } ``` --- ### `POST /members/delete` — Dar de baja a un socio **Body:** `pk_i_id` · `d_unreg_date` · `s_unreg_reason` **Respuesta:** objeto `member` actualizado --- ### `POST /members/change-type` — Cambiar tipo de socio **Body:** `id` · `s_type` **Respuesta 200:** `{ "error": 0, "msg": "ok" }` --- ### `POST /members/rem-special` — Toggle remesa especial **Body:** `pk_i_id` · `b_rem_special` **Respuesta:** objeto `member` con campo `error` --- ### `POST /members/retener` / `POST /members/retener-cancel` Marca/desmarca al socio como retenido. **Body:** `member` **Respuesta:** `{ "msg": "...", "member": { ... } }` --- ### `POST /members/promotion` — Registrar promoción **Body:** `pk_i_id` · `friend` **Respuesta:** `{ "msg": "Promoción añadida correctamente", "member": { ... } }` --- ### `POST /members/edit-comment` — Editar comentario interno **Body:** `pk_i_id` · `s_comment2` **Respuesta:** `{ "msg": "...", "member": { ... } }` --- ### `POST /members/comment/add` — Añadir comentario **Body:** `member` · `comment` **Respuesta:** `{ "msg": "...", "member": { ... } }` --- ### `GET /members/comment/edit` — Obtener comentario para editar **Query:** `id` **Respuesta:** `{ "comment": { "id": 1, "comment": "texto", "created_at": "..." } }` --- ### `POST /members/comment/edit-save` — Guardar edición de comentario **Body:** `id` · `comment` · `pk_i_id` **Respuesta:** `{ "msg": "...", "member": { ... } }` --- ### `POST /members/comment/delete` — Eliminar comentario **Body:** `id` (`"old"` para el comentario heredado) · `member_id` **Respuesta:** `{ "msg": "Comentario borrado", "member": { ... } }` --- ### Familiares (people) | Método | Ruta | Descripción | |--------|------|-------------| | `POST` | `/members/people/add` | Añadir familiar | | `POST` | `/members/people/edit` | Editar familiar | | `POST` | `/members/people/delete` | Eliminar familiar | | `POST` | `/members/people/auth` | Toggle autorización acceso | | `POST` | `/members/people/auth2` | Toggle autorización 2 | | `POST` | `/members/people/picture/upload` | Subir foto (multipart `webcam`) | | `POST` | `/members/people/picture/delete` | Eliminar foto | | `GET` | `/members/people/card` | Tarjetas de acceso | | `POST` | `/members/people/card/add` | Añadir tarjeta | | `POST` | `/members/people/card/delete` | Eliminar tarjeta | | `POST` | `/members/people/password` | Generar enlace de reset de contraseña | | `GET` | `/members/people/card/preview` | Previsualizar carnet | | `POST` | `/members/people/card/print` | Imprimir carnet | | `POST` | `/members/people/minors-card` | Actualizar visibilidad carnets menores | Todas las respuestas de escritura devuelven `{ "msg": "...", "member": { ... } }` o `{ "error": 0, "msg": "..." }`. **`GET /members/people/card`** devuelve: ```json { "cards": [ { "pk_i_id": 1, "fk_i_member_id": 42, "i_family": 1, "s_card": "ABCD1234EFGH5678" } ] } ``` **`POST /members/people/password`** devuelve: ```json { "h": "https://app.example.com/password/reset/TOKEN?email=socio@example.com" } ``` --- ## 9. Courses (cursos — admin) ### `GET /courses/levels` 🔒 **Query:** `course` (código de actividad) **Respuesta 200** ```json [ { "i_code1": 1, "i_code2": 1, "i_code3": 0, "s_name": "Iniciación" }, { "i_code1": 1, "i_code2": 2, "i_code3": 0, "s_name": "Avanzado" } ] ``` --- ### `GET /courses/groups` 🔒 **Query:** `course` · `level` **Respuesta 200** ```json [ { "i_code1": 1, "i_code2": 1, "i_code3": 1, "s_name": "Lunes 18h-19h", "f_price": "25.00", "i_max_students": 15, "s_hour": "L 18:00-19:00", "s_monitor": "Carlos", "s_place": "Pista 1", "i_one_bill": 0, "b_fitness": 0, "b_pending_count": 0, "s_comment": "" } ] ``` --- ### `GET /courses/group` 🔒 **Query:** `course` · `level` · `group` **Respuesta:** objeto grupo (igual que el array anterior, un único elemento) --- ### `GET /courses/data-alumni` 🔒 Actividades en las que está inscrito un abonado. **Query:** `code` (7 dígitos: `{member}{family}`) **Respuesta 200** ```json [ { "i_activity": "0010101", "s_name": "Pádel Iniciación L18h" }, { "i_activity": "0030101", "s_name": "Fitness Mañanas" } ] ``` --- ### `GET /courses/alumnis` 🔒 Lista de alumnos de un grupo. **Query:** `course` · `level` · `group` · `month` (opcional) · `year` (opcional) **Respuesta 200** ```json [ { "pk_i_id": 1234, "fk_i_member_id": "00042", "fk_i_family": "01", "i_activity": "0010101", "s_last_name": "García", "s_first_name": "María", "d_reg_date": "01/09/2025", "f_price": "25.00", "mod_price": "25.00", "ac_price": "25.00", "s_type": "REMESA", "s_comment": "", "b_authorized": 1, "b_authorized2": 0, "i_door_log_count": 0 } ] ``` --- ### `GET /courses/waitlist` 🔒 **Query:** `course` · `level` · `group` **Respuesta:** igual que `/courses/alumnis` con campo adicional `dt_date`. --- ### `GET /courses/alumni/get-reg-comment` 🔒 **Query:** `m` · `f` · `a` **Respuesta 200** ```json { "fk_i_member_id": "00042", "fk_i_family": "01", "i_activity": "0010101", "s_comment": "Interesada en el grupo de tarde si hay plaza." } ``` --- ### `GET /courses/alumni/get-unreg-comment` 🔒 **Query:** `i` (ID del registro en unreg_requests) **Respuesta 200** ```json { "id": 5, "comment": "Se marcha al extranjero temporalmente." } ``` --- ### Escritura de cursos | Método | Ruta | Body principal | Respuesta | |--------|------|----------------|-----------| | `POST` | `/courses/add` | `s_name` | Nuevo `i_code1` | | `POST` | `/courses/edit` | `course` · `s_name` | `"ok"` | | `POST` | `/courses/delete` | `course` | `{ "error": 0, "msg": "..." }` | | `POST` | `/courses/level/add` | `course` · `s_name` | Nuevo `i_code2` | | `POST` | `/courses/level/edit` | `course` · `level` · `s_name` | `"ok"` | | `POST` | `/courses/level/delete` | `course` · `level` | `{ "error": 0, "msg": "..." }` | | `POST` | `/courses/group/add` | `course` · `level` · `s_name` · `f_price` · `i_max_students` · `s_hour` · `s_monitor` · `s_place` | `"ok"` | | `POST` | `/courses/group/edit` | `id` · `s_field` · `s_value` | `"ok"` | | `POST` | `/courses/group/delete` | `course` · `group` | `{ "error": 0, "msg": "..." }` | | `POST` | `/courses/group/comment` | `course` · `level` · `group` · `comment` | `{ "msg": "...", "error": 0 }` | | `POST` | `/courses/alumni/add` | `course` · `level` · `group` · `fk_i_member_id` · `d_date` | `{ "error": 0, "msg": "..." }` | | `POST` | `/courses/alumni/add-from-wait` | idem + `email` | `{ "error": 0, "msg": "..." }` | | `POST` | `/courses/alumni/delete` | `course` · `level` · `group` · `fk_i_member_id` · `fk_i_family` | `{ "error": 0, "msg": "..." }` | | `POST` | `/courses/alumni/delete-by-id` | `id` | `{ "error": 0, "msg": "..." }` | | `POST` | `/courses/alumni/delete-by-ids` | `ids` (CSV) | `{ "error": 0, "msg": "..." }` | | `POST` | `/courses/alumni/delete-wait` | `course` · `level` · `group` · `fk_i_member_id` · `fk_i_family` | `{ "error": 0, "msg": "..." }` | | `POST` | `/courses/alumni/empty` | `course` · `level` · `group` | `{ "error": 0, "msg": "..." }` | | `POST` | `/courses/alumni/import` | `ocourse` · `olevel` · `ogroup` · `dcourse` · `dlevel` · `dgroup` · `date` | `{ "error": 0, "msg": "..." }` | | `POST` | `/courses/alumni/auth` | `id` · `value` | `{ "error": 0, "msg": "..." }` | | `POST` | `/courses/alumni/auth2` | `id` · `value` | `{ "error": 0, "msg": "..." }` | | `POST` | `/courses/alumni/reg-comment` | `m` · `f` · `a` · `s_comment` | `{ "error": 0, "msg": "..." }` | | `POST` | `/courses/alumni/unreg-comment` | `id` · `s_comment` | `{ "error": 0, "msg": "..." }` | --- ## 10. Bills (recibos — admin) ### `GET /bills/member-search` 🔒 **Query:** `fk_i_member_id` **Respuesta 200** — array de recibos: ```json [ { "alumni": 1234, "member": "00042", "family": "01", "name": "García, María", "ac_name": "Pádel Iniciación L18h", "activity": "0010101", "price": "25.00", "user_price": "25.00", "bill_price": "25.00", "month": 3, "year": 2026, "month_name": "marzo", "status": 0, "type": "REMESA", "bonus": 0, "cash_date": "" } ] ``` > Estado (`status`): `0` pendiente · `1` pagado · `2` eliminado · `4` devuelto. --- ### `GET /bills/alumni` 🔒 **Query:** `id` (ID de alumno) **Respuesta 200** ```json { "pk_i_id": 1234, "fk_i_member_id": "00042", "fk_i_family": "01", "i_activity": "0010101", "f_price": "25.00", "s_type": "REMESA", "s_comment": "", "person": { "s_first_name": "María", "s_last_name": "García" }, "next_bills": [ { "i_month": 4, "i_year": 2026, "i_status": 0, "f_price": "25.00" }, { "i_month": 5, "i_year": 2026, "i_status": 0, "f_price": "25.00" } ] } ``` --- ### `GET /bills/from-alumni` 🔒 **Query:** `id` **Respuesta 200** ```json { "pk_i_id": 1234, "i_activity": "0010101", "person": { "s_first_name": "María", "s_last_name": "García" }, "bills": [ { "i_month": 1, "i_year": 2026, "i_status": 1, "f_price": "25.00", "year": 0 }, { "i_month": 2, "i_year": 2026, "i_status": 1, "f_price": "25.00", "year": 0 }, { "i_month": 3, "i_year": 2026, "i_status": 0, "f_price": "25.00", "year": 0 } ] } ``` --- ### `GET /bills/number` 🔒 **Query:** `course` · `level` · `group` · `month` · `year` · `type` (`BANK`/`ALL`) · `alumni[]` (opcional) **Respuesta 200** ```json 12 ``` --- ### `GET /bills/pending` 🔒 **Query:** `id` (member) · `complete` (0/1) **Respuesta 200** — array de recibos pendientes (misma estructura que `/bills/member-search`) --- ### `GET /bills/search` 🔒 **Query:** `fk_i_member_id` · `i_month` · `status` · `ext` **Respuesta 200** ```json { "iTotalRecords": 5, "iTotalDisplayRecords": 5, "aaData": [ { "...": "objeto recibo" } ] } ``` --- ### `GET /bills/remittance-return/member` 🔒 **Query:** `member` **Respuesta 200** — mapa de miembros con recibos devueltos: ```json { "42": { "pk_i_id": 42, "people": [ { "...": "..." } ], "months": { "3": 1 }, "bills": [ { "...": "..." } ], "i_total": "25.00" } } ``` --- ### Escritura de recibos | Método | Ruta | Body | Respuesta | |--------|------|------|-----------| | `POST` | `/bills/alumni/edit` | `id` · `price` · `type` · `comment` | `{ "error": 0, "msg": "..." }` | | `POST` | `/bills/generate` | `id` · `month` · `year` | `{ "error": 0, "msg": "Recibo creado" }` | | `POST` | `/bills/generate-next` | `course` · `level` · `group` · `month` · `year` | `{ "msg": "Recibos generados", "error": 0 }` | | `POST` | `/bills/generate-multi` | `activity` · `id` · `months[]` | `{ "iTotalRecords": N, "aaData": [...] }` | | `POST` | `/bills/generate-remittance` | `course` · `level` · `group` · `month` · `year` | `{ "error": 0, "msg": "...", "paid_bills": 12 }` | | `POST` | `/bills/ticket` | `bills[]` | `{ "error": 0, "msg": "...", "ticket_number": "2025/000123", "lines": [] }` | | `POST` | `/bills/cancel` | `acs[]` | `{ "error": 0, "msg": "OK", "lines": [] }` | | `POST` | `/bills/delete` | `acs[]` | `{ "error": 0, "msg": "OK" }` | | `POST` | `/bills/delete-alumni` | `ids` (CSV) · `month` · `year` | `{ "error": 0, "msg": "OK" }` | | `POST` | `/bills/recover` | `acs[]` | `{ "error": 0, "msg": "OK" }` | | `POST` | `/bills/remittance-return/add` | `member` · `month` · `year` | `{ "error": 0, "msg": "OK" }` | | `POST` | `/bills/remittance-return/delete` | `alumni` · `month` · `year` · `id` · `special` | `{ "error": 0, "msg": "..." }` | | `POST` | `/bills/remittance-return/print` | `id` | `{ "msg": "...", "data": { "id": 1 }, "lines": [] }` | --- ## 11. Booking Admin (reservas — admin) ### `GET /booking-admin/grid` 🔒 **Query:** `fk_i_activity_id` · `d_date` (`dd/mm/yyyy`) **Respuesta 200** ```json { "aaData": [ ["