Communitygithub.com

AxelBeary/Inkglean

Patrones de arquitectura para plataformas multimercado de comisiones — páginas de usuario con plantillas, widgets integrables y alojamiento de páginas personalizadas. Cubre el sistema de tres capas para ofrecer plantillas alojadas y permitir la integración externa.

¿Qué es Inkglean?

Este skill define una arquitectura escalable para plataformas de comisiones multimercado (multi-tenant), dirigido a desarrolladores que construyen sistemas donde cada usuario (artista, creador, freelancer) necesita una página pública con capacidades de pedido. El patrón central es el Sistema de Tres Capas: desde plantillas integradas y listas para usar (Capa 1), pasando por widgets incrustables con una sola etiqueta `<script>` (Capa 2), hasta el alojamiento completo de páginas HTML/CSS/JS personalizadas (Capa 3). Soporta cualquier plataforma de agente de IA que pueda leer e implementar las decisiones arquitectónicas detalladas en el archivo SKILL.md. La arquitectura se centra en la separación de ejes ortogonales (diseño vs. paleta vs. acento) para minimizar la duplicación de código y maximizar las opciones de personalización. Incluye patrones probados para la extracción de lógica de formularios en composables reutilizables, la gestión de confirmaciones de arrastre según el contexto, la vista previa de plantillas mediante parámetros de URL y la implementación de un sistema de pasos guiados para el proceso de pedido. El skill está diseñado para ser independiente de la pila tecnológica, pero los ejemplos de implementación se centran en Vue.js, Fastify y CSS moderno, con énfasis en la capacidad de mantenimiento y la extensibilidad.

Compatible con~Claude Code~Codex CLI✓Cursor
npx skills add https://github.com/AxelBeary/Inkglean/tree/HEAD/docs/comms/archive/skills/software-development/commission-platform-architecture

Preguntar en tu IA favorita

Abre un nuevo chat con esta habilidad de agente ya precargada.

Documentación

Arquitectura de Plataforma de Comisiones

Plataformas de comisiones multimercado (multi-tenant) donde cada usuario (artista, creador, freelancer) tiene una página pública con capacidades de pedido. Este skill cubre la decisión arquitectónica de ofrecer tanto plantillas alojadas integradas como permitir que los creadores con sus propios sitios web se integren.

Sistema de Tres Capas de Página

El patrón arquitectónico central: apoyar a los creadores en tres niveles de habilidad técnica e independencia.

┌------------------------------------------------
|  Capa 3: Alojamiento de Página Personalizada   |
|  Sube tu propio HTML/CSS/JS → servido en      |
|  /artist/alice (archivos estáticos desde uploads/)
├------------------------------------------------
|  Capa 2: Widget Incrustable                     |
|  Pega una etiqueta <script> en tu sitio actual  |
|  → Botón "Solicitar Comisión" + formulario   |
├------------------------------------------------
|  Capa 1: Plantillas Integradas                  |
|  Elige una variante de diseño, cero configuración|
|  → Página completa en /artist/alice            |
â””------------------------------------------------

Capa 1: Plantillas Integradas

Para quién es: Creadores que no tienen su propio sitio web o no quieren mantener uno.

Implementación: Componentes Vue con variables CSS para temas.

// Ruta: /artist/:subdomain
routes.get('/artist/:subdomain', async (req, reply) => {
  const artist = await artistService.getBySubdomain(req.params.subdomain)

  if (artist.page_type === 'default') {
    // Renderizar SPA Vue integrada (o SSR)
    // El campo page_theme controla qué variante de plantilla usar
    return reply.sendFile('index.html')
  }
  // ... pasar a las capas 2-3 manejadas en otro lugar
})

Plantilla = Diseño × Paleta (ejes ortogonales) — la decisión de modelado más importante. Si dos "plantillas" difieren principalmente en color, NO son dos plantillas — son un diseño con dos combinaciones de colores. Agrupar el color en la dimensión de la plantilla infla el número de plantillas con código de estructura duplicado (los usuarios señalarán esto: "estas páginas se ven igual con diferentes colores"). Divide en ejes que el artista elige de forma independiente:

EjeQuién eligeEjemplosControla
Diseño (estructura)Artistaclásico (estudio de dos columnas) / galerÃa (museo a pantalla completa) / folio (página de aterrizaje simple) / taller (estilo libro cálido)Tratamiento de apertura, lógica de columnas, forma de galerÃa, navegación
Paleta (ambiente)Artistapapel / tinta / atardecer / musgo — cada una definida con variantes claro Y oscuroColores de fondo/superficie/texto de la página (variables CSS --pal-*)
Acento + modoVisitanteSelector de acento de 5 colores + preferencia claro/oscuro--color-primary, html.dark

Reglas clave:

  • Una paleta es un ambiente con variantes claro+oscuro, nunca "el tema oscuro". El artista elige tinta; los visitantes en modo claro ven gris pálido, los visitantes en modo oscuro ven carbón — el ambiente se mantiene consistente, el brillo sigue al visitante.
  • El color de acento (botones/enlaces/destacados) siempre usa la variable de acento del visitante (--color-primary), nunca las variables de paleta — los dos sistemas permanecen ortogonales y ambos siguen funcionando.
  • 4 diseños × 4 paletas = 16 aspectos distintos a partir de 8 definiciones.
  • Los diseños deben diferir estructuralmente (apertura diferente, lógica de columnas diferente) o deben fusionarse en un solo diseño.

Arquetipos de diseño que funcionan — cada uno abre con el activo más distintivo del artista (su arte), no un encabezado genérico centrado con nombre+bio+botones:

DiseñoAperturaMovimiento caracterÃstico
galerÃaObra de arte a pantalla completa, nombre como pequeña placa en la esquinaGalerÃa editorial alternando grande/pequeña, zoom al pasar el ratón, clic para previsualizar
clásicoBanner con obra destacada, nombre superpuesto al arteEscritorio de dos columnas, tarjeta de información fija con CTA de comisión siempre visible
folioPantalla dividida: texto a la izquierda, obras apiladas a la derechaResalte de navegación por desplazamiento, menú hamburguesa en móvil + CTA fijo en la parte inferior
tallerDisposición estilo libro cálida, tipografÃa serifFondo con textura de papel, márgenes editoriales, espacio en blanco generoso, centrado en el arte con ambiente literario

Arquitectura de kit compartido (probado): las plantillas nunca leen los nombres de campo del backend directamente — un composable adaptador (useArtistData(props)) proporciona imgUrl(), statusText() (i18n), socialLinks, heroArtwork; los componentes Tpl* reutilizables toman variantes mediante props (TplHero variant="banner|fullscreen|split", TplGallery layout="grid|editorial|masonry") con slots como puntos de extensión (#addons en la cuadrÃcula de niveles). Consulta references/template-system.md para el patrón completo, incluyendo correcciones de tiempo para componentes asÃncronos y mapeo de ID heredados.

Anulación del color de acento por parte del artista (patrón R49): El artista puede establecer un color de acento persistente (lista blanca de 5 colores, almacenado como artists.accent_color TEXT) que anula la elección del ThemePicker del visitante en su página pública. Implementación en ArtistHome.vue:

const ACCENT_INDEX = { '#34dbcb': '1', '#34c2db': '2', '#3498db': '3', '#346edb': '4', '#3445db': '5' }
const accentOverride = computed(() => {
  const raw = previewAccent.value || artist.value?.accentColor
  return raw ? (ACCENT_INDEX[String(raw).toLowerCase()] || null) : null
})
// watch(immediate) + onUnmounted: guardar/restaurar document.documentElement.dataset.accent

Decisiones clave: (1) Inyección del atributo global data-accent — NO se necesitan cambios de variables CSS por plantilla (theme.css ya tiene selectores data-accent="1"~"5" con variantes claro+oscuro). Añadir soporte de acento a las 4 plantillas = cero cambios en los archivos de plantilla. (2) El acento del artista tiene prioridad sobre el ThemePicker del visitante mientras está en la página del artista; la elección del visitante se restaura en onUnmounted. (3) El backend valida contra la lista blanca de 5 colores + null (limpiar); normalización toLowerCase() al escribir. (4) Interfaz de configuración: botones de muestra circulares (32px) con marca ✓ en el activo + botón "predeterminado" para limpiar. NO el-color-picker (el color libre romperÃa la lista blanca). (5) Decisión C61: el acento solo afecta a las páginas orientadas al cliente, NO al panel del artista.

Modo de vista previa mediante parámetros de URL (patrón R50): El botón de configuración "Vista previa de la página principal" abre /artist/:subdomain?_tpl=X&_pal=Y&_accent=Z en una nueva ventana. ArtistHome.vue lee route.query._tpl/_pal/_accent como anulaciones de la capa de renderizado (nunca escribe en la capa de datos). Un banner de advertencia fijo ("Modo de vista previa — los cambios no se han guardado") se muestra cuando hay algún parámetro de vista previa presente. Los computados de plantilla/paleta/acento recurren a: previewParam || artist.field || default. Seguro para acceso público — los parámetros de vista previa solo afectan al renderizado, todos los datos provienen de la API pública.

Línea de tiempo de actividad en lugar de lista plana de notas (patrón R40): Al fusionar registros de cambio de estado (notas del sistema) y notas del artista en una sola vista cronológica, usa el-timeline con diferenciación de tipo/hueco:

<el-timeline-item :type="note.created_by === 'system' ? 'info' : (note.image_path ? 'success' : 'primary')"
  :hollow="note.created_by === 'system'" :timestamp="formatDate(note.created_at)" placement="top">

Indicadores de tipo: 🔄 cambio de estado / 📝 nota / 🖼️ nota con imagen. Las notas del sistema obtienen la clase tl-item--system (color atenuado, fuente más pequeña). El botón de eliminar (R46) solo se muestra para notas que no son del sistema, se revela al pasar el ratón con respaldo táctil @media (hover: none). El cuadro de entrada se mueve a la parte inferior de la línea de tiempo. Cero cambios en el backend — las notas del sistema ya existen en order_notes con created_by='system'. La barra de operaciones (avanzar/revertir/cancelar) permanece FUERA de la tarjeta de la línea de tiempo.

Reemplazo por arrastre con comportamiento de confirmación según el contexto (R53 vs R55): Dos funciones de arrastre para reemplazar en la misma aplicación tienen REQUISITOS de confirmación DIFERENTES — NO las unifiques:

R53 Imagen de enfoque del tablero de colaR55 Imagen de ejemplo del nivel
Confirmación de sobrescrituraNo (la imagen antigua permanece en la galería del pedido)Sà — ElMessageBox.confirm (la imagen antigua se pierde)
RazónReemplazar el enfoque no es eliminar la imagenSobrescribir el ejemplo = imagen antigua irrecuperable
Implementación: infraestructura de arrastre compartida (dragover.prevent/dragleave con contains(relatedTarget) anti-parpadeo/drop.prevent + entrada de archivo oculta), pero la función de subida comprueba if (row.example_image) { await ElMessageBox.confirm(...) } solo para R55. Ambas reutilizan las API de subida existentes. La preview-src-list de el-image debe eliminarse de la imagen de enfoque de R53 para evitar que se trague el evento de clic (la misma trampa que R18).

Extracción de composable del formulario de pedido (patrón R58-1): Cuando el formulario de pedido supera las ~400 líneas (carga de datos + precios + validación + subida + envío + recuperación de borrador), extrae TODA la lógica de negocio en un composable compartido (useOrderForm.js). El componente de página se convierte en un diseño puro (~300 líneas de plantilla + estilo, el script es solo const of = useOrderForm(subdomain, formRef) + desestructuración). El composable devuelve un objeto plano de refs/reactivos/métodos agrupados por preocupación: carga de datos (artist, tiers, rulesContent, loading), estado del formulario + reglas de validación, estado de envío (submitting, showSuccess, resultNo, submit), imágenes de referencia (refFileList, handleRefUpload, handleRefRemove), precios (addonSelections, pricePreview, pricingExpanded, selectedTier, onTierChange) y HTML de reglas sanitizado. Por qué esto importa para múltiples plantillas: cuando la página de pedido soporta múltiples plantillas de diseño (paso a paso, estilo recibo, estilo revista), cada plantilla es una capa de diseño delgada que llama al MISMO composable — cero duplicación de lógica. El composable es el "núcleo", las plantillas son "pieles". Este es el mismo patrón de adaptador que useArtistData para la página principal, aplicado al formulario de pedido. Detalle clave de implementación: el composable toma formRef (la referencia de el-form de la plantilla) como parámetro porque la validación (formRef.value.validate()) debe llamar al elemento de formulario de la plantilla — el composable posee la lógica pero NO el DOM.

Diseño paso a paso del formulario de pedido (patrón R58-2, probado): El diseño de pedido predeterminado es un flujo guiado de tres pasos construido íntegramente sobre useOrderForm() (cero cambios en el composable de R58-1 — valida la extracción):

  • Indicador de paso: tres puntos numerados + líneas de conexión. Actual: agrandado + primario. Completado: ✓ + relleno primario. Pendiente: hueco gris. Móvil (≤860px): solo puntos.
  • Paso 1 — Elegir nivel: cuadrícula de tarjetas responsiva (repeat(auto-fill, minmax(240px, 1fr))) reemplazando el-select. Seleccionado: transición --ease-bounce + sello giratorio ✓ (@keyframes tier-stamp-in: escala 0→1.2→1, rotar -15°→0°). La divulgación progresiva R14 permanece dentro del paso 1.
  • Paso 2 — Describir + subir: área de texto + etiquetas de inspiración (R58-4: fichas predeterminadas codificadas, clic agrega con coma inteligente + truncamiento de 2000 caracteres) + subida de referencia (archivo + pegar sin cambios).
  • Paso 3 — Contacto: QQ + nombre + acuerdo de reglas.
  • Tarjeta de resumen fija: columna derecha en pantalla ancha (280px fija), nivel + desglose + total + cuotas en tiempo real. Móvil: parte inferior del paso activo.
  • Confirmación de recibo (R58-3): enviar → validar → el-dialog de recibo de cafetería (teleportado al body, espacio de nombres CSS global receipt-* en templates.css). Papel #fdfbf5, monoespaciado Courier, bordes en zigzag (repeating-conic-gradient), separadores discontinuos, decoración de código de barras, fichas de cuota. Tema claro fijo (metáfora del papel). Cancelar vuelve al paso 3; confirmar llama a submit().
  • Copiar información del pedido (R58-5): diálogo de éxito "Copiar información del pedido" → portapapeles de múltiples líneas (número de pedido + nivel + complementos + total).
  • Navegación de pasos: "Siguiente paso" valida el paso actual antes de avanzar; "Paso anterior" siempre permitido; sin saltos.

Página de pedido de múltiples plantillas (arquitectura R58): La página de pedido soporta plantillas de diseño seleccionables por el artista, independientes de la plantilla de la página principal. Backend (probado, migración v16): artists.order_template_id TEXT DEFAULT 'default' — validación de lista blanca en la capa de servicio (ORDER_TEMPLATES = ['default'], extender la matriz al agregar plantillas). GET /api/artists/:subdomain devuelve orderTemplateId: artist.order_template_id || 'default'. PUT /api/artist/profile acepta orderTemplateId (mapeado a order_template_id mediante keyMap). Los valores inválidos lanzan INVALID_ORDER_TEMPLATE (409). La cadena vacía vuelve a 'default'. Frontend: La página de configuración del artista muestra un selector de plantillas estilo tarjeta (mismo patrón de interfaz que la selección de plantilla de la página principal). Enrutamiento del lado del cliente: OrderForm.vue lee artist.orderTemplateId y renderiza el componente de diseño correspondiente mediante <component :is="..."> o un mapa de registro de plantillas. Todos los componentes de diseño comparten useOrderForm() — difieren SOLO en la disposición visual (indicadores de paso, diseño de tarjeta, estilo de diálogo de confirmación). Agregar una nueva plantilla de pedido = un nuevo archivo .vue con una capa de diseño + una entrada en la matriz de lista blanca del backend, cero cambios de lógica. Vista previa: reutilizar el mecanismo de parámetro URL _tpl de la vista previa de la página principal.

Patrón de contacto QQ (R58-6): Dos opciones para el contacto QQ: "Ir a QQ" (window.open('tencent://message/?uin=' + encodeURIComponent(qq), '_self')) + "Copiar QQ" (navigator.clipboard.writeText(qq) con notificación). Respaldo de portapapeles: en caso de error (permisos denegados, no HTTPS), mostrar ElMessage.warning(qq) — el número QQ como mensaje de advertencia, para que el usuario pueda copiarlo manualmente de la notificación. Aplicado en dos lugares: diálogo de éxito del lado del cliente (contactQq del artista) y detalle del pedido del lado del artista (qq del cliente). i18n: claves separadas por contexto (orderForm.jumpQq vs orderDetail.jumpQq) porque el mensaje de la notificación difiere ("QQ copiado" vs "QQ del cliente copiado").

Patrones de mejora de CSS (lote v0.16 batch1, probados):

  • Aceleración de rebote global: --ease-bounce: cubic-bezier(.34, 1.56, .64, 1) en :root. Usar solo para transiciones de transform/box-shadow (nunca opacity — no puede superar 1.0). Respaldo en el código consumidor: var(--ease-bounce, cubic-bezier(0.22, 1, 0.36, 1)).
  • Modelo físico de tres estados del botón: .el-button:hover:not(:disabled):not(.is-loading) { transform: translateY(-3px); box-shadow: más profundo } + :active { transform: translateY(-1px) scale(0.98) }. Las exclusiones :not() evitan que los botones deshabilitados/en carga se animen.
  • Tipografía fluida con clamp(): tamaños de fuente en clamp() para una escalabilidad suave sin puntos de interrupción. Ejemplo: font-size: clamp(1rem, 0.5rem + 2vw, 2rem).
  • Sombra de tarjeta con variante oscura: duplicar los valores de box-shadow con @media (prefers-color-scheme: dark) para la variante oscura (luz desde arriba a la derecha en lugar de abajo a la izquierda)." }```markdown

Skills relacionados