---
term: 'Triggers de Base de Datos (beforeSave y afterSave)'
seoTitle: 'Triggers de Base de Datos: Hooks beforeSave y afterSave'
headline: '¿Qué son los Triggers de Base de Datos (beforeSave y afterSave)?'
slug: triggers-de-base-de-datos
category: backend-compute
shortDefinition: 'Un trigger de base de datos es un hook de código que se ejecuta solo en eventos de datos: antes del save para validar, después del save para reaccionar.'
relatedTerms:
  - cloud-code-serverless-functions
  - webhooks
  - crud-operations
  - real-time-live-queries
contrastsWith:
  - webhooks
aboutTerms:
  - 'beforeSave'
  - 'afterSave'
  - 'BEFORE/AFTER Triggers'
faq:
  - question: '¿Qué es un trigger de base de datos?'
    answer: 'Código procedural que se ejecuta automáticamente cuando un evento de datos — insert, update, delete — ocurre en una tabla o clase específica. La forma clásica vive dentro de la base de datos SQL; la forma moderna, a nivel de aplicación, es una función hook como beforeSave o afterSave que el backend ejecuta alrededor de cada escritura.'
  - question: '¿Cuál es la diferencia entre triggers BEFORE y AFTER?'
    answer: 'Capacidad y timing. BEFORE corre antes de la escritura y puede validar, modificar los datos entrantes o abortar la operación por completo. AFTER corre una vez que la escritura tuvo éxito — no puede cambiar lo que pasó, solo reaccionar: registrarlo, contarlo, notificarlo. AFTER nunca se dispara para escrituras que fallaron.'
  - question: '¿Cuál es la diferencia entre un trigger y un stored procedure?'
    answer: 'La invocación. Un stored procedure se llama explícitamente, recibe parámetros y devuelve resultados. Un trigger nunca se llama — se dispara automáticamente cuando ocurre su evento, sin parámetros, atado a una tabla. La misma maquinaria procedural, con el modelo de activación opuesto.'
  - question: '¿La lógica debe vivir en triggers o en el código de la aplicación?'
    answer: 'El consenso es un híbrido: triggers (o hooks) para reglas que deben cumplirse en todo camino de escritura — validación, integridad, auditoría — y servicios de la aplicación para workflows complejos. Los hooks a nivel de aplicación son el punto medio moderno: garantías de trigger, un lenguaje de programación real y control de versiones.'
  - question: '¿Los triggers perjudican el rendimiento?'
    answer: 'Corren de forma síncrona dentro del camino de escritura, así que un trigger lento vuelve lento cada save, y los triggers a nivel de fila se multiplican en operaciones masivas — un update de cien mil filas se dispara cien mil veces. Mantén los before-hooks ligeros y empuja las reacciones lentas a after-hooks o jobs en segundo plano.'
  - question: '¿Un trigger puede causar un loop infinito?'
    answer: 'Famosamente — un trigger que escribe en su propia tabla se vuelve a disparar a sí mismo, y un afterSave que guarda el objeto que acaba de procesar entra en recursión hasta que algo se rompe. Protégete comprobando qué cambió de verdad antes de escribir, y nunca vuelvas a guardar el objeto disparador desde su propio after-hook sin una condición de parada.'
  - question: '¿Pueden los triggers llamar a servicios externos?'
    answer: 'Los triggers SQL esencialmente no pueden — ni deberían — salir de la base de datos. Esa es la ventaja estelar de los hooks a nivel de aplicación: un afterSave en JavaScript puede enviar una notificación push, llamar a cualquier API o disparar un webhook saliente, porque corre en el proceso del backend con todo el ecosistema disponible.'
  - question: '¿Para qué se usa beforeSave?'
    answer: 'Para los tres trabajos que exigen correr antes de la escritura: validación (lanza un error y el save falla, para cualquier cliente), normalización (trim, truncado, canonicalización de campos) y defaults o valores calculados. Los efectos secundarios no van ahí — si el save falla después, el efecto ya ocurrió.'
codeLanguages: [javascript, dart, swift, kotlin]
externalAuthorities:
  - name: 'PostgreSQL — CREATE TRIGGER'
    url: 'https://www.postgresql.org/docs/current/sql-createtrigger.html'
  - name: 'MySQL — Trigger Syntax and Examples'
    url: 'https://dev.mysql.com/doc/refman/8.4/en/trigger-syntax.html'
  - name: 'Cloud Code triggers guide'
    url: 'https://docs.parseplatform.org/cloudcode/guide/#beforesave-triggers'
  - name: 'Sequelize — Hooks lifecycle'
    url: 'https://sequelize.org/docs/v6/other-topics/hooks/'
  - name: 'Database trigger — Wikipedia'
    url: 'https://en.wikipedia.org/wiki/Database_trigger'
cta:
  title: 'Reglas que toda escritura obedece'
  text: 'Registra beforeSave y afterSave en cualquier clase de Back4app y la regla aplica a todos los clientes — SDKs, REST, GraphQL, scripts de admin — en JavaScript que puede validar, normalizar, contar, notificar y llamar al mundo exterior.'
  linkText: 'Empieza gratis'
  linkUrl: 'https://www.back4app.com/signup'
author: 'Back4app Engineering'
publishedDate: '2026-09-03'
translationKey: database-triggers-beforesave-aftersave
---

**Un trigger de base de datos es un hook de código que se ejecuta solo en eventos de datos: antes del save para validar, después del save para reaccionar.** La idea tiene dos linajes que comparten un principio: el **trigger SQL** clásico, código procedural que vive dentro del motor de la base de datos, y el **hook a nivel de aplicación** moderno — `beforeSave`, `afterSave`, `beforeDelete` — JavaScript registrado por clase en el backend. Ambos codifican la misma promesa: la regla se dispara para *toda* escritura, sin importar qué cliente, SDK o script la ejecutó — que es exactamente lo que la validación del lado del cliente nunca puede prometer.

## Puntos clave

| Pregunta | Respuesta |
| --- | --- |
| El principio | Código atado a eventos de datos — automático, por tabla/clase, todo camino de escritura |
| Before = | Validar · normalizar · aplicar defaults — todavía puede cambiar o **abortar** la escritura |
| After = | Reaccionar — contar, notificar, sincronizar; la escritura ya ocurrió, así que sé idempotente |
| Las dos formas | Triggers SQL dentro del motor · hooks JS en el proceso del backend |
| Los bugs clásicos | Lógica oculta · loops infinitos de autodisparo · triggers lentos frenando cada save |

## El modelo de hooks en acción

Una regla de validación y su aplicación, vistas desde ambos extremos — el servidor la define una vez, y todos los clientes en todas partes la heredan:

**JavaScript:**

```javascript
// JavaScript — Cloud Code (cloud/main.js)
// beforeSave: validate + normalize — can still change or ABORT the write
Parse.Cloud.beforeSave('Review', (req) => {
  const stars = req.object.get('stars');
  if (stars < 1 || stars > 5) throw 'Stars must be between 1 and 5';
  const comment = req.object.get('comment');
  if (comment && comment.length > 500) {
    req.object.set('comment', comment.slice(0, 497) + '…');
  }
});

// afterSave: side effects — the write already happened; be idempotent
Parse.Cloud.afterSave('Review', async (req) => {
  if (req.object.existed()) return; // count only NEW reviews, once
  await updateAverageStars(req.object.get('movie'));
});
```

**Flutter:**

```dart
// Flutter / Dart — Back4app Flutter SDK
// The hook fires for EVERY write path — this client included
final review = ParseObject('Review')
  ..set('movie', 'Arrival')
  ..set('stars', 9); // invalid — no client-side check needed
final response = await review.save();
print(response.error?.message); // "Stars must be between 1 and 5"
// The beforeSave hook rejected it server-side. A forged REST call,
// another SDK, an admin script — same rule, same rejection.
```

**Swift:**

```swift
// iOS / Swift — Back4app Swift SDK
// The hook fires for EVERY write path — this client included
var review = Review()
review.movie = "Arrival"
review.stars = 9 // invalid — no client-side check needed
do {
    _ = try await review.save()
} catch {
    print(error.localizedDescription) // "Stars must be between 1 and 5"
}
// The beforeSave hook rejected it server-side. A forged REST call,
// another SDK, an admin script — same rule, same rejection.
```

**Kotlin:**

```kotlin
// Android / Kotlin — Back4app Android SDK
// The hook fires for EVERY write path — this client included
val review = ParseObject("Review")
review.put("movie", "Arrival")
review.put("stars", 9) // invalid — no client-side check needed
try {
    review.save()
} catch (e: ParseException) {
    println(e.message) // "Stars must be between 1 and 5"
}
// The beforeSave hook rejected it server-side. A forged REST call,
// another SDK, an admin script — same rule, same rejection.
```

La familia completa de hooks extiende la misma forma a todo el ciclo de vida de los datos: `beforeSave`/`afterSave`, `beforeDelete` (bloquear la eliminación de un Album que todavía tiene Photos) y `afterDelete` (limpiar sus hijos), más `beforeFind`/`afterFind` para reescribir consultas y quitar campos de los resultados — el [CRUD](/glossary/es/operaciones-crud/), envuelto.

## La forma clásica: triggers SQL

```sql
CREATE TRIGGER audit_price_change
AFTER UPDATE ON products
FOR EACH ROW                              -- nivel de fila: se dispara por fila afectada
WHEN (OLD.price IS DISTINCT FROM NEW.price)
EXECUTE FUNCTION log_price_change();      -- OLD y NEW guardan ambas versiones
```

La taxonomía que toda base de datos comparte, según las referencias de [PostgreSQL](https://www.postgresql.org/docs/current/sql-createtrigger.html) y [MySQL](https://dev.mysql.com/doc/refman/8.4/en/trigger-syntax.html): **timing** — BEFORE (puede modificar `NEW` o abortar), AFTER (reacciona a la fila confirmada), INSTEAD OF (reemplaza la operación, principalmente en vistas); **evento** — insert, update, delete; **granularidad** — nivel de fila versus nivel de sentencia (un update de 10 filas dispara un trigger de fila 10 veces, uno de sentencia una vez). Dos semánticas que vale la pena grabarse: los triggers SQL corren *dentro de la misma transacción* que la escritura — una falla en el trigger revierte la operación entera — y los triggers AFTER solo se disparan para escrituras que de verdad tuvieron éxito.

## Before vs. after: elegidos por el trabajo

| | Before hooks | After hooks |
| --- | --- | --- |
| Puede cambiar los datos | **Sí** — mutar, aplicar defaults, truncar | No — ya está escrito |
| Puede abortar la escritura | **Sí** — lanza un error y el save falla | No |
| Correcto para | Validación, normalización, campos calculados | Contadores, notificaciones, sincronización, auditoría |
| Incorrecto para | **Efectos secundarios** — si el save falla después, el efecto ya se disparó | Cualquier cosa que debió bloquear la escritura |
| Comportamiento ante fallas | Rechaza la operación, error al cliente | Muchas veces fire-and-forget — los errores caen en los logs |
| Disciplina | Rápido — bloquea cada save | **Idempotente** — puede correr de nuevo |

Los corolarios que ningún explicador enuncia: un efecto secundario en un before-hook es un bug por construcción (el correo sale, y luego el save falla), y un after-hook que no es idempotente es un contador duplicado esperando un reintento. En sistemas de hooks como el de Back4app, `afterSave` se completa después de que el cliente ya recibió su respuesta — las reacciones son asíncronas por diseño, así que sus fallas deben ser tolerables y quedar en los logs.

```mermaid
flowchart LR
  accTitle: Ciclo de vida de la escritura a través de los hooks before y after
  accDescr: Una escritura desde cualquier cliente pasa primero por el hook before, que puede validarla, modificarla o abortarla. Si se permite, la base de datos confirma la escritura, y el hook after reacciona entonces con efectos secundarios como contadores, notificaciones y webhooks, que deben ser idempotentes porque pueden ejecutarse más de una vez.
  C["Cualquier cliente<br/>SDK · REST · script"] --> B{"beforeSave<br/>valida · normaliza"}
  B -->|"throw"| X["Save rechazado —<br/>error al cliente"]
  B -->|"permite (quizá modificado)"| W[("Escritura confirmada")]
  W --> A["afterSave<br/>efectos secundarios, idempotentes"]
  A --> R["Contadores · notificaciones ·<br/>webhooks salientes"]
```

## Hooks vs. triggers SQL

| | Hooks de aplicación (beforeSave/afterSave) | Triggers SQL |
| --- | --- | --- |
| Lenguaje | JavaScript + el SDK y el ecosistema completos | SQL / dialectos procedurales de SQL |
| Llamadas externas | Sí — APIs, push, [webhooks](/glossary/es/webhooks/) | Esencialmente no — y no deberían |
| Vive en | Tu código: versionado, testeable, desplegado | El esquema: dentro de la base de datos |
| Transacción | Los before-hooks son la puerta de la escritura; los after-hooks corren tras la respuesta | La misma transacción — la falla revierte todo |
| Cubre | Toda solicitud que pasa por la API del backend | Toda escritura a la tabla, venga de donde venga |
| Punto ciego | Las escrituras directas a la base lo esquivan | Lógica invisible para los debuggers de la aplicación |

La última fila es la simetría honesta: la garantía de cada forma está limitada a su capa. Un trigger SQL atrapa hasta una sesión psql renegada, pero esconde lógica del instrumental de la aplicación; un hook a nivel de API cubre todo camino de cliente a través del backend, pero no el acceso crudo a la base — y por eso las plataformas dueñas del gateway de API (todo el tráfico fluye por él) obtienen, en la práctica, lo mejor de ambos. La nota al pie sobre ORMs entra aquí también: los [hooks de ORM](https://sequelize.org/docs/v6/other-topics/hooks/) solo se disparan a través del ORM — las operaciones masivas y el SQL crudo pasan de largo.

## Las trampas, con honestidad

**La lógica oculta** es el clásico: un desarrollador depura su propio código por horas mientras un trigger reescribe valores en silencio — los triggers son invisibles en el punto de llamada, así que documéntalos y mantenlos pocos. **Loops infinitos**: un trigger que escribe en su propia tabla se vuelve a disparar (y un `afterSave` que guarda su propio objeto también); protégete comprobando qué cambió, y dale a la recursión una condición de parada antes de que ella encuentre una por ti. **Costo síncrono**: los before-hooks viven dentro de la latencia de cada escritura — un hook de 200 ms vuelve cada save 200 ms más lento, y las escrituras masivas multiplican los disparos por fila por el número de filas. **Cadenas en cascada**: triggers que disparan triggers que disparan triggers convierten un insert en un proyecto de arqueología. La regla paraguas de décadas de práctica: los triggers aplican *reglas*; en el momento en que uno empieza a orquestar un *workflow*, mueve el workflow a [funciones](/glossary/es/cloud-code-funciones-serverless/) o [jobs](/glossary/es/jobs-en-segundo-plano/) y deja que el trigger solo encole.

## Casos de uso comunes

- **Validación que todo cliente obedece** — la regla de estrellas-entre-1-y-5, aplicada por igual contra solicitudes falsificadas y SDKs futuros.
- **Trazas de auditoría** — quién cambió qué y cuándo, escritas por el propio evento en lugar de por clientes cooperativos.
- **Contadores desnormalizados y campos calculados** — promedios, conteos y duplicados amigables con la búsqueda mantenidos al día en el momento de escribir.
- **Integridad en cascada** — rechazar eliminaciones con hijos, o limpiar a los hijos después.
- **Efectos secundarios reactivos** — un `afterSave` que dispara una [notificación push](/glossary/es/notificaciones-push/) o un [webhook saliente](/glossary/es/webhooks/): el evento de base de datos convertido en evento de integración.

## ¿Qué hook y dónde? Matriz de decisión

| El trabajo | El hook |
| --- | --- |
| Rechazar datos malos | beforeSave — lanza un error |
| Trim, defaults, canonicalizar | beforeSave — muta el objeto |
| Actualizar un contador o agregado | afterSave — con idempotencia |
| Notificar a una persona o sistema | afterSave → push / webhook / cola |
| Bloquear eliminaciones peligrosas | beforeDelete |
| Limpiar después de eliminar | afterDelete |
| Aplicar alcance de consulta por usuario | beforeFind |
| Reacción larga o lenta | afterSave *encola* un [job](/glossary/es/jobs-en-segundo-plano/) — nunca hace el trabajo inline |

## Limitaciones y trade-offs

- **La invisibilidad es el precio de lo automático.** Lógica que nadie llama es lógica que nadie recuerda; nombres, docs y code review mantienen la magia auditable.
- **Los hooks tienen el alcance de su capa.** Los hooks de API no ven escrituras directas a la base; los triggers SQL no pierden nada, pero se esconden de tu instrumental — sabe qué punto ciego elegiste.
- **La latencia de escritura es el presupuesto.** Todo before-hook gasta de él; mide los hooks como mides las consultas.
- **Los after-hooks son eventualmente consistentes.** Los contadores se atrasan milisegundos y pueden dispararse doble — diseña las lecturas (y los reintentos) en consecuencia.
- **Los triggers no reemplazan constraints.** Índices únicos, claves foráneas y [ACLs](/glossary/es/listas-de-control-de-acceso-acl/) se aplican más barato y más temprano; los triggers empiezan donde las reglas declarativas terminan.

## Triggers de Base de Datos en Back4app

Back4app es una plataforma open-source de Backend as a Service (BaaS) que combina base de datos gestionada, APIs REST y GraphQL generadas automáticamente, autenticación, almacenamiento de archivos y funciones serverless con Cloud Code. Los triggers aquí son la familia de eventos de datos de [Cloud Code](/glossary/es/cloud-code-funciones-serverless/): registra `Parse.Cloud.beforeSave('Review', …)` una vez — las pestañas de código muestran el patrón completo — y la regla aplica a toda escritura que llega por REST, GraphQL, cualquier SDK o el dashboard, porque el gateway de API es el único camino hacia los datos. Los hooks reciben contexto rico (`request.object`, `request.original`, `request.user`, estado de master key), así que la validación puede diferir para admins, `beforeFind` puede acotar consultas por usuario encima de las ACLs, y los after-hooks tienen todo el ecosistema JavaScript para las reacciones que los triggers SQL no alcanzan — notificaciones push, webhooks salientes, encolado de jobs. Desplegado con tu código, versionado en git, testeable como cualquier función: la garantía del trigger, sin la arqueología del trigger.
