Arquitectura e integración
Yrix Park está partido en procesos con un dueño claro: el borde lee, la API guarda y autoriza, el panel y la app operan. Esta página es el mapa para quien integra. No trae hosts, contraseñas, cadenas de conexión ni datos de una playa.
Los patrones de código (CQRS, repositorios, migraciones) y el flujo de permisos están en content/docs/arquitectura y content/docs/api del repositorio. Acá alcanza con saber qué pieza habla con cuál.
Piezas
| Pieza | Tecnología | Responsabilidad |
|---|---|---|
ALPRParkingEngine | Python, en el equipo de borde | Lee la patente en el predio. La referencia de hardware es una Jetson. Yrix no la vende. |
Parking.API | .NET 8 | HTTP, MQTT, tiempo real y composición del proceso. |
Parking.Domain | .NET | Entidades. |
Parking.Application | .NET, MediatR | Comandos y consultas. |
Parking.Infrastructure | EF Core, SignalR, handlers MQTT | Persistencia, DetectionMqttHandler y HeartbeatMqttHandler. |
Parking.Identity | OpenIddict | Autenticación y roles. |
Parking.Migrator | .NET | Migraciones por tenant. No corren solas al arrancar. |
Parking.Worker | .NET | Facturación automática en segundo plano. La config es BillingRunner:*. |
Parking.FrontEnd | Angular | Panel de la playa. |
Parking.Mobile | Flutter, Android | Operación de Cabina y Atención. |
Parking.Web | Next.js | Este sitio y esta documentación. No cobra ni abre la barrera. |
Recorrido de una detección
Cámara → borde (ALPR) → MQTT → Parking.API → PostgreSQL
Parking.API → SignalR → panel Angular y app- La cámara entrega video al equipo de borde. El motor publica la detección y el heartbeat por MQTT. Los topics están en
docs/architecture/jetson-resilience.mddel repo Parking.API. DetectionMqttHandlerpersiste la detección.HeartbeatMqttHandlerregistra el heartbeat.- La API avisa al grupo SignalR del tenant. El evento de una lectura nueva es
NewDetection. - El panel y la app actualizan detecciones. La ocupación se entera por
OccupancyChanged, no por el heartbeat de la cámara.
El mismo canal lleva DeviceStatusChanged, NewNotification y NewParkingIncident. El hub es /hubs/parking, con el JWT del operador. El transporte documentado es WebSocket. En el despliegue, Nginx queda delante del API.
Si el predio se queda sin red, el controlador retiene las lecturas y las publica por MQTT al reconectar. La API no se entera de un cuadro que todavía no salió del predio.
Multi-tenant
Cada estacionamiento es un tenant:
- Tiene su propia base PostgreSQL.
TenantMiddlewareleetenant_iddel JWT.- SignalR aísla los eventos en el grupo de ese tenant.
- Las migraciones de negocio las aplica
Parking.Migrator, tenant por tenant.
Un integrador no elige la base en el request. La elige el token.
Cómo se integra
Tres bordes, y ninguno pide una IP publicada en esta guía:
| Borde | Uso |
|---|---|
REST api/v1/... | Consultas y comandos. La respuesta de negocio envuelve el resultado. Un error de regla (punto de venta repetido, por ejemplo) vuelve como error de negocio, no como un 200 vacío. |
| OpenIddict | Usuario y contraseña. El JWT trae los permisos de los roles. [RequirePermission("Recurso.Acción")] rechaza con 403 si falta el claim. |
SignalR /hubs/parking | Push al operador que ya tiene sesión. No reemplaza al REST: avisa que hay que volver a leer. |
La app móvil y el panel usan el mismo usuario de operador y el mismo hub. La app, en la versión documentada, no usa PKCE: entra con usuario y contraseña y conserva un refresh token.
El borde no se integra por HTTP público desde la cámara. La cámara habla RTSP con el equipo de la playa. Ese equipo habla con la API por MQTT. No hace falta abrir el video a internet para que el panel vea la patente.
Qué queda afuera de esta página
- Direcciones de servidores, nombres de entorno y secretos.
- Credenciales de MQTT, del proveedor fiscal y de Mercado Pago.
- Patentes, fotos o importes de un cliente.
- Un porcentaje de precisión del ALPR. La confianza es por detección, como explica Lectura de patentes.
El circuito de caja, comprobante y roles está en Gestión del estacionamiento.