Skip to content

About

Invoice and quotation PDF widget for Medusa v2 admin — download order receipts and manage company billing config.

Resources

Stars

2 stars

Watchers

0 watching

Forks

Latest commit

 

History

24 Commits

Folders and files

Repository files navigation

@codemind.ec/medusa-plugin-invoice

Plugin de facturación y cotización PDF para Medusa v2 — genera facturas de pedido y proformas de cotización con plantillas HTML personalizables desde el admin.

Medusa v2 License: MIT


Features

  • Multi-empresa — registra múltiples empresas (InvoiceConfig) y asigna una por defecto. Cada plantilla puede vincularse a una empresa específica.
  • Generación de PDFs — facturas de pedido y proformas de cotización vía pdfmake + Handlebars/Puppeteer.
  • Variables de empresa unificadas — company_name, company_ruc, company_address, company_phone, company_email y company_logo_base64 están disponibles en todos los tipos de plantilla gracias a buildCompanyContext() en la clase base.
  • Plantillas HTML editables — CRUD completo de plantillas con preview en tiempo real, paleta de variables categorizada y selector de empresa.
  • Strategy Pattern — cada tipo de documento (factura, proforma) tiene su propia estrategia extensible.
  • Templates embebidos — plantillas por defecto incluidas en el código; restaurables con un clic.
  • Admin UI — dashboard "Comprobantes y Cotizaciones" con gestión de empresas, editor de plantillas con CodeMirror, widget de descarga en pedidos.

Requisitos

Requisito Versión
Node.js >= 20
Medusa >= 2.13.3 (compilado y probado con 2.21.2)

Instalación

pnpm add @codemind.ec/medusa-plugin-invoice

Configuración

1. Registrar el plugin y módulo en medusa-config.ts

import { defineConfig } from "@medusajs/framework/utils"
// Importar los tipos para autocompletado del editor
import type { InvoiceModuleOptions } from "@codemind.ec/medusa-plugin-invoice"

// Opcional: Importar la estrategia por defecto de facturas
import { OrderInvoiceStrategy, ORDER_INVOICE_META } from "@codemind.ec/medusa-plugin-invoice"

export default defineConfig({
  // ...
  plugins: [
    {
      resolve: "@codemind.ec/medusa-plugin-invoice",
      options: {},
    },
  ],
  modules: [
    {
      resolve: "@codemind.ec/medusa-plugin-invoice/modules/invoice-generator",
      // Castear a InvoiceModuleOptions habilitará el autocompletado en tu IDE
      options: {
        strategies: [
          // Puedes registrar la estrategia de comprobante integrada aquí
          { id: "order_invoice", strategy: OrderInvoiceStrategy, meta: ORDER_INVOICE_META },
          // Y/o registrar las tuyas propias:
          // { id: "quote_proforma", strategy: MiPropiaEstrategia, meta: MI_PROPIA_META },
        ],
      } as InvoiceModuleOptions,
    },
  ],
})

2. Variables de entorno (opcionales)

El loader del módulo puede leer estas variables para configurar los valores iniciales de la empresa al crear la base de datos por primera vez:

Variable Descripción Default
STORE_NAME Nombre de la empresa —
STORE_ADDRESS Dirección de la empresa —
STORE_PHONE Teléfono de la empresa —
ADMIN_EMAIL Email de notificaciones admin —
MEDUSA_BACKEND_URL URL base para embeber imágenes http://localhost:9000

Todos los valores se pueden editar después desde el admin.


Arquitectura

medusa-plugin-invoice/
├── src/
│   ├── admin/                                  # UI del admin (widgets + rutas)
│   │   ├── widgets/order-invoice.tsx           # Botón "Descargar comprobante" en pedidos
│   │   └── routes/invoice-config/
│   │       ├── page.tsx                        # Dashboard: Comprobantes y Cotizaciones
│   │       ├── companies/
│   │       │   ├── page.tsx                    # Lista de empresas
│   │       │   ├── new/page.tsx                # Crear empresa
│   │       │   └── [id]/page.tsx               # Editar empresa
│   │       └── invoice-templates/
│   │           ├── page.tsx                    # Lista de plantillas
│   │           └── [id]/page.tsx               # Editor de plantilla (CodeMirror + variables)
│   ├── api/admin/
│   │   ├── invoice-config/                     # CRUD multi-empresa
│   │   │   ├── route.ts                        # GET (listar) / POST (crear)
│   │   │   └── [id]/
│   │   │       ├── route.ts                    # GET / POST / DELETE por ID
│   │   │       └── set-default/route.ts        # POST marcar como default
│   │   └── invoice-templates/                  # CRUD de plantillas + preview + restore
│   └── modules/
│       └── invoice-generator/
│           ├── models/                         # Invoice, InvoiceConfig, InvoiceTemplate
│           ├── templates/
│           │   ├── strategy.ts                 # BaseDocumentStrategy + buildCompanyContext()
│           │   ├── order-invoice.ts            # Estrategia de factura de pedido
│           │   ├── quote-proforma.ts           # Estrategia de proforma de cotización
│           │   └── defaults/index.ts           # HTML embebido de plantillas por defecto
│           ├── loaders/                        # Seed de config y plantillas iniciales
│           ├── migrations/                     # Migraciones de base de datos
│           ├── service.ts                      # InvoiceGeneratorService
│           └── index.ts                        # INVOICE_MODULE export
└── index.d.ts                                  # Declaraciones de tipos

Separación de responsabilidades

Capa Ubicación Responsabilidad
Módulo (modelos, servicio, migraciones, loader) Plugin Datos y lógica de dominio aislada
API routes Plugin Endpoints CRUD del módulo (config, plantillas)
Admin UI Plugin Widgets y páginas de administración
Workflows / Steps App (apps/medusa/) Orquestación cross-module
Subscribers App (apps/medusa/) Reacción a eventos del sistema

¿Por qué los workflows NO están en el plugin?

Los workflows son orquestación cross-module por naturaleza. Por ejemplo, generate-invoice-pdf consulta el módulo order (via query graph), resuelve países del módulo country, verifica metadata de cotizaciones desde tipos de la app, y luego llama al módulo invoice. Moverlos al plugin crearía dependencias circulares (plugin → app) y eliminaría la capacidad de la app de personalizar la orquestación. Esta es la convención recomendada por Medusa v2: los plugins exponen módulos + API, las apps controlan la orquestación.


API Routes

Invoice Config (Multi-empresa)

Método Ruta Descripción
GET /admin/invoice-config Listar todas las empresas
POST /admin/invoice-config Crear nueva empresa
GET /admin/invoice-config/:id Obtener empresa por ID
POST /admin/invoice-config/:id Actualizar empresa
DELETE /admin/invoice-config/:id Eliminar empresa
POST /admin/invoice-config/:id/set-default Marcar empresa como predeterminada

Invoice Templates

Método Ruta Descripción
GET /admin/invoice-templates Listar plantillas
POST /admin/invoice-templates Crear plantilla
GET /admin/invoice-templates/:id Obtener plantilla
POST /admin/invoice-templates/:id Actualizar plantilla
DELETE /admin/invoice-templates/:id Eliminar plantilla (no defaults)
POST /admin/invoice-templates/:id/preview Generar PDF de preview
POST /admin/invoice-templates/:id/restore Restaurar HTML por defecto

Uso desde la app

Imports disponibles

// Módulo, servicio y opciones de configuración
import { INVOICE_MODULE, InvoiceGeneratorService } from "@codemind.ec/medusa-plugin-invoice"
import type { InvoiceModuleOptions } from "@codemind.ec/medusa-plugin-invoice"

// Tipos de factura de pedido
import type { InvoiceOrder, InvoiceLineItem, InvoiceOrderAddress, OrderInvoiceInput } from "@codemind.ec/medusa-plugin-invoice"

// Enums
import { InvoiceStatus } from "@codemind.ec/medusa-plugin-invoice"

Generar un PDF desde un workflow/step

const invoiceService = container.resolve<InvoiceGeneratorService>(INVOICE_MODULE)

// Factura de pedido
const pdfBuffer = await invoiceService.generatePdf({
  template: "order_invoice",
  data: { order, items, invoiceDisplayId: 1, created_at: "2025-01-01" },
  invoice_id: "inv_01ABC...",
})

// Proforma de cotización
const pdfBuffer = await invoiceService.generatePdf({
  template: "quote_proforma",
  data: { serviceType: "Catering", /* ... */ },
})

Extensibilidad

Registrar una nueva estrategia de documento

// mi-estrategia.ts
import { BaseDocumentStrategy, type StrategyRegistrationMeta } from "@codemind.ec/medusa-plugin-invoice"

export class MyCustomStrategy extends BaseDocumentStrategy<MyInput> {
  async buildDocumentDefinition(input, config, htmlTemplate?) {
    // buildCompanyContext() inyecta automáticamente las 6 variables de empresa
    const companyCtx = await this.buildCompanyContext(config)

    if (htmlTemplate) {
      return this.renderHtmlTemplate(htmlTemplate, { ...companyCtx, ...input })
    }
    // ... construir doc pdfmake programáticamente
  }
}

export const MY_CUSTOM_META: StrategyRegistrationMeta = {
  label: "Mi Documento Custom",
  // Opcional:
  // variableCategories: [ ... ]
  // buildSampleData: () => ({ ... })
}

Luego regístrala en medusa-config.ts dentro de options.strategies:

// medusa-config.ts
import { MyCustomStrategy, MY_CUSTOM_META } from "./src/strategies/mi-estrategia.ts"

// ...
modules: [
  {
    resolve: "@codemind.ec/medusa-plugin-invoice/modules/invoice-generator",
    options: {
      strategies: [
        { id: "my_custom_doc", strategy: MyCustomStrategy, meta: MY_CUSTOM_META }
      ]
    } as InvoiceModuleOptions,
  }
]

Variables de empresa disponibles en todas las plantillas

buildCompanyContext(config) retorna:

Variable Descripción
company_name Nombre de la empresa
company_ruc RUC / NIT / identificador fiscal
company_address Dirección de la empresa
company_phone Teléfono
company_email Email
company_logo_base64 Logo en base64 (data URI)

Resolución de empresa en plantillas

Cuando se genera un PDF, el servicio resuelve la empresa con esta prioridad:

  1. template.company_id — si la plantilla tiene una empresa asignada.
  2. is_default = true — la empresa marcada como predeterminada.
  3. Primera empresa — fallback al primer registro.

Changelog

1.4.0

  • Compilado contra Medusa 2.21.2 — dependencias de desarrollo actualizadas a Medusa 2.21.2 (@medusajs/ui 4.2.6, Vite 7). Las peerDependencies de Medusa pasan de 2.13.3 exacto a ^2.13.3, por lo que el plugin sigue funcionando en 2.13.x y ahora también en 2.14–2.21.
  • Validadores compatibles con Zod v4 — z.record() declara la clave explícitamente (Medusa 2.14+ usa Zod v4); sigue siendo compatible con Zod v3 en Medusa 2.13.
  • Dependencias actualizadas dentro de su versión mayor (axios, handlebars, pdfmake 0.2.x, puppeteer-core 24.x, CodeMirror).

1.3.11

  • PDF HTML mas resistente en Docker — el render relanza Chromium por intento, cubre fallos durante newPage(), aumenta el timeout de protocolo y agrega flags de ejecución más estables para contenedores restringidos.

1.3.10

  • PDF HTML mas seguro — el render con Puppeteer intercepta requests y bloquea archivos locales distintos al HTML temporal, protocolos no permitidos y URLs malformadas durante la generación del PDF.

1.3.9

  • PDF HTML sin setContent() — renderHtmlToPdf() ahora escribe el HTML en un archivo temporal y navega a file://, con reintentos, para evitar frames detached de Puppeteer durante page.setContent().

1.3.8

  • PDF HTML estable al abrir Chromium — renderHtmlToPdf() espera explícitamente a que Puppeteer exponga el frame principal antes de llamar page.setContent(), evitando Requesting main frame too early!.

1.3.7

  • PDF HTML estable en contenedores — renderHtmlToPdf() vuelve a usar page.setContent() en lugar de CDP directo para evitar errores Connection closed y Target closed al generar comprobantes.

1.3.5

  • PDF HTML mas estable — renderHtmlToPdf() espera a que Puppeteer inicialice el frame principal antes de llamar page.setContent(), evitando errores Requesting main frame too early! al descargar comprobantes.

1.3.1

  • Preview HTML unificado — el preview del editor ahora reutiliza renderHtmlToPdf() del servicio en lugar de duplicar el flujo de Puppeteer.
  • Typings públicos mejorados — InvoiceGeneratorService expone renderHtmlToPdf() y se documenta mejor la configuración extensible vía InvoiceModuleOptions.
  • Documentación actualizada — ejemplos de registro de estrategias y uso del plugin alineados con la API pública actual.

1.2.0

  • Variables de empresa unificadas — buildCompanyContext() en BaseDocumentStrategy garantiza que las 6 variables de empresa (company_name, company_ruc, company_address, company_phone, company_email, company_logo_base64) estén disponibles en todos los tipos de plantilla (factura y proforma).
  • Paleta de variables categorizada en el editor ahora muestra las mismas variables de empresa para ambos tipos de documento.
  • Datos de preview actualizados para proforma con campos de empresa completos.

1.1.0

  • Soporte multi-empresa — múltiples configuraciones de empresa con is_default.
  • Asociación plantilla-empresa — campo company_id en plantillas para vincular a una empresa específica.
  • API CRUD completa — nuevos endpoints GET/POST/DELETE /admin/invoice-config/:id y POST /admin/invoice-config/:id/set-default.
  • Dashboard renovado — página principal "Comprobantes y Cotizaciones" con tarjetas de navegación.
  • Gestión de empresas — páginas de listado, creación y edición de empresas.
  • Editor de plantillas mejorado — paleta de variables categorizada, selector de empresa, preview con logo placeholder.
  • Migración automática para is_default y company_id.

1.0.0

  • Release inicial: generación PDF, plantillas editables, configuración de empresa, strategy pattern.

License

MIT — CodeMind

About

Invoice and quotation PDF widget for Medusa v2 admin — download order receipts and manage company billing config.

Resources

Stars

2 stars

Watchers

0 watching

Forks

Releases

Packages

Contributors

Languages