> ## Documentation Index
> Fetch the complete documentation index at: https://wiki.vivla.com/llms.txt
> Use this file to discover all available pages before exploring further.

# Notificaciones Slack

> Formato estándar para notificaciones de deploy y procesos automatizados en el canal #deploys

## Canal

Todas las notificaciones van al canal **#deploys** (`C091W026C23`). Este canal centraliza el estado de deploys, builds, y procesos automatizados de todos los repositorios.

## Formato estándar

Usamos [Block Kit](https://api.slack.com/block-kit) dentro de `attachments` para lograr:

* **Color lateral** verde/rojo según resultado
* **Título** con emoji + nombre del proyecto + resultado
* **Campos estructurados** con info relevante (repo, branch, commit, etc.)
* **Link al workflow** en el footer
* **Fallback text** para que el preview en móvil/desktop se vea bien

<Note>
  El campo `text` dentro del attachment es **obligatorio** para que el preview de la notificación (push móvil, desktop) muestre contenido útil en vez de "sent an attachment".
</Note>

## GitHub Actions: Dos enfoques

En GitHub Actions hay dos formas de enviar notificaciones a Slack. Cada una tiene restricciones diferentes.

### Opción A: Incoming Webhook (actual)

Usa un Incoming Webhook URL. Es la opción más simple de configurar.

**Secret necesario:** `SLACK_WEBHOOK_URL`

<Warning>
  Los Incoming Webhooks de Slack **NO soportan `header` blocks dentro de `attachments`**. Usar `type: "header"` dentro de un attachment causa el error `invalid_attachments`. En su lugar, usar un `section` con `mrkdwn` en bold para el título.
</Warning>

```yaml theme={null}
- name: Send Slack notification
  if: always()
  env:
    SLACK_WEBHOOK_URL: $\{{ secrets.SLACK_WEBHOOK_URL }}
  run: |
    if [ -z "$SLACK_WEBHOOK_URL" ]; then
      echo "Slack webhook not configured, skipping notification"
      exit 0
    fi

    # -- Determinar resultado --
    if [ "$JOB_FAILED" = "false" ]; then
      COLOR="#36a64f"
      STATUS_EMOJI="✅"
      RESULT="succeeded"
    else
      COLOR="#e01e5a"
      STATUS_EMOJI="❌"
      RESULT="failed"
    fi

    COMMIT_SHORT=$(echo "$\{{ github.sha }}" | cut -c1-7)
    PROJECT_NAME="Mi Proyecto"  # Cambiar por el nombre del proyecto
    HEADER="$STATUS_EMOJI $PROJECT_NAME deploy $RESULT"

    PAYLOAD=$(jq -n \
      --arg color "$COLOR" \
      --arg header "$HEADER" \
      --arg fallback "$HEADER ($\{{ github.ref_name }} @ $COMMIT_SHORT)" \
      --arg repo "$\{{ github.repository }}" \
      --arg branch "$\{{ github.ref_name }}" \
      --arg commit "$COMMIT_SHORT" \
      --arg author "$\{{ github.actor }}" \
      --arg url "$\{{ github.server_url }}/$\{{ github.repository }}/actions/runs/$\{{ github.run_id }}" \
      '{attachments:[{color:$color,text:$fallback,blocks:[
        {type:"section",text:{type:"mrkdwn",text:("*" + $header + "*")}},
        {type:"section",fields:[
          {type:"mrkdwn",text:("*Repo:*\n"+$repo)},
          {type:"mrkdwn",text:("*Branch:*\n`"+$branch+"`")},
          {type:"mrkdwn",text:("*Commit:*\n`"+$commit+"`")},
          {type:"mrkdwn",text:("*Author:*\n"+$author)}
        ]},
        {type:"context",elements:[
          {type:"mrkdwn",text:("<"+$url+"|View workflow run>")}
        ]}
      ]}]}')

    curl -sf -X POST "$SLACK_WEBHOOK_URL" \
      -H "Content-Type: application/json" \
      -d "$PAYLOAD"
```

### Opción B: Slack Web API (recomendada)

Usa la Slack Web API (`chat.postMessage`) directamente con un Bot Token. Soporta **todo Block Kit** incluyendo `header` blocks dentro de attachments, igual que Windmill.

**Secret necesario:** `SLACK_BOT_TOKEN` (formato `xoxb-...`)

```yaml theme={null}
- name: Send Slack notification
  if: always()
  env:
    SLACK_BOT_TOKEN: $\{{ secrets.SLACK_BOT_TOKEN }}
  run: |
    if [ -z "$SLACK_BOT_TOKEN" ]; then
      echo "Slack bot token not configured, skipping notification"
      exit 0
    fi

    # -- Determinar resultado --
    if [ "$JOB_FAILED" = "false" ]; then
      COLOR="#36a64f"
      STATUS_EMOJI="✅"
      RESULT="succeeded"
    else
      COLOR="#e01e5a"
      STATUS_EMOJI="❌"
      RESULT="failed"
    fi

    COMMIT_SHORT=$(echo "$\{{ github.sha }}" | cut -c1-7)
    PROJECT_NAME="Mi Proyecto"  # Cambiar por el nombre del proyecto
    HEADER="$STATUS_EMOJI $PROJECT_NAME deploy $RESULT"
    CHANNEL="C091W026C23"  # #deploys

    PAYLOAD=$(jq -n \
      --arg channel "$CHANNEL" \
      --arg color "$COLOR" \
      --arg header "$HEADER" \
      --arg fallback "$HEADER ($\{{ github.ref_name }} @ $COMMIT_SHORT)" \
      --arg repo "$\{{ github.repository }}" \
      --arg branch "$\{{ github.ref_name }}" \
      --arg commit "$COMMIT_SHORT" \
      --arg author "$\{{ github.actor }}" \
      --arg url "$\{{ github.server_url }}/$\{{ github.repository }}/actions/runs/$\{{ github.run_id }}" \
      '{channel:$channel,text:$fallback,attachments:[{color:$color,text:$fallback,blocks:[
        {type:"header",text:{type:"plain_text",text:$header,emoji:true}},
        {type:"section",fields:[
          {type:"mrkdwn",text:("*Repo:*\n"+$repo)},
          {type:"mrkdwn",text:("*Branch:*\n`"+$branch+"`")},
          {type:"mrkdwn",text:("*Commit:*\n`"+$commit+"`")},
          {type:"mrkdwn",text:("*Author:*\n"+$author)}
        ]},
        {type:"context",elements:[
          {type:"mrkdwn",text:("<"+$url+"|View workflow run>")}
        ]}
      ]}]}')

    curl -sf -X POST "https://slack.com/api/chat.postMessage" \
      -H "Content-Type: application/json" \
      -H "Authorization: Bearer $SLACK_BOT_TOKEN" \
      -d "$PAYLOAD"
```

### Comparación

|                               | Webhook (Opción A)             | Web API (Opción B)        |
| ----------------------------- | ------------------------------ | ------------------------- |
| **Setup**                     | Crear webhook URL en Slack app | Bot token de la Slack app |
| **Secret**                    | `SLACK_WEBHOOK_URL`            | `SLACK_BOT_TOKEN`         |
| **Header blocks**             | ❌ No soportado en attachments  | ✅ Soportado               |
| **Canal**                     | Fijo al crear el webhook       | Configurable por request  |
| **Consistencia con Windmill** | Diferente formato              | ✅ Mismo formato           |

<Tip>
  Si ya tienes una Slack app con bot token (como la que usa Windmill), la **Opción B** es preferible porque usa el mismo formato en todos los sistemas y soporta Block Kit completo.
</Tip>

## Windmill (TypeScript con Slack Web API)

```typescript theme={null}
import { WebClient } from "@slack/web-api";

const CHANNEL_ID = "C091W026C23";

interface SlackNotifyParams {
  success: boolean;
  projectName: string;
  details: Record<string, string>; // key-value pairs para los fields
  linkUrl?: string;
  linkLabel?: string;
}

export async function sendSlackNotification(
  slackToken: string,
  params: SlackNotifyParams
) {
  const web = new WebClient(slackToken);

  const color = params.success ? "#36a64f" : "#e01e5a";
  const emoji = params.success ? "✅" : "❌";
  const result = params.success ? "succeeded" : "failed";
  const headerText = `${emoji} ${params.projectName} ${result}`;

  const fields = Object.entries(params.details).map(([key, value]) => ({
    type: "mrkdwn" as const,
    text: `*${key}:*\n${value}`,
  }));

  const contextElements = [
    {
      type: "mrkdwn" as const,
      text: `Executed at: ${new Date().toISOString()}`,
    },
  ];

  if (params.linkUrl) {
    contextElements.push({
      type: "mrkdwn" as const,
      text: `<${params.linkUrl}|${params.linkLabel || "View details"}>`,
    });
  }

  await web.chat.postMessage({
    channel: CHANNEL_ID,
    text: headerText, // Fallback para preview
    attachments: [
      {
        color,
        text: headerText,
        blocks: [
          {
            type: "section",
            text: { type: "mrkdwn", text: `*${headerText}*` },
          },
          ...(fields.length > 0
            ? [{ type: "section", fields }]
            : []),
          {
            type: "context",
            elements: contextElements,
          },
        ],
      },
    ],
  });
}
```

**Ejemplo de uso en Windmill:**

```typescript theme={null}
await sendSlackNotification(slack_auth.token, {
  success: true,
  projectName: "Windmill Sync",
  details: {
    "Process": "Daily sync",
    "Records": "1,234 synced",
  },
});
```

## Campos por proyecto

Cada proyecto incluye los campos más relevantes en la sección `fields`. Máximo 6 campos (límite de Slack para el layout de 2 columnas).

| Proyecto         | Campos                                                         |
| ---------------- | -------------------------------------------------------------- |
| `vivla-tools`    | Repo, Branch, Commit, Author, Railway Backend, Vercel Frontend |
| `vivla-mobile`   | Repo, Branch, Commit, Author + campos de build por plataforma  |
| Windmill scripts | Process, Result/Records + campos específicos del proceso       |

<Tip>
  Para builds con múltiples jobs (como mobile con iOS/Android), agrega los estados como campos adicionales usando emojis de estado: ✅ success, ❌ failed, ⏭️ skipped, 🚀 deployed.
</Tip>

## Reglas

1. **Siempre usar `attachments`** con `color` y `blocks` — nunca solo `text` o solo `blocks` a nivel root.
2. **Siempre incluir `text`** dentro del attachment como fallback para el preview.
3. **Título**: emoji de estado + nombre del proyecto + resultado (`succeeded`/`failed`). Con Webhook usar `section` + `mrkdwn` bold. Con Web API se puede usar `header` block.
4. **Section fields**: datos estructurados en pares clave-valor.
5. **Context**: timestamp y/o link al workflow/proceso.
6. **Colores**: `#36a64f` (verde) para éxito, `#e01e5a` (rojo) para fallo.
