Compare commits
16
Commits
8f9df266d3
..
main
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
55fbf3524b | ||
|
|
17dc9a5133 | ||
|
|
ba96a50a42 | ||
|
|
91335ce023 | ||
|
|
43c47d99fb | ||
|
|
4baabf36ce | ||
|
|
3cc0a7c861 | ||
|
|
a3e9580348 | ||
|
|
aaf6e14742 | ||
|
|
c23803656e | ||
|
|
e86a0ebc96 | ||
|
|
b02a5e8b5f | ||
|
|
ca10ef6e9a | ||
|
|
f6918b9eab | ||
|
|
8e4f3515fe | ||
|
|
e7d7fcdb00 |
No files matched your search
@@ -234,11 +234,17 @@ jobs:
|
|||||||
shell: bash
|
shell: bash
|
||||||
run: |
|
run: |
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
# --timeout 30m (no 15m): medido en vivo 2026-09-27, el analisis de
|
||||||
|
# capas de esta imagen (node_modules de Medusa) toma ~14 min en
|
||||||
|
# el runner del NAS -- con 15m hizo timeout una vez. El paso HIGH
|
||||||
|
# de abajo reusa ese analisis cacheado (~2 s), por eso no lo
|
||||||
|
# necesita. No cambia que revisa ni que bloquea.
|
||||||
/tmp/trivy image \
|
/tmp/trivy image \
|
||||||
|
--scanners vuln \
|
||||||
--severity CRITICAL \
|
--severity CRITICAL \
|
||||||
--exit-code 1 \
|
--exit-code 1 \
|
||||||
--ignore-unfixed \
|
--ignore-unfixed \
|
||||||
--timeout 15m0s \
|
--timeout 30m0s \
|
||||||
--ignorefile "${APP_DIR}/.trivyignore" \
|
--ignorefile "${APP_DIR}/.trivyignore" \
|
||||||
"${IMAGE_NAME}:${{ steps.vars.outputs.VERSION }}"
|
"${IMAGE_NAME}:${{ steps.vars.outputs.VERSION }}"
|
||||||
|
|
||||||
@@ -248,6 +254,7 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
/tmp/trivy image \
|
/tmp/trivy image \
|
||||||
|
--scanners vuln \
|
||||||
--severity HIGH \
|
--severity HIGH \
|
||||||
--exit-code 0 \
|
--exit-code 0 \
|
||||||
--ignore-unfixed \
|
--ignore-unfixed \
|
||||||
|
|||||||
@@ -429,6 +429,7 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
/tmp/trivy image \
|
/tmp/trivy image \
|
||||||
|
--scanners vuln \
|
||||||
--severity CRITICAL \
|
--severity CRITICAL \
|
||||||
--exit-code 1 \
|
--exit-code 1 \
|
||||||
--ignore-unfixed \
|
--ignore-unfixed \
|
||||||
@@ -442,6 +443,7 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
/tmp/trivy image \
|
/tmp/trivy image \
|
||||||
|
--scanners vuln \
|
||||||
--severity HIGH \
|
--severity HIGH \
|
||||||
--exit-code 0 \
|
--exit-code 0 \
|
||||||
--ignore-unfixed \
|
--ignore-unfixed \
|
||||||
|
|||||||
@@ -231,6 +231,7 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
/tmp/trivy image \
|
/tmp/trivy image \
|
||||||
|
--scanners vuln \
|
||||||
--severity CRITICAL \
|
--severity CRITICAL \
|
||||||
--exit-code 1 \
|
--exit-code 1 \
|
||||||
--ignore-unfixed \
|
--ignore-unfixed \
|
||||||
@@ -243,6 +244,7 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
/tmp/trivy image \
|
/tmp/trivy image \
|
||||||
|
--scanners vuln \
|
||||||
--severity HIGH \
|
--severity HIGH \
|
||||||
--exit-code 0 \
|
--exit-code 0 \
|
||||||
--ignore-unfixed \
|
--ignore-unfixed \
|
||||||
|
|||||||
@@ -115,6 +115,15 @@ function normalizeTitleForMatch(title: string): string {
|
|||||||
.toLowerCase();
|
.toLowerCase();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Categorias/colecciones: ademas de lo anterior, ignora tildes y eñes
|
||||||
|
// ("Figuras de accion" == "Figuras de acción"). Desde 2026-09-27 los
|
||||||
|
// nombres reales en Medusa llevan tildes; sin esto, un CSV escrito sin
|
||||||
|
// tildes (o viejo) crearia una categoria DUPLICADA en vez de reusar la
|
||||||
|
// existente. Los titulos de producto siguen con normalizeTitleForMatch.
|
||||||
|
function normalizeNameForMatch(name: string): string {
|
||||||
|
return normalizeTitleForMatch(name).normalize("NFD").replace(/[\u0300-\u036f]/g, "");
|
||||||
|
}
|
||||||
|
|
||||||
function slugify(title: string, uniquePart: string): string {
|
function slugify(title: string, uniquePart: string): string {
|
||||||
const base = title
|
const base = title
|
||||||
.normalize("NFD")
|
.normalize("NFD")
|
||||||
@@ -166,7 +175,7 @@ export async function GET(req: MedusaRequest, res: MedusaResponse) {
|
|||||||
// ofrece descargar y lo que realmente se procesa.
|
// ofrece descargar y lo que realmente se procesa.
|
||||||
const header = "titulo,descripcion,precio,categoria,coleccion,cantidad,fotos";
|
const header = "titulo,descripcion,precio,categoria,coleccion,cantidad,fotos";
|
||||||
const example =
|
const example =
|
||||||
'"Muñeco Buzz Lightyear","Figura articulada de 30cm, edición coleccionista.",89900,"Figuras de accion","Toy Story",3,"https://ejemplo.com/foto1.jpg;https://ejemplo.com/foto2.jpg"';
|
'"Muñeco Buzz Lightyear","Figura articulada de 30cm, edición coleccionista.",89900,"Figuras de acción","Toy Story",3,"https://ejemplo.com/foto1.jpg;https://ejemplo.com/foto2.jpg"';
|
||||||
const csv = `${header}\n${example}\n`;
|
const csv = `${header}\n${example}\n`;
|
||||||
res.setHeader("Content-Type", "text/csv; charset=utf-8");
|
res.setHeader("Content-Type", "text/csv; charset=utf-8");
|
||||||
res.setHeader("Content-Disposition", 'attachment; filename="plantilla-importacion.csv"');
|
res.setHeader("Content-Disposition", 'attachment; filename="plantilla-importacion.csv"');
|
||||||
@@ -217,10 +226,10 @@ export async function POST(req: MedusaRequest<{ csv: string }>, res: MedusaRespo
|
|||||||
}
|
}
|
||||||
|
|
||||||
const categoryByName = new Map(
|
const categoryByName = new Map(
|
||||||
categories.filter((c) => c.name).map((c) => [normalizeTitleForMatch(c.name), c.id]),
|
categories.filter((c) => c.name).map((c) => [normalizeNameForMatch(c.name), c.id]),
|
||||||
);
|
);
|
||||||
const collectionByName = new Map(
|
const collectionByName = new Map(
|
||||||
collections.filter((c) => c.title).map((c) => [normalizeTitleForMatch(c.title), c.id]),
|
collections.filter((c) => c.title).map((c) => [normalizeNameForMatch(c.title), c.id]),
|
||||||
);
|
);
|
||||||
const productByTitle = new Map(
|
const productByTitle = new Map(
|
||||||
existingProducts.filter((p) => p.title).map((p) => [normalizeTitleForMatch(p.title), p]),
|
existingProducts.filter((p) => p.title).map((p) => [normalizeTitleForMatch(p.title), p]),
|
||||||
@@ -289,7 +298,7 @@ export async function POST(req: MedusaRequest<{ csv: string }>, res: MedusaRespo
|
|||||||
if (!inventoryItemId) throw new Error("No se pudo crear el registro de inventario");
|
if (!inventoryItemId) throw new Error("No se pudo crear el registro de inventario");
|
||||||
|
|
||||||
const categoryName = row.categoria?.trim();
|
const categoryName = row.categoria?.trim();
|
||||||
let categoryId = categoryName ? categoryByName.get(normalizeTitleForMatch(categoryName)) : undefined;
|
let categoryId = categoryName ? categoryByName.get(normalizeNameForMatch(categoryName)) : undefined;
|
||||||
if (categoryName && !categoryId) {
|
if (categoryName && !categoryId) {
|
||||||
const { result: createdCategories } = await createProductCategoriesWorkflow(req.scope).run({
|
const { result: createdCategories } = await createProductCategoriesWorkflow(req.scope).run({
|
||||||
input: {
|
input: {
|
||||||
@@ -299,11 +308,11 @@ export async function POST(req: MedusaRequest<{ csv: string }>, res: MedusaRespo
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
categoryId = createdCategories[0].id;
|
categoryId = createdCategories[0].id;
|
||||||
categoryByName.set(normalizeTitleForMatch(categoryName), categoryId);
|
categoryByName.set(normalizeNameForMatch(categoryName), categoryId);
|
||||||
}
|
}
|
||||||
|
|
||||||
const collectionName = row.coleccion?.trim();
|
const collectionName = row.coleccion?.trim();
|
||||||
let collectionId = collectionName ? collectionByName.get(normalizeTitleForMatch(collectionName)) : undefined;
|
let collectionId = collectionName ? collectionByName.get(normalizeNameForMatch(collectionName)) : undefined;
|
||||||
if (collectionName && !collectionId) {
|
if (collectionName && !collectionId) {
|
||||||
const { result: createdCollections } = await createCollectionsWorkflow(req.scope).run({
|
const { result: createdCollections } = await createCollectionsWorkflow(req.scope).run({
|
||||||
input: {
|
input: {
|
||||||
@@ -313,7 +322,7 @@ export async function POST(req: MedusaRequest<{ csv: string }>, res: MedusaRespo
|
|||||||
},
|
},
|
||||||
});
|
});
|
||||||
collectionId = createdCollections[0].id;
|
collectionId = createdCollections[0].id;
|
||||||
collectionByName.set(normalizeTitleForMatch(collectionName), collectionId);
|
collectionByName.set(normalizeNameForMatch(collectionName), collectionId);
|
||||||
}
|
}
|
||||||
|
|
||||||
await createProductsWorkflow(req.scope).run({
|
await createProductsWorkflow(req.scope).run({
|
||||||
|
|||||||
@@ -25,7 +25,7 @@ spec:
|
|||||||
runAsNonRoot: false
|
runAsNonRoot: false
|
||||||
containers:
|
containers:
|
||||||
- name: app
|
- name: app
|
||||||
image: gitea.cruzcloud.net/devops/docs-portal:v1.0.283
|
image: gitea.cruzcloud.net/devops/docs-portal:v1.0.294
|
||||||
# El base saca TODAS las capabilities. Probado en vivo y
|
# El base saca TODAS las capabilities. Probado en vivo y
|
||||||
# revertido: con drop: ALL (aunque se agregue NET_BIND_SERVICE
|
# revertido: con drop: ALL (aunque se agregue NET_BIND_SERVICE
|
||||||
# de vuelta) el propio entrypoint de nginx:1.27-alpine falla al
|
# de vuelta) el propio entrypoint de nginx:1.27-alpine falla al
|
||||||
|
|||||||
@@ -26,11 +26,11 @@ spec:
|
|||||||
runAsUser: 1001
|
runAsUser: 1001
|
||||||
containers:
|
containers:
|
||||||
- name: app
|
- name: app
|
||||||
image: gitea.cruzcloud.net/devops/ecommerce-frontend:v1.0.286
|
image: gitea.cruzcloud.net/devops/ecommerce-frontend:v1.0.293
|
||||||
env:
|
env:
|
||||||
# Ver comentario en workloads/ecommerce/frontend/patches/deployment.yaml
|
# Ver comentario en workloads/ecommerce/frontend/patches/deployment.yaml
|
||||||
- name: APP_VERSION
|
- name: APP_VERSION
|
||||||
value: v1.0.286
|
value: v1.0.293
|
||||||
- name: HEADLESS_PROVIDER
|
- name: HEADLESS_PROVIDER
|
||||||
value: medusa
|
value: medusa
|
||||||
# Backend COMPARTIDO con Producción a propósito (decisión del
|
# Backend COMPARTIDO con Producción a propósito (decisión del
|
||||||
|
|||||||
@@ -11,6 +11,13 @@ type CatalogPageProps = {
|
|||||||
searchParams: Promise<Record<string, string | string[] | undefined>>;
|
searchParams: Promise<Record<string, string | string[] | undefined>>;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Sin tildes/eñes ni mayusculas: los links viejos (o escritos a mano)
|
||||||
|
// como ?category=Munecas y peluches siguen llevando a la categoria real
|
||||||
|
// "Muñecas y peluches" (renombrada con tildes el 2026-09-27).
|
||||||
|
function normalizeName(value: string): string {
|
||||||
|
return value.normalize("NFD").replace(/[\u0300-\u036f]/g, "").trim().toLowerCase();
|
||||||
|
}
|
||||||
|
|
||||||
function single(value: string | string[] | undefined): string {
|
function single(value: string | string[] | undefined): string {
|
||||||
return Array.isArray(value) ? value[0] ?? "" : value ?? "";
|
return Array.isArray(value) ? value[0] ?? "" : value ?? "";
|
||||||
}
|
}
|
||||||
@@ -19,7 +26,9 @@ export default async function CatalogPage({ searchParams }: CatalogPageProps) {
|
|||||||
const params = await searchParams;
|
const params = await searchParams;
|
||||||
const [products, categories] = await Promise.all([getProducts(), getCategories()]);
|
const [products, categories] = await Promise.all([getProducts(), getCategories()]);
|
||||||
|
|
||||||
const category = single(params.category) || "Todos";
|
const requestedCategory = single(params.category) || "Todos";
|
||||||
|
const category =
|
||||||
|
categories.find((node) => normalizeName(node.name) === normalizeName(requestedCategory))?.name ?? requestedCategory;
|
||||||
const collection = single(params.collection);
|
const collection = single(params.collection);
|
||||||
const query = single(params.q);
|
const query = single(params.q);
|
||||||
const taxonomy = {
|
const taxonomy = {
|
||||||
|
|||||||
@@ -65,7 +65,7 @@ spec:
|
|||||||
memory: 64Mi
|
memory: 64Mi
|
||||||
|
|
||||||
- name: migrations
|
- name: migrations
|
||||||
image: gitea.cruzcloud.net/devops/ecommerce-medusa:v1.0.287
|
image: gitea.cruzcloud.net/devops/ecommerce-medusa:v1.0.295
|
||||||
imagePullPolicy: IfNotPresent
|
imagePullPolicy: IfNotPresent
|
||||||
command:
|
command:
|
||||||
- node
|
- node
|
||||||
@@ -86,7 +86,7 @@ spec:
|
|||||||
|
|
||||||
containers:
|
containers:
|
||||||
- name: app
|
- name: app
|
||||||
image: gitea.cruzcloud.net/devops/ecommerce-medusa:v1.0.287
|
image: gitea.cruzcloud.net/devops/ecommerce-medusa:v1.0.295
|
||||||
imagePullPolicy: IfNotPresent
|
imagePullPolicy: IfNotPresent
|
||||||
envFrom:
|
envFrom:
|
||||||
- configMapRef:
|
- configMapRef:
|
||||||
|
|||||||
@@ -25,14 +25,14 @@ spec:
|
|||||||
runAsUser: 1001
|
runAsUser: 1001
|
||||||
containers:
|
containers:
|
||||||
- name: app
|
- name: app
|
||||||
image: gitea.cruzcloud.net/devops/ecommerce-frontend:v1.0.286
|
image: gitea.cruzcloud.net/devops/ecommerce-frontend:v1.0.293
|
||||||
env:
|
env:
|
||||||
# La actualiza el mismo paso del CD (.gitea/workflows/build.yaml)
|
# La actualiza el mismo paso del CD (.gitea/workflows/build.yaml)
|
||||||
# que bumpea el tag de la imagen -- el footer la lee
|
# que bumpea el tag de la imagen -- el footer la lee
|
||||||
# (process.env.APP_VERSION) para mostrar la versión real
|
# (process.env.APP_VERSION) para mostrar la versión real
|
||||||
# desplegada en vez de un "v4.1" fijo.
|
# desplegada en vez de un "v4.1" fijo.
|
||||||
- name: APP_VERSION
|
- name: APP_VERSION
|
||||||
value: v1.0.286
|
value: v1.0.293
|
||||||
- name: HEADLESS_PROVIDER
|
- name: HEADLESS_PROVIDER
|
||||||
value: medusa
|
value: medusa
|
||||||
- name: MEDUSA_BACKEND_URL
|
- name: MEDUSA_BACKEND_URL
|
||||||
|
|||||||
Reference in new issue
Block a user