---
title: "CONFIDENCIAL: reporte para socios B&T"
subtitle: "Análisis de KPI's (desde 2023)"
abstract: |
Este reporte contiene información confidencial sobre el análisis
de indicadores clave de desempeño relacionados con operaciones del
escritorio desde el 2023.
**Uso exclusivo para socios de Bolet & Terrero.**
keywords: [marcas, patentes, estadísticas, KPI, B&T]
author: NB
lang: es
date: today
date-format: "D [de] MMMM [de] YYYY"
format:
html:
# Branding
logo: "https://byt.com.ve/wp-content/uploads/2025/05/logo-2.svg"
favicon: "https://byt.com.ve/wp-content/uploads/2025/06/favicon2-75x75.png"
# Estructura
number-sections: true
number-depth: 3
page-layout: full
# Apariencia
theme: cosmo
# Navegación
toc: true
toc-depth: 3
toc-location: left
toc-title: "Contenido"
smooth-scroll: true
anchor-sections: true
# Código
code-fold: true
code-tools: true
code-link: true
# Enlaces
link-external-newwindow: true
# Pie de página
footer: "© 2025 Bolet & Terrero - Documento Confidencial"
execute:
echo: false
warning: false
message: false
cache: false
---
<!--
_ _ _ _
___ __ _ _ __ __ _ __ _ _ __ | (_) |__ _ __ ___ _ __(_) __ _ ___
/ __/ _` | '__/ _` |/ _` | '__|____| | | '_ \| '__/ _ \ '__| |/ _` / __|
| (_| (_| | | | (_| | (_| | | |_____| | | |_) | | | __/ | | | (_| \__ \
\___\__,_|_| \__, |\__,_|_| |_|_|_.__/|_| \___|_| |_|\__,_|___/
|___/
-->
```{r}
#| label: cargar-librerias
#| include: false
# Cargar configuración
source ("galena.R" )
library (DBI)
library (DT)
library (dplyr)
library (ggplot2)
library (glue)
library (odbc)
library (tidyr)
con <- dbConnect (odbc (),
Driver = "ODBC Driver 18 for SQL Server" ,
Server = Sys.getenv ("DB_SERVER" ),
Database = Sys.getenv ("DB_DATABASE" ),
UID = Sys.getenv ("DB_USER" ),
PWD = Sys.getenv ("DB_PASSWORD" ),
Encrypt = "yes" ,
TrustServerCertificate = "yes" )
# Definir formateador de euros
euro <- scales:: label_currency (
symbol = "€" ,
accuracy = 0.01 ,
decimal.mark = "," ,
big.mark = "." ,
prefix = "" , # evita que el símbolo se repita antes del número si ya lo tienes en el eje
suffix = " €" # opcional, si quieres que el símbolo vaya al final
)
top_n <- 5
list_n <- list (pageLength = 25 )
nbs <- paste ("NB -" , Sys.time ())
dtc <- htmltools:: tags$ caption (style = 'text-align: right; font-size: 9pt; color: red; font-style: italic;' ,nbs)
pc <- element_text (hjust = 1 , size = 9 , color = "red" , face = "italic" )
ve <- '<img src="https://upload.wikimedia.org/wikipedia/commons/0/06/Flag_of_Venezuela.svg" width="40">'
```
# Marcas `r ve`
## Operaciones (desde 2023)
::: {.callout-note title="Nota"}
La información aquí presentada proviene de **Galena**. Sin embargo, puede incluir operaciones que no hayan sido gestionadas directamente por **B&T**, sino por terceros.
**Ejemplo:** cuando un portafolio de marcas es transferido a **B&T**, pueden incorporarse marcas al conteo de solicitudes de manera indirecta o no atribuible a una gestión propia.
Por lo tanto, estos datos deben considerarse como un **indicador de tendencia** y no como cifras definitivas.
:::
<!--
_
_ __ ___ __ _ _ __ ___ __ _ ___ ___ _ __ ___ _ __ __ _ ___(_) ___ _ __ ___ ___
| '_ ` _ \ / _` | '__/ __/ _` / __|_____ / _ \| '_ \ / _ \ '__/ _` |/ __| |/ _ \| '_ \ / _ \/ __|
| | | | | | (_| | | | (_| (_| \__ \_____| (_) | |_) | __/ | | (_| | (__| | (_) | | | | __/\__ \
|_| |_| |_|\__,_|_| \___\__,_|___/ \___/| .__/ \___|_| \__,_|\___|_|\___/|_| |_|\___||___/
|_|
-->
```{r}
#| label: marcas-operaciones
#| echo: false
#| message: false
#| warning: false
#| fig-width: 12
#| fig-height: 8
#| out-width: "100%"
query <- glue_sql ("
SELECT
YEAR(EXPEDIENTE.FechaSolicitud) [año]
,'SOLICITUD' [tipo]
,COUNT(*) [cantidad]
FROM
[GALENA_BYT].[dbo].[ExpedientesPrimarios] EXPEDIENTE
LEFT JOIN [GALENA_BYT].[dbo].[Marcas] MARCA ON MARCA.Id=EXPEDIENTE.IdMateria
WHERE
EXPEDIENTE.Tipo='MARCA'
AND MARCA.IdPais = 434
AND EXPEDIENTE.FechaSolicitud > DATEFROMPARTS(2023,1,1)
AND EXPEDIENTE.IdEstado NOT IN (1826)
GROUP BY
YEAR(EXPEDIENTE.FechaSolicitud)
UNION ALL
SELECT
YEAR(ES.FechaSolicitud) [año]
,ES.Tipo [tipo]
,COUNT(*) [cantidad]
FROM
[GALENA_BYT].[dbo].[ExpedientesSecundarios] ES
LEFT JOIN [GALENA_BYT].[dbo].[ExpedientesPrimarios] EXPEDIENTE ON EXPEDIENTE.Id = ES.IdExpediente
LEFT JOIN [GALENA_BYT].[dbo].[Marcas] MARCA ON MARCA.Id=EXPEDIENTE.IdMateria
WHERE
EXPEDIENTE.Tipo='MARCA'
AND ES.Tipo = 'RENOVACION'
AND MARCA.IdPais = 434
AND ES.FechaSolicitud > DATEFROMPARTS(2023,1,1)
GROUP BY
YEAR(ES.FechaSolicitud), ES.TIPO
UNION ALL
SELECT
YEAR(ES.FechaSolicitud) [año]
,'TRASPASO' [tipo]
,COUNT(*) [cantidad]
FROM
[GALENA_BYT].[dbo].[ExpedientesSecundarios] ES
LEFT JOIN [GALENA_BYT].[dbo].[ExpedientesPrimarios] EXPEDIENTE ON EXPEDIENTE.Id = ES.IdExpediente
LEFT JOIN [GALENA_BYT].[dbo].[Marcas] MARCA ON MARCA.Id=EXPEDIENTE.IdMateria
WHERE
EXPEDIENTE.Tipo='MARCA'
AND ES.Tipo <> 'RENOVACION'
AND MARCA.IdPais = 434
AND ES.FechaSolicitud > DATEFROMPARTS(2023,1,1)
GROUP BY
YEAR(ES.FechaSolicitud)
ORDER BY
año, tipo
" , .con = con)
oper <- dbGetQuery (con, query)
matriz <- oper %>%
select (año, tipo, cantidad) %>%
tidyr:: pivot_wider (
names_from = año,
values_from = cantidad,
values_fill = 0 # Rellenar con 0 si no hay datos
)
# Mostrar como tabla
matriz %>% DT:: datatable (caption = dtc, options = list_n)
# Gráfico: filtrar top x directamente desde matriz
top_x <- matriz %>%
mutate (total = rowSums (across (where (is.numeric)))) %>%
arrange (desc (total)) %>%
slice (1 : 10 ) %>%
pull (tipo)
# Graficar (necesitamos volver a formato largo solo para el top)
oper %>%
filter (tipo %in% top_x) %>%
ggplot (aes (x = reorder (tipo, cantidad), y = cantidad, fill = tipo)) +
geom_col (show.legend = FALSE ) +
geom_text (aes (label = scales:: comma (cantidad)),
hjust = 1.1 ,
size = 5 ,
color = "black" ,
fontface = "bold" ) +
coord_flip () +
facet_wrap (~ año, scales = "free_x" ) +
scale_y_continuous (
labels = scales:: comma,
expand = expansion (mult = c (0 , 0.05 ))
) +
labs (x = NULL , y = "Cantidad" , caption = nbs) +
theme_minimal () +
theme (plot.caption = pc)
```
## Traspasos detallado (desde 2023)
::: {.callout-note title="Nota"}
La información aquí presentada proviene de **Galena**. Sin embargo, puede incluir traspasos que no hayan sido gestionados directamente por **B&T**, sino por terceros.
**Ejemplo:** cuando se actualiza un expediente de marca a nivel de traspasos tras revisar la cronlogía de la **Webpi**.
Por lo tanto, estos datos deben considerarse como un **indicador de tendencia** y no como cifras definitivas.
:::
<!--
_
_ __ ___ __ _ _ __ ___ __ _ ___ | |_ _ __ __ _ ___ _ __ __ _ ___ ___ ___
| '_ ` _ \ / _` | '__/ __/ _` / __|_____| __| '__/ _` / __| '_ \ / _` / __|/ _ \/ __|
| | | | | | (_| | | | (_| (_| \__ \_____| |_| | | (_| \__ \ |_) | (_| \__ \ (_) \__ \
|_| |_| |_|\__,_|_| \___\__,_|___/ \__|_| \__,_|___/ .__/ \__,_|___/\___/|___/
|_|
-->
```{r}
#| label: marcas-traspasos
#| echo: false
#| message: false
#| warning: false
#| fig-width: 12
#| fig-height: 8
#| out-width: "100%"
query <- glue_sql ("
SELECT
YEAR(ES.FechaSolicitud) [año]
,ES.Tipo [tipo]
,COUNT(*) [cantidad]
FROM
[GALENA_BYT].[dbo].[ExpedientesSecundarios] ES
LEFT JOIN [GALENA_BYT].[dbo].[ExpedientesPrimarios] EXPEDIENTE ON EXPEDIENTE.Id = ES.IdExpediente
LEFT JOIN [GALENA_BYT].[dbo].[Marcas] MARCA ON MARCA.Id=EXPEDIENTE.IdMateria
WHERE
EXPEDIENTE.Tipo='MARCA'
AND ES.Tipo <> 'RENOVACION'
AND MARCA.IdPais = 434
AND ES.FechaSolicitud > DATEFROMPARTS(2023,1,1)
GROUP BY
YEAR(ES.FechaSolicitud), ES.Tipo
ORDER BY
YEAR(ES.FechaSolicitud), ES.Tipo
" , .con = con)
oper <- dbGetQuery (con, query)
matriz <- oper %>%
select (año, tipo, cantidad) %>%
tidyr:: pivot_wider (
names_from = año,
values_from = cantidad,
values_fill = 0 # Rellenar con 0 si no hay datos
)
# Mostrar como tabla
matriz %>% DT:: datatable (caption = dtc, options = list_n)
# Gráfico: filtrar top x directamente desde matriz
top_x <- matriz %>%
mutate (total = rowSums (across (where (is.numeric)))) %>%
arrange (desc (total)) %>%
slice (1 : 10 ) %>%
pull (tipo)
# Graficar (necesitamos volver a formato largo solo para el top)
oper %>%
filter (tipo %in% top_x) %>%
ggplot (aes (x = reorder (tipo, cantidad), y = cantidad, fill = tipo)) +
geom_col (show.legend = FALSE ) +
geom_text (aes (label = scales:: comma (cantidad)),
hjust = 1.1 ,
size = 5 ,
color = "black" ,
fontface = "bold" ) +
coord_flip () +
facet_wrap (~ año, scales = "free_x" ) +
scale_y_continuous (
labels = scales:: comma,
expand = expansion (mult = c (0 , 0.05 ))
) +
labs (x = NULL , y = "Cantidad" , caption = nbs) +
theme_minimal () +
theme (plot.caption = pc)
```
## Publicaciones en boletín (desde 2023)
::: {.callout-note title="Nota"}
La información de publicaciones en boletín para marcas es suministrada por **PuntoIp**, boletín a boletín. Al inicio de la vigencia de cada boletín se realiza un proceso de ***matching*** para identificar y marcar en **Galena** las marcas correspondientes.
En consecuencia, los datos no han sido depurados en cuanto a las marcas que se totalizan.
**Ejemplo:** pueden incluirse marcas inactivas o en vigilancia, entre otras. Asimismo, si se incorporan marcas después de realizado el ***matching***, estas no se reflejarán en el conteo.
La información debe interpretarse como un **indicador de tendencia**, y no como una cifra definitiva.
:::
<!--
_ _ _ _
_ __ ___ __ _ _ __ ___ __ _ ___ | |__ ___ | | ___| |_(_)_ __ ___ ___
| '_ ` _ \ / _` | '__/ __/ _` / __|_____| '_ \ / _ \| |/ _ \ __| | '_ \ / _ \/ __|
| | | | | | (_| | | | (_| (_| \__ \_____| |_) | (_) | | __/ |_| | | | | __/\__ \
|_| |_| |_|\__,_|_| \___\__,_|___/ |_.__/ \___/|_|\___|\__|_|_| |_|\___||___/
-->
```{r}
#| label: marcas-boletines
#| echo: false
#| message: false
#| warning: false
#| fig-width: 12
#| fig-height: 8
#| out-width: "100%"
query <- glue_sql ("
SELECT
YEAR(BOL.Fecha) [año]
,PUB.Descripcion [tipo]
,COUNT(*) [cantidad]
FROM [GALENA_BYT].[dbo].[Publicaciones] PUB
JOIN [GALENA_BYT].[dbo].[Boletines] BOL ON BOL.Id = PUB.IdBoletin
WHERE
PUB.Materia='MARCA'
AND PUB.Descripcion NOT LIKE 'BYT-%'
AND BOL.IdPais = 434
AND BOL.Fecha >= DATEFROMPARTS(2023,1,1)
GROUP BY
YEAR(BOL.Fecha),PUB.Descripcion
ORDER BY
YEAR(BOL.Fecha),PUB.Descripcion
" , .con = con)
oper <- dbGetQuery (con, query)
matriz <- oper %>%
select (año, tipo, cantidad) %>%
tidyr:: pivot_wider (
names_from = año,
values_from = cantidad,
values_fill = 0 # Rellenar con 0 si no hay datos
)
# Mostrar como tabla
matriz %>% DT:: datatable (caption = dtc, options = list_n)
# Gráfico: filtrar top x directamente desde matriz
top_x <- matriz %>%
mutate (total = rowSums (across (where (is.numeric)))) %>%
arrange (desc (total)) %>%
slice (1 : 10 ) %>%
pull (tipo)
# Graficar (necesitamos volver a formato largo solo para el top)
oper %>%
filter (tipo %in% top_x) %>%
ggplot (aes (x = reorder (tipo, cantidad), y = cantidad, fill = tipo)) +
geom_col (show.legend = FALSE ) +
geom_text (aes (label = scales:: comma (cantidad)),
hjust = 1.1 ,
size = 5 ,
color = "black" ,
fontface = "bold" ) +
coord_flip () +
facet_wrap (~ año, scales = "free_x" ) +
scale_y_continuous (
labels = scales:: comma,
expand = expansion (mult = c (0 , 0.05 ))
) +
labs (x = NULL , y = "Cantidad" , caption = nbs) +
theme_minimal () +
theme (plot.caption = pc)
```
## Pago de derechos de registro de marcas concedidas (desde 2023)
::: {.callout-note title="Nota"}
La información sobre pago de derechos de registro de marcas concedidas es tomada de los controles Excel de **NB**.
El BPI **646** está en vigencia (24-nov-2025).
:::
<!--
_ _ _
_ __ ___ __ _ _ __ ___ __ _ ___ ___ ___ _ __ ___ ___ __| (_) __| | __ _ ___
| '_ ` _ \ / _` | '__/ __/ _` / __|_____ / __/ _ \| '_ \ / __/ _ \/ _` | |/ _` |/ _` / __|
| | | | | | (_| | | | (_| (_| \__ \_____| (_| (_) | | | | (_| __/ (_| | | (_| | (_| \__ \
|_| |_| |_|\__,_|_| \___\__,_|___/ \___\___/|_| |_|\___\___|\__,_|_|\__,_|\__,_|___/
-->
```{r}
#| label: marcas-concedidas
#| echo: false
#| message: false
#| warning: false
#| fig-width: 12
#| fig-height: 8
#| out-width: "100%"
oper <- data.frame (
año = c (
2023 , 2023 , 2023 , 2023 , 2023 , 2023 ,
2024 , 2024 , 2024 , 2024 , 2024 , 2024 , 2024 , 2024 , 2024 , 2024 , 2024 ,
2025 , 2025 , 2025 , 2025 , 2025 , 2025 , 2025 , 2025 , 2025 , 2025
),
boletín = c (
620 , 621 , 622 , 623 , 624 , 625 ,
626 , 627 , 628 , 629 , 630 , 631 , 632 , 633 , 634 , 635 , 636 ,
637 , 638 , 639 , 640 , 641 , 642 , 643 , 644 , 645 , 646
),
cantidad = c (
46 , 55 , 54 , 41 , 193 , 114 ,
150 , 153 , 88 , 81 , 137 , 97 , 25 , 136 , 131 , 79 , 96 ,
66 , 145 , 200 , 81 , 86 , 31 , 110 , 114 , 105 , 12
)
)
datatable (oper, caption = dtc, options = list_n)
ggplot (oper, aes (x = boletín, y = cantidad)) +
geom_line (color = "#2E86AB" , linewidth = 1 ) +
geom_point (aes (color = factor (año)), size = 3 ) +
geom_smooth (method = "loess" , se = TRUE , color = "#A23B72" ,
linetype = "dashed" , linewidth = 0.8 , alpha = 0.2 ) +
#scale_color_manual(values = c("2023" = "#F18F01", "2024" = "#C73E1D", "2025" = "#6A994E")) +
labs (
#title = "Evolución del Pago de Derechos de Registro de Marcas",
#subtitle = "Cantidad de pagos por boletín (2023-2025)",
x = "Boletín" ,
y = "Cantidad de Pagos" ,
color = "Año" ,
caption = nbs
) +
theme_minimal (base_size = 14 ) +
theme (
#plot.title = element_text(face = "bold", size = 16),
#plot.subtitle = element_text(color = "gray40"),
plot.caption = pc,
legend.position = "top" ,
panel.grid.minor = element_blank ()
) +
scale_x_continuous (breaks = seq (620 , 646 , by = 2 ))
```
# Patentes `r ve`
## Operaciones (desde 2023)
::: {.callout-note title="Nota"}
La información aquí presentada proviene de **Galena**. Sin embargo, puede incluir operaciones que no hayan sido gestionadas directamente por **B&T**, sino por terceros.
**Ejemplo:** cuando un portafolio de patentes es transferido a **B&T**, pueden incorporarse patentes al conteo de solicitudes de manera indirecta o no atribuible a una gestión propia.
Por lo tanto, estos datos deben considerarse como un **indicador de tendencia** y no como cifras definitivas.
:::
<!--
_ _ _
_ __ __ _| |_ ___ _ __ | |_ ___ ___ ___ _ __ ___ _ __ __ _ ___(_) ___ _ __ ___ ___
| '_ \ / _` | __/ _ \ '_ \| __/ _ \/ __|_____ / _ \| '_ \ / _ \ '__/ _` |/ __| |/ _ \| '_ \ / _ \/ __|
| |_) | (_| | || __/ | | | || __/\__ \_____| (_) | |_) | __/ | | (_| | (__| | (_) | | | | __/\__ \
| .__/ \__,_|\__\___|_| |_|\__\___||___/ \___/| .__/ \___|_| \__,_|\___|_|\___/|_| |_|\___||___/
|_| |_|
-->
```{r}
#| label: patentes-operaciones
#| echo: false
#| message: false
#| warning: false
#| fig-width: 12
#| fig-height: 8
#| out-width: "100%"
query <- glue_sql ("
SELECT
YEAR(EXPEDIENTE.FechaSolicitud) [año]
,'SOLICITUD' [tipo]
,COUNT(*) [cantidad]
FROM
[GALENA_BYT].[dbo].[ExpedientesPrimarios] EXPEDIENTE
LEFT JOIN [GALENA_BYT].[dbo].[Patentes] PATENTE ON PATENTE.Id=EXPEDIENTE.IdMateria
WHERE
EXPEDIENTE.Tipo='PATENTE'
AND PATENTE.IdPais = 434
AND EXPEDIENTE.FechaSolicitud > DATEFROMPARTS(2023,1,1)
AND EXPEDIENTE.IdEstado NOT IN (1826)
GROUP BY
YEAR(EXPEDIENTE.FechaSolicitud)
UNION ALL
SELECT
YEAR(ES.FechaSolicitud) [año]
,ES.Tipo [tipo]
,COUNT(*) [cantidad]
FROM
[GALENA_BYT].[dbo].[ExpedientesSecundarios] ES
LEFT JOIN [GALENA_BYT].[dbo].[ExpedientesPrimarios] EXPEDIENTE ON EXPEDIENTE.Id = ES.IdExpediente
LEFT JOIN [GALENA_BYT].[dbo].[Patentes] PATENTE ON PATENTE.Id=EXPEDIENTE.IdMateria
WHERE
EXPEDIENTE.Tipo='PATENTE'
--AND ES.Tipo <> 'RENOVACION'
AND PATENTE.IdPais = 434
AND ES.FechaSolicitud > DATEFROMPARTS(2023,1,1)
GROUP BY
YEAR(ES.FechaSolicitud), ES.Tipo
ORDER BY
año, tipo
" , .con = con)
oper <- dbGetQuery (con, query)
matriz <- oper %>%
select (año, tipo, cantidad) %>%
tidyr:: pivot_wider (
names_from = año,
values_from = cantidad,
values_fill = 0 # Rellenar con 0 si no hay datos
)
# Mostrar como tabla
matriz %>% DT:: datatable (caption = dtc, options = list_n)
# Gráfico: filtrar top x directamente desde matriz
top_x <- matriz %>%
mutate (total = rowSums (across (where (is.numeric)))) %>%
arrange (desc (total)) %>%
slice (1 : 10 ) %>%
pull (tipo)
# Graficar (necesitamos volver a formato largo solo para el top)
oper %>%
filter (tipo %in% top_x) %>%
ggplot (aes (x = reorder (tipo, cantidad), y = cantidad, fill = tipo)) +
geom_col (show.legend = FALSE ) +
geom_text (aes (label = scales:: comma (cantidad)),
hjust = 1.1 ,
size = 5 ,
color = "black" ,
fontface = "bold" ) +
coord_flip () +
facet_wrap (~ año, scales = "free_x" ) +
scale_y_continuous (
labels = scales:: comma,
expand = expansion (mult = c (0 , 0.05 ))
) +
labs (x = NULL , y = "Cantidad" , caption = nbs) +
theme_minimal () +
theme (plot.caption = pc)
```
# Facturación y cobranzas
## Top `r top_n` de clientes por $-facturación por año (desde 2023)
<!--
__ _ _ _ _
/ _| __ _ ___| |_ _ _ _ __ __ _ __| | ___ __| | ___ | | __ _ _ __ ___ ___
| |_ / _` |/ __| __| | | | '__/ _` |/ _` |/ _ \ / _` |/ _ \| |/ _` | '__/ _ \/ __|
| _| (_| | (__| |_| |_| | | | (_| | (_| | (_) | | (_| | (_) | | (_| | | | __/\__ \
|_| \__,_|\___|\__|\__,_|_| \__,_|\__,_|\___/___\__,_|\___/|_|\__,_|_| \___||___/
|_____|
-->
```{r}
#| label: facturado_dolares
#| echo: false
#| message: false
#| warning: false
#| fig-width: 12
#| fig-height: 8
#| out-width: "100%"
query <- glue_sql ("
WITH ClientesPorAño AS (
SELECT
YEAR(F.Fecha) [año],
F.IdClienteFacturacion [id_cliente],
CONCAT(TRIM(UPPER(C.Nombre)) COLLATE Modern_Spanish_CI_AS,' [',C.Codigo COLLATE Modern_Spanish_CI_AS,']') [cliente],
SUM(F.ValorTotal) [monto],
ROW_NUMBER() OVER (PARTITION BY YEAR(F.Fecha) ORDER BY SUM(F.ValorTotal) DESC) [ranking]
FROM
[GALENA_BYT].[dbo].[Facturas] F
JOIN [GALENA_BYT].[dbo].[Clientes] C ON C.Id = F.IdClienteFacturacion
WHERE
F.Estado IN (1, 4)
AND YEAR(F.Fecha) >= 2023
AND F.Nro <> ''
AND F.ValorTotal > 0
AND C.Codigo <> '000000'
AND F.PerfilMoneda = 1 -- Solo dólares
GROUP BY
YEAR(F.Fecha), F.IdClienteFacturacion, C.Codigo, C.Nombre
),
TopClientes AS (
-- Clientes que fueron top 5 en al menos un año
SELECT DISTINCT id_cliente
FROM ClientesPorAño
WHERE ranking <= {top_n}
)
SELECT
c.año,
c.cliente,
ISNULL(c.monto, 0) [monto]
FROM
ClientesPorAño c
INNER JOIN TopClientes t ON t.id_cliente = c.id_cliente
ORDER BY
c.cliente, c.año
" , .con = con)
clientes_dolares <- dbGetQuery (con, query)
# Matriz para dólares
matriz_dolares <- clientes_dolares %>%
tidyr:: pivot_wider (
names_from = año,
values_from = monto,
values_fill = 0
)
# Agregar columna de total para ordenar
matriz_dolares <- matriz_dolares %>%
mutate (Total = rowSums (across (where (is.numeric)))) %>%
arrange (desc (Total)) %>%
select (- Total) # Quitar columna total después de ordenar
# Mostrar tabla
matriz_dolares %>%
DT:: datatable (
caption = dtc,
options = list_n,
rownames = TRUE
) %>%
formatCurrency (
columns = 2 : ncol (matriz_dolares),
currency = '$' ,
digits = 2 ,
mark = ',' ,
dec.mark = '.'
)
# Gráfico: filtrar top x directamente desde matriz
top_x_clientes <- matriz_dolares %>%
mutate (total = rowSums (across (where (is.numeric)))) %>%
arrange (desc (total)) %>%
slice (1 : 10 ) %>%
pull (cliente)
# Graficar con labels inteligentes
clientes_dolares %>%
filter (cliente %in% top_x_clientes) %>%
group_by (año) %>%
mutate (max_monto = max (monto),
label_pos = ifelse (monto < max_monto * 0.35 , monto + max_monto * 0.03 , monto),
label_hjust = ifelse (monto < max_monto * 0.35 , - 0.1 , 1.1 ),
label_color = ifelse (monto < max_monto * 0.35 , "black" , "black" )) %>%
ungroup () %>%
ggplot (aes (x = reorder (cliente, monto), y = monto, fill = cliente)) +
geom_col (show.legend = FALSE ) +
geom_text (
aes (label = scales:: dollar (monto, accuracy = 0.01 ),
y = label_pos,
hjust = label_hjust,
color = label_color),
size = 4 ,
fontface = "bold" ,
show.legend = FALSE
) +
scale_color_identity () +
coord_flip () +
facet_wrap (~ año, scales = "free_x" ) +
scale_y_continuous (
labels = scales:: dollar,
expand = expansion (mult = c (0 , 0.15 ))
) +
labs (x = NULL , y = "Facturado (USD)" , caption = nbs) +
theme_minimal () +
theme (plot.caption = pc)
```
## Top `r top_n` de clientes por €-facturación por año (desde 2023)
<!--
__ _ _
/ _| __ _ ___| |_ _ _ _ __ __ _ __| | ___ ___ _ _ _ __ ___ ___
| |_ / _` |/ __| __| | | | '__/ _` |/ _` |/ _ \ / _ \ | | | '__/ _ \/ __|
| _| (_| | (__| |_| |_| | | | (_| | (_| | (_) | | __/ |_| | | | (_) \__ \
|_| \__,_|\___|\__|\__,_|_| \__,_|\__,_|\___/___\___|\__,_|_| \___/|___/
|_____|
-->
```{r}
#| label: facturado_euros
#| echo: false
#| message: false
#| warning: false
#| fig-width: 12
#| fig-height: 8
#| out-width: "100%"
query <- glue_sql ("
WITH ClientesPorAño AS (
SELECT
YEAR(F.Fecha) [año],
F.IdClienteFacturacion [id_cliente],
CONCAT(TRIM(UPPER(C.Nombre)) COLLATE Modern_Spanish_CI_AS,' [',C.Codigo COLLATE Modern_Spanish_CI_AS,']') [cliente],
SUM(F.ValorTotal) [monto],
ROW_NUMBER() OVER (PARTITION BY YEAR(F.Fecha) ORDER BY SUM(F.ValorTotal) DESC) [ranking]
FROM
[GALENA_BYT].[dbo].[Facturas] F
JOIN [GALENA_BYT].[dbo].[Clientes] C ON C.Id = F.IdClienteFacturacion
WHERE
F.Estado IN (1, 4)
AND YEAR(F.Fecha) >= 2023
AND F.Nro <> ''
AND F.ValorTotal > 0
AND C.Codigo <> '000000'
AND F.PerfilMoneda = 3 -- Solo euros
GROUP BY
YEAR(F.Fecha), F.IdClienteFacturacion, C.Codigo, C.Nombre
),
TopClientes AS (
SELECT DISTINCT id_cliente
FROM ClientesPorAño
WHERE ranking <= {top_n}
)
SELECT
c.año,
c.cliente,
ISNULL(c.monto, 0) [monto]
FROM
ClientesPorAño c
INNER JOIN TopClientes t ON t.id_cliente = c.id_cliente
ORDER BY
c.cliente, c.año
" , .con = con)
clientes_euros <- dbGetQuery (con, query)
# Obtener años en orden
años_ordenados <- sort (unique (clientes_euros$ año))
# Matriz para euros con columnas ordenadas
matriz_euros <- clientes_euros %>%
tidyr:: pivot_wider (
names_from = año,
values_from = monto,
values_fill = 0
) %>%
# Reordenar columnas: cliente primero, luego años en orden
select (cliente, all_of (as.character (años_ordenados)))
# Agregar columna de total para ordenar
matriz_euros <- matriz_euros %>%
mutate (Total = rowSums (across (where (is.numeric)))) %>%
arrange (desc (Total)) %>%
select (- Total)
# Mostrar tabla
matriz_euros %>%
DT:: datatable (
caption = dtc,
options = list_n,
rownames = TRUE
) %>%
formatCurrency (
columns = 2 : ncol (matriz_euros),
currency = '€' ,
digits = 2 ,
mark = ',' ,
dec.mark = '.'
)
# Gráfico: filtrar top x directamente desde matriz
top_x_clientes <- matriz_euros %>%
mutate (total = rowSums (across (where (is.numeric)))) %>%
arrange (desc (total)) %>%
slice (1 : 10 ) %>%
pull (cliente)
# Graficar con labels inteligentes
clientes_euros %>%
filter (cliente %in% top_x_clientes) %>%
group_by (año) %>%
mutate (max_monto = max (monto),
label_pos = ifelse (monto < max_monto * 0.35 , monto + max_monto * 0.03 , monto),
label_hjust = ifelse (monto < max_monto * 0.35 , - 0.1 , 1.1 ),
label_color = ifelse (monto < max_monto * 0.35 , "black" , "black" )) %>% # Corregido
ungroup () %>%
ggplot (aes (x = reorder (cliente, monto), y = monto, fill = cliente)) +
geom_col (show.legend = FALSE ) +
geom_text (
aes (label = euro (monto),
y = label_pos,
hjust = label_hjust,
color = label_color),
size = 4 ,
fontface = "bold" ,
show.legend = FALSE
) +
scale_color_identity () +
coord_flip () +
facet_wrap (~ año, scales = "free_x" ) +
scale_y_continuous (
labels = euro,
expand = expansion (mult = c (0 , 0.18 ))
) +
labs (x = NULL , y = "Facturado (€)" , caption = nbs) + # Corregido
theme_minimal () +
theme (plot.caption = pc)
```
## Top `r top_n` de clientes por $-deuda por año (desde 2023)
<!--
_ _ _ _ _
__ _ __| | ___ _ _ __| | __ _ __| | ___ __| | ___ | | __ _ _ __ ___ ___
/ _` |/ _` |/ _ \ | | |/ _` |/ _` |/ _` |/ _ \ / _` |/ _ \| |/ _` | '__/ _ \/ __|
| (_| | (_| | __/ |_| | (_| | (_| | (_| | (_) | | (_| | (_) | | (_| | | | __/\__ \
\__,_|\__,_|\___|\__,_|\__,_|\__,_|\__,_|\___/___\__,_|\___/|_|\__,_|_| \___||___/
|_____|
-->
```{r}
#| label: adeudado_dolares
#| echo: false
#| message: false
#| warning: false
#| fig-width: 12
#| fig-height: 8
#| out-width: "100%"
query <- glue_sql ("
WITH ClientesPorAño AS (
SELECT
YEAR(F.Fecha) [año],
F.IdClienteFacturacion [id_cliente],
CONCAT(TRIM(UPPER(C.Nombre)) COLLATE Modern_Spanish_CI_AS,' [',C.Codigo COLLATE Modern_Spanish_CI_AS,']') [cliente],
SUM(F.ValorTotal-F.TotalPagado) [monto],
ROW_NUMBER() OVER (PARTITION BY YEAR(F.Fecha) ORDER BY SUM(F.ValorTotal-F.TotalPagado) DESC) [ranking]
FROM
[GALENA_BYT].[dbo].[Facturas] F
JOIN [GALENA_BYT].[dbo].[Clientes] C ON C.Id = F.IdClienteFacturacion
WHERE
F.Estado IN (1, 4)
AND YEAR(F.Fecha) >= 2023
AND F.Nro <> ''
AND F.ValorTotal > 0
AND C.Codigo <> '000000'
AND F.PerfilMoneda = 1 -- Solo dólares
AND (F.ValorTotal - F.TotalPagado) > 0 -- Solo deudas reales
GROUP BY
YEAR(F.Fecha), F.IdClienteFacturacion, C.Codigo, C.Nombre
),
TopClientes AS (
SELECT DISTINCT id_cliente
FROM ClientesPorAño
WHERE ranking <= {top_n}
)
SELECT
c.año,
c.cliente,
c.monto -- Sin ISNULL
FROM
ClientesPorAño c
INNER JOIN TopClientes t ON t.id_cliente = c.id_cliente
ORDER BY
c.cliente, c.año
" , .con = con)
clientes_dolares <- dbGetQuery (con, query)
# Obtener años en orden
años_ordenados <- sort (unique (clientes_dolares$ año))
# Matriz para dólares con columnas ordenadas
matriz_dolares <- clientes_dolares %>%
tidyr:: pivot_wider (
names_from = año,
values_from = monto,
values_fill = 0 # Mantener para la matriz visual
) %>%
select (cliente, all_of (as.character (años_ordenados)))
# Agregar columna de total para ordenar
matriz_dolares <- matriz_dolares %>%
mutate (Total = rowSums (across (where (is.numeric)))) %>%
arrange (desc (Total)) %>%
select (- Total)
# Mostrar tabla
matriz_dolares %>%
DT:: datatable (
caption = dtc,
options = list_n,
rownames = TRUE
) %>%
formatCurrency (
columns = 2 : ncol (matriz_dolares),
currency = '$' ,
digits = 2 ,
mark = ',' ,
dec.mark = '.'
)
# Gráfico: filtrar top x directamente desde matriz
top_x_clientes <- matriz_dolares %>%
mutate (total = rowSums (across (where (is.numeric)))) %>%
arrange (desc (total)) %>%
pull (cliente)
# Graficar con labels inteligentes - FILTRAR montos > 0
clientes_dolares %>%
filter (cliente %in% top_x_clientes, monto > 0.5 ) %>% # Filtrar montos cero
group_by (año) %>%
mutate (max_monto = max (monto),
label_pos = ifelse (monto < max_monto * 0.40 , monto + max_monto * 0.03 , monto),
label_hjust = ifelse (monto < max_monto * 0.40 , - 0.1 , 1.1 ),
label_color = ifelse (monto < max_monto * 0.40 , "black" , "black" )) %>% # Corregido
ungroup () %>%
ggplot (aes (x = reorder (cliente, monto), y = monto, fill = cliente)) +
geom_col (show.legend = FALSE ) +
geom_text (
aes (label = scales:: dollar (monto, accuracy = 0.01 ),
y = label_pos,
hjust = label_hjust,
color = label_color),
size = 4 ,
fontface = "bold" ,
show.legend = FALSE
) +
scale_color_identity () +
coord_flip () +
facet_wrap (~ año, scales = "free_x" ) +
scale_y_continuous (
labels = scales:: dollar,
expand = expansion (mult = c (0 , 0.18 ))
) +
labs (x = NULL , y = "Adeudado ($)" , caption = nbs) +
theme_minimal () +
theme (plot.caption = pc)
```
## Top `r top_n` de clientes por €-deuda por año (desde 2023)
<!--
_ _ _
__ _ __| | ___ _ _ __| | __ _ __| | ___ ___ _ _ _ __ ___ ___
/ _` |/ _` |/ _ \ | | |/ _` |/ _` |/ _` |/ _ \ / _ \ | | | '__/ _ \/ __|
| (_| | (_| | __/ |_| | (_| | (_| | (_| | (_) | | __/ |_| | | | (_) \__ \
\__,_|\__,_|\___|\__,_|\__,_|\__,_|\__,_|\___/___\___|\__,_|_| \___/|___/
|_____|
-->
```{r}
#| label: adeudado_euros
#| echo: false
#| message: false
#| warning: false
#| fig-width: 12
#| fig-height: 8
#| out-width: "100%"
query <- glue_sql ("
WITH ClientesPorAño AS (
SELECT
YEAR(F.Fecha) [año],
F.IdClienteFacturacion [id_cliente],
CONCAT(TRIM(UPPER(C.Nombre)) COLLATE Modern_Spanish_CI_AS,' [',C.Codigo COLLATE Modern_Spanish_CI_AS,']') [cliente],
SUM(F.ValorTotal-F.TotalPagado) [monto],
ROW_NUMBER() OVER (PARTITION BY YEAR(F.Fecha) ORDER BY SUM(F.ValorTotal-F.TotalPagado) DESC) [ranking]
FROM
[GALENA_BYT].[dbo].[Facturas] F
JOIN [GALENA_BYT].[dbo].[Clientes] C ON C.Id = F.IdClienteFacturacion
WHERE
F.Estado IN (1, 4)
AND YEAR(F.Fecha) >= 2023
AND F.Nro <> ''
AND F.ValorTotal > 0
AND C.Codigo <> '000000'
AND F.PerfilMoneda = 3 -- Solo euros
AND (F.ValorTotal - F.TotalPagado) > 0 -- Solo deudas reales
GROUP BY
YEAR(F.Fecha), F.IdClienteFacturacion, C.Codigo, C.Nombre
),
TopClientes AS (
SELECT DISTINCT id_cliente
FROM ClientesPorAño
WHERE ranking <= {top_n}
)
SELECT
c.año,
c.cliente,
c.monto -- Sin ISNULL
FROM
ClientesPorAño c
INNER JOIN TopClientes t ON t.id_cliente = c.id_cliente
ORDER BY
c.cliente, c.año
" , .con = con)
clientes_euros <- dbGetQuery (con, query)
# Obtener años en orden
años_ordenados <- sort (unique (clientes_euros$ año))
# Matriz para dólares con columnas ordenadas
matriz_euros <- clientes_euros %>%
tidyr:: pivot_wider (
names_from = año,
values_from = monto,
values_fill = 0 # Mantener para la matriz visual
) %>%
select (cliente, all_of (as.character (años_ordenados)))
# Agregar columna de total para ordenar
matriz_euros <- matriz_euros %>%
mutate (Total = rowSums (across (where (is.numeric)))) %>%
arrange (desc (Total)) %>%
select (- Total)
# Mostrar tabla
matriz_euros %>%
DT:: datatable (
caption = dtc,
options = list_n,
rownames = TRUE
) %>%
formatCurrency (
columns = 2 : ncol (matriz_euros),
currency = '€' ,
digits = 2 ,
mark = ',' ,
dec.mark = '.'
)
# Gráfico: filtrar top x directamente desde matriz
top_x_clientes <- matriz_euros %>%
mutate (total = rowSums (across (where (is.numeric)))) %>%
arrange (desc (total)) %>%
pull (cliente)
# Definir formateador de euros
euro2 <- scales:: label_currency (
symbol = "€" ,
accuracy = 0.01 ,
decimal.mark = "," ,
big.mark = "." ,
prefix = "" , # evita que el símbolo se repita antes del número si ya lo tienes en el eje
suffix = " €" # opcional, si quieres que el símbolo vaya al final
)
# Graficar con labels inteligentes - FILTRAR montos > 0
clientes_euros %>%
filter (cliente %in% top_x_clientes, monto > 0.5 ) %>% # Filtrar montos cero
group_by (año) %>%
mutate (max_monto = max (monto),
label_pos = ifelse (monto < max_monto * 0.40 , monto + max_monto * 0.03 , monto),
label_hjust = ifelse (monto < max_monto * 0.40 , - 0.1 , 1.1 ),
label_color = ifelse (monto < max_monto * 0.40 , "black" , "black" )) %>% # Corregido
ungroup () %>%
ggplot (aes (x = reorder (cliente, monto), y = monto, fill = cliente)) +
geom_col (show.legend = FALSE ) +
geom_text (
aes (
y = label_pos,
hjust = label_hjust,
color = label_color,
label = euro (monto)
),
size = 4 ,
fontface = "bold" ,
show.legend = FALSE
) +
scale_color_identity () +
coord_flip () +
facet_wrap (~ año, scales = "free_x" ) +
scale_y_continuous (
labels = euro,
expand = expansion (mult = c (0 , 0.18 ))
) +
labs (x = NULL , y = "Adeudado (€)" , caption = nbs) +
theme_minimal () +
theme (plot.caption = pc)
```