feat(docs-portal): pipeline GitOps y manifiestos K8s para el portal MkDocs
Agrega lo que faltaba para desplegar workloads/docs-portal/ (ya existente sin commitear): deployment/service/ingress + kustomization siguiendo el patrón de workloads/nginx, Applications de workload y gobernanza separadas, y el workflow de Gitea Actions (build+push a Gitea Registry, bump de versión en el manifiesto) siguiendo el mismo patrón que build.yaml/build-medusa.yaml.
This commit is contained in:
@@ -0,0 +1,88 @@
|
||||
# Incidente: 504 Gateway Timeout intermitente (túnel QUIC inestable) (2026-08)
|
||||
|
||||
!!! info "Origen"
|
||||
Migrado desde `apps-registry/docs/playbooks/incidente-cloudflared-504-quic-2026-08.md`.
|
||||
Contenido técnico preservado sin cambios de fondo — solo formato adaptado a MkDocs Material.
|
||||
|
||||
| | |
|
||||
|---|---|
|
||||
| **Ventana del incidente** | Intermitente durante varias horas, 2026-08 |
|
||||
| **Servicio afectado** | `cloudflared` (túnel de Cloudflare, app nativa de ZimaOS, red Host) — afecta a **todos** los subdominios detrás del mismo túnel (`gitea.cruzcloud.net`, `commerce.cruzcloud.net`, `shop.cruzcloud.net`, `media.cruzcloud.net`, Immich, Argo CD, Memos, etc.) |
|
||||
| **Impacto** | `504 Gateway Timeout` intermitente en peticiones HTTP a cualquier host detrás del túnel, sin patrón evidente de horario ni de carga |
|
||||
|
||||
## Síntoma
|
||||
|
||||
`gitea.cruzcloud.net` devolvía `504 Gateway Timeout` de forma
|
||||
intermitente. El mismo síntoma se observaba en otros hosts servidos por
|
||||
el mismo túnel, lo que apuntaba a una causa **compartida a nivel de
|
||||
túnel**, no de una app individual.
|
||||
|
||||
## Diagnóstico
|
||||
|
||||
```bash
|
||||
docker logs -f cloudflared 2>&1 | grep -i -E "connIndex|protocol|retry"
|
||||
```
|
||||
|
||||
Reveló un patrón sostenido durante horas: la conexión `connIndex=2`
|
||||
(de las 4 conexiones que `cloudflared` mantiene hacia el edge de
|
||||
Cloudflare) sufría fallos recurrentes de "control stream" cada 2-6
|
||||
minutos, reconectándose en loop:
|
||||
|
||||
```text
|
||||
control stream encountered a failure while serving
|
||||
Retrying connection in up to 1m4s
|
||||
```
|
||||
|
||||
Cuando una petición HTTP coincidía con el instante exacto de una de
|
||||
esas caídas de `connIndex=2`, el cliente recibía el 504.
|
||||
|
||||
## Causa raíz
|
||||
|
||||
El contenedor `cloudflared` (imagen oficial `cloudflare/cloudflared:latest`)
|
||||
usaba **QUIC (UDP)** como protocolo de transporte por defecto para sus
|
||||
4 conexiones hacia el edge de Cloudflare. Una de esas conexiones
|
||||
presentaba fallos de control stream recurrentes durante horas.
|
||||
|
||||
!!! warning "Causa de fondo probable"
|
||||
Inestabilidad de UDP/QUIC en la red local (NAT/firewall doméstico).
|
||||
QUIC es más sensible que TCP/HTTP2 a NATs agresivos o middleboxes
|
||||
que no manejan bien tráfico UDP de larga vida — un patrón común en
|
||||
redes domésticas, a diferencia de datacenters con NAT dedicado para
|
||||
este tipo de tráfico.
|
||||
|
||||
## Fix aplicado
|
||||
|
||||
Se agregó la variable de entorno `TUNNEL_TRANSPORT_PROTOCOL=http2` en
|
||||
la configuración del contenedor `cloudflared` (app ZimaOS → Ambiente →
|
||||
variables), forzando HTTP/2 sobre TCP en vez de QUIC/UDP.
|
||||
|
||||
Tras reiniciar el contenedor, el precheck de `cloudflared` confirmó:
|
||||
|
||||
```text
|
||||
Environment is healthy. cloudflared will use 'http2' as primary protocol.
|
||||
```
|
||||
|
||||
y las 4 conexiones pasaron a `protocol=http2`.
|
||||
|
||||
Ver también el ADR correspondiente:
|
||||
[0003 — Por qué HTTP/2 sobre QUIC](../decisiones/0003-por-que-http2-sobre-quic-tunnel.md).
|
||||
|
||||
## Validación
|
||||
|
||||
Curl en loop contra `gitea.cruzcloud.net` tras el cambio: latencia
|
||||
estable ~0.37–0.4s sostenida por 24+ minutos sin un solo 504.
|
||||
|
||||
!!! note "Degradación puntual no relacionada, pendiente de vigilar"
|
||||
Durante la ventana de validación se observó una ventana breve de
|
||||
latencia alta (hasta 39s) y algunos `502`/`530`, coincidiendo con
|
||||
una ejecución pesada de Gitea Actions (build/deploy de Medusa) en
|
||||
el mismo host. Esto es contención de recursos local (CPU/IO del
|
||||
host compitiendo entre el runner de Actions y el resto de los
|
||||
servicios), **no relacionado al fix de protocolo del túnel** — no
|
||||
se reabrió como parte de este incidente.
|
||||
|
||||
**Pendiente:** vigilar si estas ventanas de degradación coinciden
|
||||
sistemáticamente con builds/deploys de Gitea Actions. Si es
|
||||
recurrente, considerar mover el runner (`gitea-runner`) a otro host
|
||||
o limitarle recursos para evitar que compita con el resto de las
|
||||
apps del NAS.
|
||||
@@ -0,0 +1,175 @@
|
||||
# Incidente: `medusa-deploy` atascado sin levantar (2026-08)
|
||||
|
||||
!!! info "Origen"
|
||||
Migrado desde `apps-registry/docs/playbooks/incidente-medusa-crashloop-2026-08.md`.
|
||||
Contenido técnico preservado sin cambios de fondo — solo formato adaptado a MkDocs Material.
|
||||
|
||||
| | |
|
||||
|---|---|
|
||||
| **Ventana del incidente** | ~2026-08-08 (onset) → 2026-08-10 05:31 UTC (resuelto) |
|
||||
| **Servicio afectado** | `medusa-deploy` en namespace `ecommerce` |
|
||||
| **Impacto** | El rollout nuevo nunca llegaba a estar listo. El pod anterior siguió sirviendo tráfico sin interrupción (`maxUnavailable: 0`) — no hubo downtime de cara al usuario. El impacto fue "no se puede desplegar", no "el servicio está caído". |
|
||||
|
||||
## Síntoma inicial reportado vs. estado real
|
||||
|
||||
El incidente se reportó como **CrashLoopBackOff** ("sigue igual desde
|
||||
ayer, más de 20-24 horas"). Al correr `kubectl get pods -n ecommerce -o wide`
|
||||
esa descripción resultó **inexacta**: no había ningún pod en
|
||||
CrashLoopBackOff. El estado real era:
|
||||
|
||||
```text
|
||||
commerce-postgres-0 1/1 Running 0 4d9h
|
||||
medusa-deploy-59878b956f-hf45b 1/1 Running 0 21h (revisión vieja, sana)
|
||||
medusa-deploy-9d8784d7b-g9jql 0/1 Init:0/2 0 3h56m (revisión nueva, atascada)
|
||||
```
|
||||
|
||||
0 *restarts* en el pod nuevo — no estaba crasheando y reiniciando,
|
||||
estaba **colgado indefinidamente en el primer init container**
|
||||
(`wait-for-postgres`), sin timeout ni backoff que lo sacara de ese
|
||||
estado.
|
||||
|
||||
!!! tip "Lección"
|
||||
Un pod en `Init:X/Y` con 0 restarts y un `CrashLoopBackOff` requieren
|
||||
líneas de investigación distintas — y el primero no dispara las
|
||||
alertas típicas de CrashLoop, lo que probablemente explica por qué
|
||||
pasó desapercibido tanto tiempo. **Verificar siempre el estado real
|
||||
con `kubectl get pods` antes de asumir el tipo de falla que describe
|
||||
quien reporta el incidente.**
|
||||
|
||||
## Hipótesis descartadas (en orden)
|
||||
|
||||
### 1. Credenciales / secret inválido o vacío
|
||||
|
||||
- `kubectl get secret -n ecommerce commerce-secrets` → la clave
|
||||
`DATABASE_URL` existe, longitud correcta (~126 caracteres).
|
||||
- `pg_isready` ejecutado **dentro del propio pod `commerce-postgres-0`**
|
||||
con ese `DATABASE_URL`: `accepting connections`, exit 0.
|
||||
- **Descartada:** el secret existe, tiene el formato correcto y las
|
||||
credenciales son válidas.
|
||||
|
||||
### 2. NetworkPolicy bloqueando tráfico
|
||||
|
||||
- `kubectl describe networkpolicy -n ecommerce ecommerce-network-security`
|
||||
→ `PodSelector: <none>`, ingress allow-all, egress sin restricciones.
|
||||
- **Descartada:** no podía estar bloqueando la conexión de `medusa`
|
||||
hacia `postgres-svc`.
|
||||
|
||||
### 3. Salud de Postgres
|
||||
|
||||
- `SELECT count(*) FROM pg_stat_activity;` → 8 conexiones activas,
|
||||
respuesta inmediata.
|
||||
- `kubectl get endpoints -n ecommerce postgres-svc` → apunta
|
||||
correctamente a `10.42.0.98:5432`.
|
||||
- **Descartada:** Postgres estaba sano.
|
||||
|
||||
### 4. ResourceQuota / LimitRange del namespace
|
||||
|
||||
- `pods: 7/12`, `requests.cpu: 625m/3`, `requests.memory: 1504Mi/4Gi` —
|
||||
todo muy por debajo de los topes. Sin eventos de `exceeded quota`.
|
||||
- **Descartada:** no había presión de cuota.
|
||||
|
||||
### 5. DNS
|
||||
|
||||
- `getent hosts postgres-svc` resolvía a `10.42.0.98` correctamente,
|
||||
tanto en el pod sano como **dentro del propio init container
|
||||
atascado**. CoreDNS sano.
|
||||
- **Descartada:** la resolución DNS funcionaba de forma idéntica en
|
||||
ambos pods.
|
||||
|
||||
### 6. MTU/PMTUD cross-node (blackhole conocido)
|
||||
|
||||
Existe un blackhole de red confirmado (186/186 fallos reproducidos)
|
||||
cuando un pod que necesita hablar con `commerce-postgres-0` queda
|
||||
agendado en un nodo distinto al de Postgres, mitigado con `podAffinity`
|
||||
en `medusa.yaml`.
|
||||
|
||||
- `kubectl get pods -o wide` mostró que los tres pods relevantes
|
||||
estaban en el **mismo nodo** (`k3d-lab-cluster-server-0`).
|
||||
- **Descartada para este incidente puntual** (sin tráfico cross-node
|
||||
involucrado) — pero sigue siendo una condición latente real del
|
||||
cluster. Ver [nota de deuda técnica](#deuda-técnica-pendiente) abajo.
|
||||
|
||||
## Causa raíz real
|
||||
|
||||
El init container `wait-for-postgres` ejecutaba:
|
||||
|
||||
```sh
|
||||
until pg_isready -d "$DATABASE_URL" -t 5; do ...
|
||||
```
|
||||
|
||||
pasando la URI completa de `DATABASE_URL` a `pg_isready`. En algún
|
||||
momento se agregó el query param `?sslmode=disable` al `DATABASE_URL`
|
||||
generado por `create-commerce-secrets.sh` (repo `scripts`). Con ese
|
||||
query param, el parser de conninfo de `pg_isready` en la imagen
|
||||
`postgres:17-alpine` no lograba interpretar la URI y devolvía:
|
||||
|
||||
```text
|
||||
postgres-svc:5432 - no attempt
|
||||
```
|
||||
|
||||
!!! danger "`exit 3` — `no attempt`"
|
||||
Es un status propio de `pg_isready` que indica que el cliente **ni
|
||||
siquiera intentó conectar** por un problema en los parámetros de
|
||||
conexión (a diferencia de `no response`, que sí implica un intento
|
||||
de conexión fallido).
|
||||
|
||||
Aislamiento reproducido dentro del propio init container atascado:
|
||||
|
||||
- `pg_isready -h postgres-svc -p 5432 -U medusa -d medusa` (sin URI) →
|
||||
`accepting connections`, exit 0.
|
||||
- URI completa **sin** `?sslmode=disable` → también exitosa.
|
||||
- URI completa **con** `?sslmode=disable` → reproducía el fallo.
|
||||
|
||||
Como el `until` no tiene timeout ni backoff máximo, el init container
|
||||
quedaba reintentando indefinidamente sin nunca fallar de forma visible
|
||||
— de ahí que no apareciera como CrashLoopBackOff.
|
||||
|
||||
## Fix aplicado
|
||||
|
||||
`workloads/ecommerce/commerce/medusa.yaml`, init container
|
||||
`wait-for-postgres`:
|
||||
|
||||
```diff
|
||||
- until pg_isready -d "$DATABASE_URL" -t 5; do
|
||||
+ until pg_isready -h postgres-svc -p 5432 -U "$POSTGRES_USER" -d "$POSTGRES_DB" -t 5; do
|
||||
```
|
||||
|
||||
`postgres-svc` y `5432` son fijos (el Service de Postgres);
|
||||
`POSTGRES_USER` y `POSTGRES_DB` ya llegan al contenedor vía
|
||||
`envFrom: secretRef: commerce-secrets`. El chequeo de disponibilidad
|
||||
deja de depender por completo del formato de `DATABASE_URL`.
|
||||
|
||||
Commit `e9c93bf` en `main` de `apps-registry`, sincronizado por Argo CD
|
||||
(`ecommerce-app`) a las `2026-08-10T05:29:32Z`. El pod atascado fue
|
||||
reemplazado automáticamente por uno sano, sin intervención manual
|
||||
adicional.
|
||||
|
||||
## Lecciones aprendidas
|
||||
|
||||
**Separar el chequeo de disponibilidad de las migraciones en init
|
||||
containers distintos fue lo correcto** — permitió aislar el fallo al
|
||||
primer init container sin ambigüedad. La lección no es cambiar esa
|
||||
separación, sino mantenerla: cualquier chequeo de disponibilidad debe
|
||||
usar el mínimo de parámetros necesarios en vez de reusar una URI de
|
||||
conexión pensada para la app.
|
||||
|
||||
**No fue necesario crear pods de diagnóstico efímeros.** Toda la
|
||||
reproducción se hizo con `kubectl exec` sobre pods que ya existían.
|
||||
Crear pods de prueba repetidos consume cuota y puede introducir su
|
||||
propio ruido de scheduling/red — la regla práctica es usar pods
|
||||
existentes cuando el fallo es reproducible ahí, y reservar pods
|
||||
efímeros para cuando la variable a probar (nodo, imagen, versión) no
|
||||
se puede cambiar en un pod ya desplegado.
|
||||
|
||||
## Deuda técnica pendiente
|
||||
|
||||
El blackhole de red cross-node por MTU/PMTUD sigue sin arreglo de
|
||||
fondo (ajustar MTU a `1400` en la red Docker/flannel del cluster k3d).
|
||||
El workaround actual es el `podAffinity` en `medusa.yaml` que fija
|
||||
`medusa-deploy` al mismo nodo que `commerce-postgres-0`.
|
||||
|
||||
Existe una branch abierta, `fix/medusa-wait-for-postgres`, que elimina
|
||||
ese `podAffinity` y borra `docs/known-issues.md` **sin incluir el fix
|
||||
de MTU real**. Mergearla tal como está reintroduciría el blackhole
|
||||
cross-node sin red de contención. Queda pendiente decidir si se cierra
|
||||
sin mergear, o si se retoma agregando primero el fix real de MTU.
|
||||
@@ -0,0 +1,115 @@
|
||||
# Incidente: imágenes de producto en blanco (proxy host de `media.cruzcloud.net` mal configurado en NPM) (2026-08)
|
||||
|
||||
!!! info "Origen"
|
||||
Migrado desde `apps-registry/docs/playbooks/incidente-medusa-imagenes-proxy-npm-2026-08.md`.
|
||||
Contenido técnico preservado sin cambios de fondo — solo formato adaptado a MkDocs Material.
|
||||
|
||||
| | |
|
||||
|---|---|
|
||||
| **Ventana del incidente** | 2026-08-13, resuelto el mismo día |
|
||||
| **Servicio afectado** | `media.cruzcloud.net` (Nginx Proxy Manager → MinIO, `minio-svc` en namespace `ecommerce`) |
|
||||
| **Impacto** | Las imágenes subidas a productos desde el Admin de Medusa no se visualizaban en el detalle de producto del storefront (`shop.cruzcloud.net`) — el resto del producto (título, precio, subtítulo, botón añadir al carrito) se renderizaba bien |
|
||||
|
||||
## Síntoma
|
||||
|
||||
Al agregar imágenes a un producto desde el Admin de Medusa (sección
|
||||
Media), estas se veían correctamente cargadas en el Admin, pero en la
|
||||
página de detalle de producto del frontend los thumbnails aparecían
|
||||
vacíos/en blanco.
|
||||
|
||||
## Diagnóstico
|
||||
|
||||
1. **Storage:** confirmado que Medusa usa el provider
|
||||
`@medusajs/medusa/file-s3` apuntando a MinIO interno
|
||||
(`S3_ENDPOINT=http://minio-svc:9000`, bucket `ari-products`), con
|
||||
URLs públicas construidas sobre
|
||||
`S3_FILE_URL=https://media.cruzcloud.net/ari-products` (env en
|
||||
configmap `commerce-config`, namespace `ecommerce`).
|
||||
2. **API:** el producto traía URLs absolutas válidas, ej.
|
||||
`https://media.cruzcloud.net/ari-products/<archivo>.jpg`.
|
||||
3. **Prueba directa de la URL:**
|
||||
- Contra el ingress interno del cluster (bypaseando el proxy
|
||||
externo, `Host: media.cruzcloud.net` directo a la IP del
|
||||
`k3d-proxy`) → `200 OK`, MinIO sirve el JPEG correctamente.
|
||||
- Contra `https://media.cruzcloud.net/...` (la ruta real que usa el
|
||||
navegador del cliente, vía Cloudflare) → `500 Internal Server
|
||||
Error`, `Server: openresty`.
|
||||
|
||||
!!! tip "Patrón de diagnóstico clave"
|
||||
Ese contraste (funciona interno, falla público, con
|
||||
`Server: openresty` — el motor de Nginx Proxy Manager) aisló el
|
||||
problema al **proxy externo**, no a Medusa/MinIO/frontend.
|
||||
|
||||
## Causa raíz
|
||||
|
||||
En Nginx Proxy Manager (`/DATA/AppData/nginxproxymanager/`, contenedor
|
||||
`nginxproxymanager`), el proxy host de `media.cruzcloud.net`
|
||||
(`id=13` en la tabla `proxy_host` de `database.sqlite`) estaba mal
|
||||
configurado:
|
||||
|
||||
```text
|
||||
forward_host = "http://192.168.68.61/" -- esquema y barra final metidos dentro del campo (formato inválido)
|
||||
forward_port = 5005 -- puerto donde no escucha ningún proceso en el host
|
||||
```
|
||||
|
||||
Los proxy hosts que sí funcionan (`shop.cruzcloud.net` id 23,
|
||||
`commerce.cruzcloud.net` id 28) apuntan correctamente a:
|
||||
|
||||
```text
|
||||
forward_host = "192.168.68.61"
|
||||
forward_port = 90 -- puerto publicado por k3d-lab-cluster-serverlb hacia Traefik
|
||||
```
|
||||
|
||||
Se confirmó con `ss`/`docker ps` en el host que nada escuchaba en el
|
||||
puerto `5005` — este proxy host nunca estuvo apuntando correctamente
|
||||
al cluster, muy probablemente un typo desde que se creó.
|
||||
|
||||
## Fix aplicado
|
||||
|
||||
1. Backups previos: `database.sqlite.bak-pre-media-fix` y
|
||||
`13.conf.bak-pre-media-fix`.
|
||||
2. Corrección directa en la base SQLite de NPM:
|
||||
```sql
|
||||
UPDATE proxy_host SET forward_host='192.168.68.61', forward_port=90 WHERE id=13;
|
||||
```
|
||||
3. NPM regeneró automáticamente `nginx/proxy_host/13.conf` con los
|
||||
valores correctos (la app Node de NPM detecta el cambio en la base
|
||||
y reescribe el `.conf`; no hace falta editarlo a mano).
|
||||
4. Recarga de nginx dentro del contenedor:
|
||||
```bash
|
||||
docker exec nginxproxymanager nginx -t
|
||||
docker exec nginxproxymanager nginx -s reload
|
||||
```
|
||||
|
||||
Adicionalmente, se seteó el campo `thumbnail` del producto de prueba
|
||||
desde el Admin (estaba en `null`) — Medusa no lo autocompleta a partir
|
||||
del array `images`, y el frontend lo usa como imagen hero en listados/
|
||||
tarjetas de catálogo.
|
||||
|
||||
## Validación
|
||||
|
||||
Confirmado visualmente en
|
||||
`shop.cruzcloud.net/product/cybertron-sentinel-figura-de-coleccion-test-revalidate`
|
||||
que tanto la galería del detalle como el thumbnail en listados/tarjetas
|
||||
se renderizan correctamente. También se validó que ediciones de
|
||||
descripción/título/precio desde el Admin se reflejan automáticamente
|
||||
en el storefront sin necesidad de restart ni intervención manual, vía
|
||||
el subscriber `revalidate-storefront.ts`
|
||||
(`workloads/commerce-backend/src/subscribers/`) que procesa el evento
|
||||
`product.updated` de Medusa.
|
||||
|
||||
## Lecciones aprendidas
|
||||
|
||||
**Aislar interno vs. público antes de sospechar de la app.** Medusa,
|
||||
MinIO y el frontend estaban sanos todo el tiempo — la falla estaba en
|
||||
una capa de infraestructura fuera de los repos de GitOps (Nginx Proxy
|
||||
Manager, gestionado por fuera de `apps-registry`/`platform-infra`, sin
|
||||
versionar). Comparar la misma petición por la ruta interna del cluster
|
||||
contra la ruta pública real fue lo que aisló la causa en minutos, sin
|
||||
necesidad de tocar Medusa ni el frontend.
|
||||
|
||||
Ver [Red y exposición](../arquitectura/red-y-exposicion.md) para la
|
||||
topología completa Cloudflare Tunnel → Nginx Proxy Manager → k3d y el
|
||||
patrón a seguir si otro subdominio (`*.cruzcloud.net`) presenta el
|
||||
mismo síntoma (`500`/`Server: openresty` público pero sano
|
||||
internamente).
|
||||
@@ -0,0 +1,103 @@
|
||||
# Incidente: precios `null` en detalle de producto (Medusa Store API sin `region_id`) (2026-08)
|
||||
|
||||
!!! info "Origen"
|
||||
Migrado desde `apps-registry/docs/playbooks/incidente-medusa-precios-region-id-2026-08.md`.
|
||||
Contenido técnico preservado sin cambios de fondo — solo formato adaptado a MkDocs Material.
|
||||
|
||||
| | |
|
||||
|---|---|
|
||||
| **Ventana del incidente** | 2026-08-12, resuelto el mismo día (commit `60072cf`) |
|
||||
| **Servicio afectado** | `medusa-svc` (Store API), consumido por el frontend Next.js de ARI Shopping |
|
||||
| **Impacto** | En el listado de productos (`/catalog`) los precios se mostraban correctamente, pero en la página de detalle de producto individual varios artículos mostraban "Consultar precio" en vez del monto real |
|
||||
|
||||
## Hipótesis inicial descartada: regiones COP duplicadas
|
||||
|
||||
La primera hipótesis fue que existían regiones "Colombia (COP)"
|
||||
duplicadas en Medusa, causando ambigüedad al resolver el precio. Se
|
||||
descartó con evidencia directa en Postgres, consultando la tabla
|
||||
`region` **incluyendo soft-deletes**:
|
||||
|
||||
```sql
|
||||
SELECT id, name, currency_code, created_at, updated_at, deleted_at FROM region ORDER BY created_at;
|
||||
|
||||
id | name | currency_code | created_at | updated_at | deleted_at
|
||||
----------------------------------+----------+---------------+----------------------------+----------------------------+------------
|
||||
reg_01KZT86WPAH7A7ZZRDSX3350V2 | Colombia | cop | 2026-08-12 05:48:03.151+00 | 2026-08-12 05:48:03.151+00 |
|
||||
(1 row)
|
||||
```
|
||||
|
||||
Una sola fila, creada una sola vez, nunca actualizada, nunca borrada.
|
||||
**No había ni hubo nunca una región duplicada** — la hipótesis de
|
||||
deduplicación quedó descartada con esta consulta.
|
||||
|
||||
## Causa raíz real
|
||||
|
||||
El backend de Medusa **no tenía ninguna región configurada**. El
|
||||
frontend pedía precios al Store API (`/store/products`) usando el
|
||||
parámetro `currency_code=cop`, pero el validador
|
||||
`StoreGetProductsParams` de Medusa v2 (`.strict()`) **no acepta ese
|
||||
campo** — devolvía `400 Unrecognized fields: 'currency_code'` en cada
|
||||
carga de `/catalog`.
|
||||
|
||||
Se probó también `country_code`, que tampoco funcionó — Medusa
|
||||
respondía:
|
||||
|
||||
```text
|
||||
Missing required pricing context to calculate prices - region_id
|
||||
```
|
||||
|
||||
!!! danger "Nota de arquitectura"
|
||||
**Medusa v2 requiere explícitamente un `region_id` válido** para
|
||||
poder calcular precios (`calculated_price`) vía Store API; ni
|
||||
`currency_code` ni `country_code` son parámetros válidos para ese
|
||||
fin.
|
||||
|
||||
## Fix aplicado
|
||||
|
||||
Commit `60072cf` — *"fix: usar region_id en vez de currency_code para
|
||||
precios de Medusa"* (PR #5, mergeado en `04dc6bd`):
|
||||
|
||||
1. Se creó la región "Colombia" (moneda COP) vía Admin API — no
|
||||
existía ninguna región configurada previamente.
|
||||
2. Se modificó el frontend para pedir precios usando `region_id`
|
||||
explícito en vez de `currency_code`, vía una nueva variable de
|
||||
entorno `MEDUSA_REGION_ID`:
|
||||
|
||||
```diff
|
||||
# workloads/ecommerce/frontend.yaml
|
||||
- name: MEDUSA_BACKEND_URL
|
||||
value: http://medusa-svc:9000
|
||||
+ - name: MEDUSA_REGION_ID
|
||||
+ value: reg_01KZT86WPAH7A7ZZRDSX3350V2
|
||||
```
|
||||
|
||||
```diff
|
||||
# workloads/ecommerce/lib/headless/providers/medusa.ts
|
||||
const params = new URLSearchParams({
|
||||
limit: String(limit),
|
||||
- currency_code: "cop",
|
||||
+ // El validador de /store/products no acepta currency_code (400
|
||||
+ // "Unrecognized fields"); el precio calculado solo se resuelve con
|
||||
+ // region_id.
|
||||
+ region_id: regionId(),
|
||||
```
|
||||
|
||||
No hubo migración de precios entre regiones — no existían datos
|
||||
previos que migrar; la región se creó de cero como parte de este mismo
|
||||
fix. `MEDUSA_REGION_ID` no ha cambiado desde el commit original
|
||||
(verificado con `git log -p --follow` sobre `frontend.yaml`), y sigue
|
||||
apuntando a la única región que existe en la base.
|
||||
|
||||
## Lecciones aprendidas
|
||||
|
||||
**Verificar la causa raíz contra el estado real de la base de datos
|
||||
antes de asumir la primera hipótesis**, aun cuando esa hipótesis
|
||||
coincida con la intuición inicial de quien reporta el bug ("puede que
|
||||
haya algo duplicado"). La consulta a `region` incluyendo `deleted_at`
|
||||
tomó segundos y evitó documentar una causa raíz incorrecta en este
|
||||
mismo playbook.
|
||||
|
||||
**Nota de arquitectura:** Medusa v2 Store API (`StoreGetProductsParams`)
|
||||
requiere `region_id` explícito para resolver precios calculados.
|
||||
`currency_code` y `country_code` no son parámetros válidos para ese
|
||||
fin.
|
||||
Reference in New Issue
Block a user