Skip to content

feat: [TESIS-139] show integration status and link products to sales channels - #59

Merged
TomasMartin2004 merged 2 commits into
masterfrom
TESIS-139-shopify-integration
Oct 3, 2026
Merged

TomasMartin2004 merged 2 commits into
masterfrom
TESIS-139-shopify-integration

Conversation

@LauAubert

@LauAubert LauAubert commented Oct 2, 2026 •

Copy link
Copy Markdown
Member

🔗 Link

https://proyectofinalfrlp.atlassian.net/browse/TESIS-139

📝 Descripción

Agrega la pantalla de Integraciones y la tarjeta Canales de venta del detalle de producto. Con la primera, la empresa ve qué proveedores tiene conectados, en qué estado y con qué cuenta. Con la segunda, vincula cada producto con su publicación en un canal (por id o por SKU) y lo desvincula, sin Postman ni backoffice.

Conectar un proveedor no es parte de la pantalla: las credenciales las carga el equipo de OneStock desde el backoffice (ADR-018 del back). La primera versión de esta rama tenía un formulario de conexión armado con los campos de la plantilla, y se sacó en el segundo commit cuando se tomó esa decisión.

Depende de TESIS-138 (proyecto-api#98) (API): GET /integrations cambia de forma y POST /products/:id/mappings busca por SKU. Mergear la API primero.

🛠️ Cambios realizados

  • Agrega shared/api/integrations.ts y shared/hooks/useIntegrations.ts. El listado de integraciones sube a shared/ por la Regla de Dos (lo leen el panel, integraciones e inventario) y se traduce al dominio en camelCase. Ya no se usan uri ni http_method
  • useInfraHealth (panel de salud de nodos) pasa a leer del hook compartido, sin cambios visibles
  • Agrega el feature features/integrations:
    • IntegrationsPage de sólo lectura, con los proveedores agrupados en Canales de venta y Operadores logísticos
    • IntegrationCard muestra el estado (Conectado / Inactivo / No conectado) y la cuenta («Cuenta: onestock-norte»)
    • Un texto aclara que para conectar o cambiar una cuenta hay que contactar al equipo
  • La ruta /integrations carga el feature nuevo y sigue detrás del flag integrations del tenant. Se borra features/dashboard/pages/IntegrationsPage.tsx
  • Agrega SalesChannelsCard en ProductDetailPage:
    • Lista los vínculos del producto
    • Vincula con un canal conectado y activo, por id de publicación o vacío para buscar por SKU
    • Desvincula con confirmación
    • Traduce los errores de la API: 409 (la publicación ya está vinculada a otro producto), 422 (no existe o el SKU es ambiguo), 502 (el canal no responde), y muestra los warnings (SKU distinto)
    • Sólo se ve con el flag integrations
  • Agrega inventory/api.ts (/products/:id/mappings), useProductMappings, utils/channels.ts y sus tipos
  • docs/design/README.md anota que las dos pantallas se hicieron sin maqueta, con componentes del DS, para la revisión de diseño

🧪 Cómo probarlo (Opcional)

Precondición: API en la rama TESIS-138-shopify-integration con bin/rails db:migrate && bin/rails db:seed, y Shopify conectado para Norte (casos 1 y 3 del PR de la API). Front con npm run dev y VITE_API_URL apuntando a esa API.

Caso 1: estado de las integraciones

  1. Entrar como admin@norte.com (contraseña de las seeds) → Integraciones.
    → Shopify aparece Conectado con «Cuenta: onestock-norte». Mercado Libre y Tiendanube aparecen Inactivo, y los couriers en su grupo.
    → No hay ningún botón para conectar, probar, activar ni desconectar.

Caso 2: vincular por SKU y desvincular

  1. Inventario → NOR-001 → Canales de venta → Vincular, elegir Shopify y dejar el id vacío.
    → Aparece la publicación vinculada, y en el admin de Shopify el stock de NOR-001 pasa a ser el del OMS.
    → Shopify ya no se ofrece como canal para ese producto: está vinculado.
  2. En NOR-010 (sin vincular), vincular con Shopify y el id de la variante de NOR-001 → «Esa publicación ya está vinculada a otro producto» (409).
  3. Vincular con un id de publicación que no existe → «El canal no tiene una publicación con ese ID» (422).
  4. Desvincular → pide confirmación y la publicación desaparece de la lista.

Caso 3: una venta de Shopify se ve en el front

Precondición: el webhook de ventas registrado y una compra hecha en la tienda (caso 4 del PR de la API).

  1. Órdenes → aparece la orden nueva de Shopify con el cliente y el total.
  2. Inventario → el producto vendido → el stock bajó en la cantidad vendida.

Caso 4: tenant sin integraciones

  1. Entrar como admin@sur.com (?tenant=sur).
    → No aparece Integraciones en el menú ni la tarjeta Canales de venta en el detalle de producto.

Checks

  • npm run lint, npm run test (683 tests) y npm run build en verde.

📸 Evidencia (Opcional)

Integraciones Canales de venta (detalle de producto)
pendiente de adjuntar pendiente de adjuntar

🤖 Generated with Claude Code

LauAubert and others added 2 commits October 2, 2026 00:07
- new integrations feature: providers grouped by type with their status,
  connect/configure form built out of the fields each template declares,
  test the connection, activate/deactivate and disconnect with confirmation
- the connect form shows the field errors of a 422 next to each input, keeps
  loaded secrets when left blank and never prefills them
- product detail gets a sales channels card: linked publications, link by
  publication id or by the product SKU, unlink with confirmation; only for
  tenants with the integrations feature
- the integrations listing moves to shared/ (dashboard, integrations and
  inventory read it) and is translated to the domain in camelCase
- the HTTP client keeps the field errors of an API rejection

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
The OneStock team now loads each company's credentials from the
backoffice, so the screen only shows which providers are connected,
their status and the connected account. The connection form, the
test/activate/disconnect actions and the per-field API errors go away.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@LauAubert
LauAubert marked this pull request as ready for review October 2, 2026 12:29
@LauAubert
LauAubert requested a review from a team as a code owner October 2, 2026 12:29
@LauAubert
LauAubert requested review from LoLoo03 and TomasMartin2004 and removed request for a team and LoLoo03 October 2, 2026 12:29

@TomasMartin2004 TomasMartin2004 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Aprobado, y lo mergeo después de proyecto-api#98 como pedís en el cuerpo. Verificado en local sobre la rama, no de lectura.

Check Resultado
npm run test 683 pruebas · 74 archivos · 0 fallas
npm run lint 0 advertencias
npm run build limpio
CI en verde

Lo que fui a buscar primero

Que no quede ninguna llamada al contrato que la API elimina. Es el riesgo real de este par de PRs: el back borra PUT /integrations/:service_id, el DELETE y el POST .../test. Grepeé client.put y client.delete contra todo lo que toque integraciones: ninguna. La pantalla quedó de sólo lectura de verdad, no «sin el botón pero con el código».

Que shared/ no importe de features/. La Regla de Dos justifica subir el listado a shared/, pero subirlo mal rompe la regla de dependencia de la arquitectura. Verificado: cero imports de features dentro de shared/.

Dientes. Saqué el filtro integration.isActive de linkableChannels y se puso en rojo offers only the connected, active sales channels where the product is not published, y sólo ése. La regla está sostenida por un test, no por el orden de los &&.

Lo que me gustó

  • El comentario de shared/api/integrations.ts sobre el nombre. «El endpoint NO devuelve company_integrations, devuelve las plantillas conectables con el estado encima, por eso el identificador es service_id». Esa confusión es exactamente la que iba a tener el próximo que abra el archivo.
  • lastSyncedAt cableado y opcional, con el motivo escrito: el back todavía no tiene la columna, y el widget ya queda listo para encenderse solo. Es la forma correcta de dejar algo a medias — declarado, no olvidado.
  • Sacar el formulario de conexión en el segundo commit cuando se tomó la decisión del ADR-018, y contarlo en el cuerpo del PR. Tirar código propio porque cambió la decisión es lo más difícil de hacer y lo más barato de revisar.
  • linkableChannels excluye los canales donde el producto ya está publicado. Un producto, una publicación por canal: la regla del dominio resuelta en el selector en vez de esperar el 409.

⚪ Una sola observación

linkErrorMessage traduce 422 → «no existe en el canal o el SKU es ambiguo». Fui a ver si el 422 está sobrecargado del lado de la API, porque ese endpoint también responde 422 ante un RecordNotSaved (falta la integración) y ante una validación de modelo (el precio externo fuera de rango).

Conclusión: no es un problema hoy, y por una razón concreta — el formulario manda sólo company_integration_id y, opcionalmente, external_product_id. Nunca manda precio, y la integración siempre va. Así que el único 422 que esta pantalla puede producir es el que el texto describe.

Lo dejo anotado porque es frágil ante un cambio chico: el día que el formulario sume el precio externo, un precio inválido va a decirle al usuario que su publicación no existe. Si en ese momento el back distingue con un código de error en el cuerpo, se resuelve solo.

Orden de merge

Este va segundo. Mergeo primero proyecto-api#98 —que ya aprobé— y después este, para que la pantalla no quede pidiéndole a la API una forma de GET /integrations que todavía no existe.

@TomasMartin2004
TomasMartin2004 merged commit f411018 into master Oct 3, 2026
4 checks passed
@TomasMartin2004
TomasMartin2004 deleted the TESIS-139-shopify-integration branch October 3, 2026 14:31
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants