# Migración Bicicletas Maino — Shopify → PocketBase (VPS)

Última actualización: 2026-08-12

## Contexto
Cliente tenía tienda en Shopify y dejó de pagar. Se rescató el export de productos antes del cierre.
**Objetivo: construir el activo de datos, no montar el sitio.** El sitio viene después.

## Stack destino
React 19 + Vite + TS (SPA estática, tokens CSS) · Admin aparte con Tailwind + Radix/shadcn ·
**PocketBase** (BD + auth + `pb_hooks`) · Mercado Pago Checkout Pro · Resend ·
nginx + Docker + Cloudflare Tunnel. Deploy: compilar local y subir estáticos.

## Estado

| Fase | Estado |
|---|---|
| 1 · Rescate de imágenes | ✅ 338 asociaciones / 314 archivos únicos, 0 errores |
| 2 · Planilla de gaps operativos | ✅ `maino_gaps_catalogo.xlsx` |
| 3 · Normalización a PocketBase | ✅ probado end-to-end contra v0.39.3 |
| 4 · Optimización de imágenes | ✅ **33,7 → 14,1 MB (58% menos)**, sin pérdida visible |
| 5 · Enriquecimiento — primeros 48 | ✅ 46 fichas |
| 6 · Enriquecimiento — bloque A | ✅ 43 fichas |
| 7 · Calidad y herramientas | ✅ esquema canónico · matriz de compatibilidad · validador |
| 8 · Bloque C — 50 marcas | ⬜ 16 con archivo verificado + 26 degradadas + 8 sin doc |
| 9 · Bicicletas | 🔶 7 Scott fechadas · 14 Trek necesitan número de serie |

## Enriquecimiento: 89 de 169 (53%)

| | Antes | Después |
|---|---|---|
| Datos duros | 143 | **1.845** (13×) |
| Alertas de compra | 0 | **578** |
| Datos sin fuente | — | **0** |

## Activos construidos en la fase 7

### Esquema canónico de specs
**265 claves libres → 48 canónicas**, con 7 campos tipados para filtros de catálogo
(`velocidades`, `peso_g`, `nucleo_norm`, `pinon_mayor_t`, `recorrido_mm`, `rigidez`, `tornillos_cala`).
Es el prerrequisito de los filtros de la tienda y de la matriz de compatibilidad.

### Matriz de compatibilidad — el activo diferenciador
138 pares compatibles y **302 incompatibles que hoy se venden juntos**, clasificados por causa:

- **`sistema` (132):** Campagnolo o SRAM contra Shimano. Distinta relación de tiro y núcleo.
- **`velocidades` (189)**
- **`capacidad` (15) ← los caros.** Misma marca, mismas velocidades, todo parece encajar, pero el cambio
  no envuelve el piñón grande. El CS-R7000 11-32T no funciona con **ninguno** de los cinco cambios de
  jaula corta del catálogo. Es el error que el comprador descubre al armar la bici.

Entregado como herramienta consultable: `compatibilidad.html`.

**Error propio corregido en el camino:** la primera versión reportaba "falta capacidad" en pares
Shimano+Campagnolo, cuando el problema real es que no son el mismo sistema. La regla de sistema ahora
se evalúa antes que la de capacidad.

### Validador de catálogo reutilizable
`scripts/validar_catalogo.py` — corre todas las verificaciones aprendidas y **sale con código 1 si hay
bloqueantes**, así que sirve en CI. Reglas: códigos inexistentes, familias ambiguas sin rango, SKU
repetido, título duplicado, imagen compartida entre marcas distintas, sin imagen, precio en 0, sin SKU,
sin peso, marca dudosa, título sin variante.

Estado actual: **357 hallazgos — 16 bloqueantes, 30 altos, 311 medios** (los medios son casi todos
`sin-peso` y `sin-sku`, que dependen del cliente).

Es lo que evita que el catálogo se degrade cuando carguen productos nuevos.

## Errores propios detectados y corregidos
1. **Deduplicar imágenes por URL global** dejaba sin foto a productos que comparten imagen. Corregido.
2. **Al reescribir títulos perdí el color** en 6 productos, dejando pares con nombre idéntico. Corregido.
3. **Residuos del brief en 10 títulos** (`(SKU: …)`, `(SIN CÓDIGO EN EL TÍTULO)`). Corregido.
4. **Once titulares de cassette idénticos** en la primera versión del generador — el mismo defecto que
   critiqué del copy original. Rehecho para que el titular salga del dato que diferencia.
5. **Matriz de compatibilidad con motivo equivocado** en pares de marcas distintas. Corregido.
6. **Parser de los PDF de Scott** leía una de tres columnas y devolvía años equivocados. Corregido.

Todos aparecieron por verificación sistemática, no por revisión manual. Es el argumento para dejar
corriendo el validador.

## Modelo de datos (PocketBase v0.39.3)
`brands` (36) · `categories` (24, 2 niveles) · `products` (169, público solo `active`) ·
`variants` (**solo superuser**, contiene `cost`) · `catalog_variants` (view pública **sin cost**) ·
`redirects` (186). Verificado: `GET /api/collections/variants/records` sin auth → **403**.

## Seguridad del checkout (al construir pb_hooks)
- Total de la orden calculado **server-side** leyendo precios de la BD.
- Preferencia de Mercado Pago creada en `pb_hooks`, access token en variable de entorno.
- Validar stock en la misma transacción que crea la orden.
- Webhook de MP: verificar firma antes de marcar como pagada.

## Entregado
`maino_pocketbase.zip` (bundle completo) · `maino_imagenes_optimizadas.zip` ·
`compatibilidad.html` · `higiene_catalogo.html` · `fichas_revision.html` · `fichas_bloqueA.html` ·
`errores_catalogo.html` · `piloto_enriquecimiento.html` · `maino_gaps_catalogo.xlsx`
