feat(integrations): add skills endpoint, enrich creative context with client info, and support meta in creatives
This commit is contained in:
66
skills/clients/blue-parrot-school.md
Normal file
66
skills/clients/blue-parrot-school.md
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
# Skill de cliente · Blue Parrot School (slug: bps)
|
||||||
|
|
||||||
|
> Esta skill define cómo habla y cómo se ve Blue Parrot School en cualquier creatividad. Se inyecta como contexto fijo a Claude en cada petición de relevo. Si algo aquí contradice un brief puntual, manda esta skill.
|
||||||
|
|
||||||
|
## Quién es
|
||||||
|
|
||||||
|
Academia de idiomas con sede en Madrid y Málaga, clases presenciales, online e in-company. Dos líneas:
|
||||||
|
|
||||||
|
- **B2B (empresas):** programas de inglés profesional para equipos. Diferencia frente a apps conversacionales tipo Twenix: profesor fijo, sesiones de 60 minutos, programas por sector (Inglés Legal, IT, Financiero, Comercial Internacional), modalidad híbrida. Es **Entidad Organizadora de FUNDAE** (gestiona la bonificación directamente, no a través de un partner).
|
||||||
|
- **B2C (particulares):** cursos de inglés para adultos, preparación de exámenes oficiales, intensivos.
|
||||||
|
|
||||||
|
Web: blueparrotschool.com
|
||||||
|
|
||||||
|
## A quién le hablamos
|
||||||
|
|
||||||
|
**B2B**
|
||||||
|
- *Marta* — RRHH / Formación. Quiere un proveedor que no le dé trabajo: gestión FUNDAE resuelta, informes de asistencia, un interlocutor.
|
||||||
|
- *Carlos* — director o socio de despacho / consultora. Le importa que su equipo hable con clientes internacionales sin hacer el ridículo. Compra rigor, no "clases".
|
||||||
|
- *Lucía* — directora comercial / internacional. Necesita resultados medibles en poco tiempo: presentaciones, negociación, llamadas.
|
||||||
|
|
||||||
|
**B2C**
|
||||||
|
- Adultos 25-45 que necesitan el inglés para el trabajo o para un examen (Cambridge, Aptis, IELTS) y han fracasado con apps.
|
||||||
|
|
||||||
|
## Cómo habla
|
||||||
|
|
||||||
|
- **Tono:** profesional, seguro, directo. Cercano sin ser coloquial. Nunca infantil, nunca grito de descuento.
|
||||||
|
- **Postura:** "formación profesional estructurada", no "aprende inglés fácil". Vendemos **programas**, no clases sueltas.
|
||||||
|
- **Español de España**, tú o usted según el público: *tú* en B2C, *tú* en B2B cuando hablamos a la persona, *usted* nunca.
|
||||||
|
- **Frases cortas.** Un dato concreto vale más que un adjetivo.
|
||||||
|
- **Inglés en el copy:** solo nombres propios de exámenes o términos sin traducción natural. Nada de "boost", "unlock", "level up".
|
||||||
|
|
||||||
|
### Argumentos que funcionan
|
||||||
|
1. Profesor fijo + 60 minutos: progreso real, no mantenimiento.
|
||||||
|
2. FUNDAE gestionado por nosotros: coste cero o casi cero para la empresa.
|
||||||
|
3. Programas por sector: tu equipo aprende el inglés de su trabajo.
|
||||||
|
4. Presencial en Madrid y Málaga + online: híbrido de verdad.
|
||||||
|
5. Informes de asistencia y progreso para RRHH.
|
||||||
|
|
||||||
|
### Objeciones a desactivar
|
||||||
|
- "Ya usamos una app" → la app mantiene, no avanza.
|
||||||
|
- "Es caro" → FUNDAE; coste por empleado y mes.
|
||||||
|
- "No tenemos tiempo" → sesiones in-company en horario laboral.
|
||||||
|
|
||||||
|
## Prohibido
|
||||||
|
- Prometer niveles o plazos ("B2 en 3 meses").
|
||||||
|
- Comparar nombrando a la competencia (ni Twenix ni ninguna).
|
||||||
|
- Descuentos agresivos, cuentas atrás, "última oportunidad".
|
||||||
|
- Emojis en creatividades de imagen. En copy, como mucho uno al inicio y solo en B2C.
|
||||||
|
- Fotografía de stock genérica de "gente feliz con portátil" con aspecto de banco de imágenes.
|
||||||
|
- Testimonios inventados o con nombre y cargo de personas que no existen. Si no hay un caso real aprobado, no se usa testimonio.
|
||||||
|
- Deformar el logo, recolorearlo o recrearlo. Se coloca el archivo oficial.
|
||||||
|
|
||||||
|
## Identidad visual
|
||||||
|
|
||||||
|
- **Paleta exacta y única:** Azul principal `#2A4D9C` · Amarillo `#FBC02D` · Negro `#000000` · Blanco `#FFFFFF`. Ningún otro color.
|
||||||
|
- **Uso de superficies:** azul y blanco como base. Amarillo solo para resaltar una palabra, un dato o el CTA. Negro reservado a mensajes de formalidad o urgencia (FUNDAE que caduca, manifiesto).
|
||||||
|
- **Tipografía:** Poppins. Extrabold en titulares, Regular en cuerpo. Nunca otra fuente.
|
||||||
|
- **Logo:** lockup oficial *Blue Parrot* + *SCHOOL* en amarillo; versión con filete blanco sobre fondos azul o negro. Área de protección respetada, esquina inferior o superior, nunca centrado ni dominando.
|
||||||
|
- **Estilo de imagen:** fotografía real de aula, oficina o reunión con luz natural; personas adultas de 25-50 en contexto de trabajo; sin poses de catálogo. Si es ilustración o composición gráfica, geometría limpia, mucho aire, una sola idea.
|
||||||
|
- **Texto en imagen:** máximo 2 niveles (titular + apoyo). El titular no supera 8 palabras. Nada de texto tocando bordes: margen mínimo 8 % del lado corto.
|
||||||
|
|
||||||
|
## Ejemplos de hooks que han funcionado (referencia de registro, no para copiar)
|
||||||
|
- "Tu equipo no necesita más inglés. Necesita el inglés de su trabajo."
|
||||||
|
- "FUNDAE lo paga. Nosotros lo gestionamos."
|
||||||
|
- "Un profesor. Sesenta minutos. Cada semana. Así se avanza."
|
||||||
|
- "El inglés legal no se aprende en una app."
|
||||||
66
skills/clients/bps.md
Normal file
66
skills/clients/bps.md
Normal file
@@ -0,0 +1,66 @@
|
|||||||
|
# Skill de cliente · Blue Parrot School (slug: bps)
|
||||||
|
|
||||||
|
> Esta skill define cómo habla y cómo se ve Blue Parrot School en cualquier creatividad. Se inyecta como contexto fijo a Claude en cada petición de relevo. Si algo aquí contradice un brief puntual, manda esta skill.
|
||||||
|
|
||||||
|
## Quién es
|
||||||
|
|
||||||
|
Academia de idiomas con sede en Madrid y Málaga, clases presenciales, online e in-company. Dos líneas:
|
||||||
|
|
||||||
|
- **B2B (empresas):** programas de inglés profesional para equipos. Diferencia frente a apps conversacionales tipo Twenix: profesor fijo, sesiones de 60 minutos, programas por sector (Inglés Legal, IT, Financiero, Comercial Internacional), modalidad híbrida. Es **Entidad Organizadora de FUNDAE** (gestiona la bonificación directamente, no a través de un partner).
|
||||||
|
- **B2C (particulares):** cursos de inglés para adultos, preparación de exámenes oficiales, intensivos.
|
||||||
|
|
||||||
|
Web: blueparrotschool.com
|
||||||
|
|
||||||
|
## A quién le hablamos
|
||||||
|
|
||||||
|
**B2B**
|
||||||
|
- *Marta* — RRHH / Formación. Quiere un proveedor que no le dé trabajo: gestión FUNDAE resuelta, informes de asistencia, un interlocutor.
|
||||||
|
- *Carlos* — director o socio de despacho / consultora. Le importa que su equipo hable con clientes internacionales sin hacer el ridículo. Compra rigor, no "clases".
|
||||||
|
- *Lucía* — directora comercial / internacional. Necesita resultados medibles en poco tiempo: presentaciones, negociación, llamadas.
|
||||||
|
|
||||||
|
**B2C**
|
||||||
|
- Adultos 25-45 que necesitan el inglés para el trabajo o para un examen (Cambridge, Aptis, IELTS) y han fracasado con apps.
|
||||||
|
|
||||||
|
## Cómo habla
|
||||||
|
|
||||||
|
- **Tono:** profesional, seguro, directo. Cercano sin ser coloquial. Nunca infantil, nunca grito de descuento.
|
||||||
|
- **Postura:** "formación profesional estructurada", no "aprende inglés fácil". Vendemos **programas**, no clases sueltas.
|
||||||
|
- **Español de España**, tú o usted según el público: *tú* en B2C, *tú* en B2B cuando hablamos a la persona, *usted* nunca.
|
||||||
|
- **Frases cortas.** Un dato concreto vale más que un adjetivo.
|
||||||
|
- **Inglés en el copy:** solo nombres propios de exámenes o términos sin traducción natural. Nada de "boost", "unlock", "level up".
|
||||||
|
|
||||||
|
### Argumentos que funcionan
|
||||||
|
1. Profesor fijo + 60 minutos: progreso real, no mantenimiento.
|
||||||
|
2. FUNDAE gestionado por nosotros: coste cero o casi cero para la empresa.
|
||||||
|
3. Programas por sector: tu equipo aprende el inglés de su trabajo.
|
||||||
|
4. Presencial en Madrid y Málaga + online: híbrido de verdad.
|
||||||
|
5. Informes de asistencia y progreso para RRHH.
|
||||||
|
|
||||||
|
### Objeciones a desactivar
|
||||||
|
- "Ya usamos una app" → la app mantiene, no avanza.
|
||||||
|
- "Es caro" → FUNDAE; coste por empleado y mes.
|
||||||
|
- "No tenemos tiempo" → sesiones in-company en horario laboral.
|
||||||
|
|
||||||
|
## Prohibido
|
||||||
|
- Prometer niveles o plazos ("B2 en 3 meses").
|
||||||
|
- Comparar nombrando a la competencia (ni Twenix ni ninguna).
|
||||||
|
- Descuentos agresivos, cuentas atrás, "última oportunidad".
|
||||||
|
- Emojis en creatividades de imagen. En copy, como mucho uno al inicio y solo en B2C.
|
||||||
|
- Fotografía de stock genérica de "gente feliz con portátil" con aspecto de banco de imágenes.
|
||||||
|
- Testimonios inventados o con nombre y cargo de personas que no existen. Si no hay un caso real aprobado, no se usa testimonio.
|
||||||
|
- Deformar el logo, recolorearlo o recrearlo. Se coloca el archivo oficial.
|
||||||
|
|
||||||
|
## Identidad visual
|
||||||
|
|
||||||
|
- **Paleta exacta y única:** Azul principal `#2A4D9C` · Amarillo `#FBC02D` · Negro `#000000` · Blanco `#FFFFFF`. Ningún otro color.
|
||||||
|
- **Uso de superficies:** azul y blanco como base. Amarillo solo para resaltar una palabra, un dato o el CTA. Negro reservado a mensajes de formalidad o urgencia (FUNDAE que caduca, manifiesto).
|
||||||
|
- **Tipografía:** Poppins. Extrabold en titulares, Regular en cuerpo. Nunca otra fuente.
|
||||||
|
- **Logo:** lockup oficial *Blue Parrot* + *SCHOOL* en amarillo; versión con filete blanco sobre fondos azul o negro. Área de protección respetada, esquina inferior o superior, nunca centrado ni dominando.
|
||||||
|
- **Estilo de imagen:** fotografía real de aula, oficina o reunión con luz natural; personas adultas de 25-50 en contexto de trabajo; sin poses de catálogo. Si es ilustración o composición gráfica, geometría limpia, mucho aire, una sola idea.
|
||||||
|
- **Texto en imagen:** máximo 2 niveles (titular + apoyo). El titular no supera 8 palabras. Nada de texto tocando bordes: margen mínimo 8 % del lado corto.
|
||||||
|
|
||||||
|
## Ejemplos de hooks que han funcionado (referencia de registro, no para copiar)
|
||||||
|
- "Tu equipo no necesita más inglés. Necesita el inglés de su trabajo."
|
||||||
|
- "FUNDAE lo paga. Nosotros lo gestionamos."
|
||||||
|
- "Un profesor. Sesenta minutos. Cada semana. Así se avanza."
|
||||||
|
- "El inglés legal no se aprende en una app."
|
||||||
65
skills/formats/estatico-4-5.md
Normal file
65
skills/formats/estatico-4-5.md
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
# Skill de formato · Estático para Meta Ads (4:5)
|
||||||
|
|
||||||
|
> Se aplica junto a la skill del cliente. Define la estructura de un anuncio estático de imagen para feed de Instagram y Facebook. Siempre 4:5 (1080×1350).
|
||||||
|
|
||||||
|
## Qué entregas por cada variante
|
||||||
|
|
||||||
|
Un objeto JSON con exactamente estos campos:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"angle": "nombre corto del ángulo (p. ej. 'objeción tiempo', 'prueba social', 'coste FUNDAE', 'antes/después', 'autoridad')",
|
||||||
|
"hook": "titular que va EN la imagen, máx. 8 palabras",
|
||||||
|
"support": "línea de apoyo en la imagen, máx. 12 palabras, opcional (null si no hace falta)",
|
||||||
|
"headline": "título del anuncio en Meta, máx. 40 caracteres",
|
||||||
|
"primary_text": "texto principal del anuncio, 2-4 frases, máx. 300 caracteres, la primera frase es el gancho",
|
||||||
|
"cta": "uno de: 'Más información', 'Solicitar presupuesto', 'Reservar', 'Contactar', 'Registrarte'",
|
||||||
|
"image_prompt": "prompt en inglés para el generador de imagen, SIN texto ni logo, describiendo escena, personas, luz, encuadre y paleta",
|
||||||
|
"layout": "una de: 'foto-con-franja' (foto + franja de color con titular), 'tipografico' (solo color y texto), 'dato-grande' (una cifra enorme + contexto)",
|
||||||
|
"rationale": "una frase: por qué este ángulo puede relevar a la fatigada"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Reglas de ángulo
|
||||||
|
|
||||||
|
- Cada variante ataca un **ángulo distinto** entre sí y distinto del de la creatividad fatigada (viene en `avoid_angles`).
|
||||||
|
- Prioriza ángulos que aún no se hayan probado en el cliente (mira `top_creatives` del contexto: lo que ya funciona no se repite, se complementa).
|
||||||
|
- Rotación recomendada: dolor → solución → prueba → objeción → oferta. Si la fatigada era "oferta", el relevo vuelve a "dolor" o "prueba".
|
||||||
|
|
||||||
|
## Reglas de hook (titular en imagen)
|
||||||
|
|
||||||
|
- Máximo 8 palabras. Si necesita más, no es un hook.
|
||||||
|
- Una sola idea. Un verbo o un dato.
|
||||||
|
- Sin signos de exclamación. Sin mayúsculas completas.
|
||||||
|
- Se lee en 1,5 segundos en móvil con el pulgar en movimiento.
|
||||||
|
- Prohibido empezar por "¿Sabías que", "Descubre", "Imagina".
|
||||||
|
|
||||||
|
## Reglas de copy (primary_text)
|
||||||
|
|
||||||
|
- Frase 1: repite o amplía el hook, nunca lo contradice.
|
||||||
|
- Frase 2-3: un beneficio concreto o un dato.
|
||||||
|
- Frase final: el CTA en palabras, coherente con el botón.
|
||||||
|
- Sin hashtags. Sin enlaces en el texto.
|
||||||
|
|
||||||
|
## Reglas de imagen (image_prompt)
|
||||||
|
|
||||||
|
- Siempre en inglés, 40-80 palabras.
|
||||||
|
- Describe: sujeto, acción, entorno, luz, encuadre (vertical 4:5, espacio libre arriba o abajo para texto), paleta en términos de la skill del cliente.
|
||||||
|
- Termina siempre con: `no text, no logos, no watermarks, photorealistic, vertical 4:5 composition with clear negative space for a headline`.
|
||||||
|
- Nunca pidas personas reconocibles, marcas de terceros, ni niños.
|
||||||
|
- Una generación por variante. No pidas variaciones.
|
||||||
|
|
||||||
|
## Reglas de layout
|
||||||
|
|
||||||
|
- Zona segura: 8 % de margen en todos los lados. Texto nunca en el 15 % superior ni inferior (lo tapan la interfaz y el CTA de Meta).
|
||||||
|
- Jerarquía: hook ≥ 2× el tamaño del support.
|
||||||
|
- Contraste mínimo 4,5:1 entre texto y fondo.
|
||||||
|
- El logo ocupa como máximo el 12 % del ancho.
|
||||||
|
|
||||||
|
## Autocheck antes de entregar
|
||||||
|
|
||||||
|
- [ ] ¿Las variantes tienen ángulos distintos entre sí y del `avoid_angles`?
|
||||||
|
- [ ] ¿Cada hook tiene ≤ 8 palabras?
|
||||||
|
- [ ] ¿El primary_text cabe en 300 caracteres?
|
||||||
|
- [ ] ¿El image_prompt no pide texto ni logo?
|
||||||
|
- [ ] ¿Se respeta la lista de prohibiciones de la skill del cliente?
|
||||||
65
skills/formats/estatico.md
Normal file
65
skills/formats/estatico.md
Normal file
@@ -0,0 +1,65 @@
|
|||||||
|
# Skill de formato · Estático para Meta Ads (4:5)
|
||||||
|
|
||||||
|
> Se aplica junto a la skill del cliente. Define la estructura de un anuncio estático de imagen para feed de Instagram y Facebook. Siempre 4:5 (1080×1350).
|
||||||
|
|
||||||
|
## Qué entregas por cada variante
|
||||||
|
|
||||||
|
Un objeto JSON con exactamente estos campos:
|
||||||
|
|
||||||
|
```json
|
||||||
|
{
|
||||||
|
"angle": "nombre corto del ángulo (p. ej. 'objeción tiempo', 'prueba social', 'coste FUNDAE', 'antes/después', 'autoridad')",
|
||||||
|
"hook": "titular que va EN la imagen, máx. 8 palabras",
|
||||||
|
"support": "línea de apoyo en la imagen, máx. 12 palabras, opcional (null si no hace falta)",
|
||||||
|
"headline": "título del anuncio en Meta, máx. 40 caracteres",
|
||||||
|
"primary_text": "texto principal del anuncio, 2-4 frases, máx. 300 caracteres, la primera frase es el gancho",
|
||||||
|
"cta": "uno de: 'Más información', 'Solicitar presupuesto', 'Reservar', 'Contactar', 'Registrarte'",
|
||||||
|
"image_prompt": "prompt en inglés para el generador de imagen, SIN texto ni logo, describiendo escena, personas, luz, encuadre y paleta",
|
||||||
|
"layout": "una de: 'foto-con-franja' (foto + franja de color con titular), 'tipografico' (solo color y texto), 'dato-grande' (una cifra enorme + contexto)",
|
||||||
|
"rationale": "una frase: por qué este ángulo puede relevar a la fatigada"
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Reglas de ángulo
|
||||||
|
|
||||||
|
- Cada variante ataca un **ángulo distinto** entre sí y distinto del de la creatividad fatigada (viene en `avoid_angles`).
|
||||||
|
- Prioriza ángulos que aún no se hayan probado en el cliente (mira `top_creatives` del contexto: lo que ya funciona no se repite, se complementa).
|
||||||
|
- Rotación recomendada: dolor → solución → prueba → objeción → oferta. Si la fatigada era "oferta", el relevo vuelve a "dolor" o "prueba".
|
||||||
|
|
||||||
|
## Reglas de hook (titular en imagen)
|
||||||
|
|
||||||
|
- Máximo 8 palabras. Si necesita más, no es un hook.
|
||||||
|
- Una sola idea. Un verbo o un dato.
|
||||||
|
- Sin signos de exclamación. Sin mayúsculas completas.
|
||||||
|
- Se lee en 1,5 segundos en móvil con el pulgar en movimiento.
|
||||||
|
- Prohibido empezar por "¿Sabías que", "Descubre", "Imagina".
|
||||||
|
|
||||||
|
## Reglas de copy (primary_text)
|
||||||
|
|
||||||
|
- Frase 1: repite o amplía el hook, nunca lo contradice.
|
||||||
|
- Frase 2-3: un beneficio concreto o un dato.
|
||||||
|
- Frase final: el CTA en palabras, coherente con el botón.
|
||||||
|
- Sin hashtags. Sin enlaces en el texto.
|
||||||
|
|
||||||
|
## Reglas de imagen (image_prompt)
|
||||||
|
|
||||||
|
- Siempre en inglés, 40-80 palabras.
|
||||||
|
- Describe: sujeto, acción, entorno, luz, encuadre (vertical 4:5, espacio libre arriba o abajo para texto), paleta en términos de la skill del cliente.
|
||||||
|
- Termina siempre con: `no text, no logos, no watermarks, photorealistic, vertical 4:5 composition with clear negative space for a headline`.
|
||||||
|
- Nunca pidas personas reconocibles, marcas de terceros, ni niños.
|
||||||
|
- Una generación por variante. No pidas variaciones.
|
||||||
|
|
||||||
|
## Reglas de layout
|
||||||
|
|
||||||
|
- Zona segura: 8 % de margen en todos los lados. Texto nunca en el 15 % superior ni inferior (lo tapan la interfaz y el CTA de Meta).
|
||||||
|
- Jerarquía: hook ≥ 2× el tamaño del support.
|
||||||
|
- Contraste mínimo 4,5:1 entre texto y fondo.
|
||||||
|
- El logo ocupa como máximo el 12 % del ancho.
|
||||||
|
|
||||||
|
## Autocheck antes de entregar
|
||||||
|
|
||||||
|
- [ ] ¿Las variantes tienen ángulos distintos entre sí y del `avoid_angles`?
|
||||||
|
- [ ] ¿Cada hook tiene ≤ 8 palabras?
|
||||||
|
- [ ] ¿El primary_text cabe en 300 caracteres?
|
||||||
|
- [ ] ¿El image_prompt no pide texto ni logo?
|
||||||
|
- [ ] ¿Se respeta la lista de prohibiciones de la skill del cliente?
|
||||||
@@ -108,12 +108,32 @@ export async function GET(
|
|||||||
topCreatives = fallbackRows || [];
|
topCreatives = fallbackRows || [];
|
||||||
}
|
}
|
||||||
|
|
||||||
return NextResponse.json({
|
// 5. Obtener datos del cliente (drive_folder_id, slug, nombre)
|
||||||
client_id: tenantId,
|
const { data: tenant } = await adminClient
|
||||||
tenant_id: tenantId,
|
.from('tenants')
|
||||||
|
.select('id, name, slug, drive_folder_id, meta_ad_account_id')
|
||||||
|
.eq('id', tenantId)
|
||||||
|
.maybeSingle();
|
||||||
|
|
||||||
|
const contextData = {
|
||||||
|
source_creative: creative,
|
||||||
creative,
|
creative,
|
||||||
metrics,
|
metrics,
|
||||||
rules: rules || null,
|
rules: rules || null,
|
||||||
|
top_creatives: topCreatives,
|
||||||
top_historical_creatives: topCreatives,
|
top_historical_creatives: topCreatives,
|
||||||
|
client: {
|
||||||
|
id: tenantId,
|
||||||
|
name: tenant?.name || null,
|
||||||
|
slug: tenant?.slug || null,
|
||||||
|
drive_folder_id: tenant?.drive_folder_id || null,
|
||||||
|
},
|
||||||
|
};
|
||||||
|
|
||||||
|
return NextResponse.json({
|
||||||
|
data: contextData,
|
||||||
|
client_id: tenantId,
|
||||||
|
tenant_id: tenantId,
|
||||||
|
...contextData,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -66,6 +66,7 @@ export async function POST(req: NextRequest) {
|
|||||||
created_by: 'claude', // creadas por Claude
|
created_by: 'claude', // creadas por Claude
|
||||||
parent_id: c.parent_id || defaultParentId,
|
parent_id: c.parent_id || defaultParentId,
|
||||||
generation_request_id: c.generation_request_id || genRequestId || null,
|
generation_request_id: c.generation_request_id || genRequestId || null,
|
||||||
|
meta: c.meta || {},
|
||||||
}));
|
}));
|
||||||
|
|
||||||
const { data: inserted, error } = await adminClient
|
const { data: inserted, error } = await adminClient
|
||||||
|
|||||||
73
src/app/api/integrations/skills/route.ts
Normal file
73
src/app/api/integrations/skills/route.ts
Normal file
@@ -0,0 +1,73 @@
|
|||||||
|
import { NextRequest, NextResponse } from 'next/server';
|
||||||
|
import { authenticateIntegrationRequest } from '@/lib/integrations-auth';
|
||||||
|
import { createClient } from '@supabase/supabase-js';
|
||||||
|
import fs from 'fs';
|
||||||
|
import path from 'path';
|
||||||
|
|
||||||
|
export async function GET(req: NextRequest) {
|
||||||
|
const auth = await authenticateIntegrationRequest(req, { requireTenant: true });
|
||||||
|
if (!auth.ok) {
|
||||||
|
return auth.errorResponse!;
|
||||||
|
}
|
||||||
|
|
||||||
|
const tenantId = auth.tenantId!;
|
||||||
|
const supabaseUrl = process.env.NEXT_PUBLIC_SUPABASE_URL!;
|
||||||
|
const serviceRoleKey = process.env.SUPABASE_SERVICE_ROLE_KEY!;
|
||||||
|
const adminClient = createClient(supabaseUrl, serviceRoleKey);
|
||||||
|
|
||||||
|
// 1. Obtener slug del cliente
|
||||||
|
const { data: tenant, error: tErr } = await adminClient
|
||||||
|
.from('tenants')
|
||||||
|
.select('id, name, slug')
|
||||||
|
.eq('id', tenantId)
|
||||||
|
.maybeSingle();
|
||||||
|
|
||||||
|
if (tErr || !tenant) {
|
||||||
|
return NextResponse.json({ error: 'Tenant not found' }, { status: 404 });
|
||||||
|
}
|
||||||
|
|
||||||
|
const slug = (tenant.slug || 'bps').toLowerCase().trim();
|
||||||
|
const formatParam = (req.nextUrl.searchParams.get('format') || 'estatico').toLowerCase().trim();
|
||||||
|
|
||||||
|
// 2. Resolver skill del cliente (skills/clients/<slug>.md)
|
||||||
|
const skillsDir = path.join(process.cwd(), 'skills');
|
||||||
|
const clientCandidates = [
|
||||||
|
path.join(skillsDir, 'clients', `${slug}.md`),
|
||||||
|
path.join(skillsDir, 'clients', slug === 'bps' ? 'blue-parrot-school.md' : `${slug}.md`),
|
||||||
|
path.join(skillsDir, 'clients', 'blue-parrot-school.md'),
|
||||||
|
];
|
||||||
|
|
||||||
|
let clientSkill = '';
|
||||||
|
for (const p of clientCandidates) {
|
||||||
|
if (fs.existsSync(p)) {
|
||||||
|
clientSkill = fs.readFileSync(p, 'utf-8');
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// 3. Resolver skill del formato (skills/formats/<format>-4-5.md o skills/formats/<format>.md, fallback a estatico)
|
||||||
|
const formatCandidates = [
|
||||||
|
path.join(skillsDir, 'formats', `${formatParam}-4-5.md`),
|
||||||
|
path.join(skillsDir, 'formats', `${formatParam}.md`),
|
||||||
|
path.join(skillsDir, 'formats', 'estatico-4-5.md'),
|
||||||
|
path.join(skillsDir, 'formats', 'estatico.md'),
|
||||||
|
];
|
||||||
|
|
||||||
|
let formatSkill = '';
|
||||||
|
for (const p of formatCandidates) {
|
||||||
|
if (fs.existsSync(p)) {
|
||||||
|
formatSkill = fs.readFileSync(p, 'utf-8');
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return NextResponse.json({
|
||||||
|
data: {
|
||||||
|
client_skill: clientSkill,
|
||||||
|
format_skill: formatSkill,
|
||||||
|
},
|
||||||
|
// Compatibilidad en primer nivel
|
||||||
|
client_skill: clientSkill,
|
||||||
|
format_skill: formatSkill,
|
||||||
|
});
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user