> ## 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.

# Catálogos de plataforma

> Cuando esto se entregue, toda otra tabla de la plataforma tendrá los catálogos que necesita referenciar, y existirán los union types de TypeScript contra los que compila el monorepo.

> Traducción. Autoritativo: [`fs-core-0001-platform-catalogs.md`](/modules/core/features/fs-core-0001-platform-catalogs).

## Contexto

Tres conjuntos cerrados son genuinamente transversales: monedas, tipos de documento y canales de
notificación. Todo lo demás pertenece a su propio schema (DEC-B3).

Esta es la primera entrega de toda la suite por una razón mecánica. Las filas `is_system` de un
catálogo paramétrico son la única fuente que genera los union types de Zod y TypeScript en
`packages/core`; nada que referencie un `currency_code` o un `channel_code` puede compilar antes de
que existan.

DEC-A2 hace de `national_id_types` el más grande de los tres y el único con lógica real: todos los
países de Sudamérica, cada uno con sus formatos, normalizadores y algoritmos de dígito verificador,
aplicables a personas y a empresas.

## Alcance *(normativo)*

* `core.countries`: ISO 3166-1 alpha-2, el ancla de los tipos de documento.
* `core.currencies`: ISO 4217 con `minor_unit` — el exponente que hace correcta la aritmética en
  unidades menores.
* `core.national_id_types`: por país y por tipo de contacto, con regex de formato, id de
  normalizador e id de algoritmo de DV.
* `core.notification_channels`: `email`, `in_app`, `webhook`, `push`, `sms`, `whatsapp`,
  `live_activity` — la fila existe desde el día 1 aunque el adaptador llegue después (DEC-E1).
* El pipeline semilla→union: las filas `is_system` producen los esquemas Zod y los tipos TS.
* La caché de catálogos: memoria de proceso más Redis, para que los caminos calientes nunca unan.

## Fuera de alcance *(normativo)*

* Validar un documento. Esto entrega el *catálogo*; aplicar su regex y su dígito verificador a un
  valor es FS-CORE-0002.
* Enviar por un canal. La fila es una declaración de que el canal existe.
* Tipos de cambio. F1 es mono-moneda por tenant (DEC-B6).

## Comportamiento *(normativo)*

1. Todo catálogo sigue el estándar paramétrico exactamente: `code` TEXT PK, `label`, `is_system`,
   `is_active`, `sort_order`, `metadata` JSONB. Sin excepciones y sin forma extra.
2. Estos tres **no son extensibles por tenant**. Un tenant no puede inventar una moneda ni un canal:
   ambos gobiernan lógica del sistema (DEC-B4).
3. Las filas `is_system` se siembran en migraciones y son **inmutables por API**. Una semilla cambia
   por migración, nunca por un endpoint.
4. Las semillas generan los unions TS/Zod en build. Una divergencia entre los códigos de la base y
   el union generado **falla CI** (TS-005) — ese chequeo es lo que mantiene honesto al catálogo.
5. `currencies.minor_unit` es obligatorio. CLP es 0, USD es 2, y la aritmética de dinero está mal
   sin él — vale decirlo porque "montos en centavos" asume silenciosamente 2.
6. Un código **nunca se borra ni se reutiliza**. Retirarlo es `is_active = false`, porque filas
   históricas todavía lo referencian.
7. Los caminos calientes leen de caché. PROHIBIDO: unir un catálogo dentro de un request de clase
   Runtime.
8. Las filas de `national_id_types` declaran a qué `contact_kind` aplican — un RUT aplica a persona
   y a empresa en Chile, un CPF solo a persona en Brasil.

## Datos *(normativo)*

| Tabla                        | Invariantes clave                                                                                                                                                 |
| ---------------------------- | ----------------------------------------------------------------------------------------------------------------------------------------------------------------- |
| `core.countries`             | `code` CHAR(2) PK (ISO 3166-1 alpha-2); `name`; `is_active`                                                                                                       |
| `core.currencies`            | `code` CHAR(3) PK (ISO 4217); `minor_unit` SMALLINT NOT NULL; `symbol`; columnas paramétricas estándar                                                            |
| `core.national_id_types`     | `code` TEXT PK (ej. `CL_RUT`, `BR_CPF`); FK `country_code`; `applies_to` person\|company\|both; `format_regex`; `normalizer_id`; `check_digit_algorithm` nullable |
| `core.notification_channels` | `code` TEXT PK; `label`; `is_active`; `available_from_phase`                                                                                                      |

Los `national_id_types` sembrados cubren todo país sudamericano según DEC-A2: CL (RUT) · AR (DNI,
CUIT, CUIL) · BR (CPF, CNPJ) · UY (CI, RUT) · PY (CI, RUC) · BO (CI, NIT) · PE (DNI, RUC, CE) ·
EC (CI, RUC) · CO (CC, CE, NIT) · VE (CI, RIF) · GY y SR genéricos — más `PASSPORT` y `FOREIGN_ID`.

Ninguna tabla aquí se particiona; todas son pequeñas, regulares y cacheadas.

## API *(normativo)*

| Endpoint                          | Clase      | Permiso              | Presupuesto |
| --------------------------------- | ---------- | -------------------- | ----------- |
| `GET /v1/core/catalogs/{catalog}` | Management | `core.catalogs.read` | p95 \<1 s   |

Solo lectura. No existe endpoint de escritura para un catálogo de sistema, y esa ausencia es la
feature.

## Eventos *(normativo)*

Ninguno. Un catálogo es dato de referencia; un cambio de semilla es una migración.

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

1. Las migraciones siembran cada catálogo de forma idempotente: correrlas dos veces no produce
   duplicados ni errores.
2. El union TS generado para `notification_channels` contiene exactamente los códigos `is_system`
   sembrados, y `packages/core` compila contra él.
3. La divergencia de semillas se detecta: quitar un código de la semilla dejándolo en la base falla
   el chequeo de CI con un mensaje que lo nombra.
4. `minor_unit` es correcto para una moneda con 0 (CLP) y otra con 2 (USD).
5. Todo `national_id_type` sembrado con algoritmo de DV lo tiene implementado — una fila apuntando a
   una implementación inexistente falla CI.
6. Las lecturas de catálogo se sirven de caché: un request Runtime hace cero uniones de catálogo.
7. **Negativo:** ninguna ruta de API modifica una fila `is_system`.

## Ejecución

Un solo slice, comando síncrono. Se entrega como la primera parte de TS-001. Nada más del módulo se
puede escribir antes de que esto se mergee.

## Preguntas abiertas

| # | Pregunta                                                                           | Decide | Para             |
| - | ---------------------------------------------------------------------------------- | ------ | ---------------- |
| 1 | ¿Sembramos Centroamérica y México ahora, o esperamos a un tenant que los necesite? | 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.*
