@codemind.ec/medusa-plugin-invoice 1.0.0 → 1.0.1

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,11 +1,17 @@
1
1
  import { jsxs, jsx } from "react/jsx-runtime";
2
2
  import { defineWidgetConfig, defineRouteConfig } from "@medusajs/admin-sdk";
3
- import { Container, Heading, Text, Button, toast, Label, Input, Textarea } from "@medusajs/ui";
3
+ import { Container, Heading, Text, Button, toast, Table, Badge, Label, Input, Textarea, Select } from "@medusajs/ui";
4
4
  import Medusa from "@medusajs/js-sdk";
5
- import { useState, useCallback, useEffect } from "react";
6
- import { useQuery, useMutation } from "@tanstack/react-query";
5
+ import { useState, useCallback, useEffect, useRef, useMemo } from "react";
6
+ import { DocumentText } from "@medusajs/icons";
7
+ import { useQueryClient, useQuery, useMutation } from "@tanstack/react-query";
8
+ import { useNavigate, useParams } from "react-router-dom";
7
9
  import { useForm, FormProvider, Controller } from "react-hook-form";
8
10
  import { z } from "@medusajs/framework/zod";
11
+ import CodeMirror from "@uiw/react-codemirror";
12
+ import { html } from "@codemirror/lang-html";
13
+ import { oneDark } from "@codemirror/theme-one-dark";
14
+ import Handlebars from "handlebars";
9
15
  import "@medusajs/admin-shared";
10
16
  const sdk = new Medusa({
11
17
  baseUrl: "/",
@@ -61,17 +67,94 @@ const OrderInvoiceWidget = ({ data: order }) => {
61
67
  defineWidgetConfig({
62
68
  zone: "order.details.side.before"
63
69
  });
70
+ const TYPE_LABELS = {
71
+ order_invoice: "Comprobante de Pedido",
72
+ quote_proforma: "Cotización Proforma"
73
+ };
74
+ const InvoiceTemplatesPage = () => {
75
+ const navigate = useNavigate();
76
+ const queryClient = useQueryClient();
77
+ const { data, isLoading } = useQuery({
78
+ queryFn: () => sdk.client.fetch("/admin/invoice-templates"),
79
+ queryKey: ["invoice-templates"]
80
+ });
81
+ const deleteMutation = useMutation({
82
+ mutationFn: (id) => sdk.client.fetch(`/admin/invoice-templates/${id}`, { method: "DELETE" }),
83
+ onSuccess: () => {
84
+ queryClient.invalidateQueries({ queryKey: ["invoice-templates"] });
85
+ toast.success("Plantilla eliminada");
86
+ },
87
+ onError: () => {
88
+ toast.error("No se pudo eliminar la plantilla");
89
+ }
90
+ });
91
+ const templates = (data == null ? void 0 : data.invoice_templates) ?? [];
92
+ return /* @__PURE__ */ jsxs(Container, { children: [
93
+ /* @__PURE__ */ jsxs("div", { style: { display: "flex", justifyContent: "space-between", alignItems: "center", marginBottom: 24 }, children: [
94
+ /* @__PURE__ */ jsx(Heading, { level: "h1", children: "Plantillas de Documentos" }),
95
+ /* @__PURE__ */ jsxs("div", { style: { display: "flex", gap: 8 }, children: [
96
+ /* @__PURE__ */ jsx(Button, { variant: "secondary", onClick: () => navigate("/invoice-config"), children: "Configuración" }),
97
+ /* @__PURE__ */ jsx(Button, { onClick: () => navigate("/invoice-templates/new"), children: "+ Nueva Plantilla" })
98
+ ] })
99
+ ] }),
100
+ isLoading ? /* @__PURE__ */ jsx("div", { style: { textAlign: "center", padding: 40 }, children: "Cargando..." }) : templates.length === 0 ? /* @__PURE__ */ jsx("div", { style: { textAlign: "center", padding: 40, color: "#6c757d" }, children: "No hay plantillas. Se crearán automáticamente al reiniciar el servidor." }) : /* @__PURE__ */ jsxs(Table, { children: [
101
+ /* @__PURE__ */ jsx(Table.Header, { children: /* @__PURE__ */ jsxs(Table.Row, { children: [
102
+ /* @__PURE__ */ jsx(Table.HeaderCell, { children: "Nombre" }),
103
+ /* @__PURE__ */ jsx(Table.HeaderCell, { children: "Slug" }),
104
+ /* @__PURE__ */ jsx(Table.HeaderCell, { children: "Tipo" }),
105
+ /* @__PURE__ */ jsx(Table.HeaderCell, { children: "Estado" }),
106
+ /* @__PURE__ */ jsx(Table.HeaderCell, { children: "Actualizado" }),
107
+ /* @__PURE__ */ jsx(Table.HeaderCell, { style: { textAlign: "right" }, children: "Acciones" })
108
+ ] }) }),
109
+ /* @__PURE__ */ jsx(Table.Body, { children: templates.map((tpl) => /* @__PURE__ */ jsxs(
110
+ Table.Row,
111
+ {
112
+ onClick: () => navigate(`/invoice-templates/${tpl.id}`),
113
+ style: { cursor: "pointer" },
114
+ children: [
115
+ /* @__PURE__ */ jsx(Table.Cell, { children: tpl.name }),
116
+ /* @__PURE__ */ jsx(Table.Cell, { children: /* @__PURE__ */ jsx("code", { style: { fontSize: 12 }, children: tpl.slug }) }),
117
+ /* @__PURE__ */ jsx(Table.Cell, { children: TYPE_LABELS[tpl.type] ?? tpl.type }),
118
+ /* @__PURE__ */ jsx(Table.Cell, { children: tpl.is_default ? /* @__PURE__ */ jsx(Badge, { color: "blue", children: "Por defecto" }) : /* @__PURE__ */ jsx(Badge, { color: "grey", children: "Personalizada" }) }),
119
+ /* @__PURE__ */ jsx(Table.Cell, { children: new Date(tpl.updated_at).toLocaleDateString("es-ES") }),
120
+ /* @__PURE__ */ jsx(Table.Cell, { style: { textAlign: "right" }, children: /* @__PURE__ */ jsx(
121
+ Button,
122
+ {
123
+ variant: "danger",
124
+ size: "small",
125
+ disabled: tpl.is_default,
126
+ onClick: (e) => {
127
+ e.stopPropagation();
128
+ if (confirm("¿Eliminar esta plantilla?")) {
129
+ deleteMutation.mutate(tpl.id);
130
+ }
131
+ },
132
+ children: "Eliminar"
133
+ }
134
+ ) })
135
+ ]
136
+ },
137
+ tpl.id
138
+ )) })
139
+ ] })
140
+ ] });
141
+ };
142
+ const config$1 = defineRouteConfig({
143
+ label: "Plantillas PDF",
144
+ icon: DocumentText
145
+ });
64
146
  z.object({
65
147
  company_name: z.string().optional(),
66
148
  company_ruc: z.string().optional(),
67
149
  company_address: z.string().optional(),
68
150
  company_phone: z.string().optional(),
69
- company_email: z.string().email().optional(),
70
- company_logo: z.string().url().optional(),
151
+ company_email: z.string().email().optional().or(z.literal("")),
152
+ company_logo: z.string().optional(),
71
153
  notes: z.string().optional(),
72
154
  admin_notification_email: z.string().email().optional().or(z.literal(""))
73
155
  });
74
156
  const InvoiceConfigPage = () => {
157
+ const navigate = useNavigate();
75
158
  const { data, isLoading, refetch } = useQuery({
76
159
  queryFn: () => sdk.client.fetch("/admin/invoice-config"),
77
160
  queryKey: ["invoice-config"]
@@ -84,6 +167,9 @@ const InvoiceConfigPage = () => {
84
167
  onSuccess: () => {
85
168
  refetch();
86
169
  toast.success("Configuración actualizada exitosamente");
170
+ },
171
+ onError: () => {
172
+ toast.error("Error al guardar la configuración");
87
173
  }
88
174
  });
89
175
  const getFormDefaultValues = useCallback(() => {
@@ -108,16 +194,23 @@ const InvoiceConfigPage = () => {
108
194
  if (!file) {
109
195
  return;
110
196
  }
111
- const { files } = await sdk.admin.upload.create({
112
- files: [file]
113
- });
114
- form.setValue("company_logo", files[0].url);
197
+ try {
198
+ const { files } = await sdk.admin.upload.create({
199
+ files: [file]
200
+ });
201
+ form.setValue("company_logo", files[0].url);
202
+ } catch (error) {
203
+ toast.error("Error al subir el logo. Verifica que el archivo sea una imagen válida.");
204
+ }
115
205
  };
116
206
  useEffect(() => {
117
207
  form.reset(getFormDefaultValues());
118
208
  }, [getFormDefaultValues]);
119
209
  return /* @__PURE__ */ jsxs(Container, { className: "divide-y p-0", children: [
120
- /* @__PURE__ */ jsx("div", { className: "flex items-center justify-between px-6 py-4", children: /* @__PURE__ */ jsx(Heading, { level: "h1", children: "Configuración de Comprobante" }) }),
210
+ /* @__PURE__ */ jsxs("div", { className: "flex items-center justify-between px-6 py-4", children: [
211
+ /* @__PURE__ */ jsx(Heading, { level: "h1", children: "Configuración de Comprobante" }),
212
+ /* @__PURE__ */ jsx(Button, { variant: "secondary", onClick: () => navigate("/invoice-templates"), children: "Gestionar Plantillas" })
213
+ ] }),
121
214
  /* @__PURE__ */ jsx(FormProvider, { ...form, children: /* @__PURE__ */ jsxs(
122
215
  "form",
123
216
  {
@@ -246,6 +339,396 @@ const InvoiceConfigPage = () => {
246
339
  const config = defineRouteConfig({
247
340
  label: "Comprobante de Pedido"
248
341
  });
342
+ const NewTemplatePage = () => {
343
+ const navigate = useNavigate();
344
+ const [name, setName] = useState("");
345
+ const [slug, setSlug] = useState("");
346
+ const [type, setType] = useState("order_invoice");
347
+ const createMutation = useMutation({
348
+ mutationFn: (payload) => sdk.client.fetch("/admin/invoice-templates", {
349
+ method: "POST",
350
+ body: payload
351
+ }),
352
+ onSuccess: (data) => {
353
+ toast.success("Plantilla creada");
354
+ navigate(`/invoice-templates/${data.invoice_template.id}`);
355
+ },
356
+ onError: () => {
357
+ toast.error("Error al crear la plantilla");
358
+ }
359
+ });
360
+ const handleCreate = () => {
361
+ if (!name || !slug) {
362
+ toast.error("Nombre y slug son requeridos");
363
+ return;
364
+ }
365
+ createMutation.mutate({
366
+ name,
367
+ slug,
368
+ type,
369
+ html_content: getDefaultHtml(type)
370
+ });
371
+ };
372
+ return /* @__PURE__ */ jsxs(Container, { children: [
373
+ /* @__PURE__ */ jsx(Heading, { level: "h1", style: { marginBottom: 24 }, children: "Nueva Plantilla" }),
374
+ /* @__PURE__ */ jsxs("div", { style: { display: "flex", flexDirection: "column", gap: 16, maxWidth: 500 }, children: [
375
+ /* @__PURE__ */ jsxs("div", { children: [
376
+ /* @__PURE__ */ jsx(Label, { htmlFor: "name", children: "Nombre" }),
377
+ /* @__PURE__ */ jsx(Input, { id: "name", value: name, onChange: (e) => setName(e.target.value), placeholder: "Ej: Mi Comprobante" })
378
+ ] }),
379
+ /* @__PURE__ */ jsxs("div", { children: [
380
+ /* @__PURE__ */ jsx(Label, { htmlFor: "slug", children: "Slug (identificador único)" }),
381
+ /* @__PURE__ */ jsx(
382
+ Input,
383
+ {
384
+ id: "slug",
385
+ value: slug,
386
+ onChange: (e) => setSlug(e.target.value.toLowerCase().replace(/[^a-z0-9_]/g, "_")),
387
+ placeholder: "Ej: my_custom_invoice"
388
+ }
389
+ )
390
+ ] }),
391
+ /* @__PURE__ */ jsxs("div", { children: [
392
+ /* @__PURE__ */ jsx(Label, { htmlFor: "type", children: "Tipo de documento" }),
393
+ /* @__PURE__ */ jsxs(Select, { value: type, onValueChange: setType, children: [
394
+ /* @__PURE__ */ jsx(Select.Trigger, { children: /* @__PURE__ */ jsx(Select.Value, { placeholder: "Seleccionar tipo" }) }),
395
+ /* @__PURE__ */ jsxs(Select.Content, { children: [
396
+ /* @__PURE__ */ jsx(Select.Item, { value: "order_invoice", children: "Comprobante de Pedido" }),
397
+ /* @__PURE__ */ jsx(Select.Item, { value: "quote_proforma", children: "Cotización Proforma" })
398
+ ] })
399
+ ] })
400
+ ] }),
401
+ /* @__PURE__ */ jsxs("div", { style: { display: "flex", gap: 8, marginTop: 16 }, children: [
402
+ /* @__PURE__ */ jsx(Button, { variant: "secondary", onClick: () => navigate("/invoice-templates"), children: "Cancelar" }),
403
+ /* @__PURE__ */ jsx(Button, { onClick: handleCreate, isLoading: createMutation.isPending, children: "Crear Plantilla" })
404
+ ] })
405
+ ] })
406
+ ] });
407
+ };
408
+ function getDefaultHtml(type) {
409
+ if (type === "quote_proforma") {
410
+ return `<!DOCTYPE html>
411
+ <html>
412
+ <head><style>body { font-family: Helvetica, sans-serif; }</style></head>
413
+ <body>
414
+ <h1>COTIZACIÓN {{quote_number}}</h1>
415
+ <p>Fecha: {{date_str}}</p>
416
+ <p>Servicio: {{service_name}}</p>
417
+ <!-- Personaliza tu plantilla aquí -->
418
+ </body>
419
+ </html>`;
420
+ }
421
+ return `<!DOCTYPE html>
422
+ <html>
423
+ <head><style>body { font-family: Helvetica, sans-serif; }</style></head>
424
+ <body>
425
+ <h1>COMPROBANTE {{invoice_id}}</h1>
426
+ <p>Fecha: {{invoice_date}}</p>
427
+ <p>Empresa: {{company_name}}</p>
428
+ <!-- Personaliza tu plantilla aquí -->
429
+ </body>
430
+ </html>`;
431
+ }
432
+ const VARIABLES = {
433
+ order_invoice: [
434
+ "company_name",
435
+ "company_ruc",
436
+ "company_address",
437
+ "company_phone",
438
+ "company_email",
439
+ "company_logo_base64",
440
+ "invoice_id",
441
+ "invoice_date",
442
+ "order_display_id",
443
+ "order_date",
444
+ "billing_address",
445
+ "shipping_address",
446
+ "cedula",
447
+ "subtotal",
448
+ "tax_total",
449
+ "shipping_total",
450
+ "discount_total",
451
+ "total",
452
+ "is_home_delivery",
453
+ "notes",
454
+ "{{#each items}}",
455
+ "this.title",
456
+ "this.variant_title",
457
+ "this.quantity",
458
+ "this.unit_price",
459
+ "this.total",
460
+ "{{/each}}"
461
+ ],
462
+ quote_proforma: [
463
+ "company_name",
464
+ "company_website",
465
+ "quote_number",
466
+ "date_str",
467
+ "service_name",
468
+ "is_home_delivery",
469
+ "{{#each config_fields}}",
470
+ "this.label",
471
+ "this.value",
472
+ "{{/each}}",
473
+ "{{#each breakdown}}",
474
+ "this.label",
475
+ "this.total_formatted",
476
+ "{{/each}}",
477
+ "totals.subtotal_formatted",
478
+ "totals.extras_formatted",
479
+ "totals.discount_formatted",
480
+ "totals.shipping_formatted",
481
+ "totals.taxes_provided",
482
+ "totals.taxes_formatted",
483
+ "totals.total_formatted",
484
+ "{{#each includes_left}}",
485
+ "{{#each includes_right}}",
486
+ "{{#each contact_rows}}",
487
+ "this.label",
488
+ "this.value",
489
+ "{{/each}}"
490
+ ]
491
+ };
492
+ const SAMPLE_DATA = {
493
+ order_invoice: {
494
+ company_name: "Mi Empresa",
495
+ company_ruc: "1234567890001",
496
+ company_address: "Av. Principal 123, Quito",
497
+ company_phone: "+593 99 123 4567",
498
+ company_email: "info@miempresa.com",
499
+ company_logo_base64: "",
500
+ invoice_id: "INV-000001",
501
+ invoice_date: "19/06/2025",
502
+ order_display_id: "000042",
503
+ order_date: "18/06/2025",
504
+ billing_address: "Juan Pérez\nCalle Ejemplo 456\nQuito, Pichincha",
505
+ shipping_address: "Juan Pérez\nCalle Ejemplo 456\nQuito, Pichincha",
506
+ cedula: "1712345678",
507
+ items: [
508
+ { title: "Producto ejemplo", variant_title: "Grande", quantity: "2", unit_price: "$25.00", total: "$50.00" },
509
+ { title: "Otro producto", variant_title: "", quantity: "1", unit_price: "$15.00", total: "$15.00" }
510
+ ],
511
+ subtotal: "$65.00",
512
+ tax_total: "$7.80",
513
+ shipping_total: "$5.00",
514
+ discount_total: "",
515
+ total: "$77.80",
516
+ is_home_delivery: false,
517
+ notes: "Gracias por su compra."
518
+ },
519
+ quote_proforma: {
520
+ company_name: "Mi Empresa",
521
+ company_website: "www.miempresa.com",
522
+ quote_number: "QP-2025-0001",
523
+ date_str: "19/06/2025",
524
+ service_name: "SERVICIO DE EJEMPLO",
525
+ config_fields: [
526
+ { label: "Nro. personas", value: "50" },
527
+ { label: "Menú", value: "Premium" }
528
+ ],
529
+ breakdown: [
530
+ { label: "Servicio base", total_formatted: "$500.00" },
531
+ { label: "Extras", total_formatted: "$100.00" }
532
+ ],
533
+ totals: {
534
+ subtotal_formatted: "$500.00",
535
+ extras_formatted: "$100.00",
536
+ discount: 0,
537
+ discount_formatted: "",
538
+ shipping: 25,
539
+ shipping_formatted: "$25.00",
540
+ taxes_provided: true,
541
+ taxes_formatted: "$75.00",
542
+ total_formatted: "700.00"
543
+ },
544
+ is_home_delivery: false,
545
+ includes: ["Vajilla", "Mantelería", "Personal", "Montaje"],
546
+ includes_left: ["Vajilla", "Personal"],
547
+ includes_right: ["Mantelería", "Montaje"],
548
+ contact_rows: [
549
+ { label: "Nombre", value: "María García" },
550
+ { label: "Email", value: "maria@ejemplo.com" }
551
+ ]
552
+ }
553
+ };
554
+ const TemplateEditorPage = () => {
555
+ const { id } = useParams();
556
+ const navigate = useNavigate();
557
+ const queryClient = useQueryClient();
558
+ const editorRef = useRef(null);
559
+ const { data, isLoading } = useQuery({
560
+ queryFn: () => sdk.client.fetch(`/admin/invoice-templates/${id}`),
561
+ queryKey: ["invoice-template", id],
562
+ enabled: !!id
563
+ });
564
+ const template = data == null ? void 0 : data.invoice_template;
565
+ const [name, setName] = useState("");
566
+ const [htmlContent, setHtmlContent] = useState("");
567
+ const [showVariables, setShowVariables] = useState(false);
568
+ useEffect(() => {
569
+ if (template) {
570
+ setName(template.name);
571
+ setHtmlContent(template.html_content);
572
+ }
573
+ }, [template]);
574
+ const saveMutation = useMutation({
575
+ mutationFn: (payload) => sdk.client.fetch(`/admin/invoice-templates/${id}`, {
576
+ method: "POST",
577
+ body: payload
578
+ }),
579
+ onSuccess: () => {
580
+ queryClient.invalidateQueries({ queryKey: ["invoice-template", id] });
581
+ toast.success("Plantilla guardada");
582
+ },
583
+ onError: () => {
584
+ toast.error("Error al guardar");
585
+ }
586
+ });
587
+ const handleSave = () => {
588
+ saveMutation.mutate({ name, html_content: htmlContent });
589
+ };
590
+ const handlePreviewPdf = async () => {
591
+ try {
592
+ const response = await fetch(`/admin/invoice-templates/${id}/preview`, {
593
+ method: "POST",
594
+ headers: { "Content-Type": "application/json" },
595
+ credentials: "include"
596
+ });
597
+ if (!response.ok) throw new Error("Preview failed");
598
+ const blob = await response.blob();
599
+ const url = URL.createObjectURL(blob);
600
+ window.open(url, "_blank");
601
+ } catch {
602
+ toast.error("Error al generar preview PDF");
603
+ }
604
+ };
605
+ const handleRestoreDefault = async () => {
606
+ if (!(template == null ? void 0 : template.is_default)) return;
607
+ if (!confirm("¿Restaurar la plantilla a su contenido original?")) return;
608
+ try {
609
+ const res = await sdk.client.fetch(
610
+ `/admin/invoice-templates/${id}/restore`,
611
+ { method: "POST" }
612
+ );
613
+ toast.success("Plantilla restaurada");
614
+ queryClient.invalidateQueries({ queryKey: ["invoice-template", id] });
615
+ } catch {
616
+ toast.error("Error al restaurar. La función de restaurar aún no está implementada.");
617
+ }
618
+ };
619
+ const previewHtml = useMemo(() => {
620
+ if (!htmlContent || !template) return "";
621
+ try {
622
+ const compiled = Handlebars.compile(htmlContent);
623
+ return compiled(SAMPLE_DATA[template.type] ?? {});
624
+ } catch {
625
+ return `<div style="color:red;padding:20px;">Error en la plantilla Handlebars</div>`;
626
+ }
627
+ }, [htmlContent, template]);
628
+ const insertVariable = useCallback((variable) => {
629
+ const isBlock = variable.startsWith("{{#") || variable.startsWith("{{/");
630
+ const insertion = isBlock ? variable : `{{${variable}}}`;
631
+ setHtmlContent((prev) => prev + insertion);
632
+ }, []);
633
+ const availableVars = template ? VARIABLES[template.type] ?? [] : [];
634
+ if (isLoading) {
635
+ return /* @__PURE__ */ jsx(Container, { children: /* @__PURE__ */ jsx("div", { style: { textAlign: "center", padding: 40 }, children: "Cargando..." }) });
636
+ }
637
+ if (!template) {
638
+ return /* @__PURE__ */ jsx(Container, { children: /* @__PURE__ */ jsx("div", { style: { textAlign: "center", padding: 40 }, children: "Plantilla no encontrada" }) });
639
+ }
640
+ return /* @__PURE__ */ jsxs(Container, { children: [
641
+ /* @__PURE__ */ jsxs("div", { style: { display: "flex", justifyContent: "space-between", alignItems: "center", marginBottom: 16 }, children: [
642
+ /* @__PURE__ */ jsxs("div", { style: { display: "flex", alignItems: "center", gap: 12 }, children: [
643
+ /* @__PURE__ */ jsx(Button, { variant: "secondary", size: "small", onClick: () => navigate("/invoice-templates"), children: "← Volver" }),
644
+ /* @__PURE__ */ jsx("div", { children: /* @__PURE__ */ jsx(
645
+ Input,
646
+ {
647
+ value: name,
648
+ onChange: (e) => setName(e.target.value),
649
+ style: { fontWeight: "bold", fontSize: 16 }
650
+ }
651
+ ) }),
652
+ /* @__PURE__ */ jsxs("code", { style: { fontSize: 12, color: "#6c757d" }, children: [
653
+ template.slug,
654
+ " (",
655
+ template.type,
656
+ ")"
657
+ ] })
658
+ ] }),
659
+ /* @__PURE__ */ jsxs("div", { style: { display: "flex", gap: 8 }, children: [
660
+ template.is_default && /* @__PURE__ */ jsx(Button, { variant: "secondary", size: "small", onClick: handleRestoreDefault, children: "Restaurar Default" }),
661
+ /* @__PURE__ */ jsx(Button, { variant: "secondary", size: "small", onClick: () => setShowVariables(!showVariables), children: showVariables ? "Ocultar Variables" : "Variables" }),
662
+ /* @__PURE__ */ jsx(Button, { variant: "secondary", size: "small", onClick: handlePreviewPdf, children: "Preview PDF" }),
663
+ /* @__PURE__ */ jsx(Button, { onClick: handleSave, isLoading: saveMutation.isPending, children: "Guardar" })
664
+ ] })
665
+ ] }),
666
+ showVariables && /* @__PURE__ */ jsxs("div", { style: {
667
+ background: "#f8f9fa",
668
+ border: "1px solid #e2e8f0",
669
+ borderRadius: 8,
670
+ padding: 12,
671
+ marginBottom: 16,
672
+ display: "flex",
673
+ flexWrap: "wrap",
674
+ gap: 6
675
+ }, children: [
676
+ /* @__PURE__ */ jsx(Label, { style: { width: "100%", marginBottom: 4 }, children: "Variables Handlebars — click para insertar:" }),
677
+ availableVars.map((v, i) => /* @__PURE__ */ jsx(
678
+ "button",
679
+ {
680
+ onClick: () => insertVariable(v),
681
+ style: {
682
+ background: v.startsWith("{{") ? "#e2e8f0" : "#dbeafe",
683
+ border: "1px solid #cbd5e0",
684
+ borderRadius: 4,
685
+ padding: "2px 8px",
686
+ fontSize: 11,
687
+ cursor: "pointer",
688
+ fontFamily: "monospace"
689
+ },
690
+ children: v.startsWith("{{") ? v : `{{${v}}}`
691
+ },
692
+ i
693
+ ))
694
+ ] }),
695
+ /* @__PURE__ */ jsxs("div", { style: { display: "flex", gap: 16, height: "calc(100vh - 260px)" }, children: [
696
+ /* @__PURE__ */ jsxs("div", { style: { flex: 1, display: "flex", flexDirection: "column", minWidth: 0 }, children: [
697
+ /* @__PURE__ */ jsx(Label, { style: { marginBottom: 8 }, children: "Editor HTML + Handlebars" }),
698
+ /* @__PURE__ */ jsx("div", { style: { flex: 1, border: "1px solid #e2e8f0", borderRadius: 8, overflow: "hidden" }, children: /* @__PURE__ */ jsx(
699
+ CodeMirror,
700
+ {
701
+ ref: editorRef,
702
+ value: htmlContent,
703
+ onChange: (value) => setHtmlContent(value),
704
+ extensions: [html()],
705
+ theme: oneDark,
706
+ height: "100%",
707
+ style: { height: "100%" }
708
+ }
709
+ ) })
710
+ ] }),
711
+ /* @__PURE__ */ jsxs("div", { style: { flex: 1, display: "flex", flexDirection: "column", minWidth: 0 }, children: [
712
+ /* @__PURE__ */ jsx(Label, { style: { marginBottom: 8 }, children: "Preview en vivo (datos de ejemplo)" }),
713
+ /* @__PURE__ */ jsx("div", { style: {
714
+ flex: 1,
715
+ border: "1px solid #e2e8f0",
716
+ borderRadius: 8,
717
+ overflow: "auto",
718
+ background: "#fff"
719
+ }, children: /* @__PURE__ */ jsx(
720
+ "iframe",
721
+ {
722
+ srcDoc: previewHtml,
723
+ style: { width: "100%", height: "100%", border: "none" },
724
+ sandbox: "allow-same-origin",
725
+ title: "Template Preview"
726
+ }
727
+ ) })
728
+ ] })
729
+ ] })
730
+ ] });
731
+ };
249
732
  const i18nTranslations0 = {};
250
733
  const widgetModule = { widgets: [
251
734
  {
@@ -255,9 +738,21 @@ const widgetModule = { widgets: [
255
738
  ] };
256
739
  const routeModule = {
257
740
  routes: [
741
+ {
742
+ Component: InvoiceTemplatesPage,
743
+ path: "/invoice-templates"
744
+ },
258
745
  {
259
746
  Component: InvoiceConfigPage,
260
747
  path: "/invoice-config"
748
+ },
749
+ {
750
+ Component: NewTemplatePage,
751
+ path: "/invoice-templates/new"
752
+ },
753
+ {
754
+ Component: TemplateEditorPage,
755
+ path: "/invoice-templates/:id"
261
756
  }
262
757
  ]
263
758
  };
@@ -270,6 +765,14 @@ const menuItemModule = {
270
765
  nested: void 0,
271
766
  rank: void 0,
272
767
  translationNs: void 0
768
+ },
769
+ {
770
+ label: config$1.label,
771
+ icon: config$1.icon,
772
+ path: "/invoice-templates",
773
+ nested: void 0,
774
+ rank: void 0,
775
+ translationNs: void 0
273
776
  }
274
777
  ]
275
778
  };
package/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 CodeMindEC
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.