Diagramas de Ingeniería

Los diagramas de esta página reflejan la arquitectura v0.4.x. El detalle tabular de entidades está en Modelo de Datos; el despliegue operativo en el manual de integración (deployment).

Arquitectura General

graph TB subgraph Presentation["Capa de presentación (Angular)"] A1[App :6100] A2[Panel :6101] A3[Trainer :6102] A4[Sudo :6103] A5[Accounting :6104] end subgraph Application["Capa de aplicación (monorepo functions)"] B1[Express.js API :6000] B2[Auth OAuth2 / JWT] B3[Routes admin account trainer sudo accounting] B4[Relay Socket.io] end subgraph Data["Datos"] D1[(PostgreSQL unosportclub)] D2[(PostgreSQL accounting)] end subgraph External["Integraciones"] E1[Payment gateway webhooks] E2[OAuth2 local IdP] end A1 --> B1 A2 --> B1 A3 --> B1 A4 --> B1 A5 --> B1 B1 --> B2 B2 --> B3 B3 --> D1 B3 --> D2 B3 --> E2 B3 --> B4 B4 --> A1 B4 --> A2 B4 --> A3 B4 --> A4 B3 --> E1 style A1 fill:#e1f5ff style A2 fill:#e1f5ff style A3 fill:#e1f5ff style A4 fill:#e1f5ff style A5 fill:#e1f5ff style B1 fill:#fff4e1 style D1 fill:#ffe1f5 style D2 fill:#ffe1f5

Nota histórica: versiones anteriores desplegaban la API en Firebase Functions; el runtime principal en v0.4.x es Express en imagen Docker unificada (ver xref:diagrams.adoc#_arquitectura_de_despliegue).

Flujos del Sistema

Flujo de reserva (operador / panel)

Alineado con xref:workflows.adoc#_flujo_de_reserva_por_operador y ruta panel /booking.

sequenceDiagram participant O as Operador participant P as Panel participant API as Express API participant DB as PostgreSQL participant R as Relay O->>P: Selecciona cancha y horario mural P->>API: POST /admin/booking API->>DB: validateBookingSlot + INSERT reservation DB-->>API: reservation id API->>R: booking:changed API-->>P: Reserva pendiente O->>P: Registra pago (paso /booking/payment/:id) P->>API: POST /admin/booking/{id}/payment API->>DB: INSERT payment (+ foto si multipart) R-->>P: Actualización en tiempo real

Flujo cuenta: el cliente puede reservar vía app (/account/*); los estados y la hora mural siguen la misma tabla reservation.

Comprobante de pago en reserva

Subida al crear el pago (POST /admin/booking/{bookingId}/payment) o actualizar uno existente (PATCH …​/payment/{paymentId} en multipart con campo foto). El bucket S3/R2 permanece privado; la URL en payment.foto es referencia interna para el proxy de descarga.

Migración: functions/db/migrations/089_payment_foto.sql (payment.foto TEXT).

flowchart TD subgraph POST["POST /admin/booking/:bookingId/payment"] JSON["Content-Type: application/json"] --> NF["foto = null en INSERT"] MP["Content-Type: multipart/form-data"] --> M["multer: campo foto obligatorio"] M --> UP["PaymentReceiptStorage.upload → PutObject S3/R2"] UP --> URL["URL path-style en payment.foto"] URL --> INS["INSERT payment"] NF --> INS end subgraph PATCH["PATCH /admin/booking/:bookingId/payment/:paymentId"] MP2["multipart/form-data + foto"] --> UP2["PaymentReceiptStorage.upload"] UP2 --> UPD["UPDATE payment.foto"] end style POST fill:#f0f7ff style PATCH fill:#f0fff4 style UP fill:#fff4e1 style UP2 fill:#fff4e1

Límites: JPEG, PNG o WebP; máximo 5 MB. En POST JSON sin archivo conserva compatibilidad con integraciones que no envían comprobante. Si el Content-Type es multipart/form-data, el campo foto es obligatorio (POST al crear; PATCH para añadir o reemplazar comprobante).

Variables de entorno: ver Almacenamiento de comprobantes.

Visualización privada del comprobante

El panel no expone la URL de R2 al navegador. El operador abre el comprobante desde /booking/payment/:id con el botón Ver (si payment.foto está presente), lo sube con el botón verde (Subir) o lo quita desde el modal con el botón rojo (Eliminar comprobante).

sequenceDiagram participant O as Operador participant P as Panel payment-form participant API as Express API participant S3 as S3/R2 privado O->>P: Clic Ver comprobante P->>API: GET /admin/booking/{bookingId}/payment/{paymentId}/foto Note over P,API: Bearer + X-XSRF-TOKEN API->>API: Verifica pago pertenece a la reserva API->>S3: GetObject (PaymentReceiptStorage.download) S3-->>API: Stream de bytes API-->>P: Content-Type imagen, Cache-Control private no-store P->>P: blob → object URL → modal Comprobante de pago O->>P: Clic Eliminar comprobante P->>API: DELETE .../payment/{paymentId}/foto API->>S3: DeleteObject (PaymentReceiptStorage.delete) API->>API: payment.foto = null API-->>P: 204 P->>P: Cierra modal; fila muestra botón Subir Al eliminar el pago completo (`DELETE .../payment/\{paymentId\}`), la API borra el comprobante en S3/R2 si `payment.foto` tiene valor y luego elimina la fila en BD.

Flujo creación de evento (panel PMV)

sequenceDiagram participant O as Operador participant P as Panel /events participant API as Express API participant DB as PostgreSQL O->>P: Wizard disponibilidad + datos P->>API: POST /admin/events API->>DB: INSERT reservation tipo Evento API->>DB: INSERT event API-->>P: EventInterface + participants [] O->>P: Asigna casillas con tickets P->>API: POST /admin/events/:id/participants API->>DB: UPDATE ticket used + INSERT event_participant API-->>P: Participante creado

Ver xref:events-tournaments-module.adoc y xref:workflows.adoc#_flujo_de_creación_de_evento_operador.

Modelo de Datos

Diagrama ER resumido coherente con xref:data-model.adoc. No incluye todas las tablas del sistema.

erDiagram USER ||--|| CLIENT : user_id USER ||--o{ RESERVATION : operator_id CLIENT |o--o{ RESERVATION : client_id COURT ||--o{ RESERVATION : court_id RESERVATION_TYPE ||--o{ RESERVATION : tipo RESERVATION |o--|| EVENT : reservation_id EVENT_TYPE ||--o{ EVENT : event_type_id EVENT ||--o{ EVENT_PARTICIPANT : casillas USER ||--o{ EVENT_PARTICIPANT : client_id TICKET |o--|| EVENT_PARTICIPANT : ticket_id STRIP ||--o{ TICKET : strip_id RESERVATION |o--o{ PAYMENT : reservation_id USER { int id PK string local_id string email string role } RESERVATION { int id PK int court_id FK datetime checking datetime checkout int reservation_type_id FK } EVENT { int id PK int reservation_id FK int event_type_id FK string title int capacity string status } EVENT_PARTICIPANT { int id PK int event_id FK int client_id FK int ticket_id FK int slot } TICKET { int id PK int strip_id FK string status }

Arquitectura de Despliegue

graph TB subgraph Internet U[Usuarios] end subgraph Edge["Reverse proxy / Caddy"] H1[app host] H2[panel host] H3[trainer host] H4[sudo host] H5[accounting host] end subgraph Container["Imagen Docker GHCR"] N[nginx estáticos + proxy /api] API[Express + Socket.io] end subgraph Database PG1[(PostgreSQL principal)] PG2[(PostgreSQL accounting)] end U --> H1 U --> H2 U --> H3 U --> H4 U --> H5 H1 --> N H2 --> N H3 --> N H4 --> N H5 --> N N --> API API --> PG1 API --> PG2 style N fill:#fff4e1 style API fill:#fff4e1 style PG1 fill:#ffe1f5 style PG2 fill:#ffe1f5

Tags CI: rama N.M publica imagen staging; tag vX.Y.Z dispara build + release a GHCR.

Sistema Relay

Notificaciones en tiempo real vía Socket.io. El subgraph DomainEvents son mensajes de dominio (reservas, pagos); no es el módulo «Eventos» del panel (event / event_participant).

graph TB subgraph Backend B1[Express Server] B2[Relay Helper] B3[Socket.io Server] end subgraph DomainEvents["Mensajes de dominio"] E1[booking:changed] E2[payment:updated] E3[availability:updated] end subgraph Destinations D1[yo usuario] D2[room canal] D3[nosotros broadcast] end subgraph Clients F1[App] F2[Panel] F3[Trainer] F4[Accounting] end B1 --> E1 B1 --> E2 B1 --> E3 E1 --> B2 E2 --> B2 E3 --> B2 B2 --> B3 B3 --> D1 B3 --> D2 B3 --> D3 D1 --> F1 D2 --> F2 D3 --> F3 D2 --> F4 style B1 fill:#fff4e1 style DomainEvents fill:#f0f0f0

Referencias

El directorio docs/milestones/ingenieria/diagramas/ conserva material histórico pre-v0.4; no sustituye esta página ni xref:data-model.adoc.