feat: conectar Tablero CDC a Supabase empresarial
This commit is contained in:
+2
-2
@@ -1,3 +1,3 @@
|
|||||||
VITE_SUPABASE_URL="https://ecngyelpabyqbdxhftdy.supabase.co"
|
VITE_SUPABASE_URL="https://dbit.digitalcompass.agency"
|
||||||
VITE_SUPABASE_ANON_KEY="TU_ANON_PUBLIC_KEY"
|
VITE_SUPABASE_ANON_KEY="TU_ANON_PUBLIC_KEY"
|
||||||
VITE_WEBHOOK_URL=""
|
VITE_WEBHOOK_URL="https://agenteit.digitalcompass.agency/webhook/tablero-cdc-sync-proyecto"
|
||||||
|
|||||||
@@ -24,7 +24,7 @@ Esta versión incluye:
|
|||||||
- Paginación local en las vistas de Todos, Activos y Cerrados con 12 proyectos por página.
|
- Paginación local en las vistas de Todos, Activos y Cerrados con 12 proyectos por página.
|
||||||
- Persistencia de proyectos en Supabase.
|
- Persistencia de proyectos en Supabase.
|
||||||
- Sincronización multiusuario con Supabase Realtime.
|
- Sincronización multiusuario con Supabase Realtime.
|
||||||
- Desplegables dinámicos desde Supabase `app_lists`, con fallback local mientras se termina la sincronización del Sheet.
|
- Desplegables dinámicos desde Supabase `tablero_cdc_app_lists`, con fallback local mientras se termina la sincronización del Sheet.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
@@ -50,7 +50,7 @@ Esta versión incluye:
|
|||||||
|
|
||||||
Las siguientes funcionalidades se implementarán en próximas fases:
|
Las siguientes funcionalidades se implementarán en próximas fases:
|
||||||
|
|
||||||
1. Sincronización automática de la hoja `listas` del Google Sheet original hacia Supabase `app_lists`.
|
1. Sincronización automática de la hoja `listas` del Google Sheet original hacia Supabase `tablero_cdc_app_lists`.
|
||||||
2. Escritura automática de datos en el Sheet original mediante n8n u otra integración definida.
|
2. Escritura automática de datos en el Sheet original mediante n8n u otra integración definida.
|
||||||
3. Agregar una nueva columna final para enlace de brief en el Sheet original.
|
3. Agregar una nueva columna final para enlace de brief en el Sheet original.
|
||||||
4. Reglas de seguridad definitivas con Supabase RLS.
|
4. Reglas de seguridad definitivas con Supabase RLS.
|
||||||
@@ -173,22 +173,22 @@ Desarrollo y soporte técnico: **Isaac Aracena**.
|
|||||||
|
|
||||||
Esta versión ya no depende de `localStorage` para los proyectos. El tablero lee y guarda la información en Supabase usando las tablas:
|
Esta versión ya no depende de `localStorage` para los proyectos. El tablero lee y guarda la información en Supabase usando las tablas:
|
||||||
|
|
||||||
- `projects`
|
- `tablero_cdc_projects`
|
||||||
- `project_links`
|
- `tablero_cdc_project_links`
|
||||||
- `profiles`
|
- `tablero_cdc_profiles`
|
||||||
- `app_lists`
|
- `tablero_cdc_app_lists`
|
||||||
|
|
||||||
Notas importantes:
|
Notas importantes:
|
||||||
|
|
||||||
- Los proyectos abiertos se guardan en base de datos con `status = 'Activo'`, pero en la interfaz se muestran como proyectos sin estatus para mantener el comportamiento visual original.
|
- Los proyectos abiertos se guardan en base de datos con `status = 'Activo'`, pero en la interfaz se muestran como proyectos sin estatus para mantener el comportamiento visual original.
|
||||||
- Los usuarios normales no ven ni actualizan el monto interno desde la app.
|
- Los usuarios normales no ven ni actualizan el monto interno desde la app.
|
||||||
- Solo Gerardo Marrero puede modificar estatus, monto interno y eliminar proyectos desde la interfaz.
|
- Solo Gerardo Marrero puede modificar estatus, monto interno y eliminar proyectos desde la interfaz.
|
||||||
- Los links de propuestas y artes finales se guardan en `project_links`.
|
- Los links de propuestas y artes finales se guardan en `tablero_cdc_project_links`.
|
||||||
- Los desplegables se leen desde `app_lists`. Si una categoría todavía no existe en Supabase, la app usa una lista local de respaldo para no bloquear el formulario.
|
- Los desplegables se leen desde `tablero_cdc_app_lists`. Si una categoría todavía no existe en Supabase, la app usa una lista local de respaldo para no bloquear el formulario.
|
||||||
|
|
||||||
## Supabase Realtime
|
## Supabase Realtime
|
||||||
|
|
||||||
Para que la sincronización multiusuario funcione, habilita Realtime para las tablas `projects`, `project_links` y `app_lists` ejecutando en Supabase SQL Editor el archivo:
|
Para que la sincronización multiusuario funcione, habilita Realtime para las tablas `tablero_cdc_projects`, `tablero_cdc_project_links` y `tablero_cdc_app_lists` ejecutando en Supabase SQL Editor el archivo:
|
||||||
|
|
||||||
```text
|
```text
|
||||||
supabase_realtime_projects.sql
|
supabase_realtime_projects.sql
|
||||||
@@ -199,7 +199,7 @@ Luego abre la app en dos pestañas o dos navegadores: al crear, editar o elimina
|
|||||||
|
|
||||||
## Desplegables dinámicos
|
## Desplegables dinámicos
|
||||||
|
|
||||||
La app intenta cargar estos desplegables desde `public.app_lists`:
|
La app intenta cargar estos desplegables desde `public.tablero_cdc_app_lists`:
|
||||||
|
|
||||||
- `client` / `cliente` / `clientes`
|
- `client` / `cliente` / `clientes`
|
||||||
- `brand` / `marca` / `marcas`
|
- `brand` / `marca` / `marcas`
|
||||||
@@ -207,4 +207,4 @@ La app intenta cargar estos desplegables desde `public.app_lists`:
|
|||||||
- `status` / `estatus`
|
- `status` / `estatus`
|
||||||
- `country_manager` / `cm` / `bu_cm` para mapear BU → Country Manager
|
- `country_manager` / `cm` / `bu_cm` para mapear BU → Country Manager
|
||||||
|
|
||||||
Si una categoría aún no existe en Supabase, se usa el respaldo local del frontend. Más adelante n8n sincronizará la hoja `listas` del Sheet original hacia `app_lists`.
|
Si una categoría aún no existe en Supabase, se usa el respaldo local del frontend. Más adelante n8n sincronizará la hoja `listas` del Sheet original hacia `tablero_cdc_app_lists`.
|
||||||
|
|||||||
+37
-37
File diff suppressed because one or more lines are too long
Vendored
+1
-1
@@ -7,7 +7,7 @@
|
|||||||
<meta name="description" content="Herramienta interna del Departamento de Diseño Creativo." />
|
<meta name="description" content="Herramienta interna del Departamento de Diseño Creativo." />
|
||||||
<meta name="author" content="CDC" />
|
<meta name="author" content="CDC" />
|
||||||
<link rel="icon" type="image/svg+xml" href="/tablero-cdc/favicon.svg" />
|
<link rel="icon" type="image/svg+xml" href="/tablero-cdc/favicon.svg" />
|
||||||
<script type="module" crossorigin src="/tablero-cdc/assets/index-BYKs5Exj.js"></script>
|
<script type="module" crossorigin src="/tablero-cdc/assets/index-BEinrcZb.js"></script>
|
||||||
<link rel="stylesheet" crossorigin href="/tablero-cdc/assets/index-DjXLQf1G.css">
|
<link rel="stylesheet" crossorigin href="/tablero-cdc/assets/index-DjXLQf1G.css">
|
||||||
</head>
|
</head>
|
||||||
<body>
|
<body>
|
||||||
|
|||||||
@@ -108,7 +108,7 @@ export function ProjectDialog({ open, onOpenChange, project }: Props) {
|
|||||||
return match?.[1] ?? "";
|
return match?.[1] ?? "";
|
||||||
};
|
};
|
||||||
|
|
||||||
// BU → Country Manager automático desde Supabase app_lists, con fallback local.
|
// BU → Country Manager automático desde Supabase tablero_cdc_app_lists, con fallback local.
|
||||||
const onBU = (bu: string) => {
|
const onBU = (bu: string) => {
|
||||||
const cleanBu = canonicalOptionLabel(bu);
|
const cleanBu = canonicalOptionLabel(bu);
|
||||||
setForm((f) => ({ ...f, bu: cleanBu, cm: getCountryManager(cleanBu) }));
|
setForm((f) => ({ ...f, bu: cleanBu, cm: getCountryManager(cleanBu) }));
|
||||||
|
|||||||
+3
-3
@@ -137,7 +137,7 @@ export async function loadAppLists() {
|
|||||||
|
|
||||||
try {
|
try {
|
||||||
const { data, error } = await supabase
|
const { data, error } = await supabase
|
||||||
.from("app_lists")
|
.from("tablero_cdc_app_lists")
|
||||||
.select("category, value, label, sort_order, is_active")
|
.select("category, value, label, sort_order, is_active")
|
||||||
.eq("is_active", true)
|
.eq("is_active", true)
|
||||||
.order("category", { ascending: true })
|
.order("category", { ascending: true })
|
||||||
@@ -180,12 +180,12 @@ function startAppListsRealtime() {
|
|||||||
|
|
||||||
appListsRealtimeChannel = supabase
|
appListsRealtimeChannel = supabase
|
||||||
.channel("tablero-cdc-app-lists")
|
.channel("tablero-cdc-app-lists")
|
||||||
.on("postgres_changes", { event: "*", schema: "public", table: "app_lists" }, () =>
|
.on("postgres_changes", { event: "*", schema: "public", table: "tablero_cdc_app_lists" }, () =>
|
||||||
scheduleRealtimeRefresh(),
|
scheduleRealtimeRefresh(),
|
||||||
)
|
)
|
||||||
.subscribe((status) => {
|
.subscribe((status) => {
|
||||||
if (status === "CHANNEL_ERROR" || status === "TIMED_OUT") {
|
if (status === "CHANNEL_ERROR" || status === "TIMED_OUT") {
|
||||||
console.warn("Supabase Realtime no pudo suscribirse a app_lists:", status);
|
console.warn("Supabase Realtime no pudo suscribirse a tablero_cdc_app_lists:", status);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
+8
-8
@@ -294,12 +294,12 @@ function startProjectsRealtime() {
|
|||||||
.channel("tablero-cdc-projects")
|
.channel("tablero-cdc-projects")
|
||||||
.on(
|
.on(
|
||||||
"postgres_changes",
|
"postgres_changes",
|
||||||
{ event: "*", schema: "public", table: "projects" },
|
{ event: "*", schema: "public", table: "tablero_cdc_projects" },
|
||||||
() => scheduleRealtimeRefresh(),
|
() => scheduleRealtimeRefresh(),
|
||||||
)
|
)
|
||||||
.on(
|
.on(
|
||||||
"postgres_changes",
|
"postgres_changes",
|
||||||
{ event: "*", schema: "public", table: "project_links" },
|
{ event: "*", schema: "public", table: "tablero_cdc_project_links" },
|
||||||
() => scheduleRealtimeRefresh(),
|
() => scheduleRealtimeRefresh(),
|
||||||
)
|
)
|
||||||
.subscribe((status) => {
|
.subscribe((status) => {
|
||||||
@@ -323,7 +323,7 @@ function stopProjectsRealtime() {
|
|||||||
|
|
||||||
async function replaceProjectLinks(projectId: string, project: Pick<Project, "propuestaLinks" | "afLinks">) {
|
async function replaceProjectLinks(projectId: string, project: Pick<Project, "propuestaLinks" | "afLinks">) {
|
||||||
const { error: deleteError } = await supabase
|
const { error: deleteError } = await supabase
|
||||||
.from("project_links")
|
.from("tablero_cdc_project_links")
|
||||||
.delete()
|
.delete()
|
||||||
.eq("project_id", projectId);
|
.eq("project_id", projectId);
|
||||||
|
|
||||||
@@ -333,7 +333,7 @@ async function replaceProjectLinks(projectId: string, project: Pick<Project, "pr
|
|||||||
|
|
||||||
if (linkRows.length === 0) return;
|
if (linkRows.length === 0) return;
|
||||||
|
|
||||||
const { error: insertError } = await supabase.from("project_links").insert(linkRows);
|
const { error: insertError } = await supabase.from("tablero_cdc_project_links").insert(linkRows);
|
||||||
|
|
||||||
if (insertError) throw insertError;
|
if (insertError) throw insertError;
|
||||||
}
|
}
|
||||||
@@ -359,7 +359,7 @@ async function loadProjects() {
|
|||||||
brief_link,
|
brief_link,
|
||||||
created_at,
|
created_at,
|
||||||
extra_data,
|
extra_data,
|
||||||
project_links (
|
project_links:tablero_cdc_project_links (
|
||||||
id,
|
id,
|
||||||
link_type,
|
link_type,
|
||||||
url,
|
url,
|
||||||
@@ -368,7 +368,7 @@ async function loadProjects() {
|
|||||||
`;
|
`;
|
||||||
|
|
||||||
const { data, error } = await supabase
|
const { data, error } = await supabase
|
||||||
.from("projects")
|
.from("tablero_cdc_projects")
|
||||||
.select(selectColumns)
|
.select(selectColumns)
|
||||||
.order("created_at", { ascending: false });
|
.order("created_at", { ascending: false });
|
||||||
|
|
||||||
@@ -399,7 +399,7 @@ async function addProject(project: Project) {
|
|||||||
};
|
};
|
||||||
|
|
||||||
const { data, error } = await supabase
|
const { data, error } = await supabase
|
||||||
.from("projects")
|
.from("tablero_cdc_projects")
|
||||||
.insert(insertPayload)
|
.insert(insertPayload)
|
||||||
.select("id")
|
.select("id")
|
||||||
.single();
|
.single();
|
||||||
@@ -447,7 +447,7 @@ async function updateProject(id: string, patch: Partial<Project>) {
|
|||||||
updated_by: userId,
|
updated_by: userId,
|
||||||
};
|
};
|
||||||
|
|
||||||
const { error } = await supabase.from("projects").update(updatePayload).eq("id", id);
|
const { error } = await supabase.from("tablero_cdc_projects").update(updatePayload).eq("id", id);
|
||||||
|
|
||||||
if (error) {
|
if (error) {
|
||||||
setError(error.message);
|
setError(error.message);
|
||||||
|
|||||||
@@ -11,9 +11,9 @@ begin
|
|||||||
from pg_publication_tables
|
from pg_publication_tables
|
||||||
where pubname = 'supabase_realtime'
|
where pubname = 'supabase_realtime'
|
||||||
and schemaname = 'public'
|
and schemaname = 'public'
|
||||||
and tablename = 'projects'
|
and tablename = 'tablero_cdc_projects'
|
||||||
) then
|
) then
|
||||||
alter publication supabase_realtime add table public.projects;
|
alter publication supabase_realtime add table public.tablero_cdc_projects;
|
||||||
end if;
|
end if;
|
||||||
|
|
||||||
if not exists (
|
if not exists (
|
||||||
@@ -21,9 +21,9 @@ begin
|
|||||||
from pg_publication_tables
|
from pg_publication_tables
|
||||||
where pubname = 'supabase_realtime'
|
where pubname = 'supabase_realtime'
|
||||||
and schemaname = 'public'
|
and schemaname = 'public'
|
||||||
and tablename = 'project_links'
|
and tablename = 'tablero_cdc_project_links'
|
||||||
) then
|
) then
|
||||||
alter publication supabase_realtime add table public.project_links;
|
alter publication supabase_realtime add table public.tablero_cdc_project_links;
|
||||||
end if;
|
end if;
|
||||||
|
|
||||||
if not exists (
|
if not exists (
|
||||||
@@ -31,8 +31,8 @@ begin
|
|||||||
from pg_publication_tables
|
from pg_publication_tables
|
||||||
where pubname = 'supabase_realtime'
|
where pubname = 'supabase_realtime'
|
||||||
and schemaname = 'public'
|
and schemaname = 'public'
|
||||||
and tablename = 'app_lists'
|
and tablename = 'tablero_cdc_app_lists'
|
||||||
) then
|
) then
|
||||||
alter publication supabase_realtime add table public.app_lists;
|
alter publication supabase_realtime add table public.tablero_cdc_app_lists;
|
||||||
end if;
|
end if;
|
||||||
end $$;
|
end $$;
|
||||||
|
|||||||
Reference in New Issue
Block a user