[{"data":1,"prerenderedAt":2515},["ShallowReactive",2],{"blog-es-invoice-pdf-go-under-50-lines":3},{"id":4,"title":5,"author":6,"body":10,"date":2481,"description":2482,"draft":2483,"extension":2484,"howTo":2485,"image":2506,"meta":2507,"navigation":131,"path":2508,"seo":2509,"stem":2510,"tags":2511,"updated":2506,"__hash__":2514},"blogEs/es/blog/012.invoice-pdf-go-under-50-lines.md","Genera una factura PDF en Go en menos de 50 líneas",{"name":7,"url":8,"avatar":9},"Taiki Noda","https://nadai.dev/en/about","https://nadai.dev/og-default.png",{"type":11,"value":12,"toc":2462},"minimark",[13,18,36,39,64,70,74,77,80,96,99,104,107,111,1145,1154,1158,1163,1166,1233,1248,1252,1288,1301,1305,1412,1427,1436,1447,1451,1614,1623,1632,1636,1687,1697,1701,1808,1825,1829,1839,1963,1973,2079,2088,2098,2182,2186,2253,2257,2309,2312,2316,2322,2342,2352,2370,2384,2398,2402,2405,2417,2431,2435,2458],[14,15,17],"h2",{"id":16},"tldr","TL;DR",[19,20,21,22,26,27,31,32,35],"p",{},"Una factura PDF que funciona en Go, de principio a fin, en ",[23,24,25],"strong",{},"50 líneas",". Un ",[28,29,30],"code",{},"main.go",", un ",[28,33,34],{},"go get",", sin Chromium, sin CGO, sin lenguaje de plantillas, sin HTML. Tabla, filas con cebrado, totales alineados a la derecha. Corre. El código está abajo, y el resto del post explica qué hace cada bloque y dónde deja de escalar el patrón.",[19,37,38],{},"Si solo quieres leer el código primero:",[40,41,46],"pre",{"className":42,"code":43,"language":44,"meta":45,"style":45},"language-bash shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","go get github.com/gpdf-dev/gpdf\n","bash","",[28,47,48],{"__ignoreMap":45},[49,50,53,57,61],"span",{"class":51,"line":52},"line",1,[49,54,56],{"class":55},"sBMFI","go",[49,58,60],{"class":59},"sfazB"," get",[49,62,63],{"class":59}," github.com/gpdf-dev/gpdf\n",[19,65,66,67,69],{},"Luego pega el ",[28,68,30],{}," de la siguiente sección.",[14,71,73],{"id":72},"por-qué-menos-de-50-líneas-es-el-umbral-que-nos-importa","Por qué \"menos de 50 líneas\" es el umbral que nos importa",[19,75,76],{},"La razón honesta por la que existe este post: la mayoría de la gente que busca \"generar factura pdf en go\" encuentra artículos que o bien (a) recomiendan lanzar Chromium headless, o bien (b) muestran 400 líneas de operadores PDF de bajo nivel para renderizar una sola tabla. Ambas respuestas son técnicamente correctas. Ninguna es la forma que la tarea tiene.",[19,78,79],{},"Una factura razonable tiene:",[81,82,83,87,90,93],"ul",{},[84,85,86],"li",{},"Cabecera con tu empresa y la del cliente",[84,88,89],{},"Número de factura y fecha de vencimiento",[84,91,92],{},"Tabla de conceptos",[84,94,95],{},"Un total",[19,97,98],{},"Cuatro cosas. Deberían ser cuatro bloques de código. Si no cabe en una pantalla, la librería está mal elegida.",[19,100,101,103],{},[23,102,25],{}," es aproximadamente el límite donde el código todavía cabe en una pantalla de un editor normal. También es el umbral donde un revisor leerá todo de principio a fin en lugar de saltar a los tests. Llegar ahí significa que puedes pegar el resultado en un mensaje de Slack y alguien puede aprender la librería solo desde ese mensaje.",[19,105,106],{},"El código de abajo está gofmt-eado, con todos los imports expandidos y todas las rutas de error respetadas. Sin trucos, sin paquete helper escondido. Lo que ves es lo que compila.",[14,108,110],{"id":109},"las-50-líneas","Las 50 líneas",[40,112,115],{"className":113,"code":114,"language":56,"meta":45,"style":45},"language-go shiki shiki-themes material-theme-lighter material-theme material-theme-palenight","package main\n\nimport (\n    \"log\"\n    \"os\"\n\n    \"github.com/gpdf-dev/gpdf\"\n    \"github.com/gpdf-dev/gpdf/document\"\n    \"github.com/gpdf-dev/gpdf/pdf\"\n    \"github.com/gpdf-dev/gpdf/template\"\n)\n\nfunc main() {\n    doc := gpdf.NewDocument(template.WithPageSize(document.A4))\n    page := doc.AddPage()\n    page.AutoRow(func(r *template.RowBuilder) {\n        r.Col(6, func(c *template.ColBuilder) {\n            c.Text(\"ACME S.L.\", template.FontSize(22), template.Bold())\n            c.Text(\"Calle Gran Vía 1, Madrid 28013\")\n        })\n        r.Col(6, func(c *template.ColBuilder) {\n            c.Text(\"FACTURA #INV-2026-001\", template.Bold(), template.AlignRight())\n            c.Text(\"Vencimiento: 2026-03-31\", template.AlignRight())\n        })\n    })\n    page.AutoRow(func(r *template.RowBuilder) {\n        r.Col(12, func(c *template.ColBuilder) {\n            c.Spacer(document.Mm(6))\n            c.Table(\n                []string{\"Concepto\", \"Cantidad\", \"Precio unit.\", \"Importe\"},\n                [][]string{\n                    {\"Desarrollo frontend\", \"40 h\", \"150,00 €\", \"6.000,00 €\"},\n                    {\"Desarrollo backend\", \"60 h\", \"150,00 €\", \"9.000,00 €\"},\n                    {\"Diseño UI/UX\", \"20 h\", \"120,00 €\", \"2.400,00 €\"},\n                },\n                template.ColumnWidths(40, 15, 20, 25),\n                template.TableHeaderStyle(template.Bold(), template.BgColor(pdf.RGBHex(0xF0F0F0))),\n                template.TableStripe(pdf.RGBHex(0xFAFAFA)),\n            )\n            c.Text(\"Total: 17.400,00 €\", template.AlignRight(), template.Bold(), template.FontSize(14))\n        })\n    })\n    b, err := doc.Generate()\n    if err != nil {\n        log.Fatal(err)\n    }\n    if err := os.WriteFile(\"invoice.pdf\", b, 0644); err != nil {\n        log.Fatal(err)\n    }\n}\n",[28,116,117,126,133,143,155,165,170,180,190,200,210,216,221,237,280,299,332,371,420,440,446,477,515,543,548,554,579,611,636,649,700,711,753,793,834,840,874,919,945,951,1000,1005,1010,1032,1048,1066,1072,1119,1134,1139],{"__ignoreMap":45},[49,118,119,123],{"class":51,"line":52},[49,120,122],{"class":121},"sMK4o","package",[49,124,125],{"class":55}," main\n",[49,127,129],{"class":51,"line":128},2,[49,130,132],{"emptyLinePlaceholder":131},true,"\n",[49,134,136,140],{"class":51,"line":135},3,[49,137,139],{"class":138},"s7zQu","import",[49,141,142],{"class":121}," (\n",[49,144,146,149,152],{"class":51,"line":145},4,[49,147,148],{"class":121},"    \"",[49,150,151],{"class":55},"log",[49,153,154],{"class":121},"\"\n",[49,156,158,160,163],{"class":51,"line":157},5,[49,159,148],{"class":121},[49,161,162],{"class":55},"os",[49,164,154],{"class":121},[49,166,168],{"class":51,"line":167},6,[49,169,132],{"emptyLinePlaceholder":131},[49,171,173,175,178],{"class":51,"line":172},7,[49,174,148],{"class":121},[49,176,177],{"class":55},"github.com/gpdf-dev/gpdf",[49,179,154],{"class":121},[49,181,183,185,188],{"class":51,"line":182},8,[49,184,148],{"class":121},[49,186,187],{"class":55},"github.com/gpdf-dev/gpdf/document",[49,189,154],{"class":121},[49,191,193,195,198],{"class":51,"line":192},9,[49,194,148],{"class":121},[49,196,197],{"class":55},"github.com/gpdf-dev/gpdf/pdf",[49,199,154],{"class":121},[49,201,203,205,208],{"class":51,"line":202},10,[49,204,148],{"class":121},[49,206,207],{"class":55},"github.com/gpdf-dev/gpdf/template",[49,209,154],{"class":121},[49,211,213],{"class":51,"line":212},11,[49,214,215],{"class":121},")\n",[49,217,219],{"class":51,"line":218},12,[49,220,132],{"emptyLinePlaceholder":131},[49,222,224,227,231,234],{"class":51,"line":223},13,[49,225,226],{"class":121},"func",[49,228,230],{"class":229},"s2Zo4"," main",[49,232,233],{"class":121},"()",[49,235,236],{"class":121}," {\n",[49,238,240,244,247,250,253,256,259,262,264,267,269,272,274,277],{"class":51,"line":239},14,[49,241,243],{"class":242},"sTEyZ","    doc ",[49,245,246],{"class":121},":=",[49,248,249],{"class":242}," gpdf",[49,251,252],{"class":121},".",[49,254,255],{"class":229},"NewDocument",[49,257,258],{"class":121},"(",[49,260,261],{"class":242},"template",[49,263,252],{"class":121},[49,265,266],{"class":229},"WithPageSize",[49,268,258],{"class":121},[49,270,271],{"class":242},"document",[49,273,252],{"class":121},[49,275,276],{"class":242},"A4",[49,278,279],{"class":121},"))\n",[49,281,283,286,288,291,293,296],{"class":51,"line":282},15,[49,284,285],{"class":242},"    page ",[49,287,246],{"class":121},[49,289,290],{"class":242}," doc",[49,292,252],{"class":121},[49,294,295],{"class":229},"AddPage",[49,297,298],{"class":121},"()\n",[49,300,302,305,307,310,313,317,320,322,324,327,330],{"class":51,"line":301},16,[49,303,304],{"class":242},"    page",[49,306,252],{"class":121},[49,308,309],{"class":229},"AutoRow",[49,311,312],{"class":121},"(func(",[49,314,316],{"class":315},"sHdIc","r",[49,318,319],{"class":121}," *",[49,321,261],{"class":55},[49,323,252],{"class":121},[49,325,326],{"class":55},"RowBuilder",[49,328,329],{"class":121},")",[49,331,236],{"class":121},[49,333,335,338,340,343,345,349,352,355,358,360,362,364,367,369],{"class":51,"line":334},17,[49,336,337],{"class":242},"        r",[49,339,252],{"class":121},[49,341,342],{"class":229},"Col",[49,344,258],{"class":121},[49,346,348],{"class":347},"sbssI","6",[49,350,351],{"class":121},",",[49,353,354],{"class":121}," func(",[49,356,357],{"class":315},"c",[49,359,319],{"class":121},[49,361,261],{"class":55},[49,363,252],{"class":121},[49,365,366],{"class":55},"ColBuilder",[49,368,329],{"class":121},[49,370,236],{"class":121},[49,372,374,377,379,382,384,387,390,392,394,397,399,402,404,407,410,412,414,417],{"class":51,"line":373},18,[49,375,376],{"class":242},"            c",[49,378,252],{"class":121},[49,380,381],{"class":229},"Text",[49,383,258],{"class":121},[49,385,386],{"class":121},"\"",[49,388,389],{"class":59},"ACME S.L.",[49,391,386],{"class":121},[49,393,351],{"class":121},[49,395,396],{"class":242}," template",[49,398,252],{"class":121},[49,400,401],{"class":229},"FontSize",[49,403,258],{"class":121},[49,405,406],{"class":347},"22",[49,408,409],{"class":121},"),",[49,411,396],{"class":242},[49,413,252],{"class":121},[49,415,416],{"class":229},"Bold",[49,418,419],{"class":121},"())\n",[49,421,423,425,427,429,431,433,436,438],{"class":51,"line":422},19,[49,424,376],{"class":242},[49,426,252],{"class":121},[49,428,381],{"class":229},[49,430,258],{"class":121},[49,432,386],{"class":121},[49,434,435],{"class":59},"Calle Gran Vía 1, Madrid 28013",[49,437,386],{"class":121},[49,439,215],{"class":121},[49,441,443],{"class":51,"line":442},20,[49,444,445],{"class":121},"        })\n",[49,447,449,451,453,455,457,459,461,463,465,467,469,471,473,475],{"class":51,"line":448},21,[49,450,337],{"class":242},[49,452,252],{"class":121},[49,454,342],{"class":229},[49,456,258],{"class":121},[49,458,348],{"class":347},[49,460,351],{"class":121},[49,462,354],{"class":121},[49,464,357],{"class":315},[49,466,319],{"class":121},[49,468,261],{"class":55},[49,470,252],{"class":121},[49,472,366],{"class":55},[49,474,329],{"class":121},[49,476,236],{"class":121},[49,478,480,482,484,486,488,490,493,495,497,499,501,503,506,508,510,513],{"class":51,"line":479},22,[49,481,376],{"class":242},[49,483,252],{"class":121},[49,485,381],{"class":229},[49,487,258],{"class":121},[49,489,386],{"class":121},[49,491,492],{"class":59},"FACTURA #INV-2026-001",[49,494,386],{"class":121},[49,496,351],{"class":121},[49,498,396],{"class":242},[49,500,252],{"class":121},[49,502,416],{"class":229},[49,504,505],{"class":121},"(),",[49,507,396],{"class":242},[49,509,252],{"class":121},[49,511,512],{"class":229},"AlignRight",[49,514,419],{"class":121},[49,516,518,520,522,524,526,528,531,533,535,537,539,541],{"class":51,"line":517},23,[49,519,376],{"class":242},[49,521,252],{"class":121},[49,523,381],{"class":229},[49,525,258],{"class":121},[49,527,386],{"class":121},[49,529,530],{"class":59},"Vencimiento: 2026-03-31",[49,532,386],{"class":121},[49,534,351],{"class":121},[49,536,396],{"class":242},[49,538,252],{"class":121},[49,540,512],{"class":229},[49,542,419],{"class":121},[49,544,546],{"class":51,"line":545},24,[49,547,445],{"class":121},[49,549,551],{"class":51,"line":550},25,[49,552,553],{"class":121},"    })\n",[49,555,557,559,561,563,565,567,569,571,573,575,577],{"class":51,"line":556},26,[49,558,304],{"class":242},[49,560,252],{"class":121},[49,562,309],{"class":229},[49,564,312],{"class":121},[49,566,316],{"class":315},[49,568,319],{"class":121},[49,570,261],{"class":55},[49,572,252],{"class":121},[49,574,326],{"class":55},[49,576,329],{"class":121},[49,578,236],{"class":121},[49,580,582,584,586,588,590,593,595,597,599,601,603,605,607,609],{"class":51,"line":581},27,[49,583,337],{"class":242},[49,585,252],{"class":121},[49,587,342],{"class":229},[49,589,258],{"class":121},[49,591,592],{"class":347},"12",[49,594,351],{"class":121},[49,596,354],{"class":121},[49,598,357],{"class":315},[49,600,319],{"class":121},[49,602,261],{"class":55},[49,604,252],{"class":121},[49,606,366],{"class":55},[49,608,329],{"class":121},[49,610,236],{"class":121},[49,612,614,616,618,621,623,625,627,630,632,634],{"class":51,"line":613},28,[49,615,376],{"class":242},[49,617,252],{"class":121},[49,619,620],{"class":229},"Spacer",[49,622,258],{"class":121},[49,624,271],{"class":242},[49,626,252],{"class":121},[49,628,629],{"class":229},"Mm",[49,631,258],{"class":121},[49,633,348],{"class":347},[49,635,279],{"class":121},[49,637,639,641,643,646],{"class":51,"line":638},29,[49,640,376],{"class":242},[49,642,252],{"class":121},[49,644,645],{"class":229},"Table",[49,647,648],{"class":121},"(\n",[49,650,652,655,659,662,664,667,669,671,674,677,679,681,683,686,688,690,692,695,697],{"class":51,"line":651},30,[49,653,654],{"class":121},"                []",[49,656,658],{"class":657},"spNyl","string",[49,660,661],{"class":121},"{",[49,663,386],{"class":121},[49,665,666],{"class":59},"Concepto",[49,668,386],{"class":121},[49,670,351],{"class":121},[49,672,673],{"class":121}," \"",[49,675,676],{"class":59},"Cantidad",[49,678,386],{"class":121},[49,680,351],{"class":121},[49,682,673],{"class":121},[49,684,685],{"class":59},"Precio unit.",[49,687,386],{"class":121},[49,689,351],{"class":121},[49,691,673],{"class":121},[49,693,694],{"class":59},"Importe",[49,696,386],{"class":121},[49,698,699],{"class":121},"},\n",[49,701,703,706,708],{"class":51,"line":702},31,[49,704,705],{"class":121},"                [][]",[49,707,658],{"class":657},[49,709,710],{"class":121},"{\n",[49,712,714,717,719,722,724,726,728,731,733,735,737,740,742,744,746,749,751],{"class":51,"line":713},32,[49,715,716],{"class":121},"                    {",[49,718,386],{"class":121},[49,720,721],{"class":59},"Desarrollo frontend",[49,723,386],{"class":121},[49,725,351],{"class":121},[49,727,673],{"class":121},[49,729,730],{"class":59},"40 h",[49,732,386],{"class":121},[49,734,351],{"class":121},[49,736,673],{"class":121},[49,738,739],{"class":59},"150,00 €",[49,741,386],{"class":121},[49,743,351],{"class":121},[49,745,673],{"class":121},[49,747,748],{"class":59},"6.000,00 €",[49,750,386],{"class":121},[49,752,699],{"class":121},[49,754,756,758,760,763,765,767,769,772,774,776,778,780,782,784,786,789,791],{"class":51,"line":755},33,[49,757,716],{"class":121},[49,759,386],{"class":121},[49,761,762],{"class":59},"Desarrollo backend",[49,764,386],{"class":121},[49,766,351],{"class":121},[49,768,673],{"class":121},[49,770,771],{"class":59},"60 h",[49,773,386],{"class":121},[49,775,351],{"class":121},[49,777,673],{"class":121},[49,779,739],{"class":59},[49,781,386],{"class":121},[49,783,351],{"class":121},[49,785,673],{"class":121},[49,787,788],{"class":59},"9.000,00 €",[49,790,386],{"class":121},[49,792,699],{"class":121},[49,794,796,798,800,803,805,807,809,812,814,816,818,821,823,825,827,830,832],{"class":51,"line":795},34,[49,797,716],{"class":121},[49,799,386],{"class":121},[49,801,802],{"class":59},"Diseño UI/UX",[49,804,386],{"class":121},[49,806,351],{"class":121},[49,808,673],{"class":121},[49,810,811],{"class":59},"20 h",[49,813,386],{"class":121},[49,815,351],{"class":121},[49,817,673],{"class":121},[49,819,820],{"class":59},"120,00 €",[49,822,386],{"class":121},[49,824,351],{"class":121},[49,826,673],{"class":121},[49,828,829],{"class":59},"2.400,00 €",[49,831,386],{"class":121},[49,833,699],{"class":121},[49,835,837],{"class":51,"line":836},35,[49,838,839],{"class":121},"                },\n",[49,841,843,846,848,851,853,856,858,861,863,866,868,871],{"class":51,"line":842},36,[49,844,845],{"class":242},"                template",[49,847,252],{"class":121},[49,849,850],{"class":229},"ColumnWidths",[49,852,258],{"class":121},[49,854,855],{"class":347},"40",[49,857,351],{"class":121},[49,859,860],{"class":347}," 15",[49,862,351],{"class":121},[49,864,865],{"class":347}," 20",[49,867,351],{"class":121},[49,869,870],{"class":347}," 25",[49,872,873],{"class":121},"),\n",[49,875,877,879,881,884,886,888,890,892,894,896,898,901,903,906,908,911,913,916],{"class":51,"line":876},37,[49,878,845],{"class":242},[49,880,252],{"class":121},[49,882,883],{"class":229},"TableHeaderStyle",[49,885,258],{"class":121},[49,887,261],{"class":242},[49,889,252],{"class":121},[49,891,416],{"class":229},[49,893,505],{"class":121},[49,895,396],{"class":242},[49,897,252],{"class":121},[49,899,900],{"class":229},"BgColor",[49,902,258],{"class":121},[49,904,905],{"class":242},"pdf",[49,907,252],{"class":121},[49,909,910],{"class":229},"RGBHex",[49,912,258],{"class":121},[49,914,915],{"class":347},"0xF0F0F0",[49,917,918],{"class":121},"))),\n",[49,920,922,924,926,929,931,933,935,937,939,942],{"class":51,"line":921},38,[49,923,845],{"class":242},[49,925,252],{"class":121},[49,927,928],{"class":229},"TableStripe",[49,930,258],{"class":121},[49,932,905],{"class":242},[49,934,252],{"class":121},[49,936,910],{"class":229},[49,938,258],{"class":121},[49,940,941],{"class":347},"0xFAFAFA",[49,943,944],{"class":121},")),\n",[49,946,948],{"class":51,"line":947},39,[49,949,950],{"class":121},"            )\n",[49,952,954,956,958,960,962,964,967,969,971,973,975,977,979,981,983,985,987,989,991,993,995,998],{"class":51,"line":953},40,[49,955,376],{"class":242},[49,957,252],{"class":121},[49,959,381],{"class":229},[49,961,258],{"class":121},[49,963,386],{"class":121},[49,965,966],{"class":59},"Total: 17.400,00 €",[49,968,386],{"class":121},[49,970,351],{"class":121},[49,972,396],{"class":242},[49,974,252],{"class":121},[49,976,512],{"class":229},[49,978,505],{"class":121},[49,980,396],{"class":242},[49,982,252],{"class":121},[49,984,416],{"class":229},[49,986,505],{"class":121},[49,988,396],{"class":242},[49,990,252],{"class":121},[49,992,401],{"class":229},[49,994,258],{"class":121},[49,996,997],{"class":347},"14",[49,999,279],{"class":121},[49,1001,1003],{"class":51,"line":1002},41,[49,1004,445],{"class":121},[49,1006,1008],{"class":51,"line":1007},42,[49,1009,553],{"class":121},[49,1011,1013,1016,1018,1021,1023,1025,1027,1030],{"class":51,"line":1012},43,[49,1014,1015],{"class":242},"    b",[49,1017,351],{"class":121},[49,1019,1020],{"class":242}," err ",[49,1022,246],{"class":121},[49,1024,290],{"class":242},[49,1026,252],{"class":121},[49,1028,1029],{"class":229},"Generate",[49,1031,298],{"class":121},[49,1033,1035,1038,1040,1043,1046],{"class":51,"line":1034},44,[49,1036,1037],{"class":138},"    if",[49,1039,1020],{"class":242},[49,1041,1042],{"class":121},"!=",[49,1044,1045],{"class":121}," nil",[49,1047,236],{"class":121},[49,1049,1051,1054,1056,1059,1061,1064],{"class":51,"line":1050},45,[49,1052,1053],{"class":242},"        log",[49,1055,252],{"class":121},[49,1057,1058],{"class":229},"Fatal",[49,1060,258],{"class":121},[49,1062,1063],{"class":242},"err",[49,1065,215],{"class":121},[49,1067,1069],{"class":51,"line":1068},46,[49,1070,1071],{"class":121},"    }\n",[49,1073,1075,1077,1079,1081,1084,1086,1089,1091,1093,1096,1098,1100,1103,1105,1108,1111,1113,1115,1117],{"class":51,"line":1074},47,[49,1076,1037],{"class":138},[49,1078,1020],{"class":242},[49,1080,246],{"class":121},[49,1082,1083],{"class":242}," os",[49,1085,252],{"class":121},[49,1087,1088],{"class":229},"WriteFile",[49,1090,258],{"class":121},[49,1092,386],{"class":121},[49,1094,1095],{"class":59},"invoice.pdf",[49,1097,386],{"class":121},[49,1099,351],{"class":121},[49,1101,1102],{"class":242}," b",[49,1104,351],{"class":121},[49,1106,1107],{"class":347}," 0644",[49,1109,1110],{"class":121},");",[49,1112,1020],{"class":242},[49,1114,1042],{"class":121},[49,1116,1045],{"class":121},[49,1118,236],{"class":121},[49,1120,1122,1124,1126,1128,1130,1132],{"class":51,"line":1121},48,[49,1123,1053],{"class":242},[49,1125,252],{"class":121},[49,1127,1058],{"class":229},[49,1129,258],{"class":121},[49,1131,1063],{"class":242},[49,1133,215],{"class":121},[49,1135,1137],{"class":51,"line":1136},49,[49,1138,1071],{"class":121},[49,1140,1142],{"class":51,"line":1141},50,[49,1143,1144],{"class":121},"}\n",[19,1146,1147,1150,1151,1153],{},[28,1148,1149],{},"go run ."," produce ",[28,1152,1095],{}," en el directorio actual. En un M1 el programa entero termina en unos milisegundos — la generación del PDF en sí está por debajo de 150 µs, el resto es arranque del proceso.",[14,1155,1157],{"id":1156},"qué-hace-cada-bloque","Qué hace cada bloque",[1159,1160,1162],"h3",{"id":1161},"imports","Imports",[19,1164,1165],{},"Cuatro paquetes de gpdf:",[81,1167,1168,1180,1213,1228],{},[84,1169,1170,1172,1173,1176,1177,252],{},[28,1171,177],{}," — la fachada. Solo usamos ",[28,1174,1175],{},"gpdf.NewDocument",", que es un envoltorio fino sobre ",[28,1178,1179],{},"template.New",[84,1181,1182,1184,1185,1187,1188,1187,1191,1187,1194,1187,1197,1187,1200,1203,1204,1187,1206,1187,1209,1212],{},[28,1183,187],{}," — unidades (",[28,1186,629],{},", ",[28,1189,1190],{},"Pt",[28,1192,1193],{},"Cm",[28,1195,1196],{},"In",[28,1198,1199],{},"Em",[28,1201,1202],{},"Pct","), tamaños de página (",[28,1205,276],{},[28,1207,1208],{},"Letter",[28,1210,1211],{},"Legal","), márgenes.",[84,1214,1215,1217,1218,1187,1220,1223,1224,1227],{},[28,1216,197],{}," — primitivas de color (",[28,1219,910],{},[28,1221,1222],{},"Gray",", constantes como ",[28,1225,1226],{},"pdf.White",").",[84,1229,1230,1232],{},[28,1231,207],{}," — el Builder API.",[19,1234,1235,1236,1239,1240,1243,1244,1247],{},"Sin dependencias externas. Tras ",[28,1237,1238],{},"go get github.com/gpdf-dev/gpdf",", el ",[28,1241,1242],{},"require"," de ",[28,1245,1246],{},"go.mod"," tiene una sola línea.",[1159,1249,1251],{"id":1250},"construcción-del-documento","Construcción del documento",[40,1253,1255],{"className":113,"code":1254,"language":56,"meta":45,"style":45},"doc := gpdf.NewDocument(template.WithPageSize(document.A4))\n",[28,1256,1257],{"__ignoreMap":45},[49,1258,1259,1262,1264,1266,1268,1270,1272,1274,1276,1278,1280,1282,1284,1286],{"class":51,"line":52},[49,1260,1261],{"class":242},"doc ",[49,1263,246],{"class":121},[49,1265,249],{"class":242},[49,1267,252],{"class":121},[49,1269,255],{"class":229},[49,1271,258],{"class":121},[49,1273,261],{"class":242},[49,1275,252],{"class":121},[49,1277,266],{"class":229},[49,1279,258],{"class":121},[49,1281,271],{"class":242},[49,1283,252],{"class":121},[49,1285,276],{"class":242},[49,1287,279],{"class":121},[19,1289,1290,1292,1293,1296,1297,1300],{},[28,1291,1175],{}," acepta ",[28,1294,1295],{},"...template.Option",". Tamaño de página, márgenes, fuente por defecto, metadatos, fuentes custom: todo son opciones ",[28,1298,1299],{},"WithXxx",". Margen por defecto 20 mm.",[1159,1302,1304],{"id":1303},"la-fila-de-cabecera","La fila de cabecera",[40,1306,1308],{"className":113,"code":1307,"language":56,"meta":45,"style":45},"page.AutoRow(func(r *template.RowBuilder) {\n    r.Col(6, func(c *template.ColBuilder) { ... })\n    r.Col(6, func(c *template.ColBuilder) { ... })\n})\n",[28,1309,1310,1335,1373,1407],{"__ignoreMap":45},[49,1311,1312,1315,1317,1319,1321,1323,1325,1327,1329,1331,1333],{"class":51,"line":52},[49,1313,1314],{"class":242},"page",[49,1316,252],{"class":121},[49,1318,309],{"class":229},[49,1320,312],{"class":121},[49,1322,316],{"class":315},[49,1324,319],{"class":121},[49,1326,261],{"class":55},[49,1328,252],{"class":121},[49,1330,326],{"class":55},[49,1332,329],{"class":121},[49,1334,236],{"class":121},[49,1336,1337,1340,1342,1344,1346,1348,1350,1352,1354,1356,1358,1360,1362,1364,1367,1370],{"class":51,"line":128},[49,1338,1339],{"class":242},"    r",[49,1341,252],{"class":121},[49,1343,342],{"class":229},[49,1345,258],{"class":121},[49,1347,348],{"class":347},[49,1349,351],{"class":121},[49,1351,354],{"class":121},[49,1353,357],{"class":315},[49,1355,319],{"class":121},[49,1357,261],{"class":55},[49,1359,252],{"class":121},[49,1361,366],{"class":55},[49,1363,329],{"class":121},[49,1365,1366],{"class":121}," {",[49,1368,1369],{"class":121}," ...",[49,1371,1372],{"class":121}," })\n",[49,1374,1375,1377,1379,1381,1383,1385,1387,1389,1391,1393,1395,1397,1399,1401,1403,1405],{"class":51,"line":135},[49,1376,1339],{"class":242},[49,1378,252],{"class":121},[49,1380,342],{"class":229},[49,1382,258],{"class":121},[49,1384,348],{"class":347},[49,1386,351],{"class":121},[49,1388,354],{"class":121},[49,1390,357],{"class":315},[49,1392,319],{"class":121},[49,1394,261],{"class":55},[49,1396,252],{"class":121},[49,1398,366],{"class":55},[49,1400,329],{"class":121},[49,1402,1366],{"class":121},[49,1404,1369],{"class":121},[49,1406,1372],{"class":121},[49,1408,1409],{"class":51,"line":145},[49,1410,1411],{"class":121},"})\n",[19,1413,1414,1415,1418,1419,1422,1423,1426],{},"gpdf usa una ",[23,1416,1417],{},"rejilla de 12 columnas",", mismo modelo mental que Bootstrap. Una fila tiene 12 unidades de espacio horizontal, ",[28,1420,1421],{},"r.Col(6, ...)"," ocupa la mitad, dos ",[28,1424,1425],{},"Col(6)"," llenan la fila.",[19,1428,1429,1431,1432,1435],{},[28,1430,309],{}," significa que la altura de la fila es la del contenido más alto. Dentro de cada columna apilas ",[28,1433,1434],{},"c.Text(...)"," de arriba a abajo. Sin posicionamiento explícito — el builder lleva un cursor.",[19,1437,1438,1439,1442,1443,1446],{},"La columna derecha usa ",[28,1440,1441],{},"template.AlignRight()",". Las opciones de texto son componibles: ",[28,1444,1445],{},"c.Text(\"FACTURA\", template.Bold(), template.AlignRight(), template.FontSize(20))"," compone tres modificadores en una sola llamada. El orden no importa.",[1159,1448,1450],{"id":1449},"la-tabla-de-conceptos","La tabla de conceptos",[40,1452,1454],{"className":113,"code":1453,"language":56,"meta":45,"style":45},"c.Table(\n    []string{\"Concepto\", \"Cantidad\", \"Precio unit.\", \"Importe\"},\n    [][]string{ /* filas */ },\n    template.ColumnWidths(40, 15, 20, 25),\n    template.TableHeaderStyle(template.Bold(), template.BgColor(pdf.RGBHex(0xF0F0F0))),\n    template.TableStripe(pdf.RGBHex(0xFAFAFA)),\n)\n",[28,1455,1456,1466,1507,1523,1550,1588,1610],{"__ignoreMap":45},[49,1457,1458,1460,1462,1464],{"class":51,"line":52},[49,1459,357],{"class":242},[49,1461,252],{"class":121},[49,1463,645],{"class":229},[49,1465,648],{"class":121},[49,1467,1468,1471,1473,1475,1477,1479,1481,1483,1485,1487,1489,1491,1493,1495,1497,1499,1501,1503,1505],{"class":51,"line":128},[49,1469,1470],{"class":121},"    []",[49,1472,658],{"class":657},[49,1474,661],{"class":121},[49,1476,386],{"class":121},[49,1478,666],{"class":59},[49,1480,386],{"class":121},[49,1482,351],{"class":121},[49,1484,673],{"class":121},[49,1486,676],{"class":59},[49,1488,386],{"class":121},[49,1490,351],{"class":121},[49,1492,673],{"class":121},[49,1494,685],{"class":59},[49,1496,386],{"class":121},[49,1498,351],{"class":121},[49,1500,673],{"class":121},[49,1502,694],{"class":59},[49,1504,386],{"class":121},[49,1506,699],{"class":121},[49,1508,1509,1512,1514,1516,1520],{"class":51,"line":135},[49,1510,1511],{"class":121},"    [][]",[49,1513,658],{"class":657},[49,1515,661],{"class":121},[49,1517,1519],{"class":1518},"sHwdD"," /* filas */",[49,1521,1522],{"class":121}," },\n",[49,1524,1525,1528,1530,1532,1534,1536,1538,1540,1542,1544,1546,1548],{"class":51,"line":145},[49,1526,1527],{"class":242},"    template",[49,1529,252],{"class":121},[49,1531,850],{"class":229},[49,1533,258],{"class":121},[49,1535,855],{"class":347},[49,1537,351],{"class":121},[49,1539,860],{"class":347},[49,1541,351],{"class":121},[49,1543,865],{"class":347},[49,1545,351],{"class":121},[49,1547,870],{"class":347},[49,1549,873],{"class":121},[49,1551,1552,1554,1556,1558,1560,1562,1564,1566,1568,1570,1572,1574,1576,1578,1580,1582,1584,1586],{"class":51,"line":157},[49,1553,1527],{"class":242},[49,1555,252],{"class":121},[49,1557,883],{"class":229},[49,1559,258],{"class":121},[49,1561,261],{"class":242},[49,1563,252],{"class":121},[49,1565,416],{"class":229},[49,1567,505],{"class":121},[49,1569,396],{"class":242},[49,1571,252],{"class":121},[49,1573,900],{"class":229},[49,1575,258],{"class":121},[49,1577,905],{"class":242},[49,1579,252],{"class":121},[49,1581,910],{"class":229},[49,1583,258],{"class":121},[49,1585,915],{"class":347},[49,1587,918],{"class":121},[49,1589,1590,1592,1594,1596,1598,1600,1602,1604,1606,1608],{"class":51,"line":167},[49,1591,1527],{"class":242},[49,1593,252],{"class":121},[49,1595,928],{"class":229},[49,1597,258],{"class":121},[49,1599,905],{"class":242},[49,1601,252],{"class":121},[49,1603,910],{"class":229},[49,1605,258],{"class":121},[49,1607,941],{"class":347},[49,1609,944],{"class":121},[49,1611,1612],{"class":51,"line":172},[49,1613,215],{"class":121},[19,1615,1616,1618,1619,1622],{},[28,1617,850],{}," son ",[23,1620,1621],{},"porcentajes"," del ancho de la columna contenedora, no puntos absolutos. Los cuatro valores deben sumar 100. Si no suman, no hay error pero la última columna desborda — el único pie forzoso.",[19,1624,1625,1627,1628,1631],{},[28,1626,883],{}," acepta las opciones de texto habituales. ",[28,1629,1630],{},"TableStripe(color)"," alterna el fondo de las filas. La tabla mide cada celda, elige la altura por la más alta, y si desborda la página, gpdf parte la tabla y redibuja la cabecera en la continuación.",[1159,1633,1635],{"id":1634},"el-total","El total",[40,1637,1639],{"className":113,"code":1638,"language":56,"meta":45,"style":45},"c.Text(\"Total: 17.400,00 €\", template.AlignRight(), template.Bold(), template.FontSize(14))\n",[28,1640,1641],{"__ignoreMap":45},[49,1642,1643,1645,1647,1649,1651,1653,1655,1657,1659,1661,1663,1665,1667,1669,1671,1673,1675,1677,1679,1681,1683,1685],{"class":51,"line":52},[49,1644,357],{"class":242},[49,1646,252],{"class":121},[49,1648,381],{"class":229},[49,1650,258],{"class":121},[49,1652,386],{"class":121},[49,1654,966],{"class":59},[49,1656,386],{"class":121},[49,1658,351],{"class":121},[49,1660,396],{"class":242},[49,1662,252],{"class":121},[49,1664,512],{"class":229},[49,1666,505],{"class":121},[49,1668,396],{"class":242},[49,1670,252],{"class":121},[49,1672,416],{"class":229},[49,1674,505],{"class":121},[49,1676,396],{"class":242},[49,1678,252],{"class":121},[49,1680,401],{"class":229},[49,1682,258],{"class":121},[49,1684,997],{"class":347},[49,1686,279],{"class":121},[19,1688,1689,1690,1692,1693,1696],{},"Otro ",[28,1691,381],{}," fuera de la tabla, alineado a la derecha, un poco más grande. Si quieres más aire, mete un ",[28,1694,1695],{},"c.Spacer(document.Mm(3))"," antes.",[1159,1698,1700],{"id":1699},"generar-y-escribir","Generar y escribir",[40,1702,1704],{"className":113,"code":1703,"language":56,"meta":45,"style":45},"b, err := doc.Generate()\nif err != nil { log.Fatal(err) }\nif err := os.WriteFile(\"invoice.pdf\", b, 0644); err != nil { log.Fatal(err) }\n",[28,1705,1706,1725,1754],{"__ignoreMap":45},[49,1707,1708,1711,1713,1715,1717,1719,1721,1723],{"class":51,"line":52},[49,1709,1710],{"class":242},"b",[49,1712,351],{"class":121},[49,1714,1020],{"class":242},[49,1716,246],{"class":121},[49,1718,290],{"class":242},[49,1720,252],{"class":121},[49,1722,1029],{"class":229},[49,1724,298],{"class":121},[49,1726,1727,1730,1732,1734,1736,1738,1741,1743,1745,1747,1749,1751],{"class":51,"line":128},[49,1728,1729],{"class":138},"if",[49,1731,1020],{"class":242},[49,1733,1042],{"class":121},[49,1735,1045],{"class":121},[49,1737,1366],{"class":121},[49,1739,1740],{"class":242}," log",[49,1742,252],{"class":121},[49,1744,1058],{"class":229},[49,1746,258],{"class":121},[49,1748,1063],{"class":242},[49,1750,329],{"class":121},[49,1752,1753],{"class":121}," }\n",[49,1755,1756,1758,1760,1762,1764,1766,1768,1770,1772,1774,1776,1778,1780,1782,1784,1786,1788,1790,1792,1794,1796,1798,1800,1802,1804,1806],{"class":51,"line":135},[49,1757,1729],{"class":138},[49,1759,1020],{"class":242},[49,1761,246],{"class":121},[49,1763,1083],{"class":242},[49,1765,252],{"class":121},[49,1767,1088],{"class":229},[49,1769,258],{"class":121},[49,1771,386],{"class":121},[49,1773,1095],{"class":59},[49,1775,386],{"class":121},[49,1777,351],{"class":121},[49,1779,1102],{"class":242},[49,1781,351],{"class":121},[49,1783,1107],{"class":347},[49,1785,1110],{"class":121},[49,1787,1020],{"class":242},[49,1789,1042],{"class":121},[49,1791,1045],{"class":121},[49,1793,1366],{"class":121},[49,1795,1740],{"class":242},[49,1797,252],{"class":121},[49,1799,1058],{"class":229},[49,1801,258],{"class":121},[49,1803,1063],{"class":242},[49,1805,329],{"class":121},[49,1807,1753],{"class":121},[19,1809,1810,1813,1814,1817,1818,1821,1822,252],{},[28,1811,1812],{},"doc.Generate()"," devuelve ",[28,1815,1816],{},"([]byte, error)",". No toca el sistema de archivos. El slice es un PDF completo — escríbelo a disco, súbelo a S3, devuélvelo como respuesta HTTP con ",[28,1819,1820],{},"w.Write(b)",", adjúntalo en un email. Si prefieres streaming, existe ",[28,1823,1824],{},"doc.Render(w io.Writer)",[14,1826,1828],{"id":1827},"hacerla-más-bonita-sin-pasarse-de-50-líneas","Hacerla más bonita sin pasarse de 50 líneas",[19,1830,1831,1834,1835,1838],{},[23,1832,1833],{},"Color corporativo."," Elige un hex (por ejemplo un azul marino ",[28,1836,1837],{},"0x1A237E",") y úsalo en el nombre de la empresa y en la cabecera de la tabla:",[40,1840,1842],{"className":113,"code":1841,"language":56,"meta":45,"style":45},"brand := pdf.RGBHex(0x1A237E)\nc.Text(\"ACME S.L.\", template.FontSize(22), template.Bold(), template.TextColor(brand))\ntemplate.TableHeaderStyle(template.Bold(), template.TextColor(pdf.White), template.BgColor(brand)),\n",[28,1843,1844,1864,1916],{"__ignoreMap":45},[49,1845,1846,1849,1851,1854,1856,1858,1860,1862],{"class":51,"line":52},[49,1847,1848],{"class":242},"brand ",[49,1850,246],{"class":121},[49,1852,1853],{"class":242}," pdf",[49,1855,252],{"class":121},[49,1857,910],{"class":229},[49,1859,258],{"class":121},[49,1861,1837],{"class":347},[49,1863,215],{"class":121},[49,1865,1866,1868,1870,1872,1874,1876,1878,1880,1882,1884,1886,1888,1890,1892,1894,1896,1898,1900,1902,1904,1906,1909,1911,1914],{"class":51,"line":128},[49,1867,357],{"class":242},[49,1869,252],{"class":121},[49,1871,381],{"class":229},[49,1873,258],{"class":121},[49,1875,386],{"class":121},[49,1877,389],{"class":59},[49,1879,386],{"class":121},[49,1881,351],{"class":121},[49,1883,396],{"class":242},[49,1885,252],{"class":121},[49,1887,401],{"class":229},[49,1889,258],{"class":121},[49,1891,406],{"class":347},[49,1893,409],{"class":121},[49,1895,396],{"class":242},[49,1897,252],{"class":121},[49,1899,416],{"class":229},[49,1901,505],{"class":121},[49,1903,396],{"class":242},[49,1905,252],{"class":121},[49,1907,1908],{"class":229},"TextColor",[49,1910,258],{"class":121},[49,1912,1913],{"class":242},"brand",[49,1915,279],{"class":121},[49,1917,1918,1920,1922,1924,1926,1928,1930,1932,1934,1936,1938,1940,1942,1944,1946,1949,1951,1953,1955,1957,1959,1961],{"class":51,"line":135},[49,1919,261],{"class":242},[49,1921,252],{"class":121},[49,1923,883],{"class":229},[49,1925,258],{"class":121},[49,1927,261],{"class":242},[49,1929,252],{"class":121},[49,1931,416],{"class":229},[49,1933,505],{"class":121},[49,1935,396],{"class":242},[49,1937,252],{"class":121},[49,1939,1908],{"class":229},[49,1941,258],{"class":121},[49,1943,905],{"class":242},[49,1945,252],{"class":121},[49,1947,1948],{"class":242},"White",[49,1950,409],{"class":121},[49,1952,396],{"class":242},[49,1954,252],{"class":121},[49,1956,900],{"class":229},[49,1958,258],{"class":121},[49,1960,1913],{"class":242},[49,1962,944],{"class":121},[19,1964,1965,1968,1969,1972],{},[23,1966,1967],{},"Subtotal e IVA."," Sobre el total, tres ",[28,1970,1971],{},"c.Text"," apilados:",[40,1974,1976],{"className":113,"code":1975,"language":56,"meta":45,"style":45},"c.Text(\"Subtotal: 17.400,00 €\", template.AlignRight())\nc.Text(\"IVA (21%): 3.654,00 €\", template.AlignRight())\nc.Text(\"Total: 21.054,00 €\", template.AlignRight(), template.Bold(), template.FontSize(14))\n",[28,1977,1978,2005,2032],{"__ignoreMap":45},[49,1979,1980,1982,1984,1986,1988,1990,1993,1995,1997,1999,2001,2003],{"class":51,"line":52},[49,1981,357],{"class":242},[49,1983,252],{"class":121},[49,1985,381],{"class":229},[49,1987,258],{"class":121},[49,1989,386],{"class":121},[49,1991,1992],{"class":59},"Subtotal: 17.400,00 €",[49,1994,386],{"class":121},[49,1996,351],{"class":121},[49,1998,396],{"class":242},[49,2000,252],{"class":121},[49,2002,512],{"class":229},[49,2004,419],{"class":121},[49,2006,2007,2009,2011,2013,2015,2017,2020,2022,2024,2026,2028,2030],{"class":51,"line":128},[49,2008,357],{"class":242},[49,2010,252],{"class":121},[49,2012,381],{"class":229},[49,2014,258],{"class":121},[49,2016,386],{"class":121},[49,2018,2019],{"class":59},"IVA (21%): 3.654,00 €",[49,2021,386],{"class":121},[49,2023,351],{"class":121},[49,2025,396],{"class":242},[49,2027,252],{"class":121},[49,2029,512],{"class":229},[49,2031,419],{"class":121},[49,2033,2034,2036,2038,2040,2042,2044,2047,2049,2051,2053,2055,2057,2059,2061,2063,2065,2067,2069,2071,2073,2075,2077],{"class":51,"line":135},[49,2035,357],{"class":242},[49,2037,252],{"class":121},[49,2039,381],{"class":229},[49,2041,258],{"class":121},[49,2043,386],{"class":121},[49,2045,2046],{"class":59},"Total: 21.054,00 €",[49,2048,386],{"class":121},[49,2050,351],{"class":121},[49,2052,396],{"class":242},[49,2054,252],{"class":121},[49,2056,512],{"class":229},[49,2058,505],{"class":121},[49,2060,396],{"class":242},[49,2062,252],{"class":121},[49,2064,416],{"class":229},[49,2066,505],{"class":121},[49,2068,396],{"class":242},[49,2070,252],{"class":121},[49,2072,401],{"class":229},[49,2074,258],{"class":121},[49,2076,997],{"class":347},[49,2078,279],{"class":121},[19,2080,2081,2084,2085,2087],{},[23,2082,2083],{},"Requisitos de facturación electrónica (España / LatAm)."," Para Facturae (España) o CFDI (México), los campos obligatorios (NIF del emisor, número de serie, fecha de expedición) son ",[28,2086,1971],{}," adicionales. El layout no cambia — la diferencia está en los datos y la firma digital posterior, no en la maquetación.",[19,2089,2090,2093,2094,2097],{},[23,2091,2092],{},"Una línea sobre el total."," Entre el bloque de subtotal y el total, un ",[28,2095,2096],{},"c.Line()",":",[40,2099,2101],{"className":113,"code":2100,"language":56,"meta":45,"style":45},"c.Spacer(document.Mm(2))\nc.Line(template.LineThickness(document.Pt(0.5)))\nc.Spacer(document.Mm(2))\n",[28,2102,2103,2126,2160],{"__ignoreMap":45},[49,2104,2105,2107,2109,2111,2113,2115,2117,2119,2121,2124],{"class":51,"line":52},[49,2106,357],{"class":242},[49,2108,252],{"class":121},[49,2110,620],{"class":229},[49,2112,258],{"class":121},[49,2114,271],{"class":242},[49,2116,252],{"class":121},[49,2118,629],{"class":229},[49,2120,258],{"class":121},[49,2122,2123],{"class":347},"2",[49,2125,279],{"class":121},[49,2127,2128,2130,2132,2135,2137,2139,2141,2144,2146,2148,2150,2152,2154,2157],{"class":51,"line":128},[49,2129,357],{"class":242},[49,2131,252],{"class":121},[49,2133,2134],{"class":229},"Line",[49,2136,258],{"class":121},[49,2138,261],{"class":242},[49,2140,252],{"class":121},[49,2142,2143],{"class":229},"LineThickness",[49,2145,258],{"class":121},[49,2147,271],{"class":242},[49,2149,252],{"class":121},[49,2151,1190],{"class":229},[49,2153,258],{"class":121},[49,2155,2156],{"class":347},"0.5",[49,2158,2159],{"class":121},")))\n",[49,2161,2162,2164,2166,2168,2170,2172,2174,2176,2178,2180],{"class":51,"line":135},[49,2163,357],{"class":242},[49,2165,252],{"class":121},[49,2167,620],{"class":229},[49,2169,258],{"class":121},[49,2171,271],{"class":242},[49,2173,252],{"class":121},[49,2175,629],{"class":229},[49,2177,258],{"class":121},[49,2179,2123],{"class":347},[49,2181,279],{"class":121},[14,2183,2185],{"id":2184},"ejecutarlo","Ejecutarlo",[40,2187,2189],{"className":42,"code":2188,"language":44,"meta":45,"style":45},"mkdir invoice-demo\ncd invoice-demo\ngo mod init example.com/invoice-demo\ngo get github.com/gpdf-dev/gpdf\n# pega main.go\ngo run .\nopen invoice.pdf    # macOS; xdg-open en Linux, start en Windows\n",[28,2190,2191,2199,2206,2219,2227,2232,2242],{"__ignoreMap":45},[49,2192,2193,2196],{"class":51,"line":52},[49,2194,2195],{"class":55},"mkdir",[49,2197,2198],{"class":59}," invoice-demo\n",[49,2200,2201,2204],{"class":51,"line":128},[49,2202,2203],{"class":229},"cd",[49,2205,2198],{"class":59},[49,2207,2208,2210,2213,2216],{"class":51,"line":135},[49,2209,56],{"class":55},[49,2211,2212],{"class":59}," mod",[49,2214,2215],{"class":59}," init",[49,2217,2218],{"class":59}," example.com/invoice-demo\n",[49,2220,2221,2223,2225],{"class":51,"line":145},[49,2222,56],{"class":55},[49,2224,60],{"class":59},[49,2226,63],{"class":59},[49,2228,2229],{"class":51,"line":157},[49,2230,2231],{"class":1518},"# pega main.go\n",[49,2233,2234,2236,2239],{"class":51,"line":167},[49,2235,56],{"class":55},[49,2237,2238],{"class":59}," run",[49,2240,2241],{"class":59}," .\n",[49,2243,2244,2247,2250],{"class":51,"line":172},[49,2245,2246],{"class":55},"open",[49,2248,2249],{"class":59}," invoice.pdf",[49,2251,2252],{"class":1518},"    # macOS; xdg-open en Linux, start en Windows\n",[14,2254,2256],{"id":2255},"cuándo-este-patrón-deja-de-servir","Cuándo este patrón deja de servir",[81,2258,2259,2269,2278,2295],{},[84,2260,2261,2264,2265,2268],{},[23,2262,2263],{},"Los conceptos se vuelven datos."," Cuando vienen de una query o un JSON, la tabla no cambia — solo construyes ",[28,2266,2267],{},"[][]string"," a partir de tus datos.",[84,2270,2271,2274,2275,252],{},[23,2272,2273],{},"Quieres reusar el layout."," En cuanto generas facturas en bucle, saca el cuerpo a ",[28,2276,2277],{},"func renderInvoice(doc *template.Document, inv Invoice)",[84,2279,2280,2283,2284,2289,2290,2294],{},[23,2281,2282],{},"El layout tiene ramas."," Con condicionales el Builder API se vuelve verboso — el ",[2285,2286,2288],"a",{"href":2287},"/es/docs/guide/json-templates","entrypoint JSON"," o el ",[2285,2291,2293],{"href":2292},"/es/docs/guide/go-templates","entrypoint Go templates"," encaja mejor.",[84,2296,2297,2300,2301,2304,2305,252],{},[23,2298,2299],{},"Necesitas CJK."," Japonés, chino, coreano se ven como cuadros vacíos con la fuente por defecto. Registra un TTF con ",[28,2302,2303],{},"template.WithFont",". Ver ",[2285,2306,2308],{"href":2307},"/es/blog/embed-japanese-font","¿Cómo incrusto una fuente japonesa en gpdf?",[19,2310,2311],{},"Ninguna de las cuatro es \"reescribir desde cero\" — son extensiones incrementales.",[14,2313,2315],{"id":2314},"faq","FAQ",[19,2317,2318,2321],{},[23,2319,2320],{},"¿Puedo usarlo en facturas comerciales sin atribución?","\nSí. gpdf tiene licencia MIT. Puedes construir encima lo que quieras, incluidos productos comerciales cerrados. La atribución no es obligatoria (una estrella en GitHub se agradece).",[19,2323,2324,2331,2332,2335,2336,2338,2339,252],{},[23,2325,2326,2327,2330],{},"¿Admite escribir a ",[28,2328,2329],{},"io.Writer"," sin el slice?","\nSí — ",[28,2333,2334],{},"doc.Render(w io.Writer) error",". La versión con ",[28,2337,1812],{}," es una comodidad para el caso común de querer ",[28,2340,2341],{},"[]byte",[19,2343,2344,2347,2348,2351],{},[23,2345,2346],{},"¿Qué tan rápido es en la práctica?","\nLas 50 líneas de arriba generan el PDF en unos 100 µs en un M1. Un hello world de una página ronda los ",[23,2349,2350],{},"13 µs",". En cargas batch — generación nocturna de facturas — gpdf rara vez es el cuello de botella.",[19,2353,2354,2357,2358,2361,2362,2365,2366,2369],{},[23,2355,2356],{},"¿Puedo generar una factura PDF en Go sin gpdf?","\nClaro. ",[28,2359,2360],{},"jung-kurt/gofpdf"," funciona (archivado pero estable), ",[28,2363,2364],{},"signintech/gopdf"," a nivel más bajo, y ",[28,2367,2368],{},"johnfercher/maroto"," con otra abstracción de layout. Todos terminan siendo más verbosos que las 50 líneas de arriba para la misma factura.",[19,2371,2372,2379,2380,2383],{},[23,2373,2374,2375,2378],{},"¿Por qué no hay un helper ",[28,2376,2377],{},"gpdf.Invoice","?","\nPorque \"factura\" significa cosas distintas en distintos países y cualquier simplificación deja fuera un caso. Preferimos darte un punto de partida de 50 líneas adaptable que un ",[28,2381,2382],{},"NewInvoice(...)"," que se rompe al pedir un 適格請求書 japonés o una NFe brasileña.",[19,2385,2386,2389,2390,2393,2394,252],{},[23,2387,2388],{},"¿El PDF valida contra PDF/A?","\nPor defecto sale PDF 1.7 estándar. Para PDF/A-2b pasa ",[28,2391,2392],{},"gpdf.WithPDFA(pdfa.Level2B)"," al construir el documento. Ver ",[2285,2395,2397],{"href":2396},"/es/docs/guide/pdf-a","Construyendo PDF/A-2b en Go puro",[14,2399,2401],{"id":2400},"prueba-gpdf","Prueba gpdf",[19,2403,2404],{},"gpdf es una librería Go para generar PDFs. MIT, cero dependencias, CJK nativo, 10–30× más rápida que las alternativas en los workloads que medimos.",[40,2406,2407],{"className":42,"code":43,"language":44,"meta":45,"style":45},[28,2408,2409],{"__ignoreMap":45},[49,2410,2411,2413,2415],{"class":51,"line":52},[49,2412,56],{"class":55},[49,2414,60],{"class":59},[49,2416,63],{"class":59},[19,2418,2419,2425,2426],{},[2285,2420,2424],{"href":2421,"rel":2422},"https://github.com/gpdf-dev/gpdf",[2423],"nofollow","⭐ Star en GitHub"," · ",[2285,2427,2430],{"href":2428,"rel":2429},"https://gpdf.dev/es/docs/quickstart",[2423],"Leer los docs",[14,2432,2434],{"id":2433},"siguientes-lecturas","Siguientes lecturas",[81,2436,2437,2444,2451],{},[84,2438,2439,2443],{},[2285,2440,2442],{"href":2441},"/es/blog/why-gpdf-is-faster","Por qué gpdf es 10–30× más rápido que otras librerías Go de PDF"," — los números detrás del \"unos cientos de microsegundos\".",[84,2445,2446,2450],{},[2285,2447,2449],{"href":2448},"/es/blog/go-pdf-library-showdown-2026","Go PDF Library Showdown 2026"," — cuántas líneas ocupan las mismas facturas en gofpdf, gopdf y Maroto.",[84,2452,2453,2457],{},[2285,2454,2456],{"href":2455},"/es/blog/12-column-grid","¿Cómo funciona la rejilla de 12 columnas en gpdf?"," — el modelo de layout de la fila de cabecera, en más detalle.",[2459,2460,2461],"style",{},"html pre.shiki code .sBMFI, html code.shiki .sBMFI{--shiki-light:#E2931D;--shiki-default:#FFCB6B;--shiki-dark:#FFCB6B}html pre.shiki code .sfazB, html code.shiki .sfazB{--shiki-light:#91B859;--shiki-default:#C3E88D;--shiki-dark:#C3E88D}html .light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html.light .shiki span {color: var(--shiki-light);background: var(--shiki-light-bg);font-style: var(--shiki-light-font-style);font-weight: var(--shiki-light-font-weight);text-decoration: var(--shiki-light-text-decoration);}html .default .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .shiki span {color: var(--shiki-default);background: var(--shiki-default-bg);font-style: var(--shiki-default-font-style);font-weight: var(--shiki-default-font-weight);text-decoration: var(--shiki-default-text-decoration);}html .dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html.dark .shiki span {color: var(--shiki-dark);background: var(--shiki-dark-bg);font-style: var(--shiki-dark-font-style);font-weight: var(--shiki-dark-font-weight);text-decoration: var(--shiki-dark-text-decoration);}html pre.shiki code .sMK4o, html code.shiki .sMK4o{--shiki-light:#39ADB5;--shiki-default:#89DDFF;--shiki-dark:#89DDFF}html pre.shiki code .s7zQu, html code.shiki .s7zQu{--shiki-light:#39ADB5;--shiki-light-font-style:italic;--shiki-default:#89DDFF;--shiki-default-font-style:italic;--shiki-dark:#89DDFF;--shiki-dark-font-style:italic}html pre.shiki code .s2Zo4, html code.shiki .s2Zo4{--shiki-light:#6182B8;--shiki-default:#82AAFF;--shiki-dark:#82AAFF}html pre.shiki code .sTEyZ, html code.shiki .sTEyZ{--shiki-light:#90A4AE;--shiki-default:#EEFFFF;--shiki-dark:#BABED8}html pre.shiki code .sHdIc, html code.shiki .sHdIc{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#EEFFFF;--shiki-default-font-style:italic;--shiki-dark:#BABED8;--shiki-dark-font-style:italic}html pre.shiki code .sbssI, html code.shiki .sbssI{--shiki-light:#F76D47;--shiki-default:#F78C6C;--shiki-dark:#F78C6C}html pre.shiki code .spNyl, html code.shiki .spNyl{--shiki-light:#9C3EDA;--shiki-default:#C792EA;--shiki-dark:#C792EA}html pre.shiki code .sHwdD, html code.shiki .sHwdD{--shiki-light:#90A4AE;--shiki-light-font-style:italic;--shiki-default:#546E7A;--shiki-default-font-style:italic;--shiki-dark:#676E95;--shiki-dark-font-style:italic}",{"title":45,"searchDepth":128,"depth":128,"links":2463},[2464,2465,2466,2467,2475,2476,2477,2478,2479,2480],{"id":16,"depth":128,"text":17},{"id":72,"depth":128,"text":73},{"id":109,"depth":128,"text":110},{"id":1156,"depth":128,"text":1157,"children":2468},[2469,2470,2471,2472,2473,2474],{"id":1161,"depth":135,"text":1162},{"id":1250,"depth":135,"text":1251},{"id":1303,"depth":135,"text":1304},{"id":1449,"depth":135,"text":1450},{"id":1634,"depth":135,"text":1635},{"id":1699,"depth":135,"text":1700},{"id":1827,"depth":128,"text":1828},{"id":2184,"depth":128,"text":2185},{"id":2255,"depth":128,"text":2256},{"id":2314,"depth":128,"text":2315},{"id":2400,"depth":128,"text":2401},{"id":2433,"depth":128,"text":2434},"2026-04-21","Código completo y ejecutable para generar una factura PDF en Go — 50 líneas con gpdf, cero dependencias, sin Chromium, sin CGO. Incluye cabecera, tabla y total.",false,"md",{"name":2486,"totalTime":2487,"tools":2488,"steps":2490},"Generar una factura PDF en Go con gpdf","PT10M",[2489],"Go 1.22+",[2491,2494,2497,2500,2503],{"name":2492,"text":2493},"Instala gpdf","Ejecuta go get github.com/gpdf-dev/gpdf en tu módulo. gpdf no tiene dependencias transitivas, por lo que go.sum queda en una línea y tu binario no gana superficie CGO.",{"name":2495,"text":2496},"Construye el documento","Llama a gpdf.NewDocument con template.WithPageSize(document.A4). Después doc.AddPage() devuelve un PageBuilder sobre el que apilas filas.",{"name":2498,"text":2499},"Maqueta la cabecera con dos celdas de 6 columnas","Usa page.AutoRow con r.Col(6, ...) dos veces. Bloque de empresa a la izquierda, etiqueta INVOICE y número a la derecha con template.AlignRight().",{"name":2501,"text":2502},"Renderiza la tabla de conceptos","Dentro de una celda de 12 columnas, llama a c.Table con un slice de cabeceras y un slice de filas. Pasa template.ColumnWidths y template.TableHeaderStyle para estilizarla.",{"name":2504,"text":2505},"Genera y escribe los bytes","Llama a doc.Generate() para obtener []byte, luego os.WriteFile(\"invoice.pdf\", b, 0644). Un archivo en disco, sin directorio temporal ni binario externo.",null,{},"/es/blog/invoice-pdf-go-under-50-lines",{"title":5,"description":2482},"es/blog/012.invoice-pdf-go-under-50-lines",[2512,2513],"tutorial","templates","9TtJSIFhtT1V1z97dcL9mZ-_00FEyATc1FvXAVR62Fw",1779199042629]