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

# Notas

> Cuando esto se entregue, lo que un agente aprendió en una llamada sobrevive a la llamada.

> Traducción. Autoritativo: [`fs-crm-0001-notes.md`](/modules/crm/features/fs-crm-0001-notes).

## Contexto

La feature más pequeña del módulo y la que más se usa. Una nota es donde va el conocimiento que no
cabe en ningún campo: "prefiere que lo llamen después de las 6", "reclamó por la entrega de marzo",
"es dueño de la franquicia de Avenida Providencia".

Es también el dato personal menos predecible de la plataforma, porque es texto libre escrito por un
humano sobre una persona identificada. De ahí dos reglas: las notas se **auditan como cualquier cambio
de estado**, y se **borran al suprimir sin remanente anonimizado** — a diferencia de una fila del libro
de puntos, una nota no tiene valor contable que sobreviva a la persona que describe.

## Alcance *(normativo)*

* `crm.notes` sobre un contacto, con autor tipado.
* Texto enriquecido, acotado en largo.
* Fijar una nota al inicio de un contacto.
* Edición con historial de ediciones conservado.
* Búsqueda de texto completo sobre las notas del tenant.

## Fuera de alcance *(normativo)*

* Adjuntos — F1b. Los archivos traen escaneo de virus, cuotas y su propio camino de supresión.
* Menciones y notificaciones internas — F1b.
* Notas sobre algo que no sea un contacto.
* Plantillas o respuestas predefinidas. Eso es una feature de ticketing.

## Comportamiento *(normativo)*

1. Una nota pertenece a exactamente un contacto y registra un **autor tipado** — usuario, api\_key o
   sistema. Una nota sin autor es un defecto: el valor está en saber quién lo observó.
2. El contenido es texto enriquecido, sanitizado al escribir. El HTML guardado se escapa al renderizar;
   una nota no es un vector de inyección al navegador de un agente.
3. El largo está acotado a un máximo documentado. Una nota no es un repositorio de documentos.
4. Editar conserva las **versiones previas**, visibles para quien pueda leer la nota. Una nota
   reescrita en silencio tras un incidente es la falla que esto previene.
5. El borrado es **lógico** y auditado. La supresión del contacto es un **borrado duro** de la nota y
   su historial (DEC-J3).
6. Fijar es por contacto con un número acotado, para que fijar siga significando algo.
7. La búsqueda está acotada al tenant y a los permisos de quien lee.
8. Toda mutación escribe en `core.audit_log`.

## Datos *(normativo)*

| Tabla       | Invariantes clave                                                                                                                                                                                           |
| ----------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `crm.notes` | `tenant_id`, FK `contact_id`; `author_type`, `author_id`; `content` sanitizado y acotado; `is_pinned`; `edit_history` JSONB append-only; `deleted_at` nullable; índice de texto completo acotado por tenant |

## API *(normativo)*

| Endpoint                           | Clase      | Permiso            | Presupuesto |
| ---------------------------------- | ---------- | ------------------ | ----------- |
| `GET /v1/crm/contacts/{id}/notes`  | Management | `crm.notes.read`   | p95 \<1 s   |
| `POST /v1/crm/contacts/{id}/notes` | Management | `crm.notes.create` | p95 \<1 s   |
| `PATCH /v1/crm/notes/{id}`         | Management | `crm.notes.update` | p95 \<1 s   |
| `DELETE /v1/crm/notes/{id}`        | Management | `crm.notes.delete` | p95 \<1 s   |
| `GET /v1/crm/notes/search`         | Management | `crm.notes.read`   | p95 \<1 s   |

## Eventos *(normativo)*

`crm.note.created`. No se ofrece como webhook saliente: el contenido de una nota es el dato personal
menos predecible que tenemos, y empujarlo a un endpoint arbitrario por defecto no es una decisión que
tomemos por el tenant.

## Criterios de aceptación *(normativo)*

1. Una nota registra autor tipado, y crear sin él es imposible.
2. El HTML del contenido se sanitiza al escribir y se escapa al renderizar — probado con un fixture de
   inyección.
3. Editar conserva la versión previa y ambas son legibles.
4. El borrado lógico oculta la nota y la deja recuperable; la supresión del contacto la elimina por
   completo, incluido su historial.
5. La búsqueda devuelve solo las notas del propio tenant, y solo las que quien lee puede ver.
6. El número de notas fijadas está acotado por contacto.
7. Toda mutación escribe una fila de auditoría.
8. **Negativo:** ninguna nota referencia un contacto de otro tenant, y ninguna es alcanzable sin
   `crm.notes.read`.

## Ejecución

Un solo slice, comando síncrono. La búsqueda usa `tsvector` de Postgres acotado por tenant.

## Preguntas abiertas

| # | Pregunta                                                                                                                              | Decide | Para             |
| - | ------------------------------------------------------------------------------------------------------------------------------------- | ------ | ---------------- |
| 1 | ¿Una nota se puede editar, o solo agregarle? Editar es más amable; append-only es más defendible en una disputa. (pregunta 1 del PRD) | Daniel | antes de aprobar |
| 2 | ¿Largo máximo de una nota?                                                                                                            | Daniel | antes de aprobar |

## Changelog

| Versión | Fecha      | Cambio           | Por qué | Autor                  |
| ------- | ---------- | ---------------- | ------- | ---------------------- |
| 0.1.0   | 2026-08-17 | Borrador inicial | —       | daniel + claude-opus-5 |

## Registro de entrega

*Aún no implementado.*
